Add the custom renderCsm fragment shader
This commit is contained in:
1 parent
1aa4517282
commit
3d80ba088b
1 file changed
+730
@@ -0,0 +1,730 @@
|
||||
#version 120
|
||||
#extension GL_EXT_texture_array : require
|
||||
#extension GL_EXT_texture_array : enable
|
||||
|
||||
// =============================================================================
|
||||
// renderCsm_frag.glsl — Blockland high-quality / CSM fragment shader
|
||||
// Originals: _backup_defaults/
|
||||
//
|
||||
// What this file does
|
||||
// Lighting, fog, point lights, and cascaded shadow maps for bricks.
|
||||
// Shadows: 8-tap Vogel near, 4-tap mid, 1-tap far. Particles: 1-tap.
|
||||
// brickTOP: hide the overlay and light a five-face bevel in the shader.
|
||||
// Ground_Plate is not this program (baked plate.png instead).
|
||||
// Interior lamps: wrap + longer falloff so light crawls out of doorways.
|
||||
// Outdoor shade crush (bevelAmb) only runs on stud bevels, not walls.
|
||||
//
|
||||
// How to reload
|
||||
// Options → shader quality (or whatever forces a shader recompile).
|
||||
//
|
||||
// Do not
|
||||
// Use fwidth() on the shadow compare value (draws cascade lines).
|
||||
// Snap shadow UVs to texel centers (moiré on studded plates).
|
||||
// Compress albedo*light together (washes out brickTOP studs).
|
||||
// =============================================================================
|
||||
|
||||
// --- Sun terminator (Blockland stock) ---------------------------------------
|
||||
// Softens the 0.04–0.10 NdotL band so faces don't pop from lit to shadow color.
|
||||
const float NDOTL_TRANSITION_DENOM = 0.06f; // 0.1 - 0.04
|
||||
|
||||
// --- Shadow filter averages ---------------------------------------------------
|
||||
const float INV_VOGEL_SAMPLES = 0.125f; // 1/8 taps
|
||||
const float INV_VOGEL4_SAMPLES = 0.25f; // 1/4 taps
|
||||
|
||||
// --- Diffuse wrap ------------------------------------------------------------
|
||||
// wrapLit = NdotL * SCALE + BIAS. Makes studs/cylinders a bit rounder.
|
||||
// Higher BIAS = more light wraps onto the dark side.
|
||||
const float WRAP_SCALE = 0.88f;
|
||||
const float WRAP_BIAS = 0.12f;
|
||||
|
||||
// --- Shadows / ambient -------------------------------------------------------
|
||||
// How much sky ambient still reaches a fully shadowed brick (0 = pitch black
|
||||
// shade, 1 = no shadow darkening on ambient).
|
||||
const float SHADOW_AMBIENT_FILL = 0.42f;
|
||||
|
||||
// --- Highlight shoulder (stops white tiles going solid white) ---------------
|
||||
// Below KNEE, color is unchanged. Above it, extra brightness rolls off.
|
||||
// Lower KNEE / higher COMPRESS = less blowout, dimmer lamps.
|
||||
const float HIGHLIGHT_KNEE = 0.78f;
|
||||
const float HIGHLIGHT_COMPRESS = 2.2f;
|
||||
|
||||
// --- Specular ----------------------------------------------------------------
|
||||
// SUN: tight NdotH^16 spark from the directional light.
|
||||
const float SUN_SPEC_GAIN = 1.15f;
|
||||
// POINT: lamp glint brightness. This is the main "brighter lamps" knob.
|
||||
const float POINT_SPEC_GAIN = 0.45f;
|
||||
// Physical bulb size vs Blockland's attenuation radius (which is a volume,
|
||||
// not the fixture). Higher = wider, softer lamp highlights.
|
||||
const float POINT_SPEC_BULB = 0.25f;
|
||||
// Extra spec on bevels vs flat (from |tangent.xy|).
|
||||
const float BEVEL_SPEC_BOOST = 0.25f;
|
||||
// Point-light window. 0 = cubic (tight, stock), 1 = quadratic (longer spill).
|
||||
const float POINT_FALLOFF_GAIN = 0.40f;
|
||||
const float POINT_FALLOFF_SOFT = 0.35f;
|
||||
// Sky-bevel dim in shade. Only applied on studs (see applyLighting).
|
||||
const float BEVEL_AMB_MIN = 0.58f;
|
||||
|
||||
// --- brickTOP bevel (procedural, tops only) --------------------------------
|
||||
// Matches the five-region plate map. STUD_FLAT is |p| with p = uv*2-1
|
||||
// (0.5 = inner square UV 0.25–0.75). STUD_SLOPE is bevel tilt.
|
||||
const float STUD_FLAT = 0.50f;
|
||||
const float STUD_SLOPE = 0.55f;
|
||||
|
||||
// --- Cascade blend / PCF radius ---------------------------------------------
|
||||
// Extra UV radius per cascade index. Keep low so splits don't jump softness.
|
||||
#define CASCADE_SOFTNESS_SCALE 0.25f
|
||||
// Start mixing the NEXT cascade this early (0.12 = last 88% of the split).
|
||||
// Lower = wider blend, more dual-map cost.
|
||||
#define CASCADE_BLEND_START 0.12f
|
||||
#define CASCADE_BLEND_RANGE (1.0f - CASCADE_BLEND_START)
|
||||
// Atlas-edge fade: mix next map when UV is this close to 0 or 1.
|
||||
#define CASCADE_UV_MARGIN 0.02f
|
||||
#define CASCADE_UV_FADE 0.20f
|
||||
// Vogel disk size in texels. Higher = softer 2D PCF.
|
||||
#define VOGEL_RADIUS_SCALE 2.75f
|
||||
|
||||
// --- Distance fade toward unshadowed "minimum" look --------------------------
|
||||
// Stud self-shadow aliases into rings at range. Fade CSM off between these
|
||||
// view distances (world units).
|
||||
#define SHADOW_FADE_START 16.0f
|
||||
#define SHADOW_FADE_END 88.0f
|
||||
|
||||
// =============================================================================
|
||||
// Engine varyings / uniforms (set by Blockland, do not rename)
|
||||
// =============================================================================
|
||||
|
||||
varying vec4 vPos; // view-space position (cascade pick uses vPos.y)
|
||||
varying vec3 worldNormal;
|
||||
varying vec3 worldPos;
|
||||
|
||||
uniform vec4 dirLightDir; // sun direction
|
||||
uniform vec4 dirLightColor;
|
||||
uniform vec4 dirLightAmbient;
|
||||
uniform vec4 dirShadowColor; // color in full shadow
|
||||
|
||||
uniform vec3 camPos;
|
||||
uniform mat4 obj2World;
|
||||
uniform mat4 world2Cam;
|
||||
|
||||
uniform int isParticle;
|
||||
uniform int doColorMultiply;
|
||||
uniform int glow; // emissive / light brick
|
||||
|
||||
uniform sampler2DArray stex; // shadow atlas (cascades in Z)
|
||||
uniform sampler2D tex; // brick overlay (studs, prints)
|
||||
|
||||
varying vec2 texCoord;
|
||||
|
||||
// Per-pixel surface state (filled in calculateSurface)
|
||||
vec4 viewDelta; // xyz = view dir (surface → camera), w = view distance
|
||||
float specular; // sun Blinn term (NdotH^16)
|
||||
float specFactor; // 1 + bevel boost (not overlay brightness)
|
||||
float NdotL; // clamped sun lambert on surfNormal
|
||||
float NdotLRaw; // signed, used for wrap
|
||||
float geoNdotL; // geometric face vs sun (shadows / slope bias)
|
||||
float fresnel; // Schlick, F0=0.04
|
||||
float distLod; // 0 near → 1 at SHADOW_FADE_END
|
||||
vec3 surfNormal; // worldNormal, or five-face bevel on brickTOP
|
||||
|
||||
// Cotangent frame from screen-space derivatives (no vertex tangents).
|
||||
// Degenerate UV (stretched brickTOP on a side/sliver) must not perturb —
|
||||
// inversesqrt of a tiny TBN is the salt-and-pepper sparkle on those faces.
|
||||
vec3 perturbNormal(vec3 N, vec3 wp, vec2 uv, vec3 tN)
|
||||
{
|
||||
vec3 dp1 = dFdx(wp);
|
||||
vec3 dp2 = dFdy(wp);
|
||||
vec2 duv1 = dFdx(uv);
|
||||
vec2 duv2 = dFdy(uv);
|
||||
float uvLen = abs(duv1.x) + abs(duv1.y) + abs(duv2.x) + abs(duv2.y);
|
||||
if(uvLen < 1.0e-8f)
|
||||
return N;
|
||||
vec3 dp2perp = cross(dp2, N);
|
||||
vec3 dp1perp = cross(N, dp1);
|
||||
vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
|
||||
vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;
|
||||
float m = max(dot(T, T), dot(B, B));
|
||||
if(m < 1.0e-10f)
|
||||
return N;
|
||||
float invmax = inversesqrt(m);
|
||||
vec3 P = T * (tN.x * invmax) + B * (tN.y * invmax) + N * tN.z;
|
||||
float p2 = dot(P, P);
|
||||
if(p2 < 1.0e-10f)
|
||||
return N;
|
||||
return P * inversesqrt(p2);
|
||||
}
|
||||
|
||||
// Five-region plate: flat top + N/E/S/W bevels.
|
||||
vec3 plateTangent(vec2 uv)
|
||||
{
|
||||
vec2 p = fract(uv) * 2.0f - 1.0f;
|
||||
vec2 a = abs(p);
|
||||
if(a.x <= STUD_FLAT && a.y <= STUD_FLAT)
|
||||
return vec3(0.0f, 0.0f, 1.0f);
|
||||
if(a.x > a.y)
|
||||
return normalize(vec3(sign(p.x) * STUD_SLOPE, 0.0f, 1.0f));
|
||||
return normalize(vec3(0.0f, sign(p.y) * STUD_SLOPE, 1.0f));
|
||||
}
|
||||
|
||||
float plateSpecFactor(vec3 tN)
|
||||
{
|
||||
float bevel = length(tN.xy);
|
||||
return mix(0.10f, 1.0f, smoothstep(0.02f, STUD_SLOPE * 0.7f, bevel))
|
||||
+ bevel * BEVEL_SPEC_BOOST;
|
||||
}
|
||||
|
||||
bool colorNear(vec3 c, vec3 t, float e)
|
||||
{
|
||||
vec3 d = abs(c - t);
|
||||
return d.r < e && d.g < e && d.b < e;
|
||||
}
|
||||
|
||||
vec3 probeTex(vec2 uv)
|
||||
{
|
||||
return texture2D(tex, uv, -16.0f).rgb;
|
||||
}
|
||||
|
||||
// Stock brickTOP.png. Sides have a black center; prints will not match.
|
||||
bool isStockBrickTop()
|
||||
{
|
||||
vec3 c00 = probeTex(vec2(0.01f, 0.01f));
|
||||
vec3 c99 = probeTex(vec2(0.99f, 0.99f));
|
||||
vec3 c50 = probeTex(vec2(0.5f, 0.01f));
|
||||
vec3 c59 = probeTex(vec2(0.5f, 0.99f));
|
||||
vec3 c55 = probeTex(vec2(0.5f, 0.5f));
|
||||
return c00.r > 0.92f && c00.g > 0.92f && c00.b > 0.92f
|
||||
&& c99.r < 0.08f && c99.g < 0.08f && c99.b < 0.08f
|
||||
&& c50.r > 0.92f && c50.g > 0.92f && c50.b > 0.92f
|
||||
&& c59.r < 0.08f && c59.g < 0.08f && c59.b < 0.08f
|
||||
&& c55.r > 0.92f && c55.g > 0.92f && c55.b > 0.92f;
|
||||
}
|
||||
|
||||
// Five-color nmap still used as brickTOP (not the baked grayscale plate.png).
|
||||
bool isFiveRegionTopMap()
|
||||
{
|
||||
if(worldNormal.z < 0.72f)
|
||||
return false;
|
||||
vec3 s = texture2D(tex, texCoord, -16.0f).rgb;
|
||||
return colorNear(s, vec3(0.451f, 0.451f, 0.906f), 0.09f)
|
||||
|| colorNear(s, vec3(0.275f, 0.451f, 0.875f), 0.09f)
|
||||
|| colorNear(s, vec3(0.620f, 0.451f, 0.875f), 0.09f)
|
||||
|| colorNear(s, vec3(0.451f, 0.620f, 0.875f), 0.09f)
|
||||
|| colorNear(s, vec3(0.451f, 0.275f, 0.875f), 0.09f);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Brick color, view, sun spec, fresnel, distance fade.
|
||||
// -----------------------------------------------------------------------------
|
||||
void calculateSurface(vec4 color, inout vec4 albedo)
|
||||
{
|
||||
viewDelta.xyz = worldPos - camPos;
|
||||
viewDelta.w = length(viewDelta.xyz);
|
||||
viewDelta.xyz = -viewDelta.xyz / viewDelta.w; // toward camera
|
||||
|
||||
distLod = smoothstep(SHADOW_FADE_START, SHADOW_FADE_END, viewDelta.w);
|
||||
|
||||
vec4 texAlbedo = texture2D(tex, texCoord);
|
||||
surfNormal = worldNormal;
|
||||
|
||||
geoNdotL = max(dot(worldNormal, dirLightDir.xyz), 0.0f);
|
||||
|
||||
vec3 tN = vec3(0.0f, 0.0f, 1.0f);
|
||||
specFactor = 1.0f;
|
||||
|
||||
// Only real tops. isStockBrickTop() matches the bound texture, so a brick
|
||||
// that puts brickTOP on a side would otherwise get plateTangent + TBN
|
||||
// sparkle on that face.
|
||||
bool topStud = (worldNormal.z > 0.72f)
|
||||
&& (isStockBrickTop() || isFiveRegionTopMap());
|
||||
if(topStud)
|
||||
{
|
||||
// Hide brickTOP overlay — its baked dark rim fights the moving spec.
|
||||
albedo.rgb = color.rgb;
|
||||
tN = plateTangent(texCoord);
|
||||
specFactor = plateSpecFactor(tN);
|
||||
surfNormal = perturbNormal(worldNormal, worldPos, texCoord, tN);
|
||||
}
|
||||
else
|
||||
albedo.rgb = mix(color.rgb, texAlbedo.rgb, texAlbedo.a);
|
||||
|
||||
if(doColorMultiply == 1)
|
||||
albedo *= gl_Color;
|
||||
|
||||
albedo.a = color.a;
|
||||
|
||||
NdotLRaw = dot(surfNormal, dirLightDir.xyz);
|
||||
NdotL = max(NdotLRaw, 0.0f);
|
||||
|
||||
// Schlick fresnel: more on glancing edges.
|
||||
float NdotV = max(dot(surfNormal, viewDelta.xyz), 0.0f);
|
||||
float f = 1.0f - NdotV;
|
||||
float f2 = f * f;
|
||||
fresnel = 0.04f + 0.96f * f2 * f2 * f;
|
||||
|
||||
// Sun spec only if the sun actually hits this (perturbed) face.
|
||||
if(NdotL > 0.04f)
|
||||
{
|
||||
vec3 halfVec = normalize(dirLightDir.xyz + viewDelta.xyz);
|
||||
float NdotH = max(dot(surfNormal, halfVec), 0.0f);
|
||||
float d2 = NdotH * NdotH;
|
||||
float d4 = d2 * d2;
|
||||
float d8 = d4 * d4;
|
||||
specular = d8 * d8 * SUN_SPEC_GAIN * specFactor; // NdotH^16
|
||||
}
|
||||
else
|
||||
specular = 0.0f;
|
||||
}
|
||||
|
||||
// Fog (per-vertex fogCoords from the vertex shader).
|
||||
uniform vec4 fogBaseColor;
|
||||
uniform vec4 fogConsts;
|
||||
uniform sampler2D fogTex;
|
||||
varying vec2 fogCoords;
|
||||
|
||||
void applyFog(inout vec4 albedo)
|
||||
{
|
||||
vec4 fogColor = texture2D(fogTex, fogCoords) * fogBaseColor;
|
||||
// Sine remap of fog alpha (stock Blockland).
|
||||
float fogFactor = 0.5f + 0.5f * sin(fogColor.a * 3.141592654f - 1.570796327f);
|
||||
albedo = mix(albedo, fogColor, fogFactor);
|
||||
}
|
||||
|
||||
// Soft-clip values above HIGHLIGHT_KNEE so stacked lamps don't go solid white.
|
||||
vec3 compressHighlights(vec3 c)
|
||||
{
|
||||
vec3 over = max(c - vec3(HIGHLIGHT_KNEE), vec3(0.0f));
|
||||
return min(c, vec3(HIGHLIGHT_KNEE)) + over / (vec3(1.0f) + over * HIGHLIGHT_COMPRESS);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Cascaded shadow maps
|
||||
// far_d = view-depth of each split (x,y,z)
|
||||
// texSize = atlas size (x) and 1/size (y)
|
||||
// zScale = per-cascade depth scale for bias
|
||||
// =============================================================================
|
||||
uniform vec4 far_d;
|
||||
uniform vec2 texSize;
|
||||
uniform vec4 zScale;
|
||||
uniform int shadowSplitCount;
|
||||
|
||||
// 0 at start of split, 1 at the far plane of this cascade.
|
||||
float cascadeDepthBlend(float viewY, float split)
|
||||
{
|
||||
float t = clamp((viewY - split * CASCADE_BLEND_START) / (split * CASCADE_BLEND_RANGE), 0.0f, 1.0f);
|
||||
return smoothstep(0.0f, 1.0f, t);
|
||||
}
|
||||
|
||||
// 1 when UV is at the atlas border (cascade frustum edge), 0 in the middle.
|
||||
float cascadeUvBlend(vec2 uv)
|
||||
{
|
||||
float edgeDist = min(min(uv.x, 1.0f - uv.x), min(uv.y, 1.0f - uv.y));
|
||||
return 1.0f - smoothstep(CASCADE_UV_MARGIN, CASCADE_UV_FADE, edgeDist);
|
||||
}
|
||||
|
||||
// Project this pixel into cascade A (and B if blending).
|
||||
// offset = view-space jitter for the ultra 3D grid; (0,0,0) for a normal sample.
|
||||
// .xy = atlas UV, .z = cascade layer, .w = depth compare value.
|
||||
void calculateShadowCoords(vec3 offset, inout vec4 shadow_coordA, inout vec4 shadow_coordB, out float blend, bool allowBlend)
|
||||
{
|
||||
vec4 rPos = vPos + vec4(offset, 0.0f);
|
||||
int index = 3; // farthest cascade unless a nearer split matches
|
||||
|
||||
// Depth bias: more on grazing receivers, less on cascade 0 (contact at feet).
|
||||
float slope = clamp(1.0f - geoNdotL, 0.0f, 1.0f);
|
||||
float fudgeKey = 0.008f + 0.012f * slope * slope;
|
||||
if(rPos.y < far_d.x)
|
||||
fudgeKey *= 0.75f;
|
||||
|
||||
float fudgeFactorA = 4.0f * fudgeKey / zScale.w;
|
||||
float fudgeFactorB = 4.0f * fudgeKey / zScale.w;
|
||||
float depthBlend = 0.0f;
|
||||
|
||||
if(rPos.y < far_d.x)
|
||||
{
|
||||
index = 0;
|
||||
if(allowBlend && shadowSplitCount > 1)
|
||||
depthBlend = cascadeDepthBlend(rPos.y, far_d.x);
|
||||
fudgeFactorA = fudgeKey / zScale.x;
|
||||
fudgeFactorB = 2.0f * fudgeKey / zScale.y;
|
||||
}
|
||||
else if(rPos.y < far_d.y)
|
||||
{
|
||||
index = 1;
|
||||
if(allowBlend && shadowSplitCount > 2)
|
||||
depthBlend = cascadeDepthBlend(rPos.y, far_d.y);
|
||||
fudgeFactorA = 2.0f * fudgeKey / zScale.y;
|
||||
fudgeFactorB = 3.0f * fudgeKey / zScale.z;
|
||||
}
|
||||
else if(rPos.y < far_d.z)
|
||||
{
|
||||
index = 2;
|
||||
if(allowBlend && shadowSplitCount > 3)
|
||||
depthBlend = cascadeDepthBlend(rPos.y, far_d.z);
|
||||
fudgeFactorA = 3.0f * fudgeKey / zScale.z;
|
||||
fudgeFactorB = 4.0f * fudgeKey / zScale.w;
|
||||
}
|
||||
|
||||
shadow_coordA = gl_TextureMatrix[index]*rPos;
|
||||
shadow_coordA.w = shadow_coordA.z - fudgeFactorA;
|
||||
shadow_coordA.z = float(index);
|
||||
|
||||
blend = depthBlend;
|
||||
if(allowBlend && index < 3 && (index + 1) < shadowSplitCount)
|
||||
{
|
||||
shadow_coordB = gl_TextureMatrix[index + 1]*rPos;
|
||||
shadow_coordB.w = shadow_coordB.z - fudgeFactorB;
|
||||
shadow_coordB.z = float(index + 1);
|
||||
blend = max(depthBlend, cascadeUvBlend(shadow_coordA.xy));
|
||||
}
|
||||
}
|
||||
|
||||
// 1 = lit (receiver closer than stored depth), 0 = shadowed.
|
||||
float sampleShadowTap(vec2 uv, float layer, float compare)
|
||||
{
|
||||
return step(0.0f, texture2DArray(stex, vec3(uv, layer)).x - compare - 1.0e-6f);
|
||||
}
|
||||
|
||||
// 8-tap disk PCF (near cascades).
|
||||
float vogelSample(vec4 shadow_coord, float radius)
|
||||
{
|
||||
float compare = shadow_coord.w;
|
||||
float layer = shadow_coord.z;
|
||||
vec2 uv = shadow_coord.xy;
|
||||
|
||||
float hits = 0.0f;
|
||||
hits += sampleShadowTap(uv + vec2( 0.250000f, 0.000000f) * radius, layer, compare);
|
||||
hits += sampleShadowTap(uv + vec2(-0.319292f, 0.292492f) * radius, layer, compare);
|
||||
hits += sampleShadowTap(uv + vec2( 0.048890f, -0.556882f) * radius, layer, compare);
|
||||
hits += sampleShadowTap(uv + vec2( 0.401722f, 0.525731f) * radius, layer, compare);
|
||||
hits += sampleShadowTap(uv + vec2(-0.707107f, 0.000000f) * radius, layer, compare);
|
||||
hits += sampleShadowTap(uv + vec2( 0.157013f, 0.693780f) * radius, layer, compare);
|
||||
hits += sampleShadowTap(uv + vec2( 0.453990f, -0.632281f) * radius, layer, compare);
|
||||
hits += sampleShadowTap(uv + vec2(-0.340561f, -0.748511f) * radius, layer, compare);
|
||||
return hits * INV_VOGEL_SAMPLES;
|
||||
}
|
||||
|
||||
float vogelSample4(vec4 shadow_coord, float radius)
|
||||
{
|
||||
float compare = shadow_coord.w;
|
||||
float layer = shadow_coord.z;
|
||||
vec2 uv = shadow_coord.xy;
|
||||
|
||||
float hits = 0.0f;
|
||||
hits += sampleShadowTap(uv + vec2( 0.250000f, 0.000000f) * radius, layer, compare);
|
||||
hits += sampleShadowTap(uv + vec2(-0.707107f, 0.000000f) * radius, layer, compare);
|
||||
hits += sampleShadowTap(uv + vec2( 0.048890f, -0.556882f) * radius, layer, compare);
|
||||
hits += sampleShadowTap(uv + vec2( 0.401722f, 0.525731f) * radius, layer, compare);
|
||||
return hits * INV_VOGEL4_SAMPLES;
|
||||
}
|
||||
|
||||
float basicShadow(vec4 shadow_coord)
|
||||
{
|
||||
return sampleShadowTap(shadow_coord.xy, shadow_coord.z, shadow_coord.w);
|
||||
}
|
||||
|
||||
// 8-tap near, 4-tap mid, 1-tap far.
|
||||
float filterCascade(vec4 shadow_coord, float radius, int idx)
|
||||
{
|
||||
if(idx <= 1)
|
||||
return vogelSample(shadow_coord, radius);
|
||||
if(idx == 2)
|
||||
return vogelSample4(shadow_coord, radius);
|
||||
return basicShadow(shadow_coord);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Point lights (engine binds up to 8; unused have radius 0)
|
||||
// =============================================================================
|
||||
uniform vec4 pointLightPos0;
|
||||
uniform vec4 pointLightColor0;
|
||||
uniform float pointLightRadius0;
|
||||
|
||||
uniform vec4 pointLightPos1;
|
||||
uniform vec4 pointLightColor1;
|
||||
uniform float pointLightRadius1;
|
||||
|
||||
uniform vec4 pointLightPos2;
|
||||
uniform vec4 pointLightColor2;
|
||||
uniform float pointLightRadius2;
|
||||
|
||||
uniform vec4 pointLightPos3;
|
||||
uniform vec4 pointLightColor3;
|
||||
uniform float pointLightRadius3;
|
||||
|
||||
uniform vec4 pointLightPos4;
|
||||
uniform vec4 pointLightColor4;
|
||||
uniform float pointLightRadius4;
|
||||
|
||||
uniform vec4 pointLightPos5;
|
||||
uniform vec4 pointLightColor5;
|
||||
uniform float pointLightRadius5;
|
||||
|
||||
uniform vec4 pointLightPos6;
|
||||
uniform vec4 pointLightColor6;
|
||||
uniform float pointLightRadius6;
|
||||
|
||||
uniform vec4 pointLightPos7;
|
||||
uniform vec4 pointLightColor7;
|
||||
uniform float pointLightRadius7;
|
||||
|
||||
// 1 at the bulb, 0 at radius. Mix cubic→quadratic so interiors spill farther
|
||||
// without a big brightness jump at the fixture.
|
||||
float pointLightWindow(float lightDist, float radius)
|
||||
{
|
||||
float ratio = max(0.0f, 1.0f - lightDist / radius);
|
||||
float r2 = ratio * ratio;
|
||||
return r2 * mix(ratio, 1.0f, POINT_FALLOFF_SOFT) * POINT_FALLOFF_GAIN;
|
||||
}
|
||||
|
||||
// Lamp specular: tight Blinn on surfNormal. Low exponent used to
|
||||
// flood whole facets; keep it high so only a small glint reads as plastic.
|
||||
float pointBlinnSpec(vec3 lightDir, float lightDist, float radius, float lightDot)
|
||||
{
|
||||
float bulb = max(radius * POINT_SPEC_BULB, 0.08f);
|
||||
float lift = clamp(bulb / max(lightDist, bulb * 0.2f), 0.0f, 0.22f);
|
||||
vec3 L = normalize(lightDir + surfNormal * lift);
|
||||
vec3 halfVec = normalize(L + viewDelta.xyz);
|
||||
float nh = max(dot(surfNormal, halfVec), 0.0f);
|
||||
float angularSize = clamp(bulb / max(lightDist, bulb * 0.2f), 0.0f, 1.0f);
|
||||
float width = smoothstep(0.12f, 0.9f, angularSize);
|
||||
float specExp = mix(36.0f, 14.0f, width);
|
||||
float energy = mix(0.55f, 0.22f, width);
|
||||
float facing = smoothstep(0.12f, 0.42f, lightDot);
|
||||
return pow(nh, specExp) * energy * POINT_SPEC_GAIN * specFactor
|
||||
* (0.80f + 0.20f * fresnel) * facing;
|
||||
}
|
||||
|
||||
void addPointLight(vec3 pos, vec3 color, float radius, inout vec3 diffuse, inout vec3 spec)
|
||||
{
|
||||
vec3 lightDelta = worldPos.xyz - pos;
|
||||
float distSq = dot(lightDelta, lightDelta);
|
||||
float radiusSq = radius * radius;
|
||||
if(distSq >= radiusSq)
|
||||
return;
|
||||
|
||||
float invDist = inversesqrt(distSq);
|
||||
float lightDist = invDist * distSq;
|
||||
vec3 L = -lightDelta * invDist;
|
||||
float nDotL = max(dot(L, surfNormal), 0.0f);
|
||||
float wrapDot = max(dot(L, surfNormal) * WRAP_SCALE + WRAP_BIAS, 0.0f);
|
||||
float ratio = pointLightWindow(lightDist, radius);
|
||||
diffuse += ratio * wrapDot * color;
|
||||
spec += ratio * pointBlinnSpec(L, lightDist, radius, nDotL) * color;
|
||||
}
|
||||
|
||||
vec4 accumulatePointLights(out vec3 pointSpec)
|
||||
{
|
||||
vec3 diffuse = vec3(0.0f);
|
||||
pointSpec = vec3(0.0f);
|
||||
|
||||
addPointLight(pointLightPos0.xyz, pointLightColor0.xyz, pointLightRadius0, diffuse, pointSpec);
|
||||
addPointLight(pointLightPos1.xyz, pointLightColor1.xyz, pointLightRadius1, diffuse, pointSpec);
|
||||
addPointLight(pointLightPos2.xyz, pointLightColor2.xyz, pointLightRadius2, diffuse, pointSpec);
|
||||
addPointLight(pointLightPos3.xyz, pointLightColor3.xyz, pointLightRadius3, diffuse, pointSpec);
|
||||
addPointLight(pointLightPos4.xyz, pointLightColor4.xyz, pointLightRadius4, diffuse, pointSpec);
|
||||
addPointLight(pointLightPos5.xyz, pointLightColor5.xyz, pointLightRadius5, diffuse, pointSpec);
|
||||
addPointLight(pointLightPos6.xyz, pointLightColor6.xyz, pointLightRadius6, diffuse, pointSpec);
|
||||
addPointLight(pointLightPos7.xyz, pointLightColor7.xyz, pointLightRadius7, diffuse, pointSpec);
|
||||
|
||||
return vec4(diffuse, 0.0f);
|
||||
}
|
||||
|
||||
// Particles: lights 0–1 only, no NdotL, no spec (stock).
|
||||
vec4 accumulateParticlePointLights()
|
||||
{
|
||||
vec4 pointLightTotal = vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
vec3 lightDelta = vec3(0.0f, 0.0f, 0.0f);
|
||||
float distSq = 0.0f;
|
||||
float lightDist = 0.0f;
|
||||
|
||||
lightDelta = worldPos.xyz - pointLightPos0.xyz;
|
||||
distSq = dot(lightDelta, lightDelta);
|
||||
float radiusSq0 = pointLightRadius0 * pointLightRadius0;
|
||||
if(distSq < radiusSq0)
|
||||
{
|
||||
float invDist = inversesqrt(distSq);
|
||||
lightDist = invDist * distSq;
|
||||
pointLightTotal.xyz += pointLightWindow(lightDist, pointLightRadius0) * pointLightColor0.xyz;
|
||||
}
|
||||
|
||||
lightDelta = worldPos.xyz - pointLightPos1.xyz;
|
||||
distSq = dot(lightDelta, lightDelta);
|
||||
float radiusSq1 = pointLightRadius1 * pointLightRadius1;
|
||||
if(distSq < radiusSq1)
|
||||
{
|
||||
float invDist = inversesqrt(distSq);
|
||||
lightDist = invDist * distSq;
|
||||
pointLightTotal.xyz += pointLightWindow(lightDist, pointLightRadius1) * pointLightColor1.xyz;
|
||||
}
|
||||
|
||||
return pointLightTotal;
|
||||
}
|
||||
|
||||
// Combine sun, ambient, shadow color, lamps, spec, rim. Compress light, then
|
||||
// multiply albedo (preserves stud contrast).
|
||||
void applyLighting(inout vec4 combinedColor, vec4 albedo, float occlusionFactor)
|
||||
{
|
||||
// Dielectric: most of the glint takes the brick color so brown stays brown.
|
||||
vec3 specularTint = mix(vec3(1.0f), albedo.rgb, 0.72f);
|
||||
vec3 dirLightSpecular = occlusionFactor * specular * 0.5f * dirLightColor.rgb * specularTint;
|
||||
|
||||
// More ambient on faces pointing up (world Z). Fades to even at distance.
|
||||
float skyFactor = mix(surfNormal.z * 0.5f + 0.5f, 0.5f, distLod);
|
||||
vec4 skyAmbient = dirLightAmbient * (0.72f + skyFactor * 0.55f);
|
||||
|
||||
float wrapLit = max(NdotLRaw * WRAP_SCALE + WRAP_BIAS, 0.0f);
|
||||
float ambOcc = mix(SHADOW_AMBIENT_FILL, 1.0f, occlusionFactor);
|
||||
vec4 dirLightDirect = ((wrapLit * dirLightColor) * occlusionFactor) + (skyAmbient * ambOcc) + (dirShadowColor * (1.0f - occlusionFactor));
|
||||
|
||||
vec3 coolShadow = dirShadowColor.rgb * vec3(0.92f, 0.96f, 1.08f);
|
||||
vec3 shadedFill = coolShadow + skyAmbient.rgb * SHADOW_AMBIENT_FILL;
|
||||
|
||||
// Sky-facing bevel in shade: form lighting on studs only. Walls used to
|
||||
// get this too, which crushed indoor corridors to flat black.
|
||||
float studMask = clamp(length(surfNormal - worldNormal) * 5.0f, 0.0f, 1.0f);
|
||||
vec3 skyDir = vec3(0.14f, 0.28f, 0.95f);
|
||||
skyDir = skyDir * inversesqrt(dot(skyDir, skyDir));
|
||||
float skyN = max(dot(surfNormal, skyDir), 0.0f);
|
||||
float bevelAmb = mix(1.0f, mix(BEVEL_AMB_MIN, 1.0f, skyN), studMask);
|
||||
shadedFill *= bevelAmb;
|
||||
dirLightDirect.rgb *= mix(bevelAmb, 1.0f, occlusionFactor);
|
||||
|
||||
// Terminator on the geometric face, not the stud N — otherwise each
|
||||
// pyramid facet dumps to shade as a solid polygon.
|
||||
if(geoNdotL <= 0.04f)
|
||||
{
|
||||
dirLightDirect = vec4(shadedFill, dirShadowColor.a);
|
||||
dirLightSpecular = vec3(0.0f);
|
||||
}
|
||||
else if(geoNdotL <= 0.1f)
|
||||
{
|
||||
float val = (geoNdotL - 0.04f) / NDOTL_TRANSITION_DENOM;
|
||||
dirLightDirect.rgb = (dirLightDirect.rgb * val) + (shadedFill * (1.0f - val));
|
||||
dirLightSpecular *= val;
|
||||
}
|
||||
|
||||
vec3 pointSpec = vec3(0.0f);
|
||||
dirLightDirect.rgb += accumulatePointLights(pointSpec).rgb;
|
||||
vec3 specTotal = dirLightSpecular + pointSpec * specularTint;
|
||||
|
||||
vec3 halfSky = normalize(skyDir + viewDelta.xyz);
|
||||
float ambSpec = pow(max(dot(surfNormal, halfSky), 0.0f), 22.0f)
|
||||
* specFactor * 0.22f * studMask;
|
||||
specTotal += ambSpec * specularTint * (dirLightAmbient.rgb + vec3(0.12f));
|
||||
|
||||
vec3 rimLight = fresnel * dirLightAmbient.rgb * 0.15f * (1.0f - distLod);
|
||||
|
||||
float specIntensity = length(specTotal);
|
||||
float kD = max(1.0f - specIntensity * 0.2f, 0.8f); // leave some diffuse where spec is strong
|
||||
float fakeAO = mix(0.90f + surfNormal.z * 0.10f, 1.0f, distLod);
|
||||
|
||||
dirLightDirect.a *= min(occlusionFactor + 0.75f, 1.0f);
|
||||
|
||||
vec3 light = compressHighlights(dirLightDirect.rgb * kD * fakeAO);
|
||||
combinedColor.rgb = light * albedo.rgb + rimLight + specTotal;
|
||||
combinedColor.a = albedo.a;
|
||||
}
|
||||
|
||||
// Returns 0 (shadowed) … 1 (lit). cheap = particles (1-tap + cascade blend).
|
||||
float shadowCoef(bool cheap)
|
||||
{
|
||||
float blend = 0.0f;
|
||||
vec4 shadow_coordA = vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
vec4 shadow_coordB = vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
calculateShadowCoords(vec3(0.0f), shadow_coordA, shadow_coordB, blend, true);
|
||||
|
||||
float baseRadius = max(texSize.y, 0.5f / 1024.0f) * VOGEL_RADIUS_SCALE;
|
||||
float texel = max(texSize.y, 0.5f / 1024.0f);
|
||||
float slope = max(0.0f, 1.0f - geoNdotL);
|
||||
float slopeBias = texel * 0.15f * slope;
|
||||
shadow_coordA.w -= slopeBias;
|
||||
|
||||
int idx = int(shadow_coordA.z);
|
||||
float radiusNear = baseRadius * (1.0f + float(idx) * CASCADE_SOFTNESS_SCALE);
|
||||
float radiusFar = baseRadius * (1.0f + float(idx + 1) * CASCADE_SOFTNESS_SCALE);
|
||||
float radiusA = mix(radiusNear, radiusFar, blend);
|
||||
|
||||
if(cheap)
|
||||
{
|
||||
float shadowA = basicShadow(shadow_coordA);
|
||||
if(blend > 0.0f)
|
||||
{
|
||||
shadow_coordB.w -= slopeBias;
|
||||
return mix(shadowA, basicShadow(shadow_coordB), blend);
|
||||
}
|
||||
return shadowA;
|
||||
}
|
||||
|
||||
// 8 / 4 / 1 tap by cascade. Blend mixes the next map at splits.
|
||||
float shadowA = filterCascade(shadow_coordA, radiusA, idx);
|
||||
|
||||
if(blend > 0.0f)
|
||||
{
|
||||
shadow_coordB.w -= slopeBias;
|
||||
float shadowB = filterCascade(shadow_coordB, radiusFar, idx + 1);
|
||||
return mix(shadowA, shadowB, blend);
|
||||
}
|
||||
return shadowA;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
// Glow / emitter: skip lighting and shadows.
|
||||
if(glow == 1 || (worldNormal.x + worldNormal.y + worldNormal.z) > 2.0f)
|
||||
{
|
||||
vec4 texAlbedo = texture2D(tex, texCoord);
|
||||
vec4 albedo;
|
||||
albedo.rgb = mix(gl_Color.rgb, texAlbedo.rgb, texAlbedo.a);
|
||||
albedo.a = gl_Color.a;
|
||||
if(doColorMultiply == 1)
|
||||
albedo *= gl_Color;
|
||||
gl_FragColor = vec4(albedo.rgb * 1.5f, albedo.a);
|
||||
return;
|
||||
}
|
||||
|
||||
if(isParticle == 1)
|
||||
{
|
||||
NdotL = max(dot(worldNormal, dirLightDir.xyz), 0.0f);
|
||||
geoNdotL = NdotL;
|
||||
float occlusionFactor = 0.0f;
|
||||
if(NdotL > -0.01f)
|
||||
{
|
||||
if(shadowSplitCount <= 0)
|
||||
occlusionFactor = 1.0f;
|
||||
else
|
||||
occlusionFactor = shadowCoef(true);
|
||||
}
|
||||
|
||||
vec4 texAlbedo = texture2D(tex, texCoord);
|
||||
vec4 dirLightDirect = (dirLightColor * occlusionFactor) + (dirLightAmbient * occlusionFactor) + (dirShadowColor * (1.0f - occlusionFactor));
|
||||
vec4 plt = accumulateParticlePointLights();
|
||||
|
||||
vec4 lightTotal = dirLightDirect + plt;
|
||||
lightTotal.rgb = compressHighlights(lightTotal.rgb);
|
||||
vec4 fragColor = texAlbedo * gl_Color * lightTotal;
|
||||
applyFog(fragColor);
|
||||
fragColor.a = texAlbedo.a * gl_Color.a;
|
||||
gl_FragColor = fragColor;
|
||||
return;
|
||||
}
|
||||
|
||||
vec4 albedo = vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
calculateSurface(gl_Color, albedo);
|
||||
|
||||
float occlusionFactor = 0.0f;
|
||||
if(geoNdotL > -0.01f)
|
||||
{
|
||||
if(shadowSplitCount <= 0 || distLod >= 1.0f)
|
||||
occlusionFactor = 1.0f;
|
||||
else
|
||||
occlusionFactor = shadowCoef(false);
|
||||
}
|
||||
|
||||
occlusionFactor = mix(occlusionFactor, 1.0f, distLod);
|
||||
|
||||
vec4 fragColor = vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
applyLighting(fragColor, albedo, occlusionFactor);
|
||||
applyFog(fragColor);
|
||||
|
||||
gl_FragColor = fragColor;
|
||||
}
|
||||
Reference in new issue
Block a user