UNPKG

playcanvas

Version:

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

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