Skip to content
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

Depth prepass generates linear depth #7119

Merged
merged 3 commits into from
Nov 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
12 changes: 12 additions & 0 deletions examples/src/examples/graphics/taa.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ window.focus();
const assets = {
orbit: new pc.Asset('script', 'script', { url: `${rootPath}/static/scripts/camera/orbit-camera.js` }),
house: new pc.Asset('house', 'container', { url: `${rootPath}/static/assets/models/pbr-house.glb` }),
cube: new pc.Asset('cube', 'container', { url: `${rootPath}/static/assets/models/playcanvas-cube.glb` }),
envatlas: new pc.Asset(
'env-atlas',
'texture',
Expand Down Expand Up @@ -111,6 +112,10 @@ assetListLoader.load(() => {
app.root.addChild(light);
light.setLocalEulerAngles(40, 10, 0);

const cubeEntity = assets.cube.resource.instantiateRenderEntity();
cubeEntity.setLocalScale(30, 30, 30);
app.root.addChild(cubeEntity);

// ------ Custom render passes set up ------

/** @type { CameraFrame } */
Expand Down Expand Up @@ -152,6 +157,13 @@ assetListLoader.load(() => {
jitter: 1
}
});

let time = 0;
app.on('update', (/** @type {number} */ dt) => {
time += dt;
cubeEntity.setLocalPosition(130 * Math.sin(time), 0, 130 * Math.cos(time));
cubeEntity.rotate(50 * dt, 20 * dt, 30 * dt);
});
});

export { app };
50 changes: 7 additions & 43 deletions src/extras/render-passes/render-pass-camera-frame.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { LAYERID_SKYBOX, LAYERID_IMMEDIATE, TONEMAP_NONE, GAMMA_NONE } from '../../scene/constants.js';
import {
ADDRESS_CLAMP_TO_EDGE, FILTER_LINEAR, FILTER_NEAREST,
PIXELFORMAT_DEPTH, PIXELFORMAT_DEPTHSTENCIL, PIXELFORMAT_R32F, PIXELFORMAT_RGBA8
} from '../../platform/graphics/constants.js';
import { ADDRESS_CLAMP_TO_EDGE, FILTER_LINEAR, PIXELFORMAT_RGBA8 } from '../../platform/graphics/constants.js';
import { Texture } from '../../platform/graphics/texture.js';
import { RenderPass } from '../../platform/graphics/render-pass.js';
import { RenderPassColorGrab } from '../../scene/graphics/render-pass-color-grab.js';
Expand Down Expand Up @@ -100,7 +97,6 @@ class RenderPassCameraFrame extends RenderPass {
reset() {

this.sceneTexture = null;
this.sceneDepth = null;

if (this.rt) {
this.rt.destroyTextureBuffers();
Expand Down Expand Up @@ -213,28 +209,10 @@ class RenderPassCameraFrame extends RenderPass {
addressV: ADDRESS_CLAMP_TO_EDGE
});

let depthFormat = options.stencil ? PIXELFORMAT_DEPTHSTENCIL : PIXELFORMAT_DEPTH;
if (options.prepassEnabled && device.isWebGPU && options.samples > 1) {
// on WebGPU the depth format cannot be resolved, so we need to use a float format in that case
// TODO: ideally we expose this using some option or similar public API to hide this implementation detail
depthFormat = PIXELFORMAT_R32F;
}

this.sceneDepth = new Texture(device, {
name: 'SceneDepth',
width: 4,
height: 4,
format: depthFormat,
mipmaps: false,
minFilter: FILTER_NEAREST,
magFilter: FILTER_NEAREST,
addressU: ADDRESS_CLAMP_TO_EDGE,
addressV: ADDRESS_CLAMP_TO_EDGE
});

this.rt = new RenderTarget({
colorBuffer: this.sceneTexture,
depthBuffer: this.sceneDepth,
// depthBuffer: this.sceneDepth,
depth: true,
samples: options.samples,
flipY: !!targetRenderTarget?.flipY // flipY is inherited from the target renderTarget
});
Expand Down Expand Up @@ -286,11 +264,7 @@ class RenderPassCameraFrame extends RenderPass {

const { app, device, cameraComponent } = this;
const { scene, renderer } = app;

// ssao & taa need resolved depth
const resolveDepth = this.options.ssaoType !== SSAOTYPE_NONE || this.options.taaEnabled;

this.prePass = new RenderPassPrepass(device, scene, renderer, cameraComponent, this.sceneDepth, resolveDepth, this.sceneOptions, options.samples);
this.prePass = new RenderPassPrepass(device, scene, renderer, cameraComponent, this.sceneOptions);
}
}

Expand All @@ -306,17 +280,6 @@ class RenderPassCameraFrame extends RenderPass {
this.scenePass = new RenderPassForward(device, composition, scene, renderer);
this.scenePass.init(this.rt, this.sceneOptions);

// if prepass is enabled, do not clear the depth buffer when rendering the scene, and preserve it
if (options.prepassEnabled) {
if (!options.stencil) {
// when stencil is used, the depth buffer might not be correct as the prepass does not
// handle stencil, so we need to clear it - in this case, depth prepass does not give
// us any benefit
this.scenePass.noDepthClear = true;
}
this.scenePass.depthStencilOps.storeDepth = true;
}

// layers this pass renders depend on the grab pass being used
const lastLayerId = options.sceneColorMap ? options.lastGrabLayerId : options.lastSceneLayerId;
const lastLayerIsTransparent = options.sceneColorMap ? options.lastGrabLayerIsTransparent : options.lastSceneLayerIsTransparent;
Expand Down Expand Up @@ -359,9 +322,10 @@ class RenderPassCameraFrame extends RenderPass {
}

setupSsaoPass(options) {
const { camera, ssaoBlurEnabled, ssaoType } = options;
const { ssaoBlurEnabled, ssaoType } = options;
const { device, cameraComponent } = this;
if (ssaoType !== SSAOTYPE_NONE) {
this.ssaoPass = new RenderPassSsao(this.device, this.sceneTexture, camera, ssaoBlurEnabled);
this.ssaoPass = new RenderPassSsao(device, this.sceneTexture, cameraComponent, ssaoBlurEnabled);
}
}

Expand Down
32 changes: 17 additions & 15 deletions src/extras/render-passes/render-pass-depth-aware-blur.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RenderPassShaderQuad } from '../../scene/graphics/render-pass-shader-quad.js';
import { shaderChunks } from '../../scene/shader-lib/chunks/chunks.js';
import { ChunkUtils } from '../../scene/shader-lib/chunk-utils.js';

/**
* Render pass implementation of a depth-aware bilateral blur filter.
Expand All @@ -8,11 +8,13 @@ import { shaderChunks } from '../../scene/shader-lib/chunks/chunks.js';
* @ignore
*/
class RenderPassDepthAwareBlur extends RenderPassShaderQuad {
constructor(device, sourceTexture, horizontal) {
constructor(device, sourceTexture, cameraComponent, horizontal) {
super(device);
this.sourceTexture = sourceTexture;

this.shader = this.createQuadShader(`DepthAware${horizontal ? 'Horizontal' : 'Vertical'}BlurShader`, `${shaderChunks.screenDepthPS /* glsl */}
const screenDepth = ChunkUtils.getScreenDepthChunk(device, cameraComponent.shaderParams);
this.shader = this.createQuadShader(`DepthAware${horizontal ? 'Horizontal' : 'Vertical'}BlurShader`,
/* glsl */ `${screenDepth}

${horizontal ? '#define HORIZONTAL' : ''}

Expand All @@ -27,17 +29,17 @@ class RenderPassDepthAwareBlur extends RenderPassShaderQuad {
return fract(m.z * fract(dot(w, m.xy)));
}

float bilateralWeight(in float depth, in float sampleDepth) {
float diff = (sampleDepth - depth);
mediump float bilateralWeight(in mediump float depth, in mediump float sampleDepth) {
mediump float diff = (sampleDepth - depth);
return max(0.0, 1.0 - diff * diff);
}

void tap(inout float sum, inout float totalWeight, float weight, float depth, vec2 position) {

float color = texture2D(sourceTexture, position).r;
float textureDepth = -getLinearScreenDepth(position);
mediump float color = texture2D(sourceTexture, position).r;
mediump float textureDepth = -getLinearScreenDepth(position);

float bilateral = bilateralWeight(depth, textureDepth);
mediump float bilateral = bilateralWeight(depth, textureDepth);

bilateral *= weight;
sum += color * bilateral;
Expand All @@ -48,13 +50,13 @@ class RenderPassDepthAwareBlur extends RenderPassShaderQuad {
void main() {

// handle the center pixel separately because it doesn't participate in bilateral filtering
float depth = -getLinearScreenDepth(uv0);
float totalWeight = 1.0;
float color = texture2D(sourceTexture, uv0 ).r;
float sum = color * totalWeight;
mediump float depth = -getLinearScreenDepth(uv0);
mediump float totalWeight = 1.0;
mediump float color = texture2D(sourceTexture, uv0 ).r;
mediump float sum = color * totalWeight;

for (int i = -filterSize; i <= filterSize; i++) {
float weight = 1.0;
for (mediump int i = -filterSize; i <= filterSize; i++) {
mediump float weight = 1.0;

#ifdef HORIZONTAL
vec2 offset = vec2(i, 0) * sourceInvResolution;
Expand All @@ -65,7 +67,7 @@ class RenderPassDepthAwareBlur extends RenderPassShaderQuad {
tap(sum, totalWeight, weight, depth, uv0 + offset);
}

float ao = sum / totalWeight;
mediump float ao = sum / totalWeight;

// simple dithering helps a lot (assumes 8 bits target)
// this is most useful with high quality/large blurs
Expand Down
74 changes: 39 additions & 35 deletions src/extras/render-passes/render-pass-prepass.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import {
FILTER_NEAREST,
PIXELFORMAT_RGBA32F,
PIXELFORMAT_RGBA16F,
ADDRESS_CLAMP_TO_EDGE
ADDRESS_CLAMP_TO_EDGE,
PIXELFORMAT_R32F,
PIXELFORMAT_RGBA8
} from '../../platform/graphics/constants.js';
import { Texture } from '../../platform/graphics/texture.js';
import { RenderPass } from '../../platform/graphics/render-pass.js';
import { RenderTarget } from '../../platform/graphics/render-target.js';

import {
LAYERID_DEPTH,
SHADER_PREPASS_VELOCITY
SHADER_PREPASS
} from '../../scene/constants.js';
import { Color } from '../../core/math/color.js';

/**
* @import { BindGroup } from '../../platform/graphics/bind-group.js'
Expand All @@ -22,9 +23,6 @@ const tempMeshInstances = [];
// uniform name of the depth texture
const DEPTH_UNIFORM_NAME = 'uSceneDepthMap';

// uniform name of the velocity texture
const VELOCITY_UNIFORM_NAME = 'uSceneVelocityMap';

/**
* A render pass which typically executes before the rendering of the main scene, and renders data
* that is required for the main rendering pass (and also in following passes) into separate render
Expand All @@ -38,24 +36,26 @@ class RenderPassPrepass extends RenderPass {
viewBindGroups = [];

/** @type {Texture} */
velocityTexture;
linearDepthTexture;

/** @type {Color} */
linearDepthClearValue;

constructor(device, scene, renderer, camera, depthBuffer, resolveDepth, options, samples) {
constructor(device, scene, renderer, camera, options) {
super(device);
this.scene = scene;
this.renderer = renderer;
this.camera = camera;
this.samples = samples;

this.setupRenderTarget(depthBuffer, resolveDepth, options);
this.setupRenderTarget(options);
}

destroy() {
super.destroy();
this.renderTarget?.destroy();
this.renderTarget = null;
this.velocityTexture?.destroy();
this.velocityTexture = null;
this.linearDepthTexture?.destroy();
this.linearDepthTexture = null;

this.viewBindGroups.forEach((bg) => {
bg.defaultUniformBuffer.destroy();
Expand All @@ -64,17 +64,16 @@ class RenderPassPrepass extends RenderPass {
this.viewBindGroups.length = 0;
}

setupRenderTarget(depthBuffer, resolveDepth, options) {
setupRenderTarget(options) {

const { device } = this;

// TODO: only two channel texture is needed here, but that is not supported by WebGL
const velocityFormat = device.getRenderableHdrFormat([PIXELFORMAT_RGBA32F, PIXELFORMAT_RGBA16F]);
this.velocityTexture = new Texture(device, {
name: 'VelocityTexture',
width: 4,
height: 4,
format: velocityFormat,
const linearDepthFormat = device.textureFloatRenderable ? PIXELFORMAT_R32F : PIXELFORMAT_RGBA8;
this.linearDepthTexture = new Texture(device, {
name: 'SceneLinearDepthTexture',
width: 1,
height: 1,
format: linearDepthFormat,
mipmaps: false,
minFilter: FILTER_NEAREST,
magFilter: FILTER_NEAREST,
Expand All @@ -84,26 +83,30 @@ class RenderPassPrepass extends RenderPass {

const renderTarget = new RenderTarget({
name: 'PrepassRT',
// colorBuffer: this.velocityTexture,
depthBuffer: depthBuffer,
samples: this.samples
colorBuffer: this.linearDepthTexture,

// use depth buffer, but this can be discarded after the prepass as the depth is stored in the linearDepthTexture
depth: true,

// always single sampled
samples: 1
});

// scene depth will be linear
this.camera.shaderParams.sceneDepthMapLinear = true;

this.init(renderTarget, options);
this.depthStencilOps.clearStencil = true;
this.depthStencilOps.storeDepth = true;

if (resolveDepth) {
this.depthStencilOps.resolveDepth = true;
}
// clear color for the linear depth texture
this.linearDepthClearValue = new Color(linearDepthFormat === PIXELFORMAT_R32F ?
[1, 1, 1, 1] : // only R is used
[0.5, 0.0, 0.0, 0.0] // represents linear value of -1 encoded into RGBA8
);
}

after() {

// Assign the depth to the uniform. Note that the depth buffer is still used as a render
// target in the following scene passes, and cannot be used as a texture inside those passes.
this.device.scope.resolve(DEPTH_UNIFORM_NAME).setValue(this.renderTarget.depthBuffer);
this.device.scope.resolve(VELOCITY_UNIFORM_NAME).setValue(this.velocityTexture);
// Assign the lienar depth texture to the uniform
this.device.scope.resolve(DEPTH_UNIFORM_NAME).setValue(this.linearDepthTexture);
}

execute() {
Expand Down Expand Up @@ -139,7 +142,7 @@ class RenderPassPrepass extends RenderPass {
}
}

renderer.renderForwardLayer(camera, renderTarget, null, undefined, SHADER_PREPASS_VELOCITY, this.viewBindGroups, {
renderer.renderForwardLayer(camera, renderTarget, null, undefined, SHADER_PREPASS, this.viewBindGroups, {
meshInstances: tempMeshInstances
});

Expand All @@ -156,6 +159,7 @@ class RenderPassPrepass extends RenderPass {
// depth clear value (1 or no clear) set up each frame
const { camera } = this;
this.setClearDepth(camera.clearDepthBuffer ? 1 : undefined);
this.setClearColor(camera.clearDepthBuffer ? this.linearDepthClearValue : undefined);
}
}

Expand Down
Loading