@animech-public/playcanvas
Version:
PlayCanvas WebGL game engine
540 lines (531 loc) • 21.8 kB
JavaScript
var clusteredLightPS = `
uniform highp sampler2D clusterWorldTexture;
uniform highp sampler2D lightsTexture8;
uniform highp sampler2D lightsTextureFloat;
uniform sampler2DShadow shadowAtlasTexture;
uniform sampler2D shadowAtlasTexture;
uniform sampler2D cookieAtlasTexture;
uniform int clusterMaxCells;
uniform float clusterMaxCells;
uniform vec4 lightsTextureInvSize;
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;
struct ClusterLightData {
vec3 halfWidth;
float lightType;
vec3 halfHeight;
int lightIndex;
float lightV;
vec3 position;
float shape;
vec3 direction;
float falloffMode;
vec3 color;
float shadowIntensity;
vec3 omniAtlasViewport;
float range;
vec4 cookieChannelMask;
float shadowBias;
float shadowNormalBias;
float innerConeAngleCos;
float outerConeAngleCos;
float cookie;
float cookieRgb;
float cookieIntensity;
float mask;
};
mat4 lightProjectionMatrix;
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)
);
}
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);
}
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);
}
void decodeClusterLightCore(inout ClusterLightData clusterLightData, float lightIndex) {
clusterLightData.lightIndex = int(lightIndex);
clusterLightData.lightV = (lightIndex + 0.5) * lightsTextureInvSize.w;
vec4 lightInfo = sampleLightsTexture8(clusterLightData, CLUSTER_TEXTURE_8_FLAGS);
clusterLightData.lightType = lightInfo.x;
clusterLightData.shape = lightInfo.y;
clusterLightData.falloffMode = lightInfo.z;
clusterLightData.shadowIntensity = lightInfo.w;
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;
clusterLightData.cookie = colorB.z;
clusterLightData.mask = colorB.w;
vec4 lightPosRange = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_POSITION_RANGE);
clusterLightData.position = lightPosRange.xyz;
clusterLightData.range = lightPosRange.w;
vec4 lightDir_Unused = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_SPOT_DIRECTION);
clusterLightData.direction = lightDir_Unused.xyz;
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;
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;
}
void decodeClusterLightSpot(inout ClusterLightData clusterLightData) {
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) {
clusterLightData.omniAtlasViewport = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_PROJ_MAT_0).xyz;
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));
}
void decodeClusterLightAreaData(inout ClusterLightData clusterLightData) {
clusterLightData.halfWidth = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_AREA_DATA_WIDTH).xyz;
clusterLightData.halfHeight = sampleLightTextureF(clusterLightData, CLUSTER_TEXTURE_F_AREA_DATA_HEIGHT).xyz;
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));
}
void decodeClusterLightProjectionMatrixData(inout ClusterLightData clusterLightData) {
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);
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));
lightProjectionMatrix = mat4(m0, m1, m2, m3);
}
void decodeClusterLightShadowData(inout ClusterLightData clusterLightData) {
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,
vec3 clearcoatReflectionDir,
float gloss,
vec3 specularity,
vec3 geometricNormal,
mat3 tbn,
vec3 iridescenceFresnel,
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;
getLightDirPoint(light.position);
if (isClusteredLightArea(light)) {
decodeClusterLightAreaData(light);
if (isClusteredLightRect(light)) {
calcRectLightValues(light.position, light.halfWidth, light.halfHeight);
} else if (isClusteredLightDisk(light)) {
calcDiskLightValues(light.position, light.halfWidth, light.halfHeight);
} else {
calcSphereLightValues(light.position, light.halfWidth, light.halfHeight);
}
falloffAttenuation = getFalloffWindow(light.range, dLightDirW);
} else
{
if (isClusteredLightFalloffLinear(light))
falloffAttenuation = getFalloffLinear(light.range, dLightDirW);
else
falloffAttenuation = getFalloffInvSquared(light.range, dLightDirW);
}
if (falloffAttenuation > 0.00001) {
if (isClusteredLightArea(light)) {
if (isClusteredLightRect(light)) {
diffuseAttenuation = getRectLightDiffuse(worldNormal, viewDir, dLightDirW, dLightDirNormW) * 16.0;
} else if (isClusteredLightDisk(light)) {
diffuseAttenuation = getDiskLightDiffuse(worldNormal, viewDir, dLightDirW, dLightDirNormW) * 16.0;
} else {
diffuseAttenuation = getSphereLightDiffuse(worldNormal, viewDir, dLightDirW, dLightDirNormW) * 16.0;
}
} else
{
falloffAttenuation *= getLightDiffuse(worldNormal, viewDir, dLightDirW, dLightDirNormW);
}
if (isClusteredLightSpot(light)) {
decodeClusterLightSpot(light);
falloffAttenuation *= getSpotEffect(light.direction, light.innerConeAngleCos, light.outerConeAngleCos, dLightDirNormW);
}
if (falloffAttenuation > 0.00001) {
if (isClusteredLightCastShadow(light) || isClusteredLightCookie(light)) {
if (isClusteredLightSpot(light)) {
decodeClusterLightProjectionMatrixData(light);
} else {
decodeClusterLightOmniAtlasViewport(light);
}
float shadowTextureResolution = shadowAtlasParams.x;
float shadowEdgePixels = shadowAtlasParams.y;
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);
}
}
if (isClusteredLightCastShadow(light)) {
decodeClusterLightShadowData(light);
vec4 shadowParams = vec4(shadowTextureResolution, light.shadowNormalBias, light.shadowBias, 1.0 / light.range);
if (isClusteredLightSpot(light)) {
getShadowCoordPerspZbufferNormalOffset(lightProjectionMatrix, shadowParams, geometricNormal);
float shadow = getShadowSpotClusteredPCF1(SHADOWMAP_PASS(shadowAtlasTexture), dShadowCoord, shadowParams);
float shadow = getShadowSpotClusteredPCF3(SHADOWMAP_PASS(shadowAtlasTexture), dShadowCoord, shadowParams);
float shadow = getShadowSpotClusteredPCF5(SHADOWMAP_PASS(shadowAtlasTexture), dShadowCoord, shadowParams);
float shadow = getShadowSpotClusteredPCSS(SHADOWMAP_PASS(shadowAtlasTexture), dShadowCoord, shadowParams);
falloffAttenuation *= mix(1.0, shadow, light.shadowIntensity);
} else {
vec3 dir = normalOffsetPointShadow(shadowParams, dLightPosW, dLightDirW, dLightDirNormW, geometricNormal);
float shadow = getShadowOmniClusteredPCF1(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);
float shadow = getShadowOmniClusteredPCF3(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);
float shadow = getShadowOmniClusteredPCF5(SHADOWMAP_PASS(shadowAtlasTexture), shadowParams, light.omniAtlasViewport, shadowEdgePixels, dir);
falloffAttenuation *= mix(1.0, shadow, light.shadowIntensity);
}
}
}
}
if (isClusteredLightArea(light)) {
{
vec3 areaDiffuse = (diffuseAttenuation * falloffAttenuation) * light.color * cookieAttenuation;
areaDiffuse = mix(areaDiffuse, vec3(0), dLTCSpecFres);
dDiffuseLight += areaDiffuse;
}
float areaLightSpecular;
if (isClusteredLightRect(light)) {
areaLightSpecular = getRectLightSpecular(worldNormal, viewDir);
} else if (isClusteredLightDisk(light)) {
areaLightSpecular = getDiskLightSpecular(worldNormal, viewDir);
} else {
areaLightSpecular = getSphereLightSpecular(worldNormal, viewDir);
}
dSpecularLight += dLTCSpecFres * areaLightSpecular * falloffAttenuation * light.color * cookieAttenuation;
float areaLightSpecularCC;
if (isClusteredLightRect(light)) {
areaLightSpecularCC = getRectLightSpecular(clearcoat_worldNormal, viewDir);
} else if (isClusteredLightDisk(light)) {
areaLightSpecularCC = getDiskLightSpecular(clearcoat_worldNormal, viewDir);
} else {
areaLightSpecularCC = getSphereLightSpecular(clearcoat_worldNormal, viewDir);
}
ccSpecularLight += ccLTCSpecFres * areaLightSpecularCC * falloffAttenuation * light.color * cookieAttenuation;
} else
{
{
vec3 punctualDiffuse = falloffAttenuation * light.color * cookieAttenuation;
punctualDiffuse = mix(punctualDiffuse, vec3(0), specularity);
dDiffuseLight += punctualDiffuse;
}
vec3 halfDir = normalize(-dLightDirNormW + viewDir);
dSpecularLight +=
getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, dLightDirNormW, gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation *
getFresnel(
dot(viewDir, halfDir),
gloss,
specularity
, iridescenceFresnel,
iridescence_intensity
);
dSpecularLight += getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, dLightDirNormW, gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * specularity;
ccSpecularLight += getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, dLightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * getFresnelCC(dot(viewDir, halfDir));
ccSpecularLight += getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, dLightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation;
sSpecularLight += getLightSpecularSheen(halfDir, worldNormal, viewDir, dLightDirNormW, sheen_gloss) * falloffAttenuation * light.color * cookieAttenuation;
}
}
dAtten = falloffAttenuation;
dAttenD = diffuseAttenuation;
dAtten3 = cookieAttenuation;
}
void evaluateClusterLight(
float lightIndex,
vec3 worldNormal,
vec3 viewDir,
vec3 reflectionDir,
vec3 clearcoatReflectionDir,
float gloss,
vec3 specularity,
vec3 geometricNormal,
mat3 tbn,
vec3 iridescenceFresnel,
vec3 clearcoat_worldNormal,
float clearcoat_gloss,
float sheen_gloss,
float iridescence_intensity
) {
ClusterLightData clusterLightData;
decodeClusterLightCore(clusterLightData, lightIndex);
if (acceptLightMask(clusterLightData))
evaluateLight(
clusterLightData,
worldNormal,
viewDir,
reflectionDir,
clearcoatReflectionDir,
gloss,
specularity,
geometricNormal,
tbn,
iridescenceFresnel,
clearcoat_worldNormal,
clearcoat_gloss,
sheen_gloss,
iridescence_intensity
);
}
void addClusteredLights(
vec3 worldNormal,
vec3 viewDir,
vec3 reflectionDir,
vec3 clearcoatReflectionDir,
float gloss,
vec3 specularity,
vec3 geometricNormal,
mat3 tbn,
vec3 iridescenceFresnel,
vec3 clearcoat_worldNormal,
float clearcoat_gloss,
float sheen_gloss,
float iridescence_intensity
) {
if (clusterSkip > 0.5)
return;
vec3 cellCoords = floor((vPositionW - clusterBoundsMin) * clusterCellsCountByBoundsSize);
if (!(any(lessThan(cellCoords, vec3(0.0))) || any(greaterThanEqual(cellCoords, clusterCellsMax)))) {
float cellIndex = dot(clusterCellsDot, cellCoords);
float clusterV = floor(cellIndex * clusterTextureSize.y);
float clusterU = cellIndex - (clusterV * clusterTextureSize.x);
for (int lightCellIndex = 0; lightCellIndex < clusterMaxCells; lightCellIndex++) {
float lightIndex = texelFetch(clusterWorldTexture, ivec2(int(clusterU) + lightCellIndex, clusterV), 0).x;
if (lightIndex <= 0.0)
return;
evaluateClusterLight(
lightIndex * 255.0,
worldNormal,
viewDir,
reflectionDir,
clearcoatReflectionDir,
gloss,
specularity,
geometricNormal,
tbn,
iridescenceFresnel,
clearcoat_worldNormal,
clearcoat_gloss,
sheen_gloss,
iridescence_intensity
);
}
clusterV = (clusterV + 0.5) * clusterTextureSize.z;
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,
clearcoatReflectionDir,
gloss,
specularity,
geometricNormal,
tbn,
iridescenceFresnel,
clearcoat_worldNormal,
clearcoat_gloss,
sheen_gloss,
iridescence_intensity
);
if (lightCellIndex >= clusterMaxCells) {
break;
}
}
}
}
`;
export { clusteredLightPS as default };