UNPKG

@animech-public/playcanvas

Version:
770 lines (610 loc) 29.9 kB
var clusteredLightPS = /* glsl */` uniform highp sampler2D clusterWorldTexture; uniform highp sampler2D lightsTexture8; uniform highp sampler2D lightsTextureFloat; // complex ifdef expression are not supported, handle it here // defined(CLUSTER_COOKIES) || defined(CLUSTER_SHADOWS) #if defined(CLUSTER_COOKIES) #define CLUSTER_COOKIES_OR_SHADOWS #endif #if defined(CLUSTER_SHADOWS) #define CLUSTER_COOKIES_OR_SHADOWS #endif #ifdef CLUSTER_SHADOWS #ifdef GL2 // TODO: when VSM shadow is supported, it needs to use sampler2D in webgl2 uniform sampler2DShadow shadowAtlasTexture; #else uniform sampler2D shadowAtlasTexture; #endif #endif #ifdef CLUSTER_COOKIES uniform sampler2D cookieAtlasTexture; #endif #ifdef GL2 uniform int clusterMaxCells; #else uniform float clusterMaxCells; uniform vec4 lightsTextureInvSize; #endif // 1.0 if clustered lighting can be skipped (0 lights in the clusters) uniform float clusterSkip; uniform vec3 clusterCellsCountByBoundsSize; uniform vec3 clusterTextureSize; uniform vec3 clusterBoundsMin; uniform vec3 clusterBoundsDelta; uniform vec3 clusterCellsDot; uniform vec3 clusterCellsMax; uniform vec2 clusterCompressionLimit0; uniform vec2 shadowAtlasParams; // structure storing light properties of a clustered light // it's sorted to have all vectors aligned to 4 floats to limit padding struct ClusterLightData { // area light sizes / orientation vec3 halfWidth; // type of the light (spot or omni) float lightType; // area light sizes / orientation vec3 halfHeight; #ifdef GL2 // light index int lightIndex; #else // v coordinate to look up the light textures - this is the same as lightIndex but in 0..1 range float lightV; #endif // world space position vec3 position; // area light shape float shape; // world space direction (spot light only) vec3 direction; // light follow mode float falloffMode; // color vec3 color; // 0.0 if the light doesn't cast shadows float shadowIntensity; // atlas viewport for omni light shadow and cookie (.xy is offset to the viewport slot, .z is size of the face in the atlas) vec3 omniAtlasViewport; // range of the light float range; // channel mask - one of the channels has 1, the others are 0 vec4 cookieChannelMask; // shadow bias values float shadowBias; float shadowNormalBias; // spot light inner and outer angle cosine float innerConeAngleCos; float outerConeAngleCos; // 1.0 if the light has a cookie texture float cookie; // 1.0 if cookie texture is rgb, otherwise it is using a single channel selectable by cookieChannelMask float cookieRgb; // intensity of the cookie float cookieIntensity; // light mask float mask; }; // 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. // shadow (spot light only) / cookie projection matrix mat4 lightProjectionMatrix; // macros for light properties #define isClusteredLightCastShadow(light) ( light.shadowIntensity > 0.0 ) #define isClusteredLightCookie(light) (light.cookie > 0.5 ) #define isClusteredLightCookieRgb(light) (light.cookieRgb > 0.5 ) #define isClusteredLightSpot(light) ( light.lightType > 0.5 ) #define isClusteredLightFalloffLinear(light) ( light.falloffMode < 0.5 ) // macros to test light shape // Note: Following functions need to be called serially in listed order as they do not test both '>' and '<' #define isClusteredLightArea(light) ( light.shape > 0.1 ) #define isClusteredLightRect(light) ( light.shape < 0.3 ) #define isClusteredLightDisk(light) ( light.shape < 0.6 ) // macro to test light mask (mesh accepts dynamic vs lightmapped lights) #ifdef CLUSTER_MESH_DYNAMIC_LIGHTS // accept lights marked as dynamic or both dynamic and lightmapped #define acceptLightMask(light) ( light.mask < 0.75) #else // accept lights marked as lightmapped or both dynamic and lightmapped #define acceptLightMask(light) ( light.mask > 0.25) #endif vec4 decodeClusterLowRange4Vec4(vec4 d0, vec4 d1, vec4 d2, vec4 d3) { return vec4( bytes2floatRange4(d0, -2.0, 2.0), bytes2floatRange4(d1, -2.0, 2.0), bytes2floatRange4(d2, -2.0, 2.0), bytes2floatRange4(d3, -2.0, 2.0) ); } #ifdef GL2 vec4 sampleLightsTexture8(const ClusterLightData clusterLightData, int index) { return texelFetch(lightsTexture8, ivec2(index, clusterLightData.lightIndex), 0); } vec4 sampleLightTextureF(const ClusterLightData clusterLightData, int index) { return texelFetch(lightsTextureFloat, ivec2(index, clusterLightData.lightIndex), 0); } #else vec4 sampleLightsTexture8(const ClusterLightData clusterLightData, float index) { return texture2DLodEXT(lightsTexture8, vec2(index * lightsTextureInvSize.z, clusterLightData.lightV), 0.0); } vec4 sampleLightTextureF(const ClusterLightData clusterLightData, float index) { return texture2DLodEXT(lightsTextureFloat, vec2(index * lightsTextureInvSize.x, clusterLightData.lightV), 0.0); } #endif void decodeClusterLightCore(inout ClusterLightData clusterLightData, float lightIndex) { // light index #ifdef GL2 clusterLightData.lightIndex = int(lightIndex); #else clusterLightData.lightV = (lightIndex + 0.5) * lightsTextureInvSize.w; #endif // shared data from 8bit texture vec4 lightInfo = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_FLAGS); clusterLightData.lightType = lightInfo.x; clusterLightData.shape = lightInfo.y; clusterLightData.falloffMode = lightInfo.z; clusterLightData.shadowIntensity = lightInfo.w; // color vec4 colorA = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_COLOR_A); vec4 colorB = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_COLOR_B); clusterLightData.color = vec3(bytes2float2(colorA.xy), bytes2float2(colorA.zw), bytes2float2(colorB.xy)) * clusterCompressionLimit0.y; // cookie clusterLightData.cookie = colorB.z; // light mask clusterLightData.mask = colorB.w; #ifdef CLUSTER_TEXTURE_FLOAT vec4 lightPosRange = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_POSITION_RANGE); clusterLightData.position = lightPosRange.xyz; clusterLightData.range = lightPosRange.w; // spot light direction vec4 lightDir_Unused = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_SPOT_DIRECTION); clusterLightData.direction = lightDir_Unused.xyz; #else // 8bit vec4 encPosX = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_POSITION_X); vec4 encPosY = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_POSITION_Y); vec4 encPosZ = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_POSITION_Z); clusterLightData.position = vec3(bytes2float4(encPosX), bytes2float4(encPosY), bytes2float4(encPosZ)) * clusterBoundsDelta + clusterBoundsMin; vec4 encRange = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_RANGE); clusterLightData.range = bytes2float4(encRange) * clusterCompressionLimit0.x; // spot light direction vec4 encDirX = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_SPOT_DIRECTION_X); vec4 encDirY = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_SPOT_DIRECTION_Y); vec4 encDirZ = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_SPOT_DIRECTION_Z); clusterLightData.direction = vec3(bytes2float4(encDirX), bytes2float4(encDirY), bytes2float4(encDirZ)) * 2.0 - 1.0; #endif } void decodeClusterLightSpot(inout ClusterLightData clusterLightData) { // spot light cos angles vec4 coneAngle = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_SPOT_ANGLES); clusterLightData.innerConeAngleCos = bytes2float2(coneAngle.xy) * 2.0 - 1.0; clusterLightData.outerConeAngleCos = bytes2float2(coneAngle.zw) * 2.0 - 1.0; } void decodeClusterLightOmniAtlasViewport(inout ClusterLightData clusterLightData) { #ifdef CLUSTER_TEXTURE_FLOAT clusterLightData.omniAtlasViewport = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_PROJ_MAT_0).xyz; #else vec4 viewportA = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_ATLAS_VIEWPORT_A); vec4 viewportB = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_ATLAS_VIEWPORT_B); clusterLightData.omniAtlasViewport = vec3(bytes2float2(viewportA.xy), bytes2float2(viewportA.zw), bytes2float2(viewportB.xy)); #endif } void decodeClusterLightAreaData(inout ClusterLightData clusterLightData) { #ifdef CLUSTER_TEXTURE_FLOAT clusterLightData.halfWidth = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_AREA_DATA_WIDTH).xyz; clusterLightData.halfHeight = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_AREA_DATA_HEIGHT).xyz; #else vec4 areaWidthX = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_AREA_DATA_WIDTH_X); vec4 areaWidthY = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_AREA_DATA_WIDTH_Y); vec4 areaWidthZ = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_AREA_DATA_WIDTH_Z); clusterLightData.halfWidth = vec3(mantissaExponent2Float(areaWidthX), mantissaExponent2Float(areaWidthY), mantissaExponent2Float(areaWidthZ)); vec4 areaHeightX = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_AREA_DATA_HEIGHT_X); vec4 areaHeightY = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_AREA_DATA_HEIGHT_Y); vec4 areaHeightZ = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_AREA_DATA_HEIGHT_Z); clusterLightData.halfHeight = vec3(mantissaExponent2Float(areaHeightX), mantissaExponent2Float(areaHeightY), mantissaExponent2Float(areaHeightZ)); #endif } void decodeClusterLightProjectionMatrixData(inout ClusterLightData clusterLightData) { // shadow matrix #ifdef CLUSTER_TEXTURE_FLOAT vec4 m0 = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_PROJ_MAT_0); vec4 m1 = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_PROJ_MAT_1); vec4 m2 = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_PROJ_MAT_2); vec4 m3 = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_PROJ_MAT_3); #else vec4 m00 = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_PROJ_MAT_00); vec4 m01 = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_PROJ_MAT_01); vec4 m02 = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_PROJ_MAT_02); vec4 m03 = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_PROJ_MAT_03); vec4 m0 = decodeClusterLowRange4Vec4(m00, m01, m02, m03); vec4 m10 = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_PROJ_MAT_10); vec4 m11 = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_PROJ_MAT_11); vec4 m12 = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_PROJ_MAT_12); vec4 m13 = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_PROJ_MAT_13); vec4 m1 = decodeClusterLowRange4Vec4(m10, m11, m12, m13); vec4 m20 = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_PROJ_MAT_20); vec4 m21 = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_PROJ_MAT_21); vec4 m22 = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_PROJ_MAT_22); vec4 m23 = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_PROJ_MAT_23); vec4 m2 = decodeClusterLowRange4Vec4(m20, m21, m22, m23); vec4 m30 = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_PROJ_MAT_30); vec4 m31 = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_PROJ_MAT_31); vec4 m32 = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_PROJ_MAT_32); vec4 m33 = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_PROJ_MAT_33); vec4 m3 = vec4(mantissaExponent2Float(m30), mantissaExponent2Float(m31), mantissaExponent2Float(m32), mantissaExponent2Float(m33)); #endif lightProjectionMatrix = mat4(m0, m1, m2, m3); } void decodeClusterLightShadowData(inout ClusterLightData clusterLightData) { // shadow biases vec4 biases = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_SHADOW_BIAS); clusterLightData.shadowBias = bytes2floatRange2(biases.xy, -1.0, 20.0), clusterLightData.shadowNormalBias = bytes2float2(biases.zw); } void decodeClusterLightCookieData(inout ClusterLightData clusterLightData) { vec4 cookieA = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_COOKIE_A); clusterLightData.cookieIntensity = cookieA.x; clusterLightData.cookieRgb = cookieA.y; clusterLightData.cookieChannelMask = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_COOKIE_B); } 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 getLightDirPoint(light.position); #ifdef CLUSTER_AREALIGHTS // distance attenuation if (isClusteredLightArea(light)) { // area light // area lights decodeClusterLightAreaData(light); // handle light shape if (isClusteredLightRect(light)) { calcRectLightValues(light.position, light.halfWidth, light.halfHeight); } else if (isClusteredLightDisk(light)) { calcDiskLightValues(light.position, light.halfWidth, light.halfHeight); } else { // sphere calcSphereLightValues(light.position, light.halfWidth, light.halfHeight); } falloffAttenuation = getFalloffWindow(light.range, dLightDirW); } else #endif { // punctual light if (isClusteredLightFalloffLinear(light)) falloffAttenuation = getFalloffLinear(light.range, dLightDirW); else falloffAttenuation = getFalloffInvSquared(light.range, dLightDirW); } if (falloffAttenuation > 0.00001) { #ifdef CLUSTER_AREALIGHTS if (isClusteredLightArea(light)) { // area light // handle light shape if (isClusteredLightRect(light)) { diffuseAttenuation = getRectLightDiffuse(worldNormal, viewDir, dLightDirW, dLightDirNormW) * 16.0; } else if (isClusteredLightDisk(light)) { diffuseAttenuation = getDiskLightDiffuse(worldNormal, viewDir, dLightDirW, dLightDirNormW) * 16.0; } else { // sphere diffuseAttenuation = getSphereLightDiffuse(worldNormal, viewDir, dLightDirW, dLightDirNormW) * 16.0; } } else #endif { falloffAttenuation *= getLightDiffuse(worldNormal, viewDir, dLightDirW, dLightDirNormW); } // spot light falloff if (isClusteredLightSpot(light)) { decodeClusterLightSpot(light); falloffAttenuation *= getSpotEffect(light.direction, light.innerConeAngleCos, light.outerConeAngleCos, dLightDirNormW); } #if defined(CLUSTER_COOKIES_OR_SHADOWS) if (falloffAttenuation > 0.00001) { // shadow / cookie if (isClusteredLightCastShadow(light) || isClusteredLightCookie(light)) { // shared shadow / cookie data depends on light type if (isClusteredLightSpot(light)) { decodeClusterLightProjectionMatrixData(light); } else { decodeClusterLightOmniAtlasViewport(light); } float shadowTextureResolution = shadowAtlasParams.x; float shadowEdgePixels = shadowAtlasParams.y; #ifdef CLUSTER_COOKIES // cookie if (isClusteredLightCookie(light)) { decodeClusterLightCookieData(light); if (isClusteredLightSpot(light)) { cookieAttenuation = getCookie2DClustered(TEXTURE_PASS(cookieAtlasTexture), lightProjectionMatrix, vPositionW, light.cookieIntensity, isClusteredLightCookieRgb(light), light.cookieChannelMask); } else { cookieAttenuation = getCookieCubeClustered(TEXTURE_PASS(cookieAtlasTexture), dLightDirW, light.cookieIntensity, isClusteredLightCookieRgb(light), light.cookieChannelMask, shadowTextureResolution, shadowEdgePixels, light.omniAtlasViewport); } } #endif #ifdef CLUSTER_SHADOWS // shadow if (isClusteredLightCastShadow(light)) { decodeClusterLightShadowData(light); vec4 shadowParams = vec4(shadowTextureResolution, light.shadowNormalBias, light.shadowBias, 1.0 / light.range); if (isClusteredLightSpot(light)) { // spot shadow getShadowCoordPerspZbufferNormalOffset(lightProjectionMatrix, shadowParams, geometricNormal); #if defined(CLUSTER_SHADOW_TYPE_PCF1) float shadow = getShadowSpotClusteredPCF1(SHADOWMAP_PASS(shadowAtlasTexture), dShadowCoord, shadowParams); #elif defined(CLUSTER_SHADOW_TYPE_PCF3) float shadow = getShadowSpotClusteredPCF3(SHADOWMAP_PASS(shadowAtlasTexture), dShadowCoord, shadowParams); #elif defined(CLUSTER_SHADOW_TYPE_PCF5) float shadow = getShadowSpotClusteredPCF5(SHADOWMAP_PASS(shadowAtlasTexture), dShadowCoord, shadowParams); #elif defined(CLUSTER_SHADOW_TYPE_PCSS) float shadow = getShadowSpotClusteredPCSS(SHADOWMAP_PASS(shadowAtlasTexture), dShadowCoord, shadowParams); #endif falloffAttenuation *= mix(1.0, shadow, light.shadowIntensity); } else { // omni shadow vec3 dir = normalOffsetPointShadow(shadowParams, dLightPosW, dLightDirW, dLightDirNormW, geometricNormal); // normalBias adjusted for distance #if defined(CLUSTER_SHADOW_TYPE_PCF1) float shadow = getShadowOmniClusteredPCF1(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir); #elif defined(CLUSTER_SHADOW_TYPE_PCF3) float shadow = getShadowOmniClusteredPCF3(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir); #elif defined(CLUSTER_SHADOW_TYPE_PCF5) float shadow = getShadowOmniClusteredPCF5(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir); #endif falloffAttenuation *= mix(1.0, shadow, light.shadowIntensity); } } #endif } } #endif // diffuse / specular / clearcoat #ifdef CLUSTER_AREALIGHTS if (isClusteredLightArea(light)) { // area light // area light diffuse { vec3 areaDiffuse = (diffuseAttenuation * falloffAttenuation) * light.color * cookieAttenuation; #if defined(LIT_SPECULAR) #if defined(LIT_CONSERVE_ENERGY) areaDiffuse = mix(areaDiffuse, vec3(0), dLTCSpecFres); #endif #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 (isClusteredLightRect(light)) { areaLightSpecular = getRectLightSpecular(worldNormal, viewDir); } else if (isClusteredLightDisk(light)) { 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 (isClusteredLightRect(light)) { areaLightSpecularCC = getRectLightSpecular(clearcoat_worldNormal, viewDir); } else if (isClusteredLightDisk(light)) { 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) #if defined(LIT_CONSERVE_ENERGY) punctualDiffuse = mix(punctualDiffuse, vec3(0), specularity); #endif #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(-dLightDirNormW + viewDir); // specular #ifdef LIT_SPECULAR_FRESNEL dSpecularLight += getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, dLightDirNormW, 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, dLightDirNormW, gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * specularity; #endif #ifdef LIT_CLEARCOAT #ifdef LIT_SPECULAR_FRESNEL ccSpecularLight += getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, dLightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * getFresnelCC(dot(viewDir, halfDir)); #else ccSpecularLight += getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, dLightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation; #endif #endif #ifdef LIT_SHEEN sSpecularLight += getLightSpecularSheen(halfDir, worldNormal, viewDir, dLightDirNormW, sheen_gloss) * falloffAttenuation * light.color * cookieAttenuation; #endif #endif } } // Write to global attenuation values (for lightmapper) dAtten = falloffAttenuation; dAttenD = diffuseAttenuation; dAtten3 = cookieAttenuation; } void evaluateClusterLight( float 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(clusterLightData, lightIndex); // evaluate light if it uses accepted light mask if (acceptLightMask(clusterLightData)) 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 lights if no lights at all if (clusterSkip > 0.5) return; // world space position to 3d integer cell cordinates in the cluster structure vec3 cellCoords = floor((vPositionW - clusterBoundsMin) * clusterCellsCountByBoundsSize); // no lighting when cell coordinate is out of range if (!(any(lessThan(cellCoords, vec3(0.0))) || any(greaterThanEqual(cellCoords, clusterCellsMax)))) { // cell index (mapping from 3d cell coordinates to linear memory) float cellIndex = dot(clusterCellsDot, cellCoords); // convert cell index to uv coordinates float clusterV = floor(cellIndex * clusterTextureSize.y); float clusterU = cellIndex - (clusterV * clusterTextureSize.x); #ifdef GL2 // loop over maximum number of light cells for (int lightCellIndex = 0; lightCellIndex < clusterMaxCells; lightCellIndex++) { // using a single channel texture with data in alpha channel float lightIndex = texelFetch(clusterWorldTexture, ivec2(int(clusterU) + lightCellIndex, clusterV), 0).x; if (lightIndex <= 0.0) return; evaluateClusterLight( lightIndex * 255.0, 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 ); } #else clusterV = (clusterV + 0.5) * clusterTextureSize.z; // loop over maximum possible number of supported light cells const float maxLightCells = 256.0; for (float lightCellIndex = 0.5; lightCellIndex < maxLightCells; lightCellIndex++) { float lightIndex = texture2DLodEXT(clusterWorldTexture, vec2(clusterTextureSize.y * (clusterU + lightCellIndex), clusterV), 0.0).x; if (lightIndex <= 0.0) return; evaluateClusterLight( lightIndex * 255.0, 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 ); // end of the cell array if (lightCellIndex >= clusterMaxCells) { break; } } #endif } } `; export { clusteredLightPS as default };