mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-06-21 18:17:03 +08:00
* feat(rendering): add basic diffuse and ambient lighting to block rendering * feat(world): add day/night cycle with server tick system * feat(renderer): make ambient strength adjustable via dev panel * fix(game_time): use unsigned tick type and enforce positive tick speed * feat(renderer): add shadow mapping with PCF soft shadows Introduce shadow mapping using a dedicated depth framebuffer and shader. The block fragment shader now performs percentage-closer filtering (PCF) with Poisson disk sampling and random rotation for soft shadows. The vertex shader outputs light-space coordinates. A new depth shader pair handles rendering from the light's perspective, discarding transparent fragments. The renderer sets up the light projection based on the camera position and sun direction, and applies the shadow factor to diffuse lighting. Day/night cycle can now be toggled off in the world server thread. * perf(shadow): increase depth map resolution and refine PCF sampling * feat(dev-panel): add tick freeze toggle and fix TickType sign Add checkbox to freeze tick advancement in dev panel. Change TickType from unsigned long long to signed long long to prevent underflow. * chore(world): add missing <numbers> include * feat(renderer): add runtime shader and shadow mode controls Introduce user-controllable shader on/off, shadow mode (rotated Poisson disk, 3x3 grid, or off), light cull face, and discard transparent in depth pass. Expose all settings in new dev panel "shader" tab. Move ambient strength slider to the new tab. * fix(texture): set texture wrap mode to clamp to edge * feat(renderer): smooth shadow sun direction transitions using quantized directions and slerp * feat(renderer): add PCSS shadow mode with configurable samples and softness Implement Percentage Closer Soft Shadows (PCSS) as shadow mode 3. Add a FindBlocker function and three Poisson disk arrays (8, 16, 32 samples). Expose new uniforms (lightSizeUV, minRadius, maxRadius, samples) and provide slider/combo controls in the dev panel for sample count, light size, and penumbra radius limits. * feat(renderer): add roughness-based specular lighting to blocks * feat(renderer): compute ambient and sunlight colors based on sun height * feat(renderer): add moonlight and smooth day/night light blending * refactor(renderer): extract day/night calculation and add billboard shaders Add a new ParallelLight struct to encapsulate lighting parameters. Move day-night cycle computation from render_world() to a new day_night_calculation() method. Update sky shaders to use procedural colors based on sun position. Add separate billboard shaders for sun/moon. * feat(sky): add animated clouds to sky shader with speed control * feat(renderer): add configurable cloud thresholds and white mix
330 lines
8.8 KiB
GLSL
330 lines
8.8 KiB
GLSL
#version 460
|
|
|
|
in vec2 tc;
|
|
in vec3 normal;
|
|
in vec3 vert_pos;
|
|
in vec4 FragPosLightSpace;
|
|
in float roughness;
|
|
flat in int tex_layer;
|
|
out vec4 color;
|
|
layout (binding = 0) uniform sampler2D shadowMap;
|
|
layout (binding = 1) uniform sampler2DArray samp;
|
|
|
|
uniform float ambientStrength;
|
|
uniform vec3 sunlightColor;
|
|
uniform vec3 ambientColor;
|
|
uniform vec3 sunlightDir;
|
|
uniform vec3 cameraPos;
|
|
uniform bool shader_on;
|
|
uniform int shadowMode;
|
|
uniform float specularStrength;
|
|
uniform float lightSizeUV;
|
|
uniform float minRadius;
|
|
uniform float maxRadius;
|
|
const vec2 poissonDisk32[32] = vec2[](
|
|
vec2(-0.975402, -0.071138),
|
|
vec2(-0.920347, -0.411420),
|
|
vec2(-0.883908, 0.217872),
|
|
vec2(-0.815442, -0.879125),
|
|
vec2(-0.775043, 0.543896),
|
|
vec2(-0.698126, -0.227570),
|
|
vec2(-0.682433, 0.801894),
|
|
vec2(-0.563905, 0.021517),
|
|
vec2(-0.443233, -0.975116),
|
|
vec2(-0.412231, 0.361307),
|
|
vec2(-0.264969, -0.418930),
|
|
vec2(-0.241888, 0.997065),
|
|
vec2(-0.094184, -0.929389),
|
|
vec2(-0.019101, 0.680997),
|
|
vec2( 0.143832, -0.141008),
|
|
vec2( 0.199841, 0.786414),
|
|
|
|
vec2( 0.344959, 0.293878),
|
|
vec2( 0.443233, -0.475115),
|
|
vec2( 0.537430, -0.473734),
|
|
vec2( 0.589349, 0.569135),
|
|
vec2( 0.674281, -0.178897),
|
|
vec2( 0.791975, 0.190902),
|
|
vec2( 0.815442, 0.879125),
|
|
vec2( 0.896420, -0.613392),
|
|
vec2( 0.945586, -0.768907),
|
|
vec2( 0.974844, 0.756484),
|
|
vec2(-0.814100, 0.914376),
|
|
vec2(-0.382775, 0.276768),
|
|
vec2(-0.915886, 0.457714),
|
|
vec2( 0.537800, 0.912200),
|
|
vec2(-0.620000, -0.650000),
|
|
vec2( 0.120000, -0.780000)
|
|
);
|
|
|
|
const vec2 poissonDisk16[16] = vec2[](
|
|
vec2(-0.94201624, -0.39906216), vec2(0.94558609, -0.76890725),
|
|
vec2(-0.09418410, -0.92938870), vec2(0.34495938, 0.29387760),
|
|
vec2(-0.91588581, 0.45771432), vec2(-0.81544232, -0.87912464),
|
|
vec2(-0.38277543, 0.27676845), vec2(0.97484398, 0.75648379),
|
|
vec2(0.44323325, -0.97511554), vec2(0.53742981, -0.47373420),
|
|
vec2(-0.26496911, -0.41893023), vec2(0.79197514, 0.19090188),
|
|
vec2(-0.24188840, 0.99706507), vec2(-0.81409955, 0.91437590),
|
|
vec2(0.19984126, 0.78641367), vec2(0.14383161, -0.14100790)
|
|
);
|
|
const vec2 poissonDisk8[8] = vec2[](
|
|
vec2( 0.1440, 0.7659), vec2(-0.5761, 0.4479),
|
|
vec2(-0.3220, -0.6058), vec2( 0.5693, -0.4048),
|
|
vec2(-0.1276, 0.1657), vec2(-0.0649, -0.0165),
|
|
vec2( 0.2773, -0.0305), vec2(-0.1134, -0.2122)
|
|
);
|
|
uniform int samples;
|
|
float random(vec3 seed) {
|
|
return fract(sin(dot(seed, vec3(12.9898,78.233,45.5432))) * 43758.5453);
|
|
}
|
|
|
|
float FindBlocker(vec2 uv,
|
|
float zReceiver,
|
|
vec2 texelSize,
|
|
float bias,
|
|
float lightSizeUV)
|
|
{
|
|
float avgDepth = 0.0;
|
|
int blockers = 0;
|
|
|
|
float searchRadius = lightSizeUV * 0.5;
|
|
|
|
for(int i = 0; i < samples; i++)
|
|
{
|
|
vec2 offset;
|
|
if (samples == 32) {
|
|
offset =
|
|
poissonDisk32[i]
|
|
* searchRadius
|
|
* texelSize;
|
|
} else if (samples == 16) {
|
|
offset =
|
|
poissonDisk16[i]
|
|
* searchRadius
|
|
* texelSize;
|
|
} else if (samples == 8) {
|
|
offset =
|
|
poissonDisk8[i]
|
|
* searchRadius
|
|
* texelSize;
|
|
} else {
|
|
offset =
|
|
poissonDisk32[i]
|
|
* searchRadius
|
|
* texelSize;
|
|
}
|
|
float depth =
|
|
texture(shadowMap, uv + offset).r;
|
|
|
|
if(depth < zReceiver - bias)
|
|
{
|
|
avgDepth += depth;
|
|
blockers++;
|
|
}
|
|
}
|
|
|
|
if(blockers == 0)
|
|
return -1.0;
|
|
|
|
return avgDepth / blockers;
|
|
}
|
|
|
|
float ShadowCalculation(vec4 fragPosLightSpace, vec3 norm, vec3 lightDir)
|
|
{
|
|
|
|
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
|
|
|
|
projCoords = projCoords * 0.5 + 0.5;
|
|
if (projCoords.x < 0.0 || projCoords.x > 1.0 ||
|
|
projCoords.y < 0.0 || projCoords.y > 1.0 ||
|
|
projCoords.z < 0.0 || projCoords.z > 1.0) {
|
|
return 0.0;
|
|
}
|
|
float currentDepth = projCoords.z;
|
|
vec2 texelSize = 1.0 / vec2(textureSize(shadowMap, 0));
|
|
float shadow = 0.0;
|
|
|
|
float bias =
|
|
clamp(
|
|
0.001 * (1.0 - dot(norm, lightDir)),
|
|
0.0003,
|
|
0.003
|
|
);
|
|
|
|
if (shadowMode == 0) {
|
|
vec3 seed = vert_pos * 37.0 + sin(vert_pos * 91.7) * 13.0;
|
|
float angle = random(seed) * 6.2831853;; // 2*PI
|
|
float s = sin(angle), c = cos(angle);
|
|
mat2 rot = mat2(c, -s, s, c);
|
|
//float radius = 0.7;
|
|
float radius = mix(1.0, 4.0, currentDepth);
|
|
|
|
for (int i = 0; i < samples; ++i) {
|
|
vec2 offset;
|
|
if (samples == 32) {
|
|
offset = rot * poissonDisk32[i] * radius * texelSize;
|
|
} else if (samples == 16) {
|
|
offset = rot * poissonDisk16[i] * radius * texelSize;
|
|
} else if (samples == 8) {
|
|
offset = rot * poissonDisk8[i] * radius * texelSize;
|
|
} else {
|
|
offset = rot * poissonDisk32[i] * radius * texelSize;
|
|
}
|
|
float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
|
|
shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0);
|
|
}
|
|
shadow /= float(samples);
|
|
} else if (shadowMode == 1) {
|
|
for (int x = -1; x <= 1; ++x) {
|
|
for (int y = -1; y <= 1; ++y) {
|
|
vec2 offset = vec2(x, y) * texelSize;
|
|
float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
|
|
shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0);
|
|
}
|
|
}
|
|
shadow /= 9.0;
|
|
} else if (shadowMode == 2) {
|
|
// pcf off
|
|
float pcfDepth =
|
|
texture(shadowMap, projCoords.xy).r;
|
|
|
|
shadow =
|
|
currentDepth - bias > pcfDepth
|
|
? 1.0
|
|
: 0.0;
|
|
} else if (shadowMode == 3) {
|
|
float avgBlockerDepth =
|
|
FindBlocker(
|
|
projCoords.xy,
|
|
currentDepth,
|
|
texelSize,
|
|
bias,
|
|
lightSizeUV
|
|
);
|
|
|
|
if(avgBlockerDepth < 0.0)
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
vec3 seed = vert_pos * 37.0 + sin(vert_pos * 91.7) * 13.0;
|
|
float angle = random(seed) * 6.2831853;; // 2*PI
|
|
float s = sin(angle), c = cos(angle);
|
|
mat2 rot = mat2(c, -s, s, c);
|
|
/*
|
|
float penumbraRatio = (currentDepth - avgBlockerDepth);
|
|
float radius = clamp(
|
|
penumbraRatio * lightSizeUV,
|
|
minRadius,
|
|
maxRadius
|
|
);
|
|
*/
|
|
float radius =
|
|
mix(
|
|
minRadius,
|
|
maxRadius,
|
|
smoothstep(
|
|
0.0,
|
|
0.05,
|
|
currentDepth - avgBlockerDepth
|
|
)
|
|
);
|
|
for (int i = 0; i < samples; ++i) {
|
|
vec2 offset;
|
|
if (samples == 32) {
|
|
offset = rot * poissonDisk32[i] * radius * texelSize;
|
|
} else if (samples == 16) {
|
|
offset = rot * poissonDisk16[i] * radius * texelSize;
|
|
} else if (samples == 8) {
|
|
offset = rot * poissonDisk8[i] * radius * texelSize;
|
|
} else {
|
|
offset = rot * poissonDisk32[i] * radius * texelSize;
|
|
}
|
|
float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
|
|
shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0);
|
|
}
|
|
shadow /= float(samples);
|
|
|
|
} else {
|
|
float pcfDepth =
|
|
texture(shadowMap, projCoords.xy).r;
|
|
|
|
shadow =
|
|
currentDepth - bias > pcfDepth
|
|
? 1.0
|
|
: 0.0;
|
|
}
|
|
|
|
|
|
return shadow;
|
|
}
|
|
|
|
|
|
void main(void) {
|
|
vec4 objectColor = texture(samp, vec3(tc, tex_layer));
|
|
|
|
if (objectColor.a < 0.8) {
|
|
discard;
|
|
}
|
|
if (!shader_on) {
|
|
color = objectColor;
|
|
return;
|
|
}
|
|
|
|
|
|
vec3 lightDir = normalize(-sunlightDir);
|
|
|
|
vec3 norm = normalize(normal);
|
|
|
|
vec3 V =
|
|
normalize(cameraPos - vert_pos);
|
|
|
|
vec3 H =
|
|
normalize(lightDir + V);
|
|
|
|
vec3 ambient = ambientStrength * ambientColor;
|
|
|
|
float diff = max(dot(norm, lightDir), 0.0);
|
|
|
|
vec3 diffuse = diff * sunlightColor;
|
|
|
|
float r =
|
|
clamp(roughness, 0.0, 1.0);
|
|
|
|
float shininess =
|
|
mix(
|
|
512.0,
|
|
4.0,
|
|
r
|
|
);
|
|
float ks =
|
|
mix(
|
|
0.8,
|
|
0.02,
|
|
r
|
|
);
|
|
|
|
float spec = 0.0;
|
|
|
|
if(diff > 0.0)
|
|
{
|
|
spec =
|
|
ks *
|
|
pow(
|
|
max(dot(norm,H),0.0),
|
|
shininess
|
|
);
|
|
}
|
|
|
|
|
|
|
|
|
|
vec3 specular = spec * sunlightColor * specularStrength;
|
|
|
|
float shadow = ShadowCalculation(FragPosLightSpace, norm, lightDir);
|
|
|
|
color = vec4((ambient + (1.0 - shadow) * (diffuse)) * objectColor.rgb + (1.0-shadow) * specular, objectColor.a);
|
|
|
|
//color = varyingColor;
|
|
}
|