playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
3 lines (2 loc) • 24.6 kB
TypeScript
declare const _default: "\n\n#include \"lightBufferDefinesPS\"\n\n// include this before shadow / cookie code\n#include \"clusteredLightUtilsPS\"\n\n#ifdef CLUSTER_COOKIES\n #include \"clusteredLightCookiesPS\"\n#endif\n\n#ifdef CLUSTER_SHADOWS\n #include \"clusteredLightShadowsPS\"\n#endif\n\nvar clusterWorldTexture: texture_2d<u32>;\nvar lightsTexture: texture_2d<uff>;\n\n#ifdef CLUSTER_SHADOWS\n // TODO: when VSM shadow is supported, it needs to use sampler2D in webgl2\n var shadowAtlasTexture: texture_depth_2d;\n var shadowAtlasTextureSampler: sampler_comparison;\n#endif\n\n#ifdef CLUSTER_COOKIES\n var cookieAtlasTexture: texture_2d<f32>;\n var cookieAtlasTextureSampler: sampler;\n#endif\n\nuniform clusterMaxCells: i32;\n\n// number of lights in the cluster structure\nuniform numClusteredLights: i32;\n\n// width of the cluster texture\nuniform clusterTextureWidth: i32;\n\nuniform clusterCellsCountByBoundsSize: vec3f;\nuniform clusterBoundsMin: vec3f;\nuniform clusterBoundsDelta: vec3f;\nuniform clusterCellsDot: vec3i;\nuniform clusterCellsMax: vec3i;\nuniform shadowAtlasParams: vec2f;\n\n// structure storing light properties of a clustered light. Vectors and scalars are interleaved\n// so each vec3 packs with an adjacent 4-byte field into a 16-byte slot, minimising padding for\n// compilers that don't reorder struct members.\nstruct ClusterLightData {\n\n // world space position\n position: vec3f,\n\n // light index in the lights texture\n lightIndex: i32,\n\n // world space direction (spot light only)\n direction: vec3f,\n\n // area light shape\n shape: u32,\n\n // color\n color: vec3f,\n\n // 0.0 if the light doesn't cast shadows\n shadowIntensity: f32,\n\n // range of the light\n range: f32,\n\n // compressed biases, two half-floats stored in a float\n biasesData: f32,\n\n // intensity of the cookie\n cookieIntensity: f32,\n\n // true for spot lights\n isSpot: bool,\n\n // light follow mode\n falloffModeLinear: bool,\n\n // light mask (mutually exclusive)\n isDynamic: bool,\n isLightmapped: bool\n}\n\n// Spot light cone angles, decoded on demand only when the light is a spot light.\nstruct ClusterLightSpotData {\n innerConeAngleCos: f32,\n outerConeAngleCos: f32\n}\n\n// Area light dimensions and orientation, decoded on demand only for non-punctual lights.\nstruct ClusterLightAreaData {\n halfWidth: vec3f,\n halfHeight: vec3f\n}\n\n// Shadow bias parameters, decoded on demand only when the light casts shadows.\nstruct ClusterLightShadowData {\n shadowBias: f32,\n shadowNormalBias: f32\n}\n\n// Note: on some devices (tested on Pixel 3A XL), this matrix when stored inside the light struct has lower precision compared to\n// when stored outside, so we store it outside to avoid spot shadow flickering. This might need to be done to other / all members\n// of the structure if further similar issues are observed.\n\n// shadow (spot light only) / cookie projection matrix\nvar<private> lightProjectionMatrix: mat4x4f;\n\n// NOTE: On some Samsung devices, these values can suffer precision / corruption issues when stored\n// as members of ClusterLightData. Keep them as module-scope temporaries instead. See issue #7800.\nvar<private> clusterLightData_flags: u32; // 32bit of flags\nvar<private> clusterLightData_anglesData: f32; // compressed angles, two half-floats stored in a float\nvar<private> clusterLightData_colorBFlagsData: u32; // blue color component and angle flags (as uint for efficient bit operations)\n\nfn sampleLightTextureF(lightIndex: i32, index: i32) -> vec4f {\n return textureLoad(lightsTexture, vec2<i32>(index, lightIndex), 0);\n}\n\nfn decodeClusterLightCore(lightIndex: i32) -> ClusterLightData {\n var clusterLightData: ClusterLightData;\n\n // light index\n clusterLightData.lightIndex = lightIndex;\n\n // sample data encoding half-float values into 32bit uints\n let halfData: vec4f = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_COLOR_ANGLES_BIAS});\n\n // store values needed by later decode steps (anglesData / colorBFlagsData live outside the\n // struct due to Samsung precision issues - see #7800)\n clusterLightData_anglesData = halfData.z;\n clusterLightData.biasesData = halfData.w;\n clusterLightData_colorBFlagsData = bitcast<u32>(halfData.y);\n\n // decompress color half-floats\n let colorRG: vec2f = unpack2x16float(bitcast<u32>(halfData.x));\n let colorB_flags: vec2f = unpack2x16float(clusterLightData_colorBFlagsData);\n clusterLightData.color = vec3f(colorRG, colorB_flags.x) * {LIGHT_COLOR_DIVIDER};\n\n // position and range, full floats\n let lightPosRange: vec4f = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_POSITION_RANGE});\n clusterLightData.position = lightPosRange.xyz;\n clusterLightData.range = lightPosRange.w;\n\n // spot direction & flags data\n let lightDir_Flags: vec4f = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_DIRECTION_FLAGS});\n\n // spot light direction\n clusterLightData.direction = lightDir_Flags.xyz;\n\n // 32bit flags (kept outside the struct, see #7800)\n clusterLightData_flags = bitcast<u32>(lightDir_Flags.w);\n clusterLightData.isSpot = (clusterLightData_flags & (1u << 30u)) != 0u;\n clusterLightData.shape = (clusterLightData_flags >> 28u) & 0x3u;\n clusterLightData.falloffModeLinear = (clusterLightData_flags & (1u << 27u)) == 0u;\n clusterLightData.shadowIntensity = f32((clusterLightData_flags >> 0u) & 0xFFu) / 255.0;\n clusterLightData.cookieIntensity = f32((clusterLightData_flags >> 8u) & 0xFFu) / 255.0;\n clusterLightData.isDynamic = (clusterLightData_flags & (1u << 22u)) != 0u;\n clusterLightData.isLightmapped = (clusterLightData_flags & (1u << 21u)) != 0u;\n\n return clusterLightData;\n}\n\nfn decodeClusterLightSpot() -> ClusterLightSpotData {\n // decompress spot light angles\n let angleFlags: u32 = (clusterLightData_colorBFlagsData >> 16u) & 0xFFFFu; // Extract upper 16 bits as integer\n\n let angleValues: vec2f = unpack2x16float(bitcast<u32>(clusterLightData_anglesData));\n let innerVal: f32 = angleValues.x;\n let outerVal: f32 = angleValues.y;\n\n // decode based on flags (branch-free)\n let innerIsVersine: bool = (angleFlags & 1u) != 0u; // bit 0: inner angle format\n let outerIsVersine: bool = ((angleFlags >> 1u) & 1u) != 0u; // bit 1: outer angle format\n\n return ClusterLightSpotData(\n select(innerVal, 1.0 - innerVal, innerIsVersine),\n select(outerVal, 1.0 - outerVal, outerIsVersine)\n );\n}\n\nfn decodeClusterLightOmniAtlasViewport(lightIndex: i32) -> vec3f {\n return sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_0}).xyz;\n}\n\nfn decodeClusterLightAreaData(lightIndex: i32) -> ClusterLightAreaData {\n return ClusterLightAreaData(\n sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_AREA_DATA_WIDTH}).xyz,\n sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_AREA_DATA_HEIGHT}).xyz\n );\n}\n\nfn decodeClusterLightProjectionMatrixData(lightIndex: i32) -> mat4x4f {\n // shadow matrix\n let m0: vec4f = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_0});\n let m1: vec4f = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_1});\n let m2: vec4f = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_2});\n let m3: vec4f = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_3});\n return mat4x4f(m0, m1, m2, m3);\n}\n\nfn decodeClusterLightShadowData(biasesData: f32) -> ClusterLightShadowData {\n // shadow biases\n let biases: vec2f = unpack2x16float(bitcast<u32>(biasesData));\n return ClusterLightShadowData(biases.x, biases.y);\n}\n\nfn decodeClusterLightCookieData() -> vec4f {\n\n // extract channel mask from flags\n let cookieFlags: u32 = (clusterLightData_flags >> 23u) & 0x0Fu; // 4bits, each bit enables a channel\n let mask_uvec: vec4<u32> = vec4<u32>(cookieFlags) & vec4<u32>(1u, 2u, 4u, 8u);\n return step(vec4f(1.0), vec4f(mask_uvec)); // Normalize to 0.0 or 1.0\n}\n\nfn evaluateLight(\n light: ClusterLightData,\n worldNormal: vec3f,\n viewDir: vec3f,\n reflectionDir: vec3f,\n#if defined(LIT_CLEARCOAT)\n clearcoatReflectionDir: vec3f,\n#endif\n gloss: f32,\n specularity: vec3f,\n geometricNormal: vec3f,\n tbn: mat3x3f,\n#if defined(LIT_IRIDESCENCE)\n iridescenceFresnel: vec3f,\n#endif\n clearcoat_worldNormal: vec3f,\n clearcoat_gloss: f32,\n sheen_gloss: f32,\n iridescence_intensity: f32\n) {\n\n var cookieAttenuation: vec3f = vec3f(1.0);\n var diffuseAttenuation: f32 = 1.0;\n var falloffAttenuation: f32 = 1.0;\n\n // evaluate omni part of the light\n let lightDirW: vec3f = evalOmniLight(light.position);\n let lightDirNormW: vec3f = normalize(lightDirW);\n\n #ifdef CLUSTER_AREALIGHTS\n\n // distance attenuation\n if (light.shape != {LIGHTSHAPE_PUNCTUAL}) { // area light\n\n // area lights\n let areaData: ClusterLightAreaData = decodeClusterLightAreaData(light.lightIndex);\n\n // handle light shape\n if (light.shape == {LIGHTSHAPE_RECT}) {\n calcRectLightValues(light.position, areaData.halfWidth, areaData.halfHeight);\n } else if (light.shape == {LIGHTSHAPE_DISK}) {\n calcDiskLightValues(light.position, areaData.halfWidth, areaData.halfHeight);\n } else { // sphere\n calcSphereLightValues(light.position, areaData.halfWidth, areaData.halfHeight);\n }\n\n falloffAttenuation = getFalloffWindow(light.range, lightDirW);\n\n } else\n\n #endif\n\n { // punctual light\n\n if (light.falloffModeLinear) {\n falloffAttenuation = getFalloffLinear(light.range, lightDirW);\n } else {\n falloffAttenuation = getFalloffInvSquared(light.range, lightDirW);\n }\n }\n\n if (falloffAttenuation > 0.00001) {\n\n #ifdef CLUSTER_AREALIGHTS\n\n if (light.shape != {LIGHTSHAPE_PUNCTUAL}) { // area light\n\n // handle light shape\n if (light.shape == {LIGHTSHAPE_RECT}) {\n diffuseAttenuation = getRectLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0;\n } else if (light.shape == {LIGHTSHAPE_DISK}) {\n diffuseAttenuation = getDiskLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0;\n } else { // sphere\n diffuseAttenuation = getSphereLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0;\n }\n\n } else\n\n #endif\n\n {\n falloffAttenuation = falloffAttenuation * getLightDiffuse(worldNormal, viewDir, lightDirNormW);\n }\n\n // spot light falloff\n if (light.isSpot) {\n let spotData: ClusterLightSpotData = decodeClusterLightSpot();\n falloffAttenuation = falloffAttenuation * getSpotEffect(light.direction, spotData.innerConeAngleCos, spotData.outerConeAngleCos, lightDirNormW);\n }\n\n #if defined(CLUSTER_COOKIES) || defined(CLUSTER_SHADOWS)\n\n if (falloffAttenuation > 0.00001) {\n\n // shadow / cookie\n if (light.shadowIntensity > 0.0 || light.cookieIntensity > 0.0) {\n\n var omniAtlasViewport: vec3f = vec3f(0.0);\n\n // shared shadow / cookie data depends on light type\n if (light.isSpot) {\n lightProjectionMatrix = decodeClusterLightProjectionMatrixData(light.lightIndex);\n } else {\n omniAtlasViewport = decodeClusterLightOmniAtlasViewport(light.lightIndex);\n }\n\n let shadowTextureResolution: f32 = uniform.shadowAtlasParams.x;\n let shadowEdgePixels: f32 = uniform.shadowAtlasParams.y;\n\n #ifdef CLUSTER_COOKIES\n\n // cookie\n if (light.cookieIntensity > 0.0) {\n let cookieChannelMask: vec4f = decodeClusterLightCookieData();\n\n if (light.isSpot) {\n cookieAttenuation = getCookie2DClustered(cookieAtlasTexture, cookieAtlasTextureSampler, lightProjectionMatrix, vPositionW, light.cookieIntensity, cookieChannelMask);\n } else {\n cookieAttenuation = getCookieCubeClustered(cookieAtlasTexture, cookieAtlasTextureSampler, lightDirW, light.cookieIntensity, cookieChannelMask, shadowTextureResolution, shadowEdgePixels, omniAtlasViewport);\n }\n }\n\n #endif\n\n #ifdef CLUSTER_SHADOWS\n\n // shadow\n if (light.shadowIntensity > 0.0) {\n let shadowData: ClusterLightShadowData = decodeClusterLightShadowData(light.biasesData);\n\n let shadowParams: vec4f = vec4f(shadowTextureResolution, shadowData.shadowNormalBias, shadowData.shadowBias, 1.0 / light.range);\n\n if (light.isSpot) {\n\n // spot shadow\n let shadowCoord: vec3f = getShadowCoordPerspZbufferNormalOffset(lightProjectionMatrix, shadowParams, geometricNormal);\n\n #if defined(CLUSTER_SHADOW_TYPE_PCF1)\n let shadow: f32 = getShadowSpotClusteredPCF1(shadowAtlasTexture, shadowAtlasTextureSampler, shadowCoord, shadowParams);\n #elif defined(CLUSTER_SHADOW_TYPE_PCF3)\n let shadow: f32 = getShadowSpotClusteredPCF3(shadowAtlasTexture, shadowAtlasTextureSampler, shadowCoord, shadowParams);\n #elif defined(CLUSTER_SHADOW_TYPE_PCF5)\n let shadow: f32 = getShadowSpotClusteredPCF5(shadowAtlasTexture, shadowAtlasTextureSampler, shadowCoord, shadowParams);\n #elif defined(CLUSTER_SHADOW_TYPE_PCSS)\n let shadow: f32 = getShadowSpotClusteredPCSS(shadowAtlasTexture, shadowAtlasTextureSampler, shadowCoord, shadowParams);\n #endif\n falloffAttenuation = falloffAttenuation * mix(1.0, shadow, light.shadowIntensity);\n\n } else {\n\n // omni shadow\n let dir: vec3f = normalOffsetPointShadow(shadowParams, light.position, lightDirW, lightDirNormW, geometricNormal); // normalBias adjusted for distance\n\n #if defined(CLUSTER_SHADOW_TYPE_PCF1)\n let shadow: f32 = getShadowOmniClusteredPCF1(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, omniAtlasViewport, shadowEdgePixels, dir);\n #elif defined(CLUSTER_SHADOW_TYPE_PCF3)\n let shadow: f32 = getShadowOmniClusteredPCF3(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, omniAtlasViewport, shadowEdgePixels, dir);\n #elif defined(CLUSTER_SHADOW_TYPE_PCF5)\n let shadow: f32 = getShadowOmniClusteredPCF5(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, omniAtlasViewport, shadowEdgePixels, dir);\n #endif\n falloffAttenuation = falloffAttenuation * mix(1.0, shadow, light.shadowIntensity);\n }\n }\n\n #endif\n }\n }\n\n #endif\n\n // diffuse / specular / clearcoat\n #ifdef CLUSTER_AREALIGHTS\n\n if (light.shape != {LIGHTSHAPE_PUNCTUAL}) { // area light\n\n // area light diffuse\n {\n var areaDiffuse: vec3f = (diffuseAttenuation * falloffAttenuation) * light.color * cookieAttenuation;\n\n #if defined(LIT_SPECULAR)\n areaDiffuse = mix(areaDiffuse, vec3f(0.0), dLTCSpecFres);\n #endif\n\n // area light diffuse - it does not mix diffuse lighting into specular attenuation\n dDiffuseLight = dDiffuseLight + areaDiffuse;\n }\n\n // specular and clear coat are material settings and get included by a define based on the material\n #ifdef LIT_SPECULAR\n\n // area light specular\n var areaLightSpecular: f32; // Use var because assigned in if/else\n\n if (light.shape == {LIGHTSHAPE_RECT}) {\n areaLightSpecular = getRectLightSpecular(worldNormal, viewDir);\n } else if (light.shape == {LIGHTSHAPE_DISK}) {\n areaLightSpecular = getDiskLightSpecular(worldNormal, viewDir);\n } else { // sphere\n areaLightSpecular = getSphereLightSpecular(worldNormal, viewDir);\n }\n\n dSpecularLight = dSpecularLight + dLTCSpecFres * areaLightSpecular * falloffAttenuation * light.color * cookieAttenuation;\n\n #ifdef LIT_CLEARCOAT\n\n // area light specular clear coat\n var areaLightSpecularCC: f32; // Use var because assigned in if/else\n\n if (light.shape == {LIGHTSHAPE_RECT}) {\n areaLightSpecularCC = getRectLightSpecular(clearcoat_worldNormal, viewDir);\n } else if (light.shape == {LIGHTSHAPE_DISK}) {\n areaLightSpecularCC = getDiskLightSpecular(clearcoat_worldNormal, viewDir);\n } else { // sphere\n areaLightSpecularCC = getSphereLightSpecular(clearcoat_worldNormal, viewDir);\n }\n\n ccSpecularLight = ccSpecularLight + ccLTCSpecFres * areaLightSpecularCC * falloffAttenuation * light.color * cookieAttenuation;\n\n #endif\n\n #endif\n\n } else\n\n #endif\n\n { // punctual light\n\n // punctual light diffuse\n {\n var punctualDiffuse: vec3f = falloffAttenuation * light.color * cookieAttenuation;\n\n #if defined(CLUSTER_AREALIGHTS)\n #if defined(LIT_SPECULAR)\n punctualDiffuse = mix(punctualDiffuse, vec3f(0.0), specularity);\n #endif\n #endif\n\n dDiffuseLight = dDiffuseLight + punctualDiffuse;\n }\n\n // specular and clear coat are material settings and get included by a define based on the material\n #ifdef LIT_SPECULAR\n\n let halfDir: vec3f = normalize(-lightDirNormW + viewDir);\n\n // specular\n #ifdef LIT_SPECULAR_FRESNEL\n dSpecularLight = dSpecularLight +\n getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, lightDirNormW, gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation *\n getFresnel(\n dot(viewDir, halfDir),\n gloss,\n specularity\n #if defined(LIT_IRIDESCENCE)\n , iridescenceFresnel,\n iridescence_intensity\n #endif\n );\n #else\n dSpecularLight = dSpecularLight + getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, lightDirNormW, gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * specularity;\n #endif\n\n #ifdef LIT_CLEARCOAT\n #ifdef LIT_SPECULAR_FRESNEL\n ccSpecularLight = ccSpecularLight + getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, lightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * getFresnelCC(dot(viewDir, halfDir));\n #else\n ccSpecularLight = ccSpecularLight + getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, lightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation;\n #endif\n #endif\n\n #ifdef LIT_SHEEN\n sSpecularLight = sSpecularLight + getLightSpecularSheen(halfDir, worldNormal, viewDir, lightDirNormW, sheen_gloss) * falloffAttenuation * light.color * cookieAttenuation;\n #endif\n\n #endif\n }\n }\n\n // Write to global attenuation values (for lightmapper)\n dAtten = falloffAttenuation;\n dLightDirNormW = lightDirNormW;\n}\n\n\nfn evaluateClusterLight(\n lightIndex: i32,\n worldNormal: vec3f,\n viewDir: vec3f,\n reflectionDir: vec3f,\n#if defined(LIT_CLEARCOAT)\n clearcoatReflectionDir: vec3f,\n#endif\n gloss: f32,\n specularity: vec3f,\n geometricNormal: vec3f,\n tbn: mat3x3f,\n#if defined(LIT_IRIDESCENCE)\n iridescenceFresnel: vec3f,\n#endif\n clearcoat_worldNormal: vec3f,\n clearcoat_gloss: f32,\n sheen_gloss: f32,\n iridescence_intensity: f32\n) {\n\n // decode core light data from textures\n let clusterLightData: ClusterLightData = decodeClusterLightCore(lightIndex);\n\n // evaluate light if it uses accepted light mask\n #ifdef CLUSTER_MESH_DYNAMIC_LIGHTS\n let acceptLightMask: bool = clusterLightData.isDynamic;\n #else\n let acceptLightMask: bool = clusterLightData.isLightmapped;\n #endif\n\n if (acceptLightMask) {\n evaluateLight(\n clusterLightData,\n worldNormal,\n viewDir,\n reflectionDir,\n#if defined(LIT_CLEARCOAT)\n clearcoatReflectionDir,\n#endif\n gloss,\n specularity,\n geometricNormal,\n tbn,\n#if defined(LIT_IRIDESCENCE)\n iridescenceFresnel,\n#endif\n clearcoat_worldNormal,\n clearcoat_gloss,\n sheen_gloss,\n iridescence_intensity\n );\n }\n}\n\n\nfn addClusteredLights(\n worldNormal: vec3f,\n viewDir: vec3f,\n reflectionDir: vec3f,\n#if defined(LIT_CLEARCOAT)\n clearcoatReflectionDir: vec3f,\n#endif\n gloss: f32,\n specularity: vec3f,\n geometricNormal: vec3f,\n tbn: mat3x3f,\n#if defined(LIT_IRIDESCENCE)\n iridescenceFresnel: vec3f,\n#endif\n clearcoat_worldNormal: vec3f,\n clearcoat_gloss: f32,\n sheen_gloss: f32,\n iridescence_intensity: f32\n) {\n\n // skip if no lights (index 0 is reserved for 'no light')\n if (uniform.numClusteredLights <= 1) {\n return;\n }\n\n // world space position to 3d integer cell cordinates in the cluster structure\n let cellCoords: vec3i = vec3i(floor((vPositionW - uniform.clusterBoundsMin) * uniform.clusterCellsCountByBoundsSize));\n\n // no lighting when cell coordinate is out of range\n if (!(any(cellCoords < vec3i(0)) || any(cellCoords >= uniform.clusterCellsMax))) {\n\n // cell index (mapping from 3d cell coordinates to linear memory)\n let cellIndex: i32 = cellCoords.x * uniform.clusterCellsDot.x + cellCoords.y * uniform.clusterCellsDot.y + cellCoords.z * uniform.clusterCellsDot.z;\n\n // convert cell index to uv coordinates\n let clusterV: i32 = cellIndex / uniform.clusterTextureWidth;\n let clusterU: i32 = cellIndex - clusterV * uniform.clusterTextureWidth;\n\n // loop over maximum number of light cells\n for (var lightCellIndex: i32 = 0; lightCellIndex < uniform.clusterMaxCells; lightCellIndex = lightCellIndex + 1) {\n\n // using a single channel texture with data in red channel\n let lightIndex: u32 = textureLoad(clusterWorldTexture, vec2<i32>(clusterU + lightCellIndex, clusterV), 0).r;\n\n if (lightIndex == 0u) {\n break;\n }\n\n evaluateClusterLight(\n i32(lightIndex),\n worldNormal,\n viewDir,\n reflectionDir,\n#if defined(LIT_CLEARCOAT)\n clearcoatReflectionDir,\n#endif\n gloss,\n specularity,\n geometricNormal,\n tbn,\n#if defined(LIT_IRIDESCENCE)\n iridescenceFresnel,\n#endif\n clearcoat_worldNormal,\n clearcoat_gloss,\n sheen_gloss,\n iridescence_intensity\n );\n }\n }\n}";
export default _default;