Skip to content

Commit b196b22

Browse files
mvaligurskyMartin Valigursky
andauthored
feat: fix frontFace double-flip, remove twoSidedLightingNegScaleFactor, add setDrawStates (#8503)
Follow-up to #8448. - Fix double-flip bug in setupCullModeAndFrontFace: only flip frontFace (not cullMode) when flipFaces < 0, since cull mode is defined relative to frontFace. - Remove the now-redundant twoSidedLightingNegScaleFactor uniform from renderer.js and both GLSL/WGSL twoSidedLighting shader chunks. With frontFace corrected at the GPU level, gl_FrontFacing/pcFrontFacing is accurate and the uniform is no longer needed. - Add setDrawStates() convenience method to GraphicsDevice that sets all draw-related render states (blend, depth, cull, frontFace, stencil) in one call with sensible defaults. - Update 8 internal call sites to use setDrawStates(), cleaning up redundant imports. - Update QuadRender JSDoc to recommend setDrawStates(). - Add hidden two-sided-lighting test example with TwoSidedPlane.glb. Made-with: Cursor Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
1 parent 3f31773 commit b196b22

File tree

15 files changed

+169
-108
lines changed

15 files changed

+169
-108
lines changed
154 KB
Binary file not shown.
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// @config HIDDEN
2+
import { deviceType, rootPath, fileImport } from 'examples/utils';
3+
import * as pc from 'playcanvas';
4+
5+
const { CameraControls } = await fileImport(`${rootPath}/static/scripts/esm/camera-controls.mjs`);
6+
7+
const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas'));
8+
window.focus();
9+
10+
const assets = {
11+
helipad: new pc.Asset(
12+
'morning-env-atlas',
13+
'texture',
14+
{ url: `${rootPath}/static/assets/cubemaps/morning-env-atlas.png` },
15+
{ type: pc.TEXTURETYPE_RGBP, mipmaps: false }
16+
),
17+
model: new pc.Asset('model', 'container', { url: `${rootPath}/static/assets/models/TwoSidedPlane.glb` })
18+
};
19+
20+
const gfxOptions = {
21+
deviceTypes: [deviceType]
22+
};
23+
24+
const device = await pc.createGraphicsDevice(canvas, gfxOptions);
25+
device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
26+
27+
const createOptions = new pc.AppOptions();
28+
createOptions.graphicsDevice = device;
29+
30+
createOptions.componentSystems = [
31+
pc.RenderComponentSystem,
32+
pc.CameraComponentSystem,
33+
pc.LightComponentSystem,
34+
pc.ScriptComponentSystem
35+
];
36+
createOptions.resourceHandlers = [pc.TextureHandler, pc.ContainerHandler, pc.ScriptHandler];
37+
38+
const app = new pc.AppBase(canvas);
39+
app.init(createOptions);
40+
41+
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
42+
app.setCanvasResolution(pc.RESOLUTION_AUTO);
43+
44+
const resize = () => app.resizeCanvas();
45+
window.addEventListener('resize', resize);
46+
app.on('destroy', () => {
47+
window.removeEventListener('resize', resize);
48+
});
49+
50+
await new Promise((resolve) => {
51+
new pc.AssetListLoader(Object.values(assets), app.assets).load(resolve);
52+
});
53+
54+
app.start();
55+
56+
app.scene.skyboxMip = 1;
57+
app.scene.skyboxIntensity = 0.4;
58+
app.scene.envAtlas = assets.helipad.resource;
59+
60+
const light = new pc.Entity();
61+
light.addComponent('light', {
62+
type: 'directional',
63+
color: new pc.Color(1, 0.8, 0.25),
64+
intensity: 2
65+
});
66+
light.setLocalEulerAngles(45, 30, 0);
67+
app.root.addChild(light);
68+
69+
const entity = assets.model.resource.instantiateRenderEntity();
70+
app.root.addChild(entity);
71+
72+
const camera = new pc.Entity();
73+
camera.addComponent('camera', {
74+
toneMapping: pc.TONEMAP_ACES
75+
});
76+
camera.addComponent('script');
77+
camera.setPosition(0, 2, 6);
78+
app.root.addChild(camera);
79+
80+
const cc = /** @type {CameraControls} */ (camera.script.create(CameraControls));
81+
cc.focusPoint = new pc.Vec3(0, 0, 0);
82+
83+
const lightMaterial = new pc.StandardMaterial();
84+
lightMaterial.emissive = pc.Color.WHITE;
85+
lightMaterial.diffuse = pc.Color.BLACK;
86+
lightMaterial.useLighting = false;
87+
lightMaterial.update();
88+
89+
const omniLight = new pc.Entity();
90+
omniLight.addComponent('light', {
91+
type: 'omni',
92+
color: new pc.Color(1, 1, 1),
93+
intensity: 4,
94+
range: 10,
95+
castShadows: false
96+
});
97+
omniLight.addComponent('render', {
98+
type: 'sphere',
99+
material: lightMaterial,
100+
castShadows: false,
101+
receiveShadows: false
102+
});
103+
omniLight.setLocalScale(0.1, 0.1, 0.1);
104+
app.root.addChild(omniLight);
105+
106+
const orbitRadius = 2;
107+
let time = 0;
108+
app.on('update', (dt) => {
109+
time += dt * 0.5;
110+
omniLight.setPosition(
111+
Math.cos(time) * orbitRadius,
112+
Math.sin(time) * orbitRadius,
113+
0
114+
);
115+
});
116+
117+
export { app };

src/extras/renderers/outline-renderer.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ import { Entity } from '../../framework/entity.js';
33
import { BlendState } from '../../platform/graphics/blend-state.js';
44
import {
55
ADDRESS_CLAMP_TO_EDGE, BLENDEQUATION_ADD, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDMODE_SRC_ALPHA,
6-
CULLFACE_NONE,
7-
FILTER_LINEAR, FILTER_LINEAR_MIPMAP_LINEAR, FRONTFACE_CCW, PIXELFORMAT_SRGBA8,
6+
FILTER_LINEAR, FILTER_LINEAR_MIPMAP_LINEAR, PIXELFORMAT_SRGBA8,
87
SEMANTIC_POSITION
98
} from '../../platform/graphics/constants.js';
10-
import { DepthState } from '../../platform/graphics/depth-state.js';
119
import { RenderTarget } from '../../platform/graphics/render-target.js';
1210
import { Texture } from '../../platform/graphics/texture.js';
1311
import { drawQuadWithShader } from '../../scene/graphics/quad-render-utils.js';
@@ -314,10 +312,7 @@ class OutlineRenderer {
314312
const device = this.app.graphicsDevice;
315313
device.scope.resolve('source').setValue(this.rt.colorBuffer);
316314

317-
device.setDepthState(DepthState.NODEPTH);
318-
device.setCullMode(CULLFACE_NONE);
319-
device.setFrontFace(FRONTFACE_CCW);
320-
device.setBlendState(this.blendState);
315+
device.setDrawStates(this.blendState);
321316
this.quadRenderer.render();
322317
}
323318

src/platform/graphics/graphics-device.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Color } from '../../core/math/color.js';
99
import { TRACEID_TEXTURES } from '../../core/constants.js';
1010
import {
1111
BUFFER_STATIC,
12-
CULLFACE_BACK,
12+
CULLFACE_BACK, CULLFACE_NONE,
1313
CLEARFLAG_COLOR, CLEARFLAG_DEPTH,
1414
INDEXFORMAT_UINT16,
1515
PRIMITIVE_POINTS, PRIMITIVE_TRIFAN, SEMANTIC_POSITION, TYPE_FLOAT32, PIXELFORMAT_111110F, PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F,
@@ -854,6 +854,28 @@ class GraphicsDevice extends EventHandler {
854854
Debug.assert(false);
855855
}
856856

857+
/**
858+
* Sets all draw-related render states in a single call. All parameters have sensible defaults
859+
* for utility rendering (full-screen quads, particles, etc.), so calling `setDrawStates()` with
860+
* no arguments resets to a safe baseline.
861+
*
862+
* @param {BlendState} [blendState] - Blend state. Defaults to {@link BlendState.NOBLEND}.
863+
* @param {DepthState} [depthState] - Depth state. Defaults to {@link DepthState.NODEPTH}.
864+
* @param {number} [cullMode] - Cull mode. Defaults to {@link CULLFACE_NONE}.
865+
* @param {number} [frontFace] - Front face winding. Defaults to {@link FRONTFACE_CCW}.
866+
* @param {StencilParameters} [stencilFront] - Front stencil parameters.
867+
* @param {StencilParameters} [stencilBack] - Back stencil parameters.
868+
*/
869+
setDrawStates(blendState = BlendState.NOBLEND, depthState = DepthState.NODEPTH,
870+
cullMode = CULLFACE_NONE, frontFace = FRONTFACE_CCW,
871+
stencilFront, stencilBack) {
872+
this.setBlendState(blendState);
873+
this.setDepthState(depthState);
874+
this.setCullMode(cullMode);
875+
this.setFrontFace(frontFace);
876+
this.setStencilState(stencilFront, stencilBack);
877+
}
878+
857879
/**
858880
* Sets the specified render target on the device. If null is passed as a parameter, the back
859881
* buffer becomes the current target for all rendering operations.

src/platform/graphics/webgpu/webgpu-clear-renderer.js

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ import { Debug } from '../../../core/debug.js';
22
import { UniformBufferFormat, UniformFormat } from '../uniform-buffer-format.js';
33
import { BlendState } from '../blend-state.js';
44
import {
5-
CULLFACE_NONE,
65
PRIMITIVE_TRISTRIP, SHADERLANGUAGE_WGSL,
76
UNIFORMTYPE_FLOAT, UNIFORMTYPE_VEC4, BINDGROUP_MESH, CLEARFLAG_COLOR, CLEARFLAG_DEPTH, CLEARFLAG_STENCIL,
8-
BINDGROUP_MESH_UB,
9-
FRONTFACE_CCW
7+
BINDGROUP_MESH_UB
108
} from '../constants.js';
119
import { Shader } from '../shader.js';
1210
import { DynamicBindGroup } from '../bind-group.js';
@@ -110,25 +108,25 @@ class WebgpuClearRenderer {
110108
device.setBindGroup(BINDGROUP_MESH, device.emptyBindGroup);
111109

112110
// setup clear color
111+
let blendState;
113112
if ((flags & CLEARFLAG_COLOR) && (renderTarget.colorBuffer || renderTarget.impl.assignedColorTexture)) {
114113
const color = options.color ?? defaultOptions.color;
115114
this.colorData.set(color);
116-
117-
device.setBlendState(BlendState.NOBLEND);
115+
blendState = BlendState.NOBLEND;
118116
} else {
119-
device.setBlendState(BlendState.NOWRITE);
117+
blendState = BlendState.NOWRITE;
120118
}
121119
uniformBuffer.set('color', this.colorData);
122120

123121
// setup depth clear
122+
let depthState;
124123
if ((flags & CLEARFLAG_DEPTH) && renderTarget.depth) {
125124
const depth = options.depth ?? defaultOptions.depth;
126125
uniformBuffer.set('depth', depth);
127-
device.setDepthState(DepthState.WRITEDEPTH);
128-
126+
depthState = DepthState.WRITEDEPTH;
129127
} else {
130128
uniformBuffer.set('depth', 1);
131-
device.setDepthState(DepthState.NODEPTH);
129+
depthState = DepthState.NODEPTH;
132130
}
133131

134132
// setup stencil clear
@@ -138,8 +136,7 @@ class WebgpuClearRenderer {
138136

139137
uniformBuffer.endUpdate();
140138

141-
device.setCullMode(CULLFACE_NONE);
142-
device.setFrontFace(FRONTFACE_CCW);
139+
device.setDrawStates(blendState, depthState);
143140

144141
// render 4 vertices without vertex buffer
145142
device.setShader(this.shader);

src/scene/graphics/quad-render.js

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,10 @@ const _dynamicBindGroup = new DynamicBindGroup();
2626
* An object that renders a quad using a {@link Shader}.
2727
*
2828
* Note: QuadRender does not modify render states. Before calling {@link QuadRender#render},
29-
* you should set up the following states as needed, otherwise previously set states will be used:
30-
* - Blend state via {@link GraphicsDevice#setBlendState}
31-
* - Cull mode via {@link GraphicsDevice#setCullMode}
32-
* - FrontFace via {@link GraphicsDevice#setFrontFace}
33-
* - Depth state via {@link GraphicsDevice#setDepthState}
34-
* - Stencil state via {@link GraphicsDevice#setStencilState}
29+
* you should set up the required states using {@link GraphicsDevice#setDrawStates}, or the
30+
* individual setters ({@link GraphicsDevice#setBlendState}, {@link GraphicsDevice#setCullMode},
31+
* {@link GraphicsDevice#setFrontFace}, {@link GraphicsDevice#setDepthState},
32+
* {@link GraphicsDevice#setStencilState}). Otherwise previously set states will be used.
3533
*
3634
* Example:
3735
*
@@ -44,12 +42,8 @@ const _dynamicBindGroup = new DynamicBindGroup();
4442
* });
4543
* const quad = new QuadRender(shader);
4644
*
47-
* // Set up render states before rendering
48-
* app.graphicsDevice.setBlendState(BlendState.NOBLEND);
49-
* app.graphicsDevice.setCullMode(CULLFACE_NONE);
50-
* app.graphicsDevice.setFrontFace(FRONTFACE_CCW);
51-
* app.graphicsDevice.setDepthState(DepthState.NODEPTH);
52-
* app.graphicsDevice.setStencilState(null, null);
45+
* // Set up render states before rendering (defaults are suitable for full-screen quads)
46+
* app.graphicsDevice.setDrawStates();
5347
*
5448
* quad.render();
5549
* quad.destroy();

src/scene/graphics/render-pass-quad.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import { CULLFACE_NONE, FRONTFACE_CCW } from '../../platform/graphics/constants.js';
21
import { DebugGraphics } from '../../platform/graphics/debug-graphics.js';
3-
import { DepthState } from '../../platform/graphics/depth-state.js';
42
import { RenderPass } from '../../platform/graphics/render-pass.js';
53

64
/**
@@ -19,10 +17,7 @@ class RenderPassQuad extends RenderPass {
1917
const { device } = this;
2018
DebugGraphics.pushGpuMarker(device, `${this.name}:${this.quad.shader.name}`);
2119

22-
device.setCullMode(CULLFACE_NONE);
23-
device.setFrontFace(FRONTFACE_CCW);
24-
device.setDepthState(DepthState.NODEPTH);
25-
device.setStencilState(null, null);
20+
device.setDrawStates();
2621

2722
this.quad.render(this.rect, this.scissorRect);
2823
DebugGraphics.popGpuMarker(device);

src/scene/graphics/render-pass-shader-quad.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,7 @@ class RenderPassShaderQuad extends RenderPass {
108108
execute() {
109109

110110
// render state
111-
const device = this.device;
112-
device.setBlendState(this.blendState);
113-
device.setCullMode(this.cullMode);
114-
device.setFrontFace(this.frontFace);
115-
device.setDepthState(this.depthState);
116-
device.setStencilState(this.stencilFront, this.stencilBack);
111+
this.device.setDrawStates(this.blendState, this.depthState, this.cullMode, this.frontFace, this.stencilFront, this.stencilBack);
117112

118113
this.quadRender?.render(this.viewport, this.scissor);
119114
}

src/scene/gsplat-unified/gsplat-work-buffer-render-pass.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ import { Vec3 } from '../../core/math/vec3.js';
55
import { Quat } from '../../core/math/quat.js';
66
import { RenderPass } from '../../platform/graphics/render-pass.js';
77
import { DebugGraphics } from '../../platform/graphics/debug-graphics.js';
8-
import { BlendState } from '../../platform/graphics/blend-state.js';
9-
import { DepthState } from '../../platform/graphics/depth-state.js';
10-
import { CULLFACE_NONE, FRONTFACE_CCW, PIXELFORMAT_RGBA32U } from '../../platform/graphics/constants.js';
8+
import { PIXELFORMAT_RGBA32U } from '../../platform/graphics/constants.js';
119
import { Texture } from '../../platform/graphics/texture.js';
1210
import { TextureUtils } from '../../platform/graphics/texture-utils.js';
1311

@@ -199,11 +197,7 @@ class GSplatWorkBufferRenderPass extends RenderPass {
199197
DebugGraphics.pushGpuMarker(device, 'GSplatWorkBuffer');
200198

201199
// Set up render state
202-
device.setBlendState(BlendState.NOBLEND);
203-
device.setCullMode(CULLFACE_NONE);
204-
device.setFrontFace(FRONTFACE_CCW);
205-
device.setDepthState(DepthState.NODEPTH);
206-
device.setStencilState();
200+
device.setDrawStates();
207201

208202
// view matrix
209203
const viewInvMat = cameraNode.getWorldTransform();

src/scene/gsplat/gsplat-resolve-sh.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
import { Vec3 } from '../../core/math/vec3.js';
22
import { Mat4 } from '../../core/math/mat4.js';
3-
import { BlendState } from '../../platform/graphics/blend-state.js';
43
import {
5-
CULLFACE_NONE,
6-
FRONTFACE_CCW,
74
PIXELFORMAT_RGBA8,
85
SEMANTIC_POSITION
96
} from '../../platform/graphics/constants.js';
10-
import { DepthState } from '../../platform/graphics/depth-state.js';
117
import { RenderTarget } from '../../platform/graphics/render-target.js';
128
import { ShaderUtils } from '../shader-lib/shader-utils.js';
139
import { ShaderChunks } from '../shader-lib/shader-chunks.js';
@@ -290,11 +286,7 @@ class GSplatResolveSH {
290286
shN_maxs: meta.shN.maxs
291287
});
292288

293-
device.setCullMode(CULLFACE_NONE);
294-
device.setFrontFace(FRONTFACE_CCW);
295-
device.setDepthState(DepthState.NODEPTH);
296-
device.setStencilState(null, null);
297-
device.setBlendState(BlendState.NOBLEND);
289+
device.setDrawStates();
298290

299291
this.quadRender.render();
300292
};

0 commit comments

Comments
 (0)