Skip to content

Commit

Permalink
DOF - near blur is optional (#7220)
Browse files Browse the repository at this point in the history
* DOF - near blur is optional

* fix merge issue

---------

Co-authored-by: Martin Valigursky <[email protected]>
  • Loading branch information
mvaligursky and Martin Valigursky authored Dec 20, 2024
1 parent 893cd6d commit d0318ba
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 37 deletions.
9 changes: 9 additions & 0 deletions examples/src/examples/graphics/depth-of-field.controls.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => {
link: { observer, path: 'data.dof.enabled' }
})
),
jsx(
LabelGroup,
{ text: 'Near Blur' },
jsx(BooleanInput, {
type: 'toggle',
binding: new BindingTwoWay(),
link: { observer, path: 'data.dof.nearBlur' }
})
),
jsx(
LabelGroup,
{ text: 'Focus Distance' },
Expand Down
2 changes: 2 additions & 0 deletions examples/src/examples/graphics/depth-of-field.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ assetListLoader.load(() => {

// DOF
cameraFrame.dof.enabled = data.get('data.dof.enabled');
cameraFrame.dof.nearBlur = data.get('data.dof.nearBlur');
cameraFrame.dof.focusDistance = data.get('data.dof.focusDistance');
cameraFrame.dof.focusRange = data.get('data.dof.focusRange');
cameraFrame.dof.blurRadius = data.get('data.dof.blurRadius');
Expand Down Expand Up @@ -193,6 +194,7 @@ assetListLoader.load(() => {
},
dof: {
enabled: true,
nearBlur: true,
focusDistance: 200,
focusRange: 100,
blurRadius: 5,
Expand Down
3 changes: 3 additions & 0 deletions src/extras/render-passes/camera-frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera-
*
* @typedef {Object} Dof
* @property {boolean} enabled - Whether DoF is enabled. Defaults to false.
* @property {boolean} nearBlur - Whether the near blur is enabled. Defaults to false.
* @property {number} focusDistance - The distance at which the focus is set. Defaults to 100.
* @property {number} focusRange - The range around the focus distance where the focus is sharp.
* Defaults to 10.
Expand Down Expand Up @@ -265,6 +266,7 @@ class CameraFrame {
*/
dof = {
enabled: false,
nearBlur: false,
focusDistance: 100,
focusRange: 10,
blurRadius: 3,
Expand Down Expand Up @@ -367,6 +369,7 @@ class CameraFrame {
options.ssaoBlurEnabled = ssao.blurEnabled;
options.formats = rendering.renderFormats.slice();
options.dofEnabled = this.dof.enabled;
options.dofNearBlur = this.dof.nearBlur;
options.dofHighQuality = this.dof.highQuality;
}

Expand Down
5 changes: 4 additions & 1 deletion src/extras/render-passes/render-pass-camera-frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class CameraFrameOptions {
// DOF
dofEnabled = false;

dofNearBlur = false;

dofHighQuality = true;
}

Expand Down Expand Up @@ -186,6 +188,7 @@ class RenderPassCameraFrame extends RenderPass {
options.prepassEnabled !== currentOptions.prepassEnabled ||
options.sceneColorMap !== currentOptions.sceneColorMap ||
options.dofEnabled !== currentOptions.dofEnabled ||
options.dofNearBlur !== currentOptions.dofNearBlur ||
options.dofHighQuality !== currentOptions.dofHighQuality ||
arraysNotEqual(options.formats, currentOptions.formats);
}
Expand Down Expand Up @@ -407,7 +410,7 @@ class RenderPassCameraFrame extends RenderPass {

setupDofPass(options, inputTexture, inputTextureHalf) {
if (options.dofEnabled) {
this.dofPass = new RenderPassDof(this.device, this.cameraComponent, inputTexture, inputTextureHalf, options.dofHighQuality);
this.dofPass = new RenderPassDof(this.device, this.cameraComponent, inputTexture, inputTextureHalf, options.dofHighQuality, options.dofNearBlur);
}
}

Expand Down
18 changes: 12 additions & 6 deletions src/extras/render-passes/render-pass-coc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ChunkUtils } from '../../scene/shader-lib/chunk-utils.js';
/**
* Render pass implementation of a Circle of Confusion texture generation, used by Depth of Field.
* This pass generates a CoC texture based on the scene's depth buffer, and focus range and distance
* parameters. The CoC texture stores near and far CoC values in the red and green channels.
* parameters. The CoC texture stores far and near CoC values in the red and green channels.
*
* @category Graphics
* @ignore
Expand All @@ -15,13 +15,14 @@ class RenderPassCoC extends RenderPassShaderQuad {

focusRange;

constructor(device, cameraComponent) {
constructor(device, cameraComponent, nearBlur) {
super(device);
this.cameraComponent = cameraComponent;

const screenDepth = ChunkUtils.getScreenDepthChunk(device, cameraComponent.shaderParams);
this.shader = this.createQuadShader('CocShader', /* glsl */`
this.shader = this.createQuadShader(`CocShader-${nearBlur}`, /* glsl */`
${nearBlur ? '#define NEAR_BLUR' : ''}
${screenDepth}
varying vec2 uv0;
uniform vec3 params;
Expand All @@ -34,14 +35,19 @@ class RenderPassCoC extends RenderPassShaderQuad {
float focusDistance = params.x;
float focusRange = params.y;
float invRange = params.z;
float nearRange = focusDistance - focusRange * 0.5;
float farRange = focusDistance + focusRange * 0.5;
// near and far CoC
float cocNear = min((nearRange - depth) * invRange, 1.0);
float cocFar = min((depth - farRange) * invRange, 1.0);
gl_FragColor = vec4(cocNear, cocFar, 0.0, 0.0);
#ifdef NEAR_BLUR
float nearRange = focusDistance - focusRange * 0.5;
float cocNear = min((nearRange - depth) * invRange, 1.0);
#else
float cocNear = 0.0;
#endif
gl_FragColor = vec4(cocFar, cocNear, 0.0, 0.0);
}`
);

Expand Down
57 changes: 38 additions & 19 deletions src/extras/render-passes/render-pass-dof-blur.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Kernel } from '../../core/math/kernel.js';
import { RenderPassShaderQuad } from '../../scene/graphics/render-pass-shader-quad.js';

/**
* @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
* @import { Texture } from '../../platform/graphics/texture.js'
*/

/**
* Render pass implementation of a down-sample filter used by the Depth of Field pass. Based on
* a texel of the CoC texture, it generates blurred version of the near or far texture.
Expand All @@ -17,6 +22,12 @@ class RenderPassDofBlur extends RenderPassShaderQuad {

_blurRingPoints = 3;

/**
* @param {GraphicsDevice} device - The graphics device.
* @param {Texture|null} nearTexture - The near texture to blur. Skip near blur if the texture is null.
* @param {Texture} farTexture - The far texture to blur.
* @param {Texture} cocTexture - The CoC texture.
*/
constructor(device, nearTexture, farTexture, cocTexture) {
super(device);
this.nearTexture = nearTexture;
Expand Down Expand Up @@ -59,10 +70,16 @@ class RenderPassDofBlur extends RenderPassShaderQuad {
createShader() {
this.kernel = new Float32Array(Kernel.concentric(this.blurRings, this.blurRingPoints));
const kernelCount = this.kernel.length >> 1;
const nearBlur = this.nearTexture !== null;
const shaderName = `DofBlurShader-${kernelCount}-${nearBlur ? 'nearBlur' : 'noNearBlur'}`;

this.shader = this.createQuadShader(shaderName, /* glsl */`
this.shader = this.createQuadShader(`DofBlurShader-${kernelCount}`, /* glsl */`
${nearBlur ? '#define NEAR_BLUR' : ''}
uniform sampler2D nearTexture;
#if defined(NEAR_BLUR)
uniform sampler2D nearTexture;
#endif
uniform sampler2D farTexture;
uniform sampler2D cocTexture;
uniform float blurRadiusNear;
Expand All @@ -74,39 +91,41 @@ class RenderPassDofBlur extends RenderPassShaderQuad {
void main()
{
vec2 coc = texture2D(cocTexture, uv0).rg;
float cocNear = coc.r;
float cocFar = coc.g;
float cocFar = coc.r;
vec3 sum = vec3(0.0, 0.0, 0.0);
// near blur
if (cocNear > 0.0001) {
#if defined(NEAR_BLUR)
// near blur
float cocNear = coc.g;
if (cocNear > 0.0001) {
ivec2 nearTextureSize = textureSize(nearTexture, 0);
vec2 step = cocNear * blurRadiusNear / vec2(nearTextureSize);
ivec2 nearTextureSize = textureSize(nearTexture, 0);
vec2 step = cocNear * blurRadiusNear / vec2(nearTextureSize);
for (int i = 0; i < ${kernelCount}; i++)
{
vec2 uv = uv0 + step * kernel[i];
vec3 tap = texture2DLod(nearTexture, uv, 0.0).rgb;
sum += tap.rgb;
}
for (int i = 0; i < ${kernelCount}; i++) {
vec2 uv = uv0 + step * kernel[i];
vec3 tap = texture2DLod(nearTexture, uv, 0.0).rgb;
sum += tap.rgb;
}
sum *= ${1.0 / kernelCount};
sum *= ${1.0 / kernelCount};
} else if (cocFar > 0.0001) { // far blur
} else
#endif
if (cocFar > 0.0001) { // far blur
ivec2 farTextureSize = textureSize(farTexture, 0);
vec2 step = cocFar * blurRadiusFar / vec2(farTextureSize);
float sumCoC = 0.0;
for (int i = 0; i < ${kernelCount}; i++)
{
for (int i = 0; i < ${kernelCount}; i++) {
vec2 uv = uv0 + step * kernel[i];
vec3 tap = texture2DLod(farTexture, uv, 0.0).rgb;
// block out sharp objects to avoid leaking to far blur
float cocThis = texture2DLod(cocTexture, uv, 0.0).g;
float cocThis = texture2DLod(cocTexture, uv, 0.0).r;
tap *= cocThis;
sumCoC += cocThis;
Expand Down
25 changes: 14 additions & 11 deletions src/extras/render-passes/render-pass-dof.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Color } from '../../core/math/color.js';
import { Texture } from '../../platform/graphics/texture.js';
import { RenderTarget } from '../../platform/graphics/render-target.js';
import { RenderPass } from '../../platform/graphics/render-pass.js';
import { FILTER_LINEAR, ADDRESS_CLAMP_TO_EDGE, PIXELFORMAT_RGBA8 } from '../../platform/graphics/constants.js';
import { FILTER_LINEAR, ADDRESS_CLAMP_TO_EDGE, PIXELFORMAT_RG8, PIXELFORMAT_R8 } from '../../platform/graphics/constants.js';

import { RenderPassDownsample } from './render-pass-downsample.js';
import { RenderPassCoC } from './render-pass-coc.js';
Expand Down Expand Up @@ -53,13 +53,14 @@ class RenderPassDof extends RenderPass {
* @param {Texture} sceneTexture - The full resolution texture.
* @param {Texture} sceneTextureHalf - The half resolution texture.
* @param {boolean} highQuality - Whether to use high quality setup.
* @param {boolean} nearBlur - Whether to apply near blur.
*/
constructor(device, cameraComponent, sceneTexture, sceneTextureHalf, highQuality) {
constructor(device, cameraComponent, sceneTexture, sceneTextureHalf, highQuality, nearBlur) {
super(device);
this.highQuality = highQuality;

// full resolution CoC texture
this.cocPass = this.setupCocPass(device, cameraComponent, sceneTexture);
this.cocPass = this.setupCocPass(device, cameraComponent, sceneTexture, nearBlur);
this.beforePasses.push(this.cocPass);

// prepare the source image for the background blur, half or quarter resolution
Expand All @@ -72,7 +73,7 @@ class RenderPassDof extends RenderPass {
// Low quality: far texture was resized from half to quarter resolution
// In both cases, the near texture is supplied scene half resolution
// the result is a blurred texture, full or quarter resolution based on quality
this.blurPass = this.setupBlurPass(device, sceneTextureHalf, highQuality ? 2 : 0.5);
this.blurPass = this.setupBlurPass(device, sceneTextureHalf, nearBlur, highQuality ? 2 : 0.5);
this.beforePasses.push(this.blurPass);
}

Expand Down Expand Up @@ -104,13 +105,15 @@ class RenderPassDof extends RenderPass {
}
}

setupCocPass(device, cameraComponent, sourceTexture) {
setupCocPass(device, cameraComponent, sourceTexture, nearBlur) {

// render full resolution CoC texture, R - near CoC, G - far CoC
this.cocRT = this.createRenderTarget('CoCTexture', PIXELFORMAT_RGBA8);
// render full resolution CoC texture, R - far CoC, G - near CoC
// when near blur is not enabled, we only need format with R channel
const format = nearBlur ? PIXELFORMAT_RG8 : PIXELFORMAT_R8;
this.cocRT = this.createRenderTarget('CoCTexture', format);
this.cocTexture = this.cocRT.colorBuffer;

const cocPass = new RenderPassCoC(device, cameraComponent);
const cocPass = new RenderPassCoC(device, cameraComponent, nearBlur);
cocPass.init(this.cocRT, {
resizeSource: sourceTexture
});
Expand All @@ -125,7 +128,7 @@ class RenderPassDof extends RenderPass {
const farPass = new RenderPassDownsample(device, sourceTexture, {
boxFilter: true,
premultiplyTexture: this.cocTexture,
premultiplySrcChannel: 'g' // far CoC
premultiplySrcChannel: 'r' // far CoC
});

farPass.init(this.farRt, {
Expand All @@ -137,11 +140,11 @@ class RenderPassDof extends RenderPass {
return farPass;
}

setupBlurPass(device, nearTexture, scale) {
setupBlurPass(device, nearTexture, nearBlur, scale) {
const farTexture = this.farRt?.colorBuffer;
this.blurRt = this.createRenderTarget('DofBlurTexture', nearTexture.format);
this.blurTexture = this.blurRt.colorBuffer;
const blurPass = new RenderPassDofBlur(device, nearTexture, farTexture, this.cocTexture);
const blurPass = new RenderPassDofBlur(device, nearBlur ? nearTexture : null, farTexture, this.cocTexture);
blurPass.init(this.blurRt, {
resizeSource: nearTexture,
scaleX: scale,
Expand Down

0 comments on commit d0318ba

Please sign in to comment.