playcanvas
Version:
PlayCanvas WebGL game engine
409 lines (407 loc) • 16.2 kB
JavaScript
var clusteredLightPS = `
#include "lightBufferDefinesPS"
#include "clusteredLightUtilsPS"
#ifdef CLUSTER_COOKIES
#include "clusteredLightCookiesPS"
#endif
#ifdef CLUSTER_SHADOWS
#include "clusteredLightShadowsPS"
#endif
var clusterWorldTexture: texture_2d<f32>;
var lightsTexture: texture_2d<f32>;
#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 clusterSkip: f32;
uniform clusterCellsCountByBoundsSize: vec3f;
uniform clusterTextureSize: vec3f;
uniform clusterBoundsMin: vec3f;
uniform clusterBoundsDelta: vec3f;
uniform clusterCellsDot: vec3f;
uniform clusterCellsMax: vec3f;
uniform shadowAtlasParams: vec2f;
struct ClusterLightData {
flags: u32,
halfWidth: vec3f,
isSpot: bool,
halfHeight: vec3f,
lightIndex: i32,
position: vec3f,
shape: u32,
direction: vec3f,
falloffModeLinear: bool,
color: vec3f,
shadowIntensity: f32,
omniAtlasViewport: vec3f,
range: f32,
cookieChannelMask: vec4f,
biasesData: f32,
shadowBias: f32,
shadowNormalBias: f32,
anglesData: f32,
innerConeAngleCos: f32,
outerConeAngleCos: f32,
cookieIntensity: f32,
isDynamic: bool,
isLightmapped: bool
}
var<private> lightProjectionMatrix: mat4x4f;
fn sampleLightTextureF(lightIndex: i32, index: i32) -> vec4f {
return textureLoad(lightsTexture, vec2<i32>(index, lightIndex), 0);
}
fn decodeClusterLightCore(clusterLightData: ptr<function, ClusterLightData>, lightIndex: f32) {
clusterLightData.lightIndex = i32(lightIndex);
let halfData: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_COLOR_ANGLES_BIAS});
clusterLightData.anglesData = halfData.z;
clusterLightData.biasesData = halfData.w;
let colorRG: vec2f = unpack2x16float(bitcast<u32>(halfData.x));
let colorB_: vec2f = unpack2x16float(bitcast<u32>(halfData.y));
clusterLightData.color = vec3f(colorRG, colorB_.x) * {LIGHT_COLOR_DIVIDER};
let lightPosRange: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_POSITION_RANGE});
clusterLightData.position = lightPosRange.xyz;
clusterLightData.range = lightPosRange.w;
let lightDir_Flags: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_DIRECTION_FLAGS});
clusterLightData.direction = lightDir_Flags.xyz;
let flags_uint: u32 = bitcast<u32>(lightDir_Flags.w);
clusterLightData.flags = flags_uint;
clusterLightData.isSpot = (flags_uint & (1u << 30u)) != 0u;
clusterLightData.shape = (flags_uint >> 28u) & 0x3u;
clusterLightData.falloffModeLinear = (flags_uint & (1u << 27u)) == 0u;
clusterLightData.shadowIntensity = f32((flags_uint >> 0u) & 0xFFu) / 255.0;
clusterLightData.cookieIntensity = f32((flags_uint >> 8u) & 0xFFu) / 255.0;
clusterLightData.isDynamic = (flags_uint & (1u << 22u)) != 0u;
clusterLightData.isLightmapped = (flags_uint & (1u << 21u)) != 0u;
}
fn decodeClusterLightSpot(clusterLightData: ptr<function, ClusterLightData>) {
let angles: vec2f = unpack2x16float(bitcast<u32>(clusterLightData.anglesData));
clusterLightData.innerConeAngleCos = angles.x;
clusterLightData.outerConeAngleCos = angles.y;
}
fn decodeClusterLightOmniAtlasViewport(clusterLightData: ptr<function, ClusterLightData>) {
clusterLightData.omniAtlasViewport = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_0}).xyz;
}
fn decodeClusterLightAreaData(clusterLightData: ptr<function, ClusterLightData>) {
clusterLightData.halfWidth = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_AREA_DATA_WIDTH}).xyz;
clusterLightData.halfHeight = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_AREA_DATA_HEIGHT}).xyz;
}
fn decodeClusterLightProjectionMatrixData(clusterLightData: ptr<function, ClusterLightData>) {
let m0: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_0});
let m1: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_1});
let m2: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_2});
let m3: vec4f = sampleLightTextureF(clusterLightData.lightIndex, {CLUSTER_TEXTURE_PROJ_MAT_3});
lightProjectionMatrix = mat4x4f(m0, m1, m2, m3);
}
fn decodeClusterLightShadowData(clusterLightData: ptr<function, ClusterLightData>) {
let biases: vec2f = unpack2x16float(bitcast<u32>(clusterLightData.biasesData));
clusterLightData.shadowBias = biases.x;
clusterLightData.shadowNormalBias = biases.y;
}
fn decodeClusterLightCookieData(clusterLightData: ptr<function, ClusterLightData>) {
let cookieFlags: u32 = (clusterLightData.flags >> 23u) & 0x0Fu;
let mask_uvec: vec4<u32> = vec4<u32>(cookieFlags) & vec4<u32>(1u, 2u, 4u, 8u);
clusterLightData.cookieChannelMask = step(vec4f(1.0), vec4f(mask_uvec));
}
fn evaluateLight(
light: ptr<function, 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}) {
decodeClusterLightAreaData(light);
if (light.shape == {LIGHTSHAPE_RECT}) {
calcRectLightValues(light.position, light.halfWidth, light.halfHeight);
} else if (light.shape == {LIGHTSHAPE_DISK}) {
calcDiskLightValues(light.position, light.halfWidth, light.halfHeight);
} else {
calcSphereLightValues(light.position, light.halfWidth, light.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) {
decodeClusterLightSpot(light);
falloffAttenuation = falloffAttenuation * getSpotEffect(light.direction, light.innerConeAngleCos, light.outerConeAngleCos, lightDirNormW);
}
#if defined(CLUSTER_COOKIES) || defined(CLUSTER_SHADOWS)
if (falloffAttenuation > 0.00001) {
if (light.shadowIntensity > 0.0 || light.cookieIntensity > 0.0) {
if (light.isSpot) {
decodeClusterLightProjectionMatrixData(light);
} else {
decodeClusterLightOmniAtlasViewport(light);
}
let shadowTextureResolution: f32 = uniform.shadowAtlasParams.x;
let shadowEdgePixels: f32 = uniform.shadowAtlasParams.y;
#ifdef CLUSTER_COOKIES
if (light.cookieIntensity > 0.0) {
decodeClusterLightCookieData(light);
if (light.isSpot) {
cookieAttenuation = getCookie2DClustered(cookieAtlasTexture, cookieAtlasTextureSampler, lightProjectionMatrix, vPositionW, light.cookieIntensity, light.cookieChannelMask);
} else {
cookieAttenuation = getCookieCubeClustered(cookieAtlasTexture, cookieAtlasTextureSampler, lightDirW, light.cookieIntensity, light.cookieChannelMask, shadowTextureResolution, shadowEdgePixels, light.omniAtlasViewport);
}
}
#endif
#ifdef CLUSTER_SHADOWS
if (light.shadowIntensity > 0.0) {
decodeClusterLightShadowData(light);
let shadowParams: vec4f = vec4f(shadowTextureResolution, light.shadowNormalBias, light.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, light.omniAtlasViewport, shadowEdgePixels, dir);
#elif defined(CLUSTER_SHADOW_TYPE_PCF3)
let shadow: f32 = getShadowOmniClusteredPCF3(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);
#elif defined(CLUSTER_SHADOW_TYPE_PCF5)
let shadow: f32 = getShadowOmniClusteredPCF5(shadowAtlasTexture, shadowAtlasTextureSampler, shadowParams, light.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: f32,
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 clusterLightData: ClusterLightData;
decodeClusterLightCore(&clusterLightData, 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.clusterSkip > 0.5) {
return;
}
let cellCoords: vec3f = floor((vPositionW - uniform.clusterBoundsMin) * uniform.clusterCellsCountByBoundsSize);
if (!(any(cellCoords < vec3f(0.0)) || any(cellCoords >= uniform.clusterCellsMax))) {
let cellIndex: f32 = dot(uniform.clusterCellsDot, cellCoords);
let clusterV: f32 = floor(cellIndex * uniform.clusterTextureSize.y);
let clusterU: f32 = cellIndex - (clusterV * uniform.clusterTextureSize.x);
for (var lightCellIndex: i32 = 0; lightCellIndex < uniform.clusterMaxCells; lightCellIndex = lightCellIndex + 1) {
let lightIndexPacked: f32 = textureLoad(clusterWorldTexture, vec2<i32>(i32(clusterU) + lightCellIndex, i32(clusterV)), 0).r;
if (lightIndexPacked <= 0.0) {
break;
}
evaluateClusterLight(
lightIndexPacked * 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
);
}
}
}`;
export { clusteredLightPS as default };