Skip to content

Commit

Permalink
Refactor of the RenderPassCameraFrame (#6400)
Browse files Browse the repository at this point in the history
* Refactor of the RenderPassCameraFrame

* small update

---------

Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
  • Loading branch information
mvaligursky and Martin Valigursky committed May 20, 2024
1 parent bffb1c8 commit 1414911
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 57 deletions.
10 changes: 5 additions & 5 deletions examples/src/examples/graphics/post-processing.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,15 @@ assetListLoader.load(() => {
app.root.addChild(mosquitoEntity);

// helper function to create a box primitive
const createBox = (x, y, z, r, g, b) => {
const createBox = (x, y, z, r, g, b, name) => {
// create material of random color
const material = new pc.StandardMaterial();
material.diffuse = pc.Color.BLACK;
material.emissive = new pc.Color(r, g, b);
material.update();

// create primitive
const primitive = new pc.Entity();
const primitive = new pc.Entity(name);
primitive.addComponent('render', {
type: 'box',
material: material
Expand All @@ -131,9 +131,9 @@ assetListLoader.load(() => {

// create 3 emissive boxes
const boxes = [
createBox(100, 20, 0, 200, 0, 0),
createBox(-50, 20, 100, 0, 80, 0),
createBox(90, 20, -80, 80, 80, 20)
createBox(100, 20, 0, 200, 0, 0, 'boxRed'),
createBox(-50, 20, 100, 0, 80, 0, 'boxGreen'),
createBox(90, 20, -80, 80, 80, 20, 'boxYellow')
];

// Create an Entity with a camera component
Expand Down
161 changes: 109 additions & 52 deletions src/extras/render-passes/render-pass-camera-frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class RenderPassCameraFrame extends RenderPass {
* @type {RenderTarget}
* @private
*/
_rt = null;
rt = null;

constructor(app, options = {}) {
super(app.graphicsDevice);
Expand All @@ -53,10 +53,13 @@ class RenderPassCameraFrame extends RenderPass {

destroy() {

if (this._rt) {
this._rt.destroyTextureBuffers();
this._rt.destroy();
this._rt = null;
this.sceneTexture = null;
this.sceneDepth = null;

if (this.rt) {
this.rt.destroyTextureBuffers();
this.rt.destroy();
this.rt = null;
}

// destroy all passes we created
Expand Down Expand Up @@ -120,27 +123,26 @@ class RenderPassCameraFrame extends RenderPass {

setupRenderPasses(options) {

const { app, device } = this;
const { scene, renderer } = app;
const composition = scene.layers;
const { device } = this;
const cameraComponent = options.camera;
const targetRenderTarget = cameraComponent.renderTarget;

this.hdrFormat = device.getRenderableHdrFormat() || PIXELFORMAT_RGBA8;

// create a render target to render the scene into
const format = device.getRenderableHdrFormat() || PIXELFORMAT_RGBA8;
const sceneTexture = new Texture(device, {
this.sceneTexture = new Texture(device, {
name: 'SceneColor',
width: 4,
height: 4,
format: format,
format: this.hdrFormat,
mipmaps: false,
minFilter: FILTER_LINEAR,
magFilter: FILTER_LINEAR,
addressU: ADDRESS_CLAMP_TO_EDGE,
addressV: ADDRESS_CLAMP_TO_EDGE
});

const sceneDepth = new Texture(device, {
this.sceneDepth = new Texture(device, {
name: 'SceneDepth',
width: 4,
height: 4,
Expand All @@ -152,32 +154,74 @@ class RenderPassCameraFrame extends RenderPass {
addressV: ADDRESS_CLAMP_TO_EDGE
});

const rt = new RenderTarget({
colorBuffer: sceneTexture,
depthBuffer: sceneDepth,
this.rt = new RenderTarget({
colorBuffer: this.sceneTexture,
depthBuffer: this.sceneDepth,
samples: options.samples
});
this._rt = rt;

const sceneOptions = {
this.sceneOptions = {
resizeSource: targetRenderTarget,
scaleX: this.renderTargetScale,
scaleY: this.renderTargetScale
};

// ------ SCENE PREPASS ------
this.createPasses(options);

const allPasses = this.collectPasses();
this.beforePasses = allPasses.filter(element => element !== undefined);
}

collectPasses() {

// use these prepared render passes in the order they should be executed
return [this.prePass, this.scenePass, this.colorGrabPass, this.scenePassTransparent, this.taaPass, this.bloomPass, this.composePass, this.afterPass];
}

createPasses(options) {

// pre-pass
this.setupScenePrepass(options);

// scene including color grab pass
const scenePassesInfo = this.setupScenePass(options);

// TAA
const sceneTextureWithTaa = this.setupTaaPass(options);

// bloom
this.setupBloomPass(sceneTextureWithTaa);

// compose
this.setupComposePass(options);

// after pass
this.setupAfterPass(options, scenePassesInfo);
}

setupScenePrepass(options) {
if (options.prepassEnabled) {
this.prePass = new RenderPassPrepass(device, scene, renderer, cameraComponent, sceneDepth, sceneOptions);

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

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

setupScenePass(options) {

// ------ SCENE RENDERING WITH OPTIONAL GRAB PASS ------
const { app, device } = this;
const { scene, renderer } = app;
const composition = scene.layers;
const cameraComponent = options.camera;

// render pass that renders the scene to the render target. Render target size automatically
// matches the back-buffer size with the optional scale. Note that the scale parameters
// allow us to render the 3d scene at lower resolution, improving performance.
this.scenePass = new RenderPassForward(device, composition, scene, renderer);
this.scenePass.init(rt, sceneOptions);
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) {
Expand All @@ -189,73 +233,86 @@ class RenderPassCameraFrame extends RenderPass {
const lastLayerId = options.sceneColorMap ? options.lastGrabLayerId : options.lastSceneLayerId;
const lastLayerIsTransparent = options.sceneColorMap ? options.lastGrabLayerIsTransparent : options.lastSceneLayerIsTransparent;

let clearRenderTarget = true;
let lastAddedIndex = 0;
lastAddedIndex = this.scenePass.addLayers(composition, cameraComponent, lastAddedIndex, clearRenderTarget, lastLayerId, lastLayerIsTransparent);
clearRenderTarget = false;
// return values
const ret = {
lastAddedIndex: 0, // the last layer index added to the scene pass
clearRenderTarget: true // true if the render target should be cleared
};

ret.lastAddedIndex = this.scenePass.addLayers(composition, cameraComponent, ret.lastAddedIndex, ret.clearRenderTarget, lastLayerId, lastLayerIsTransparent);
ret.clearRenderTarget = false;

// grab pass allowing us to copy the render scene into a texture and use for refraction
// the source for the copy is the texture we render the scene to
let colorGrabPass;
let scenePassTransparent;
if (options.sceneColorMap) {
colorGrabPass = new RenderPassColorGrab(device);
colorGrabPass.source = rt;
this.colorGrabPass = new RenderPassColorGrab(device);
this.colorGrabPass.source = this.rt;

// if grab pass is used, render the layers after it (otherwise they were already rendered)
scenePassTransparent = new RenderPassForward(device, composition, scene, renderer);
scenePassTransparent.init(rt);
lastAddedIndex = scenePassTransparent.addLayers(composition, cameraComponent, lastAddedIndex, clearRenderTarget, options.lastSceneLayerId, options.lastSceneLayerIsTransparent);
this.scenePassTransparent = new RenderPassForward(device, composition, scene, renderer);
this.scenePassTransparent.init(this.rt);
ret.lastAddedIndex = this.scenePassTransparent.addLayers(composition, cameraComponent, ret.lastAddedIndex, ret.clearRenderTarget, options.lastSceneLayerId, options.lastSceneLayerIsTransparent);

// if prepass is enabled, we need to store the depth, as by default it gets discarded
if (options.prepassEnabled) {
scenePassTransparent.depthStencilOps.storeDepth = true;
this.scenePassTransparent.depthStencilOps.storeDepth = true;
}
}

// ------ TAA ------
return ret;
}

setupBloomPass(inputTexture) {
// create a bloom pass, which generates bloom texture based on the just rendered scene texture
this.bloomPass = new RenderPassBloom(this.device, inputTexture, this.hdrFormat);
}

let sceneTextureWithTaa = sceneTexture;
setupTaaPass(options) {
let textureWithTaa = this.sceneTexture;
if (options.taaEnabled) {
this.taaPass = new RenderPassTAA(device, sceneTexture, cameraComponent);
sceneTextureWithTaa = this.taaPass.historyTexture;
const cameraComponent = options.camera;
this.taaPass = new RenderPassTAA(this.device, this.sceneTexture, cameraComponent);
textureWithTaa = this.taaPass.historyTexture;
}

// ------ BLOOM GENERATION ------

// create a bloom pass, which generates bloom texture based on the just rendered scene texture
this.bloomPass = new RenderPassBloom(app.graphicsDevice, sceneTextureWithTaa, format);
return textureWithTaa;
}

// ------ COMPOSITION ------
setupComposePass(options) {

// create a compose pass, which combines the scene texture with the bloom texture
this.composePass = new RenderPassCompose(app.graphicsDevice);
this.composePass = new RenderPassCompose(this.device);
this.composePass.bloomTexture = this.bloomPass.bloomTexture;
this.composePass.taaEnabled = options.taaEnabled;

// compose pass renders directly to target renderTarget
const cameraComponent = options.camera;
const targetRenderTarget = cameraComponent.renderTarget;
this.composePass.init(targetRenderTarget);
}

setupAfterPass(options, scenePassesInfo) {

// ------ AFTER COMPOSITION RENDERING ------
const { app } = this;
const { scene, renderer } = app;
const composition = scene.layers;
const cameraComponent = options.camera;
const targetRenderTarget = cameraComponent.renderTarget;

// final pass renders directly to the target renderTarget on top of the bloomed scene, and it renders a transparent UI layer
const afterPass = new RenderPassForward(device, composition, scene, renderer);
afterPass.init(targetRenderTarget);
this.afterPass = new RenderPassForward(this.device, composition, scene, renderer);
this.afterPass.init(targetRenderTarget);

// add all remaining layers the camera renders
afterPass.addLayers(composition, cameraComponent, lastAddedIndex, clearRenderTarget);

// use these prepared render passes in the order they should be executed
const allPasses = [this.prePass, this.scenePass, colorGrabPass, scenePassTransparent, this.taaPass, this.bloomPass, this.composePass, afterPass];
this.beforePasses = allPasses.filter(element => element !== undefined);
this.afterPass.addLayers(composition, cameraComponent, scenePassesInfo.lastAddedIndex, scenePassesInfo.clearRenderTarget);
}

frameUpdate() {

super.frameUpdate();

// scene texture is either output of taa pass or the scene render target
const sceneTexture = this.taaPass?.update() ?? this._rt.colorBuffer;
const sceneTexture = this.taaPass?.update() ?? this.rt.colorBuffer;

// TAA history buffer is double buffered, assign the current one to the follow up passes.
this.composePass.sceneTexture = sceneTexture;
Expand Down
1 change: 1 addition & 0 deletions src/framework/components/camera/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ class CameraComponent extends Component {
* equivalent to {@link SHADERPASS_FORWARD}. Can be:
*
* - {@link SHADERPASS_FORWARD}
* - {@link SHADERPASS_FORWARD_HDR}
* - {@link SHADERPASS_ALBEDO}
* - {@link SHADERPASS_OPACITY}
* - {@link SHADERPASS_WORLDNORMAL}
Expand Down
9 changes: 9 additions & 0 deletions src/scene/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,15 @@ export const SHADER_PREPASS_VELOCITY = 5;
*/
export const SHADERPASS_FORWARD = 'forward';

/**
* Shader that performs forward rendering in HDR mode (gamma correction and tonemapping are
* disabled).
*
* @type {string}
* @category Graphics
*/
export const SHADERPASS_FORWARD_HDR = 'forward_hdr';

/**
* Shader used for debug rendering of albedo.
*
Expand Down

0 comments on commit 1414911

Please sign in to comment.