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

Color grab pass can be used for custom render pass rendering #5866

Merged
merged 1 commit into from
Nov 29, 2023
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
Binary file added examples/assets/models/scifi-platform.glb
Binary file not shown.
5 changes: 5 additions & 0 deletions examples/assets/models/scifi-platform.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
The model has been obtained from this address:
https://sketchfab.com/3d-models/scifi-platform-stage-scene-baked-64adb59a716d43e5a8705ff6fe86c0ce

It's distributed under CC license:
https://creativecommons.org/licenses/by/4.0/
3 changes: 0 additions & 3 deletions examples/src/examples/graphics/post-effects.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,6 @@ async function example({ canvas, deviceType, data, assetPath, scriptsPath, glsla
deviceTypes: [deviceType],
glslangUrl: glslangPath + 'glslang.js',
twgslUrl: twgslPath + 'twgsl.js',

// WebGPU does not currently support antialiased depth resolve, disable it till we implement a shader resolve solution
antialias: false
};

const device = await pc.createGraphicsDevice(canvas, gfxOptions);
Expand Down
129 changes: 98 additions & 31 deletions examples/src/examples/graphics/post-processing.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ function controls({ observer, ReactPCUI, React, jsx, fragment }) {
precision: 1
})
),
jsx(LabelGroup, { text: 'background' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'data.scene.background' },
min: 0,
max: 50,
precision: 1
})
),
jsx(LabelGroup, { text: 'emissive' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'data.scene.emissive' },
min: 0,
max: 400,
precision: 1
})
),
jsx(LabelGroup, { text: 'Tonemapping' },
jsx(SelectInput, {
binding: new BindingTwoWay(),
Expand Down Expand Up @@ -113,7 +131,8 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,

const assets = {
orbit: new pc.Asset('script', 'script', { url: scriptsPath + 'camera/orbit-camera.js' }),
board: new pc.Asset('statue', 'container', { url: assetPath + 'models/chess-board.glb' }),
platform: new pc.Asset('statue', 'container', { url: assetPath + 'models/scifi-platform.glb' }),
mosquito: new pc.Asset('mosquito', 'container', { url: assetPath + 'models/MosquitoInAmber.glb' }),
font: new pc.Asset('font', 'font', { url: assetPath + 'fonts/arial.json' }),
helipad: new pc.Asset('helipad-env-atlas', 'texture', { url: assetPath + 'cubemaps/helipad-env-atlas.png' }, { type: pc.TEXTURETYPE_RGBP, mipmaps: false })
};
Expand All @@ -123,7 +142,9 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,
glslangUrl: glslangPath + 'glslang.js',
twgslUrl: twgslPath + 'twgsl.js',

// WebGPU does not currently support antialiased depth resolve, disable it till we implement a shader resolve solution
// The scene is rendered to an antialiased texture, so we disable antialiasing on the canvas
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you referring to 2d UI in which case msaa doesn't help anyway?

Can't you get rid of depth buffer too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point! depth/stencil on the backbuffer is useless, but when I try to remove it, I get some exception on WebGPU renderer (WebGL is fine) .. I'll fix in a separate PR

// to avoid the additional cost. This is only used for the UI which renders on top of the
// post-processed scene, and we're typically happy with some aliasing on the UI.
antialias: false
};

Expand Down Expand Up @@ -177,7 +198,7 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,

app.start();

// setup skydome
// setup skydome with low intensity
app.scene.envAtlas = assets.helipad.resource;
app.scene.skyboxMip = 2;
app.scene.exposure = 0.3;
Expand All @@ -186,12 +207,28 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,
app.scene.toneMapping = pc.TONEMAP_LINEAR;
app.scene.gammaCorrection = pc.GAMMA_NONE;

// get the instance of the chess board and set up with render component
const boardEntity = assets.board.resource.instantiateRenderEntity();
app.root.addChild(boardEntity);
// create an instance of the platform and add it to the scene
const platformEntity = assets.platform.resource.instantiateRenderEntity();
platformEntity.setLocalScale(10, 10, 10);
app.root.addChild(platformEntity);

// get a list of emissive materials from the scene to allow their intensity to be changed
const emissiveMaterials = [];
const emissiveNames = new Set(['Light_Upper_Light-Upper_0', 'Emissive_Cyan__0']);
platformEntity.findComponents("render").forEach((render) => {
if (emissiveNames.has(render.entity.name)) {
render.meshInstances.forEach(meshInstance => emissiveMaterials.push(meshInstance.material));
}
});

// add an instance of the mosquito mesh
const mosquitoEntity = assets.mosquito.resource.instantiateRenderEntity();
mosquitoEntity.setLocalScale(600, 600, 600);
mosquitoEntity.setLocalPosition(0, 20, 0);
app.root.addChild(mosquitoEntity);

// helper function to create a box primitive
const createBox = (x, y, z, sx, sy, sz, r, g, b) => {
const createBox = (x, y, z, r, g, b) => {
// create material of random color
const material = new pc.StandardMaterial();
material.diffuse = pc.Color.BLACK;
Expand All @@ -207,41 +244,40 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,

// set position and scale
primitive.setLocalPosition(x, y, z);
primitive.setLocalScale(sx, sy, sz);
app.root.addChild(primitive);

return primitive;
};

// create 3 emissive boxes
const boxes = [
createBox(0, 20, 0, 10, 10, 10, 300, 0, 0),
createBox(40, 20, 0, 10, 10, 10, 0, 80, 0),
createBox(-40, 20, 0, 15, 15, 15, 80, 80, 20)
createBox(100, 20, 0, 200, 0, 0),
createBox(-50, 20, 100, 0, 80, 0),
createBox(90, 20, -80, 80, 80, 20)
];

// Create an Entity with a camera component
const cameraEntity = new pc.Entity();
cameraEntity.addComponent("camera", {
clearColor: new pc.Color(0, 0, 0),
farClip: 500
farClip: 500,
fov: 80
});

// add orbit camera script with a mouse and a touch support
cameraEntity.addComponent("script");
cameraEntity.script.create("orbitCamera", {
attributes: {
inertiaFactor: 0.2,
focusEntity: boxes[0],
distanceMax: 160,
focusEntity: mosquitoEntity,
distanceMax: 190,
frameOnStart: false
}
});
cameraEntity.script.create("orbitCameraInputMouse");
cameraEntity.script.create("orbitCameraInputTouch");

// position the camera in the world
cameraEntity.setLocalPosition(0, 40, -160);
cameraEntity.setLocalPosition(0, 40, -220);
cameraEntity.lookAt(0, 0, 100);
app.root.addChild(cameraEntity);

Expand All @@ -255,20 +291,22 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,
});
app.root.addChild(screen);

// add a directional light
// add a shadow casting directional light
const lightColor = new pc.Color(1, 0.7, 0.1);
const light = new pc.Entity();
light.addComponent("light", {
type: "directional",
color: pc.Color.WHITE,
intensity: 3,
range: 500,
shadowDistance: 500,
color: lightColor,
intensity: 80,
range: 400,
shadowResolution: 4096,
shadowDistance: 400,
castShadows: true,
shadowBias: 0.2,
normalOffsetBias: 0.05
});
app.root.addChild(light);
light.setLocalEulerAngles(45, 30, 0);
light.setLocalEulerAngles(80, 10, 0);

// a helper function to add a label to the screen
const addLabel = (name, text, x, y, layer) => {
Expand All @@ -295,9 +333,11 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,
const uiLayer = app.scene.layers.getLayerById(pc.LAYERID_UI);
addLabel('TopUI', 'Text on theUI layer after the post-processing', 0.1, 0.1, uiLayer);

// render passes
let scenePass;
let composePass;
let bloomPass;
let colorGrabPass;

// helper function to create a render passes for the camera
const setupRenderPasses = () => {
Expand All @@ -322,18 +362,30 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,
samples: 4
});

// render pass that renders the scene to the render target. Render target size automatically
// matches the back-buffer size with the optional scale.
// 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
colorGrabPass = new pc.RenderPassColorGrab(app.graphicsDevice);
colorGrabPass.source = rt;

// render pass that renders the opaque 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.
scenePass = new pc.RenderPassRenderActions(app.graphicsDevice, app.scene.layers, app.scene, app.renderer);
scenePass.init(rt, {
resizeSource: null,
scaleX: 1,
scaleY: 1
});

// this pass render both opaque and transparent meshes on the world layer
scenePass.addLayer(cameraEntity.camera, worldLayer, false);
scenePass.addLayer(cameraEntity.camera, worldLayer, true);
// this pass render opaquemeshes on the world layer
let clearRenderTarget = true;
scenePass.addLayer(cameraEntity.camera, worldLayer, false, clearRenderTarget);

// similar pass that renders transparent meshes from the world layer to the same render target
clearRenderTarget = false;
const scenePassTransparent = new pc.RenderPassRenderActions(app.graphicsDevice, app.scene.layers, app.scene, app.renderer);
scenePassTransparent.init(rt);
scenePassTransparent.addLayer(cameraEntity.camera, worldLayer, true, clearRenderTarget);

// create a bloom pass, which generates bloom texture based on the just rendered scene texture
bloomPass = new pcx.RenderPassBloom(app.graphicsDevice, sceneTexture, format);
Expand All @@ -349,10 +401,10 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,
// final pass renders directly to the back-buffer on top of the bloomed scene, and it renders a transparent UI layer
const afterPass = new pc.RenderPassRenderActions(app.graphicsDevice, app.scene.layers, app.scene, app.renderer);
afterPass.init(null);
afterPass.addLayer(cameraEntity.camera, uiLayer, true, false);
afterPass.addLayer(cameraEntity.camera, uiLayer, true, clearRenderTarget);

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

// set up render passes on the camera, to use those instead of the default camera rendering
Expand All @@ -370,6 +422,16 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,
composePass.toneMapping = value;
composePass._shaderDirty = true;
}
if (pathArray[2] === 'background') {
cameraEntity.camera.clearColor = new pc.Color(lightColor.r * value, lightColor.g * value, lightColor.b * value);
light.light.intensity = value;
}
if (pathArray[2] === 'emissive') {
emissiveMaterials.forEach((material) => {
material.emissiveIntensity = value;
material.update();
});
}
}
if (pathArray[1] === 'bloom') {
if (pathArray[2] === 'intensity') {
Expand Down Expand Up @@ -401,6 +463,8 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,
data.set('data', {
scene: {
scale: 1.8,
background: 6,
emissive: 200,
tonemapping: pc.TONEMAP_ACES
},
bloom: {
Expand All @@ -424,9 +488,12 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,
// scale the boxes
for (let i = 0; i < boxes.length; i++) {
const offset = Math.PI * 2 * i / (boxes.length);
const scale = 10 + Math.sin(angle + offset) * 7;
const scale = 25 + Math.sin(angle + offset) * 10;
boxes[i].setLocalScale(scale, scale, scale);
}

// rotate the mosquitoEntity
mosquitoEntity.setLocalEulerAngles(0, angle * 30, 0);
});
});
return app;
Expand Down
3 changes: 0 additions & 3 deletions examples/src/examples/graphics/render-pass.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,
deviceTypes: [deviceType],
glslangUrl: glslangPath + 'glslang.js',
twgslUrl: twgslPath + 'twgsl.js',

// WebGPU does not currently support antialiased depth resolve, disable it till we implement a shader resolve solution
antialias: false
};

const device = await pc.createGraphicsDevice(canvas, gfxOptions);
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export { Skeleton } from './scene/animation/skeleton.js';
// SCENE / GRAPHICS
export { EnvLighting } from './scene/graphics/env-lighting.js';
export { PostEffect } from './scene/graphics/post-effect.js';
export { RenderPassColorGrab } from './scene/graphics/render-pass-color-grab.js';
export { RenderPassShaderQuad } from './scene/graphics/render-pass-shader-quad.js';
export { shFromCubemap } from './scene/graphics/prefilter-cubemap.js';
export { reprojectTexture } from './scene/graphics/reproject-texture.js';
Expand Down
24 changes: 15 additions & 9 deletions src/platform/graphics/render-pass.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,8 @@ class RenderPass {
/**
* Mark render pass as clearing the full color buffer.
*
* @param {Color} color - The color to clear to.
* @param {Color|undefined} color - The color to clear to, or undefined to preserve the existing
* content.
*/
setClearColor(color) {

Expand All @@ -271,29 +272,34 @@ class RenderPass {
const count = this.colorArrayOps.length;
for (let i = 0; i < count; i++) {
const colorOps = this.colorArrayOps[i];
colorOps.clearValue.copy(color);
colorOps.clear = true;
if (color)
colorOps.clearValue.copy(color);
colorOps.clear = !!color;
}
}

/**
* Mark render pass as clearing the full depth buffer.
*
* @param {number} depthValue - The depth value to clear to.
* @param {number|undefined} depthValue - The depth value to clear to, or undefined to preserve
* the existing content.
*/
setClearDepth(depthValue) {
this.depthStencilOps.clearDepthValue = depthValue;
this.depthStencilOps.clearDepth = true;
if (depthValue)
this.depthStencilOps.clearDepthValue = depthValue;
this.depthStencilOps.clearDepth = depthValue !== undefined;
}

/**
* Mark render pass as clearing the full stencil buffer.
*
* @param {number} stencilValue - The stencil value to clear to.
* @param {number|undefined} stencilValue - The stencil value to clear to, or undefined to preserve the
* existing content.
*/
setClearStencil(stencilValue) {
this.depthStencilOps.clearStencilValue = stencilValue;
this.depthStencilOps.clearStencil = true;
if (stencilValue)
this.depthStencilOps.clearStencilValue = stencilValue;
this.depthStencilOps.clearStencil = stencilValue !== undefined;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/scene/camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ class Camera {
_enableRenderPassColorGrab(device, enable) {
if (enable) {
if (!this.renderPassColorGrab) {
this.renderPassColorGrab = new RenderPassColorGrab(device, this);
this.renderPassColorGrab = new RenderPassColorGrab(device);
}
} else {
this.renderPassColorGrab?.destroy();
Expand Down
Loading