From 7edd72015c3b9fe35706c3a55a148b2f39d79fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Fri, 30 Aug 2024 03:46:46 +0200 Subject: [PATCH] CSM shadowmapping --- bind/v4k.lua | 22 +- demos/09-shadows.c | 20 +- engine/art/shaderlib/light.glsl | 4 +- engine/art/shaderlib/shadowmap.glsl | 71 ++-- engine/art/shaders/fs_shadow_blur.glsl | 20 +- engine/art/shaders/fs_shadow_vsm.glsl | 16 +- engine/art/shaders/vs_shadow_blur.glsl | 28 +- engine/joint/v4k.h | 437 ++++++++++++++++++++----- engine/split/v4k_render.c | 409 +++++++++++++++++++---- engine/split/v4k_render.h | 25 +- engine/split/v4k_scene.h | 3 + engine/v4k.c | 409 +++++++++++++++++++---- engine/v4k.h | 28 +- 13 files changed, 1196 insertions(+), 296 deletions(-) diff --git a/bind/v4k.lua b/bind/v4k.lua index 601dbc6..e9252d2 100644 --- a/bind/v4k.lua +++ b/bind/v4k.lua @@ -1020,6 +1020,7 @@ typedef struct renderstate_t { unsigned polygon_mode_draw; bool scissor_test_enabled; bool seamless_cubemap; + bool depth_clamp_enabled; } renderstate_t; renderstate_t renderstate(); bool renderstate_compare(const renderstate_t *stateA, const renderstate_t *stateB); @@ -1160,7 +1161,7 @@ typedef struct light_t { unsigned shadow_technique; float shadow_distance; float shadow_bias; - mat44 shadow_matrix; + mat44 shadow_matrix[4]; bool cached; } light_t; light_t light(); @@ -1178,25 +1179,33 @@ typedef struct light_t { typedef struct shadowmap_t { mat44 V; mat44 PV; - int texture_width; + int vsm_texture_width; + int pcf_texture_width; int step; int light_step; + int cascade_index; unsigned shadow_technique; + float cascade_splits[4]; + float cascade_distances[4]; + bool blur_pcf; + float blur_scale; bool skip_render; int lights_pushed; struct { + unsigned shadow_technique; handle fbos[6], texture, depth_texture; - handle fbo_2d, texture_2d, depth_texture_2d; + handle fbo_2d[4], texture_2d[4], depth_texture_2d[4]; + handle blur_fbo_2d, blur_texture_2d; } maps[MAX_LIGHTS]; handle saved_fb; handle saved_pass; int saved_vp[4]; } shadowmap_t; - shadowmap_t shadowmap(int texture_width); + shadowmap_t shadowmap(int vsm_texture_width, int pcf_texture_width); void shadowmap_destroy(shadowmap_t *s); void shadowmap_begin(shadowmap_t *s); bool shadowmap_step(shadowmap_t *s); - void shadowmap_light(shadowmap_t *s, light_t *l); + void shadowmap_light(shadowmap_t *s, light_t *l, float cam_fov, mat44 cam_view); void shadowmap_end(shadowmap_t *s); unsigned shader(const char *vs, const char *fs, const char *attribs, const char *fragcolor, const char *defines); unsigned shader_geom(const char *gs, const char *vs, const char *fs, const char *attribs, const char *fragcolor, const char *defines); @@ -1638,6 +1647,7 @@ typedef struct object_t { aabb bounds; unsigned billboard; bool disable_frustum_check; + bool cast_shadows; handle* old_texture_ids; texture_t* old_textures; float distance; @@ -1663,12 +1673,14 @@ enum SCENE_FLAGS { SCENE_BACKGROUND = 4, SCENE_FOREGROUND = 8, SCENE_UPDATE_SH_COEF = 16, + SCENE_CAST_SHADOWS = 32, }; typedef struct scene_t { object_t* objs; light_t* lights; skybox_t skybox; int u_coefficients_sh; + shadowmap_t shadowmap; } scene_t; scene_t* scene_push(); void scene_pop(); diff --git a/demos/09-shadows.c b/demos/09-shadows.c index 8a009a0..8d690d5 100644 --- a/demos/09-shadows.c +++ b/demos/09-shadows.c @@ -32,21 +32,26 @@ int main(int argc, char** argv) { light_t lit = light(); { lit.type = LIGHT_POINT; lit.cast_shadows = true; - // lit.shadow_distance = 3.0f; + // lit.shadow_distance = 5.0f; + // lit.falloff.linear = 3.0f; } light_t lit2 = light(); { lit2.type = LIGHT_POINT; lit2.cast_shadows = true; lit2.diffuse = vec3(1, 0.7, 0.8); + // lit2.shadow_distance = 5.0f; + // lit2.falloff.linear = 3.0f; } light_t lit3 = light(); { lit3.type = LIGHT_SPOT; lit3.cast_shadows = true; + // lit3.shadow_distance = 5.0f; lit3.diffuse = vec3(1, 0.7, 0.8); } light_t lit4 = light(); { lit4.type = LIGHT_DIRECTIONAL; lit4.cast_shadows = true; + lit4.shadow_distance = 2000.0f; lit4.diffuse = vec3(1, 0.7, 0.8); } @@ -81,7 +86,7 @@ int main(int argc, char** argv) { if( !initialized ) { initialized = 1; sky = skybox(flag("--mie") ? 0 : SKY_DIRS[SKY_DIR], 0); - sm = shadowmap(1024); + sm = shadowmap(512, 2048); mdl = model(OBJ_MDLS[OBJ_MDL], 0); shader_bind(mdl.program); cubemap_sh_shader(&sky.cubemap); @@ -100,7 +105,7 @@ int main(int argc, char** argv) { enum { POINT, SPOT, DIR, ALL }; - static unsigned mode = POINT; + static unsigned mode = DIR; if (input_down(KEY_1)) mode = POINT; if (input_down(KEY_2)) mode = SPOT; @@ -135,6 +140,7 @@ int main(int argc, char** argv) { } if (mode == DIR) { + lights[0].pos = cam.position; lights[0].dir = vec3(1,-1,-1); } @@ -160,7 +166,7 @@ int main(int argc, char** argv) { { for (int i = 0; i < array_count(lights); i++) { while (shadowmap_step(&sm)) { - shadowmap_light(&sm, &lights[i]); + shadowmap_light(&sm, &lights[i], cam.fov, cam.view); model_render(mdl, cam.proj, cam.view, mdl.pivot, 0); } } @@ -171,14 +177,10 @@ int main(int argc, char** argv) { mat44 mvp; multiply44x2(mvp, cam.proj, cam.view); { skybox_render(&sky, cam.proj, cam.view); + shader_bind(mdl.program); - shader_int("u_textured", false); light_update(array_count(lights), lights); - // ddraw_sphere(lights[0].pos, 0.1f); - // ddraw_sphere(lights[1].pos, 0.1f); - // ddraw_flush(); - model_shadow(&mdl, &sm); model_render(mdl, cam.proj, cam.view, mdl.pivot, 0); } diff --git a/engine/art/shaderlib/light.glsl b/engine/art/shaderlib/light.glsl index ff129b3..2f9d36b 100644 --- a/engine/art/shaderlib/light.glsl +++ b/engine/art/shaderlib/light.glsl @@ -5,6 +5,8 @@ uniform int u_num_lights; +#define NUM_SHADOW_CASCADES 4 + struct light_t { int type; vec3 diffuse; @@ -23,7 +25,7 @@ struct light_t { float quadratic; // shadows - mat4 shadow_matrix; + mat4 shadow_matrix[NUM_SHADOW_CASCADES]; }; #define MAX_LIGHTS 16 diff --git a/engine/art/shaderlib/shadowmap.glsl b/engine/art/shaderlib/shadowmap.glsl index 169e56e..4e705c5 100644 --- a/engine/art/shaderlib/shadowmap.glsl +++ b/engine/art/shaderlib/shadowmap.glsl @@ -1,8 +1,10 @@ in vec4 vpeye; in vec4 vneye; uniform bool u_shadow_receiver; +uniform float u_cascade_splits[NUM_SHADOW_CASCADES]; +uniform float u_cascade_distances[NUM_SHADOW_CASCADES]; uniform samplerCube shadowMap[MAX_LIGHTS]; -uniform sampler2D shadowMap2D[MAX_LIGHTS]; +uniform sampler2D shadowMap2D[MAX_LIGHTS * NUM_SHADOW_CASCADES]; //// From http://fabiensanglard.net/shadowmappingVSM/index.php float shadow_vsm(float distance, vec3 dir, int light_index) { @@ -22,44 +24,61 @@ float shadow_vsm(float distance, vec3 dir, int light_index) { float d = distance - moments.x; float p_max = variance / (variance + d*d); - + return p_max; } -float shadow_pcf(vec4 fragPosLightSpace, vec3 lightDir, int light_index) { +float shadow_pcf(float distance, vec3 lightDir, int light_index) { + // Determine which cascade to use + int cascade_index = -1; + for (int i = 0; i < NUM_SHADOW_CASCADES; i++) { + if (distance < u_cascade_distances[i]) { + cascade_index = i; + break; + } + } + if (cascade_index == -1) { + cascade_index = NUM_SHADOW_CASCADES - 1; + } + + vec4 fragPosLightSpace = u_lights[light_index].shadow_matrix[cascade_index] * vec4(v_position_ws, 1.0); + int index = light_index * NUM_SHADOW_CASCADES + cascade_index; + // Perform perspective divide vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; // Transform to [0,1] range projCoords = projCoords * 0.5 + 0.5; - - // Get closest depth value from light's perspective (using [0,1] range fragPosLight as coords) - float closestDepth = texture(shadowMap2D[light_index], projCoords.xy).r; - - // Get depth of current fragment from light's perspective + float currentDepth = projCoords.z; - - // Calculate bias (based on depth map resolution and slope) + + if (currentDepth > 1.0) { + return 1.0; + } + + // Calculate bias vec3 normal = normalize(vneye.xyz); - float bias = max(0.05 * (1.0 - dot(normal, -lightDir)), 0.005); - + float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005); + const float bias_modifier = 0.5; + if (cascade_index == NUM_SHADOW_CASCADES-1) { + bias *= 1 / (u_cascade_distances[NUM_SHADOW_CASCADES-1] * bias_modifier); + } else { + bias *= 1 / (u_cascade_distances[cascade_index] * bias_modifier); + } + // PCF float shadow = 0.0; - vec2 texelSize = 1.0 / textureSize(shadowMap2D[light_index], 0); + vec2 texelSize = 1.0 / textureSize(shadowMap2D[index], 0); for(int x = -1; x <= 1; ++x) { for(int y = -1; y <= 1; ++y) { - float pcfDepth = texture(shadowMap2D[light_index], projCoords.xy + vec2(x, y) * texelSize).r; - shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0; + float pcfDepth = texture(shadowMap2D[index], projCoords.xy + vec2(x, y) * texelSize).r; + shadow += projCoords.z - bias > pcfDepth ? 1.0 : 0.0; } } shadow /= 9.0; - - // Keep the shadow at 0.0 when outside the far_plane region of the light's frustum. - if(projCoords.z >= 0.9) - shadow = 0.0; - + return 1.0 - shadow; } @@ -74,22 +93,14 @@ vec4 shadowmap(in vec4 peye, in vec4 neye) { if (light.type == LIGHT_DIRECTIONAL) { total_casters++; - vec4 frag_pos = light.shadow_matrix * vec4(v_position_ws, 1.0); - factor += shadow_pcf(frag_pos, light.dir, i); - } else if (light.type == LIGHT_POINT) { - total_casters++; - vec3 light_pos = (view * vec4(light.pos, 1.0)).xyz; - vec3 dir = light_pos - fragment; - vec4 sc = inv_view * vec4(dir, 0.0); - factor += shadow_vsm(length(dir), -sc.xyz, i); - } else if (light.type == LIGHT_SPOT) { + factor += shadow_pcf(-peye.z, light.dir, i); + } else if (light.type == LIGHT_POINT || light.type == LIGHT_SPOT) { total_casters++; vec3 light_pos = (view * vec4(light.pos, 1.0)).xyz; vec3 dir = light_pos - fragment; vec4 sc = inv_view * vec4(dir, 0.0); factor += shadow_vsm(length(dir), -sc.xyz, i); } - shadowFactor += factor; } diff --git a/engine/art/shaders/fs_shadow_blur.glsl b/engine/art/shaders/fs_shadow_blur.glsl index 621be61..c817c4f 100644 --- a/engine/art/shaders/fs_shadow_blur.glsl +++ b/engine/art/shaders/fs_shadow_blur.glsl @@ -1,17 +1,17 @@ uniform sampler2D textureSource; uniform vec2 ScaleU; -in vec2 Texcoord; -out vec4 outColor; +in vec2 uv; +out vec4 fragcolor; void main() { vec4 color = vec4(0.0); - color += texture( textureSource, Texcoord.st + vec2( -3.0*ScaleU.x, -3.0*ScaleU.y ) ) * 0.015625; - color += texture( textureSource, Texcoord.st + vec2( -2.0*ScaleU.x, -2.0*ScaleU.y ) )*0.09375; - color += texture( textureSource, Texcoord.st + vec2( -1.0*ScaleU.x, -1.0*ScaleU.y ) )*0.234375; - color += texture( textureSource, Texcoord.st + vec2( 0.0 , 0.0) )*0.3125; - color += texture( textureSource, Texcoord.st + vec2( 1.0*ScaleU.x, 1.0*ScaleU.y ) )*0.234375; - color += texture( textureSource, Texcoord.st + vec2( 2.0*ScaleU.x, 2.0*ScaleU.y ) )*0.09375; - color += texture( textureSource, Texcoord.st + vec2( 3.0*ScaleU.x, -3.0*ScaleU.y ) ) * 0.015625; - outColor = vec4(color.xyz, 1.0); + color += texture( textureSource, uv.st + vec2( -3.0*ScaleU.x, -3.0*ScaleU.y ) ) * 0.015625; + color += texture( textureSource, uv.st + vec2( -2.0*ScaleU.x, -2.0*ScaleU.y ) )*0.09375; + color += texture( textureSource, uv.st + vec2( -1.0*ScaleU.x, -1.0*ScaleU.y ) )*0.234375; + color += texture( textureSource, uv.st + vec2( 0.0 , 0.0) )*0.3125; + color += texture( textureSource, uv.st + vec2( 1.0*ScaleU.x, 1.0*ScaleU.y ) )*0.234375; + color += texture( textureSource, uv.st + vec2( 2.0*ScaleU.x, 2.0*ScaleU.y ) )*0.09375; + color += texture( textureSource, uv.st + vec2( 3.0*ScaleU.x, -3.0*ScaleU.y ) ) * 0.015625; + fragcolor = vec4(color.xyz, 1.0); } \ No newline at end of file diff --git a/engine/art/shaders/fs_shadow_vsm.glsl b/engine/art/shaders/fs_shadow_vsm.glsl index 93cdb41..4092fa0 100644 --- a/engine/art/shaders/fs_shadow_vsm.glsl +++ b/engine/art/shaders/fs_shadow_vsm.glsl @@ -8,15 +8,15 @@ uniform int shadow_technique; void main() { if (shadow_technique == SHADOW_VSM) { - float depth = length(v_position) / 20; + float depth = length(v_position) / 20; - float moment1 = depth; - float moment2 = depth * depth; - - float dx = dFdx(depth); - float dy = dFdy(depth); - moment2 += 0.25*(dx*dx+dy*dy); - fragcolor = vec4( moment1, moment2, 0.0, 1.0); + float moment1 = depth; + float moment2 = depth * depth; + + float dx = dFdx(depth); + float dy = dFdy(depth); + moment2 += 0.25*(dx*dx+dy*dy); + fragcolor = vec4( moment1, moment2, 0.0, 1.0); } else if (shadow_technique == SHADOW_PCF) { fragcolor = vec4(vec3(gl_FragCoord.z), 1.0); diff --git a/engine/art/shaders/vs_shadow_blur.glsl b/engine/art/shaders/vs_shadow_blur.glsl index ca78468..6842dc8 100644 --- a/engine/art/shaders/vs_shadow_blur.glsl +++ b/engine/art/shaders/vs_shadow_blur.glsl @@ -1,8 +1,26 @@ -in vec3 position; -in vec2 texcoord; -out vec2 Texcoord; +out vec2 uv; void main() { - gl_Position = vec4(position, 1.0); - Texcoord = texcoord; + // Calculate vertex position based on gl_VertexID + vec2 positions[6] = vec2[6]( + vec2(-1.0, 1.0), // Top-left + vec2( 1.0, 1.0), // Top-right + vec2( 1.0, -1.0), // Bottom-right + vec2( 1.0, -1.0), // Bottom-right (repeated) + vec2(-1.0, -1.0), // Bottom-left + vec2(-1.0, 1.0) // Top-left (repeated) + ); + + // Calculate UV coordinates based on gl_VertexID + vec2 texCoords[6] = vec2[6]( + vec2(0.0, 1.0), // Top-left + vec2(1.0, 1.0), // Top-right + vec2(1.0, 0.0), // Bottom-right + vec2(1.0, 0.0), // Bottom-right (repeated) + vec2(0.0, 0.0), // Bottom-left + vec2(0.0, 1.0) // Top-left (repeated) + ); + + gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0); + uv = texCoords[gl_VertexID]; } \ No newline at end of file diff --git a/engine/joint/v4k.h b/engine/joint/v4k.h index 99275a5..1ce836a 100644 --- a/engine/joint/v4k.h +++ b/engine/joint/v4k.h @@ -17034,8 +17034,11 @@ typedef struct renderstate_t { // Scissor test bool scissor_test_enabled; - // bool Seamless Cubemap + // Seamless cubemap bool seamless_cubemap; + + // Depth clamp + bool depth_clamp_enabled; } renderstate_t; API renderstate_t renderstate(); @@ -17264,6 +17267,8 @@ enum SHADOW_TECHNIQUE { SHADOW_PCF, }; +#define NUM_SHADOW_CASCADES 4 + typedef struct light_t { char type; vec3 diffuse, specular, ambient; @@ -17281,7 +17286,7 @@ typedef struct light_t { unsigned shadow_technique; float shadow_distance; float shadow_bias; - mat44 shadow_matrix; + mat44 shadow_matrix[NUM_SHADOW_CASCADES]; // internals bool cached; //< used by scene to invalidate cached light data @@ -17314,18 +17319,26 @@ API void light_update(unsigned num_lights, light_t *lv); typedef struct shadowmap_t { mat44 V; mat44 PV; - int texture_width; + int vsm_texture_width; + int pcf_texture_width; int step; int light_step; + int cascade_index; unsigned shadow_technique; + float cascade_splits[NUM_SHADOW_CASCADES]; + float cascade_distances[NUM_SHADOW_CASCADES]; + bool blur_pcf; + float blur_scale; // signals bool skip_render; int lights_pushed; struct { + unsigned shadow_technique; handle fbos[6], texture, depth_texture; - handle fbo_2d, texture_2d, depth_texture_2d; + handle fbo_2d[NUM_SHADOW_CASCADES], texture_2d[NUM_SHADOW_CASCADES], depth_texture_2d[NUM_SHADOW_CASCADES]; + handle blur_fbo_2d, blur_texture_2d; } maps[MAX_LIGHTS]; handle saved_fb; @@ -17333,12 +17346,12 @@ typedef struct shadowmap_t { int saved_vp[4]; } shadowmap_t; -API shadowmap_t shadowmap(int texture_width); // = 1024 +API shadowmap_t shadowmap(int vsm_texture_width, int pcf_texture_width); // = 512, 4096 API void shadowmap_destroy(shadowmap_t *s); API void shadowmap_begin(shadowmap_t *s); API bool shadowmap_step(shadowmap_t *s); -API void shadowmap_light(shadowmap_t *s, light_t *l); //< can be called once per shadowmap_step +API void shadowmap_light(shadowmap_t *s, light_t *l, float cam_fov, mat44 cam_view); //< can be called once per shadowmap_step API void shadowmap_end(shadowmap_t *s); // ----------------------------------------------------------------------------- @@ -17993,6 +18006,7 @@ typedef struct object_t { aabb bounds; unsigned billboard; // [0..7] x(4),y(2),z(1) masks bool disable_frustum_check; + bool cast_shadows; // internal states array(handle) old_texture_ids; @@ -18027,6 +18041,7 @@ enum SCENE_FLAGS { SCENE_BACKGROUND = 4, SCENE_FOREGROUND = 8, SCENE_UPDATE_SH_COEF = 16, + SCENE_CAST_SHADOWS = 32, }; typedef struct scene_t { @@ -18036,6 +18051,7 @@ typedef struct scene_t { // special objects below: skybox_t skybox; int u_coefficients_sh; + shadowmap_t shadowmap; } scene_t; API scene_t* scene_push(); @@ -381836,6 +381852,9 @@ renderstate_t renderstate() { // Enable seamless cubemap by default state.seamless_cubemap = GL_TRUE; + // Disable depth clamp by default + state.depth_clamp_enabled = GL_FALSE; + return state; } @@ -381949,6 +381968,13 @@ void renderstate_apply(const renderstate_t *state) { } else { glDisable(GL_TEXTURE_CUBE_MAP_SEAMLESS); } + + // Apply depth clamp + if (state->depth_clamp_enabled) { + glEnable(GL_DEPTH_CLAMP); + } else { + glDisable(GL_DEPTH_CLAMP); + } } } @@ -383186,8 +383212,9 @@ light_t light() { l.specularPower = 32.f; l.innerCone = 0.85f;// 31 deg l.outerCone = 0.9f; // 25 deg + l.cast_shadows = true; l.shadow_distance = 100.0f; - l.shadow_bias = 0.5f; + l.shadow_bias = 0.01f; return l; } @@ -383266,7 +383293,9 @@ void light_update(unsigned num_lights, light_t *lv) { shader_float(va("u_lights[%d].quadratic", i), lv[i].falloff.quadratic); shader_float(va("u_lights[%d].innerCone", i), lv[i].innerCone); shader_float(va("u_lights[%d].outerCone", i), lv[i].outerCone); - shader_mat44(va("u_lights[%d].shadow_matrix", i), lv[i].shadow_matrix); + for (int j = 0; j < NUM_SHADOW_CASCADES; j++) { + shader_mat44(va("u_lights[%d].shadow_matrix[%d]", i, j), lv[i].shadow_matrix[j]); + } } } @@ -383274,10 +383303,14 @@ void light_update(unsigned num_lights, light_t *lv) { // ----------------------------------------------------------------------------- // shadowmaps -static inline -shadowmap_init_caster(shadowmap_t *s, int light_index, int texture_width) { +static inline void +shadowmap_init_caster_vsm(shadowmap_t *s, int light_index, int texture_width) { float borderColor[] = {1.0, 1.0, 1.0, 1.0}; + if (s->maps[light_index].texture) { + return; + } + // Create a cubemap color texture glGenTextures(1, &s->maps[light_index].texture); glBindTexture(GL_TEXTURE_CUBE_MAP, s->maps[light_index].texture); @@ -383318,70 +383351,147 @@ shadowmap_init_caster(shadowmap_t *s, int light_index, int texture_width) { if (GL_FRAMEBUFFER_COMPLETE != result) { PANIC("ERROR: Framebuffer is not complete: %x\n", result); } - // #if is(ems) - // GLenum nones[] = { GL_NONE }; - // glDrawBuffers(1, nones); - // glReadBuffer(GL_NONE); - // #else - // glDrawBuffer(GL_NONE); - // glReadBuffer(GL_NONE); - // #endif + } +} + +static inline void +shadowmap_init_caster_pcf(shadowmap_t *s, int light_index, int texture_width) { + float borderColor[] = {1.0, 1.0, 1.0, 1.0}; + + if (s->maps[light_index].texture_2d[0]) { + return; } // Initialise shadow map 2D - glGenTextures(1, &s->maps[light_index].texture_2d); - glBindTexture(GL_TEXTURE_2D, s->maps[light_index].texture_2d); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, texture_width, texture_width, 0, GL_RGB, GL_FLOAT, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); + for (int i = 0; i < NUM_SHADOW_CASCADES; i++) { + glGenTextures(1, &s->maps[light_index].texture_2d[i]); + glBindTexture(GL_TEXTURE_2D, s->maps[light_index].texture_2d[i]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, texture_width, texture_width, 0, GL_RGB, GL_FLOAT, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); - glGenTextures(1, &s->maps[light_index].depth_texture_2d); - glBindTexture(GL_TEXTURE_2D, s->maps[light_index].depth_texture_2d); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, texture_width, texture_width, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); + glGenTextures(1, &s->maps[light_index].depth_texture_2d[i]); + glBindTexture(GL_TEXTURE_2D, s->maps[light_index].depth_texture_2d[i]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, texture_width, texture_width, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); + } glBindTexture(GL_TEXTURE_2D, 0); - glGenFramebuffers(1, &s->maps[light_index].fbo_2d); - glBindFramebuffer(GL_FRAMEBUFFER, s->maps[light_index].fbo_2d); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, s->maps[light_index].texture_2d, 0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, s->maps[light_index].depth_texture_2d, 0); + for (int i = 0; i < NUM_SHADOW_CASCADES; i++) { + glGenFramebuffers(1, &s->maps[light_index].fbo_2d[i]); + glBindFramebuffer(GL_FRAMEBUFFER, s->maps[light_index].fbo_2d[i]); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, s->maps[light_index].texture_2d[i], 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, s->maps[light_index].depth_texture_2d[i], 0); + GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (GL_FRAMEBUFFER_COMPLETE != result) { + PANIC("ERROR: Framebuffer is not complete: %x\n", result); + } + } + + // Blur texture + glGenTextures(1, &s->maps[light_index].blur_texture_2d); + glBindTexture(GL_TEXTURE_2D, s->maps[light_index].blur_texture_2d); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, texture_width, texture_width, 0, GL_RGB, GL_FLOAT, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Blur FBO + glGenFramebuffers(1, &s->maps[light_index].blur_fbo_2d); + glBindFramebuffer(GL_FRAMEBUFFER, s->maps[light_index].blur_fbo_2d); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, s->maps[light_index].blur_texture_2d, 0); GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (GL_FRAMEBUFFER_COMPLETE != result) { PANIC("ERROR: Framebuffer is not complete: %x\n", result); } - } -shadowmap_t shadowmap(int texture_width) { // = 1024 +static inline +shadowmap_init_caster(shadowmap_t *s, int light_index) { + shadowmap_init_caster_vsm(s, light_index, s->vsm_texture_width); + shadowmap_init_caster_pcf(s, light_index, s->pcf_texture_width); +} + +shadowmap_t shadowmap(int vsm_texture_width, int pcf_texture_width) { // = 512, 4096 shadowmap_t s = {0}; - s.texture_width = texture_width; + s.vsm_texture_width = vsm_texture_width; + s.pcf_texture_width = pcf_texture_width; s.saved_fb = 0; + s.blur_pcf = true; + s.blur_scale = 0.5f; + s.cascade_splits[0] = 0.1f; + s.cascade_splits[1] = 0.3f; + s.cascade_splits[2] = 0.5f; + s.cascade_splits[3] = 1.0f; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &s.saved_fb); +#if 0 for (int i = 0; i < MAX_LIGHTS; i++) { - shadowmap_init_caster(&s, i, texture_width); + shadowmap_init_caster(&s, i); } - +#else + for (int i = 0; i < MAX_LIGHTS; i++) { + s.maps[i].shadow_technique = 0xFFFF; + } +#endif + glBindFramebuffer(GL_FRAMEBUFFER, s.saved_fb); return s; } +static inline +void shadowmap_destroy_light(shadowmap_t *s, int light_index) { + if (s->maps[light_index].fbos[0]) { + glDeleteFramebuffers(6, s->maps[light_index].fbos); + s->maps[light_index].fbos[0] = 0; + } + if (s->maps[light_index].texture) { + glDeleteTextures(1, &s->maps[light_index].texture); + s->maps[light_index].texture = 0; + } + if (s->maps[light_index].depth_texture) { + glDeleteTextures(1, &s->maps[light_index].depth_texture); + s->maps[light_index].depth_texture = 0; + } + + for (int i = 0; i < NUM_SHADOW_CASCADES; i++) { + if (s->maps[light_index].fbo_2d[i]) { + glDeleteFramebuffers(1, &s->maps[light_index].fbo_2d[i]); + s->maps[light_index].fbo_2d[i] = 0; + } + if (s->maps[light_index].texture_2d[i]) { + glDeleteTextures(1, &s->maps[light_index].texture_2d[i]); + s->maps[light_index].texture_2d[i] = 0; + } + if (s->maps[light_index].depth_texture_2d[i]) { + glDeleteTextures(1, &s->maps[light_index].depth_texture_2d[i]); + s->maps[light_index].depth_texture_2d[i] = 0; + } + } + + if (s->maps[light_index].blur_fbo_2d) { + glDeleteFramebuffers(1, &s->maps[light_index].blur_fbo_2d); + s->maps[light_index].blur_fbo_2d = 0; + } + if (s->maps[light_index].blur_texture_2d) { + glDeleteTextures(1, &s->maps[light_index].blur_texture_2d); + s->maps[light_index].blur_texture_2d = 0; + } +} + void shadowmap_destroy(shadowmap_t *s) { for (int i = 0; i < MAX_LIGHTS; i++) { - glDeleteFramebuffers(6, s->maps[i].fbos); - glDeleteTextures(1, &s->maps[i].texture); - glDeleteTextures(1, &s->maps[i].depth_texture); - glDeleteFramebuffers(1, &s->maps[i].fbo_2d); - glDeleteTextures(1, &s->maps[i].texture_2d); - glDeleteTextures(1, &s->maps[i].depth_texture_2d); + shadowmap_destroy_light(s, i); } shadowmap_t z = {0}; *s = z; @@ -383396,6 +383506,7 @@ void shadowmap_begin(shadowmap_t *s) { s->saved_pass = model_setpass(RENDER_PASS_SHADOW); s->step = 0; s->light_step = 0; + s->cascade_index = 0; active_shadowmap = s; } @@ -383419,48 +383530,206 @@ static void shadowmap_light_point(shadowmap_t *s, light_t *l, int dir) { s->shadow_technique = l->shadow_technique = SHADOW_VSM; } -static void shadowmap_light_directional(shadowmap_t *s, light_t *l, int dir) { +static array(vec3) frustum_corners = 0; + +static inline +void shadowmap_light_directional_calc_frustum_corners(mat44 cam_proj, mat44 cam_view) { + mat44 PV; multiply44x2(PV, cam_proj, cam_view); + mat44 inverse_view_proj; invert44(inverse_view_proj, PV); + array_resize(frustum_corners, 0); + for (unsigned x = 0; x < 2; x++) { + for (unsigned y = 0; y < 2; y++) { + for (unsigned z = 0; z < 2; z++) { + vec4 corner = { + x * 2.0f - 1.0f, + y * 2.0f - 1.0f, + z * 2.0f - 1.0f, + 1.0f + }; + vec4 world_corner = transform444(inverse_view_proj, corner); + world_corner = scale4(world_corner, 1.0f / world_corner.w); + array_push(frustum_corners, vec3(world_corner.x, world_corner.y, world_corner.z)); + } + } + } +} + +static void shadowmap_light_directional(shadowmap_t *s, light_t *l, int dir, float cam_fov, mat44 cam_view) { if (dir != 0) { s->skip_render = true; return; } - - mat44 P, V, PV; - l->shadow_distance = 25.0f; - ortho44(P, - -l->shadow_distance/2.0, l->shadow_distance/2.0, - -l->shadow_distance/2.0, l->shadow_distance/2.0, - l->shadow_bias, l->shadow_distance); + float far_plane = 0.0f; + float near_plane = 0.0f; + + if (s->cascade_index == 0) { + near_plane = l->shadow_bias; + far_plane = l->shadow_distance * s->cascade_splits[0]; + } else if (s->cascade_index < NUM_SHADOW_CASCADES - 1) { + near_plane = l->shadow_distance * s->cascade_splits[s->cascade_index-1]; + far_plane = l->shadow_distance * s->cascade_splits[s->cascade_index]; + } else { + near_plane = l->shadow_distance * s->cascade_splits[NUM_SHADOW_CASCADES-1]; + far_plane = l->shadow_distance; + } + + mat44 proj; perspective44(proj, cam_fov, window_width() / (float)window_height(), near_plane, far_plane); + shadowmap_light_directional_calc_frustum_corners(proj, cam_view); + + vec3 center = {0,0,0}; + for (unsigned i = 0; i < array_count(frustum_corners); i++) { + center = add3(center, frustum_corners[i]); + } + center = scale3(center, 1.0f / array_count(frustum_corners)); + + + s->cascade_distances[s->cascade_index] = far_plane; + + float minX = FLT_MAX, maxX = FLT_MIN; + float minY = FLT_MAX, maxY = FLT_MIN; + float minZ = FLT_MAX, maxZ = FLT_MIN; + + mat44 V; vec3 lightDir = norm3(l->dir); vec3 up = vec3(0, 1, 0); - // Ensure up vector is not parallel to light direction - if (fabs(dot3(lightDir, up)) > 0.99f) { - up = vec3(0, 0, 1); + lookat44(V, sub3(center, lightDir), center, up); + + + for (unsigned i = 0; i < array_count(frustum_corners); i++) { + vec3 corner = frustum_corners[i]; + corner = transform344(V, corner); + minX = min(minX, corner.x); + maxX = max(maxX, corner.x); + minY = min(minY, corner.y); + maxY = max(maxY, corner.y); + minZ = min(minZ, corner.z); + maxZ = max(maxZ, corner.z); } - - vec3 center = vec3(0, 0, 0); - vec3 lightPos = sub3(center, scale3(lightDir, l->shadow_distance*0.5f)); - lookat44(V, lightPos, center, up); + + float zMult = 10.0f; + + // if (minZ < 0) { + // minZ *= zMult; + // } else { + // minZ /= zMult; + // } + // if (maxZ < 0) { + // maxZ /= zMult; + // } else { + // maxZ *= zMult; + // } + + + mat44 P, PV; + ortho44(P, + minX, maxX, + minY, maxY, + minZ, maxZ); multiply44x2(PV, P, V); copy44(s->V, V); copy44(s->PV, PV); - copy44(l->shadow_matrix, PV); + copy44(l->shadow_matrix[s->cascade_index], PV); s->shadow_technique = l->shadow_technique = SHADOW_PCF; } -bool shadowmap_step(shadowmap_t *s) { - if (s->step >= 6) { - s->step = 0; - s->light_step++; - return false; +static inline +void shadowmap_blur_pcf(shadowmap_t *s, int light_index) { + if (!s->blur_pcf) { + return; } - glViewport(0, 0, s->texture_width, s->texture_width); + float blur_scale = 1.999 * (1 - s->blur_scale) + 0.001; + // blur_scale = 0.1f; + + static renderstate_t rs; + static int program = -1, vao = -1, u_scale = -1, u_source = -1; + if (program < 0) { + rs = renderstate(); { + rs.depth_test_enabled = false; + rs.depth_write_enabled = false; + rs.blend_enabled = false; + } + const char* vs = vfs_read("shaders/vs_shadow_blur.glsl"); + const char* fs = vfs_read("shaders/fs_shadow_blur.glsl"); + + program = shader(vs, fs, "", "fragcolor", NULL); + u_scale = glGetUniformLocation(program, "ScaleU"); + u_source = glGetUniformLocation(program, "textureSource"); + glGenVertexArrays(1, &vao); + } + + renderstate_apply(&rs); + glViewport(0, 0, s->pcf_texture_width, s->pcf_texture_width); + + unsigned oldprog = last_shader; + glUseProgram(program); + + glBindVertexArray(vao); + glActiveTexture(GL_TEXTURE0); + + // Horizontal pass + for (int i = 0; i < NUM_SHADOW_CASCADES; i++) { + glBindFramebuffer(GL_FRAMEBUFFER, s->maps[light_index].blur_fbo_2d); + glUniform2f(u_scale, 1.0f / (s->pcf_texture_width * blur_scale), 0); + glBindTexture(GL_TEXTURE_2D, s->maps[light_index].texture_2d[i]); + glUniform1i(u_source, 0); + //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glDrawArrays(GL_TRIANGLES, 0, 6); + profile_incstat("Render.num_drawcalls", +1); + profile_incstat("Render.num_triangles", +2); + + // Vertical pass + glBindFramebuffer(GL_FRAMEBUFFER, s->maps[light_index].fbo_2d[i]); + glUniform2f(u_scale, 0, 1.0f / (s->pcf_texture_width * blur_scale)); + glBindTexture(GL_TEXTURE_2D, s->maps[light_index].blur_texture_2d); + glUniform1i(u_source, 0); + //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glDrawArrays(GL_TRIANGLES, 0, 6); + profile_incstat("Render.num_drawcalls", +1); + profile_incstat("Render.num_triangles", +2); + } + + glBindVertexArray(0); + glUseProgram(oldprog); +} + +static inline +bool shadowmap_step_finish(shadowmap_t *s) { + if (s->shadow_technique == SHADOW_PCF) { + if (s->cascade_index < NUM_SHADOW_CASCADES - 1) { + s->cascade_index++; + s->step = 0; + return false; + } + shadowmap_blur_pcf(s, s->light_step); + } + + s->step = 0; + s->light_step++; + s->cascade_index = 0; + return true; +} + +bool shadowmap_step(shadowmap_t *s) { + int max_steps = s->shadow_technique == 0xffff ? 1 : s->shadow_technique == SHADOW_PCF ? 1 : 6; + if (s->step >= max_steps) { + if (shadowmap_step_finish(s)) { + return false; + } else { + return true; + } + } + + unsigned texture_width = s->shadow_technique == SHADOW_VSM ? s->vsm_texture_width : s->pcf_texture_width; + + glViewport(0, 0, texture_width, texture_width); s->step++; s->skip_render = false; @@ -383470,12 +383739,12 @@ bool shadowmap_step(shadowmap_t *s) { static inline void shadowmap_clear_fbo() { - glClearColor(0, 0, 0, 0); + glClearColor(1, 1, 1, 1); glClearDepth(1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } -void shadowmap_light(shadowmap_t *s, light_t *l) { +void shadowmap_light(shadowmap_t *s, light_t *l, float cam_fov, mat44 cam_view) { if (l->cast_shadows) { int step = s->step - 1; @@ -383484,18 +383753,29 @@ void shadowmap_light(shadowmap_t *s, light_t *l) { } else if (l->type == LIGHT_SPOT) { shadowmap_light_point(s, l, step); } else if (l->type == LIGHT_DIRECTIONAL) { - shadowmap_light_directional(s, l, step); + shadowmap_light_directional(s, l, step, cam_fov, cam_view); } if (s->skip_render) { return; } + if (s->maps[s->light_step].shadow_technique != l->shadow_technique) { + // shadowmap_destroy_light(s, s->light_step); // @todo: we might wanna free the other set + if (l->shadow_technique == SHADOW_VSM) { + shadowmap_init_caster_vsm(s, s->light_step, s->vsm_texture_width); + } else if (l->shadow_technique == SHADOW_PCF) { + shadowmap_init_caster_pcf(s, s->light_step, s->pcf_texture_width); + } + } + + s->maps[s->light_step].shadow_technique = l->shadow_technique; + ASSERT(s->lights_pushed == 0); s->lights_pushed++; if (l->type == LIGHT_DIRECTIONAL) { - glBindFramebuffer(GL_FRAMEBUFFER, s->maps[s->light_step].fbo_2d); + glBindFramebuffer(GL_FRAMEBUFFER, s->maps[s->light_step].fbo_2d[s->cascade_index]); shadowmap_clear_fbo(); } else { glBindFramebuffer(GL_FRAMEBUFFER, s->maps[s->light_step].fbos[step]); @@ -385689,7 +385969,11 @@ void model_set_uniforms(model_t m, int shader, mat44 mv, mat44 proj, mat44 view, shader_bool("u_shadow_receiver", GL_TRUE); for (int i = 0; i < MAX_LIGHTS; i++) { shader_cubemap(va("shadowMap[%d]", i), m.shadow_map->maps[i].texture); - shader_texture_unit(va("shadowMap2D[%d]", i), m.shadow_map->maps[i].texture_2d, texture_unit()); + for (int j = 0; j < NUM_SHADOW_CASCADES; j++) { + shader_texture_unit(va("shadowMap2D[%d]", i * NUM_SHADOW_CASCADES + j), m.shadow_map->maps[i].texture_2d[j], texture_unit()); + shader_float(va("u_cascade_splits[%d]", j), m.shadow_map->cascade_splits[j]); + shader_float(va("u_cascade_distances[%d]", j), m.shadow_map->cascade_distances[j]); + } } } else { shader_bool("u_shadow_receiver", GL_FALSE); @@ -386317,6 +386601,7 @@ void model_set_renderstates(model_t *m) { shadow_rs->cull_face_enabled = 1; shadow_rs->cull_face_mode = GL_BACK; shadow_rs->front_face = GL_CW; + shadow_rs->depth_clamp_enabled = 1; } // Lightmap pass diff --git a/engine/split/v4k_render.c b/engine/split/v4k_render.c index fc374e2..2cb081e 100644 --- a/engine/split/v4k_render.c +++ b/engine/split/v4k_render.c @@ -122,6 +122,9 @@ renderstate_t renderstate() { // Enable seamless cubemap by default state.seamless_cubemap = GL_TRUE; + // Disable depth clamp by default + state.depth_clamp_enabled = GL_FALSE; + return state; } @@ -235,6 +238,13 @@ void renderstate_apply(const renderstate_t *state) { } else { glDisable(GL_TEXTURE_CUBE_MAP_SEAMLESS); } + + // Apply depth clamp + if (state->depth_clamp_enabled) { + glEnable(GL_DEPTH_CLAMP); + } else { + glDisable(GL_DEPTH_CLAMP); + } } } @@ -1472,8 +1482,9 @@ light_t light() { l.specularPower = 32.f; l.innerCone = 0.85f;// 31 deg l.outerCone = 0.9f; // 25 deg + l.cast_shadows = true; l.shadow_distance = 100.0f; - l.shadow_bias = 0.5f; + l.shadow_bias = 0.01f; return l; } @@ -1552,7 +1563,9 @@ void light_update(unsigned num_lights, light_t *lv) { shader_float(va("u_lights[%d].quadratic", i), lv[i].falloff.quadratic); shader_float(va("u_lights[%d].innerCone", i), lv[i].innerCone); shader_float(va("u_lights[%d].outerCone", i), lv[i].outerCone); - shader_mat44(va("u_lights[%d].shadow_matrix", i), lv[i].shadow_matrix); + for (int j = 0; j < NUM_SHADOW_CASCADES; j++) { + shader_mat44(va("u_lights[%d].shadow_matrix[%d]", i, j), lv[i].shadow_matrix[j]); + } } } @@ -1560,10 +1573,14 @@ void light_update(unsigned num_lights, light_t *lv) { // ----------------------------------------------------------------------------- // shadowmaps -static inline -shadowmap_init_caster(shadowmap_t *s, int light_index, int texture_width) { +static inline void +shadowmap_init_caster_vsm(shadowmap_t *s, int light_index, int texture_width) { float borderColor[] = {1.0, 1.0, 1.0, 1.0}; + if (s->maps[light_index].texture) { + return; + } + // Create a cubemap color texture glGenTextures(1, &s->maps[light_index].texture); glBindTexture(GL_TEXTURE_CUBE_MAP, s->maps[light_index].texture); @@ -1604,70 +1621,147 @@ shadowmap_init_caster(shadowmap_t *s, int light_index, int texture_width) { if (GL_FRAMEBUFFER_COMPLETE != result) { PANIC("ERROR: Framebuffer is not complete: %x\n", result); } - // #if is(ems) - // GLenum nones[] = { GL_NONE }; - // glDrawBuffers(1, nones); - // glReadBuffer(GL_NONE); - // #else - // glDrawBuffer(GL_NONE); - // glReadBuffer(GL_NONE); - // #endif + } +} + +static inline void +shadowmap_init_caster_pcf(shadowmap_t *s, int light_index, int texture_width) { + float borderColor[] = {1.0, 1.0, 1.0, 1.0}; + + if (s->maps[light_index].texture_2d[0]) { + return; } // Initialise shadow map 2D - glGenTextures(1, &s->maps[light_index].texture_2d); - glBindTexture(GL_TEXTURE_2D, s->maps[light_index].texture_2d); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, texture_width, texture_width, 0, GL_RGB, GL_FLOAT, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); + for (int i = 0; i < NUM_SHADOW_CASCADES; i++) { + glGenTextures(1, &s->maps[light_index].texture_2d[i]); + glBindTexture(GL_TEXTURE_2D, s->maps[light_index].texture_2d[i]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, texture_width, texture_width, 0, GL_RGB, GL_FLOAT, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); - glGenTextures(1, &s->maps[light_index].depth_texture_2d); - glBindTexture(GL_TEXTURE_2D, s->maps[light_index].depth_texture_2d); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, texture_width, texture_width, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); + glGenTextures(1, &s->maps[light_index].depth_texture_2d[i]); + glBindTexture(GL_TEXTURE_2D, s->maps[light_index].depth_texture_2d[i]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, texture_width, texture_width, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); + } glBindTexture(GL_TEXTURE_2D, 0); - glGenFramebuffers(1, &s->maps[light_index].fbo_2d); - glBindFramebuffer(GL_FRAMEBUFFER, s->maps[light_index].fbo_2d); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, s->maps[light_index].texture_2d, 0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, s->maps[light_index].depth_texture_2d, 0); + for (int i = 0; i < NUM_SHADOW_CASCADES; i++) { + glGenFramebuffers(1, &s->maps[light_index].fbo_2d[i]); + glBindFramebuffer(GL_FRAMEBUFFER, s->maps[light_index].fbo_2d[i]); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, s->maps[light_index].texture_2d[i], 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, s->maps[light_index].depth_texture_2d[i], 0); + GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (GL_FRAMEBUFFER_COMPLETE != result) { + PANIC("ERROR: Framebuffer is not complete: %x\n", result); + } + } + + // Blur texture + glGenTextures(1, &s->maps[light_index].blur_texture_2d); + glBindTexture(GL_TEXTURE_2D, s->maps[light_index].blur_texture_2d); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, texture_width, texture_width, 0, GL_RGB, GL_FLOAT, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Blur FBO + glGenFramebuffers(1, &s->maps[light_index].blur_fbo_2d); + glBindFramebuffer(GL_FRAMEBUFFER, s->maps[light_index].blur_fbo_2d); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, s->maps[light_index].blur_texture_2d, 0); GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (GL_FRAMEBUFFER_COMPLETE != result) { PANIC("ERROR: Framebuffer is not complete: %x\n", result); } - } -shadowmap_t shadowmap(int texture_width) { // = 1024 +static inline +shadowmap_init_caster(shadowmap_t *s, int light_index) { + shadowmap_init_caster_vsm(s, light_index, s->vsm_texture_width); + shadowmap_init_caster_pcf(s, light_index, s->pcf_texture_width); +} + +shadowmap_t shadowmap(int vsm_texture_width, int pcf_texture_width) { // = 512, 4096 shadowmap_t s = {0}; - s.texture_width = texture_width; + s.vsm_texture_width = vsm_texture_width; + s.pcf_texture_width = pcf_texture_width; s.saved_fb = 0; + s.blur_pcf = true; + s.blur_scale = 0.5f; + s.cascade_splits[0] = 0.1f; + s.cascade_splits[1] = 0.3f; + s.cascade_splits[2] = 0.5f; + s.cascade_splits[3] = 1.0f; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &s.saved_fb); +#if 0 for (int i = 0; i < MAX_LIGHTS; i++) { - shadowmap_init_caster(&s, i, texture_width); + shadowmap_init_caster(&s, i); } - +#else + for (int i = 0; i < MAX_LIGHTS; i++) { + s.maps[i].shadow_technique = 0xFFFF; + } +#endif + glBindFramebuffer(GL_FRAMEBUFFER, s.saved_fb); return s; } +static inline +void shadowmap_destroy_light(shadowmap_t *s, int light_index) { + if (s->maps[light_index].fbos[0]) { + glDeleteFramebuffers(6, s->maps[light_index].fbos); + s->maps[light_index].fbos[0] = 0; + } + if (s->maps[light_index].texture) { + glDeleteTextures(1, &s->maps[light_index].texture); + s->maps[light_index].texture = 0; + } + if (s->maps[light_index].depth_texture) { + glDeleteTextures(1, &s->maps[light_index].depth_texture); + s->maps[light_index].depth_texture = 0; + } + + for (int i = 0; i < NUM_SHADOW_CASCADES; i++) { + if (s->maps[light_index].fbo_2d[i]) { + glDeleteFramebuffers(1, &s->maps[light_index].fbo_2d[i]); + s->maps[light_index].fbo_2d[i] = 0; + } + if (s->maps[light_index].texture_2d[i]) { + glDeleteTextures(1, &s->maps[light_index].texture_2d[i]); + s->maps[light_index].texture_2d[i] = 0; + } + if (s->maps[light_index].depth_texture_2d[i]) { + glDeleteTextures(1, &s->maps[light_index].depth_texture_2d[i]); + s->maps[light_index].depth_texture_2d[i] = 0; + } + } + + if (s->maps[light_index].blur_fbo_2d) { + glDeleteFramebuffers(1, &s->maps[light_index].blur_fbo_2d); + s->maps[light_index].blur_fbo_2d = 0; + } + if (s->maps[light_index].blur_texture_2d) { + glDeleteTextures(1, &s->maps[light_index].blur_texture_2d); + s->maps[light_index].blur_texture_2d = 0; + } +} + void shadowmap_destroy(shadowmap_t *s) { for (int i = 0; i < MAX_LIGHTS; i++) { - glDeleteFramebuffers(6, s->maps[i].fbos); - glDeleteTextures(1, &s->maps[i].texture); - glDeleteTextures(1, &s->maps[i].depth_texture); - glDeleteFramebuffers(1, &s->maps[i].fbo_2d); - glDeleteTextures(1, &s->maps[i].texture_2d); - glDeleteTextures(1, &s->maps[i].depth_texture_2d); + shadowmap_destroy_light(s, i); } shadowmap_t z = {0}; *s = z; @@ -1682,6 +1776,7 @@ void shadowmap_begin(shadowmap_t *s) { s->saved_pass = model_setpass(RENDER_PASS_SHADOW); s->step = 0; s->light_step = 0; + s->cascade_index = 0; active_shadowmap = s; } @@ -1705,48 +1800,206 @@ static void shadowmap_light_point(shadowmap_t *s, light_t *l, int dir) { s->shadow_technique = l->shadow_technique = SHADOW_VSM; } -static void shadowmap_light_directional(shadowmap_t *s, light_t *l, int dir) { +static array(vec3) frustum_corners = 0; + +static inline +void shadowmap_light_directional_calc_frustum_corners(mat44 cam_proj, mat44 cam_view) { + mat44 PV; multiply44x2(PV, cam_proj, cam_view); + mat44 inverse_view_proj; invert44(inverse_view_proj, PV); + array_resize(frustum_corners, 0); + for (unsigned x = 0; x < 2; x++) { + for (unsigned y = 0; y < 2; y++) { + for (unsigned z = 0; z < 2; z++) { + vec4 corner = { + x * 2.0f - 1.0f, + y * 2.0f - 1.0f, + z * 2.0f - 1.0f, + 1.0f + }; + vec4 world_corner = transform444(inverse_view_proj, corner); + world_corner = scale4(world_corner, 1.0f / world_corner.w); + array_push(frustum_corners, vec3(world_corner.x, world_corner.y, world_corner.z)); + } + } + } +} + +static void shadowmap_light_directional(shadowmap_t *s, light_t *l, int dir, float cam_fov, mat44 cam_view) { if (dir != 0) { s->skip_render = true; return; } - - mat44 P, V, PV; - l->shadow_distance = 25.0f; - ortho44(P, - -l->shadow_distance/2.0, l->shadow_distance/2.0, - -l->shadow_distance/2.0, l->shadow_distance/2.0, - l->shadow_bias, l->shadow_distance); + float far_plane = 0.0f; + float near_plane = 0.0f; + + if (s->cascade_index == 0) { + near_plane = l->shadow_bias; + far_plane = l->shadow_distance * s->cascade_splits[0]; + } else if (s->cascade_index < NUM_SHADOW_CASCADES - 1) { + near_plane = l->shadow_distance * s->cascade_splits[s->cascade_index-1]; + far_plane = l->shadow_distance * s->cascade_splits[s->cascade_index]; + } else { + near_plane = l->shadow_distance * s->cascade_splits[NUM_SHADOW_CASCADES-1]; + far_plane = l->shadow_distance; + } + + mat44 proj; perspective44(proj, cam_fov, window_width() / (float)window_height(), near_plane, far_plane); + shadowmap_light_directional_calc_frustum_corners(proj, cam_view); + + vec3 center = {0,0,0}; + for (unsigned i = 0; i < array_count(frustum_corners); i++) { + center = add3(center, frustum_corners[i]); + } + center = scale3(center, 1.0f / array_count(frustum_corners)); + + + s->cascade_distances[s->cascade_index] = far_plane; + + float minX = FLT_MAX, maxX = FLT_MIN; + float minY = FLT_MAX, maxY = FLT_MIN; + float minZ = FLT_MAX, maxZ = FLT_MIN; + + mat44 V; vec3 lightDir = norm3(l->dir); vec3 up = vec3(0, 1, 0); - // Ensure up vector is not parallel to light direction - if (fabs(dot3(lightDir, up)) > 0.99f) { - up = vec3(0, 0, 1); + lookat44(V, sub3(center, lightDir), center, up); + + + for (unsigned i = 0; i < array_count(frustum_corners); i++) { + vec3 corner = frustum_corners[i]; + corner = transform344(V, corner); + minX = min(minX, corner.x); + maxX = max(maxX, corner.x); + minY = min(minY, corner.y); + maxY = max(maxY, corner.y); + minZ = min(minZ, corner.z); + maxZ = max(maxZ, corner.z); } - - vec3 center = vec3(0, 0, 0); - vec3 lightPos = sub3(center, scale3(lightDir, l->shadow_distance*0.5f)); - lookat44(V, lightPos, center, up); + + float zMult = 10.0f; + + // if (minZ < 0) { + // minZ *= zMult; + // } else { + // minZ /= zMult; + // } + // if (maxZ < 0) { + // maxZ /= zMult; + // } else { + // maxZ *= zMult; + // } + + + mat44 P, PV; + ortho44(P, + minX, maxX, + minY, maxY, + minZ, maxZ); multiply44x2(PV, P, V); copy44(s->V, V); copy44(s->PV, PV); - copy44(l->shadow_matrix, PV); + copy44(l->shadow_matrix[s->cascade_index], PV); s->shadow_technique = l->shadow_technique = SHADOW_PCF; } -bool shadowmap_step(shadowmap_t *s) { - if (s->step >= 6) { - s->step = 0; - s->light_step++; - return false; +static inline +void shadowmap_blur_pcf(shadowmap_t *s, int light_index) { + if (!s->blur_pcf) { + return; } - glViewport(0, 0, s->texture_width, s->texture_width); + float blur_scale = 1.999 * (1 - s->blur_scale) + 0.001; + // blur_scale = 0.1f; + + static renderstate_t rs; + static int program = -1, vao = -1, u_scale = -1, u_source = -1; + if (program < 0) { + rs = renderstate(); { + rs.depth_test_enabled = false; + rs.depth_write_enabled = false; + rs.blend_enabled = false; + } + const char* vs = vfs_read("shaders/vs_shadow_blur.glsl"); + const char* fs = vfs_read("shaders/fs_shadow_blur.glsl"); + + program = shader(vs, fs, "", "fragcolor", NULL); + u_scale = glGetUniformLocation(program, "ScaleU"); + u_source = glGetUniformLocation(program, "textureSource"); + glGenVertexArrays(1, &vao); + } + + renderstate_apply(&rs); + glViewport(0, 0, s->pcf_texture_width, s->pcf_texture_width); + + unsigned oldprog = last_shader; + glUseProgram(program); + + glBindVertexArray(vao); + glActiveTexture(GL_TEXTURE0); + + // Horizontal pass + for (int i = 0; i < NUM_SHADOW_CASCADES; i++) { + glBindFramebuffer(GL_FRAMEBUFFER, s->maps[light_index].blur_fbo_2d); + glUniform2f(u_scale, 1.0f / (s->pcf_texture_width * blur_scale), 0); + glBindTexture(GL_TEXTURE_2D, s->maps[light_index].texture_2d[i]); + glUniform1i(u_source, 0); + //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glDrawArrays(GL_TRIANGLES, 0, 6); + profile_incstat("Render.num_drawcalls", +1); + profile_incstat("Render.num_triangles", +2); + + // Vertical pass + glBindFramebuffer(GL_FRAMEBUFFER, s->maps[light_index].fbo_2d[i]); + glUniform2f(u_scale, 0, 1.0f / (s->pcf_texture_width * blur_scale)); + glBindTexture(GL_TEXTURE_2D, s->maps[light_index].blur_texture_2d); + glUniform1i(u_source, 0); + //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glDrawArrays(GL_TRIANGLES, 0, 6); + profile_incstat("Render.num_drawcalls", +1); + profile_incstat("Render.num_triangles", +2); + } + + glBindVertexArray(0); + glUseProgram(oldprog); +} + +static inline +bool shadowmap_step_finish(shadowmap_t *s) { + if (s->shadow_technique == SHADOW_PCF) { + if (s->cascade_index < NUM_SHADOW_CASCADES - 1) { + s->cascade_index++; + s->step = 0; + return false; + } + shadowmap_blur_pcf(s, s->light_step); + } + + s->step = 0; + s->light_step++; + s->cascade_index = 0; + return true; +} + +bool shadowmap_step(shadowmap_t *s) { + int max_steps = s->shadow_technique == 0xffff ? 1 : s->shadow_technique == SHADOW_PCF ? 1 : 6; + if (s->step >= max_steps) { + if (shadowmap_step_finish(s)) { + return false; + } else { + return true; + } + } + + unsigned texture_width = s->shadow_technique == SHADOW_VSM ? s->vsm_texture_width : s->pcf_texture_width; + + glViewport(0, 0, texture_width, texture_width); s->step++; s->skip_render = false; @@ -1756,12 +2009,12 @@ bool shadowmap_step(shadowmap_t *s) { static inline void shadowmap_clear_fbo() { - glClearColor(0, 0, 0, 0); + glClearColor(1, 1, 1, 1); glClearDepth(1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } -void shadowmap_light(shadowmap_t *s, light_t *l) { +void shadowmap_light(shadowmap_t *s, light_t *l, float cam_fov, mat44 cam_view) { if (l->cast_shadows) { int step = s->step - 1; @@ -1770,18 +2023,29 @@ void shadowmap_light(shadowmap_t *s, light_t *l) { } else if (l->type == LIGHT_SPOT) { shadowmap_light_point(s, l, step); } else if (l->type == LIGHT_DIRECTIONAL) { - shadowmap_light_directional(s, l, step); + shadowmap_light_directional(s, l, step, cam_fov, cam_view); } if (s->skip_render) { return; } + if (s->maps[s->light_step].shadow_technique != l->shadow_technique) { + // shadowmap_destroy_light(s, s->light_step); // @todo: we might wanna free the other set + if (l->shadow_technique == SHADOW_VSM) { + shadowmap_init_caster_vsm(s, s->light_step, s->vsm_texture_width); + } else if (l->shadow_technique == SHADOW_PCF) { + shadowmap_init_caster_pcf(s, s->light_step, s->pcf_texture_width); + } + } + + s->maps[s->light_step].shadow_technique = l->shadow_technique; + ASSERT(s->lights_pushed == 0); s->lights_pushed++; if (l->type == LIGHT_DIRECTIONAL) { - glBindFramebuffer(GL_FRAMEBUFFER, s->maps[s->light_step].fbo_2d); + glBindFramebuffer(GL_FRAMEBUFFER, s->maps[s->light_step].fbo_2d[s->cascade_index]); shadowmap_clear_fbo(); } else { glBindFramebuffer(GL_FRAMEBUFFER, s->maps[s->light_step].fbos[step]); @@ -3975,7 +4239,11 @@ void model_set_uniforms(model_t m, int shader, mat44 mv, mat44 proj, mat44 view, shader_bool("u_shadow_receiver", GL_TRUE); for (int i = 0; i < MAX_LIGHTS; i++) { shader_cubemap(va("shadowMap[%d]", i), m.shadow_map->maps[i].texture); - shader_texture_unit(va("shadowMap2D[%d]", i), m.shadow_map->maps[i].texture_2d, texture_unit()); + for (int j = 0; j < NUM_SHADOW_CASCADES; j++) { + shader_texture_unit(va("shadowMap2D[%d]", i * NUM_SHADOW_CASCADES + j), m.shadow_map->maps[i].texture_2d[j], texture_unit()); + shader_float(va("u_cascade_splits[%d]", j), m.shadow_map->cascade_splits[j]); + shader_float(va("u_cascade_distances[%d]", j), m.shadow_map->cascade_distances[j]); + } } } else { shader_bool("u_shadow_receiver", GL_FALSE); @@ -4603,6 +4871,7 @@ void model_set_renderstates(model_t *m) { shadow_rs->cull_face_enabled = 1; shadow_rs->cull_face_mode = GL_BACK; shadow_rs->front_face = GL_CW; + shadow_rs->depth_clamp_enabled = 1; } // Lightmap pass diff --git a/engine/split/v4k_render.h b/engine/split/v4k_render.h index 86a4cc4..1947b8e 100644 --- a/engine/split/v4k_render.h +++ b/engine/split/v4k_render.h @@ -66,8 +66,11 @@ typedef struct renderstate_t { // Scissor test bool scissor_test_enabled; - // bool Seamless Cubemap + // Seamless cubemap bool seamless_cubemap; + + // Depth clamp + bool depth_clamp_enabled; } renderstate_t; API renderstate_t renderstate(); @@ -296,6 +299,8 @@ enum SHADOW_TECHNIQUE { SHADOW_PCF, }; +#define NUM_SHADOW_CASCADES 4 + typedef struct light_t { char type; vec3 diffuse, specular, ambient; @@ -313,7 +318,7 @@ typedef struct light_t { unsigned shadow_technique; float shadow_distance; float shadow_bias; - mat44 shadow_matrix; + mat44 shadow_matrix[NUM_SHADOW_CASCADES]; // internals bool cached; //< used by scene to invalidate cached light data @@ -346,18 +351,26 @@ API void light_update(unsigned num_lights, light_t *lv); typedef struct shadowmap_t { mat44 V; mat44 PV; - int texture_width; + int vsm_texture_width; + int pcf_texture_width; int step; int light_step; + int cascade_index; unsigned shadow_technique; + float cascade_splits[NUM_SHADOW_CASCADES]; + float cascade_distances[NUM_SHADOW_CASCADES]; + bool blur_pcf; + float blur_scale; // signals bool skip_render; int lights_pushed; struct { + unsigned shadow_technique; handle fbos[6], texture, depth_texture; - handle fbo_2d, texture_2d, depth_texture_2d; + handle fbo_2d[NUM_SHADOW_CASCADES], texture_2d[NUM_SHADOW_CASCADES], depth_texture_2d[NUM_SHADOW_CASCADES]; + handle blur_fbo_2d, blur_texture_2d; } maps[MAX_LIGHTS]; handle saved_fb; @@ -365,12 +378,12 @@ typedef struct shadowmap_t { int saved_vp[4]; } shadowmap_t; -API shadowmap_t shadowmap(int texture_width); // = 1024 +API shadowmap_t shadowmap(int vsm_texture_width, int pcf_texture_width); // = 512, 4096 API void shadowmap_destroy(shadowmap_t *s); API void shadowmap_begin(shadowmap_t *s); API bool shadowmap_step(shadowmap_t *s); -API void shadowmap_light(shadowmap_t *s, light_t *l); //< can be called once per shadowmap_step +API void shadowmap_light(shadowmap_t *s, light_t *l, float cam_fov, mat44 cam_view); //< can be called once per shadowmap_step API void shadowmap_end(shadowmap_t *s); // ----------------------------------------------------------------------------- diff --git a/engine/split/v4k_scene.h b/engine/split/v4k_scene.h index e44489c..067763d 100644 --- a/engine/split/v4k_scene.h +++ b/engine/split/v4k_scene.h @@ -49,6 +49,7 @@ typedef struct object_t { aabb bounds; unsigned billboard; // [0..7] x(4),y(2),z(1) masks bool disable_frustum_check; + bool cast_shadows; // internal states array(handle) old_texture_ids; @@ -83,6 +84,7 @@ enum SCENE_FLAGS { SCENE_BACKGROUND = 4, SCENE_FOREGROUND = 8, SCENE_UPDATE_SH_COEF = 16, + SCENE_CAST_SHADOWS = 32, }; typedef struct scene_t { @@ -92,6 +94,7 @@ typedef struct scene_t { // special objects below: skybox_t skybox; int u_coefficients_sh; + shadowmap_t shadowmap; } scene_t; API scene_t* scene_push(); diff --git a/engine/v4k.c b/engine/v4k.c index 71a51e9..ca5af90 100644 --- a/engine/v4k.c +++ b/engine/v4k.c @@ -16921,6 +16921,9 @@ renderstate_t renderstate() { // Enable seamless cubemap by default state.seamless_cubemap = GL_TRUE; + // Disable depth clamp by default + state.depth_clamp_enabled = GL_FALSE; + return state; } @@ -17034,6 +17037,13 @@ void renderstate_apply(const renderstate_t *state) { } else { glDisable(GL_TEXTURE_CUBE_MAP_SEAMLESS); } + + // Apply depth clamp + if (state->depth_clamp_enabled) { + glEnable(GL_DEPTH_CLAMP); + } else { + glDisable(GL_DEPTH_CLAMP); + } } } @@ -18271,8 +18281,9 @@ light_t light() { l.specularPower = 32.f; l.innerCone = 0.85f;// 31 deg l.outerCone = 0.9f; // 25 deg + l.cast_shadows = true; l.shadow_distance = 100.0f; - l.shadow_bias = 0.5f; + l.shadow_bias = 0.01f; return l; } @@ -18351,7 +18362,9 @@ void light_update(unsigned num_lights, light_t *lv) { shader_float(va("u_lights[%d].quadratic", i), lv[i].falloff.quadratic); shader_float(va("u_lights[%d].innerCone", i), lv[i].innerCone); shader_float(va("u_lights[%d].outerCone", i), lv[i].outerCone); - shader_mat44(va("u_lights[%d].shadow_matrix", i), lv[i].shadow_matrix); + for (int j = 0; j < NUM_SHADOW_CASCADES; j++) { + shader_mat44(va("u_lights[%d].shadow_matrix[%d]", i, j), lv[i].shadow_matrix[j]); + } } } @@ -18359,10 +18372,14 @@ void light_update(unsigned num_lights, light_t *lv) { // ----------------------------------------------------------------------------- // shadowmaps -static inline -shadowmap_init_caster(shadowmap_t *s, int light_index, int texture_width) { +static inline void +shadowmap_init_caster_vsm(shadowmap_t *s, int light_index, int texture_width) { float borderColor[] = {1.0, 1.0, 1.0, 1.0}; + if (s->maps[light_index].texture) { + return; + } + // Create a cubemap color texture glGenTextures(1, &s->maps[light_index].texture); glBindTexture(GL_TEXTURE_CUBE_MAP, s->maps[light_index].texture); @@ -18403,70 +18420,147 @@ shadowmap_init_caster(shadowmap_t *s, int light_index, int texture_width) { if (GL_FRAMEBUFFER_COMPLETE != result) { PANIC("ERROR: Framebuffer is not complete: %x\n", result); } - // #if is(ems) - // GLenum nones[] = { GL_NONE }; - // glDrawBuffers(1, nones); - // glReadBuffer(GL_NONE); - // #else - // glDrawBuffer(GL_NONE); - // glReadBuffer(GL_NONE); - // #endif + } +} + +static inline void +shadowmap_init_caster_pcf(shadowmap_t *s, int light_index, int texture_width) { + float borderColor[] = {1.0, 1.0, 1.0, 1.0}; + + if (s->maps[light_index].texture_2d[0]) { + return; } // Initialise shadow map 2D - glGenTextures(1, &s->maps[light_index].texture_2d); - glBindTexture(GL_TEXTURE_2D, s->maps[light_index].texture_2d); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, texture_width, texture_width, 0, GL_RGB, GL_FLOAT, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); + for (int i = 0; i < NUM_SHADOW_CASCADES; i++) { + glGenTextures(1, &s->maps[light_index].texture_2d[i]); + glBindTexture(GL_TEXTURE_2D, s->maps[light_index].texture_2d[i]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, texture_width, texture_width, 0, GL_RGB, GL_FLOAT, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); - glGenTextures(1, &s->maps[light_index].depth_texture_2d); - glBindTexture(GL_TEXTURE_2D, s->maps[light_index].depth_texture_2d); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, texture_width, texture_width, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); + glGenTextures(1, &s->maps[light_index].depth_texture_2d[i]); + glBindTexture(GL_TEXTURE_2D, s->maps[light_index].depth_texture_2d[i]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, texture_width, texture_width, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); + } glBindTexture(GL_TEXTURE_2D, 0); - glGenFramebuffers(1, &s->maps[light_index].fbo_2d); - glBindFramebuffer(GL_FRAMEBUFFER, s->maps[light_index].fbo_2d); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, s->maps[light_index].texture_2d, 0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, s->maps[light_index].depth_texture_2d, 0); + for (int i = 0; i < NUM_SHADOW_CASCADES; i++) { + glGenFramebuffers(1, &s->maps[light_index].fbo_2d[i]); + glBindFramebuffer(GL_FRAMEBUFFER, s->maps[light_index].fbo_2d[i]); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, s->maps[light_index].texture_2d[i], 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, s->maps[light_index].depth_texture_2d[i], 0); + GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (GL_FRAMEBUFFER_COMPLETE != result) { + PANIC("ERROR: Framebuffer is not complete: %x\n", result); + } + } + + // Blur texture + glGenTextures(1, &s->maps[light_index].blur_texture_2d); + glBindTexture(GL_TEXTURE_2D, s->maps[light_index].blur_texture_2d); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, texture_width, texture_width, 0, GL_RGB, GL_FLOAT, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Blur FBO + glGenFramebuffers(1, &s->maps[light_index].blur_fbo_2d); + glBindFramebuffer(GL_FRAMEBUFFER, s->maps[light_index].blur_fbo_2d); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, s->maps[light_index].blur_texture_2d, 0); GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (GL_FRAMEBUFFER_COMPLETE != result) { PANIC("ERROR: Framebuffer is not complete: %x\n", result); } - } -shadowmap_t shadowmap(int texture_width) { // = 1024 +static inline +shadowmap_init_caster(shadowmap_t *s, int light_index) { + shadowmap_init_caster_vsm(s, light_index, s->vsm_texture_width); + shadowmap_init_caster_pcf(s, light_index, s->pcf_texture_width); +} + +shadowmap_t shadowmap(int vsm_texture_width, int pcf_texture_width) { // = 512, 4096 shadowmap_t s = {0}; - s.texture_width = texture_width; + s.vsm_texture_width = vsm_texture_width; + s.pcf_texture_width = pcf_texture_width; s.saved_fb = 0; + s.blur_pcf = true; + s.blur_scale = 0.5f; + s.cascade_splits[0] = 0.1f; + s.cascade_splits[1] = 0.3f; + s.cascade_splits[2] = 0.5f; + s.cascade_splits[3] = 1.0f; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &s.saved_fb); +#if 0 for (int i = 0; i < MAX_LIGHTS; i++) { - shadowmap_init_caster(&s, i, texture_width); + shadowmap_init_caster(&s, i); } - +#else + for (int i = 0; i < MAX_LIGHTS; i++) { + s.maps[i].shadow_technique = 0xFFFF; + } +#endif + glBindFramebuffer(GL_FRAMEBUFFER, s.saved_fb); return s; } +static inline +void shadowmap_destroy_light(shadowmap_t *s, int light_index) { + if (s->maps[light_index].fbos[0]) { + glDeleteFramebuffers(6, s->maps[light_index].fbos); + s->maps[light_index].fbos[0] = 0; + } + if (s->maps[light_index].texture) { + glDeleteTextures(1, &s->maps[light_index].texture); + s->maps[light_index].texture = 0; + } + if (s->maps[light_index].depth_texture) { + glDeleteTextures(1, &s->maps[light_index].depth_texture); + s->maps[light_index].depth_texture = 0; + } + + for (int i = 0; i < NUM_SHADOW_CASCADES; i++) { + if (s->maps[light_index].fbo_2d[i]) { + glDeleteFramebuffers(1, &s->maps[light_index].fbo_2d[i]); + s->maps[light_index].fbo_2d[i] = 0; + } + if (s->maps[light_index].texture_2d[i]) { + glDeleteTextures(1, &s->maps[light_index].texture_2d[i]); + s->maps[light_index].texture_2d[i] = 0; + } + if (s->maps[light_index].depth_texture_2d[i]) { + glDeleteTextures(1, &s->maps[light_index].depth_texture_2d[i]); + s->maps[light_index].depth_texture_2d[i] = 0; + } + } + + if (s->maps[light_index].blur_fbo_2d) { + glDeleteFramebuffers(1, &s->maps[light_index].blur_fbo_2d); + s->maps[light_index].blur_fbo_2d = 0; + } + if (s->maps[light_index].blur_texture_2d) { + glDeleteTextures(1, &s->maps[light_index].blur_texture_2d); + s->maps[light_index].blur_texture_2d = 0; + } +} + void shadowmap_destroy(shadowmap_t *s) { for (int i = 0; i < MAX_LIGHTS; i++) { - glDeleteFramebuffers(6, s->maps[i].fbos); - glDeleteTextures(1, &s->maps[i].texture); - glDeleteTextures(1, &s->maps[i].depth_texture); - glDeleteFramebuffers(1, &s->maps[i].fbo_2d); - glDeleteTextures(1, &s->maps[i].texture_2d); - glDeleteTextures(1, &s->maps[i].depth_texture_2d); + shadowmap_destroy_light(s, i); } shadowmap_t z = {0}; *s = z; @@ -18481,6 +18575,7 @@ void shadowmap_begin(shadowmap_t *s) { s->saved_pass = model_setpass(RENDER_PASS_SHADOW); s->step = 0; s->light_step = 0; + s->cascade_index = 0; active_shadowmap = s; } @@ -18504,48 +18599,206 @@ static void shadowmap_light_point(shadowmap_t *s, light_t *l, int dir) { s->shadow_technique = l->shadow_technique = SHADOW_VSM; } -static void shadowmap_light_directional(shadowmap_t *s, light_t *l, int dir) { +static array(vec3) frustum_corners = 0; + +static inline +void shadowmap_light_directional_calc_frustum_corners(mat44 cam_proj, mat44 cam_view) { + mat44 PV; multiply44x2(PV, cam_proj, cam_view); + mat44 inverse_view_proj; invert44(inverse_view_proj, PV); + array_resize(frustum_corners, 0); + for (unsigned x = 0; x < 2; x++) { + for (unsigned y = 0; y < 2; y++) { + for (unsigned z = 0; z < 2; z++) { + vec4 corner = { + x * 2.0f - 1.0f, + y * 2.0f - 1.0f, + z * 2.0f - 1.0f, + 1.0f + }; + vec4 world_corner = transform444(inverse_view_proj, corner); + world_corner = scale4(world_corner, 1.0f / world_corner.w); + array_push(frustum_corners, vec3(world_corner.x, world_corner.y, world_corner.z)); + } + } + } +} + +static void shadowmap_light_directional(shadowmap_t *s, light_t *l, int dir, float cam_fov, mat44 cam_view) { if (dir != 0) { s->skip_render = true; return; } - - mat44 P, V, PV; - l->shadow_distance = 25.0f; - ortho44(P, - -l->shadow_distance/2.0, l->shadow_distance/2.0, - -l->shadow_distance/2.0, l->shadow_distance/2.0, - l->shadow_bias, l->shadow_distance); + float far_plane = 0.0f; + float near_plane = 0.0f; + + if (s->cascade_index == 0) { + near_plane = l->shadow_bias; + far_plane = l->shadow_distance * s->cascade_splits[0]; + } else if (s->cascade_index < NUM_SHADOW_CASCADES - 1) { + near_plane = l->shadow_distance * s->cascade_splits[s->cascade_index-1]; + far_plane = l->shadow_distance * s->cascade_splits[s->cascade_index]; + } else { + near_plane = l->shadow_distance * s->cascade_splits[NUM_SHADOW_CASCADES-1]; + far_plane = l->shadow_distance; + } + + mat44 proj; perspective44(proj, cam_fov, window_width() / (float)window_height(), near_plane, far_plane); + shadowmap_light_directional_calc_frustum_corners(proj, cam_view); + + vec3 center = {0,0,0}; + for (unsigned i = 0; i < array_count(frustum_corners); i++) { + center = add3(center, frustum_corners[i]); + } + center = scale3(center, 1.0f / array_count(frustum_corners)); + + + s->cascade_distances[s->cascade_index] = far_plane; + + float minX = FLT_MAX, maxX = FLT_MIN; + float minY = FLT_MAX, maxY = FLT_MIN; + float minZ = FLT_MAX, maxZ = FLT_MIN; + + mat44 V; vec3 lightDir = norm3(l->dir); vec3 up = vec3(0, 1, 0); - // Ensure up vector is not parallel to light direction - if (fabs(dot3(lightDir, up)) > 0.99f) { - up = vec3(0, 0, 1); + lookat44(V, sub3(center, lightDir), center, up); + + + for (unsigned i = 0; i < array_count(frustum_corners); i++) { + vec3 corner = frustum_corners[i]; + corner = transform344(V, corner); + minX = min(minX, corner.x); + maxX = max(maxX, corner.x); + minY = min(minY, corner.y); + maxY = max(maxY, corner.y); + minZ = min(minZ, corner.z); + maxZ = max(maxZ, corner.z); } - - vec3 center = vec3(0, 0, 0); - vec3 lightPos = sub3(center, scale3(lightDir, l->shadow_distance*0.5f)); - lookat44(V, lightPos, center, up); + + float zMult = 10.0f; + + // if (minZ < 0) { + // minZ *= zMult; + // } else { + // minZ /= zMult; + // } + // if (maxZ < 0) { + // maxZ /= zMult; + // } else { + // maxZ *= zMult; + // } + + + mat44 P, PV; + ortho44(P, + minX, maxX, + minY, maxY, + minZ, maxZ); multiply44x2(PV, P, V); copy44(s->V, V); copy44(s->PV, PV); - copy44(l->shadow_matrix, PV); + copy44(l->shadow_matrix[s->cascade_index], PV); s->shadow_technique = l->shadow_technique = SHADOW_PCF; } -bool shadowmap_step(shadowmap_t *s) { - if (s->step >= 6) { - s->step = 0; - s->light_step++; - return false; +static inline +void shadowmap_blur_pcf(shadowmap_t *s, int light_index) { + if (!s->blur_pcf) { + return; } - glViewport(0, 0, s->texture_width, s->texture_width); + float blur_scale = 1.999 * (1 - s->blur_scale) + 0.001; + // blur_scale = 0.1f; + + static renderstate_t rs; + static int program = -1, vao = -1, u_scale = -1, u_source = -1; + if (program < 0) { + rs = renderstate(); { + rs.depth_test_enabled = false; + rs.depth_write_enabled = false; + rs.blend_enabled = false; + } + const char* vs = vfs_read("shaders/vs_shadow_blur.glsl"); + const char* fs = vfs_read("shaders/fs_shadow_blur.glsl"); + + program = shader(vs, fs, "", "fragcolor", NULL); + u_scale = glGetUniformLocation(program, "ScaleU"); + u_source = glGetUniformLocation(program, "textureSource"); + glGenVertexArrays(1, &vao); + } + + renderstate_apply(&rs); + glViewport(0, 0, s->pcf_texture_width, s->pcf_texture_width); + + unsigned oldprog = last_shader; + glUseProgram(program); + + glBindVertexArray(vao); + glActiveTexture(GL_TEXTURE0); + + // Horizontal pass + for (int i = 0; i < NUM_SHADOW_CASCADES; i++) { + glBindFramebuffer(GL_FRAMEBUFFER, s->maps[light_index].blur_fbo_2d); + glUniform2f(u_scale, 1.0f / (s->pcf_texture_width * blur_scale), 0); + glBindTexture(GL_TEXTURE_2D, s->maps[light_index].texture_2d[i]); + glUniform1i(u_source, 0); + //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glDrawArrays(GL_TRIANGLES, 0, 6); + profile_incstat("Render.num_drawcalls", +1); + profile_incstat("Render.num_triangles", +2); + + // Vertical pass + glBindFramebuffer(GL_FRAMEBUFFER, s->maps[light_index].fbo_2d[i]); + glUniform2f(u_scale, 0, 1.0f / (s->pcf_texture_width * blur_scale)); + glBindTexture(GL_TEXTURE_2D, s->maps[light_index].blur_texture_2d); + glUniform1i(u_source, 0); + //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glDrawArrays(GL_TRIANGLES, 0, 6); + profile_incstat("Render.num_drawcalls", +1); + profile_incstat("Render.num_triangles", +2); + } + + glBindVertexArray(0); + glUseProgram(oldprog); +} + +static inline +bool shadowmap_step_finish(shadowmap_t *s) { + if (s->shadow_technique == SHADOW_PCF) { + if (s->cascade_index < NUM_SHADOW_CASCADES - 1) { + s->cascade_index++; + s->step = 0; + return false; + } + shadowmap_blur_pcf(s, s->light_step); + } + + s->step = 0; + s->light_step++; + s->cascade_index = 0; + return true; +} + +bool shadowmap_step(shadowmap_t *s) { + int max_steps = s->shadow_technique == 0xffff ? 1 : s->shadow_technique == SHADOW_PCF ? 1 : 6; + if (s->step >= max_steps) { + if (shadowmap_step_finish(s)) { + return false; + } else { + return true; + } + } + + unsigned texture_width = s->shadow_technique == SHADOW_VSM ? s->vsm_texture_width : s->pcf_texture_width; + + glViewport(0, 0, texture_width, texture_width); s->step++; s->skip_render = false; @@ -18555,12 +18808,12 @@ bool shadowmap_step(shadowmap_t *s) { static inline void shadowmap_clear_fbo() { - glClearColor(0, 0, 0, 0); + glClearColor(1, 1, 1, 1); glClearDepth(1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } -void shadowmap_light(shadowmap_t *s, light_t *l) { +void shadowmap_light(shadowmap_t *s, light_t *l, float cam_fov, mat44 cam_view) { if (l->cast_shadows) { int step = s->step - 1; @@ -18569,18 +18822,29 @@ void shadowmap_light(shadowmap_t *s, light_t *l) { } else if (l->type == LIGHT_SPOT) { shadowmap_light_point(s, l, step); } else if (l->type == LIGHT_DIRECTIONAL) { - shadowmap_light_directional(s, l, step); + shadowmap_light_directional(s, l, step, cam_fov, cam_view); } if (s->skip_render) { return; } + if (s->maps[s->light_step].shadow_technique != l->shadow_technique) { + // shadowmap_destroy_light(s, s->light_step); // @todo: we might wanna free the other set + if (l->shadow_technique == SHADOW_VSM) { + shadowmap_init_caster_vsm(s, s->light_step, s->vsm_texture_width); + } else if (l->shadow_technique == SHADOW_PCF) { + shadowmap_init_caster_pcf(s, s->light_step, s->pcf_texture_width); + } + } + + s->maps[s->light_step].shadow_technique = l->shadow_technique; + ASSERT(s->lights_pushed == 0); s->lights_pushed++; if (l->type == LIGHT_DIRECTIONAL) { - glBindFramebuffer(GL_FRAMEBUFFER, s->maps[s->light_step].fbo_2d); + glBindFramebuffer(GL_FRAMEBUFFER, s->maps[s->light_step].fbo_2d[s->cascade_index]); shadowmap_clear_fbo(); } else { glBindFramebuffer(GL_FRAMEBUFFER, s->maps[s->light_step].fbos[step]); @@ -20774,7 +21038,11 @@ void model_set_uniforms(model_t m, int shader, mat44 mv, mat44 proj, mat44 view, shader_bool("u_shadow_receiver", GL_TRUE); for (int i = 0; i < MAX_LIGHTS; i++) { shader_cubemap(va("shadowMap[%d]", i), m.shadow_map->maps[i].texture); - shader_texture_unit(va("shadowMap2D[%d]", i), m.shadow_map->maps[i].texture_2d, texture_unit()); + for (int j = 0; j < NUM_SHADOW_CASCADES; j++) { + shader_texture_unit(va("shadowMap2D[%d]", i * NUM_SHADOW_CASCADES + j), m.shadow_map->maps[i].texture_2d[j], texture_unit()); + shader_float(va("u_cascade_splits[%d]", j), m.shadow_map->cascade_splits[j]); + shader_float(va("u_cascade_distances[%d]", j), m.shadow_map->cascade_distances[j]); + } } } else { shader_bool("u_shadow_receiver", GL_FALSE); @@ -21402,6 +21670,7 @@ void model_set_renderstates(model_t *m) { shadow_rs->cull_face_enabled = 1; shadow_rs->cull_face_mode = GL_BACK; shadow_rs->front_face = GL_CW; + shadow_rs->depth_clamp_enabled = 1; } // Lightmap pass diff --git a/engine/v4k.h b/engine/v4k.h index 7c7de32..4be6b76 100644 --- a/engine/v4k.h +++ b/engine/v4k.h @@ -3101,8 +3101,11 @@ typedef struct renderstate_t { // Scissor test bool scissor_test_enabled; - // bool Seamless Cubemap + // Seamless cubemap bool seamless_cubemap; + + // Depth clamp + bool depth_clamp_enabled; } renderstate_t; API renderstate_t renderstate(); @@ -3331,6 +3334,8 @@ enum SHADOW_TECHNIQUE { SHADOW_PCF, }; +#define NUM_SHADOW_CASCADES 4 + typedef struct light_t { char type; vec3 diffuse, specular, ambient; @@ -3348,7 +3353,7 @@ typedef struct light_t { unsigned shadow_technique; float shadow_distance; float shadow_bias; - mat44 shadow_matrix; + mat44 shadow_matrix[NUM_SHADOW_CASCADES]; // internals bool cached; //< used by scene to invalidate cached light data @@ -3381,18 +3386,26 @@ API void light_update(unsigned num_lights, light_t *lv); typedef struct shadowmap_t { mat44 V; mat44 PV; - int texture_width; + int vsm_texture_width; + int pcf_texture_width; int step; int light_step; + int cascade_index; unsigned shadow_technique; + float cascade_splits[NUM_SHADOW_CASCADES]; + float cascade_distances[NUM_SHADOW_CASCADES]; + bool blur_pcf; + float blur_scale; // signals bool skip_render; int lights_pushed; struct { + unsigned shadow_technique; handle fbos[6], texture, depth_texture; - handle fbo_2d, texture_2d, depth_texture_2d; + handle fbo_2d[NUM_SHADOW_CASCADES], texture_2d[NUM_SHADOW_CASCADES], depth_texture_2d[NUM_SHADOW_CASCADES]; + handle blur_fbo_2d, blur_texture_2d; } maps[MAX_LIGHTS]; handle saved_fb; @@ -3400,12 +3413,12 @@ typedef struct shadowmap_t { int saved_vp[4]; } shadowmap_t; -API shadowmap_t shadowmap(int texture_width); // = 1024 +API shadowmap_t shadowmap(int vsm_texture_width, int pcf_texture_width); // = 512, 4096 API void shadowmap_destroy(shadowmap_t *s); API void shadowmap_begin(shadowmap_t *s); API bool shadowmap_step(shadowmap_t *s); -API void shadowmap_light(shadowmap_t *s, light_t *l); //< can be called once per shadowmap_step +API void shadowmap_light(shadowmap_t *s, light_t *l, float cam_fov, mat44 cam_view); //< can be called once per shadowmap_step API void shadowmap_end(shadowmap_t *s); // ----------------------------------------------------------------------------- @@ -4060,6 +4073,7 @@ typedef struct object_t { aabb bounds; unsigned billboard; // [0..7] x(4),y(2),z(1) masks bool disable_frustum_check; + bool cast_shadows; // internal states array(handle) old_texture_ids; @@ -4094,6 +4108,7 @@ enum SCENE_FLAGS { SCENE_BACKGROUND = 4, SCENE_FOREGROUND = 8, SCENE_UPDATE_SH_COEF = 16, + SCENE_CAST_SHADOWS = 32, }; typedef struct scene_t { @@ -4103,6 +4118,7 @@ typedef struct scene_t { // special objects below: skybox_t skybox; int u_coefficients_sh; + shadowmap_t shadowmap; } scene_t; API scene_t* scene_push();