diff --git a/src/extras/renderers/outline-renderer.js b/src/extras/renderers/outline-renderer.js index 41b0cb26bed..9a0a60b3694 100644 --- a/src/extras/renderers/outline-renderer.js +++ b/src/extras/renderers/outline-renderer.js @@ -4,7 +4,7 @@ import { BlendState } from '../../platform/graphics/blend-state.js'; import { ADDRESS_CLAMP_TO_EDGE, BLENDEQUATION_ADD, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDMODE_SRC_ALPHA, CULLFACE_NONE, - FILTER_LINEAR, FILTER_LINEAR_MIPMAP_LINEAR, PIXELFORMAT_SRGBA8, + FILTER_LINEAR, FILTER_LINEAR_MIPMAP_LINEAR, FRONTFACE_CCW, PIXELFORMAT_SRGBA8, SEMANTIC_POSITION } from '../../platform/graphics/constants.js'; import { DepthState } from '../../platform/graphics/depth-state.js'; @@ -316,6 +316,7 @@ class OutlineRenderer { device.setDepthState(DepthState.NODEPTH); device.setCullMode(CULLFACE_NONE); + device.setFrontFace(FRONTFACE_CCW); device.setBlendState(this.blendState); this.quadRenderer.render(); } diff --git a/src/platform/graphics/constants.js b/src/platform/graphics/constants.js index c5808cbcca1..d6f4c035206 100644 --- a/src/platform/graphics/constants.js +++ b/src/platform/graphics/constants.js @@ -344,6 +344,20 @@ export const CULLFACE_FRONT = 2; */ export const CULLFACE_FRONTANDBACK = 3; +/** + * The counterclockwise winding. Specifies whether polygons are front- or back-facing by setting a winding orientation. + * + * @category Graphics + */ +export const FRONTFACE_CCW = 0; + +/** + * The clockwise winding. Specifies whether polygons are front- or back-facing by setting a winding orientation. + * + * @category Graphics + */ +export const FRONTFACE_CW = 1; + /** * Point sample filtering. * diff --git a/src/platform/graphics/graphics-device.js b/src/platform/graphics/graphics-device.js index 5a18ca1741c..e1f6c073c45 100644 --- a/src/platform/graphics/graphics-device.js +++ b/src/platform/graphics/graphics-device.js @@ -14,7 +14,8 @@ import { INDEXFORMAT_UINT16, PRIMITIVE_POINTS, PRIMITIVE_TRIFAN, SEMANTIC_POSITION, TYPE_FLOAT32, PIXELFORMAT_111110F, PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F, DISPLAYFORMAT_LDR, - semanticToLocation + semanticToLocation, + FRONTFACE_CCW } from './constants.js'; import { BlendState } from './blend-state.js'; import { DepthState } from './depth-state.js'; @@ -772,6 +773,7 @@ class GraphicsDevice extends EventHandler { this.blendState = new BlendState(); this.depthState = new DepthState(); this.cullMode = CULLFACE_BACK; + this.frontFace = FRONTFACE_CCW; // Cached viewport and scissor dimensions this.vx = this.vy = this.vw = this.vh = 0; @@ -839,6 +841,19 @@ class GraphicsDevice extends EventHandler { Debug.assert(false); } + /** + * Controls whether polygons are front- or back-facing by setting a winding + * orientation. The default frontFace is {@link FRONTFACE_CCW}. + * + * @param {number} frontFace - The front face to set. Can be: + * + * - {@link FRONTFACE_CW} + * - {@link FRONTFACE_CCW} + */ + setFrontFace(frontFace) { + Debug.assert(false); + } + /** * Sets the specified render target on the device. If null is passed as a parameter, the back * buffer becomes the current target for all rendering operations. diff --git a/src/platform/graphics/null/null-graphics-device.js b/src/platform/graphics/null/null-graphics-device.js index dd935dde275..34fff37589b 100644 --- a/src/platform/graphics/null/null-graphics-device.js +++ b/src/platform/graphics/null/null-graphics-device.js @@ -126,6 +126,9 @@ class NullGraphicsDevice extends GraphicsDevice { setCullMode(cullMode) { } + setFrontFace(frontFace) { + } + setAlphaToCoverage(state) { } diff --git a/src/platform/graphics/webgl/webgl-graphics-device.js b/src/platform/graphics/webgl/webgl-graphics-device.js index d76fbf7a2ef..9ed787dac0b 100644 --- a/src/platform/graphics/webgl/webgl-graphics-device.js +++ b/src/platform/graphics/webgl/webgl-graphics-device.js @@ -335,6 +335,11 @@ class WebglGraphicsDevice extends GraphicsDevice { gl.FRONT_AND_BACK ]; + this.glFrontFace = [ + gl.CCW, + gl.CW + ]; + this.glFilter = [ gl.NEAREST, gl.LINEAR, @@ -2512,6 +2517,14 @@ class WebglGraphicsDevice extends GraphicsDevice { } } + setFrontFace(frontFace) { + if (this.frontFace !== frontFace) { + const mode = this.glFrontFace[frontFace]; + this.gl.frontFace(mode); + this.frontFace = frontFace; + } + } + /** * Sets the active shader to be used during subsequent draw calls. * diff --git a/src/platform/graphics/webgpu/webgpu-clear-renderer.js b/src/platform/graphics/webgpu/webgpu-clear-renderer.js index 88fcbacd8d9..1c5d970b240 100644 --- a/src/platform/graphics/webgpu/webgpu-clear-renderer.js +++ b/src/platform/graphics/webgpu/webgpu-clear-renderer.js @@ -5,7 +5,8 @@ import { CULLFACE_NONE, PRIMITIVE_TRISTRIP, SHADERLANGUAGE_WGSL, UNIFORMTYPE_FLOAT, UNIFORMTYPE_VEC4, BINDGROUP_MESH, CLEARFLAG_COLOR, CLEARFLAG_DEPTH, CLEARFLAG_STENCIL, - BINDGROUP_MESH_UB + BINDGROUP_MESH_UB, + FRONTFACE_CCW } from '../constants.js'; import { Shader } from '../shader.js'; import { DynamicBindGroup } from '../bind-group.js'; @@ -138,6 +139,7 @@ class WebgpuClearRenderer { uniformBuffer.endUpdate(); device.setCullMode(CULLFACE_NONE); + device.setFrontFace(FRONTFACE_CCW); // render 4 vertices without vertex buffer device.setShader(this.shader); diff --git a/src/platform/graphics/webgpu/webgpu-graphics-device.js b/src/platform/graphics/webgpu/webgpu-graphics-device.js index 8730ad4c93a..5d3d3a111ed 100644 --- a/src/platform/graphics/webgpu/webgpu-graphics-device.js +++ b/src/platform/graphics/webgpu/webgpu-graphics-device.js @@ -732,7 +732,7 @@ class WebgpuGraphicsDevice extends GraphicsDevice { // render pipeline pipeline = this.renderPipeline.get(primitive, vb0?.format, vb1?.format, indexBuffer?.format, this.shader, this.renderTarget, this.bindGroupFormats, this.blendState, this.depthState, this.cullMode, - this.stencilEnabled, this.stencilFront, this.stencilBack); + this.stencilEnabled, this.stencilFront, this.stencilBack, this.frontFace); Debug.assert(pipeline); if (this.pipeline !== pipeline) { @@ -851,6 +851,10 @@ class WebgpuGraphicsDevice extends GraphicsDevice { this.cullMode = cullMode; } + setFrontFace(frontFace) { + this.frontFace = frontFace; + } + setAlphaToCoverage(state) { } diff --git a/src/platform/graphics/webgpu/webgpu-render-pipeline.js b/src/platform/graphics/webgpu/webgpu-render-pipeline.js index 9520faa7289..6fe98e67f4d 100644 --- a/src/platform/graphics/webgpu/webgpu-render-pipeline.js +++ b/src/platform/graphics/webgpu/webgpu-render-pipeline.js @@ -72,6 +72,11 @@ const _cullModes = [ 'front' // CULLFACE_FRONT ]; +const _frontFace = [ + 'ccw', // FRONTFACE_CCW + 'cw' // FRONTFACE_CW +]; + const _stencilOps = [ 'keep', // STENCILOP_KEEP 'zero', // STENCILOP_ZERO @@ -107,7 +112,7 @@ class CacheEntry { } class WebgpuRenderPipeline extends WebgpuPipeline { - lookupHashes = new Uint32Array(14); + lookupHashes = new Uint32Array(15); constructor(device) { super(device); @@ -141,11 +146,12 @@ class WebgpuRenderPipeline extends WebgpuPipeline { * @param {boolean} stencilEnabled - Whether stencil is enabled. * @param {StencilParameters} stencilFront - The stencil state for front faces. * @param {StencilParameters} stencilBack - The stencil state for back faces. + * @param {number} frontFace - The front face. * @returns {GPURenderPipeline} Returns the render pipeline. * @private */ get(primitive, vertexFormat0, vertexFormat1, ibFormat, shader, renderTarget, bindGroupFormats, blendState, - depthState, cullMode, stencilEnabled, stencilFront, stencilBack) { + depthState, cullMode, stencilEnabled, stencilFront, stencilBack, frontFace) { Debug.assert(bindGroupFormats.length <= 3); @@ -177,6 +183,7 @@ class WebgpuRenderPipeline extends WebgpuPipeline { lookupHashes[11] = stencilEnabled ? stencilFront.key : 0; lookupHashes[12] = stencilEnabled ? stencilBack.key : 0; lookupHashes[13] = ibFormat ?? 0; + lookupHashes[14] = frontFace; const hash = hash32Fnv1a(lookupHashes); // cached pipeline @@ -206,7 +213,7 @@ class WebgpuRenderPipeline extends WebgpuPipeline { const cacheEntry = new CacheEntry(); cacheEntry.hashes = new Uint32Array(lookupHashes); cacheEntry.pipeline = this.create(primitiveTopology, ibFormat, shader, renderTarget, pipelineLayout, blendState, - depthState, vertexBufferLayout, cullMode, stencilEnabled, stencilFront, stencilBack); + depthState, vertexBufferLayout, cullMode, stencilEnabled, stencilFront, stencilBack, frontFace); // add to cache if (cacheEntries) { @@ -313,7 +320,7 @@ class WebgpuRenderPipeline extends WebgpuPipeline { } create(primitiveTopology, ibFormat, shader, renderTarget, pipelineLayout, blendState, depthState, vertexBufferLayout, - cullMode, stencilEnabled, stencilFront, stencilBack) { + cullMode, stencilEnabled, stencilFront, stencilBack, frontFace) { const wgpu = this.device.wgpu; @@ -330,7 +337,7 @@ class WebgpuRenderPipeline extends WebgpuPipeline { primitive: { topology: primitiveTopology, - frontFace: 'ccw', + frontFace: _frontFace[frontFace], cullMode: _cullModes[cullMode] }, diff --git a/src/scene/graphics/quad-render.js b/src/scene/graphics/quad-render.js index 192ace60893..f583602f265 100644 --- a/src/scene/graphics/quad-render.js +++ b/src/scene/graphics/quad-render.js @@ -29,6 +29,7 @@ const _dynamicBindGroup = new DynamicBindGroup(); * you should set up the following states as needed, otherwise previously set states will be used: * - Blend state via {@link GraphicsDevice#setBlendState} * - Cull mode via {@link GraphicsDevice#setCullMode} + * - FrontFace via {@link GraphicsDevice#setFrontFace} * - Depth state via {@link GraphicsDevice#setDepthState} * - Stencil state via {@link GraphicsDevice#setStencilState} * @@ -46,6 +47,7 @@ const _dynamicBindGroup = new DynamicBindGroup(); * // Set up render states before rendering * app.graphicsDevice.setBlendState(BlendState.NOBLEND); * app.graphicsDevice.setCullMode(CULLFACE_NONE); + * app.graphicsDevice.setFrontFace(FRONTFACE_CCW); * app.graphicsDevice.setDepthState(DepthState.NODEPTH); * app.graphicsDevice.setStencilState(null, null); * diff --git a/src/scene/graphics/render-pass-quad.js b/src/scene/graphics/render-pass-quad.js index a2e4899becb..13ace57045e 100644 --- a/src/scene/graphics/render-pass-quad.js +++ b/src/scene/graphics/render-pass-quad.js @@ -1,4 +1,4 @@ -import { CULLFACE_NONE } from '../../platform/graphics/constants.js'; +import { CULLFACE_NONE, FRONTFACE_CCW } from '../../platform/graphics/constants.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; import { DepthState } from '../../platform/graphics/depth-state.js'; import { RenderPass } from '../../platform/graphics/render-pass.js'; @@ -20,6 +20,7 @@ class RenderPassQuad extends RenderPass { DebugGraphics.pushGpuMarker(device, `${this.name}:${this.quad.shader.name}`); device.setCullMode(CULLFACE_NONE); + device.setFrontFace(FRONTFACE_CCW); device.setDepthState(DepthState.NODEPTH); device.setStencilState(null, null); diff --git a/src/scene/graphics/render-pass-shader-quad.js b/src/scene/graphics/render-pass-shader-quad.js index 3ffc45bee0c..2ed175c9b10 100644 --- a/src/scene/graphics/render-pass-shader-quad.js +++ b/src/scene/graphics/render-pass-shader-quad.js @@ -1,6 +1,6 @@ import { QuadRender } from './quad-render.js'; import { BlendState } from '../../platform/graphics/blend-state.js'; -import { CULLFACE_NONE } from '../../platform/graphics/constants.js'; +import { CULLFACE_NONE, FRONTFACE_CCW } from '../../platform/graphics/constants.js'; import { DepthState } from '../../platform/graphics/depth-state.js'; import { RenderPass } from '../../platform/graphics/render-pass.js'; @@ -33,6 +33,11 @@ class RenderPassShaderQuad extends RenderPass { */ cullMode = CULLFACE_NONE; + /** + * The front face to use when rendering the quad. Defaults to {@link FRONTFACE_CCW}. + */ + frontFace = FRONTFACE_CCW; + /** * A blend state to use when rendering the quad. Defaults to {@link BlendState.NOBLEND}. * @@ -106,6 +111,7 @@ class RenderPassShaderQuad extends RenderPass { const device = this.device; device.setBlendState(this.blendState); device.setCullMode(this.cullMode); + device.setFrontFace(this.frontFace); device.setDepthState(this.depthState); device.setStencilState(this.stencilFront, this.stencilBack); diff --git a/src/scene/gsplat-unified/gsplat-work-buffer-render-pass.js b/src/scene/gsplat-unified/gsplat-work-buffer-render-pass.js index 903682b3591..6df473971ae 100644 --- a/src/scene/gsplat-unified/gsplat-work-buffer-render-pass.js +++ b/src/scene/gsplat-unified/gsplat-work-buffer-render-pass.js @@ -6,7 +6,7 @@ import { RenderPass } from '../../platform/graphics/render-pass.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; import { BlendState } from '../../platform/graphics/blend-state.js'; import { DepthState } from '../../platform/graphics/depth-state.js'; -import { CULLFACE_NONE } from '../../platform/graphics/constants.js'; +import { CULLFACE_NONE, FRONTFACE_CCW } from '../../platform/graphics/constants.js'; /** * @import { GSplatInfo } from './gsplat-info.js' @@ -105,6 +105,7 @@ class GSplatWorkBufferRenderPass extends RenderPass { // Set up render state device.setBlendState(BlendState.NOBLEND); device.setCullMode(CULLFACE_NONE); + device.setFrontFace(FRONTFACE_CCW); device.setDepthState(DepthState.NODEPTH); device.setStencilState(); diff --git a/src/scene/gsplat/gsplat-resolve-sh.js b/src/scene/gsplat/gsplat-resolve-sh.js index a3d9978cb8f..16932fcbc53 100644 --- a/src/scene/gsplat/gsplat-resolve-sh.js +++ b/src/scene/gsplat/gsplat-resolve-sh.js @@ -3,6 +3,7 @@ import { Mat4 } from '../../core/math/mat4.js'; import { BlendState } from '../../platform/graphics/blend-state.js'; import { CULLFACE_NONE, + FRONTFACE_CCW, PIXELFORMAT_RGBA8, SEMANTIC_POSITION } from '../../platform/graphics/constants.js'; @@ -290,6 +291,7 @@ class GSplatResolveSH { }); device.setCullMode(CULLFACE_NONE); + device.setFrontFace(FRONTFACE_CCW); device.setDepthState(DepthState.NODEPTH); device.setStencilState(null, null); device.setBlendState(BlendState.NOBLEND); diff --git a/src/scene/materials/material.js b/src/scene/materials/material.js index 58b06dc7d68..e5cf31f3551 100644 --- a/src/scene/materials/material.js +++ b/src/scene/materials/material.js @@ -6,7 +6,8 @@ import { BLENDEQUATION_ADD, BLENDEQUATION_REVERSE_SUBTRACT, BLENDEQUATION_MIN, BLENDEQUATION_MAX, CULLFACE_BACK, - SHADERLANGUAGE_GLSL + SHADERLANGUAGE_GLSL, + FRONTFACE_CCW } from '../../platform/graphics/constants.js'; import { BlendState } from '../../platform/graphics/blend-state.js'; import { DepthState } from '../../platform/graphics/depth-state.js'; @@ -167,6 +168,19 @@ class Material { */ cull = CULLFACE_BACK; + /** + * Controls whether polygons are front- or back-facing by setting a winding + * orientation. Can be: + * + * - {@link FRONTFACE_CW}: The clock-wise winding. + * - {@link FRONTFACE_CCW}: The counterclockwise winding. + * + * Defaults to {@link FRONTFACE_CCW}. + * + * @type {number} + */ + frontFace = FRONTFACE_CCW; + /** * Stencil parameters for front faces (default is null). * @@ -637,6 +651,7 @@ class Material { this._depthState.copy(source._depthState); this.cull = source.cull; + this.frontFace = source.frontFace; this.stencilFront = source.stencilFront?.clone(); if (source.stencilBack) { diff --git a/src/scene/particle-system/gpu-updater.js b/src/scene/particle-system/gpu-updater.js index 13c031d1282..ba1f24e5c37 100644 --- a/src/scene/particle-system/gpu-updater.js +++ b/src/scene/particle-system/gpu-updater.js @@ -3,7 +3,7 @@ import { Mat3 } from '../../core/math/mat3.js'; import { Mat4 } from '../../core/math/mat4.js'; import { Vec3 } from '../../core/math/vec3.js'; -import { CULLFACE_NONE } from '../../platform/graphics/constants.js'; +import { CULLFACE_NONE, FRONTFACE_CCW } from '../../platform/graphics/constants.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; import { BlendState } from '../../platform/graphics/blend-state.js'; import { DepthState } from '../../platform/graphics/depth-state.js'; @@ -96,6 +96,7 @@ class ParticleGPUUpdater { device.setBlendState(BlendState.NOBLEND); device.setDepthState(DepthState.NODEPTH); device.setCullMode(CULLFACE_NONE); + device.setFrontFace(FRONTFACE_CCW); this.randomize(); diff --git a/src/scene/renderer/forward-renderer.js b/src/scene/renderer/forward-renderer.js index fdc96b28a24..f930929a904 100644 --- a/src/scene/renderer/forward-renderer.js +++ b/src/scene/renderer/forward-renderer.js @@ -625,7 +625,7 @@ class ForwardRenderer extends Renderer { DebugGraphics.pushGpuMarker(device, `Node: ${drawCall.node.name}, Material: ${material.name}`); - this.setupCullMode(camera._cullFaces, flipFactor, drawCall); + this.setupCullModeAndFrontFace(camera._cullFaces, flipFactor, drawCall); const stencilFront = drawCall.stencilFront ?? material.stencilFront; const stencilBack = drawCall.stencilBack ?? material.stencilBack; diff --git a/src/scene/renderer/render-pass-cookie-renderer.js b/src/scene/renderer/render-pass-cookie-renderer.js index d72b4c60100..87804053181 100644 --- a/src/scene/renderer/render-pass-cookie-renderer.js +++ b/src/scene/renderer/render-pass-cookie-renderer.js @@ -1,7 +1,7 @@ import { Debug } from '../../core/debug.js'; import { Vec4 } from '../../core/math/vec4.js'; import { Mat4 } from '../../core/math/mat4.js'; -import { CULLFACE_NONE, SEMANTIC_POSITION } from '../../platform/graphics/constants.js'; +import { CULLFACE_NONE, FRONTFACE_CCW, SEMANTIC_POSITION } from '../../platform/graphics/constants.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; import { LIGHTTYPE_DIRECTIONAL, LIGHTTYPE_OMNI } from '../constants.js'; import { ShaderUtils } from '../shader-lib/shader-utils.js'; @@ -165,6 +165,7 @@ class RenderPassCookieRenderer extends RenderPass { const device = this.device; device.setBlendState(BlendState.NOBLEND); device.setCullMode(CULLFACE_NONE); + device.setFrontFace(FRONTFACE_CCW); device.setDepthState(DepthState.NODEPTH); device.setStencilState(); diff --git a/src/scene/renderer/renderer.js b/src/scene/renderer/renderer.js index eb05b7d3272..be117f2406f 100644 --- a/src/scene/renderer/renderer.js +++ b/src/scene/renderer/renderer.js @@ -14,7 +14,9 @@ import { UNIFORMTYPE_MAT4, UNIFORMTYPE_MAT3, UNIFORMTYPE_VEC4, UNIFORMTYPE_VEC3, UNIFORMTYPE_IVEC3, UNIFORMTYPE_VEC2, UNIFORMTYPE_FLOAT, UNIFORMTYPE_INT, SHADERSTAGE_VERTEX, SHADERSTAGE_FRAGMENT, CULLFACE_BACK, CULLFACE_FRONT, CULLFACE_NONE, - BINDGROUP_MESH_UB + BINDGROUP_MESH_UB, + FRONTFACE_CCW, + FRONTFACE_CW } from '../../platform/graphics/constants.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; import { UniformBuffer } from '../../platform/graphics/uniform-buffer.js'; @@ -482,29 +484,47 @@ class Renderer { } } - setupCullMode(cullFaces, flipFactor, drawCall) { + setupCullModeAndFrontFace(cullFaces, flipFactor, drawCall) { const material = drawCall.material; - let mode = CULLFACE_NONE; + const worldScaleSign = drawCall.node.worldScaleSign; + + // Calculate total face flip factor + const flipFaces = flipFactor * drawCall.flipFacesFactor * worldScaleSign; + + let cullMode = CULLFACE_NONE; + let frontFace = material.frontFace; + + if (flipFaces < 0) { + frontFace = frontFace === FRONTFACE_CCW ? FRONTFACE_CW : FRONTFACE_CCW; + } + if (cullFaces) { - let flipFaces = 1; - if (material.cull === CULLFACE_FRONT || material.cull === CULLFACE_BACK) { - flipFaces = flipFactor * drawCall.flipFacesFactor * drawCall.node.worldScaleSign; - } + cullMode = material.cull; if (flipFaces < 0) { - mode = material.cull === CULLFACE_FRONT ? CULLFACE_BACK : CULLFACE_FRONT; - } else { - mode = material.cull; + + if (cullMode === CULLFACE_FRONT) { + cullMode = CULLFACE_BACK; + } else if (cullMode === CULLFACE_BACK) { + cullMode = CULLFACE_FRONT; + } } } - this.device.setCullMode(mode); - if (mode === CULLFACE_NONE && material.cull === CULLFACE_NONE) { - this.twoSidedLightingNegScaleFactorId.setValue(drawCall.node.worldScaleSign); + this.device.setCullMode(cullMode); + this.device.setFrontFace(frontFace); + + if (cullMode === CULLFACE_NONE && material.cull === CULLFACE_NONE) { + this.twoSidedLightingNegScaleFactorId.setValue(worldScaleSign); } } + setupCullMode(cullFaces, flipFactor, drawCall) { + Debug.deprecated('pc.Renderer.setupCullMode is deprecated. Use \'pc.Renderer.setupCullModeAndFrontFace(cullFaces, flipFactor, drawCall);\' format instead.'); + this.setupCullModeAndFrontFace(cullFaces, flipFactor, drawCall); + } + updateCameraFrustum(camera) { if (camera.xr && camera.xr.views.list.length) { @@ -552,6 +572,9 @@ class Renderer { // Cull mode device.setCullMode(material.cull); + // Front face + device.setFrontFace(material.frontFace); + // Alpha test if (material.opacityMap) { this.opacityMapId.setValue(material.opacityMap); diff --git a/src/scene/renderer/shadow-renderer.js b/src/scene/renderer/shadow-renderer.js index e3b196fcd03..a1b90d9177e 100644 --- a/src/scene/renderer/shadow-renderer.js +++ b/src/scene/renderer/shadow-renderer.js @@ -329,7 +329,7 @@ class ShadowRenderer { material.dirty = false; } - renderer.setupCullMode(true, flipFactor, meshInstance); + renderer.setupCullModeAndFrontFace(true, flipFactor, meshInstance); // Uniforms I (shadow): material material.setParameters(device); diff --git a/test/scene/materials/shader-material.test.mjs b/test/scene/materials/shader-material.test.mjs index 06a3c2e8ba4..23dc662b5ac 100644 --- a/test/scene/materials/shader-material.test.mjs +++ b/test/scene/materials/shader-material.test.mjs @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { CULLFACE_BACK, FUNC_LESSEQUAL } from '../../../src/platform/graphics/constants.js'; +import { CULLFACE_BACK, FUNC_LESSEQUAL, FRONTFACE_CCW } from '../../../src/platform/graphics/constants.js'; import { BLEND_NONE } from '../../../src/scene/constants.js'; import { ShaderMaterial } from '../../../src/scene/materials/shader-material.js'; @@ -14,6 +14,7 @@ describe('Material', function () { expect(material.blendType).to.equal(BLEND_NONE); expect(material.blueWrite).to.equal(true); expect(material.cull).to.equal(CULLFACE_BACK); + expect(material.frontFace).to.equal(FRONTFACE_CCW); expect(material.depthBias).to.equal(0); expect(material.depthTest).to.equal(true); expect(material.depthFunc).to.equal(FUNC_LESSEQUAL);