playcanvas
Version:
PlayCanvas WebGL game engine
408 lines (401 loc) • 15.3 kB
JavaScript
var clusteredLightPS = `
uniform highp sampler2D clusterWorldTexture;
uniform highp sampler2D lightsTexture;
uniform sampler2DShadow shadowAtlasTexture;
uniform sampler2D cookieAtlasTexture;
uniform int clusterMaxCells;
uniform float clusterSkip;
uniform vec3 clusterCellsCountByBoundsSize;
uniform vec3 clusterTextureSize;
uniform vec3 clusterBoundsMin;
uniform vec3 clusterBoundsDelta;
uniform vec3 clusterCellsDot;
uniform vec3 clusterCellsMax;
uniform vec2 shadowAtlasParams;
struct ClusterLightData {
uint flags;
vec3 halfWidth;
bool isSpot;
vec3 halfHeight;
int lightIndex;
vec3 position;
uint shape;
vec3 direction;
bool falloffModeLinear;
vec3 color;
float shadowIntensity;
vec3 omniAtlasViewport;
float range;
vec4 cookieChannelMask;
float biasesData;
float shadowBias;
float shadowNormalBias;
float anglesData;
float innerConeAngleCos;
float outerConeAngleCos;
float cookieIntensity;
bool isDynamic;
bool isLightmapped;
};
mat4 lightProjectionMatrix;
vec4 sampleLightTextureF(const ClusterLightData clusterLightData, int index) {
return texelFetch(lightsTexture, ivec2(index, clusterLightData.lightIndex), 0);
}
void decodeClusterLightCore(inout ClusterLightData clusterLightData, float lightIndex) {
clusterLightData.lightIndex = int(lightIndex);
vec4 halfData = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_COLOR_ANGLES_BIAS});
clusterLightData.anglesData = halfData.z;
clusterLightData.biasesData = halfData.w;
vec2 colorRG = unpackHalf2x16(floatBitsToUint(halfData.x));
vec2 colorB_ = unpackHalf2x16(floatBitsToUint(halfData.y));
clusterLightData.color = vec3(colorRG, colorB_.x) * {LIGHT_COLOR_DIVIDER};
vec4 lightPosRange = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_POSITION_RANGE});
clusterLightData.position = lightPosRange.xyz;
clusterLightData.range = lightPosRange.w;
vec4 lightDir_Flags = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_DIRECTION_FLAGS});
clusterLightData.direction = lightDir_Flags.xyz;
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;
}
void decodeClusterLightSpot(inout ClusterLightData clusterLightData) {
vec2 angles = unpackHalf2x16(floatBitsToUint(clusterLightData.anglesData));
clusterLightData.innerConeAngleCos = angles.x;
clusterLightData.outerConeAngleCos = angles.y;
}
void decodeClusterLightOmniAtlasViewport(inout ClusterLightData clusterLightData) {
clusterLightData.omniAtlasViewport = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_PROJ_MAT_0}).xyz;
}
void decodeClusterLightAreaData(inout ClusterLightData clusterLightData) {
clusterLightData.halfWidth = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_AREA_DATA_WIDTH}).xyz;
clusterLightData.halfHeight = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_AREA_DATA_HEIGHT}).xyz;
}
void decodeClusterLightProjectionMatrixData(inout ClusterLightData clusterLightData) {
vec4 m0 = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_PROJ_MAT_0});
vec4 m1 = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_PROJ_MAT_1});
vec4 m2 = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_PROJ_MAT_2});
vec4 m3 = sampleLightTextureF(clusterLightData, {CLUSTER_TEXTURE_PROJ_MAT_3});
lightProjectionMatrix = mat4(m0, m1, m2, m3);
}
void decodeClusterLightShadowData(inout ClusterLightData clusterLightData) {
vec2 biases = unpackHalf2x16(floatBitsToUint(clusterLightData.biasesData));
clusterLightData.shadowBias = biases.x;
clusterLightData.shadowNormalBias = biases.y;
}
void decodeClusterLightCookieData(inout ClusterLightData clusterLightData) {
uint cookieFlags = (clusterLightData.flags >> 23u) & 0x0Fu;
clusterLightData.cookieChannelMask = vec4(uvec4(cookieFlags) & uvec4(1u, 2u, 4u, 8u));
clusterLightData.cookieChannelMask = step(1.0, clusterLightData.cookieChannelMask);
}
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;
vec3 lightDirW = evalOmniLight(light.position);
vec3 lightDirNormW = normalize(lightDirW);
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
{
if (light.falloffModeLinear)
falloffAttenuation = getFalloffLinear(light.range, lightDirW);
else
falloffAttenuation = getFalloffInvSquared(light.range, lightDirW);
}
if (falloffAttenuation > 0.00001) {
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
{
falloffAttenuation *= getLightDiffuse(worldNormal, viewDir, lightDirNormW);
}
if (light.isSpot) {
decodeClusterLightSpot(light);
falloffAttenuation *= getSpotEffect(light.direction, light.innerConeAngleCos, light.outerConeAngleCos, lightDirNormW);
}
if (falloffAttenuation > 0.00001) {
if (light.shadowIntensity > 0.0 || light.cookieIntensity > 0.0) {
if (light.isSpot) {
decodeClusterLightProjectionMatrixData(light);
} else {
decodeClusterLightOmniAtlasViewport(light);
}
float shadowTextureResolution = shadowAtlasParams.x;
float shadowEdgePixels = shadowAtlasParams.y;
if (light.cookieIntensity > 0.0) {
decodeClusterLightCookieData(light);
if (light.isSpot) {
cookieAttenuation = getCookie2DClustered(TEXTURE_PASS(cookieAtlasTexture), lightProjectionMatrix, vPositionW, light.cookieIntensity, light.cookieChannelMask);
} else {
cookieAttenuation = getCookieCubeClustered(TEXTURE_PASS(cookieAtlasTexture), lightDirW, light.cookieIntensity, light.cookieChannelMask, shadowTextureResolution, shadowEdgePixels, light.omniAtlasViewport);
}
}
if (light.shadowIntensity > 0.0) {
decodeClusterLightShadowData(light);
vec4 shadowParams = vec4(shadowTextureResolution, light.shadowNormalBias, light.shadowBias, 1.0 / light.range);
if (light.isSpot) {
vec3 shadowCoord = getShadowCoordPerspZbufferNormalOffset(lightProjectionMatrix, shadowParams, geometricNormal);
float shadow = getShadowSpotClusteredPCF1(SHADOWMAP_PASS(shadowAtlasTexture), shadowCoord, shadowParams);
float shadow = getShadowSpotClusteredPCF3(SHADOWMAP_PASS(shadowAtlasTexture), shadowCoord, shadowParams);
float shadow = getShadowSpotClusteredPCF5(SHADOWMAP_PASS(shadowAtlasTexture), shadowCoord, shadowParams);
float shadow = getShadowSpotClusteredPCSS(SHADOWMAP_PASS(shadowAtlasTexture), shadowCoord, shadowParams);
falloffAttenuation *= mix(1.0, shadow, light.shadowIntensity);
} else {
vec3 dir = normalOffsetPointShadow(shadowParams, light.position, lightDirW, lightDirNormW, 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 (light.shape != {LIGHTSHAPE_PUNCTUAL}) {
{
vec3 areaDiffuse = (diffuseAttenuation * falloffAttenuation) * light.color * cookieAttenuation;
areaDiffuse = mix(areaDiffuse, vec3(0), dLTCSpecFres);
dDiffuseLight += areaDiffuse;
}
float areaLightSpecular;
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 += dLTCSpecFres * areaLightSpecular * falloffAttenuation * light.color * cookieAttenuation;
float areaLightSpecularCC;
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 += ccLTCSpecFres * areaLightSpecularCC * falloffAttenuation * light.color * cookieAttenuation;
} else
{
{
vec3 punctualDiffuse = falloffAttenuation * light.color * cookieAttenuation;
punctualDiffuse = mix(punctualDiffuse, vec3(0), specularity);
dDiffuseLight += punctualDiffuse;
}
vec3 halfDir = normalize(-lightDirNormW + viewDir);
dSpecularLight +=
getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, lightDirNormW, gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation *
getFresnel(
dot(viewDir, halfDir),
gloss,
specularity
, iridescenceFresnel,
iridescence_intensity
);
dSpecularLight += getLightSpecular(halfDir, reflectionDir, worldNormal, viewDir, lightDirNormW, gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * specularity;
ccSpecularLight += getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, lightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation * getFresnelCC(dot(viewDir, halfDir));
ccSpecularLight += getLightSpecular(halfDir, clearcoatReflectionDir, clearcoat_worldNormal, viewDir, lightDirNormW, clearcoat_gloss, tbn) * falloffAttenuation * light.color * cookieAttenuation;
sSpecularLight += getLightSpecularSheen(halfDir, worldNormal, viewDir, lightDirNormW, sheen_gloss) * falloffAttenuation * light.color * cookieAttenuation;
}
}
dAtten = falloffAttenuation;
dLightDirNormW = lightDirNormW;
}
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);
bool acceptLightMask = clusterLightData.isDynamic;
bool acceptLightMask = clusterLightData.isLightmapped;
if (acceptLightMask)
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)
break;
evaluateClusterLight(
lightIndex * 255.0,
worldNormal,
viewDir,
reflectionDir,
clearcoatReflectionDir,
gloss,
specularity,
geometricNormal,
tbn,
iridescenceFresnel,
clearcoat_worldNormal,
clearcoat_gloss,
sheen_gloss,
iridescence_intensity
);
}
}
}
`;
export { clusteredLightPS as default };