UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

3 lines (2 loc) 23.7 kB
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\nuniform highp usampler2D clusterWorldTexture;\nuniform highp sampler2D lightsTexture;\n\n#ifdef CLUSTER_SHADOWS\n // TODO: when VSM shadow is supported, it needs to use sampler2D in webgl2\n uniform sampler2DShadow shadowAtlasTexture;\n#endif\n\n#ifdef CLUSTER_COOKIES\n uniform sampler2D cookieAtlasTexture;\n#endif\n\nuniform int clusterMaxCells;\n\n// number of lights in the cluster structure\nuniform int numClusteredLights;\n\n// width of the cluster texture\nuniform int clusterTextureWidth;\n\nuniform vec3 clusterCellsCountByBoundsSize;\nuniform vec3 clusterBoundsMin;\nuniform vec3 clusterBoundsDelta;\nuniform ivec3 clusterCellsDot;\nuniform ivec3 clusterCellsMax;\nuniform vec2 shadowAtlasParams;\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 vec3 position;\n\n // light index in the lights texture\n int lightIndex;\n\n // world space direction (spot light only)\n vec3 direction;\n\n // area light shape\n uint shape;\n\n // color\n vec3 color;\n\n // 0.0 if the light doesn't cast shadows\n float shadowIntensity;\n\n // range of the light\n float range;\n\n // compressed biases, two half-floats stored in a float\n float biasesData;\n\n // intensity of the cookie\n float cookieIntensity;\n\n // true for spot lights\n bool isSpot;\n\n // light follow mode\n bool falloffModeLinear;\n\n // light mask (mutually exclusive)\n bool isDynamic;\n bool isLightmapped;\n};\n\n// Spot light cone angles, decoded on demand only when the light is a spot light.\nstruct ClusterLightSpotData {\n float innerConeAngleCos;\n float outerConeAngleCos;\n};\n\n// Area light dimensions and orientation, decoded on demand only for non-punctual lights.\nstruct ClusterLightAreaData {\n vec3 halfWidth;\n vec3 halfHeight;\n};\n\n// Shadow bias parameters, decoded on demand only when the light casts shadows.\nstruct ClusterLightShadowData {\n float shadowBias;\n float shadowNormalBias;\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. See https://github.com/KhronosGroup/WebGL/issues/3351\n\n// shadow (spot light only) / cookie projection matrix\nmat4 lightProjectionMatrix;\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.\nuint clusterLightData_flags; // 32bit of flags\nfloat clusterLightData_anglesData; // compressed angles, two half-floats stored in a float\nuint clusterLightData_colorBFlagsData; // blue color component and angle flags (as uint for efficient bit operations)\n\nvec4 sampleLightTextureF(int lightIndex, int index) {\n return texelFetch(lightsTexture, ivec2(index, lightIndex), 0);\n}\n\nClusterLightData decodeClusterLightCore(int lightIndex) {\n ClusterLightData clusterLightData;\n\n // light index\n clusterLightData.lightIndex = lightIndex;\n\n // sample data encoding half-float values into 32bit uints\n vec4 halfData = 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 = floatBitsToUint(halfData.y);\n\n // decompress color half-floats\n vec2 colorRG = unpackHalf2x16(floatBitsToUint(halfData.x));\n vec2 colorB_flags = unpackHalf2x16(clusterLightData_colorBFlagsData);\n clusterLightData.color = vec3(colorRG, colorB_flags.x) * {LIGHT_COLOR_DIVIDER};\n\n // position and range, full floats\n vec4 lightPosRange = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_POSITION_RANGE});\n clusterLightData.position = lightPosRange.xyz;\n clusterLightData.range = lightPosRange.w;\n\n // spot direction & flags data\n vec4 lightDir_Flags = 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 = floatBitsToUint(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 = float((clusterLightData_flags >> 0u) & 0xFFu) / 255.0;\n clusterLightData.cookieIntensity = float((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\nClusterLightSpotData decodeClusterLightSpot() {\n // decompress spot light angles\n uint angleFlags = (clusterLightData_colorBFlagsData >> 16u) & 0xFFFFu; // Extract upper 16 bits as integer\n\n vec2 angleValues = unpackHalf2x16(floatBitsToUint(clusterLightData_anglesData));\n float innerVal = angleValues.x;\n float outerVal = angleValues.y;\n\n // decode based on flags (branch-free)\n float innerIsVersine = float(angleFlags & 1u); // bit 0: inner angle format\n float outerIsVersine = float((angleFlags >> 1u) & 1u); // bit 1: outer angle format\n\n return ClusterLightSpotData(\n mix(innerVal, 1.0 - innerVal, innerIsVersine),\n mix(outerVal, 1.0 - outerVal, outerIsVersine)\n );\n}\n\nvec3 decodeClusterLightOmniAtlasViewport(int lightIndex) {\n return sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_0}).xyz;\n}\n\nClusterLightAreaData decodeClusterLightAreaData(int lightIndex) {\n return ClusterLightAreaData(\n sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_AREA_DATA_WIDTH}).xyz,\n sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_AREA_DATA_HEIGHT}).xyz\n );\n}\n\nmat4 decodeClusterLightProjectionMatrixData(int lightIndex) {\n // shadow matrix\n vec4 m0 = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_0});\n vec4 m1 = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_1});\n vec4 m2 = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_2});\n vec4 m3 = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_3});\n return mat4(m0, m1, m2, m3);\n}\n\nClusterLightShadowData decodeClusterLightShadowData(float biasesData) {\n // shadow biases\n vec2 biases = unpackHalf2x16(floatBitsToUint(biasesData));\n return ClusterLightShadowData(biases.x, biases.y);\n}\n\nvec4 decodeClusterLightCookieData() {\n // extract channel mask from flags\n uint cookieFlags = (clusterLightData_flags >> 23u) & 0x0Fu; // 4bits, each bit enables a channel\n vec4 mask = vec4(uvec4(cookieFlags) & uvec4(1u, 2u, 4u, 8u));\n return step(1.0, mask); // Normalize to 0.0 or 1.0\n}\n\nvoid evaluateLight(\n ClusterLightData light, \n vec3 worldNormal, \n vec3 viewDir, \n vec3 reflectionDir,\n#if defined(LIT_CLEARCOAT)\n vec3 clearcoatReflectionDir,\n#endif\n float gloss, \n vec3 specularity, \n vec3 geometricNormal, \n mat3 tbn, \n#if defined(LIT_IRIDESCENCE)\n vec3 iridescenceFresnel,\n#endif\n vec3 clearcoat_worldNormal,\n float clearcoat_gloss,\n float sheen_gloss,\n float iridescence_intensity\n) {\n\n vec3 cookieAttenuation = vec3(1.0);\n float diffuseAttenuation = 1.0;\n float falloffAttenuation = 1.0;\n\n // evaluate omni part of the light\n vec3 lightDirW = evalOmniLight(light.position);\n vec3 lightDirNormW = normalize(lightDirW);\n\n #ifdef CLUSTER_AREALIGHTS\n\n // distance attenuation\n if (light.shape != {LIGHTSHAPE_PUNCTUAL}) { // area light\n\n // area lights\n ClusterLightAreaData areaData = 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 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 *= getLightDiffuse(worldNormal, viewDir, lightDirNormW); \n }\n\n // spot light falloff\n if (light.isSpot) {\n ClusterLightSpotData spotData = decodeClusterLightSpot();\n 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 vec3 omniAtlasViewport = vec3(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 float shadowTextureResolution = shadowAtlasParams.x;\n float shadowEdgePixels = shadowAtlasParams.y;\n\n #ifdef CLUSTER_COOKIES\n\n // cookie\n if (light.cookieIntensity > 0.0) {\n vec4 cookieChannelMask = decodeClusterLightCookieData();\n\n if (light.isSpot) {\n cookieAttenuation = getCookie2DClustered(TEXTURE_PASS(cookieAtlasTexture), lightProjectionMatrix, vPositionW, light.cookieIntensity, cookieChannelMask);\n } else {\n cookieAttenuation = getCookieCubeClustered(TEXTURE_PASS(cookieAtlasTexture), 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 ClusterLightShadowData shadowData = decodeClusterLightShadowData(light.biasesData);\n\n vec4 shadowParams = vec4(shadowTextureResolution, shadowData.shadowNormalBias, shadowData.shadowBias, 1.0 / light.range);\n\n if (light.isSpot) {\n\n // spot shadow\n vec3 shadowCoord = getShadowCoordPerspZbufferNormalOffset(lightProjectionMatrix, shadowParams, geometricNormal);\n \n #if defined(CLUSTER_SHADOW_TYPE_PCF1)\n float shadow = getShadowSpotClusteredPCF1(SHADOWMAP_PASS(shadowAtlasTexture), shadowCoord, shadowParams);\n #elif defined(CLUSTER_SHADOW_TYPE_PCF3)\n float shadow = getShadowSpotClusteredPCF3(SHADOWMAP_PASS(shadowAtlasTexture), shadowCoord, shadowParams);\n #elif defined(CLUSTER_SHADOW_TYPE_PCF5)\n float shadow = getShadowSpotClusteredPCF5(SHADOWMAP_PASS(shadowAtlasTexture), shadowCoord, shadowParams);\n #elif defined(CLUSTER_SHADOW_TYPE_PCSS)\n float shadow = getShadowSpotClusteredPCSS(SHADOWMAP_PASS(shadowAtlasTexture), shadowCoord, shadowParams);\n #endif\n falloffAttenuation *= mix(1.0, shadow, light.shadowIntensity);\n\n } else {\n\n // omni shadow\n vec3 dir = normalOffsetPointShadow(shadowParams, light.position, lightDirW, lightDirNormW, geometricNormal); // normalBias adjusted for distance\n\n #if defined(CLUSTER_SHADOW_TYPE_PCF1)\n float shadow = getShadowOmniClusteredPCF1(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, omniAtlasViewport, shadowEdgePixels, dir);\n #elif defined(CLUSTER_SHADOW_TYPE_PCF3)\n float shadow = getShadowOmniClusteredPCF3(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, omniAtlasViewport, shadowEdgePixels, dir);\n #elif defined(CLUSTER_SHADOW_TYPE_PCF5)\n float shadow = getShadowOmniClusteredPCF5(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, omniAtlasViewport, shadowEdgePixels, dir);\n #endif\n 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 vec3 areaDiffuse = (diffuseAttenuation * falloffAttenuation) * light.color * cookieAttenuation;\n\n #if defined(LIT_SPECULAR)\n areaDiffuse = mix(areaDiffuse, vec3(0), dLTCSpecFres);\n #endif\n\n // area light diffuse - it does not mix diffuse lighting into specular attenuation\n 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 float areaLightSpecular;\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 += dLTCSpecFres * areaLightSpecular * falloffAttenuation * light.color * cookieAttenuation;\n\n #ifdef LIT_CLEARCOAT\n\n // area light specular clear coat\n float areaLightSpecularCC;\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 += 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 vec3 punctualDiffuse = falloffAttenuation * light.color * cookieAttenuation;\n\n #if defined(CLUSTER_AREALIGHTS)\n #if defined(LIT_SPECULAR)\n punctualDiffuse = mix(punctualDiffuse, vec3(0), specularity);\n #endif\n #endif\n\n 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 vec3 halfDir = normalize(-lightDirNormW + viewDir);\n \n // specular\n #ifdef LIT_SPECULAR_FRESNEL\n 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 += 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 += getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, lightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * getFresnelCC(dot(viewDir, halfDir));\n #else\n 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 += 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\nvoid evaluateClusterLight(\n int lightIndex, \n vec3 worldNormal, \n vec3 viewDir, \n vec3 reflectionDir, \n#if defined(LIT_CLEARCOAT)\n vec3 clearcoatReflectionDir,\n#endif\n float gloss, \n vec3 specularity, \n vec3 geometricNormal, \n mat3 tbn, \n#if defined(LIT_IRIDESCENCE)\n vec3 iridescenceFresnel,\n#endif\n vec3 clearcoat_worldNormal,\n float clearcoat_gloss,\n float sheen_gloss,\n float iridescence_intensity\n) {\n\n // decode core light data from textures\n ClusterLightData clusterLightData = decodeClusterLightCore(lightIndex);\n\n // evaluate light if it uses accepted light mask\n #ifdef CLUSTER_MESH_DYNAMIC_LIGHTS\n bool acceptLightMask = clusterLightData.isDynamic;\n #else\n bool acceptLightMask = 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\nvoid addClusteredLights(\n vec3 worldNormal, \n vec3 viewDir, \n vec3 reflectionDir, \n#if defined(LIT_CLEARCOAT)\n vec3 clearcoatReflectionDir,\n#endif\n float gloss, \n vec3 specularity, \n vec3 geometricNormal, \n mat3 tbn, \n#if defined(LIT_IRIDESCENCE)\n vec3 iridescenceFresnel,\n#endif\n vec3 clearcoat_worldNormal,\n float clearcoat_gloss,\n float sheen_gloss,\n float iridescence_intensity\n) {\n\n // skip if no lights (index 0 is reserved for 'no light')\n if (numClusteredLights <= 1)\n return;\n\n // world space position to 3d integer cell cordinates in the cluster structure\n ivec3 cellCoords = ivec3(floor((vPositionW - clusterBoundsMin) * clusterCellsCountByBoundsSize));\n\n // no lighting when cell coordinate is out of range\n if (!(any(lessThan(cellCoords, ivec3(0))) || any(greaterThanEqual(cellCoords, clusterCellsMax)))) {\n\n // cell index (mapping from 3d cell coordinates to linear memory)\n int cellIndex = cellCoords.x * clusterCellsDot.x + cellCoords.y * clusterCellsDot.y + cellCoords.z * clusterCellsDot.z;\n\n // convert cell index to uv coordinates\n int clusterV = cellIndex / clusterTextureWidth;\n int clusterU = cellIndex - clusterV * clusterTextureWidth;\n\n // loop over maximum number of light cells\n for (int lightCellIndex = 0; lightCellIndex < clusterMaxCells; lightCellIndex++) {\n\n // using a single channel texture with data in red channel\n uint lightIndex = texelFetch(clusterWorldTexture, ivec2(clusterU + lightCellIndex, clusterV), 0).x;\n\n if (lightIndex == 0u)\n break;\n\n evaluateClusterLight(\n int(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}\n"; export default _default;