Skip to content
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
58 changes: 44 additions & 14 deletions src/platform/graphics/webgpu/webgpu-shader-processor-wgsl.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ class WebgpuShaderProcessorWGSL {
const vertexVaryingsBlock = WebgpuShaderProcessorWGSL.processVaryings(vertexExtracted.varyings, varyingMap, true, device);

// FS - convert a list of varyings to a shader block
const fragmentVaryingsBlock = WebgpuShaderProcessorWGSL.processVaryings(fragmentExtracted.varyings, varyingMap, false, device);
const fragmentVaryingsBlock = WebgpuShaderProcessorWGSL.processVaryings(fragmentExtracted.varyings, varyingMap, false, device, fragmentExtracted.src);

// uniforms - merge vertex and fragment uniforms, and create shared uniform buffers
// Note that as both vertex and fragment can declare the same uniform, we need to remove duplicates
Expand Down Expand Up @@ -701,10 +701,16 @@ class WebgpuShaderProcessorWGSL {
return code;
}

static processVaryings(varyingLines, varyingMap, isVertex, device) {
static processVaryings(varyingLines, varyingMap, isVertex, device, source = '') {
let block = '';
let blockPrivates = '';
let blockCopy = '';
const needsPosition = !isVertex && source.includes('pcPosition');
const needsFrontFacing = !isVertex && source.includes('pcFrontFacing');
const needsPrimitiveIndex = !isVertex && source.includes('pcPrimitiveIndex');

// Requesting sample_index can force sample-rate shading on MSAA render targets.
const needsSampleIndex = !isVertex && source.includes('pcSampleIndex');
varyingLines.forEach((line, index) => {
const match = line.match(VARYING);
Debug.assert(match, `Varying line is not valid: ${line}`);
Expand Down Expand Up @@ -740,36 +746,60 @@ class WebgpuShaderProcessorWGSL {
if (isVertex) {
block += ' @builtin(position) position : vec4f,\n'; // output position
} else {
block += ' @builtin(position) position : vec4f,\n'; // interpolated fragment position
block += ' @builtin(front_facing) frontFacing : bool,\n'; // front-facing
block += ' @builtin(sample_index) sampleIndex : u32,\n'; // sample index for MSAA
if (device.supportsPrimitiveIndex) {
if (needsPosition) {
block += ' @builtin(position) position : vec4f,\n'; // interpolated fragment position
}
if (needsFrontFacing) {
block += ' @builtin(front_facing) frontFacing : bool,\n'; // front-facing
}
if (needsSampleIndex) {
block += ' @builtin(sample_index) sampleIndex : u32,\n'; // sample index for MSAA
}
Comment on lines +749 to +757
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

addressed in a followup

if (device.supportsPrimitiveIndex && needsPrimitiveIndex) {
block += ' @builtin(primitive_index) primitiveIndex : u32,\n'; // primitive index
}
}

// primitive index support
const primitiveIndexGlobals = device.supportsPrimitiveIndex ? `
const primitiveIndexGlobals = device.supportsPrimitiveIndex && needsPrimitiveIndex ? `
var<private> pcPrimitiveIndex: u32;
` : '';
const primitiveIndexCopy = device.supportsPrimitiveIndex ? `
const primitiveIndexCopy = device.supportsPrimitiveIndex && needsPrimitiveIndex ? `
pcPrimitiveIndex = input.primitiveIndex;
` : '';
const sampleIndexGlobal = needsSampleIndex ? `
var<private> pcSampleIndex: u32;
` : '';
const sampleIndexCopy = needsSampleIndex ? `
pcSampleIndex = input.sampleIndex;
` : '';
const positionGlobal = needsPosition ? `
var<private> pcPosition: vec4f;
` : '';
const positionCopy = needsPosition ? `
pcPosition = input.position;
` : '';
const frontFacingGlobal = needsFrontFacing ? `
var<private> pcFrontFacing: bool;
` : '';
const frontFacingCopy = needsFrontFacing ? `
pcFrontFacing = input.frontFacing;
` : '';

// global variables for build-in input into fragment shader
const fragmentGlobals = isVertex ? '' : `
var<private> pcPosition: vec4f;
var<private> pcFrontFacing: bool;
var<private> pcSampleIndex: u32;
${positionGlobal}
${frontFacingGlobal}
${sampleIndexGlobal}
${primitiveIndexGlobals}
${blockPrivates}

// function to copy inputs (varyings) to private global variables
fn _pcCopyInputs(input: FragmentInput) {
${blockCopy}
pcPosition = input.position;
pcFrontFacing = input.frontFacing;
pcSampleIndex = input.sampleIndex;
${positionCopy}
${frontFacingCopy}
${sampleIndexCopy}
${primitiveIndexCopy}
}
`;
Expand Down
116 changes: 60 additions & 56 deletions src/scene/shader-lib/wgsl/chunks/lit/frag/clusteredLight.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,6 @@ struct ClusterLightData {
// 32bit of flags
flags: u32,

// area light sizes / orientation
halfWidth: vec3f,

isSpot: bool,

// area light sizes / orientation
halfHeight: vec3f,

// light index
lightIndex: i32,

Expand All @@ -69,6 +61,9 @@ struct ClusterLightData {
// world space direction (spot light only)
direction: vec3f,

// true for spot lights
isSpot: bool,

// light follow mode
falloffModeLinear: bool,

Expand All @@ -78,32 +73,18 @@ struct ClusterLightData {
// 0.0 if the light doesn't cast shadows
shadowIntensity: f32,

// atlas viewport for omni light shadow and cookie (.xy is offset to the viewport slot, .z is size of the face in the atlas)
omniAtlasViewport: vec3f,

// range of the light
range: f32,

// channel mask - one of the channels has 1, the others are 0
cookieChannelMask: vec4f,

// compressed biases, two haf-floats stored in a float
biasesData: f32,

// blue color component and angle flags (as uint for efficient bit operations)
colorBFlagsData: u32,

// shadow bias values
shadowBias: f32,
shadowNormalBias: f32,

// compressed angles, two haf-floats stored in a float
anglesData: f32,

// spot light inner and outer angle cosine
innerConeAngleCos: f32,
outerConeAngleCos: f32,

// intensity of the cookie
cookieIntensity: f32,

Expand All @@ -113,6 +94,21 @@ struct ClusterLightData {
isLightmapped: bool
}

struct ClusterLightSpotData {
innerConeAngleCos: f32,
outerConeAngleCos: f32
}

struct ClusterLightAreaData {
halfWidth: vec3f,
halfHeight: vec3f
}

struct ClusterLightShadowData {
shadowBias: f32,
shadowNormalBias: f32
}

// Note: on some devices (tested on Pixel 3A XL), this matrix when stored inside the light struct has lower precision compared to
// when stored outside, so we store it outside to avoid spot shadow flickering. This might need to be done to other / all members
// of the structure if further similar issues are observed.
Expand All @@ -124,7 +120,8 @@ fn sampleLightTextureF(lightIndex: i32, index: i32) -> vec4f {
return textureLoad(lightsTexture, vec2<i32>(index, lightIndex), 0);
}

fn decodeClusterLightCore(clusterLightData: ptr<function, ClusterLightData>, lightIndex: i32) {
fn decodeClusterLightCore(lightIndex: i32) -> ClusterLightData {
var clusterLightData: ClusterLightData;

// light index
clusterLightData.lightIndex = lightIndex;
Expand Down Expand Up @@ -163,9 +160,11 @@ fn decodeClusterLightCore(clusterLightData: ptr<function, ClusterLightData>, lig
clusterLightData.cookieIntensity = f32((flags_uint >> 8u) & 0xFFu) / 255.0;
clusterLightData.isDynamic = (flags_uint & (1u << 22u)) != 0u;
clusterLightData.isLightmapped = (flags_uint & (1u << 21u)) != 0u;

return clusterLightData;
}

fn decodeClusterLightSpot(clusterLightData: ptr<function, ClusterLightData>) {
fn decodeClusterLightSpot(clusterLightData: ClusterLightData) -> ClusterLightSpotData {
// decompress spot light angles
let angleFlags: u32 = (clusterLightData.colorBFlagsData >> 16u) & 0xFFFFu; // Extract upper 16 bits as integer

Expand All @@ -176,20 +175,25 @@ fn decodeClusterLightSpot(clusterLightData: ptr<function, ClusterLightData>) {
// decode based on flags (branch-free)
let innerIsVersine: bool = (angleFlags & 1u) != 0u; // bit 0: inner angle format
let outerIsVersine: bool = ((angleFlags >> 1u) & 1u) != 0u; // bit 1: outer angle format
clusterLightData.innerConeAngleCos = select(innerVal, 1.0 - innerVal, innerIsVersine);
clusterLightData.outerConeAngleCos = select(outerVal, 1.0 - outerVal, outerIsVersine);

return ClusterLightSpotData(
select(innerVal, 1.0 - innerVal, innerIsVersine),
select(outerVal, 1.0 - outerVal, outerIsVersine)
);
}

fn decodeClusterLightOmniAtlasViewport(clusterLightData: ptr<function, ClusterLightData>) {
clusterLightData.omniAtlasViewport = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_0}).xyz;
fn decodeClusterLightOmniAtlasViewport(clusterLightData: ClusterLightData) -> vec3f {
return sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_0}).xyz;
}

fn decodeClusterLightAreaData(clusterLightData: ptr<function, ClusterLightData>) {
clusterLightData.halfWidth = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_AREA_DATA_WIDTH}).xyz;
clusterLightData.halfHeight = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_AREA_DATA_HEIGHT}).xyz;
fn decodeClusterLightAreaData(clusterLightData: ClusterLightData) -> ClusterLightAreaData {
return ClusterLightAreaData(
sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_AREA_DATA_WIDTH}).xyz,
sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_AREA_DATA_HEIGHT}).xyz
);
}

fn decodeClusterLightProjectionMatrixData(clusterLightData: ptr<function, ClusterLightData>) {
fn decodeClusterLightProjectionMatrixData(clusterLightData: ClusterLightData) {
// shadow matrix
let m0: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_0});
let m1: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_1});
Expand All @@ -198,23 +202,22 @@ fn decodeClusterLightProjectionMatrixData(clusterLightData: ptr<function, Cluste
lightProjectionMatrix = mat4x4f(m0, m1, m2, m3);
}

fn decodeClusterLightShadowData(clusterLightData: ptr<function, ClusterLightData>) {
fn decodeClusterLightShadowData(clusterLightData: ClusterLightData) -> ClusterLightShadowData {
// shadow biases
let biases: vec2f = unpack2x16float(bitcast<u32>(clusterLightData.biasesData));
clusterLightData.shadowBias = biases.x;
clusterLightData.shadowNormalBias = biases.y;
return ClusterLightShadowData(biases.x, biases.y);
}

fn decodeClusterLightCookieData(clusterLightData: ptr<function, ClusterLightData>) {
fn decodeClusterLightCookieData(clusterLightData: ClusterLightData) -> vec4f {

// extract channel mask from flags
let cookieFlags: u32 = (clusterLightData.flags >> 23u) & 0x0Fu; // 4bits, each bit enables a channel
let mask_uvec: vec4<u32> = vec4<u32>(cookieFlags) & vec4<u32>(1u, 2u, 4u, 8u);
clusterLightData.cookieChannelMask = step(vec4f(1.0), vec4f(mask_uvec)); // Normalize to 0.0 or 1.0
return step(vec4f(1.0), vec4f(mask_uvec)); // Normalize to 0.0 or 1.0
}

fn evaluateLight(
light: ptr<function, ClusterLightData>,
light: ClusterLightData,
worldNormal: vec3f,
viewDir: vec3f,
reflectionDir: vec3f,
Expand Down Expand Up @@ -248,15 +251,15 @@ fn evaluateLight(
if (light.shape != {LIGHTSHAPE_PUNCTUAL}) { // area light

// area lights
decodeClusterLightAreaData(light);
let areaData: ClusterLightAreaData = decodeClusterLightAreaData(light);

// handle light shape
if (light.shape == {LIGHTSHAPE_RECT}) {
calcRectLightValues(light.position, light.halfWidth, light.halfHeight);
calcRectLightValues(light.position, areaData.halfWidth, areaData.halfHeight);
} else if (light.shape == {LIGHTSHAPE_DISK}) {
calcDiskLightValues(light.position, light.halfWidth, light.halfHeight);
calcDiskLightValues(light.position, areaData.halfWidth, areaData.halfHeight);
} else { // sphere
calcSphereLightValues(light.position, light.halfWidth, light.halfHeight);
calcSphereLightValues(light.position, areaData.halfWidth, areaData.halfHeight);
}

falloffAttenuation = getFalloffWindow(light.range, lightDirW);
Expand Down Expand Up @@ -299,8 +302,8 @@ fn evaluateLight(

// spot light falloff
if (light.isSpot) {
decodeClusterLightSpot(light);
falloffAttenuation = falloffAttenuation * getSpotEffect(light.direction, light.innerConeAngleCos, light.outerConeAngleCos, lightDirNormW);
let spotData: ClusterLightSpotData = decodeClusterLightSpot(light);
falloffAttenuation = falloffAttenuation * getSpotEffect(light.direction, spotData.innerConeAngleCos, spotData.outerConeAngleCos, lightDirNormW);
}

#if defined(CLUSTER_COOKIES) || defined(CLUSTER_SHADOWS)
Expand All @@ -310,11 +313,13 @@ fn evaluateLight(
// shadow / cookie
if (light.shadowIntensity > 0.0 || light.cookieIntensity > 0.0) {

var omniAtlasViewport: vec3f = vec3f(0.0);

// shared shadow / cookie data depends on light type
if (light.isSpot) {
decodeClusterLightProjectionMatrixData(light);
} else {
decodeClusterLightOmniAtlasViewport(light);
omniAtlasViewport = decodeClusterLightOmniAtlasViewport(light);
}

let shadowTextureResolution: f32 = uniform.shadowAtlasParams.x;
Expand All @@ -324,14 +329,14 @@ fn evaluateLight(

// cookie
if (light.cookieIntensity > 0.0) {
decodeClusterLightCookieData(light);
let cookieChannelMask: vec4f = decodeClusterLightCookieData(light);

if (light.isSpot) {
// !!!!!!!!!!! TEXTURE_PASS likely needs sampler. Assuming cookieAtlasTextureSampler exists.
cookieAttenuation = getCookie2DClustered(cookieAtlasTexture, cookieAtlasTextureSampler, lightProjectionMatrix, vPositionW, light.cookieIntensity, light.cookieChannelMask);
cookieAttenuation = getCookie2DClustered(cookieAtlasTexture, cookieAtlasTextureSampler, lightProjectionMatrix, vPositionW, light.cookieIntensity, cookieChannelMask);
} else {
// !!!!!!!!!!! TEXTURE_PASS likely needs sampler. Assuming cookieAtlasTextureSampler exists.
cookieAttenuation = getCookieCubeClustered(cookieAtlasTexture, cookieAtlasTextureSampler, lightDirW, light.cookieIntensity, light.cookieChannelMask, shadowTextureResolution, shadowEdgePixels, light.omniAtlasViewport);
cookieAttenuation = getCookieCubeClustered(cookieAtlasTexture, cookieAtlasTextureSampler, lightDirW, light.cookieIntensity, cookieChannelMask, shadowTextureResolution, shadowEdgePixels, omniAtlasViewport);
}
}

Expand All @@ -341,9 +346,9 @@ fn evaluateLight(

// shadow
if (light.shadowIntensity > 0.0) {
decodeClusterLightShadowData(light);
let shadowData: ClusterLightShadowData = decodeClusterLightShadowData(light);

let shadowParams: vec4f = vec4f(shadowTextureResolution, light.shadowNormalBias, light.shadowBias, 1.0 / light.range);
let shadowParams: vec4f = vec4f(shadowTextureResolution, shadowData.shadowNormalBias, shadowData.shadowBias, 1.0 / light.range);

if (light.isSpot) {

Expand Down Expand Up @@ -371,11 +376,11 @@ fn evaluateLight(
// !!!!!!!!!!! SHADOWMAP_PASS needs texture and sampler_comparison.
// !!!!!!!!!!! Shadow functions need update for WGSL textureSampleCompare etc. Assuming these are handled in includes.
#if defined(CLUSTER_SHADOW_TYPE_PCF1)
let shadow: f32 = getShadowOmniClusteredPCF1(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);
let shadow: f32 = getShadowOmniClusteredPCF1(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, omniAtlasViewport, shadowEdgePixels, dir);
#elif defined(CLUSTER_SHADOW_TYPE_PCF3)
let shadow: f32 = getShadowOmniClusteredPCF3(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);
let shadow: f32 = getShadowOmniClusteredPCF3(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, omniAtlasViewport, shadowEdgePixels, dir);
#elif defined(CLUSTER_SHADOW_TYPE_PCF5)
let shadow: f32 = getShadowOmniClusteredPCF5(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);
let shadow: f32 = getShadowOmniClusteredPCF5(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, omniAtlasViewport, shadowEdgePixels, dir);
#endif
falloffAttenuation = falloffAttenuation * mix(1.0, shadow, light.shadowIntensity);
}
Expand Down Expand Up @@ -524,8 +529,7 @@ fn evaluateClusterLight(
) {

// decode core light data from textures
var clusterLightData: ClusterLightData;
decodeClusterLightCore(&clusterLightData, lightIndex);
let clusterLightData: ClusterLightData = decodeClusterLightCore(lightIndex);

// evaluate light if it uses accepted light mask
#ifdef CLUSTER_MESH_DYNAMIC_LIGHTS
Expand All @@ -536,7 +540,7 @@ fn evaluateClusterLight(

if (acceptLightMask) {
evaluateLight(
&clusterLightData,
clusterLightData,
worldNormal,
viewDir,
reflectionDir,
Expand Down