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:
Mathias Agopian
2021-05-04 17:21:01 -07:00
committed by Mathias Agopian
parent 812f0d577b
commit c0fe2d1c09
14 changed files with 473 additions and 80 deletions

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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
)

View File

@@ -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
};
/**

View File

@@ -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;

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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
}
}
}
}

View 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);
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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.