Skip to content

Commit 286f92a

Browse files
mvaligurskyMartin Valigursky
and
Martin Valigursky
authored
Color grab pass can be used for custom render pass rendering (#5866)
Co-authored-by: Martin Valigursky <[email protected]>
1 parent b219172 commit 286f92a

File tree

11 files changed

+162
-88
lines changed

11 files changed

+162
-88
lines changed
4.09 MB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
The model has been obtained from this address:
2+
https://sketchfab.com/3d-models/scifi-platform-stage-scene-baked-64adb59a716d43e5a8705ff6fe86c0ce
3+
4+
It's distributed under CC license:
5+
https://creativecommons.org/licenses/by/4.0/

examples/src/examples/graphics/post-effects.mjs

-3
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,6 @@ async function example({ canvas, deviceType, data, assetPath, scriptsPath, glsla
176176
deviceTypes: [deviceType],
177177
glslangUrl: glslangPath + 'glslang.js',
178178
twgslUrl: twgslPath + 'twgsl.js',
179-
180-
// WebGPU does not currently support antialiased depth resolve, disable it till we implement a shader resolve solution
181-
antialias: false
182179
};
183180

184181
const device = await pc.createGraphicsDevice(canvas, gfxOptions);

examples/src/examples/graphics/post-processing.mjs

+98-31
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,24 @@ function controls({ observer, ReactPCUI, React, jsx, fragment }) {
1717
precision: 1
1818
})
1919
),
20+
jsx(LabelGroup, { text: 'background' },
21+
jsx(SliderInput, {
22+
binding: new BindingTwoWay(),
23+
link: { observer, path: 'data.scene.background' },
24+
min: 0,
25+
max: 50,
26+
precision: 1
27+
})
28+
),
29+
jsx(LabelGroup, { text: 'emissive' },
30+
jsx(SliderInput, {
31+
binding: new BindingTwoWay(),
32+
link: { observer, path: 'data.scene.emissive' },
33+
min: 0,
34+
max: 400,
35+
precision: 1
36+
})
37+
),
2038
jsx(LabelGroup, { text: 'Tonemapping' },
2139
jsx(SelectInput, {
2240
binding: new BindingTwoWay(),
@@ -113,7 +131,8 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,
113131

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

126-
// WebGPU does not currently support antialiased depth resolve, disable it till we implement a shader resolve solution
145+
// The scene is rendered to an antialiased texture, so we disable antialiasing on the canvas
146+
// to avoid the additional cost. This is only used for the UI which renders on top of the
147+
// post-processed scene, and we're typically happy with some aliasing on the UI.
127148
antialias: false
128149
};
129150

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

178199
app.start();
179200

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

189-
// get the instance of the chess board and set up with render component
190-
const boardEntity = assets.board.resource.instantiateRenderEntity();
191-
app.root.addChild(boardEntity);
210+
// create an instance of the platform and add it to the scene
211+
const platformEntity = assets.platform.resource.instantiateRenderEntity();
212+
platformEntity.setLocalScale(10, 10, 10);
213+
app.root.addChild(platformEntity);
214+
215+
// get a list of emissive materials from the scene to allow their intensity to be changed
216+
const emissiveMaterials = [];
217+
const emissiveNames = new Set(['Light_Upper_Light-Upper_0', 'Emissive_Cyan__0']);
218+
platformEntity.findComponents("render").forEach((render) => {
219+
if (emissiveNames.has(render.entity.name)) {
220+
render.meshInstances.forEach(meshInstance => emissiveMaterials.push(meshInstance.material));
221+
}
222+
});
223+
224+
// add an instance of the mosquito mesh
225+
const mosquitoEntity = assets.mosquito.resource.instantiateRenderEntity();
226+
mosquitoEntity.setLocalScale(600, 600, 600);
227+
mosquitoEntity.setLocalPosition(0, 20, 0);
228+
app.root.addChild(mosquitoEntity);
192229

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

208245
// set position and scale
209246
primitive.setLocalPosition(x, y, z);
210-
primitive.setLocalScale(sx, sy, sz);
211247
app.root.addChild(primitive);
212248

213249
return primitive;
214250
};
215251

216252
// create 3 emissive boxes
217253
const boxes = [
218-
createBox(0, 20, 0, 10, 10, 10, 300, 0, 0),
219-
createBox(40, 20, 0, 10, 10, 10, 0, 80, 0),
220-
createBox(-40, 20, 0, 15, 15, 15, 80, 80, 20)
254+
createBox(100, 20, 0, 200, 0, 0),
255+
createBox(-50, 20, 100, 0, 80, 0),
256+
createBox(90, 20, -80, 80, 80, 20)
221257
];
222258

223259
// Create an Entity with a camera component
224260
const cameraEntity = new pc.Entity();
225261
cameraEntity.addComponent("camera", {
226-
clearColor: new pc.Color(0, 0, 0),
227-
farClip: 500
262+
farClip: 500,
263+
fov: 80
228264
});
229265

230266
// add orbit camera script with a mouse and a touch support
231267
cameraEntity.addComponent("script");
232268
cameraEntity.script.create("orbitCamera", {
233269
attributes: {
234270
inertiaFactor: 0.2,
235-
focusEntity: boxes[0],
236-
distanceMax: 160,
271+
focusEntity: mosquitoEntity,
272+
distanceMax: 190,
237273
frameOnStart: false
238274
}
239275
});
240276
cameraEntity.script.create("orbitCameraInputMouse");
241277
cameraEntity.script.create("orbitCameraInputTouch");
242278

243279
// position the camera in the world
244-
cameraEntity.setLocalPosition(0, 40, -160);
280+
cameraEntity.setLocalPosition(0, 40, -220);
245281
cameraEntity.lookAt(0, 0, 100);
246282
app.root.addChild(cameraEntity);
247283

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

258-
// add a directional light
294+
// add a shadow casting directional light
295+
const lightColor = new pc.Color(1, 0.7, 0.1);
259296
const light = new pc.Entity();
260297
light.addComponent("light", {
261298
type: "directional",
262-
color: pc.Color.WHITE,
263-
intensity: 3,
264-
range: 500,
265-
shadowDistance: 500,
299+
color: lightColor,
300+
intensity: 80,
301+
range: 400,
302+
shadowResolution: 4096,
303+
shadowDistance: 400,
266304
castShadows: true,
267305
shadowBias: 0.2,
268306
normalOffsetBias: 0.05
269307
});
270308
app.root.addChild(light);
271-
light.setLocalEulerAngles(45, 30, 0);
309+
light.setLocalEulerAngles(80, 10, 0);
272310

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

336+
// render passes
298337
let scenePass;
299338
let composePass;
300339
let bloomPass;
340+
let colorGrabPass;
301341

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

325-
// render pass that renders the scene to the render target. Render target size automatically
326-
// matches the back-buffer size with the optional scale.
365+
// grab pass allowing us to copy the render scene into a texture and use for refraction
366+
// the source for the copy is the texture we render the scene to
367+
colorGrabPass = new pc.RenderPassColorGrab(app.graphicsDevice);
368+
colorGrabPass.source = rt;
369+
370+
// render pass that renders the opaque scene to the render target. Render target size
371+
// automatically matches the back-buffer size with the optional scale. Note that the scale
372+
// parameters allow us to render the 3d scene at lower resolution, improving performance.
327373
scenePass = new pc.RenderPassRenderActions(app.graphicsDevice, app.scene.layers, app.scene, app.renderer);
328374
scenePass.init(rt, {
329375
resizeSource: null,
330376
scaleX: 1,
331377
scaleY: 1
332378
});
333379

334-
// this pass render both opaque and transparent meshes on the world layer
335-
scenePass.addLayer(cameraEntity.camera, worldLayer, false);
336-
scenePass.addLayer(cameraEntity.camera, worldLayer, true);
380+
// this pass render opaquemeshes on the world layer
381+
let clearRenderTarget = true;
382+
scenePass.addLayer(cameraEntity.camera, worldLayer, false, clearRenderTarget);
383+
384+
// similar pass that renders transparent meshes from the world layer to the same render target
385+
clearRenderTarget = false;
386+
const scenePassTransparent = new pc.RenderPassRenderActions(app.graphicsDevice, app.scene.layers, app.scene, app.renderer);
387+
scenePassTransparent.init(rt);
388+
scenePassTransparent.addLayer(cameraEntity.camera, worldLayer, true, clearRenderTarget);
337389

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

354-
// return these prepared render passes
355-
return [scenePass, bloomPass, composePass, afterPass];
406+
// return these prepared render passes in the order they should be executed
407+
return [scenePass, colorGrabPass, scenePassTransparent, bloomPass, composePass, afterPass];
356408
};
357409

358410
// set up render passes on the camera, to use those instead of the default camera rendering
@@ -370,6 +422,16 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,
370422
composePass.toneMapping = value;
371423
composePass._shaderDirty = true;
372424
}
425+
if (pathArray[2] === 'background') {
426+
cameraEntity.camera.clearColor = new pc.Color(lightColor.r * value, lightColor.g * value, lightColor.b * value);
427+
light.light.intensity = value;
428+
}
429+
if (pathArray[2] === 'emissive') {
430+
emissiveMaterials.forEach((material) => {
431+
material.emissiveIntensity = value;
432+
material.update();
433+
});
434+
}
373435
}
374436
if (pathArray[1] === 'bloom') {
375437
if (pathArray[2] === 'intensity') {
@@ -401,6 +463,8 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,
401463
data.set('data', {
402464
scene: {
403465
scale: 1.8,
466+
background: 6,
467+
emissive: 200,
404468
tonemapping: pc.TONEMAP_ACES
405469
},
406470
bloom: {
@@ -424,9 +488,12 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,
424488
// scale the boxes
425489
for (let i = 0; i < boxes.length; i++) {
426490
const offset = Math.PI * 2 * i / (boxes.length);
427-
const scale = 10 + Math.sin(angle + offset) * 7;
491+
const scale = 25 + Math.sin(angle + offset) * 10;
428492
boxes[i].setLocalScale(scale, scale, scale);
429493
}
494+
495+
// rotate the mosquitoEntity
496+
mosquitoEntity.setLocalEulerAngles(0, angle * 30, 0);
430497
});
431498
});
432499
return app;

examples/src/examples/graphics/render-pass.mjs

-3
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@ async function example({ canvas, deviceType, assetPath, glslangPath, twgslPath,
4747
deviceTypes: [deviceType],
4848
glslangUrl: glslangPath + 'glslang.js',
4949
twgslUrl: twgslPath + 'twgsl.js',
50-
51-
// WebGPU does not currently support antialiased depth resolve, disable it till we implement a shader resolve solution
52-
antialias: false
5350
};
5451

5552
const device = await pc.createGraphicsDevice(canvas, gfxOptions);

src/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ export { Skeleton } from './scene/animation/skeleton.js';
150150
// SCENE / GRAPHICS
151151
export { EnvLighting } from './scene/graphics/env-lighting.js';
152152
export { PostEffect } from './scene/graphics/post-effect.js';
153+
export { RenderPassColorGrab } from './scene/graphics/render-pass-color-grab.js';
153154
export { RenderPassShaderQuad } from './scene/graphics/render-pass-shader-quad.js';
154155
export { shFromCubemap } from './scene/graphics/prefilter-cubemap.js';
155156
export { reprojectTexture } from './scene/graphics/reproject-texture.js';

src/platform/graphics/render-pass.js

+15-9
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,8 @@ class RenderPass {
262262
/**
263263
* Mark render pass as clearing the full color buffer.
264264
*
265-
* @param {Color} color - The color to clear to.
265+
* @param {Color|undefined} color - The color to clear to, or undefined to preserve the existing
266+
* content.
266267
*/
267268
setClearColor(color) {
268269

@@ -271,29 +272,34 @@ class RenderPass {
271272
const count = this.colorArrayOps.length;
272273
for (let i = 0; i < count; i++) {
273274
const colorOps = this.colorArrayOps[i];
274-
colorOps.clearValue.copy(color);
275-
colorOps.clear = true;
275+
if (color)
276+
colorOps.clearValue.copy(color);
277+
colorOps.clear = !!color;
276278
}
277279
}
278280

279281
/**
280282
* Mark render pass as clearing the full depth buffer.
281283
*
282-
* @param {number} depthValue - The depth value to clear to.
284+
* @param {number|undefined} depthValue - The depth value to clear to, or undefined to preserve
285+
* the existing content.
283286
*/
284287
setClearDepth(depthValue) {
285-
this.depthStencilOps.clearDepthValue = depthValue;
286-
this.depthStencilOps.clearDepth = true;
288+
if (depthValue)
289+
this.depthStencilOps.clearDepthValue = depthValue;
290+
this.depthStencilOps.clearDepth = depthValue !== undefined;
287291
}
288292

289293
/**
290294
* Mark render pass as clearing the full stencil buffer.
291295
*
292-
* @param {number} stencilValue - The stencil value to clear to.
296+
* @param {number|undefined} stencilValue - The stencil value to clear to, or undefined to preserve the
297+
* existing content.
293298
*/
294299
setClearStencil(stencilValue) {
295-
this.depthStencilOps.clearStencilValue = stencilValue;
296-
this.depthStencilOps.clearStencil = true;
300+
if (stencilValue)
301+
this.depthStencilOps.clearStencilValue = stencilValue;
302+
this.depthStencilOps.clearStencil = stencilValue !== undefined;
297303
}
298304

299305
/**

src/scene/camera.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ class Camera {
456456
_enableRenderPassColorGrab(device, enable) {
457457
if (enable) {
458458
if (!this.renderPassColorGrab) {
459-
this.renderPassColorGrab = new RenderPassColorGrab(device, this);
459+
this.renderPassColorGrab = new RenderPassColorGrab(device);
460460
}
461461
} else {
462462
this.renderPassColorGrab?.destroy();

0 commit comments

Comments
 (0)