diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index 69d1cad054..042473a671 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -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 diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index 748ef7ff3e..c0f8d1eb5f 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -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 PostProcessManager::dof(FrameGraph& fg, auto depth = blackboard.get("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 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 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 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 inCocMaxMin; @@ -866,11 +876,12 @@ FrameGraphId PostProcessManager::dof(FrameGraph& fg, for (size_t i = 0; i < tileReductionCount; i++) { auto& ppDoFTiling = fg.addPass("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 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 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 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 PostProcessManager::dof(FrameGraph& fg, driver.endRenderPass(); }); + /* + * DoF median + */ + + + struct PostProcessDofMedian { + FrameGraphId inForeground; + FrameGraphId inAlpha; + FrameGraphId tilesCocMaxMin; + FrameGraphId outForeground; + FrameGraphId outAlpha; + FrameGraphRenderTargetHandle rt; + }; + + auto& ppDoFMedian = fg.addPass("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 PostProcessManager::dof(FrameGraph& fg, auto& ppDoFCombine = fg.addPass("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 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 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)); diff --git a/filament/src/PostProcessManager.h b/filament/src/PostProcessManager.h index 3dcf5b4f40..09707868f3 100644 --- a/filament/src/PostProcessManager.h +++ b/filament/src/PostProcessManager.h @@ -167,6 +167,7 @@ private: PostProcessMaterial mDoF; PostProcessMaterial mDoFTiles; PostProcessMaterial mDoFDilate; + PostProcessMaterial mDoFMedian; PostProcessMaterial mDoFCombine; PostProcessMaterial mBloomDownsample; PostProcessMaterial mBloomUpsample; diff --git a/filament/src/materials/dof.mat b/filament/src/materials/dof.mat index 588ff41f08..3bd70b5ce4 100644 --- a/filament/src/materials/dof.mat +++ b/filament/src/materials/dof.mat @@ -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); diff --git a/filament/src/materials/dofCombine.mat b/filament/src/materials/dofCombine.mat index 5bc070bf46..b561df7b4d 100644 --- a/filament/src/materials/dofCombine.mat +++ b/filament/src/materials/dofCombine.mat @@ -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; } diff --git a/filament/src/materials/dofDownsample.mat b/filament/src/materials/dofDownsample.mat index aa6ce046bb..4d70e185d8 100644 --- a/filament/src/materials/dofDownsample.mat +++ b/filament/src/materials/dofDownsample.mat @@ -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; diff --git a/filament/src/materials/dofMedian.mat b/filament/src/materials/dofMedian.mat new file mode 100644 index 0000000000..b639c5431e --- /dev/null +++ b/filament/src/materials/dofMedian.mat @@ -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; +} + +} diff --git a/filament/src/materials/dofMipmap.mat b/filament/src/materials/dofMipmap.mat index 42b1dab1e5..7dc0f6b377 100644 --- a/filament/src/materials/dofMipmap.mat +++ b/filament/src/materials/dofMipmap.mat @@ -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; diff --git a/filament/src/materials/dofTiles.mat b/filament/src/materials/dofTiles.mat index 3a1e7f3748..0b32fae8d9 100644 --- a/filament/src/materials/dofTiles.mat +++ b/filament/src/materials/dofTiles.mat @@ -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; } } diff --git a/filament/src/materials/dofUtils.fs b/filament/src/materials/dofUtils.fs index 4dbeac62d0..3ed69f4ab8 100644 --- a/filament/src/materials/dofUtils.fs +++ b/filament/src/materials/dofUtils.fs @@ -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) {