DoF: fix many quality/performance issues

- make sure all downsampling is exactly correct and use proper
  uv values to access these textures. This stabilizes the DoF a bit.

- for now only use NEAREST filtering, as bilinear can't be used naively.

- noise is now mandatory, fix noise radius

- add a median pass (performance is not impacted because DoF is faster
  due to using NEAREST filtering)

- fix  be consistant in how the kernel radius is computed, fix a couple
  errors in that area.

- change ring density from 8 to 4 (but starting with 8), so each new
  ring is 4 samples larger than the previous. This reduces the number
  of samples needed from 25 to 21 on mobile, and 49 to 37 on desktop. 
  It also makes it conceivable to use 5 rings (57 samples). Quality is
  not visibly impacted because the center ring is still 8 samples.
  This density, matches the number of samples required for "square"
  rings.

- fix scaling issue with diaphragm simulation

- fix typos in background accumulation code -- this fixes a lot of 
  quality issues.

- improve performance of "fast tiles" during both DoF and combine passes

Overall the effect runs about 0.5ms faster and quality is greatly
improved.
This commit is contained in:
Pixelflinger
2020-07-07 17:06:46 -07:00
committed by Mathias Agopian
parent 71f256585d
commit 27c4174abd
10 changed files with 431 additions and 168 deletions

View File

@@ -155,6 +155,7 @@ set(MATERIAL_SRCS
src/materials/dofTiles.mat
src/materials/dofDilate.mat
src/materials/dofMipmap.mat
src/materials/dofMedian.mat
src/materials/blitLow.mat
src/materials/blitMedium.mat
src/materials/blitHigh.mat
@@ -267,6 +268,12 @@ add_custom_command(
APPEND
)
add_custom_command(
OUTPUT "${MATERIAL_DIR}/dofMedian.filamat"
DEPENDS src/materials/dofUtils.fs
APPEND
)
add_custom_command(
OUTPUT "${MATERIAL_DIR}/dofCombine.filamat"
DEPENDS src/materials/dofUtils.fs

View File

@@ -163,6 +163,7 @@ void PostProcessManager::init() noexcept {
mDoFTiles = { engine, MATERIAL(DOFTILES) };
mDoFDilate = { engine, MATERIAL(DOFDILATE) };
mDoF = { engine, MATERIAL(DOF) };
mDoFMedian = { engine, MATERIAL(DOFMEDIAN) };
mDoFCombine = { engine, MATERIAL(DOFCOMBINE) };
if (driver.isFrameBufferFetchSupported()) {
mColorGradingAsSubpass = { engine, MATERIAL(COLORGRADINGASSUBPASS) };
@@ -211,6 +212,7 @@ void PostProcessManager::terminate(DriverApi& driver) noexcept {
mDoFTiles.terminate(engine);
mDoFDilate.terminate(engine);
mDoF.terminate(engine);
mDoFMedian.terminate(engine);
mDoFCombine.terminate(engine);
}
@@ -708,11 +710,14 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
auto depth = blackboard.get<FrameGraphTexture>("depth");
assert(depth.isValid());
auto const& inputDesc = fg.getDescriptor(input);
const uint32_t width = (inputDesc.width + 1) / 2;
const uint32_t height = (inputDesc.height + 1) / 2;
// the downsampled target is multiple of 8, so we can have 4 clean mipmap levels
constexpr const uint32_t maxMipLevels = 4u;
constexpr const uint32_t maxMipLevelsMask = (1u << maxMipLevels) - 1u;
auto const& colorDesc = fg.getDescriptor(input);
const uint32_t width = ((colorDesc.width + maxMipLevelsMask) & ~maxMipLevelsMask) / 2;
const uint32_t height = ((colorDesc.height + maxMipLevelsMask) & ~maxMipLevelsMask) / 2;
const uint8_t maxLevelCount = FTexture::maxLevelCount(width, height);
uint8_t mipmapCount = min(maxLevelCount, uint8_t(4));
uint8_t mipmapCount = min(maxLevelCount, uint8_t(maxMipLevels));
/*
* Setup:
@@ -772,6 +777,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
mi->setParameter("color", color, { .filterMin = SamplerMinFilter::NEAREST });
mi->setParameter("depth", depth, { .filterMin = SamplerMinFilter::NEAREST });
mi->setParameter("cocParams", cocParams);
mi->setParameter("uvscale", float4{ width, height, 1.0f / colorDesc.width, 1.0f / colorDesc.height });
mi->commit(driver);
mi->use(driver);
PipelineState pipeline(material.getPipelineState(variant));
@@ -804,6 +810,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
data.inOutBackground = builder.write(data.inOutBackground);
data.inOutCocFgBg = builder.write(data.inOutCocFgBg);
for (size_t i = 0; i < mipmapCount - 1u; i++) {
// make sure inputs are always multiple of two (should be true by construction)
// (this is so that we can compute clean mip levels)
assert((FTexture::valueForLevel(uint8_t(i), fg.getDescriptor(data.inOutForeground).width ) & 0x1) == 0);
assert((FTexture::valueForLevel(uint8_t(i), fg.getDescriptor(data.inOutForeground).height) & 0x1) == 0);
using Attachment = FrameGraphRenderTarget::Attachments::AttachmentInfo;
data.rt[i] = builder.createRenderTarget("DoF Target", {
.attachments = {
@@ -853,9 +863,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
// Match this with TILE_SIZE in dofDilate.mat
const size_t tileSize = 16; // size of the tile in full resolution pixel
size_t tileReductionCount = std::log2(tileSize) - 1.0; // -1 because we start from half-resolution
tileReductionCount = min(tileReductionCount,
(size_t)FTexture::maxLevelCount((width + 1) / 2, (height + 1) / 2));
const uint32_t tileBufferWidth = ((colorDesc.width + (tileSize - 1u)) & ~(tileSize - 1u)) / 4u;
const uint32_t tileBufferHeight = ((colorDesc.height + (tileSize - 1u)) & ~(tileSize - 1u)) / 4u;
const size_t tileReductionCount = std::log2(tileSize) - 1.0; // -1 because we start from half-resolution
struct PostProcessDofTiling1 {
FrameGraphId<FrameGraphTexture> inCocMaxMin;
@@ -866,11 +876,12 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
for (size_t i = 0; i < tileReductionCount; i++) {
auto& ppDoFTiling = fg.addPass<PostProcessDofTiling1>("DoF Tiling",
[&](FrameGraph::Builder& builder, auto& data) {
auto const& inputDesc = fg.getDescriptor(inTilesCocMaxMin);
assert((tileBufferWidth & 1u) == 0);
assert((tileBufferHeight & 1u) == 0);
data.inCocMaxMin = builder.sample(inTilesCocMaxMin);
data.outTilesCocMaxMin = builder.createTexture("dof tiles output", {
.width = (inputDesc.width + 1) / 2,
.height = (inputDesc.height+ 1) / 2,
.width = tileBufferWidth >> i,
.height = tileBufferHeight >> i,
.format = TextureFormat::RG16F
});
data.outTilesCocMaxMin = builder.write(data.outTilesCocMaxMin);
@@ -880,11 +891,14 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
},
[=](FrameGraphPassResources const& resources,
auto const& data, DriverApi& driver) {
auto const& inputDesc = resources.getDescriptor(data.inCocMaxMin);
auto const& outputDesc = resources.getDescriptor(data.outTilesCocMaxMin);
auto const& out = resources.get(data.rt);
auto inCocMaxMin = resources.getTexture(data.inCocMaxMin);
PostProcessMaterial& material = mDoFTiles;
FMaterialInstance* const mi = material.getMaterialInstance();
mi->setParameter("cocMaxMin", inCocMaxMin, { .filterMin = SamplerMinFilter::NEAREST });
mi->setParameter("uvscale", float4{ outputDesc.width, outputDesc.height, 1.0f / inputDesc.width, 1.0f / inputDesc.height });
mi->commit(driver);
mi->use(driver);
PipelineState pipeline(material.getPipelineState());
@@ -961,11 +975,16 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
data.cocFgBg = builder.sample(ppDoFMipmap.getData().inOutCocFgBg);
data.tilesCocMaxMin = builder.sample(dilated);
auto const& inputDesc = fg.getDescriptor(data.foreground);
data.outForeground = builder.createTexture("dof color output", inputDesc);
// The DoF buffer (output) doesn't need to be a multiple of 8 because it's not
// mipmapped. We just need to adjust the uv properly.
data.outForeground = builder.createTexture("dof color output", {
.width = (colorDesc.width + 1) / 2,
.height = (colorDesc.height + 1) / 2,
.format = fg.getDescriptor(data.foreground).format
});
data.outAlpha = builder.createTexture("dof alpha output", {
.width = inputDesc.width,
.height = inputDesc.height,
.width = (colorDesc.width + 1) / 2,
.height = (colorDesc.height + 1) / 2,
.format = TextureFormat::R8
});
data.outForeground = builder.write(data.outForeground);
@@ -984,15 +1003,24 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
auto cocFgBg = resources.getTexture(data.cocFgBg);
auto tilesCocMaxMin = resources.getTexture(data.tilesCocMaxMin);
auto const& desc = resources.getDescriptor(data.cocFgBg);
auto const& inputDesc = resources.getDescriptor(data.cocFgBg);
auto const& outputDesc = resources.getDescriptor(data.outForeground);
auto const& tilesDesc = resources.getDescriptor(data.tilesCocMaxMin);
PostProcessMaterial& material = mDoF;
FMaterialInstance* const mi = material.getMaterialInstance();
mi->setParameter("foreground", foreground, { .filterMin = SamplerMinFilter::LINEAR_MIPMAP_LINEAR });
mi->setParameter("background", background, { .filterMin = SamplerMinFilter::LINEAR_MIPMAP_LINEAR });
mi->setParameter("cocFgBg", cocFgBg, { .filterMin = SamplerMinFilter::LINEAR_MIPMAP_LINEAR });
// it's not safe to use bilinear filtering in the general case (causes artifacts around edges)
mi->setParameter("foreground", foreground, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
mi->setParameter("background", background, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
mi->setParameter("cocFgBg", cocFgBg, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
mi->setParameter("tiles", tilesCocMaxMin, { .filterMin = SamplerMinFilter::NEAREST });
mi->setParameter("fullResPixelSize", 0.5f / float2{ desc.width, desc.height });
mi->setParameter("cocToTexelOffset", 0.5f / float2{ inputDesc.width, inputDesc.height });
mi->setParameter("uvscale", float4{
outputDesc.width / float(inputDesc.width),
outputDesc.height / float(inputDesc.height),
outputDesc.width / (tileSize * 0.5f * tilesDesc.width),
outputDesc.height / (tileSize * 0.5f * tilesDesc.height)
});
mi->setParameter("bokehAngle", bokehAngle);
mi->commit(driver);
mi->use(driver);
@@ -1002,6 +1030,65 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
driver.endRenderPass();
});
/*
* DoF median
*/
struct PostProcessDofMedian {
FrameGraphId<FrameGraphTexture> inForeground;
FrameGraphId<FrameGraphTexture> inAlpha;
FrameGraphId<FrameGraphTexture> tilesCocMaxMin;
FrameGraphId<FrameGraphTexture> outForeground;
FrameGraphId<FrameGraphTexture> outAlpha;
FrameGraphRenderTargetHandle rt;
};
auto& ppDoFMedian = fg.addPass<PostProcessDofMedian>("DoF Median",
[&](FrameGraph::Builder& builder, auto& data) {
data.inForeground = builder.sample(ppDoF.getData().outForeground);
data.inAlpha = builder.sample(ppDoF.getData().outAlpha);
data.tilesCocMaxMin = builder.sample(dilated);
data.outForeground = builder.createTexture("dof color output", fg.getDescriptor(data.inForeground));
data.outAlpha = builder.createTexture("dof alpha output", fg.getDescriptor(data.inAlpha));
data.outForeground = builder.write(data.outForeground);
data.outAlpha = builder.write(data.outAlpha);
data.rt = builder.createRenderTarget("DoF Target", {
.attachments = { { data.outForeground,
data.outAlpha, {}, {} }, {}, {} }
});
},
[=](FrameGraphPassResources const& resources,
auto const& data, DriverApi& driver) {
auto const& out = resources.get(data.rt);
auto inForeground = resources.getTexture(data.inForeground);
auto inAlpha = resources.getTexture(data.inAlpha);
auto tilesCocMaxMin = resources.getTexture(data.tilesCocMaxMin);
auto const& outputDesc = resources.getDescriptor(data.outForeground);
auto const& tilesDesc = resources.getDescriptor(data.tilesCocMaxMin);
PostProcessMaterial& material = mDoFMedian;
FMaterialInstance* const mi = material.getMaterialInstance();
mi->setParameter("dof", inForeground, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
mi->setParameter("alpha", inAlpha, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
mi->setParameter("tiles", tilesCocMaxMin, { .filterMin = SamplerMinFilter::NEAREST });
mi->setParameter("uvscale", float2{
outputDesc.width / (tileSize * 0.5f * tilesDesc.width),
outputDesc.height / (tileSize * 0.5f * tilesDesc.height)
});
mi->commit(driver);
mi->use(driver);
PipelineState pipeline(material.getPipelineState(variant));
driver.beginRenderPass(out.target, out.params);
driver.draw(pipeline, fullScreenRenderPrimitive);
driver.endRenderPass();
});
/*
* DoF recombine
*/
@@ -1018,8 +1105,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
auto& ppDoFCombine = fg.addPass<PostProcessDofCombine>("DoF combine",
[&](FrameGraph::Builder& builder, auto& data) {
data.color = builder.sample(input);
data.dof = builder.sample(ppDoF.getData().outForeground);
data.alpha = builder.sample(ppDoF.getData().outAlpha);
data.dof = builder.sample(ppDoFMedian.getData().outForeground);
data.alpha = builder.sample(ppDoFMedian.getData().outAlpha);
data.tilesCocMaxMin = builder.sample(dilated);
auto const& inputDesc = fg.getDescriptor(data.color);
data.output = builder.createTexture("dof output", inputDesc);
@@ -1030,6 +1117,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
},
[=](FrameGraphPassResources const& resources,
auto const& data, DriverApi& driver) {
auto const& dofDesc = resources.getDescriptor(data.dof);
auto const& tilesDesc = resources.getDescriptor(data.tilesCocMaxMin);
auto const& out = resources.get(data.rt);
auto color = resources.getTexture(data.color);
@@ -1040,9 +1129,15 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
PostProcessMaterial& material = mDoFCombine;
FMaterialInstance* const mi = material.getMaterialInstance();
mi->setParameter("color", color, { .filterMin = SamplerMinFilter::NEAREST });
mi->setParameter("dof", dof, { .filterMag = SamplerMagFilter::LINEAR });
mi->setParameter("alpha", alpha, { .filterMag = SamplerMagFilter::LINEAR });
mi->setParameter("dof", dof, { .filterMag = SamplerMagFilter::NEAREST });
mi->setParameter("alpha", alpha, { .filterMag = SamplerMagFilter::NEAREST });
mi->setParameter("tiles", tilesCocMaxMin, { .filterMin = SamplerMinFilter::NEAREST });
mi->setParameter("uvscale", float4{
colorDesc.width / (dofDesc.width * 2.0f),
colorDesc.height / (dofDesc.height * 2.0f),
colorDesc.width / (tilesDesc.width * float(tileSize)),
colorDesc.height / (tilesDesc.height * float(tileSize))
});
mi->commit(driver);
mi->use(driver);
PipelineState pipeline(material.getPipelineState(variant));

View File

@@ -167,6 +167,7 @@ private:
PostProcessMaterial mDoF;
PostProcessMaterial mDoFTiles;
PostProcessMaterial mDoFDilate;
PostProcessMaterial mDoFMedian;
PostProcessMaterial mDoFCombine;
PostProcessMaterial mBloomDownsample;
PostProcessMaterial mBloomUpsample;

View File

@@ -23,7 +23,12 @@ material {
},
{
type : float2,
name : fullResPixelSize,
name : cocToTexelOffset,
precision: high
},
{
type : float4,
name : uvscale,
precision: high
},
{
@@ -41,7 +46,7 @@ material {
vertex {
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
postProcess.vertex.xy = postProcess.normalizedUV;
postProcess.vertex = postProcess.normalizedUV.xyxy * materialParams.uvscale;
}
}
@@ -53,18 +58,18 @@ fragment {
#define DOF_DIAPHRAGM_STRAIGHT_BLADES 1
#define DOF_DIAPHRAGM DOF_DIAPHRAGM_CIRCLE
// # of sample w.r.t. ring count and density
// 3 rings w/ density 6 : 19
// 3 rings w/ density 8 : 25
// 4 rings w/ density 6 : 37
// 4 rings w/ density 8 : 49
// # of sample w.r.t. ring count and density of 4
// 3 rings : 21
// 4 rings : 37
// 5 rings : 57
// don't change density
#define RING_DENSITY 4
#if defined(TARGET_MOBILE)
#define RING_DENSITY 8
#define RING_COUNT_GATHER 3
#define RING_COUNT_FAST 3
#else
#define RING_DENSITY 8
#define RING_COUNT_GATHER 4
#define RING_COUNT_FAST 4
#endif
@@ -74,10 +79,9 @@ fragment {
// This is here mostly for debugging.
#define KERNEL_USE_MIPMAP 1
// Experimentally, adding spacial noise is not necessary if KERNEL_USE_MIPMAP is enabled,
// however, noise helps a lot otherwise.
// Noise is necessary and even more so when KERNEL_USE_MIPMAP is enabled.
// This is here mostly for debugging.
#define KERNEL_USE_NOISE 0
#define KERNEL_USE_NOISE 1
void dummy(){}
@@ -86,16 +90,12 @@ layout(location = 1) out float outAlpha;
float sampleCount(const float ringCount) {
const float ringDensity = float(RING_DENSITY);
return ((ringDensity * ringCount * (ringCount - 1.0) + 2.0) * 0.5);
return (ringDensity * ringCount * (ringCount + 1.0) - 6.0) * 0.5;
}
float sampleWeight(const float coc, const float mip) {
#if KERNEL_USE_MIPMAP
// When mipmaping is used this creates aliasing artifacts around geometry edges.
// Applying the mip correction factor (as below) helps but doesn't solve the issue.
// In the end, it's better to just assume equal strength for all samples.
// with mipmapping this creates severe aliasing artifacts
return 1.0;
#else
// The contribution of sample is inversely proportional to *it's* area
@@ -108,7 +108,11 @@ float sampleWeight(const float coc, const float mip) {
// The high resolution pixel radius is sqrt(2) * 0.5.
// x2 to scale to 1/4 res, x 2^mip for the current mip.
// ^2 for the area.
#if defined(TARGET_MOBILE)
const float pixelRadiusSquared = 2.0; // (sqrt(2) * 0.5 * 2.0)^2
#else
float pixelRadiusSquared = pow(2.0, mip);
#endif
return (MAX_COC_RADIUS * MAX_COC_RADIUS) / (max(coc * coc, pixelRadiusSquared));
#endif
}
@@ -131,11 +135,12 @@ highp vec2 diaphragm(const highp vec2 center, const vec2 offset) {
return center + offset;
#elif DOF_DIAPHRAGM == DOF_DIAPHRAGM_STRAIGHT_BLADES
// this could be eliminated with a LUT
float bladeCount = 5.0;
const float bladeCount = 6.0;
const float K = cos(0.5 / bladeCount * (2.0 * PI));
float angle = atan(offset.y, offset.x) + materialParams.bokehAngle;
float blade = floor(angle / (2.0 * PI) * bladeCount);
float bladeAngle = (blade + 0.5) / bladeCount * (2.0 * PI);
float s = 1.0 / cos(angle - bladeAngle);
float s = K / cos(angle - bladeAngle);
return center + s * offset;
#endif
}
@@ -148,7 +153,7 @@ highp vec2 diaphragm(const highp vec2 center, const vec2 offset) {
struct Bucket {
vec4 c; // accumulated, weighted color
float cw; // accumulated weight
float o; // # of hits for the ring
float o; // # of miss for the ring
float coc; // accumulated, weighted coc
};
@@ -166,8 +171,10 @@ void initBucket(out Bucket ring) {
void initRing(const float i, const float ringCount, const float kernelSize, out float count, out mat2 r, out vec2 p) {
const float ringDensity = float(RING_DENSITY);
float radius = (kernelSize / ringCount) * i;
count = max(1.0, ringDensity * i);
float radius = (kernelSize / (ringCount - 0.5)) * i;
// this is never called with i == 0
count = ringDensity * (i + 1.0);
float inc = 2.0 * PI / count;
vec2 t = vec2(cos(inc), sin(inc));
@@ -190,9 +197,8 @@ void mergeRings(inout Bucket curr, inout Bucket prev, const float count) {
float prevCocAverage = prev.coc * rcp(prev.cw);
float occluded = saturate(prevCocAverage - currCocAverage);
// Average opacity of current ring is 1.0 - the average hit count for the previous ring
// (we track the previous hit count because it's easier)
float currentRingOpacity = 1.0 - saturate(prev.o * rcp(count));
// Average opacity of current ring is 1.0 - the average miss count
float currentRingOpacity = 1.0 - curr.o * rcp(count);
// Opacity of previous ring
float previousRingWeight = 0.0;
@@ -207,24 +213,34 @@ void mergeRings(inout Bucket curr, inout Bucket prev, const float count) {
}
}
void accumulate(inout Bucket curr, inout Bucket prev, const Sample tap, const float border, const float mip, const bool first) {
void accumulate(inout Bucket curr, inout Bucket prev, const Sample tap,
const float radius, const float border, const float mip, const bool first) {
float inLayer = isBackground(tap.coc);
float coc = abs(tap.coc);
float intersects = intersection(border, coc, mip);
float weight = sampleWeight(coc, mip);
float w = intersects * weight * inLayer;
// The sample has a CoC larger than the border radius, then it belongs to the previous ring,
// on the other hand if its CoC is similar to the border radius, then it belongs to the
// current ring. This is precisely what intersection() returns.
float inPrevious = intersects;
// current ring. This very similar to what intersection() returns.
float inPrevious = saturate(coc - border + 0.5);
// fixme: unclear if we should use border or radius here
// - radius works only when applying the pow() in intersection(), but it actually
// doesn't work that great, because objects in front don't bleed well on the background.
// it also creates black spots. imho, radius is not correct.
// - border works only when NOT applying the pow() in intersections(), which is weird.
// with the pow() it creates strong aliasing artifacts on edges of near in-focus
// geometry. imho, using border is more correct because it matches the curr/prev selection.
//float w = intersection(border, coc, mip) * sampleWeight(coc, mip) * inLayer;
float w = inPrevious * sampleWeight(coc, mip) * inLayer;
if (first) { // (this test is always inlined)
// The outmost ring always accumulate to 'curr'.
curr.c += w * tap.s;
curr.coc += w * coc;
curr.cw += w;
} else {
float currWeight = w * (1.0 - inPrevious);
float inCurrent = 1.0 - inPrevious;
float currWeight = w * inCurrent;
float prevWeight = w * inPrevious;
curr.c += currWeight * tap.s;
@@ -234,34 +250,36 @@ void accumulate(inout Bucket curr, inout Bucket prev, const Sample tap, const fl
prev.c += prevWeight * tap.s;
prev.coc += prevWeight * coc;
prev.cw += prevWeight;
prev.o += inPrevious;
}
curr.o += inPrevious;
}
void accumulateBackground(inout Bucket curr, inout Bucket prev,
const highp vec2 pos, const float border, const float mip, const bool first) {
void accumulateBackground(inout Bucket curr, inout Bucket prev, const highp vec2 pos,
const float radius, const float border, const float mip, const bool first) {
Sample tap;
tap.s = textureLod(materialParams_foreground, pos, mip);
tap.coc = textureLod(materialParams_cocFgBg, pos, mip).r;
accumulate(curr, prev, tap, border, mip, first);
accumulate(curr, prev, tap, radius, border, mip, first);
}
void accumulateBackgroundMirror(inout Bucket curr, inout Bucket prev,
const highp vec2 center, const vec2 offset, const float border, const float mip, const bool first) {
accumulateBackground(curr, prev, diaphragm(center, offset), border, mip, first);
accumulateBackground(curr, prev, diaphragm(center, -offset), border, mip, first);
const highp vec2 center, const vec2 offset,
const float radius, const float border, const float mip, const bool first) {
accumulateBackground(curr, prev, diaphragm(center, offset), radius, border, mip, first);
accumulateBackground(curr, prev, diaphragm(center, -offset), radius, border, mip, first);
}
void accumulateBackgroundCenter(inout Bucket prev,
const highp vec2 pos, const float border, const float mip) {
const highp vec2 pos, const float ringCount, const float kernelSize, const float mip) {
Bucket curr;
initBucket(curr);
accumulateBackground(curr, prev, pos, border, mip, false);
float border = (kernelSize / (ringCount - 0.5)) * 0.5;
accumulateBackground(curr, prev, pos, kernelSize, border, mip, false);
mergeRings(curr, prev, 1.0);
}
void accumulateRing(inout Bucket prev, const float index, const float ringCount, const float kernelSize,
const highp vec2 uvCenter, const highp vec2 fullResPixelSize, const float mip, const bool first) {
const highp vec2 uvCenter, const highp vec2 cocToTexelOffset, const float mip, const bool first) {
// we accumulate the larger rings first
float i = (ringCount - 1.0) - index;
@@ -274,9 +292,9 @@ void accumulateRing(inout Bucket prev, const float index, const float ringCount,
mat2 r;
initRing(i, ringCount, kernelSize, count, r, p);
float border = (kernelSize / ringCount) * (i + 0.5);
float border = (kernelSize / (ringCount - 0.5)) * (i + 0.5);
for (float j = 0.0; j < count; j += 2.0) {
accumulateBackgroundMirror(curr, prev, uvCenter, p * fullResPixelSize, border, mip, first);
accumulateBackgroundMirror(curr, prev, uvCenter, p * cocToTexelOffset, kernelSize, border, mip, first);
p = r * p;
}
@@ -307,8 +325,9 @@ void accumulateForegroundCenter(inout vec4 foreground, inout float opacity, inou
void accumulateForegroundMirror(inout vec4 foreground, inout float opacity, inout float i,
const highp vec2 center, const vec2 offset, const float border, const float mip) {
// The code below is equivalent to:
// accumulateForeground(foreground, opacity, i, center + offset, border, mip);
// accumulateForeground(foreground, opacity, i, center - offset, border, mip);
// accumulateForeground(foreground, opacity, i, diaphragm(center, offset), border, mip);
// accumulateForeground(foreground, opacity, i, diaphragm(center, -offset), border, mip);
// return;
// but selects the min coc of opposite samples as a way to guess the color occluded by
// the geometry.
@@ -330,8 +349,9 @@ void accumulateForegroundMirror(inout vec4 foreground, inout float opacity, inou
float getMipLevel(const float ringCount, const float kernelSize) {
#if KERNEL_USE_MIPMAP
float s = 1.0 / (ringCount + 0.5);
float s = 1.0 / (ringCount - 0.5);
float mip = log2(kernelSize * s);
// the "nearest" mipmap will be chosen
return mip;
#else
return 0.0;
@@ -341,8 +361,7 @@ float getMipLevel(const float ringCount, const float kernelSize) {
void postProcess(inout PostProcessInputs postProcess) {
const float ringCountGather = float(RING_COUNT_GATHER);
const float ringCountFast = float(RING_COUNT_FAST);
highp vec2 uv = variable_vertex.xy;
vec2 tiles = textureLod(materialParams_tiles, uv, 0.0).rg;
vec2 tiles = textureLod(materialParams_tiles, variable_vertex.zw, 0.0).rg;
/*
* Tiles that are neither foreground or background (in focus) can be skipped
@@ -358,13 +377,15 @@ void postProcess(inout PostProcessInputs postProcess) {
vec4 background = vec4(0.0);
// we use the full resolution pixel size because that's the unit the CoC is in
highp vec2 fullResPixelSize = materialParams.fullResPixelSize;
highp vec2 uv = variable_vertex.xy;
highp vec2 cocToTexelOffset = materialParams.cocToTexelOffset;
#if KERNEL_USE_NOISE
// When using mipmaping the noise seems to hurt more than it helps
// A good random generator is essential, this one is okay.
float randomAngle = random(gl_FragCoord.xy) * (2.0 * PI);
float random01 = random(gl_FragCoord.xy * vec2(7.0, 11.0));
vec2 randomUniformDisk = 0.7071 * sqrt(random01) * vec2(cos(randomAngle), sin(randomAngle));
float random01 = random(gl_FragCoord.xy * vec2(5099.0, 3499.0)); // large primes seem to work ok
vec2 randomUniformDisk = 0.5 * sqrt(random01) * vec2(cos(randomAngle), sin(randomAngle));
#else
const vec2 noise = vec2(0.0);
#endif
@@ -372,21 +393,24 @@ void postProcess(inout PostProcessInputs postProcess) {
if (isFastTile(tiles)) {
// for a foreground tile, the kernel size is the largest CoC radius
#if KERNEL_USE_NOISE
vec2 noise = randomUniformDisk * rcp(ringCountFast + 0.5);
vec2 noise = randomUniformDisk * rcp(ringCountFast - 0.5);
#endif
float coc = abs(tiles.r);
float kernelSize = coc;
float mip = getMipLevel(ringCountFast, kernelSize);
vec2 uvCenter = uv + noise * kernelSize * fullResPixelSize;
vec2 uvCenter = uv + noise * kernelSize * cocToTexelOffset;
for (float i = 0.0; i < ringCountFast; i += 1.0) {
foreground = textureLod(materialParams_foreground, uvCenter, mip);
for (float i = 1.0; i < ringCountFast; i += 1.0) {
float count;
vec2 p;
mat2 r;
initRing(i, ringCountFast, kernelSize, count, r, p);
for (float j = 0.0; j < count; j += 1.0) {
vec2 pos = diaphragm(uvCenter, p * fullResPixelSize);
foreground += textureLod(materialParams_foreground, pos, mip);
for (float j = 0.0; j < count; j += 2.0) {
foreground += textureLod(materialParams_foreground,
diaphragm(uvCenter, p * cocToTexelOffset), mip);
foreground += textureLod(materialParams_foreground,
diaphragm(uvCenter, -p * cocToTexelOffset), mip);
p = r * p;
}
}
@@ -408,7 +432,7 @@ void postProcess(inout PostProcessInputs postProcess) {
float fgOpacity = 0.0;
float bgOpacity = 0.0;
#if KERNEL_USE_NOISE
vec2 noise = randomUniformDisk * rcp(ringCountGather + 0.5);
vec2 noise = randomUniformDisk * rcp(ringCountGather - 0.5);
#endif
@@ -416,7 +440,7 @@ void postProcess(inout PostProcessInputs postProcess) {
// for a foreground tile, the kernel size is the largest CoC radius
float kernelSize = -tiles.g;
float mip = getMipLevel(ringCountGather, kernelSize);
vec2 uvCenter = uv + noise * kernelSize * fullResPixelSize;
vec2 uvCenter = uv + noise * kernelSize * cocToTexelOffset;
float c = 0.0;
// the center sample is handled separately, since it's by itself
@@ -424,7 +448,7 @@ void postProcess(inout PostProcessInputs postProcess) {
for (float i = 1.0; i < ringCountGather; i += 1.0) {
float border = (kernelSize / ringCountGather) * i;
float border = (kernelSize / (ringCountGather - 0.5)) * i;
float count;
vec2 p;
mat2 r;
@@ -433,7 +457,7 @@ void postProcess(inout PostProcessInputs postProcess) {
for (float j = 0.0; j < count; j += 2.0) {
accumulateForegroundMirror(foreground, fgOpacity, c,
uvCenter, p * fullResPixelSize, border, mip);
uvCenter, p * cocToTexelOffset, border, mip);
p = r * p;
}
}
@@ -445,6 +469,7 @@ void postProcess(inout PostProcessInputs postProcess) {
foreground *= rcp(c);
fgOpacity *= rcp(sampleCount(ringCountGather));
}
//foreground.r += 0.1;
}
@@ -452,25 +477,23 @@ void postProcess(inout PostProcessInputs postProcess) {
vec2 centerCoc = textureLod(materialParams_cocFgBg, uv, 0.0).rg;
float kernelSize = abs(centerCoc.g);
float mip = getMipLevel(ringCountGather, kernelSize);
vec2 uvCenter = uv + noise * kernelSize * fullResPixelSize;
vec2 uvCenter = uv + noise * kernelSize * cocToTexelOffset;
Bucket prev;
initBucket(prev);
accumulateRing(prev, 0.0, ringCountGather, kernelSize, uvCenter, fullResPixelSize, mip, true);
accumulateRing(prev, 0.0, ringCountGather, kernelSize, uvCenter, cocToTexelOffset, mip, true);
// the optimizer is not able to remove this loop when it has only one iteration
#if RING_COUNT_GATHER == 3
accumulateRing(prev, 1.0, ringCountGather, kernelSize, uvCenter, fullResPixelSize, mip, false);
accumulateRing(prev, 1.0, ringCountGather, kernelSize, uvCenter, cocToTexelOffset, mip, false);
#else
for (float i = 1.0; i < ringCountGather - 1.0 ; i += 1.0) {
accumulateRing(prev, i, ringCountGather, kernelSize, uvCenter, fullResPixelSize, mip, false);
accumulateRing(prev, i, ringCountGather, kernelSize, uvCenter, cocToTexelOffset, mip, false);
}
#endif
// the center sample is handled separately, since it's by itself
float border = (kernelSize / ringCountGather) * 0.5;
accumulateBackgroundCenter(prev, uvCenter, border, mip);
accumulateBackgroundCenter(prev, uvCenter, ringCountGather, kernelSize, mip);
background = prev.c;
bgOpacity = cocToAlpha(prev.coc);

View File

@@ -20,6 +20,11 @@ material {
type : sampler2d,
name : alpha,
precision: medium
},
{
type : float4,
name : uvscale,
precision: high
}
],
variables : [
@@ -32,8 +37,9 @@ material {
vertex {
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
postProcess.vertex.xy = postProcess.normalizedUV;
}
postProcess.vertex.xy = postProcess.normalizedUV;
postProcess.vertex.zw = postProcess.normalizedUV * materialParams.uvscale.zw;
}
}
fragment {
@@ -43,21 +49,22 @@ fragment {
void dummy(){}
void postProcess(inout PostProcessInputs postProcess) {
highp vec2 uv = variable_vertex.xy;
vec2 tiles = textureLod(materialParams_tiles, uv, 0.0).rg;
// above this CoC radius, in-focus layer is not visible
const float opaqueCocRadiusThreshold = 1.0 + MAX_IN_FOCUS_COC;
highp vec2 uv2 = variable_vertex.zw;
vec2 tiles = textureLod(materialParams_tiles, uv2, 0.0).rg;
vec4 color;
if (isTrivialTile(tiles)) {
color = textureLod(materialParams_color, uv, 0.0);
} else if (tiles.g > opaqueCocRadiusThreshold || tiles.r < -opaqueCocRadiusThreshold) {
color = textureLod(materialParams_dof, uv, 0.0);
highp vec2 uv0 = variable_vertex.xy;
color = textureLod(materialParams_color, uv0, 0.0);
} else if (isFastTile(tiles)) {
highp vec2 uv1 = variable_vertex.xy * materialParams.uvscale.xy;
color = textureLod(materialParams_dof, uv1, 0.0);
} else {
float alpha = textureLod(materialParams_alpha, uv, 0.0).r;
vec4 foreground = textureLod(materialParams_dof, uv, 0.0);
color = textureLod(materialParams_color, uv, 0.0);
highp vec2 uv0 = variable_vertex.xy;
highp vec2 uv1 = variable_vertex.xy * materialParams.uvscale.xy;
float alpha = textureLod(materialParams_alpha, uv1, 0.0).r;
vec4 foreground = textureLod(materialParams_dof, uv1, 0.0);
color = textureLod(materialParams_color, uv0, 0.0);
color = foreground + (1.0 - alpha) * color;
}

View File

@@ -14,6 +14,11 @@ material {
{
type : float2,
name : cocParams
},
{
type : float4,
name : uvscale,
precision: high
}
],
variables : [
@@ -25,8 +30,11 @@ material {
}
vertex {
void dummy(){}
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
postProcess.vertex.xy = postProcess.normalizedUV;
postProcess.vertex.xy = (postProcess.normalizedUV * materialParams.uvscale.xy * 2.0 - 0.5) * materialParams.uvscale.zw;
}
}
@@ -40,35 +48,21 @@ layout(location = 2) out vec2 outCocFgBg;
void dummy(){}
void postProcess(inout PostProcessInputs postProcess) {
highp vec2 uv = variable_vertex.xy;
highp vec2 size = vec2(textureSize(materialParams_color, 0));
highp vec2 pixelSize = 1.0 / vec2(textureSize(materialParams_color, 0));
highp vec4 uv = vec4(variable_vertex.xy + pixelSize, variable_vertex.xy);
// Note: we can't assume bilinear weights of 0.25 because there is no guarantee that
// each level is a multiple of two.
// the source is guaranteed to be a multiple of two, so we now the bilinear weights are 0.25
// image coordinates
vec2 coords = uv * size - 0.5;
ivec2 i = ivec2(coords); // floor
// bilinear weights
vec4 f = vec4(fract(coords), 1.0 - fract(coords));
vec4 b = vec4(f.z * f.y, f.x * f.y, f.x * f.w, f.z * f.w);
vec4 s01 = texelFetch(materialParams_color, i + ivec2(0, 1), 0);
vec4 s11 = texelFetch(materialParams_color, i + ivec2(1, 1), 0);
vec4 s10 = texelFetch(materialParams_color, i + ivec2(1, 0), 0);
vec4 s00 = texelFetch(materialParams_color, i, 0);
vec4 s01 = textureLod(materialParams_color, uv.zy, 0.0);
vec4 s11 = textureLod(materialParams_color, uv.xy, 0.0);
vec4 s10 = textureLod(materialParams_color, uv.xw, 0.0);
vec4 s00 = textureLod(materialParams_color, uv.zw, 0.0);
vec4 d;
#if defined(TARGET_VULKAN_ENVIRONMENT) || !defined(TARGET_MOBILE)
d = textureGather(materialParams_depth, uv, 0); // 01, 11, 10, 00
#else
d.x = texelFetch(materialParams_depth, i + ivec2(0, 1), 0).r;
d.y = texelFetch(materialParams_depth, i + ivec2(1, 1), 0).r;
d.z = texelFetch(materialParams_depth, i + ivec2(1, 0), 0).r;
d.w = texelFetch(materialParams_depth, i, 0).r;
#endif
d.x = textureLod(materialParams_depth, uv.zy, 0.0).r;
d.y = textureLod(materialParams_depth, uv.xy, 0.0).r;
d.z = textureLod(materialParams_depth, uv.xw, 0.0).r;
d.w = textureLod(materialParams_depth, uv.zw, 0.0).r;
// Get the CoC radius of each four samples to downsample.
// We multiply by 0.5 to convert from diameter to radius.
@@ -81,14 +75,12 @@ void postProcess(inout PostProcessInputs postProcess) {
// The forground bilateral weight is calculated as saturate(1.0 - (fgCoc - w)), which always
// yields 1.0. Note the "missing" abs(), this is because we want to let the background layer
// leak into the foreground layer, to avoid aliasing artifacts.
vec4 fw = b;
float fgScale = 1.0 / (fw.x + fw.y + fw.z + fw.w);
vec4 foreground = (s01 * fw.x + s11 * fw.y + s10 * fw.z + s00 * fw.w) * fgScale;
vec4 foreground = (s01 + s11 + s10 + s00) * 0.25;
// the background bilateral weight is calculated as saturate(1.0 - abs(bgCoc - w)), but the
// abs() is not needed since bgCoc - w is guaranteed to be >= 0.
float bgCoc = max2(max(w.xy, w.zw));
vec4 bw = saturate(1.0 - (bgCoc - w)) * b;
vec4 bw = saturate(1.0 - (bgCoc - w)) * 0.25;
float bgScale = 1.0 / (bw.x + bw.y + bw.z + bw.w);
vec4 background = (s01 * bw.x + s11 * bw.y + s10 * bw.z + s00 * bw.w) * bgScale;

View File

@@ -0,0 +1,139 @@
material {
name : DepthOfFieldMedian,
parameters : [
{
type : sampler2d,
name : dof,
precision: medium
},
{
type : sampler2d,
name : alpha,
precision: medium
},
{
type : sampler2d,
name : tiles,
precision: medium
},
{
type : float2,
name : uvscale,
precision: high
}
],
variables : [
vertex
],
domain : postprocess,
depthWrite : false,
depthCulling : false
}
vertex {
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
postProcess.vertex.xy = postProcess.normalizedUV;
postProcess.vertex.zw = postProcess.vertex.xy * materialParams.uvscale;
}
}
fragment {
#include "dofUtils.fs"
layout(location = 1) out float outAlpha;
/*
* Based on "A Fast, Small-Radius GPU Median Filter"
* by Morgan McGuire & Williams College
* [https://casual-effects.com/research/McGuire2008Median/index.html]
*/
void sort2(inout vec4 a, inout vec4 b) {
vec4 t = a;
a = min(a, b);
b = max(t, b);
}
void minLeft(inout vec4 a, inout vec4 b, inout vec4 c) {
sort2(a, b);
sort2(a, c);
}
void maxRight(inout vec4 a, inout vec4 b, inout vec4 c) {
sort2(b, c);
sort2(a, c);
}
void minLeftMaxRight(inout vec4 a, inout vec4 b, inout vec4 c) {
maxRight(a, b, c);
sort2(a, b);
}
void minLeftMaxRight(inout vec4 a, inout vec4 b, inout vec4 c, inout vec4 d) {
sort2(a, b);
sort2(c, d);
sort2(a, c);
sort2(b, d);
}
void minLeftMaxRight(inout vec4 a, inout vec4 b, inout vec4 c, inout vec4 d, inout vec4 e) {
sort2(a, b);
sort2(c, d);
minLeft(a, c, e);
maxRight(b, d, e);
}
void minLeftMaxRight(inout vec4 a, inout vec4 b, inout vec4 c, inout vec4 d, inout vec4 e, inout vec4 f) {
sort2(a, d);
sort2(b, e);
sort2(c, f);
minLeft(a, b, c);
maxRight(d, e, f);
}
vec4 makeSample(ivec2 ij) {
vec4 r = vec4(texelFetch(materialParams_dof, ij, 0).rgb,
texelFetch(materialParams_alpha, ij, 0).r);
return r;
}
vec4 median(ivec2 ij) {
vec4 v[9];
v[0] = makeSample(ij + ivec2(-1, -1));
v[1] = makeSample(ij + ivec2( 0, -1));
v[2] = makeSample(ij + ivec2( 1, -1));
v[3] = makeSample(ij + ivec2(-1, 0));
v[4] = makeSample(ij + ivec2( 0, 0));
v[5] = makeSample(ij + ivec2( 1, 0));
v[6] = makeSample(ij + ivec2(-1, 1));
v[7] = makeSample(ij + ivec2( 0, 1));
v[8] = makeSample(ij + ivec2( 1, 1));
// max() is a faster alternative to the median filter, but it looks pretty bad imho
//return max(v[0], max(v[1], max(v[2], max(v[3], max(v[4], max(v[5], max(v[6], max(v[7], v[8]))))))));
// it looks better (than max()) to skip this filter and keep the noise
//return v[4];
minLeftMaxRight(v[0], v[1], v[2], v[3], v[4], v[5]);
minLeftMaxRight(v[1], v[2], v[3], v[4], v[6]);
minLeftMaxRight(v[2], v[3], v[4], v[7]);
minLeftMaxRight(v[3], v[4], v[8]);
return v[4];
}
void postProcess(inout PostProcessInputs postProcess) {
vec2 tiles = textureLod(materialParams_tiles, variable_vertex.zw, 0.0).rg;
vec4 dof = vec4(0.0);
if (!isTrivialTile(tiles)) {
vec2 size = vec2(textureSize(materialParams_dof, 0));
ivec2 ij = ivec2(variable_vertex.xy * size);
dof = median(ij);
}
postProcess.color = dof;
outAlpha = dof.a;
}
}

View File

@@ -49,31 +49,22 @@ layout(location = 2) out vec2 outCocFgBg;
void dummy(){}
void postProcess(inout PostProcessInputs postProcess) {
int mip = materialParams.mip;
highp vec2 size = vec2(textureSize(materialParams_foreground, mip));
highp vec2 uv = variable_vertex.xy;
highp vec2 pixelSize = 1.0 / vec2(textureSize(materialParams_foreground, materialParams.mip));
highp vec2 adjusted_uv = variable_vertex.xy - pixelSize.xy * 0.5;
highp vec4 uv = vec4(adjusted_uv + pixelSize, adjusted_uv);
// Note: we can't assume bilinear weights of 0.25 because there is no guarantee that
// each level is a multiple of two.
// image coordinates
vec2 coords = uv * size - 0.5;
ivec2 i = ivec2(coords); // floor
// bilinear weights
vec4 f = vec4(fract(coords), 1.0 - fract(coords));
vec4 b = vec4(f.z * f.y, f.x * f.y, f.x * f.w, f.z * f.w);
// the source is guaranteed to be a multiple of two, so we now the bilinear weights are 0.25
// the bilateral weights need to be scaled by to match the lower resolution
float weightScale = materialParams.weightScale;
float mip = float(materialParams.mip);
// fetch the 4 corresponding CoC
vec4 d0, d1;
d0.xy = texelFetch(materialParams_cocFgBg, i + ivec2(0, 1), mip).rg;
d0.zw = texelFetch(materialParams_cocFgBg, i + ivec2(1, 1), mip).rg;
d1.xy = texelFetch(materialParams_cocFgBg, i + ivec2(1, 0), mip).rg;
d1.zw = texelFetch(materialParams_cocFgBg, i, mip).rg;
d0.xy = textureLod(materialParams_cocFgBg, uv.zy, mip).rg;
d0.zw = textureLod(materialParams_cocFgBg, uv.xy, mip).rg;
d1.xy = textureLod(materialParams_cocFgBg, uv.xw, mip).rg;
d1.zw = textureLod(materialParams_cocFgBg, uv.zw, mip).rg;
vec4 fw = vec4(d0.xz, d1.xz);
vec4 bw = vec4(d0.yw, d1.yw);
@@ -83,10 +74,10 @@ void postProcess(inout PostProcessInputs postProcess) {
* foreground
*/
vec4 fg01 = texelFetch(materialParams_foreground, i + ivec2(0, 1), mip);
vec4 fg11 = texelFetch(materialParams_foreground, i + ivec2(1, 1), mip);
vec4 fg10 = texelFetch(materialParams_foreground, i + ivec2(1, 0), mip);
vec4 fg00 = texelFetch(materialParams_foreground, i, mip);
vec4 fg01 = textureLod(materialParams_foreground, uv.zy, mip);
vec4 fg11 = textureLod(materialParams_foreground, uv.xy, mip);
vec4 fg10 = textureLod(materialParams_foreground, uv.xw, mip);
vec4 fg00 = textureLod(materialParams_foreground, uv.zw, mip);
// fireflies/flickering filtering
hdr.x = 1.0 / (1.0 + max4(fg00));
@@ -101,7 +92,7 @@ void postProcess(inout PostProcessInputs postProcess) {
// The forground bilateral weight is calculated as saturate(1.0 - (fgCoc - w)), which always
// yields 1.0. Note the "missing" abs(), this is because we want to let the background layer
// leak into the foreground layer, to avoid aliasing artifacts.
fw = hdr * b;
fw = hdr;
float fgScale = 1.0 / (fw.x + fw.y + fw.z + fw.w);
vec4 foreground = (fg01 * fw.x + fg11 * fw.y + fg10 * fw.z + fg00 * fw.w) * fgScale;
@@ -109,10 +100,10 @@ void postProcess(inout PostProcessInputs postProcess) {
* background
*/
vec4 bg01 = texelFetch(materialParams_background, i + ivec2(0, 1), mip);
vec4 bg11 = texelFetch(materialParams_background, i + ivec2(1, 1), mip);
vec4 bg10 = texelFetch(materialParams_background, i + ivec2(1, 0), mip);
vec4 bg00 = texelFetch(materialParams_background, i, mip);
vec4 bg01 = textureLod(materialParams_background, uv.zy, mip);
vec4 bg11 = textureLod(materialParams_background, uv.xy, mip);
vec4 bg10 = textureLod(materialParams_background, uv.xw, mip);
vec4 bg00 = textureLod(materialParams_background, uv.zw, mip);
// fireflies/flickering filtering
hdr.x = 1.0 / (1.0 + max4(bg00));
@@ -124,7 +115,7 @@ void postProcess(inout PostProcessInputs postProcess) {
// The background bilateral weight is calculated as saturate(1.0 - abs(bgCoc - w)),
// but the abs() is not needed since bgCoc - w is guaranteed to be >= 0.
bw = saturate(1.0 - (bgCoc - bw) * weightScale) * hdr * b;
bw = saturate(1.0 - (bgCoc - bw) * weightScale) * hdr;
float bgScale = 1.0 / (bw.x + bw.y + bw.z + bw.w);
vec4 background = (bg01 * bw.x + bg11 * bw.y + bg10 * bw.z + bg00 * bw.w) * bgScale;

View File

@@ -5,6 +5,11 @@ material {
type : sampler2d,
name : cocMaxMin,
precision: medium
},
{
type : float4,
name : uvscale,
precision: high
}
],
variables : [
@@ -17,7 +22,7 @@ material {
vertex {
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
postProcess.vertex.xy = postProcess.normalizedUV;
postProcess.vertex.xy = (postProcess.normalizedUV * materialParams.uvscale.xy * 2.0 - 0.5) * materialParams.uvscale.zw;
}
}

View File

@@ -6,11 +6,14 @@
// Below this value we transition to the in-focus image. This value is chosen as a compromise
// between allowing slight out-of-focus and reducing sampling artifacts around blurry objects
// on sharp background. Based on experiments, it should be between 1.0 and 2.0.
#define MAX_IN_FOCUS_COC 2.0
#define MAX_IN_FOCUS_COC 1.0
// The maximum circle-of-confusion radius we allow in high-resolution pixels.
// This is limited by our tile size (hard limit) and dilate pass as well as the kernel density
// (soft/quality limit).
// This is mostly limited by the kernel density and how many mips we allow -- when the CoC becomes
// too large, the rings start to appear; but worse, the median pass will start erasing pixels
// if the gaps between rings becomes too large. This is also limited by the dilate pass.
// With a minimum ring count of 3 and 4 mip levels, 48 seems to be the maximum allowable.
// Currently out dilate pass is set-up for 32 max.
#define MAX_COC_RADIUS 32.0
float random(const highp vec2 w) {