Skip to content

DOF - near blur is optional #7220

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading