UNPKG

playcanvas

Version:

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

425 lines (424 loc) 16.6 kB
var clusteredLight_default = ` #include "lightBufferDefinesPS" #include "clusteredLightUtilsPS" #ifdef CLUSTER_COOKIES #include "clusteredLightCookiesPS" #endif #ifdef CLUSTER_SHADOWS #include "clusteredLightShadowsPS" #endif var clusterWorldTexture: texture_2d<u32>; var lightsTexture: texture_2d<uff>; #ifdef CLUSTER_SHADOWS var shadowAtlasTexture: texture_depth_2d; var shadowAtlasTextureSampler: sampler_comparison; #endif #ifdef CLUSTER_COOKIES var cookieAtlasTexture: texture_2d<f32>; var cookieAtlasTextureSampler: sampler; #endif uniform clusterMaxCells: i32; uniform numClusteredLights: i32; uniform clusterTextureWidth: i32; uniform clusterCellsCountByBoundsSize: vec3f; uniform clusterBoundsMin: vec3f; uniform clusterBoundsDelta: vec3f; uniform clusterCellsDot: vec3i; uniform clusterCellsMax: vec3i; uniform shadowAtlasParams: vec2f; struct ClusterLightData { position: vec3f, lightIndex: i32, direction: vec3f, shape: u32, color: vec3f, shadowIntensity: f32, range: f32, biasesData: f32, cookieIntensity: f32, isSpot: bool, falloffModeLinear: bool, isDynamic: bool, isLightmapped: bool } struct ClusterLightSpotData { innerConeAngleCos: f32, outerConeAngleCos: f32 } struct ClusterLightAreaData { halfWidth: vec3f, halfHeight: vec3f } struct ClusterLightShadowData { shadowBias: f32, shadowNormalBias: f32 } var<private> lightProjectionMatrix: mat4x4f; var<private> clusterLightData_flags: u32; var<private> clusterLightData_anglesData: f32; var<private> clusterLightData_colorBFlagsData: u32; fn sampleLightTextureF(lightIndex: i32, index: i32) -> vec4f { return textureLoad(lightsTexture, vec2<i32>(index, lightIndex), 0); } fn decodeClusterLightCore(lightIndex: i32) -> ClusterLightData { var clusterLightData: ClusterLightData; clusterLightData.lightIndex = lightIndex; let halfData: vec4f = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_COLOR_ANGLES_BIAS}); clusterLightData_anglesData = halfData.z; clusterLightData.biasesData = halfData.w; clusterLightData_colorBFlagsData = bitcast<u32>(halfData.y); let colorRG: vec2f = unpack2x16float(bitcast<u32>(halfData.x)); let colorB_flags: vec2f = unpack2x16float(clusterLightData_colorBFlagsData); clusterLightData.color = vec3f(colorRG, colorB_flags.x) * {LIGHT_COLOR_DIVIDER}; let lightPosRange: vec4f = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_POSITION_RANGE}); clusterLightData.position = lightPosRange.xyz; clusterLightData.range = lightPosRange.w; let lightDir_Flags: vec4f = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_DIRECTION_FLAGS}); clusterLightData.direction = lightDir_Flags.xyz; clusterLightData_flags = bitcast<u32>(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 = f32((clusterLightData_flags >> 0u) & 0xFFu) / 255.0; clusterLightData.cookieIntensity = f32((clusterLightData_flags >> 8u) & 0xFFu) / 255.0; clusterLightData.isDynamic = (clusterLightData_flags & (1u << 22u)) != 0u; clusterLightData.isLightmapped = (clusterLightData_flags & (1u << 21u)) != 0u; return clusterLightData; } fn decodeClusterLightSpot() -> ClusterLightSpotData { let angleFlags: u32 = (clusterLightData_colorBFlagsData >> 16u) & 0xFFFFu; let angleValues: vec2f = unpack2x16float(bitcast<u32>(clusterLightData_anglesData)); let innerVal: f32 = angleValues.x; let outerVal: f32 = angleValues.y; let innerIsVersine: bool = (angleFlags & 1u) != 0u; let outerIsVersine: bool = ((angleFlags >> 1u) & 1u) != 0u; return ClusterLightSpotData( select(innerVal, 1.0 - innerVal, innerIsVersine), select(outerVal, 1.0 - outerVal, outerIsVersine) ); } fn decodeClusterLightOmniAtlasViewport(lightIndex: i32) -> vec3f { return sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_0}).xyz; } fn decodeClusterLightAreaData(lightIndex: i32) -> ClusterLightAreaData { return ClusterLightAreaData( sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_AREA_DATA_WIDTH}).xyz, sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_AREA_DATA_HEIGHT}).xyz ); } fn decodeClusterLightProjectionMatrixData(lightIndex: i32) -> mat4x4f { let m0: vec4f = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_0}); let m1: vec4f = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_1}); let m2: vec4f = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_2}); let m3: vec4f = sampleLightTextureF(lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_3}); return mat4x4f(m0, m1, m2, m3); } fn decodeClusterLightShadowData(biasesData: f32) -> ClusterLightShadowData { let biases: vec2f = unpack2x16float(bitcast<u32>(biasesData)); return ClusterLightShadowData(biases.x, biases.y); } fn decodeClusterLightCookieData() -> vec4f { let cookieFlags: u32 = (clusterLightData_flags >> 23u) & 0x0Fu; let mask_uvec: vec4<u32> = vec4<u32>(cookieFlags) & vec4<u32>(1u, 2u, 4u, 8u); return step(vec4f(1.0), vec4f(mask_uvec)); } fn evaluateLight( light: ClusterLightData, worldNormal: vec3f, viewDir: vec3f, reflectionDir: vec3f, #if defined(LIT_CLEARCOAT) clearcoatReflectionDir: vec3f, #endif gloss: f32, specularity: vec3f, geometricNormal: vec3f, tbn: mat3x3f, #if defined(LIT_IRIDESCENCE) iridescenceFresnel: vec3f, #endif clearcoat_worldNormal: vec3f, clearcoat_gloss: f32, sheen_gloss: f32, iridescence_intensity: f32 ) { var cookieAttenuation: vec3f = vec3f(1.0); var diffuseAttenuation: f32 = 1.0; var falloffAttenuation: f32 = 1.0; let lightDirW: vec3f = evalOmniLight(light.position); let lightDirNormW: vec3f = normalize(lightDirW); #ifdef CLUSTER_AREALIGHTS if (light.shape != {LIGHTSHAPE_PUNCTUAL}) { let areaData: ClusterLightAreaData = decodeClusterLightAreaData(light.lightIndex); 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 { calcSphereLightValues(light.position, areaData.halfWidth, areaData.halfHeight); } falloffAttenuation = getFalloffWindow(light.range, lightDirW); } else #endif { 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}) { 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 { diffuseAttenuation = getSphereLightDiffuse(worldNormal, viewDir, lightDirW, lightDirNormW) * 16.0; } } else #endif { falloffAttenuation = falloffAttenuation * getLightDiffuse(worldNormal, viewDir, lightDirNormW); } if (light.isSpot) { let spotData: ClusterLightSpotData = decodeClusterLightSpot(); falloffAttenuation = falloffAttenuation * getSpotEffect(light.direction, spotData.innerConeAngleCos, spotData.outerConeAngleCos, lightDirNormW); } #if defined(CLUSTER_COOKIES) || defined(CLUSTER_SHADOWS) if (falloffAttenuation > 0.00001) { if (light.shadowIntensity > 0.0 || light.cookieIntensity > 0.0) { var omniAtlasViewport: vec3f = vec3f(0.0); if (light.isSpot) { lightProjectionMatrix = decodeClusterLightProjectionMatrixData(light.lightIndex); } else { omniAtlasViewport = decodeClusterLightOmniAtlasViewport(light.lightIndex); } let shadowTextureResolution: f32 = uniform.shadowAtlasParams.x; let shadowEdgePixels: f32 = uniform.shadowAtlasParams.y; #ifdef CLUSTER_COOKIES if (light.cookieIntensity > 0.0) { let cookieChannelMask: vec4f = decodeClusterLightCookieData(); if (light.isSpot) { cookieAttenuation = getCookie2DClustered(cookieAtlasTexture, cookieAtlasTextureSampler, lightProjectionMatrix, vPositionW, light.cookieIntensity, cookieChannelMask); } else { cookieAttenuation = getCookieCubeClustered(cookieAtlasTexture, cookieAtlasTextureSampler, lightDirW, light.cookieIntensity, cookieChannelMask, shadowTextureResolution, shadowEdgePixels, omniAtlasViewport); } } #endif #ifdef CLUSTER_SHADOWS if (light.shadowIntensity > 0.0) { let shadowData: ClusterLightShadowData = decodeClusterLightShadowData(light.biasesData); let shadowParams: vec4f = vec4f(shadowTextureResolution, shadowData.shadowNormalBias, shadowData.shadowBias, 1.0 / light.range); if (light.isSpot) { let shadowCoord: vec3f = getShadowCoordPerspZbufferNormalOffset(lightProjectionMatrix, shadowParams, geometricNormal); #if defined(CLUSTER_SHADOW_TYPE_PCF1) let shadow: f32 = getShadowSpotClusteredPCF1(shadowAtlasTexture, shadowAtlasTextureSampler, shadowCoord, shadowParams); #elif defined(CLUSTER_SHADOW_TYPE_PCF3) let shadow: f32 = getShadowSpotClusteredPCF3(shadowAtlasTexture, shadowAtlasTextureSampler, shadowCoord, shadowParams); #elif defined(CLUSTER_SHADOW_TYPE_PCF5) let shadow: f32 = getShadowSpotClusteredPCF5(shadowAtlasTexture, shadowAtlasTextureSampler, shadowCoord, shadowParams); #elif defined(CLUSTER_SHADOW_TYPE_PCSS) let shadow: f32 = getShadowSpotClusteredPCSS(shadowAtlasTexture, shadowAtlasTextureSampler, shadowCoord, shadowParams); #endif falloffAttenuation = falloffAttenuation * mix(1.0, shadow, light.shadowIntensity); } else { let dir: vec3f = normalOffsetPointShadow(shadowParams, light.position, lightDirW, lightDirNormW, geometricNormal); #if defined(CLUSTER_SHADOW_TYPE_PCF1) let shadow: f32 = getShadowOmniClusteredPCF1(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, omniAtlasViewport, shadowEdgePixels, dir); #elif defined(CLUSTER_SHADOW_TYPE_PCF3) let shadow: f32 = getShadowOmniClusteredPCF3(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, omniAtlasViewport, shadowEdgePixels, dir); #elif defined(CLUSTER_SHADOW_TYPE_PCF5) let shadow: f32 = getShadowOmniClusteredPCF5(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, omniAtlasViewport, shadowEdgePixels, dir); #endif falloffAttenuation = falloffAttenuation * mix(1.0, shadow, light.shadowIntensity); } } #endif } } #endif #ifdef CLUSTER_AREALIGHTS if (light.shape != {LIGHTSHAPE_PUNCTUAL}) { { var areaDiffuse: vec3f = (diffuseAttenuation * falloffAttenuation) * light.color * cookieAttenuation; #if defined(LIT_SPECULAR) areaDiffuse = mix(areaDiffuse, vec3f(0.0), dLTCSpecFres); #endif dDiffuseLight = dDiffuseLight + areaDiffuse; } #ifdef LIT_SPECULAR var areaLightSpecular: f32; if (light.shape == {LIGHTSHAPE_RECT}) { areaLightSpecular = getRectLightSpecular(worldNormal, viewDir); } else if (light.shape == {LIGHTSHAPE_DISK}) { areaLightSpecular = getDiskLightSpecular(worldNormal, viewDir); } else { areaLightSpecular = getSphereLightSpecular(worldNormal, viewDir); } dSpecularLight = dSpecularLight + dLTCSpecFres * areaLightSpecular * falloffAttenuation * light.color * cookieAttenuation; #ifdef LIT_CLEARCOAT var areaLightSpecularCC: f32; if (light.shape == {LIGHTSHAPE_RECT}) { areaLightSpecularCC = getRectLightSpecular(clearcoat_worldNormal, viewDir); } else if (light.shape == {LIGHTSHAPE_DISK}) { areaLightSpecularCC = getDiskLightSpecular(clearcoat_worldNormal, viewDir); } else { areaLightSpecularCC = getSphereLightSpecular(clearcoat_worldNormal, viewDir); } ccSpecularLight = ccSpecularLight + ccLTCSpecFres * areaLightSpecularCC * falloffAttenuation * light.color * cookieAttenuation; #endif #endif } else #endif { { var punctualDiffuse: vec3f = falloffAttenuation * light.color * cookieAttenuation; #if defined(CLUSTER_AREALIGHTS) #if defined(LIT_SPECULAR) punctualDiffuse = mix(punctualDiffuse, vec3f(0.0), specularity); #endif #endif dDiffuseLight = dDiffuseLight + punctualDiffuse; } #ifdef LIT_SPECULAR let halfDir: vec3f = normalize(-lightDirNormW + viewDir); #ifdef LIT_SPECULAR_FRESNEL dSpecularLight = 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 = dSpecularLight + getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, lightDirNormW, gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * specularity; #endif #ifdef LIT_CLEARCOAT #ifdef LIT_SPECULAR_FRESNEL ccSpecularLight = ccSpecularLight + getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, lightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * getFresnelCC(dot(viewDir, halfDir)); #else ccSpecularLight = ccSpecularLight + getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, lightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation; #endif #endif #ifdef LIT_SHEEN sSpecularLight = sSpecularLight + getLightSpecularSheen(halfDir, worldNormal, viewDir, lightDirNormW, sheen_gloss) * falloffAttenuation * light.color * cookieAttenuation; #endif #endif } } dAtten = falloffAttenuation; dLightDirNormW = lightDirNormW; } fn evaluateClusterLight( lightIndex: i32, worldNormal: vec3f, viewDir: vec3f, reflectionDir: vec3f, #if defined(LIT_CLEARCOAT) clearcoatReflectionDir: vec3f, #endif gloss: f32, specularity: vec3f, geometricNormal: vec3f, tbn: mat3x3f, #if defined(LIT_IRIDESCENCE) iridescenceFresnel: vec3f, #endif clearcoat_worldNormal: vec3f, clearcoat_gloss: f32, sheen_gloss: f32, iridescence_intensity: f32 ) { let clusterLightData: ClusterLightData = decodeClusterLightCore(lightIndex); #ifdef CLUSTER_MESH_DYNAMIC_LIGHTS let acceptLightMask: bool = clusterLightData.isDynamic; #else let acceptLightMask: bool = 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 ); } } fn addClusteredLights( worldNormal: vec3f, viewDir: vec3f, reflectionDir: vec3f, #if defined(LIT_CLEARCOAT) clearcoatReflectionDir: vec3f, #endif gloss: f32, specularity: vec3f, geometricNormal: vec3f, tbn: mat3x3f, #if defined(LIT_IRIDESCENCE) iridescenceFresnel: vec3f, #endif clearcoat_worldNormal: vec3f, clearcoat_gloss: f32, sheen_gloss: f32, iridescence_intensity: f32 ) { if (uniform.numClusteredLights <= 1) { return; } let cellCoords: vec3i = vec3i(floor((vPositionW - uniform.clusterBoundsMin) * uniform.clusterCellsCountByBoundsSize)); if (!(any(cellCoords < vec3i(0)) || any(cellCoords >= uniform.clusterCellsMax))) { let cellIndex: i32 = cellCoords.x * uniform.clusterCellsDot.x + cellCoords.y * uniform.clusterCellsDot.y + cellCoords.z * uniform.clusterCellsDot.z; let clusterV: i32 = cellIndex / uniform.clusterTextureWidth; let clusterU: i32 = cellIndex - clusterV * uniform.clusterTextureWidth; for (var lightCellIndex: i32 = 0; lightCellIndex < uniform.clusterMaxCells; lightCellIndex = lightCellIndex + 1) { let lightIndex: u32 = textureLoad(clusterWorldTexture, vec2<i32>(clusterU + lightCellIndex, clusterV), 0).r; if (lightIndex == 0u) { break; } evaluateClusterLight( i32(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 };