render3d: r3d_fog_wall(dist) - fog that closes to the sky's horizon colour from 0.3 dist to dist, and nothing drawn past dist + 8 m
Every lit program blends toward the horizon colour (the prefiltered sky with the sun's inscatter, so it carries the day's grade); the sky's horizon band is pulled to the same colour, wider the closer the wall. The far plane, scatter and stream cull reach and the shadow cascades are clamped to the wall; the reflection pass shares the shaders and the cull. 0 is off: every branch is gated on u_fog_wall > 0 and r3d_reach hands back the far it was given. R3D_FOG_WALL=<m> for a shot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
160ce96e4f
commit
b9f9cb8495
67 changed files with 335 additions and 227 deletions
|
|
@ -37,7 +37,7 @@ function cam_update(render3d_st: mut Render3dState) -> void {
|
|||
let py = render3d_st.cam_pos[1]
|
||||
let pz = render3d_st.cam_pos[2]
|
||||
m4_look_at_xyz(render3d_st.cam_view, px, py, pz, px + render3d_st.cam_fwd[0], py + render3d_st.cam_fwd[1], pz + render3d_st.cam_fwd[2], 0.0, 1.0, 0.0)
|
||||
m4_perspective(render3d_st.cam_proj, render3d_st.cam_fov, render3d_st.cam_aspect, render3d_st.cam_near, render3d_st.cam_far)
|
||||
m4_perspective(render3d_st.cam_proj, render3d_st.cam_fov, render3d_st.cam_aspect, render3d_st.cam_near, r3d_reach(render3d_st, render3d_st.cam_far))
|
||||
m4_mul(render3d_st.cam_vp_clean, render3d_st.cam_proj, render3d_st.cam_view)
|
||||
m4_inverse(render3d_st.cam_inv_vp_clean, render3d_st.cam_vp_clean)
|
||||
# DLSS's sub-pixel jitter (streamline.ludic), in the projection the scene draws with only:
|
||||
|
|
|
|||
|
|
@ -584,6 +584,7 @@ export state Render3dState {
|
|||
q_sz: floats = null
|
||||
q_st: floats = null
|
||||
r3d_sky_prog: int = 0
|
||||
r3d_fog_wall: float = 0.0 # r3d_fog_wall: > 0, nothing past it is seen or drawn
|
||||
r3d_fog_density: float = 0.0
|
||||
r3d_fog_falloff: float = 0.0
|
||||
r3d_fog_inscatter: float = 0.02 # 0.02, what the shader used to hard-code
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ function fog_bind(render3d_st: mut Render3dState, prog: int) -> void {
|
|||
u_f(render3d_st, gpu_uniform(render3d_st, prog, "u_fog_base"), render3d_st.r3d_fog_base)
|
||||
u_f(render3d_st, gpu_uniform(render3d_st, prog, "u_fog_inscatter"), render3d_st.r3d_fog_inscatter)
|
||||
u_f(render3d_st, gpu_uniform(render3d_st, prog, "u_fog_desat"), render3d_st.r3d_fog_desat)
|
||||
u_f(render3d_st, gpu_uniform(render3d_st, prog, "u_fog_wall"), render3d_st.r3d_fog_wall)
|
||||
u_f3(render3d_st, gpu_uniform(render3d_st, prog, "u_ground_alb"), render3d_st.r3d_ground_alb_r, render3d_st.r3d_ground_alb_g, render3d_st.r3d_ground_alb_b)
|
||||
u_f3(render3d_st, gpu_uniform(render3d_st, prog, "u_ground_alb_hi"), render3d_st.r3d_ground_hi_r, render3d_st.r3d_ground_hi_g, render3d_st.r3d_ground_hi_b)
|
||||
u_f(render3d_st, gpu_uniform(render3d_st, prog, "u_ground_hi_y"), render3d_st.r3d_ground_hi_y)
|
||||
|
|
@ -46,11 +47,31 @@ function fog_bind(render3d_st: mut Render3dState, prog: int) -> void {
|
|||
u_f(render3d_st, gpu_uniform(render3d_st, prog, "u_time"), render3d_st.r3d_time)
|
||||
}
|
||||
|
||||
# The fog wall (a game's fog setting): dist > 0 hides everything past dist in the sky's colour - from
|
||||
# three tenths of it to all of it - and draws nothing past it in any pass: the far plane, every scatter
|
||||
# layer's reach, the shadow cascades. dist <= 0 leaves everything as it was.
|
||||
const R3D_FOG_MARGIN: float = 8.0
|
||||
export function r3d_fog_wall(render3d_st: mut Render3dState, dist: float) -> void {
|
||||
var d = dist
|
||||
if d < 0.0 { d = 0.0 }
|
||||
render3d_st.r3d_fog_wall = d
|
||||
cam_update(render3d_st)
|
||||
}
|
||||
# how far anything is drawn: the fog wall and a margin, or `far` (0: no limit of its own) without one
|
||||
function r3d_reach(render3d_st: Render3dState, far: float) -> float {
|
||||
if render3d_st.r3d_fog_wall <= 0.0 { return far }
|
||||
let w = render3d_st.r3d_fog_wall + R3D_FOG_MARGIN
|
||||
if far <= 0.0 or far > w { return w }
|
||||
return far
|
||||
}
|
||||
|
||||
# profiling switches (environment): R3D_NOSHADOW R3D_NOGI R3D_MSAA=n R3D_NOBLADES R3D_NOCARDS R3D_NOTREES R3D_NEAR=m
|
||||
function r3d_env_flags(render3d_st: mut Render3dState) -> void {
|
||||
render3d_st.r3d_no_shadow = r3d_env_has(render3d_st, "R3D_NOSHADOW")
|
||||
render3d_st.r3d_no_trees = r3d_env_has(render3d_st, "R3D_NOTREES")
|
||||
render3d_st.r3d_no_refl = r3d_env_has(render3d_st, "R3D_NOREFL")
|
||||
# R3D_FOG_WALL=<m>: the fog wall at that distance, for a shot (a game sets it with r3d_fog_wall)
|
||||
if r3d_env_has(render3d_st, "R3D_FOG_WALL") { render3d_st.r3d_fog_wall = float(Text.to_int(r3d_env(render3d_st, "R3D_FOG_WALL"))) }
|
||||
if r3d_env_has(render3d_st, "R3D_DEBUG") { render3d_st.r3d_debug = true }
|
||||
if r3d_env_has(render3d_st, "R3D_DBGSHADOW") { render3d_st.r3d_debug_shadow = true }
|
||||
if r3d_env_has(render3d_st, "R3D_NOGI") { render3d_st.post_gi_strength = 0.0; render3d_st.post_ao_strength = 0.0; render3d_st.post_no_gi = true }
|
||||
|
|
@ -152,6 +173,8 @@ function r3d_draw_sky(render3d_st: mut Render3dState) -> void {
|
|||
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_sky_gain"), 0.95)
|
||||
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_sky_relight"), render3d_st.r3d_sky_relight)
|
||||
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_sky_sat"), 1.35)
|
||||
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_fog_wall"), render3d_st.r3d_fog_wall)
|
||||
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_fog_inscatter"), render3d_st.r3d_fog_inscatter)
|
||||
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_time"), render3d_st.r3d_time)
|
||||
mesh_draw(render3d_st, render3d_st.sky_fullscreen)
|
||||
gpu_depth_write(render3d_st, true)
|
||||
|
|
|
|||
|
|
@ -561,7 +561,7 @@ function layer_gpu_cull(render3d_st: mut Render3dState, l: Layer) -> void {
|
|||
let pr = render3d_st.sc_gpu_pr
|
||||
for i in 0 .. 36 { pr[i] = 0 }
|
||||
if render3d_st.cam_planes != null { for i in 0 .. 16 { pr[i] = float_bits(render3d_st.cam_planes[i]) } }
|
||||
pr[16] = float_bits(render3d_st.cam_pos[0]); pr[17] = float_bits(render3d_st.cam_pos[1]); pr[18] = float_bits(render3d_st.cam_pos[2]); pr[19] = float_bits(l.cull)
|
||||
pr[16] = float_bits(render3d_st.cam_pos[0]); pr[17] = float_bits(render3d_st.cam_pos[1]); pr[18] = float_bits(render3d_st.cam_pos[2]); pr[19] = float_bits(r3d_reach(render3d_st, l.cull))
|
||||
for k in 0 .. l.n_lods { pr[20 + k] = float_bits(l.lod_dist[k]); pr[24 + k] = len(l.lods[k].prims) }
|
||||
pr[28] = l.count; pr[29] = l.count; pr[30] = l.n_lods; pr[31] = 1
|
||||
# as layer_grid_gather pads a cell: the tallest instance, plus a margin
|
||||
|
|
@ -622,7 +622,7 @@ function layer_grid_build(render3d_st: mut Render3dState, l: Layer, cell: float)
|
|||
function layer_grid_gather(render3d_st: Render3dState, l: Layer) -> void {
|
||||
let cell = l.gcell
|
||||
let half = cell * 0.5
|
||||
let reach = l.cull + cell * 0.71
|
||||
let reach = r3d_reach(render3d_st, l.cull) + cell * 0.71
|
||||
var n = 0
|
||||
for iz in 0 .. l.gnz {
|
||||
let wz = l.gz0 + float(iz) * cell + half
|
||||
|
|
@ -631,7 +631,7 @@ function layer_grid_gather(render3d_st: Render3dState, l: Layer) -> void {
|
|||
let cnt = l.gstart[c + 1] - l.gstart[c]
|
||||
if cnt == 0 { continue }
|
||||
let wx = l.gx0 + float(ix) * cell + half
|
||||
if l.cull != 0.0 {
|
||||
if r3d_reach(render3d_st, l.cull) != 0.0 {
|
||||
let dx = wx - render3d_st.cam_pos[0]; let dz = wz - render3d_st.cam_pos[2]
|
||||
if Math.sqrt(dx * dx + dz * dz) > reach { continue }
|
||||
}
|
||||
|
|
@ -654,14 +654,15 @@ function layer_partition_lods(render3d_st: mut Render3dState, l: Layer, src: flo
|
|||
if n + 2 > SC_LOD_ROOM { return } # past the room made with the state
|
||||
let counts = render3d_st.sc_lod_counts
|
||||
for k in 0 .. n + 2 { counts[k] = 0 }
|
||||
let cull2 = l.cull * l.cull
|
||||
let lc = r3d_reach(render3d_st, l.cull)
|
||||
let cull2 = lc * lc
|
||||
let open = l.lod_dist[n - 1] == 0.0 # the last level runs out to the cull distance
|
||||
for i in 0 .. total {
|
||||
let o = i * INST_FLOATS
|
||||
let dx = src[o] - render3d_st.cam_pos[0]; let dz = src[o + 2] - render3d_st.cam_pos[2]
|
||||
let d2 = dx * dx + dz * dz
|
||||
var lv = n + 1 # n + 1 = dropped
|
||||
if l.cull == 0.0 or not (d2 > cull2) {
|
||||
if lc == 0.0 or not (d2 > cull2) {
|
||||
let d = Math.sqrt(d2)
|
||||
lv = n # n = the impostor bucket
|
||||
var k = 0
|
||||
|
|
@ -727,7 +728,8 @@ function layer_update(render3d_st: mut Render3dState, l: Layer) -> void {
|
|||
var total = l.count
|
||||
if l.gcell != 0.0 { layer_grid_gather(render3d_st, l); src = l.vis; total = l.n_vis }
|
||||
let near2 = l.near * l.near
|
||||
let cull2 = l.cull * l.cull
|
||||
let lc = r3d_reach(render3d_st, l.cull)
|
||||
let cull2 = lc * lc
|
||||
var nn = 0
|
||||
var nf = 0
|
||||
let far_off = l.cap * INST_FLOATS # far instances fill the scratch from its end backwards
|
||||
|
|
@ -743,7 +745,7 @@ function layer_update(render3d_st: mut Render3dState, l: Layer) -> void {
|
|||
let dx = src[o] - render3d_st.cam_pos[0]
|
||||
let dz = src[o + 2] - render3d_st.cam_pos[2]
|
||||
let d2 = dx * dx + dz * dz
|
||||
if l.cull != 0.0 and d2 > cull2 { i += 1; continue }
|
||||
if lc != 0.0 and d2 > cull2 { i += 1; continue }
|
||||
if l.near == 0.0 or d2 < near2 {
|
||||
let q = nn * INST_FLOATS
|
||||
for k in 0 .. INST_FLOATS { tmp[q + k] = src[o + k] }
|
||||
|
|
@ -905,7 +907,7 @@ function layer_draw_model(render3d_st: mut Render3dState, l: Layer, model: Model
|
|||
}
|
||||
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_rough_scale"), l.rough)
|
||||
if l.blade { u_v3(render3d_st, gpu_uniform(render3d_st, p, "u_blade_base"), render3d_st.sc_blade_base); u_v3(render3d_st, gpu_uniform(render3d_st, p, "u_blade_tip"), render3d_st.sc_blade_tip) }
|
||||
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_cull"), l.cull)
|
||||
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_cull"), r3d_reach(render3d_st, l.cull))
|
||||
sky_bind_lighting(render3d_st, p)
|
||||
shadow_bind(render3d_st, p)
|
||||
fog_bind(render3d_st, p)
|
||||
|
|
|
|||
|
|
@ -285,6 +285,13 @@ vec3 shade(vec3 wpos, vec3 n, vec3 albedo, float rough, float metal, float ao, f
|
|||
// is that air.
|
||||
uniform float u_fog_inscatter; // how hard the air scatters the sun forward
|
||||
uniform float u_fog_desat; // how fast distance takes a surface's own colour away
|
||||
uniform float u_fog_wall; // r3d_fog_wall: > 0, the distance past which nothing is seen
|
||||
// the colour the fog wall hides things in: the sky at the horizon under this view, and the sun
|
||||
// scattered through it, so what is far melts into the sky behind it (the sky's band meets it)
|
||||
vec3 fogWallCol(vec3 dir) {
|
||||
vec3 c = skyPrefiltered(vec3(dir.x, max(dir.y, 0.02), dir.z), 0.6);
|
||||
return c + u_sun_color * u_fog_inscatter * pow(max(dot(dir, u_sun_dir), 0.0), 8.0);
|
||||
}
|
||||
vec3 applyFog(vec3 col, vec3 wpos, float dist) {
|
||||
vec3 dir = normalize(wpos - u_cam_pos);
|
||||
float hf = u_fog_height_falloff;
|
||||
|
|
@ -306,5 +313,11 @@ vec3 applyFog(vec3 col, vec3 wpos, float dist) {
|
|||
// hour rather than only at the one where the sky was already grey.
|
||||
float l = dot(col, vec3(0.2126, 0.7152, 0.0722));
|
||||
col = mix(col, vec3(l), clamp(f * u_fog_desat, 0.0, 1.0));
|
||||
return mix(col, fogCol, f);
|
||||
col = mix(col, fogCol, f);
|
||||
// the fog wall: from three tenths of its distance to all of it, everything goes to the sky's colour
|
||||
if (u_fog_wall > 0.0) {
|
||||
float w = smoothstep(0.3 * u_fog_wall, u_fog_wall, dist);
|
||||
col = mix(col, fogWallCol(dir), w);
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,5 +112,11 @@ void main() {
|
|||
float below = smoothstep(0.0, -0.08, dir.y);
|
||||
vec3 fogCol = skyPrefiltered(vec3(dir.x, 0.02, dir.z), 0.6);
|
||||
col = mix(col, fogCol, below);
|
||||
// under a fog wall the horizon band is the fog's own colour, higher the thicker the fog, so what
|
||||
// the wall hides meets the sky with no seam; the sky overhead stays
|
||||
if (u_fog_wall > 0.0) {
|
||||
float band = clamp(90.0 / u_fog_wall, 0.06, 0.75);
|
||||
col = mix(col, fogWallCol(dir), 1.0 - smoothstep(-0.02, band, dir.y));
|
||||
}
|
||||
o_color = vec4(sane(col), 1.0);
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
|
@ -142,17 +142,22 @@ function shadow_pass(render3d_st: mut Render3dState) -> void {
|
|||
gpu_depth_func(render3d_st, GL_LESS)
|
||||
gpu_depth_bias(render3d_st, 2.0, 4.0)
|
||||
gpu_cull(render3d_st, false)
|
||||
# a fog wall: a cascade reaches no further than it, and one that begins past it is only cleared
|
||||
let wall = r3d_reach(render3d_st, 0.0)
|
||||
for c in 0 .. SHADOW_CASCADES {
|
||||
render3d_st.sh_cascade = c
|
||||
shadow_fit(render3d_st, c, near, render3d_st.sh_split[c])
|
||||
var far = render3d_st.sh_split[c]
|
||||
if wall > 0.0 and far > wall { far = Math.max(wall, near + 1.0) }
|
||||
shadow_fit(render3d_st, c, near, far)
|
||||
prof_cpu_mark(render3d_st, "shadow fit")
|
||||
let past = wall > 0.0 and near >= wall
|
||||
near = render3d_st.sh_split[c]
|
||||
gpu_fb_depth_layer(render3d_st, render3d_st.sh_tex, c)
|
||||
if render3d_st.r3d_debug and c == 0 { shadow_debug_fbo(render3d_st) }
|
||||
gpu_clear(render3d_st, GL_DEPTH_BUFFER_BIT)
|
||||
let vp = shadow_cascade_vp(render3d_st, c)
|
||||
# shadows off (a video setting): the cascades stay cleared, so everything reads lit
|
||||
if render3d_st.sh_enabled {
|
||||
if render3d_st.sh_enabled and not past {
|
||||
if not render3d_st.sh_skip_terrain { terrain_draw_shadow(vp) }
|
||||
r3d_scene_casters(render3d_st, vp)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@ function gsl_constants(render3d_st: mut Render3dState) -> void {
|
|||
let fix = m4_new(); let proj = m4_new(); let v2c = m4_new(); let c2v = m4_new()
|
||||
let cur = m4_new(); let prev = m4_new(); let inv_cur = m4_new(); let c2p = m4_new(); let p2c = m4_new()
|
||||
gsl_clip_fix(fix)
|
||||
m4_perspective(proj, render3d_st.cam_fov, render3d_st.cam_aspect, render3d_st.cam_near, render3d_st.cam_far)
|
||||
m4_perspective(proj, render3d_st.cam_fov, render3d_st.cam_aspect, render3d_st.cam_near, r3d_reach(render3d_st, render3d_st.cam_far))
|
||||
m4_mul(v2c, fix, proj)
|
||||
m4_inverse(c2v, v2c)
|
||||
gsl_put_m4(k, 32, v2c) # cameraViewToClip (no jitter)
|
||||
|
|
@ -393,7 +393,7 @@ function gsl_constants(render3d_st: mut Render3dState) -> void {
|
|||
gsl_put_v3(k, 400, render3d_st.cam_right)
|
||||
gsl_put_v3(k, 412, render3d_st.cam_fwd)
|
||||
Vk.put_i32(k, 424, float_bits(render3d_st.cam_near))
|
||||
Vk.put_i32(k, 428, float_bits(render3d_st.cam_far))
|
||||
Vk.put_i32(k, 428, float_bits(r3d_reach(render3d_st, render3d_st.cam_far)))
|
||||
Vk.put_i32(k, 432, float_bits(render3d_st.cam_fov))
|
||||
Vk.put_i32(k, 436, float_bits(render3d_st.cam_aspect))
|
||||
Vk.put_i32(k, 440, float_bits(0.0)) # motionVectorsInvalidValue
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue