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

Refactor of the RenderPassCameraFrame #6400

Merged
merged 2 commits into from
May 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
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