Improve bloom
- add a quality option - remove the ping-pong code, we'll disable for GPU that don't work instead. - improve quality by doing a better first downscale (using a 5x5 gaussian). - improve performance by using a 9 tap filter instead of 13 for in most cases - fix usages of setMinMaxLevels as it resets the base level to "min"
This commit is contained in:
committed by
Mathias Agopian
parent
9c0fb67b87
commit
9f62dc2f2f
@@ -1418,6 +1418,17 @@ public class View {
|
||||
* limit highlights to this value before bloom [10, +inf]
|
||||
*/
|
||||
public float highlight = 1000.0f;
|
||||
/**
|
||||
* Bloom quality level.
|
||||
* LOW (default): use a more optimized down-sampling filter, however there can be artifacts
|
||||
* with dynamic resolution, this can be alleviated by using the homogenous mode.
|
||||
* MEDIUM: Good balance between quality and performance.
|
||||
* HIGH: In this mode the bloom resolution is automatically increased to avoid artifacts.
|
||||
* This mode can be significantly slower on mobile, especially at high resolution.
|
||||
* This mode greatly improves the anamorphic bloom.
|
||||
*/
|
||||
@NonNull
|
||||
public QualityLevel quality = QualityLevel.LOW;
|
||||
/**
|
||||
* enable screen-space lens flare
|
||||
*/
|
||||
|
||||
@@ -230,6 +230,8 @@ set(MATERIAL_SRCS
|
||||
src/materials/flare/flare.mat
|
||||
src/materials/blitLow.mat
|
||||
src/materials/bloom/bloomDownsample.mat
|
||||
src/materials/bloom/bloomDownsample2x.mat
|
||||
src/materials/bloom/bloomDownsample9.mat
|
||||
src/materials/bloom/bloomUpsample.mat
|
||||
src/materials/ssao/bilateralBlur.mat
|
||||
src/materials/ssao/bilateralBlurBentNormals.mat
|
||||
|
||||
@@ -141,6 +141,17 @@ struct BloomOptions {
|
||||
bool enabled = false; //!< enable or disable bloom
|
||||
float highlight = 1000.0f; //!< limit highlights to this value before bloom [10, +inf]
|
||||
|
||||
/**
|
||||
* Bloom quality level.
|
||||
* LOW (default): use a more optimized down-sampling filter, however there can be artifacts
|
||||
* with dynamic resolution, this can be alleviated by using the homogenous mode.
|
||||
* MEDIUM: Good balance between quality and performance.
|
||||
* HIGH: In this mode the bloom resolution is automatically increased to avoid artifacts.
|
||||
* This mode can be significantly slower on mobile, especially at high resolution.
|
||||
* This mode greatly improves the anamorphic bloom.
|
||||
*/
|
||||
QualityLevel quality = QualityLevel::LOW;
|
||||
|
||||
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
|
||||
|
||||
@@ -212,6 +212,8 @@ static const PostProcessManager::MaterialInfo sMaterialList[] = {
|
||||
{ "bilateralBlurBentNormals", MATERIAL(BILATERALBLURBENTNORMALS) },
|
||||
{ "blitLow", MATERIAL(BLITLOW) },
|
||||
{ "bloomDownsample", MATERIAL(BLOOMDOWNSAMPLE) },
|
||||
{ "bloomDownsample2x", MATERIAL(BLOOMDOWNSAMPLE2X) },
|
||||
{ "bloomDownsample9", MATERIAL(BLOOMDOWNSAMPLE9) },
|
||||
{ "bloomUpsample", MATERIAL(BLOOMUPSAMPLE) },
|
||||
{ "colorGrading", MATERIAL(COLORGRADING) },
|
||||
{ "colorGradingAsSubpass", MATERIAL(COLORGRADINGASSUBPASS) },
|
||||
@@ -450,7 +452,6 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
|
||||
for (size_t level = 0; level < levelCount - 1; level++) {
|
||||
auto out = resources.getRenderPassInfo(level);
|
||||
driver.setMinMaxLevels(in, level, level);
|
||||
mi->setParameter("level", uint32_t(level));
|
||||
commitAndRender(out, material, driver);
|
||||
}
|
||||
driver.setMinMaxLevels(in, 0, levelCount - 1);
|
||||
@@ -1596,8 +1597,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
|
||||
auto const& out = resources.getRenderPassInfo(data.rp[level]);
|
||||
driver.setMinMaxLevels(inOutColor, level, level);
|
||||
driver.setMinMaxLevels(inOutCoc, level, level);
|
||||
mi->setParameter("mip", uint32_t(level));
|
||||
mi->setParameter("weightScale", 0.5f / float(1u<<level)); // FIXME: halfres?
|
||||
mi->setParameter("weightScale", 0.5f / float(1u << level)); // FIXME: halfres?
|
||||
mi->setParameter("texelSize", float2{ 1.0f / w, 1.0f / h });
|
||||
mi->commit(driver);
|
||||
render(out, pipeline, driver);
|
||||
@@ -1870,15 +1870,53 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
|
||||
return bloomPass(fg, input, outFormat, inoutBloomOptions, scale);
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::downscalePass(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor const& outDesc,
|
||||
bool threshold, float highlight, bool fireflies) noexcept {
|
||||
struct DownsampleData {
|
||||
FrameGraphId<FrameGraphTexture> input;
|
||||
FrameGraphId<FrameGraphTexture> output;
|
||||
};
|
||||
auto& downsamplePass = fg.addPass<DownsampleData>("Downsample",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
data.input = builder.sample(input);
|
||||
data.output = builder.createTexture("Downsample-output", outDesc);
|
||||
builder.declareRenderPass(data.output);
|
||||
},
|
||||
[=](FrameGraphResources const& resources,
|
||||
auto const& data, DriverApi& driver) {
|
||||
auto const& material = getPostProcessMaterial("bloomDownsample2x");
|
||||
auto* mi = material.getMaterialInstance(mEngine);
|
||||
mi->setParameter("source", resources.getTexture(data.input), {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR
|
||||
});
|
||||
mi->setParameter("level", 0);
|
||||
mi->setParameter("threshold", threshold ? 1.0f : 0.0f);
|
||||
mi->setParameter("fireflies", fireflies ? 1.0f : 0.0f);
|
||||
mi->setParameter("invHighlight", std::isinf(highlight) ? 0.0f : 1.0f / highlight);
|
||||
commitAndRender(resources.getRenderPassInfo(), material, driver);
|
||||
});
|
||||
return downsamplePass->output;
|
||||
}
|
||||
|
||||
PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input, TextureFormat outFormat,
|
||||
BloomOptions& inoutBloomOptions, float2 scale) noexcept {
|
||||
// Figure out a good size for the bloom buffer.
|
||||
auto const& desc = fg.getDescriptor(input);
|
||||
|
||||
// Figure out a good size for the bloom buffer. We must use a fixed bloom buffer size so
|
||||
// that the size/strength of the bloom doesn't vary much with the resolution, otherwise
|
||||
// dynamic resolution would affect the bloom effect too much.
|
||||
auto desc = fg.getDescriptor(input);
|
||||
|
||||
// width and height after dynamic resolution upscaling
|
||||
const float aspect = (float(desc.width) * scale.y) / (float(desc.height) * scale.x);
|
||||
|
||||
// FIXME: don't allow inoutBloomOptions.resolution to be larger than input's resolution
|
||||
// (avoid upscale) but how does this affect dynamic resolution
|
||||
// FIXME: check what happens on WebGL and intel's processors
|
||||
|
||||
// compute the desired bloom buffer size
|
||||
float bloomHeight = float(inoutBloomOptions.resolution);
|
||||
float bloomWidth = bloomHeight * aspect;
|
||||
@@ -1893,38 +1931,73 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg
|
||||
bloomHeight *= inoutBloomOptions.anamorphism;
|
||||
}
|
||||
|
||||
// convert back to integer width/height
|
||||
const uint32_t width = std::max(1u, uint32_t(std::floor(bloomWidth)));
|
||||
const uint32_t height = std::max(1u, uint32_t(std::floor(bloomHeight)));
|
||||
|
||||
// we might need to adjust the max # of levels
|
||||
const uint32_t major = uint32_t(std::max(bloomWidth, bloomHeight));
|
||||
const uint8_t maxLevels = FTexture::maxLevelCount(major);
|
||||
inoutBloomOptions.levels = std::min(inoutBloomOptions.levels, maxLevels);
|
||||
inoutBloomOptions.levels = std::min(inoutBloomOptions.levels, kMaxBloomLevels);
|
||||
|
||||
if (2 * width < desc.width || 2 * height < desc.height) {
|
||||
// if we're scaling down by more than 2x, prescale the image with a blit to improve
|
||||
// performance. This is important on mobile/tilers.
|
||||
input = opaqueBlit(fg, input, { 0, 0, desc.width, desc.height }, {
|
||||
.width = std::max(1u, desc.width / 2),
|
||||
.height = std::max(1u, desc.height / 2),
|
||||
.format = outFormat
|
||||
});
|
||||
if (inoutBloomOptions.quality == QualityLevel::LOW) {
|
||||
// In low quality mode, we adjust the bloom buffer size so that both dimensions
|
||||
// have enough exact mip levels. This can slightly affect the aspect ratio causing
|
||||
// some artifacts:
|
||||
// - add some anamorphism (experimentally not visible)
|
||||
// - visible bloom size changes with dynamic resolution in non-homogenous mode
|
||||
// This allows us to use the 9 sample downsampling filter (instead of 13)
|
||||
// for at least 4 levels.
|
||||
uint32_t width = std::max(1u, uint32_t(std::floor(bloomWidth)));
|
||||
uint32_t height = std::max(1u, uint32_t(std::floor(bloomHeight)));
|
||||
width &= ~((1 << 4) - 1); // at least 4 levels
|
||||
height &= ~((1 << 4) - 1);
|
||||
bloomWidth = float(width);
|
||||
bloomHeight = float(height);
|
||||
}
|
||||
|
||||
bool threshold = inoutBloomOptions.threshold;
|
||||
|
||||
while (2 * bloomWidth < float(desc.width) || 2 * bloomHeight < float(desc.height)) {
|
||||
if (inoutBloomOptions.quality == QualityLevel::LOW ||
|
||||
inoutBloomOptions.quality == QualityLevel::MEDIUM) {
|
||||
input = downscalePass(fg, input, {
|
||||
.width = (desc.width = std::max(1u, desc.width / 2)),
|
||||
.height = (desc.height = std::max(1u, desc.height / 2)),
|
||||
.format = outFormat
|
||||
},
|
||||
threshold, inoutBloomOptions.highlight, threshold);
|
||||
threshold = false; // we do the thresholding only once during down sampling
|
||||
} else if (inoutBloomOptions.quality == QualityLevel::HIGH ||
|
||||
inoutBloomOptions.quality == QualityLevel::ULTRA) {
|
||||
// In high quality mode, we increase the size of the bloom buffer such that the
|
||||
// first scaling is less than 2x, and we increase the number of levels accordingly.
|
||||
if (bloomWidth * 2.0f > 2048.0f || bloomHeight * 2.0f > 2048.0f) {
|
||||
// but we can't scale above the h/w guaranteed minspec
|
||||
break;
|
||||
}
|
||||
bloomWidth *= 2.0f;
|
||||
bloomHeight *= 2.0f;
|
||||
inoutBloomOptions.levels++;
|
||||
}
|
||||
}
|
||||
|
||||
// convert back to integer width/height
|
||||
uint32_t const width = std::max(1u, uint32_t(std::floor(bloomWidth)));
|
||||
uint32_t const height = std::max(1u, uint32_t(std::floor(bloomHeight)));
|
||||
|
||||
input = downscalePass(fg, input,
|
||||
{ .width = width, .height = height, .format = outFormat },
|
||||
threshold, inoutBloomOptions.highlight, threshold);
|
||||
|
||||
struct BloomPassData {
|
||||
FrameGraphId<FrameGraphTexture> in;
|
||||
FrameGraphId<FrameGraphTexture> out;
|
||||
FrameGraphId<FrameGraphTexture> stage;
|
||||
uint32_t outRT[kMaxBloomLevels];
|
||||
uint32_t stageRT[kMaxBloomLevels];
|
||||
};
|
||||
|
||||
// downsample phase
|
||||
// Creating a mip-chain poses a "feedback" loop problem on some GPU. We will disable
|
||||
// Bloom on these.
|
||||
// See: https://github.com/google/filament/issues/2338
|
||||
|
||||
auto& bloomDownsamplePass = fg.addPass<BloomPassData>("Bloom Downsample",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
data.in = builder.sample(input);
|
||||
data.out = builder.createTexture("Bloom Out Texture", {
|
||||
.width = width,
|
||||
.height = height,
|
||||
@@ -1933,165 +2006,107 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg
|
||||
});
|
||||
data.out = builder.sample(data.out);
|
||||
|
||||
data.stage = builder.createTexture("Bloom Stage Texture", {
|
||||
.width = width,
|
||||
.height = height,
|
||||
.levels = inoutBloomOptions.levels,
|
||||
.format = outFormat
|
||||
});
|
||||
data.stage = builder.sample(data.stage);
|
||||
|
||||
for (size_t i = 0; i < inoutBloomOptions.levels; i++) {
|
||||
auto out = builder.createSubresource(data.out, "Bloom Out Texture mip",
|
||||
{ .level = uint8_t(i) });
|
||||
auto stage = builder.createSubresource(data.stage,
|
||||
"Bloom Stage Texture mip", { .level = uint8_t(i) });
|
||||
if (i == 0) {
|
||||
// this causes the last blit above to render into this mip
|
||||
fg.forwardResource(out, input);
|
||||
}
|
||||
builder.declareRenderPass(out, &data.outRT[i]);
|
||||
builder.declareRenderPass(stage, &data.stageRT[i]);
|
||||
}
|
||||
},
|
||||
[=](FrameGraphResources const& resources,
|
||||
auto const& data, DriverApi& driver) {
|
||||
|
||||
auto hwIn = resources.getTexture(data.in);
|
||||
// TODO: if downsampling is not exactly a multiple of two, use the 13 samples
|
||||
// filter. This is generally the accepted solution, however, the 13 samples
|
||||
// filter is not correct either when we don't sample at integer coordinates,
|
||||
// but it seems ot create less artifacts.
|
||||
// A better solution might be to use the filter described in
|
||||
// Castaño, 2013, "Shadow Mapping Summary Part 1", which is 5x5 filter with
|
||||
// 9 samples, but works at all coordinates.
|
||||
|
||||
auto hwOut = resources.getTexture(data.out);
|
||||
auto hwStage = resources.getTexture(data.stage);
|
||||
|
||||
auto const& material = getPostProcessMaterial("bloomDownsample");
|
||||
auto const* ma = material.getMaterial(mEngine);
|
||||
auto const& material9 = getPostProcessMaterial("bloomDownsample9");
|
||||
auto const& material13 = getPostProcessMaterial("bloomDownsample");
|
||||
|
||||
FMaterialInstance* mis[] = {
|
||||
ma->createInstance("bloomDownsample-ping"),
|
||||
ma->createInstance("bloomDownsample-pong"),
|
||||
ma->createInstance("bloomDownsample-first"),
|
||||
};
|
||||
auto* mi9 = material9.getMaterialInstance(mEngine);
|
||||
auto* mi13 = material13.getMaterialInstance(mEngine);
|
||||
|
||||
mis[0]->setParameter("source", hwOut, {
|
||||
mi9->setParameter("source", hwOut, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST });
|
||||
|
||||
mis[1]->setParameter("source", hwStage, {
|
||||
mi13->setParameter("source", hwOut, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
mis[2]->setParameter("source", hwIn, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST });
|
||||
|
||||
for (auto* mi : mis) {
|
||||
mi->setParameter("level", 0.0f);
|
||||
mi->setParameter("threshold", inoutBloomOptions.threshold ? 1.0f : 0.0f);
|
||||
mi->setParameter("invHighlight", std::isinf(inoutBloomOptions.highlight)
|
||||
? 0.0f : 1.0f / inoutBloomOptions.highlight);
|
||||
mi->commit(driver);
|
||||
}
|
||||
mi9->commit(driver);
|
||||
mi13->commit(driver);
|
||||
|
||||
const PipelineState pipeline(material.getPipelineState(mEngine));
|
||||
|
||||
{ // first iteration
|
||||
auto hwDstRT = resources.getRenderPassInfo(data.outRT[0]);
|
||||
hwDstRT.params.flags.discardStart = TargetBufferFlags::COLOR;
|
||||
hwDstRT.params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
mis[2]->use(driver);
|
||||
render(hwDstRT, pipeline, driver);
|
||||
}
|
||||
// PipelineState for both materials should be the same
|
||||
const PipelineState pipeline(material9.getPipelineState(mEngine));
|
||||
|
||||
for (size_t i = 1; i < inoutBloomOptions.levels; i++) {
|
||||
const size_t parity = 1u - (i & 0x1u);
|
||||
auto hwDstRT = resources.getRenderPassInfo(parity ? data.outRT[i] : data.stageRT[i]);
|
||||
auto hwDstRT = resources.getRenderPassInfo(data.outRT[i]);
|
||||
hwDstRT.params.flags.discardStart = TargetBufferFlags::COLOR;
|
||||
hwDstRT.params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
mis[parity]->setParameter("level", float(i - 1));
|
||||
mis[parity]->commit(driver);
|
||||
mis[parity]->use(driver);
|
||||
|
||||
// if downsampling is a multiple of 2 in each dimension we can use the
|
||||
// 9 samples filter.
|
||||
auto vp = resources.getRenderPassInfo(data.outRT[i-1]).params.viewport;
|
||||
auto* const mi = (vp.width & 1 || vp.height & 1) ? mi13 : mi9;
|
||||
mi->use(driver);
|
||||
driver.setMinMaxLevels(hwOut, i - 1, i - 1); // this offsets baseLevel to i-1
|
||||
render(hwDstRT, pipeline, driver);
|
||||
}
|
||||
|
||||
for (auto& mi : mis) {
|
||||
mEngine.destroy(mi);
|
||||
}
|
||||
driver.setMinMaxLevels(hwOut, 0, inoutBloomOptions.levels - 1);
|
||||
});
|
||||
|
||||
FrameGraphId<FrameGraphTexture> output = bloomDownsamplePass->out;
|
||||
FrameGraphId<FrameGraphTexture> stage = bloomDownsamplePass->stage;
|
||||
// output of bloom downsample pass becomes input of next (flare) pass
|
||||
input = bloomDownsamplePass->out;
|
||||
|
||||
// flare pass
|
||||
auto flare = flarePass(fg, bloomDownsamplePass->out, width, height, outFormat, inoutBloomOptions);
|
||||
auto flare = flarePass(fg, input, width, height, outFormat, inoutBloomOptions);
|
||||
|
||||
// upsample phase
|
||||
auto& bloomUpsamplePass = fg.addPass<BloomPassData>("Bloom Upsample",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
data.out = builder.sample(output);
|
||||
data.stage = builder.sample(stage);
|
||||
data.out = builder.sample(input);
|
||||
for (size_t i = 0; i < inoutBloomOptions.levels; i++) {
|
||||
auto out = builder.createSubresource(data.out, "Bloom Out Texture mip",
|
||||
{ .level = uint8_t(i) });
|
||||
auto staging = builder.createSubresource(data.stage,
|
||||
"Bloom Stage Texture mip", { .level = uint8_t(i) });
|
||||
builder.declareRenderPass(out, &data.outRT[i]);
|
||||
builder.declareRenderPass(staging, &data.stageRT[i]);
|
||||
}
|
||||
},
|
||||
[=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
|
||||
|
||||
auto hwOut = resources.getTexture(data.out);
|
||||
auto hwStage = resources.getTexture(data.stage);
|
||||
auto const& outDesc = resources.getDescriptor(data.out);
|
||||
|
||||
auto const& material = getPostProcessMaterial("bloomUpsample");
|
||||
auto const* ma = material.getMaterial(mEngine);
|
||||
|
||||
FMaterialInstance* mis[] = {
|
||||
ma->createInstance("bloomUpsample-ping"),
|
||||
ma->createInstance("bloomUpsample-pong"),
|
||||
};
|
||||
|
||||
mis[0]->setParameter("source", hwOut, {
|
||||
auto* mi = material.getMaterialInstance(mEngine);
|
||||
mi->setParameter("source", hwOut, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
|
||||
mis[1]->setParameter("source", hwStage, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST});
|
||||
mi->use(driver);
|
||||
|
||||
PipelineState pipeline(material.getPipelineState(mEngine));
|
||||
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
|
||||
pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE;
|
||||
|
||||
for (size_t j = inoutBloomOptions.levels, i = j - 1; i >= 1; i--, j++) {
|
||||
const size_t parity = 1u - (j % 2u);
|
||||
|
||||
auto hwDstRT = resources.getRenderPassInfo(
|
||||
parity ? data.outRT[i - 1] : data.stageRT[i - 1]);
|
||||
auto hwDstRT = resources.getRenderPassInfo(data.outRT[i - 1]);
|
||||
hwDstRT.params.flags.discardStart = TargetBufferFlags::NONE; // b/c we'll blend
|
||||
hwDstRT.params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
|
||||
auto w = FTexture::valueForLevel(i - 1, outDesc.width);
|
||||
auto h = FTexture::valueForLevel(i - 1, outDesc.height);
|
||||
mis[parity]->setParameter("resolution", float4{ w, h, 1.0f / w, 1.0f / h });
|
||||
mis[parity]->setParameter("level", float(i));
|
||||
mis[parity]->commit(driver);
|
||||
mis[parity]->use(driver);
|
||||
mi->setParameter("resolution", float4{ w, h, 1.0f / w, 1.0f / h });
|
||||
mi->commit(driver);
|
||||
driver.setMinMaxLevels(hwOut, i, i); // this offsets baseLevel to i
|
||||
render(hwDstRT, pipeline, driver);
|
||||
}
|
||||
|
||||
for (auto& mi : mis) {
|
||||
mEngine.destroy(mi);
|
||||
}
|
||||
|
||||
// Every other level is missing from the out texture, so we need to do
|
||||
// blits to complete the chain.
|
||||
const SamplerMagFilter filter = SamplerMagFilter::NEAREST;
|
||||
for (size_t i = 1; i < inoutBloomOptions.levels; i += 2) {
|
||||
auto in = resources.getRenderPassInfo(data.stageRT[i]);
|
||||
auto out = resources.getRenderPassInfo(data.outRT[i]);
|
||||
driver.blit(TargetBufferFlags::COLOR, out.target, out.params.viewport,
|
||||
in.target, in.params.viewport, filter);
|
||||
}
|
||||
driver.setMinMaxLevels(hwOut, 0, inoutBloomOptions.levels - 1);
|
||||
});
|
||||
|
||||
return { bloomUpsamplePass->out, flare };
|
||||
@@ -3036,7 +3051,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::vsmMipmapPass(FrameGraph& fg
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
mi->setParameter("level", uint32_t(level));
|
||||
mi->setParameter("layer", uint32_t(layer));
|
||||
mi->setParameter("uvscale", 1.0f / float(dim));
|
||||
mi->commit(driver);
|
||||
|
||||
@@ -292,6 +292,11 @@ private:
|
||||
FrameGraphId<FrameGraphTexture> input, backend::TextureFormat outFormat,
|
||||
BloomOptions& inoutBloomOptions, math::float2 scale) noexcept;
|
||||
|
||||
FrameGraphId<FrameGraphTexture> downscalePass(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor const& outDesc,
|
||||
bool threshold, float highlight, bool fireflies) noexcept;
|
||||
|
||||
void commitAndRender(FrameGraphResources::RenderPassInfo const& out,
|
||||
PostProcessMaterial const& material, uint8_t variant,
|
||||
backend::DriverApi& driver) const noexcept;
|
||||
|
||||
@@ -1080,8 +1080,9 @@ void FView::setSoftShadowOptions(SoftShadowOptions options) noexcept {
|
||||
|
||||
void FView::setBloomOptions(BloomOptions options) noexcept {
|
||||
options.dirtStrength = math::saturate(options.dirtStrength);
|
||||
options.levels = math::clamp(options.levels, uint8_t(1), uint8_t(11));
|
||||
options.resolution = math::clamp(options.resolution, 1u << options.levels, 2048u);
|
||||
options.resolution = math::clamp(options.resolution, 2u, 2048u);
|
||||
options.levels = math::clamp(options.levels, uint8_t(1),
|
||||
FTexture::maxLevelCount(options.resolution));
|
||||
options.anamorphism = math::clamp(options.anamorphism, 1.0f/32.0f, 32.0f);
|
||||
options.highlight = std::max(10.0f, options.highlight);
|
||||
mBloomOptions = options;
|
||||
|
||||
@@ -5,18 +5,6 @@ material {
|
||||
type : sampler2d,
|
||||
name : source,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : level
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : threshold
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : invHighlight
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
@@ -37,85 +25,43 @@ fragment {
|
||||
|
||||
void dummy(){}
|
||||
|
||||
void threshold(inout vec3 c) {
|
||||
// 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) {
|
||||
return (s0 + s1 + s2 + s3) * 0.25;
|
||||
}
|
||||
|
||||
vec3 box4x4Reinhard(vec3 s0, vec3 s1, vec3 s2, vec3 s3) {
|
||||
float w0 = 1.0 / (1.0 + max3(s0));
|
||||
float w1 = 1.0 / (1.0 + max3(s1));
|
||||
float w2 = 1.0 / (1.0 + max3(s2));
|
||||
float w3 = 1.0 / (1.0 + max3(s3));
|
||||
return (s0 * w0 + s1 * w1 + s2 * w2 + s3 * w3) * (1.0 / (w0 + w1 + w2 + w3));
|
||||
}
|
||||
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
float lod = materialParams.level;
|
||||
highp vec2 uv = variable_vertex.xy;
|
||||
|
||||
// see SIGGRAPH 2014: Advances in Real-Time Rendering
|
||||
// "Next Generation Post-Processing in Call of Duty Advanced Warfare"
|
||||
// Jorge Jimenez
|
||||
vec3 c = textureLod(materialParams_source, uv, lod).rgb;
|
||||
vec3 c = textureLod(materialParams_source, uv, 0.0).rgb;
|
||||
|
||||
// The offsets below are in "source" texture space
|
||||
vec3 lt = textureLodOffset(materialParams_source, uv, lod, ivec2(-1, -1)).rgb;
|
||||
vec3 rt = textureLodOffset(materialParams_source, uv, lod, ivec2( 1, -1)).rgb;
|
||||
vec3 rb = textureLodOffset(materialParams_source, uv, lod, ivec2( 1, 1)).rgb;
|
||||
vec3 lb = textureLodOffset(materialParams_source, uv, lod, ivec2(-1, 1)).rgb;
|
||||
vec3 lt = textureLodOffset(materialParams_source, uv, 0.0, ivec2(-1, -1)).rgb;
|
||||
vec3 rt = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 1, -1)).rgb;
|
||||
vec3 rb = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 1, 1)).rgb;
|
||||
vec3 lb = textureLodOffset(materialParams_source, uv, 0.0, ivec2(-1, 1)).rgb;
|
||||
|
||||
vec3 lt2 = textureLodOffset(materialParams_source, uv, lod, ivec2(-2, -2)).rgb;
|
||||
vec3 rt2 = textureLodOffset(materialParams_source, uv, lod, ivec2( 2, -2)).rgb;
|
||||
vec3 rb2 = textureLodOffset(materialParams_source, uv, lod, ivec2( 2, 2)).rgb;
|
||||
vec3 lb2 = textureLodOffset(materialParams_source, uv, lod, ivec2(-2, 2)).rgb;
|
||||
vec3 lt2 = textureLodOffset(materialParams_source, uv, 0.0, ivec2(-2, -2)).rgb;
|
||||
vec3 rt2 = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 2, -2)).rgb;
|
||||
vec3 rb2 = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 2, 2)).rgb;
|
||||
vec3 lb2 = textureLodOffset(materialParams_source, uv, 0.0, ivec2(-2, 2)).rgb;
|
||||
|
||||
vec3 l = textureLodOffset(materialParams_source, uv, lod, ivec2(-2, 0)).rgb;
|
||||
vec3 t = textureLodOffset(materialParams_source, uv, lod, ivec2( 0, -2)).rgb;
|
||||
vec3 r = textureLodOffset(materialParams_source, uv, lod, ivec2( 2, 0)).rgb;
|
||||
vec3 b = textureLodOffset(materialParams_source, uv, lod, ivec2( 0, 2)).rgb;
|
||||
vec3 l = textureLodOffset(materialParams_source, uv, 0.0, ivec2(-2, 0)).rgb;
|
||||
vec3 t = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 0, -2)).rgb;
|
||||
vec3 r = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 2, 0)).rgb;
|
||||
vec3 b = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 0, 2)).rgb;
|
||||
|
||||
// five h4x4 boxes
|
||||
vec3 c0, c1;
|
||||
|
||||
if (materialParams.level <= 0.5) {
|
||||
if (materialParams.threshold > 0.0) {
|
||||
// Threshold the first level blur
|
||||
threshold(c);
|
||||
threshold(lt);
|
||||
threshold(rt);
|
||||
threshold(rb);
|
||||
threshold(lb);
|
||||
threshold(lt2);
|
||||
threshold(rt2);
|
||||
threshold(rb2);
|
||||
threshold(lb2);
|
||||
threshold(l);
|
||||
threshold(t);
|
||||
threshold(r);
|
||||
threshold(b);
|
||||
}
|
||||
// Also apply fireflies (flickering) filtering
|
||||
c0 = box4x4Reinhard(lt, rt, rb, lb);
|
||||
c1 = box4x4Reinhard(c, l, t, lt2);
|
||||
c1 += box4x4Reinhard(c, r, t, rt2);
|
||||
c1 += box4x4Reinhard(c, r, b, rb2);
|
||||
c1 += box4x4Reinhard(c, l, b, lb2);
|
||||
} else {
|
||||
// common case
|
||||
c0 = box4x4(lt, rt, rb, lb);
|
||||
c1 = box4x4(c, l, t, lt2);
|
||||
c1 += box4x4(c, r, t, rt2);
|
||||
c1 += box4x4(c, r, b, rb2);
|
||||
c1 += box4x4(c, l, b, lb2);
|
||||
}
|
||||
// common case
|
||||
c0 = box4x4(lt, rt, rb, lb);
|
||||
c1 = box4x4(c, l, t, lt2);
|
||||
c1 += box4x4(c, r, t, rt2);
|
||||
c1 += box4x4(c, r, b, rb2);
|
||||
c1 += box4x4(c, l, b, lb2);
|
||||
|
||||
// weighted average of the five boxes
|
||||
postProcess.color.rgb = c0 * 0.5 + c1 * 0.125;
|
||||
|
||||
98
filament/src/materials/bloom/bloomDownsample2x.mat
Normal file
98
filament/src/materials/bloom/bloomDownsample2x.mat
Normal file
@@ -0,0 +1,98 @@
|
||||
material {
|
||||
name : bloomDownsample2x,
|
||||
parameters : [
|
||||
{
|
||||
type : sampler2d,
|
||||
name : source,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : level
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : threshold
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : fireflies
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : invHighlight
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
vertex
|
||||
],
|
||||
domain : postprocess,
|
||||
depthWrite : false,
|
||||
depthCulling : false
|
||||
}
|
||||
|
||||
vertex {
|
||||
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
|
||||
postProcess.vertex.xy = uvToRenderTargetUV(postProcess.normalizedUV);
|
||||
}
|
||||
}
|
||||
|
||||
fragment {
|
||||
|
||||
void dummy(){}
|
||||
|
||||
void threshold(inout vec3 c) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
float lod = materialParams.level;
|
||||
|
||||
highp vec2 size = vec2(textureSize(materialParams_source, int(lod)));
|
||||
highp vec2 texelSize = vec2(1.0) / size;
|
||||
|
||||
// Castaño, 2013, "Shadow Mapping Summary Part 1"
|
||||
// 3x3 gaussian filter with 4 linear samples
|
||||
vec2 offset = vec2(0.5);
|
||||
highp vec2 uv = (variable_vertex.xy * size) + offset;
|
||||
highp vec2 base = (floor(uv) - offset) * texelSize;
|
||||
highp vec2 st = fract(uv);
|
||||
vec2 uw = vec2(3.0 - 2.0 * st.x, 1.0 + 2.0 * st.x);
|
||||
vec2 vw = vec2(3.0 - 2.0 * st.y, 1.0 + 2.0 * st.y);
|
||||
highp vec2 u = vec2((2.0 - st.x) / uw.x - 1.0, st.x / uw.y + 1.0) * texelSize.x;
|
||||
highp vec2 v = vec2((2.0 - st.y) / vw.x - 1.0, st.y / vw.y + 1.0) * texelSize.y;
|
||||
vec3 c0 = textureLod(materialParams_source, base + vec2(u.x, v.x), lod).rgb;
|
||||
vec3 c1 = textureLod(materialParams_source, base + vec2(u.y, v.x), lod).rgb;
|
||||
vec3 c2 = textureLod(materialParams_source, base + vec2(u.x, v.y), lod).rgb;
|
||||
vec3 c3 = textureLod(materialParams_source, base + vec2(u.y, v.y), lod).rgb;
|
||||
|
||||
float w0 = uw.x * vw.x * (1.0 / 16.0);
|
||||
float w1 = uw.y * vw.x * (1.0 / 16.0);
|
||||
float w2 = uw.x * vw.y * (1.0 / 16.0);
|
||||
float w3 = uw.y * vw.y * (1.0 / 16.0);
|
||||
|
||||
if (materialParams.fireflies > 0.0) {
|
||||
w0 /= (1.0 + max3(c0));
|
||||
w1 /= (1.0 + max3(c1));
|
||||
w2 /= (1.0 + max3(c2));
|
||||
w3 /= (1.0 + max3(c3));
|
||||
float w = 1.0 / (w0 + w1 + w2 + w3);
|
||||
w0 *= w;
|
||||
w1 *= w;
|
||||
w2 *= w;
|
||||
w3 *= w;
|
||||
}
|
||||
|
||||
vec3 c = c0 * w0 + c1 * w1 + c2 * w2 + c3 * w3;
|
||||
|
||||
if (materialParams.threshold > 0.0) {
|
||||
threshold(c);
|
||||
}
|
||||
|
||||
postProcess.color.rgb = c;
|
||||
}
|
||||
}
|
||||
65
filament/src/materials/bloom/bloomDownsample9.mat
Normal file
65
filament/src/materials/bloom/bloomDownsample9.mat
Normal file
@@ -0,0 +1,65 @@
|
||||
material {
|
||||
name : bloomDownsample9,
|
||||
parameters : [
|
||||
{
|
||||
type : sampler2d,
|
||||
name : source,
|
||||
precision: medium
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
vertex
|
||||
],
|
||||
domain : postprocess,
|
||||
depthWrite : false,
|
||||
depthCulling : false
|
||||
}
|
||||
|
||||
vertex {
|
||||
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
|
||||
postProcess.vertex.xy = uvToRenderTargetUV(postProcess.normalizedUV);
|
||||
}
|
||||
}
|
||||
|
||||
fragment {
|
||||
|
||||
void dummy(){}
|
||||
|
||||
// see https://www.shadertoy.com/view/cslczj
|
||||
// 6x6 downsampling kernel implemented via 9 bilinear samples
|
||||
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
highp vec2 uv = variable_vertex.xy;
|
||||
highp vec2 size = vec2(1.0) / vec2(textureSize(materialParams_source, 0));
|
||||
|
||||
float o = 1.5 + 0.261629;
|
||||
float wa = 7.46602 / 32.0;
|
||||
float wb = 1.0 - wa * 2.0;
|
||||
float wab = wa * wb;
|
||||
float waa = wa * wa;
|
||||
float wbb = wb * wb;
|
||||
|
||||
size *= o;
|
||||
|
||||
vec3 c = textureLod(materialParams_source, uv + vec2(0.0) , 0.0).rgb;
|
||||
vec3 l = textureLod(materialParams_source, uv + vec2(-size.x, 0.0), 0.0).rgb;
|
||||
vec3 r = textureLod(materialParams_source, uv + vec2( size.x, 0.0), 0.0).rgb;
|
||||
vec3 b = textureLod(materialParams_source, uv + vec2( 0.0,-size.y), 0.0).rgb;
|
||||
vec3 t = textureLod(materialParams_source, uv + vec2( 0.0, size.y), 0.0).rgb;
|
||||
vec3 lb = textureLod(materialParams_source, uv + vec2(-size.x,-size.y), 0.0).rgb;
|
||||
vec3 rb = textureLod(materialParams_source, uv + vec2( size.x,-size.y), 0.0).rgb;
|
||||
vec3 lt = textureLod(materialParams_source, uv + vec2(-size.x, size.y), 0.0).rgb;
|
||||
vec3 rt = textureLod(materialParams_source, uv + vec2( size.x, size.y), 0.0).rgb;
|
||||
|
||||
postProcess.color.rgb =
|
||||
(c * wbb +
|
||||
(l * wab +
|
||||
(r * wab +
|
||||
(b * wab +
|
||||
(t * wab +
|
||||
(lb * waa +
|
||||
(rb * waa +
|
||||
(lt * waa +
|
||||
(rt * waa)))))))));
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,6 @@ material {
|
||||
type : float4,
|
||||
name : resolution,
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : level
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
@@ -34,16 +30,15 @@ fragment {
|
||||
void dummy(){}
|
||||
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
float lod = materialParams.level;
|
||||
highp vec2 uv = variable_vertex.xy;
|
||||
|
||||
#if FILAMENT_QUALITY < FILAMENT_QUALITY_HIGH
|
||||
highp vec4 d = vec4(materialParams.resolution.zw, -materialParams.resolution.zw) * 0.5;
|
||||
vec3 c;
|
||||
c = textureLod(materialParams_source, uv + d.zw, lod).rgb;
|
||||
c += textureLod(materialParams_source, uv + d.xw, lod).rgb;
|
||||
c += textureLod(materialParams_source, uv + d.xy, lod).rgb;
|
||||
c += textureLod(materialParams_source, uv + d.zy, lod).rgb;
|
||||
c = textureLod(materialParams_source, uv + d.zw, 0.0).rgb;
|
||||
c += textureLod(materialParams_source, uv + d.xw, 0.0).rgb;
|
||||
c += textureLod(materialParams_source, uv + d.xy, 0.0).rgb;
|
||||
c += textureLod(materialParams_source, uv + d.zy, 0.0).rgb;
|
||||
postProcess.color.rgb = c * 0.25;
|
||||
#else
|
||||
// see SIGGRAPH 2014: Advances in Real-Time Rendering
|
||||
@@ -52,15 +47,15 @@ fragment {
|
||||
const float radius = 1.0;
|
||||
highp vec4 d = vec4(materialParams.resolution.zw, -materialParams.resolution.zw) * radius;
|
||||
vec3 c0, c1;
|
||||
c0 = textureLod(materialParams_source, uv + d.zw, lod).rgb;
|
||||
c0 += textureLod(materialParams_source, uv + d.xw, lod).rgb;
|
||||
c0 += textureLod(materialParams_source, uv + d.xy, lod).rgb;
|
||||
c0 += textureLod(materialParams_source, uv + d.zy, lod).rgb;
|
||||
c0 += 4.0 * textureLod(materialParams_source, uv, lod).rgb;
|
||||
c1 = textureLod(materialParams_source, uv + vec2(d.z, 0.0), lod).rgb;
|
||||
c1 += textureLod(materialParams_source, uv + vec2(0.0, d.w), lod).rgb;
|
||||
c1 += textureLod(materialParams_source, uv + vec2(d.x, 0.0), lod).rgb;
|
||||
c1 += textureLod(materialParams_source, uv + vec2( 0.0, d.y), lod).rgb;
|
||||
c0 = textureLod(materialParams_source, uv + d.zw, 0.0).rgb;
|
||||
c0 += textureLod(materialParams_source, uv + d.xw, 0.0).rgb;
|
||||
c0 += textureLod(materialParams_source, uv + d.xy, 0.0).rgb;
|
||||
c0 += textureLod(materialParams_source, uv + d.zy, 0.0).rgb;
|
||||
c0 += 4.0 * textureLod(materialParams_source, uv, 0.0).rgb;
|
||||
c1 = textureLod(materialParams_source, uv + vec2(d.z, 0.0), 0.0).rgb;
|
||||
c1 += textureLod(materialParams_source, uv + vec2(0.0, d.w), 0.0).rgb;
|
||||
c1 += textureLod(materialParams_source, uv + vec2(d.x, 0.0), 0.0).rgb;
|
||||
c1 += textureLod(materialParams_source, uv + vec2( 0.0, d.y), 0.0).rgb;
|
||||
postProcess.color.rgb = (c0 + 2.0 * c1) * (1.0 / 16.0);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -11,10 +11,6 @@ material {
|
||||
name : coc,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : int,
|
||||
name : mip
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : weightScale
|
||||
@@ -65,19 +61,18 @@ void postProcess(inout PostProcessInputs postProcess) {
|
||||
|
||||
// the bilateral weights need to be scaled by to match the lower resolution
|
||||
float weightScale = materialParams.weightScale;
|
||||
float mip = float(materialParams.mip);
|
||||
|
||||
vec4 s01 = textureLodOffset(materialParams_color, uv, mip, ivec2(0, 1));
|
||||
vec4 s11 = textureLodOffset(materialParams_color, uv, mip, ivec2(1, 1));
|
||||
vec4 s10 = textureLodOffset(materialParams_color, uv, mip, ivec2(1, 0));
|
||||
vec4 s00 = textureLodOffset(materialParams_color, uv, mip, ivec2(0, 0));
|
||||
vec4 s01 = textureLodOffset(materialParams_color, uv, 0.0, ivec2(0, 1));
|
||||
vec4 s11 = textureLodOffset(materialParams_color, uv, 0.0, ivec2(1, 1));
|
||||
vec4 s10 = textureLodOffset(materialParams_color, uv, 0.0, ivec2(1, 0));
|
||||
vec4 s00 = textureLodOffset(materialParams_color, uv, 0.0, ivec2(0, 0));
|
||||
|
||||
// fetch the 4 corresponding CoC (textureGather with LOD doesn't exist)
|
||||
vec4 c;
|
||||
c[0] = textureLodOffset(materialParams_coc, uv, mip, ivec2(0, 1)).r;
|
||||
c[1] = textureLodOffset(materialParams_coc, uv, mip, ivec2(1, 1)).r;
|
||||
c[2] = textureLodOffset(materialParams_coc, uv, mip, ivec2(1, 0)).r;
|
||||
c[3] = textureLodOffset(materialParams_coc, uv, mip, ivec2(0, 0)).r;
|
||||
c[0] = textureLodOffset(materialParams_coc, uv, 0.0, ivec2(0, 1)).r;
|
||||
c[1] = textureLodOffset(materialParams_coc, uv, 0.0, ivec2(1, 1)).r;
|
||||
c[2] = textureLodOffset(materialParams_coc, uv, 0.0, ivec2(1, 0)).r;
|
||||
c[3] = textureLodOffset(materialParams_coc, uv, 0.0, ivec2(0, 0)).r;
|
||||
|
||||
float outCoc = downsampleCoC(c);
|
||||
vec4 w = downsampleCocWeights(c, outCoc, weightScale);
|
||||
|
||||
@@ -5,10 +5,6 @@ material {
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : int,
|
||||
name : level
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
@@ -30,9 +26,8 @@ fragment {
|
||||
// We use a rotated grid sub-sample as it's cheap and gives good results
|
||||
// See Scalable Ambient Obscurance by McGuire and al.
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
int level = materialParams.level;
|
||||
ivec2 icoord = ivec2(gl_FragCoord.xy);
|
||||
postProcess.depth = texelFetch(materialParams_depth,
|
||||
2 * icoord + ivec2(icoord.y & 1, icoord.x & 1), level).r;
|
||||
2 * icoord + ivec2(icoord.y & 1, icoord.x & 1), 0).r;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,6 @@ material {
|
||||
type : int,
|
||||
name : layer
|
||||
},
|
||||
{
|
||||
type : int,
|
||||
name : level
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : uvscale
|
||||
@@ -36,7 +32,6 @@ fragment {
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
highp vec2 uv = gl_FragCoord.xy * materialParams.uvscale;
|
||||
postProcess.color = textureLod(materialParams_color,
|
||||
vec3(uv, materialParams.layer),
|
||||
float(materialParams.level));
|
||||
vec3(uv, materialParams.layer), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,6 +249,8 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, BloomOptions* o
|
||||
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, "quality") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->quality);
|
||||
} else if (compare(tok, jsonChunk, "lensFlare") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->lensFlare);
|
||||
} else if (compare(tok, jsonChunk, "starburst") == 0) {
|
||||
@@ -291,6 +293,7 @@ std::ostream& operator<<(std::ostream& out, const BloomOptions& in) {
|
||||
<< "\"threshold\": " << to_string(in.threshold) << ",\n"
|
||||
<< "\"enabled\": " << to_string(in.enabled) << ",\n"
|
||||
<< "\"highlight\": " << (in.highlight) << ",\n"
|
||||
<< "\"quality\": " << (in.quality) << ",\n"
|
||||
<< "\"lensFlare\": " << to_string(in.lensFlare) << ",\n"
|
||||
<< "\"starburst\": " << to_string(in.starburst) << ",\n"
|
||||
<< "\"chromaticAberration\": " << (in.chromaticAberration) << ",\n"
|
||||
|
||||
@@ -777,6 +777,10 @@ void ViewerGui::updateUserInterface() {
|
||||
ImGui::SliderInt("Levels", &levels, 3, 11);
|
||||
mSettings.view.bloom.levels = levels;
|
||||
|
||||
int quality = (int) mSettings.view.bloom.quality;
|
||||
ImGui::SliderInt("Bloom Quality", &quality, 0, 3);
|
||||
mSettings.view.bloom.quality = (View::QualityLevel) quality;
|
||||
|
||||
ImGui::Checkbox("Lens Flare", &mSettings.view.bloom.lensFlare);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ Filament.loadGeneratedExtensions = function() {
|
||||
threshold: true,
|
||||
enabled: false,
|
||||
highlight: 1000.0,
|
||||
quality: Filament.View$QualityLevel.LOW,
|
||||
lensFlare: false,
|
||||
starburst: true,
|
||||
chromaticAberration: 0.005,
|
||||
|
||||
10
web/filament-js/filament.d.ts
vendored
10
web/filament-js/filament.d.ts
vendored
@@ -1253,6 +1253,16 @@ export interface View$BloomOptions {
|
||||
* limit highlights to this value before bloom [10, +inf]
|
||||
*/
|
||||
highlight?: number;
|
||||
/**
|
||||
* Bloom quality level.
|
||||
* LOW (default): use a more optimized down-sampling filter, however there can be artifacts
|
||||
* with dynamic resolution, this can be alleviated by using the homogenous mode.
|
||||
* MEDIUM: Good balance between quality and performance.
|
||||
* HIGH: In this mode the bloom resolution is automatically increased to avoid artifacts.
|
||||
* This mode can be significantly slower on mobile, especially at high resolution.
|
||||
* This mode greatly improves the anamorphic bloom.
|
||||
*/
|
||||
quality?: View$QualityLevel;
|
||||
/**
|
||||
* enable screen-space lens flare
|
||||
*/
|
||||
|
||||
@@ -30,6 +30,7 @@ value_object<View::BloomOptions>("View$BloomOptions")
|
||||
.field("threshold", &View::BloomOptions::threshold)
|
||||
.field("enabled", &View::BloomOptions::enabled)
|
||||
.field("highlight", &View::BloomOptions::highlight)
|
||||
.field("quality", &View::BloomOptions::quality)
|
||||
.field("lensFlare", &View::BloomOptions::lensFlare)
|
||||
.field("starburst", &View::BloomOptions::starburst)
|
||||
.field("chromaticAberration", &View::BloomOptions::chromaticAberration)
|
||||
|
||||
Reference in New Issue
Block a user