UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

4 lines (2 loc) 23.6 kB
var clusteredLightPS = "\nuniform highp sampler2D clusterWorldTexture;\nuniform highp sampler2D lightsTexture8;\nuniform highp sampler2D lightsTextureFloat;\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// 1.0 if clustered lighting can be skipped (0 lights in the clusters)\nuniform float clusterSkip;\n\nuniform vec3 clusterCellsCountByBoundsSize;\nuniform vec3 clusterTextureSize;\nuniform vec3 clusterBoundsMin;\nuniform vec3 clusterBoundsDelta;\nuniform vec3 clusterCellsDot;\nuniform vec3 clusterCellsMax;\nuniform vec2 clusterCompressionLimit0;\nuniform vec2 shadowAtlasParams;\n\n// structure storing light properties of a clustered light\n// it's sorted to have all vectors aligned to 4 floats to limit padding\nstruct ClusterLightData {\n\n // area light sizes / orientation\n vec3 halfWidth;\n\n // type of the light (spot or omni)\n float lightType;\n\n // area light sizes / orientation\n vec3 halfHeight;\n\n // light index\n int lightIndex;\n\n // world space position\n vec3 position;\n\n // area light shape\n float shape;\n\n // world space direction (spot light only)\n vec3 direction;\n\n // light follow mode\n float falloffMode;\n\n // color\n vec3 color;\n\n // 0.0 if the light doesn't cast shadows\n float shadowIntensity;\n\n // atlas viewport for omni light shadow and cookie (.xy is offset to the viewport slot, .z is size of the face in the atlas)\n vec3 omniAtlasViewport;\n\n // range of the light\n float range;\n\n // channel mask - one of the channels has 1, the others are 0\n vec4 cookieChannelMask;\n\n // shadow bias values\n float shadowBias;\n float shadowNormalBias;\n\n // spot light inner and outer angle cosine\n float innerConeAngleCos;\n float outerConeAngleCos;\n\n // 1.0 if the light has a cookie texture\n float cookie;\n\n // 1.0 if cookie texture is rgb, otherwise it is using a single channel selectable by cookieChannelMask\n float cookieRgb;\n\n // intensity of the cookie\n float cookieIntensity;\n\n // light mask\n float mask;\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\nmat4 lightProjectionMatrix;\n\n// macros for light properties\nbool isClusteredLightCastShadow(in ClusterLightData light) {\n return light.shadowIntensity > 0.0;\n}\n\nbool isClusteredLightCookie(in ClusterLightData light) {\n return light.cookie > 0.5;\n}\n\nbool isClusteredLightCookieRgb(in ClusterLightData light) {\n return light.cookieRgb > 0.5;\n}\n\nbool isClusteredLightSpot(in ClusterLightData light) {\n return light.lightType > 0.5;\n}\n\nbool isClusteredLightFalloffLinear(in ClusterLightData light) {\n return light.falloffMode < 0.5;\n}\n\n// macros to test light shape\n// Note: Following functions need to be called serially in listed order as they do not test both '>' and '<'\nbool isClusteredLightArea(in ClusterLightData light) {\n return light.shape > 0.1;\n}\n\nbool isClusteredLightRect(in ClusterLightData light) {\n return light.shape < 0.3;\n}\n\nbool isClusteredLightDisk(in ClusterLightData light) {\n return light.shape < 0.6;\n}\n\n// macro to test light mask (mesh accepts dynamic vs lightmapped lights)\n#ifdef CLUSTER_MESH_DYNAMIC_LIGHTS\n // accept lights marked as dynamic or both dynamic and lightmapped\n bool acceptLightMask(in ClusterLightData light) {\n return light.mask < 0.75;\n }\n#else\n // accept lights marked as lightmapped or both dynamic and lightmapped\n bool acceptLightMask(in ClusterLightData light) {\n return light.mask > 0.25;\n }\n#endif\n\nvec4 decodeClusterLowRange4Vec4(vec4 d0, vec4 d1, vec4 d2, vec4 d3) {\n return vec4(\n bytes2floatRange4(d0, -2.0, 2.0),\n bytes2floatRange4(d1, -2.0, 2.0),\n bytes2floatRange4(d2, -2.0, 2.0),\n bytes2floatRange4(d3, -2.0, 2.0)\n );\n}\n\nvec4 sampleLightsTexture8(const ClusterLightData clusterLightData, int index) {\n return texelFetch(lightsTexture8, ivec2(index, clusterLightData.lightIndex), 0);\n}\n\nvec4 sampleLightTextureF(const ClusterLightData clusterLightData, int index) {\n return texelFetch(lightsTextureFloat, ivec2(index, clusterLightData.lightIndex), 0);\n}\n\nvoid decodeClusterLightCore(inout ClusterLightData clusterLightData, float lightIndex) {\n\n // light index\n clusterLightData.lightIndex = int(lightIndex);\n\n // shared data from 8bit texture\n vec4 lightInfo = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_FLAGS);\n clusterLightData.lightType = lightInfo.x;\n clusterLightData.shape = lightInfo.y;\n clusterLightData.falloffMode = lightInfo.z;\n clusterLightData.shadowIntensity = lightInfo.w;\n\n // color\n vec4 colorA = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_COLOR_A);\n vec4 colorB = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_COLOR_B);\n clusterLightData.color = vec3(bytes2float2(colorA.xy), bytes2float2(colorA.zw), bytes2float2(colorB.xy)) * clusterCompressionLimit0.y;\n\n // cookie\n clusterLightData.cookie = colorB.z;\n\n // light mask\n clusterLightData.mask = colorB.w;\n\n vec4 lightPosRange = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_POSITION_RANGE);\n clusterLightData.position = lightPosRange.xyz;\n clusterLightData.range = lightPosRange.w;\n\n // spot light direction\n vec4 lightDir_Unused = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_SPOT_DIRECTION);\n clusterLightData.direction = lightDir_Unused.xyz;\n}\n\nvoid decodeClusterLightSpot(inout ClusterLightData clusterLightData) {\n\n // spot light cos angles\n vec4 coneAngle = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_SPOT_ANGLES);\n clusterLightData.innerConeAngleCos = bytes2float2(coneAngle.xy) * 2.0 - 1.0;\n clusterLightData.outerConeAngleCos = bytes2float2(coneAngle.zw) * 2.0 - 1.0;\n}\n\nvoid decodeClusterLightOmniAtlasViewport(inout ClusterLightData clusterLightData) {\n clusterLightData.omniAtlasViewport = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_PROJ_MAT_0).xyz;\n}\n\nvoid decodeClusterLightAreaData(inout ClusterLightData clusterLightData) {\n clusterLightData.halfWidth = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_AREA_DATA_WIDTH).xyz;\n clusterLightData.halfHeight = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_AREA_DATA_HEIGHT).xyz;\n}\n\nvoid decodeClusterLightProjectionMatrixData(inout ClusterLightData clusterLightData) {\n \n // shadow matrix\n vec4 m0 = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_PROJ_MAT_0);\n vec4 m1 = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_PROJ_MAT_1);\n vec4 m2 = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_PROJ_MAT_2);\n vec4 m3 = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_PROJ_MAT_3);\n lightProjectionMatrix = mat4(m0, m1, m2, m3);\n}\n\nvoid decodeClusterLightShadowData(inout ClusterLightData clusterLightData) {\n \n // shadow biases\n vec4 biases = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_SHADOW_BIAS);\n clusterLightData.shadowBias = bytes2floatRange2(biases.xy, -1.0, 20.0),\n clusterLightData.shadowNormalBias = bytes2float2(biases.zw);\n}\n\nvoid decodeClusterLightCookieData(inout ClusterLightData clusterLightData) {\n\n vec4 cookieA = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_COOKIE_A);\n clusterLightData.cookieIntensity = cookieA.x;\n clusterLightData.cookieRgb = cookieA.y;\n\n clusterLightData.cookieChannelMask = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_COOKIE_B);\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;\n vec3 lightDirNormW;\n evalOmniLight(light.position, lightDirW, lightDirNormW);\n\n #ifdef CLUSTER_AREALIGHTS\n\n // distance attenuation\n if (isClusteredLightArea(light)) { // area light\n\n // area lights\n decodeClusterLightAreaData(light);\n\n // handle light shape\n if (isClusteredLightRect(light)) {\n calcRectLightValues(light.position, light.halfWidth, light.halfHeight);\n } else if (isClusteredLightDisk(light)) {\n calcDiskLightValues(light.position, light.halfWidth, light.halfHeight);\n } else { // sphere\n calcSphereLightValues(light.position, light.halfWidth, light.halfHeight);\n }\n\n falloffAttenuation = getFalloffWindow(light.range, lightDirW);\n\n } else\n\n #endif\n\n { // punctual light\n\n if (isClusteredLightFalloffLinear(light))\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 (isClusteredLightArea(light)) { // area light\n\n // handle light shape\n if (isClusteredLightRect(light)) {\n diffuseAttenuation = getRectLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0;\n } else if (isClusteredLightDisk(light)) {\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 (isClusteredLightSpot(light)) {\n decodeClusterLightSpot(light);\n falloffAttenuation *= getSpotEffect(light.direction, light.innerConeAngleCos, light.outerConeAngleCos, lightDirNormW);\n }\n\n #if defined(CLUSTER_COOKIES) || defined(CLUSTER_SHADOWS)\n\n if (falloffAttenuation > 0.00001) {\n\n // shadow / cookie\n if (isClusteredLightCastShadow(light) || isClusteredLightCookie(light)) {\n\n // shared shadow / cookie data depends on light type\n if (isClusteredLightSpot(light)) {\n decodeClusterLightProjectionMatrixData(light);\n } else {\n decodeClusterLightOmniAtlasViewport(light);\n }\n\n float shadowTextureResolution = shadowAtlasParams.x;\n float shadowEdgePixels = shadowAtlasParams.y;\n\n #ifdef CLUSTER_COOKIES\n\n // cookie\n if (isClusteredLightCookie(light)) {\n decodeClusterLightCookieData(light);\n\n if (isClusteredLightSpot(light)) {\n cookieAttenuation = getCookie2DClustered(TEXTURE_PASS(cookieAtlasTexture), lightProjectionMatrix, vPositionW, light.cookieIntensity, isClusteredLightCookieRgb(light), light.cookieChannelMask);\n } else {\n cookieAttenuation = getCookieCubeClustered(TEXTURE_PASS(cookieAtlasTexture), lightDirW, light.cookieIntensity, isClusteredLightCookieRgb(light), light.cookieChannelMask, shadowTextureResolution, shadowEdgePixels, light.omniAtlasViewport);\n }\n }\n\n #endif\n\n #ifdef CLUSTER_SHADOWS\n\n // shadow\n if (isClusteredLightCastShadow(light)) {\n decodeClusterLightShadowData(light);\n\n vec4 shadowParams = vec4(shadowTextureResolution, light.shadowNormalBias, light.shadowBias, 1.0 / light.range);\n\n if (isClusteredLightSpot(light)) {\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, light.omniAtlasViewport, shadowEdgePixels, dir);\n #elif defined(CLUSTER_SHADOW_TYPE_PCF3)\n float shadow = getShadowOmniClusteredPCF3(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);\n #elif defined(CLUSTER_SHADOW_TYPE_PCF5)\n float shadow = getShadowOmniClusteredPCF5(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, light.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 (isClusteredLightArea(light)) { // 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 (isClusteredLightRect(light)) {\n areaLightSpecular = getRectLightSpecular(worldNormal, viewDir);\n } else if (isClusteredLightDisk(light)) {\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 (isClusteredLightRect(light)) {\n areaLightSpecularCC = getRectLightSpecular(clearcoat_worldNormal, viewDir);\n } else if (isClusteredLightDisk(light)) {\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 float 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;\n decodeClusterLightCore(clusterLightData, lightIndex);\n\n // evaluate light if it uses accepted light mask\n if (acceptLightMask(clusterLightData))\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 lights if no lights at all\n if (clusterSkip > 0.5)\n return;\n\n // world space position to 3d integer cell cordinates in the cluster structure\n vec3 cellCoords = floor((vPositionW - clusterBoundsMin) * clusterCellsCountByBoundsSize);\n\n // no lighting when cell coordinate is out of range\n if (!(any(lessThan(cellCoords, vec3(0.0))) || any(greaterThanEqual(cellCoords, clusterCellsMax)))) {\n\n // cell index (mapping from 3d cell coordinates to linear memory)\n float cellIndex = dot(clusterCellsDot, cellCoords);\n\n // convert cell index to uv coordinates\n float clusterV = floor(cellIndex * clusterTextureSize.y);\n float clusterU = cellIndex - (clusterV * clusterTextureSize.x);\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 alpha channel\n float lightIndex = texelFetch(clusterWorldTexture, ivec2(int(clusterU) + lightCellIndex, clusterV), 0).x;\n\n if (lightIndex <= 0.0)\n return;\n\n evaluateClusterLight(\n lightIndex * 255.0, \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 { clusteredLightPS as default };