new screen space lens flare effect
- this PR also improves bloom slightly Note: for now this is not available on WebGL due to complications in the bloom pass.
This commit is contained in:
committed by
Mathias Agopian
parent
812f0d577b
commit
c0fe2d1c09
@@ -5,6 +5,8 @@ A new header is inserted each time a *tag* is created.
|
||||
|
||||
## Next release (main branch)
|
||||
|
||||
- engine: new screen-space lens flare
|
||||
|
||||
## v1.9.24
|
||||
|
||||
## v1.9.23
|
||||
|
||||
@@ -253,7 +253,10 @@ extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nSetBloomOptions(JNIEnv*, jclass,
|
||||
jlong nativeView, jlong nativeTexture,
|
||||
jfloat dirtStrength, jfloat strength, jint resolution, jfloat anamorphism, jint levels,
|
||||
jint blendMode, jboolean threshold, jboolean enabled, jfloat highlight) {
|
||||
jint blendMode, jboolean threshold, jboolean enabled, jfloat highlight,
|
||||
jboolean lensFlare, jboolean starburst, jfloat chromaticAberration, jint ghostCount,
|
||||
jfloat ghostSpacing, jfloat ghostThreshold, jfloat haloThickness, jfloat haloRadius,
|
||||
jfloat haloThreshold) {
|
||||
View* view = (View*) nativeView;
|
||||
Texture* dirt = (Texture*) nativeTexture;
|
||||
View::BloomOptions options = {
|
||||
@@ -266,7 +269,16 @@ Java_com_google_android_filament_View_nSetBloomOptions(JNIEnv*, jclass,
|
||||
.blendMode = (View::BloomOptions::BlendMode)blendMode,
|
||||
.threshold = (bool)threshold,
|
||||
.enabled = (bool)enabled,
|
||||
.highlight = highlight
|
||||
.highlight = highlight,
|
||||
.lensFlare = (bool)lensFlare,
|
||||
.starburst = (bool)starburst,
|
||||
.chromaticAberration = chromaticAberration,
|
||||
.ghostCount = (uint8_t)ghostCount,
|
||||
.ghostSpacing = ghostSpacing,
|
||||
.ghostThreshold = ghostThreshold,
|
||||
.haloThickness = haloThickness,
|
||||
.haloRadius = haloRadius,
|
||||
.haloThreshold = haloThreshold
|
||||
};
|
||||
view->setBloomOptions(options);
|
||||
}
|
||||
|
||||
@@ -363,6 +363,52 @@ public class View {
|
||||
* minimum value is 10.0.
|
||||
*/
|
||||
public float highlight = 1000.0f;
|
||||
|
||||
|
||||
/**
|
||||
* enable screen-space lens flare
|
||||
*/
|
||||
public boolean lensFlare = false;
|
||||
|
||||
/**
|
||||
* enable starburst effect on lens flare
|
||||
*/
|
||||
public boolean starburst = true;
|
||||
|
||||
/**
|
||||
* amount of chromatic aberration
|
||||
*/
|
||||
public float chromaticAberration = 0.005f;
|
||||
|
||||
/**
|
||||
* number of flare "ghosts"
|
||||
*/
|
||||
public int ghostCount = 4;
|
||||
|
||||
/**
|
||||
* spacing of the ghost in screen units [0, 1[
|
||||
*/
|
||||
public float ghostSpacing = 0.6f;
|
||||
|
||||
/**
|
||||
* hdr threshold for the ghosts
|
||||
*/
|
||||
public float ghostThreshold = 10.0f;
|
||||
|
||||
/**
|
||||
* thickness of halo in vertical screen units, 0 to disable
|
||||
*/
|
||||
public float haloThickness = 0.1f;
|
||||
|
||||
/**
|
||||
* radius of halo in vertical screen units [0, 0.5]
|
||||
*/
|
||||
public float haloRadius = 0.4f;
|
||||
|
||||
/**
|
||||
* hdr threshold for the halo
|
||||
*/
|
||||
public float haloThreshold = 10.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1357,7 +1403,10 @@ public class View {
|
||||
nSetBloomOptions(getNativeObject(), options.dirt != null ? options.dirt.getNativeObject() : 0,
|
||||
options.dirtStrength, options.strength, options.resolution,
|
||||
options.anamorphism, options.levels, options.blendingMode.ordinal(),
|
||||
options.threshold, options.enabled, options.highlight);
|
||||
options.threshold, options.enabled, options.highlight,
|
||||
options.lensFlare, options.starburst, options.chromaticAberration,
|
||||
options.ghostCount, options.ghostSpacing, options.ghostThreshold,
|
||||
options.haloThickness, options.haloRadius, options.haloThreshold);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1501,7 +1550,8 @@ public class View {
|
||||
private static native int nGetAmbientOcclusion(long nativeView);
|
||||
private static native void nSetAmbientOcclusionOptions(long nativeView, float radius, float bias, float power, float resolution, float intensity, int quality, int lowPassFilter, int upsampling, boolean enabled, float minHorizonAngleRad);
|
||||
private static native void nSetSSCTOptions(long nativeView, float ssctLightConeRad, float ssctStartTraceDistance, float ssctContactDistanceMax, float ssctIntensity, float v, float v1, float v2, float ssctDepthBias, float ssctDepthSlopeBias, int ssctSampleCount, int ssctRayCount, boolean ssctEnabled);
|
||||
private static native void nSetBloomOptions(long nativeView, long dirtNativeObject, float dirtStrength, float strength, int resolution, float anamorphism, int levels, int blendMode, boolean threshold, boolean enabled, float highlight);
|
||||
private static native void nSetBloomOptions(long nativeView, long dirtNativeObject, float dirtStrength, float strength, int resolution, float anamorphism, int levels, int blendMode, boolean threshold, boolean enabled, float highlight,
|
||||
boolean lensFlare, boolean starburst, float chromaticAberration, int ghostCount, float ghostSpacing, float ghostThreshold, float haloThickness, float haloRadius, float haloThreshold);
|
||||
private static native void nSetFogOptions(long nativeView, float distance, float maximumOpacity, float height, float heightFalloff, float v, float v1, float v2, float density, float inScatteringStart, float inScatteringSize, boolean fogColorFromIbl, boolean enabled);
|
||||
private static native void nSetBlendMode(long nativeView, int blendMode);
|
||||
private static native void nSetDepthOfFieldOptions(long nativeView, float focusDistance, float cocScale, float maxApertureDiameter, boolean enabled, int filter,
|
||||
|
||||
@@ -170,6 +170,7 @@ set(MATERIAL_SRCS
|
||||
src/materials/dof/dofDilate.mat
|
||||
src/materials/dof/dofMipmap.mat
|
||||
src/materials/dof/dofMedian.mat
|
||||
src/materials/flare/flare.mat
|
||||
src/materials/blitLow.mat
|
||||
src/materials/blitMedium.mat
|
||||
src/materials/blitHigh.mat
|
||||
@@ -255,7 +256,6 @@ add_custom_command(
|
||||
OUTPUT "${MATERIAL_DIR}/colorGrading.filamat"
|
||||
DEPENDS ../shaders/src/dithering.fs
|
||||
DEPENDS ../shaders/src/vignette.fs
|
||||
DEPENDS ../shaders/src/bloom.fs
|
||||
APPEND
|
||||
)
|
||||
|
||||
|
||||
@@ -159,6 +159,16 @@ public:
|
||||
bool threshold = true; //!< whether to threshold the source
|
||||
bool enabled = false; //!< enable or disable bloom
|
||||
float highlight = 1000.0f; //!< limit highlights to this value before bloom [10, +inf]
|
||||
|
||||
bool lensFlare = false; //!< enable screen-space lens flare
|
||||
bool starburst = true; //!< enable starburst effect on lens flare
|
||||
float chromaticAberration = 0.005f; //!< amount of chromatic aberration
|
||||
uint8_t ghostCount = 4; //!< number of flare "ghosts"
|
||||
float ghostSpacing = 0.6f; //!< spacing of the ghost in screen units [0, 1[
|
||||
float ghostThreshold = 10.0f; //!< hdr threshold for the ghosts
|
||||
float haloThickness = 0.1f; //!< thickness of halo in vertical screen units, 0 to disable
|
||||
float haloRadius = 0.4f; //!< radius of halo in vertical screen units [0, 0.5]
|
||||
float haloThreshold = 10.0f; //!< hdr threshold for the halo
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -209,6 +209,7 @@ static const MaterialInfo sMaterialList[] = {
|
||||
{ "separableGaussianBlur", MATERIAL(SEPARABLEGAUSSIANBLUR) },
|
||||
{ "bloomDownsample", MATERIAL(BLOOMDOWNSAMPLE) },
|
||||
{ "bloomUpsample", MATERIAL(BLOOMUPSAMPLE) },
|
||||
{ "flare", MATERIAL(FLARE) },
|
||||
{ "blitLow", MATERIAL(BLITLOW) },
|
||||
{ "blitMedium", MATERIAL(BLITMEDIUM) },
|
||||
{ "blitHigh", MATERIAL(BLITHIGH) },
|
||||
@@ -254,15 +255,25 @@ void PostProcessManager::init() noexcept {
|
||||
mDummyZeroTexture = driver.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, 1, 1, 1, TextureUsage::DEFAULT);
|
||||
|
||||
mStarburstTexture = driver.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::R8, 1, 256, 1, 1, TextureUsage::DEFAULT);
|
||||
|
||||
PixelBufferDescriptor dataOne(driver.allocate(4), 4, PixelDataFormat::RGBA, PixelDataType::UBYTE);
|
||||
PixelBufferDescriptor dataOneArray(driver.allocate(4), 4, PixelDataFormat::RGBA, PixelDataType::UBYTE);
|
||||
PixelBufferDescriptor dataZero(driver.allocate(4), 4, PixelDataFormat::RGBA, PixelDataType::UBYTE);
|
||||
PixelBufferDescriptor dataStarburst(driver.allocate(256), 256, PixelDataFormat::R, PixelDataType::UBYTE);
|
||||
*static_cast<uint32_t *>(dataOne.buffer) = 0xFFFFFFFF;
|
||||
*static_cast<uint32_t *>(dataOneArray.buffer) = 0xFFFFFFFF;
|
||||
*static_cast<uint32_t *>(dataZero.buffer) = 0;
|
||||
std::generate_n((uint8_t*)dataStarburst.buffer, 256,
|
||||
[&dist = mUniformDistribution, &gen = mEngine.getRandomEngine()]() {
|
||||
float r = 0.5 + 0.5 * dist(gen);
|
||||
return uint8_t(r * 255.0f);
|
||||
});
|
||||
driver.update2DImage(mDummyOneTexture, 0, 0, 0, 1, 1, std::move(dataOne));
|
||||
driver.update3DImage(mDummyOneTextureArray, 0, 0, 0, 0, 1, 1, 1, std::move(dataOneArray));
|
||||
driver.update2DImage(mDummyZeroTexture, 0, 0, 0, 1, 1, std::move(dataZero));
|
||||
driver.update2DImage(mStarburstTexture, 0, 0, 0, 256, 1, std::move(dataStarburst));
|
||||
}
|
||||
|
||||
void PostProcessManager::terminate(DriverApi& driver) noexcept {
|
||||
@@ -270,6 +281,7 @@ void PostProcessManager::terminate(DriverApi& driver) noexcept {
|
||||
driver.destroyTexture(mDummyOneTexture);
|
||||
driver.destroyTexture(mDummyOneTextureArray);
|
||||
driver.destroyTexture(mDummyZeroTexture);
|
||||
driver.destroyTexture(mStarburstTexture);
|
||||
auto first = mMaterialRegistry.begin();
|
||||
auto last = mMaterialRegistry.end();
|
||||
while (first != last) {
|
||||
@@ -1343,6 +1355,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::bloom(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input, TextureFormat outFormat,
|
||||
View::BloomOptions& bloomOptions, float2 scale) noexcept {
|
||||
|
||||
FrameGraphId<FrameGraphTexture> bloom = bloomPass(fg, input,
|
||||
outFormat, bloomOptions, scale);
|
||||
|
||||
@@ -1467,10 +1480,56 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::bloomPass(FrameGraph& fg,
|
||||
driver.setMinMaxLevels(hwOut, i,
|
||||
i); // safe because we're using LINEAR_MIPMAP_NEAREST
|
||||
}
|
||||
driver.setMinMaxLevels(hwOut, 0, bloomOptions.levels - 1);
|
||||
});
|
||||
|
||||
input = bloomDownsamplePass->out;
|
||||
|
||||
// flare pass
|
||||
auto& flarePass = fg.addPass<BloomPassData>("Flare",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
data.in = builder.sample(input);
|
||||
data.out = builder.createTexture("Flare Texture", {
|
||||
.width = width / 2,
|
||||
.height = height / 2,
|
||||
.format = outFormat
|
||||
});
|
||||
data.out = builder.declareRenderPass(data.out);
|
||||
},
|
||||
[=](FrameGraphResources const& resources,
|
||||
auto const& data, DriverApi& driver) {
|
||||
auto in = resources.getTexture(data.in);
|
||||
auto out = resources.getRenderPassInfo(0);
|
||||
const float aspectRatio = float(width) / height;
|
||||
|
||||
auto const& material = getPostProcessMaterial("flare");
|
||||
FMaterialInstance* mi = material.getMaterialInstance();
|
||||
|
||||
mi->setParameter("color", in, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
|
||||
mi->setParameter("level", 1.0f); // adjust with resolution
|
||||
mi->setParameter("aspectRatio",
|
||||
float2{ aspectRatio, 1.0f / aspectRatio });
|
||||
mi->setParameter("threshold",
|
||||
float2{ bloomOptions.ghostThreshold, bloomOptions.haloThreshold });
|
||||
mi->setParameter("chromaticAberration",
|
||||
bloomOptions.chromaticAberration);
|
||||
mi->setParameter("ghostCount", (float)bloomOptions.ghostCount);
|
||||
mi->setParameter("ghostSpacing", bloomOptions.ghostSpacing);
|
||||
mi->setParameter("haloRadius", bloomOptions.haloRadius);
|
||||
mi->setParameter("haloThickness", bloomOptions.haloThickness);
|
||||
|
||||
commitAndRender(out, material, driver);
|
||||
});
|
||||
|
||||
auto flare = gaussianBlurPass(fg, flarePass->out, 0,
|
||||
{}, 0, false, 9);
|
||||
|
||||
fg.getBlackboard().put("flare", flare);
|
||||
|
||||
// upsample phase
|
||||
auto& bloomUpsamplePass = fg.addPass<BloomPassData>("Bloom Upsample",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
@@ -1521,6 +1580,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::bloomPass(FrameGraph& fg,
|
||||
|
||||
driver.setMinMaxLevels(hwIn, 0, bloomOptions.levels - 1);
|
||||
});
|
||||
|
||||
return bloomUpsamplePass->out;
|
||||
|
||||
} else { // !isWebGL
|
||||
@@ -1756,11 +1816,13 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
Blackboard& blackboard = fg.getBlackboard();
|
||||
|
||||
FrameGraphId<FrameGraphTexture> bloomDirt;
|
||||
FrameGraphId<FrameGraphTexture> bloomBlur = blackboard.get<FrameGraphTexture>("bloom");
|
||||
FrameGraphId<FrameGraphTexture> starburst;
|
||||
FrameGraphId<FrameGraphTexture> bloom = blackboard.get<FrameGraphTexture>("bloom");
|
||||
FrameGraphId<FrameGraphTexture> flare = blackboard.get<FrameGraphTexture>("flare");
|
||||
|
||||
float bloom = 0.0f;
|
||||
float bloomStrength = 0.0f;
|
||||
if (bloomOptions.enabled) {
|
||||
bloom = clamp(bloomOptions.strength, 0.0f, 1.0f);
|
||||
bloomStrength = clamp(bloomOptions.strength, 0.0f, 1.0f);
|
||||
if (bloomOptions.dirt) {
|
||||
FTexture* fdirt = upcast(bloomOptions.dirt);
|
||||
FrameGraphTexture frameGraphTexture{ .handle = fdirt->getHwHandle() };
|
||||
@@ -1770,13 +1832,22 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
.format = fdirt->getFormat()
|
||||
}, FrameGraphTexture::Usage::SAMPLEABLE, frameGraphTexture);
|
||||
}
|
||||
|
||||
if (bloomOptions.lensFlare && bloomOptions.starburst) {
|
||||
starburst = fg.import("starburst", {
|
||||
.width = 256, .height = 1, .format = TextureFormat::R8
|
||||
}, FrameGraphTexture::Usage::SAMPLEABLE,
|
||||
FrameGraphTexture{ .handle = mStarburstTexture });
|
||||
}
|
||||
}
|
||||
|
||||
struct PostProcessColorGrading {
|
||||
FrameGraphId<FrameGraphTexture> input;
|
||||
FrameGraphId<FrameGraphTexture> output;
|
||||
FrameGraphId<FrameGraphTexture> bloom;
|
||||
FrameGraphId<FrameGraphTexture> flare;
|
||||
FrameGraphId<FrameGraphTexture> dirt;
|
||||
FrameGraphId<FrameGraphTexture> starburst;
|
||||
};
|
||||
|
||||
auto& ppColorGrading = fg.addPass<PostProcessColorGrading>("colorGrading",
|
||||
@@ -1790,12 +1861,16 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
});
|
||||
data.output = builder.declareRenderPass(data.output);
|
||||
|
||||
if (bloomBlur) {
|
||||
data.bloom = builder.sample(bloomBlur);
|
||||
if (bloom) {
|
||||
data.bloom = builder.sample(bloom);
|
||||
}
|
||||
if (bloomDirt) {
|
||||
data.dirt = builder.sample(bloomDirt);
|
||||
}
|
||||
if (bloomOptions.lensFlare && flare) {
|
||||
data.flare = builder.sample(flare);
|
||||
data.starburst = builder.sample(starburst);
|
||||
}
|
||||
},
|
||||
[=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
|
||||
Handle<HwTexture> colorTexture = resources.getTexture(data.input);
|
||||
@@ -1803,9 +1878,15 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
Handle<HwTexture> bloomTexture =
|
||||
data.bloom ? resources.getTexture(data.bloom) : getZeroTexture();
|
||||
|
||||
Handle<HwTexture> flareTexture =
|
||||
data.flare ? resources.getTexture(data.flare) : getZeroTexture();
|
||||
|
||||
Handle<HwTexture> dirtTexture =
|
||||
data.dirt ? resources.getTexture(data.dirt) : getOneTexture();
|
||||
|
||||
Handle<HwTexture> starburstTexture =
|
||||
data.starburst ? resources.getTexture(data.starburst) : getOneTexture();
|
||||
|
||||
auto const& out = resources.getRenderPassInfo();
|
||||
|
||||
auto const& material = getPostProcessMaterial("colorGrading");
|
||||
@@ -1819,17 +1900,27 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR /* always read base level in shader */
|
||||
});
|
||||
mi->setParameter("flareBuffer", flareTexture, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR
|
||||
});
|
||||
mi->setParameter("dirtBuffer", dirtTexture, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR
|
||||
});
|
||||
mi->setParameter("starburstBuffer", starburstTexture, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR,
|
||||
.wrapS = SamplerWrapMode::REPEAT,
|
||||
.wrapT = SamplerWrapMode::REPEAT
|
||||
});
|
||||
|
||||
// Bloom params
|
||||
float4 bloomParameters{
|
||||
bloom / float(bloomOptions.levels),
|
||||
bloomStrength / float(bloomOptions.levels),
|
||||
1.0f,
|
||||
(bloomOptions.enabled && bloomOptions.dirt) ? bloomOptions.dirtStrength : 0.0f,
|
||||
0.0f
|
||||
bloomOptions.lensFlare ? bloomStrength : 0.0f
|
||||
};
|
||||
if (bloomOptions.blendMode == View::BloomOptions::BlendMode::INTERPOLATE) {
|
||||
bloomParameters.y = 1.0f - bloomParameters.x;
|
||||
|
||||
@@ -218,6 +218,7 @@ private:
|
||||
backend::Handle<backend::HwTexture> mDummyOneTexture;
|
||||
backend::Handle<backend::HwTexture> mDummyOneTextureArray;
|
||||
backend::Handle<backend::HwTexture> mDummyZeroTexture;
|
||||
backend::Handle<backend::HwTexture> mStarburstTexture;
|
||||
|
||||
size_t mSeparableGaussianBlurKernelStorageSize = 0;
|
||||
|
||||
|
||||
@@ -43,9 +43,11 @@ fragment {
|
||||
void dummy(){}
|
||||
|
||||
void threshold(inout vec3 c) {
|
||||
float l = max3(c);
|
||||
highp float f = l;
|
||||
c *= step(1.0, l) / (1.0 + f * materialParams.invHighlight);
|
||||
// threshold everything below 1.0
|
||||
c = max(vec3(0.0), c - 1.0);
|
||||
// crush everything above 1
|
||||
highp float f = max3(c);
|
||||
c *= 1.0 / (1.0 + f * materialParams.invHighlight);
|
||||
}
|
||||
|
||||
vec3 box4x4(vec3 s0, vec3 s1, vec3 s2, vec3 s3) {
|
||||
|
||||
@@ -16,11 +16,21 @@ material {
|
||||
name : bloomBuffer,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : sampler2d,
|
||||
name : flareBuffer,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : sampler2d,
|
||||
name : dirtBuffer,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : sampler2d,
|
||||
name : starburstBuffer,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : int,
|
||||
name : dithering
|
||||
@@ -65,66 +75,108 @@ fragment {
|
||||
|
||||
#include "../../../../shaders/src/dithering.fs"
|
||||
#include "../../../../shaders/src/vignette.fs"
|
||||
#include "../../../../shaders/src/bloom.fs"
|
||||
|
||||
vec3 colorGrade(mediump sampler3D lut, const vec3 x) {
|
||||
// Alexa LogC EI 1000
|
||||
const float a = 5.555556;
|
||||
const float b = 0.047996;
|
||||
const float c = 0.244161 / log2(10.0);
|
||||
const float d = 0.386036;
|
||||
vec3 logc = c * log2(a * x + b) + d;
|
||||
return textureLod(lut, logc, 0.0).rgb;
|
||||
void dummy(){ }
|
||||
|
||||
float starburst(const vec2 uv) {
|
||||
// get an offset that continuously moves with the camera
|
||||
vec3 forward = getViewFromWorldMatrix()[2].xyz;
|
||||
float offset = forward.x + forward.y + forward.z;
|
||||
|
||||
vec2 center = uv - vec2(0.5001);
|
||||
float d = length(center);
|
||||
float radial = acosFastPositive(center.x / d);
|
||||
float mask =
|
||||
textureLod(materialParams_starburstBuffer, fract(vec2(radial + offset * 1.0, 0.0)), 0.0).r *
|
||||
textureLod(materialParams_starburstBuffer, fract(vec2(radial - offset * 0.5, 0.0)), 0.0).r ;
|
||||
|
||||
return saturate(mask + (1.0 - smoothstep(0.0, 0.3, d)));
|
||||
}
|
||||
|
||||
vec3 bloom(const vec3 color) {
|
||||
highp vec2 uv = variable_vertex.xy;
|
||||
|
||||
vec3 result = vec3(0.0);
|
||||
|
||||
if (materialParams.bloom.x > 0.0) {
|
||||
vec3 bloom = textureLod(materialParams_bloomBuffer, uv, 0.0).rgb;
|
||||
result += bloom * materialParams.bloom.x;
|
||||
}
|
||||
|
||||
vec3 resolveFragment(const ivec2 uv) {
|
||||
return texelFetch(materialParams_colorBuffer, uv, 0).rgb;
|
||||
if (materialParams.bloom.w > 0.0) {
|
||||
float starburstMask = starburst(uv);
|
||||
vec3 flare = textureLod(materialParams_flareBuffer, uv, 0.0).rgb;
|
||||
result += flare * (materialParams.bloom.w * starburstMask);
|
||||
}
|
||||
|
||||
vec4 resolveAlphaFragment(const ivec2 uv) {
|
||||
return texelFetch(materialParams_colorBuffer, uv, 0);
|
||||
if (materialParams.bloom.z > 0.0) {
|
||||
float dirtIntensity = materialParams.bloom.z;
|
||||
vec3 dirt = textureLod(materialParams_dirtBuffer, uv, 0.0).rgb;
|
||||
result *= dirt * dirtIntensity;
|
||||
}
|
||||
|
||||
vec4 resolve() {
|
||||
result += color * materialParams.bloom.y;
|
||||
return result;
|
||||
}
|
||||
|
||||
vec3 colorGrade(mediump sampler3D lut, const vec3 x) {
|
||||
// Alexa LogC EI 1000
|
||||
const float a = 5.555556;
|
||||
const float b = 0.047996;
|
||||
const float c = 0.244161 / log2(10.0);
|
||||
const float d = 0.386036;
|
||||
vec3 logc = c * log2(a * x + b) + d;
|
||||
return textureLod(lut, logc, 0.0).rgb;
|
||||
}
|
||||
|
||||
vec3 resolveFragment(const ivec2 uv) {
|
||||
return texelFetch(materialParams_colorBuffer, uv, 0).rgb;
|
||||
}
|
||||
|
||||
vec4 resolveAlphaFragment(const ivec2 uv) {
|
||||
return texelFetch(materialParams_colorBuffer, uv, 0);
|
||||
}
|
||||
|
||||
vec4 resolve() {
|
||||
#if POST_PROCESS_OPAQUE
|
||||
vec4 color = vec4(resolveFragment(ivec2(getUV())), 1.0);
|
||||
if (materialParams.bloom.x > 0.0) {
|
||||
color.rgb = bloom(color.rgb);
|
||||
}
|
||||
if (materialParams.vignette.x < MEDIUMP_FLT_MAX) {
|
||||
highp vec2 uv = getUV() * frameUniforms.resolution.zw;
|
||||
color.rgb = vignette(color.rgb, uv, materialParams.vignette, materialParams.vignetteColor);
|
||||
}
|
||||
color.rgb = colorGrade(materialParams_lut, color.rgb);
|
||||
if (materialParams.fxaa > 0) {
|
||||
color.a = luminance(color.rgb);
|
||||
}
|
||||
#else
|
||||
vec4 color = resolveAlphaFragment(ivec2(getUV()));
|
||||
color.rgb /= color.a + FLT_EPS;
|
||||
if (materialParams.bloom.x > 0.0) {
|
||||
color.rgb = bloom(color.rgb);
|
||||
}
|
||||
if (materialParams.vignette.x < MEDIUMP_FLT_MAX) {
|
||||
highp vec2 uv = getUV() * frameUniforms.resolution.zw;
|
||||
color.rgb = vignette(color.rgb, uv, materialParams.vignette, materialParams.vignetteColor);
|
||||
}
|
||||
color.rgb = colorGrade(materialParams_lut, color.rgb);
|
||||
color.rgb *= color.a + FLT_EPS;
|
||||
#endif
|
||||
return color;
|
||||
vec4 color = vec4(resolveFragment(ivec2(getUV())), 1.0);
|
||||
if (materialParams.bloom.x > 0.0) {
|
||||
color.rgb = bloom(color.rgb);
|
||||
}
|
||||
if (materialParams.vignette.x < MEDIUMP_FLT_MAX) {
|
||||
highp vec2 uv = getUV() * frameUniforms.resolution.zw;
|
||||
color.rgb = vignette(color.rgb, uv, materialParams.vignette, materialParams.vignetteColor);
|
||||
}
|
||||
color.rgb = colorGrade(materialParams_lut, color.rgb);
|
||||
if (materialParams.fxaa > 0) {
|
||||
color.a = luminance(color.rgb);
|
||||
}
|
||||
#else
|
||||
vec4 color = resolveAlphaFragment(ivec2(getUV()));
|
||||
color.rgb /= color.a + FLT_EPS;
|
||||
if (materialParams.bloom.x > 0.0) {
|
||||
color.rgb = bloom(color.rgb);
|
||||
}
|
||||
if (materialParams.vignette.x < MEDIUMP_FLT_MAX) {
|
||||
highp vec2 uv = getUV() * frameUniforms.resolution.zw;
|
||||
color.rgb = vignette(color.rgb, uv, materialParams.vignette, materialParams.vignetteColor);
|
||||
}
|
||||
color.rgb = colorGrade(materialParams_lut, color.rgb);
|
||||
color.rgb *= color.a + FLT_EPS;
|
||||
#endif
|
||||
return color;
|
||||
}
|
||||
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
postProcess.color = resolve();
|
||||
if (materialParams.dithering > 0) {
|
||||
vec4 dithered = dither(postProcess.color, materialParams.temporalNoise);
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
postProcess.color = resolve();
|
||||
if (materialParams.dithering > 0) {
|
||||
vec4 dithered = dither(postProcess.color, materialParams.temporalNoise);
|
||||
#if POST_PROCESS_OPAQUE
|
||||
postProcess.color.rgb = dithered.rgb;
|
||||
postProcess.color.rgb = dithered.rgb;
|
||||
#else
|
||||
postProcess.color = dithered;
|
||||
postProcess.color = dithered;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
162
filament/src/materials/flare/flare.mat
Normal file
162
filament/src/materials/flare/flare.mat
Normal file
@@ -0,0 +1,162 @@
|
||||
material {
|
||||
name : flare,
|
||||
parameters : [
|
||||
{
|
||||
type : sampler2d,
|
||||
name : color,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : level,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : float2,
|
||||
name : aspectRatio,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : float2,
|
||||
name : threshold,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : chromaticAberration,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : ghostCount,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : ghostSpacing,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : haloRadius,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : haloThickness,
|
||||
precision: medium
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
vertex
|
||||
],
|
||||
outputs : [
|
||||
{
|
||||
name : color,
|
||||
target : color,
|
||||
type : float4
|
||||
}
|
||||
],
|
||||
domain : postprocess,
|
||||
depthWrite : false,
|
||||
depthCulling : false
|
||||
}
|
||||
|
||||
|
||||
vertex {
|
||||
|
||||
void dummy(){ }
|
||||
|
||||
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
|
||||
postProcess.vertex.xy = postProcess.normalizedUV;
|
||||
#if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT)
|
||||
// On metal/vulkan postProcess.normalizedUV has its origin at the left-top, but we need a
|
||||
// uniform coordinate space. So, we flip the y coordinate so we have bottom-left UV
|
||||
// coordinates across all backends.
|
||||
postProcess.vertex.y = 1.0 - postProcess.vertex.y;
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fragment {
|
||||
|
||||
void dummy(){ }
|
||||
|
||||
float smoothstep01(const float t) {
|
||||
return t * t * (3.0 - 2.0 * t);
|
||||
}
|
||||
|
||||
float ring(float x, const float c, const float r) {
|
||||
return 1.0 - smoothstep01(min(abs(x - c) / r, 1.0));
|
||||
}
|
||||
|
||||
vec3 sampleColor(const vec2 uv) {
|
||||
// because we work on reduced resolution, we assume mediump uv is enough.
|
||||
float level = materialParams.level;
|
||||
float chromaticAberration = materialParams.chromaticAberration;
|
||||
// make sure to avoid divide-by-zero in normalize
|
||||
vec2 offset = normalize(0.5001 - uv) * chromaticAberration;
|
||||
return vec3(
|
||||
textureLod(materialParams_color, uv + offset, level).r,
|
||||
textureLod(materialParams_color, uv, level).g,
|
||||
textureLod(materialParams_color, uv - offset, level).b
|
||||
);
|
||||
}
|
||||
|
||||
vec3 ghosts(const vec2 vertex_uv) {
|
||||
// because we work on reduced resolution, we assume mediump uv is enough.
|
||||
float ghostSpacing = materialParams.ghostSpacing;
|
||||
float ghostCount = materialParams.ghostCount;
|
||||
float threshold = materialParams.threshold.x;
|
||||
vec2 uv = 1.0 - vertex_uv;
|
||||
vec2 v = (0.5 - uv) * ghostSpacing;
|
||||
vec3 color = vec3(0.0);
|
||||
for (float i = 0.0; i < ghostCount; i += 1.0) {
|
||||
vec2 suv = fract(uv + v * i);
|
||||
vec3 s = sampleColor(suv);
|
||||
s = clamp(s - threshold, 0.0, 1.0);
|
||||
color += s;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
vec3 halo(const vec2 uv) {
|
||||
float haloRadius = materialParams.haloRadius;
|
||||
float haloThickness = materialParams.haloThickness;
|
||||
float threshold = materialParams.threshold.y;
|
||||
float2 aspectRatio = materialParams.aspectRatio;
|
||||
|
||||
// make sure to avoid divide-by-zero in normalize
|
||||
vec2 v = vec2(0.5001) - uv;
|
||||
// handle aspect ratio
|
||||
v.x *= aspectRatio.x;
|
||||
v = normalize(v);
|
||||
v.x *= aspectRatio.y;
|
||||
|
||||
vec3 s = sampleColor(uv + v * haloRadius);
|
||||
s = clamp(s - threshold, 0.0, 1.0);
|
||||
|
||||
vec2 uvWeight = (uv - vec2(0.5, 0.0)) * vec2(aspectRatio.x, 1.0) + vec2(0.5, 0.0);
|
||||
float d = distance(uvWeight, vec2(0.5));
|
||||
float weight = ring(d, haloRadius, haloThickness);
|
||||
return s * weight;
|
||||
}
|
||||
|
||||
/*
|
||||
* Screen space lens flare, greatly inspired from John Chapman's 2017 blog post
|
||||
* https://john-chapman.github.io/2017/11/05/pseudo-lens-flare.html
|
||||
*/
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
// because we work on reduced resolution, we assume mediump uv is enough.
|
||||
vec2 uv = variable_vertex.xy;
|
||||
vec3 color = vec3(0.0);
|
||||
if (materialParams.ghostCount > 0.0) {
|
||||
color += ghosts(uv);
|
||||
}
|
||||
if (materialParams.haloThickness > 0.0) {
|
||||
color += halo(uv);
|
||||
}
|
||||
postProcess.color = vec4(color, 1.0);
|
||||
}
|
||||
@@ -416,6 +416,24 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, BloomOpt
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->enabled);
|
||||
} else if (compare(tok, jsonChunk, "highlight") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->highlight);
|
||||
} else if (compare(tok, jsonChunk, "lensFlare") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->lensFlare);
|
||||
} else if (compare(tok, jsonChunk, "starburst") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->starburst);
|
||||
} else if (compare(tok, jsonChunk, "chromaticAberration") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->chromaticAberration);
|
||||
} else if (compare(tok, jsonChunk, "ghostCount") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->ghostCount);
|
||||
} else if (compare(tok, jsonChunk, "ghostSpacing") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->ghostSpacing);
|
||||
} else if (compare(tok, jsonChunk, "ghostThreshold") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->ghostThreshold);
|
||||
} else if (compare(tok, jsonChunk, "haloThickness") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->haloThickness);
|
||||
} else if (compare(tok, jsonChunk, "haloRadius") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->haloRadius);
|
||||
} else if (compare(tok, jsonChunk, "haloThreshold") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->haloThreshold);
|
||||
} else {
|
||||
slog.w << "Invalid bloom options key: '" << STR(tok, jsonChunk) << "'" << io::endl;
|
||||
i = parse(tokens, i + 1);
|
||||
|
||||
@@ -622,6 +622,7 @@ void SimpleViewer::updateUserInterface() {
|
||||
|
||||
ImGui::Checkbox("SSAO", &mSettings.view.ssao.enabled);
|
||||
ImGui::Checkbox("Bloom", &mSettings.view.bloom.enabled);
|
||||
ImGui::Checkbox("Flare", &mSettings.view.bloom.lensFlare);
|
||||
|
||||
if (ImGui::CollapsingHeader("SSAO Options")) {
|
||||
auto& ssao = mSettings.view.ssao;
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// Bloom
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
vec3 bloom(vec3 color) {
|
||||
highp vec2 uv = variable_vertex.xy;
|
||||
vec3 blurred = textureLod(materialParams_bloomBuffer, uv, 0.0).rgb;
|
||||
color = blurred * materialParams.bloom.x + color * materialParams.bloom.y;
|
||||
|
||||
if (materialParams.bloom.z > 0.0) {
|
||||
float dirtIntensity = materialParams.bloom.z;
|
||||
vec3 dirt = textureLod(materialParams_dirtBuffer, uv, 0.0).rgb;
|
||||
color += blurred * dirt * dirtIntensity;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
@@ -388,7 +388,16 @@ value_object<filament::View::BloomOptions>("View$BloomOptions")
|
||||
.field("threshold", &filament::View::BloomOptions::threshold)
|
||||
.field("enabled", &filament::View::BloomOptions::enabled)
|
||||
.field("blendMode", &filament::View::BloomOptions::blendMode)
|
||||
.field("highlight", &filament::View::BloomOptions::highlight);
|
||||
.field("highlight", &filament::View::BloomOptions::highlight)
|
||||
.field("lensFlare", &filament::View::BloomOptions::lensFlare)
|
||||
.field("starburst", &filament::View::BloomOptions::starburst)
|
||||
.field("chromaticAberration", &filament::View::BloomOptions::chromaticAberration)
|
||||
.field("ghostCount", &filament::View::BloomOptions::ghostCount)
|
||||
.field("ghostSpacing", &filament::View::BloomOptions::ghostSpacing)
|
||||
.field("ghostThreshold", &filament::View::BloomOptions::ghostThreshold)
|
||||
.field("haloThickness", &filament::View::BloomOptions::haloThickness)
|
||||
.field("haloRadius", &filament::View::BloomOptions::haloRadius)
|
||||
.field("haloThreshold", &filament::View::BloomOptions::haloThreshold);
|
||||
|
||||
// TODO: add support for dirt texture in BloomOptions.
|
||||
// Note that simply including the field in the above list causes binding errors for nullptr.
|
||||
|
||||
Reference in New Issue
Block a user