mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-09 02:07:04 +08:00
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.
This commit is contained in:
@@ -15,17 +15,117 @@ uniform vec3 sunlightDir;
|
|||||||
uniform bool shader_on;
|
uniform bool shader_on;
|
||||||
uniform int shadowMode;
|
uniform int shadowMode;
|
||||||
|
|
||||||
const vec2 poissonDisk[8] = vec2[](
|
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.1440, 0.7659), vec2(-0.5761, 0.4479),
|
||||||
vec2(-0.3220, -0.6058), vec2( 0.5693, -0.4048),
|
vec2(-0.3220, -0.6058), vec2( 0.5693, -0.4048),
|
||||||
vec2(-0.1276, 0.1657), vec2(-0.0649, -0.0165),
|
vec2(-0.1276, 0.1657), vec2(-0.0649, -0.0165),
|
||||||
vec2( 0.2773, -0.0305), vec2(-0.1134, -0.2122)
|
vec2( 0.2773, -0.0305), vec2(-0.1134, -0.2122)
|
||||||
);
|
);
|
||||||
|
uniform int samples;
|
||||||
float random(vec3 seed) {
|
float random(vec3 seed) {
|
||||||
return fract(sin(dot(seed, vec3(12.9898,78.233,45.5432))) * 43758.5453);
|
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)
|
float ShadowCalculation(vec4 fragPosLightSpace, vec3 norm, vec3 lightDir)
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -40,10 +140,12 @@ float ShadowCalculation(vec4 fragPosLightSpace, vec3 norm, vec3 lightDir)
|
|||||||
float currentDepth = projCoords.z;
|
float currentDepth = projCoords.z;
|
||||||
vec2 texelSize = 1.0 / vec2(textureSize(shadowMap, 0));
|
vec2 texelSize = 1.0 / vec2(textureSize(shadowMap, 0));
|
||||||
float shadow = 0.0;
|
float shadow = 0.0;
|
||||||
|
|
||||||
float bias =
|
float bias =
|
||||||
max(
|
clamp(
|
||||||
|
0.001 * (1.0 - dot(norm, lightDir)),
|
||||||
0.0003,
|
0.0003,
|
||||||
0.001 * (1.0 - dot(norm, lightDir))
|
0.003
|
||||||
);
|
);
|
||||||
|
|
||||||
if (shadowMode == 0) {
|
if (shadowMode == 0) {
|
||||||
@@ -51,13 +153,20 @@ float ShadowCalculation(vec4 fragPosLightSpace, vec3 norm, vec3 lightDir)
|
|||||||
float angle = random(seed) * 6.2831853;; // 2*PI
|
float angle = random(seed) * 6.2831853;; // 2*PI
|
||||||
float s = sin(angle), c = cos(angle);
|
float s = sin(angle), c = cos(angle);
|
||||||
mat2 rot = mat2(c, -s, s, c);
|
mat2 rot = mat2(c, -s, s, c);
|
||||||
|
//float radius = 0.7;
|
||||||
float radius = 0.7;
|
float radius = mix(1.0, 4.0, currentDepth);
|
||||||
|
|
||||||
|
|
||||||
const int samples = 8;
|
|
||||||
for (int i = 0; i < samples; ++i) {
|
for (int i = 0; i < samples; ++i) {
|
||||||
vec2 offset = rot * poissonDisk[i] * radius * texelSize;
|
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;
|
float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
|
||||||
shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0);
|
shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0);
|
||||||
}
|
}
|
||||||
@@ -80,6 +189,59 @@ float ShadowCalculation(vec4 fragPosLightSpace, vec3 norm, vec3 lightDir)
|
|||||||
currentDepth - bias > pcfDepth
|
currentDepth - bias > pcfDepth
|
||||||
? 1.0
|
? 1.0
|
||||||
: 0.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 {
|
} else {
|
||||||
float pcfDepth =
|
float pcfDepth =
|
||||||
texture(shadowMap, projCoords.xy).r;
|
texture(shadowMap, projCoords.xy).r;
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ private:
|
|||||||
int m_pre_set_day_tick = 0;
|
int m_pre_set_day_tick = 0;
|
||||||
int m_pre_set_tick_speed = 1;
|
int m_pre_set_tick_speed = 1;
|
||||||
bool m_tick_frezze = false;
|
bool m_tick_frezze = false;
|
||||||
|
int m_samples_idx = 1;
|
||||||
void show_about_table_bar();
|
void show_about_table_bar();
|
||||||
void show_biome_table_bar();
|
void show_biome_table_bar();
|
||||||
void show_time_table_bar();
|
void show_time_table_bar();
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ public:
|
|||||||
bool& shader_on();
|
bool& shader_on();
|
||||||
int& shadow_mode();
|
int& shadow_mode();
|
||||||
int& light_cull_face();
|
int& light_cull_face();
|
||||||
|
int& light_size_uv();
|
||||||
|
float& min_radius();
|
||||||
|
float& max_radius();
|
||||||
|
int& samples();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr glm::vec3 SUNLIGHT_COLOR{1.0f, 1.0f, 1.0f};
|
static constexpr glm::vec3 SUNLIGHT_COLOR{1.0f, 1.0f, 1.0f};
|
||||||
@@ -99,7 +103,11 @@ private:
|
|||||||
float m_blend_t = 1.0f;
|
float m_blend_t = 1.0f;
|
||||||
bool m_blend_initialized = false;
|
bool m_blend_initialized = false;
|
||||||
static constexpr float BLEND_DURATION = 0.15f;
|
static constexpr float BLEND_DURATION = 0.15f;
|
||||||
|
int m_light_size_uv = 20;
|
||||||
|
|
||||||
|
float m_min_radius = 2.0f;
|
||||||
|
float m_max_radius = 20.0f;
|
||||||
|
int m_samples = 16;
|
||||||
/*
|
/*
|
||||||
0 - quad vao
|
0 - quad vao
|
||||||
1 - sky vao
|
1 - sky vao
|
||||||
|
|||||||
@@ -611,9 +611,10 @@ void DevPanel::show_items_tab_item() {
|
|||||||
|
|
||||||
void DevPanel::show_shader_tab_item() {
|
void DevPanel::show_shader_tab_item() {
|
||||||
|
|
||||||
static const char* shader_mode[] = {"Rotated Poisson Disk PCF",
|
static const char* shader_mode[] = {
|
||||||
"3x3 Square Grid PCF", "PCF off"};
|
"Rotated Poisson Disk PCF", "3x3 Square Grid PCF", "PCF off", "PCSS"};
|
||||||
static const char* cull_face_mode[] = {"Front", "Back"};
|
static const char* cull_face_mode[] = {"Front", "Back"};
|
||||||
|
static const char* samples[] = {"8", "16", "32"};
|
||||||
if (ImGui::BeginTabItem("shader")) {
|
if (ImGui::BeginTabItem("shader")) {
|
||||||
ImGui::Checkbox("Shader", &m_app.renderer().shader_on());
|
ImGui::Checkbox("Shader", &m_app.renderer().shader_on());
|
||||||
if (ImGui::SliderFloat("AmbientStrength",
|
if (ImGui::SliderFloat("AmbientStrength",
|
||||||
@@ -626,6 +627,25 @@ void DevPanel::show_shader_tab_item() {
|
|||||||
IM_ARRAYSIZE(shader_mode));
|
IM_ARRAYSIZE(shader_mode));
|
||||||
ImGui::Combo("LightCullFaceMode", &m_app.renderer().light_cull_face(),
|
ImGui::Combo("LightCullFaceMode", &m_app.renderer().light_cull_face(),
|
||||||
cull_face_mode, IM_ARRAYSIZE(cull_face_mode));
|
cull_face_mode, IM_ARRAYSIZE(cull_face_mode));
|
||||||
|
if (ImGui::Combo("samples", &m_samples_idx, samples,
|
||||||
|
IM_ARRAYSIZE(samples))) {
|
||||||
|
if (m_samples_idx == 0) {
|
||||||
|
m_app.renderer().samples() = 8;
|
||||||
|
} else if (m_samples_idx == 1) {
|
||||||
|
m_app.renderer().samples() = 16;
|
||||||
|
} else if (m_samples_idx == 2) {
|
||||||
|
m_app.renderer().samples() = 32;
|
||||||
|
} else {
|
||||||
|
Logger::warn("Samples Index {} is invaild", m_samples_idx);
|
||||||
|
m_app.renderer().samples() = 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::SliderInt("LightSizeUV", &m_app.renderer().light_size_uv(), 0,
|
||||||
|
800);
|
||||||
|
ImGui::SliderFloat("MinRaduis", &m_app.renderer().min_radius(), 0.0f,
|
||||||
|
20.0f);
|
||||||
|
ImGui::SliderFloat("MaxRadius", &m_app.renderer().max_radius(), 0.0f,
|
||||||
|
100.0f);
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -505,6 +505,7 @@ void Renderer::render_world() {
|
|||||||
glm::mat4 light_space_matrix;
|
glm::mat4 light_space_matrix;
|
||||||
auto& m_render_snapshots = m_world.render_snapshots();
|
auto& m_render_snapshots = m_world.render_snapshots();
|
||||||
auto& camera_pos = m_camera.get_camera_pos();
|
auto& camera_pos = m_camera.get_camera_pos();
|
||||||
|
float texels_per_unit = 0.0f;
|
||||||
if (m_shader_on) {
|
if (m_shader_on) {
|
||||||
const auto& depth_shader = get_shader("depth_shader");
|
const auto& depth_shader = get_shader("depth_shader");
|
||||||
depth_shader.use();
|
depth_shader.use();
|
||||||
@@ -524,7 +525,7 @@ void Renderer::render_world() {
|
|||||||
: glm::vec3(0, 1, 0);
|
: glm::vec3(0, 1, 0);
|
||||||
|
|
||||||
glm::mat4 light_basis = glm::lookAt(glm::vec3(0.0f), shadow_sundir, up);
|
glm::mat4 light_basis = glm::lookAt(glm::vec3(0.0f), shadow_sundir, up);
|
||||||
float texels_per_unit = DEPTH_MAP_SIZE / (half_extent * 2.0f);
|
texels_per_unit = DEPTH_MAP_SIZE / (half_extent * 2.0f);
|
||||||
glm::vec3 ls_center = glm::vec3(light_basis * glm::vec4(center, 1.0f));
|
glm::vec3 ls_center = glm::vec3(light_basis * glm::vec4(center, 1.0f));
|
||||||
ls_center.x =
|
ls_center.x =
|
||||||
std::round(ls_center.x * texels_per_unit) / texels_per_unit;
|
std::round(ls_center.x * texels_per_unit) / texels_per_unit;
|
||||||
@@ -633,6 +634,12 @@ void Renderer::render_world() {
|
|||||||
glm::value_ptr(light_dir_view));
|
glm::value_ptr(light_dir_view));
|
||||||
glUniform1i(normal_block_shader.loc("shadowMode"), m_shadow_mode);
|
glUniform1i(normal_block_shader.loc("shadowMode"), m_shadow_mode);
|
||||||
glUniform1i(normal_block_shader.loc("shader_on"), m_shader_on);
|
glUniform1i(normal_block_shader.loc("shader_on"), m_shader_on);
|
||||||
|
glUniform1f(normal_block_shader.loc("texelsPerUnit"), texels_per_unit);
|
||||||
|
glUniform1f(normal_block_shader.loc("lightSizeUV"),
|
||||||
|
static_cast<GLfloat>(m_light_size_uv));
|
||||||
|
glUniform1f(normal_block_shader.loc("minRadius"), m_min_radius);
|
||||||
|
glUniform1f(normal_block_shader.loc("maxRadius"), m_max_radius);
|
||||||
|
glUniform1i(normal_block_shader.loc("samples"), m_samples);
|
||||||
m_mvp_mat = m_p_mat * m_mv_mat;
|
m_mvp_mat = m_p_mat * m_mv_mat;
|
||||||
|
|
||||||
auto& m_planes = m_world.planes();
|
auto& m_planes = m_world.planes();
|
||||||
@@ -814,4 +821,8 @@ bool& Renderer::discard_transparent() { return m_discard_tranparent; }
|
|||||||
bool& Renderer::shader_on() { return m_shader_on; }
|
bool& Renderer::shader_on() { return m_shader_on; }
|
||||||
int& Renderer::shadow_mode() { return m_shadow_mode; }
|
int& Renderer::shadow_mode() { return m_shadow_mode; }
|
||||||
int& Renderer::light_cull_face() { return m_light_cull_face; }
|
int& Renderer::light_cull_face() { return m_light_cull_face; }
|
||||||
|
int& Renderer::light_size_uv() { return m_light_size_uv; }
|
||||||
|
float& Renderer::min_radius() { return m_min_radius; }
|
||||||
|
float& Renderer::max_radius() { return m_max_radius; }
|
||||||
|
int& Renderer::samples() { return m_samples; }
|
||||||
} // namespace Cubed
|
} // namespace Cubed
|
||||||
Reference in New Issue
Block a user