feat(lang): strict numbers in float files; render3d on float
A numbers float file adapts decimal literals to a fixed operand or slot, and refuses to promote a computed int to a float implicitly: there it is almost always float bits. Explicit float(x) is always allowed. render3d's numbers are float, converted by tools/migrate/floatbits.py - a whole-program inference of which ints carried IEEE bits (union-find over flows, calls, returns, buffers, nested buffers and lexical scopes) and a rewriter to operators, Math.* and float literals, with float_bits / float_from_bits left only where bits really cross (runtime scratch buffers, mixed buffers). Seed regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
3ac0d8d5cb
commit
cc89fc37a4
35 changed files with 57282 additions and 54382 deletions
|
|
@ -13,14 +13,14 @@
|
|||
|
||||
property Actor {
|
||||
model: Model,
|
||||
x: int = 0, # float bits, metres
|
||||
y: int = 0,
|
||||
z: int = 0,
|
||||
yaw: int = 0, # radians; 0 faces -z like the camera
|
||||
scale: int = 0,
|
||||
tint: words,
|
||||
rough: int = 0,
|
||||
mat: words, # the model matrix
|
||||
x: float = 0.0, # float bits, metres
|
||||
y: float = 0.0,
|
||||
z: float = 0.0,
|
||||
yaw: float = 0.0, # radians; 0 faces -z like the camera
|
||||
scale: float = 0.0,
|
||||
tint: floats,
|
||||
rough: float = 0.0,
|
||||
mat: floats, # the model matrix
|
||||
visible: bool = true,
|
||||
# Drawn and CASTING are not the same question. An actor hidden because the camera is inside
|
||||
# its head still stands in the sun, and a body that stops casting the moment you look out of
|
||||
|
|
@ -31,14 +31,14 @@ property Actor {
|
|||
casts: bool = true,
|
||||
id: int = 0,
|
||||
cutout: bool = false, # alpha-tested (a flame's cards)
|
||||
emissive: int = 0, # float bits: self-lit strength
|
||||
emissive: float = 0.0, # float bits: self-lit strength
|
||||
skin: Skin, # this instance's own pose (skin_clone); null: the model's
|
||||
ptint: words, # 4 per primitive: on flag, r, g, b (a part's own colour)
|
||||
hide: words, # 1 per primitive: skip it (a cap taken off)
|
||||
radius: int = 0, # float bits: bounding radius for culling (0 = never culled)
|
||||
cull: int = 0, # float bits: not drawn beyond this distance (0 = always)
|
||||
outline: int = 0, # float bits: metres of rim drawn around it (0 = none)
|
||||
ocol: words # the rim's colour (null = white)
|
||||
radius: float = 0.0, # float bits: bounding radius for culling (0 = never culled)
|
||||
cull: float = 0.0, # float bits: not drawn beyond this distance (0 = always)
|
||||
outline: float = 0.0, # float bits: metres of rim drawn around it (0 = none)
|
||||
ocol: floats # the rim's colour (null = white)
|
||||
}
|
||||
# a program and its uniform locations
|
||||
property AcProg {
|
||||
|
|
@ -98,11 +98,11 @@ function actor_remove(a: Actor) -> void {
|
|||
ac_actors = keep
|
||||
}
|
||||
# colour one named part of the model (a material name from the file)
|
||||
function actor_tint_part(a: Actor, name: string, r: int, g: int, b: int) -> void {
|
||||
function actor_tint_part(a: Actor, name: string, r: float, g: float, b: float) -> void {
|
||||
if a.model == null { return }
|
||||
let n = len(a.model.prims)
|
||||
if a.ptint == null { a.ptint = words(n * 4); for i in 0 .. n * 4 { a.ptint[i] = 0 } }
|
||||
for i in 0 .. n { if a.model.prims[i].name == name { a.ptint[i * 4] = 1; a.ptint[i * 4 + 1] = r; a.ptint[i * 4 + 2] = g; a.ptint[i * 4 + 3] = b } }
|
||||
for i in 0 .. n { if a.model.prims[i].name == name { a.ptint[i * 4] = 1; a.ptint[i * 4 + 1] = float_bits(r); a.ptint[i * 4 + 2] = float_bits(g); a.ptint[i * 4 + 3] = float_bits(b) } }
|
||||
}
|
||||
function actor_hide_part(a: Actor, name: string, hidden: bool) -> void {
|
||||
if a.model == null { return }
|
||||
|
|
@ -116,19 +116,19 @@ function actor_hide_part(a: Actor, name: string, hidden: bool) -> void {
|
|||
function actor_new(model: Model) -> Actor {
|
||||
let a = new Actor
|
||||
a.model = model
|
||||
a.scale = F_ONE
|
||||
a.tint = v3_new(F_ONE, F_ONE, F_ONE)
|
||||
a.rough = F_ONE
|
||||
a.scale = 1.0
|
||||
a.tint = v3_new(1.0, 1.0, 1.0)
|
||||
a.rough = 1.0
|
||||
a.mat = m4_new()
|
||||
ac_next_id += 1; a.id = ac_next_id
|
||||
if model != null { a.radius = f_add(f_max(model.radius, model.height), F_ONE) }
|
||||
a.cull = fi(450)
|
||||
if model != null { a.radius = Math.max(model.radius, model.height) + 1.0 }
|
||||
a.cull = 450.0
|
||||
if ac_actors == null { ac_actors = new []Actor }
|
||||
push(ac_actors, a)
|
||||
return a
|
||||
}
|
||||
|
||||
function actor_place(a: Actor, x: int, y: int, z: int, yaw: int) -> void {
|
||||
function actor_place(a: Actor, x: float, y: float, z: float, yaw: float) -> void {
|
||||
a.x = x; a.y = y; a.z = z; a.yaw = yaw
|
||||
m4_trs(a.mat, x, y, z, yaw, a.scale)
|
||||
}
|
||||
|
|
@ -141,7 +141,7 @@ function ac_bind_scene(ap: AcProg) -> void {
|
|||
gpu_use_program(p)
|
||||
u_mat4(ap.l_view, cam_view)
|
||||
u_mat4(ap.l_proj, cam_proj)
|
||||
u_f(ap.l_mh, F_ZERO)
|
||||
u_f(ap.l_mh, 0.0)
|
||||
sky_bind_lighting(p)
|
||||
shadow_bind(p)
|
||||
fog_bind(p)
|
||||
|
|
@ -150,22 +150,22 @@ function ac_visible(a: Actor, shadow: bool) -> bool {
|
|||
if a.model == null { return false }
|
||||
if not a.visible and not (shadow and a.cast_hidden) { return false }
|
||||
if shadow and not a.casts { return false }
|
||||
if a.cull != 0 {
|
||||
let dx = f_sub(a.x, cam_pos[0]); let dz = f_sub(a.z, cam_pos[2])
|
||||
let d2 = f_add(f_mul(dx, dx), f_mul(dz, dz))
|
||||
if a.cull != 0.0 {
|
||||
let dx = a.x - cam_pos[0]; let dz = a.z - cam_pos[2]
|
||||
let d2 = dx * dx + dz * dz
|
||||
var c = a.cull
|
||||
if shadow { c = f_min(c, fi(300)) }
|
||||
if f_gt(d2, f_mul(c, c)) { return false }
|
||||
if shadow { c = Math.min(c, 300.0) }
|
||||
if d2 > c * c { return false }
|
||||
}
|
||||
if not shadow and a.radius != 0 {
|
||||
let r = f_mul(a.radius, a.scale)
|
||||
var cy = f_add(a.y, r)
|
||||
if not shadow and a.radius != 0.0 {
|
||||
let r = a.radius * a.scale
|
||||
var cy = a.y + r
|
||||
# In the water reflection what reaches the picture is the actor's mirror image, 2L - y. The pass
|
||||
# keeps the main camera's frustum planes, so the image is what has to be tested against them: the
|
||||
# actor itself was, and the town's people - far above the lake, their images far below the frame -
|
||||
# all drew again into the reflection.
|
||||
if ter_reflect { cy = f_sub(f_mul(F_TWO, water_level), cy) }
|
||||
if not cam_sphere_visible(a.x, cy, a.z, f_mul(r, fl(1.5))) { return false }
|
||||
if ter_reflect { cy = 2.0 * water_level - cy }
|
||||
if not cam_sphere_visible(a.x, cy, a.z, r * 1.5) { return false }
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -173,9 +173,9 @@ function actor_draw_one(a: Actor, ap: AcProg, shadow: bool) -> void {
|
|||
let p = ap.prog
|
||||
gpu_use_program(p)
|
||||
u_mat4(ap.l_model, a.mat)
|
||||
var skinned = F_ZERO
|
||||
if a.skin != null { skinned = F_ONE; u_mat4n(ap.l_bones, a.skin.n_joints, a.skin.bones) }
|
||||
else if a.model.skin != null { skinned = F_ONE; u_mat4n(ap.l_bones, a.model.skin.n_joints, a.model.skin.bones) }
|
||||
var skinned = 0.0
|
||||
if a.skin != null { skinned = 1.0; u_mat4n(ap.l_bones, a.skin.n_joints, a.skin.bones) }
|
||||
else if a.model.skin != null { skinned = 1.0; u_mat4n(ap.l_bones, a.model.skin.n_joints, a.model.skin.bones) }
|
||||
u_f(ap.l_skin, skinned)
|
||||
if not shadow {
|
||||
u_f(ap.l_emis, a.emissive)
|
||||
|
|
@ -187,7 +187,7 @@ function actor_draw_one(a: Actor, ap: AcProg, shadow: bool) -> void {
|
|||
let pr = model.prims[i]
|
||||
if a.hide != null and a.hide[i] != 0 { continue }
|
||||
if not shadow {
|
||||
if a.ptint != null and a.ptint[i * 4] != 0 { u_f3(ap.l_tint, a.ptint[i * 4 + 1], a.ptint[i * 4 + 2], a.ptint[i * 4 + 3]) }
|
||||
if a.ptint != null and a.ptint[i * 4] != 0 { u_f3(ap.l_tint, float_from_bits(a.ptint[i * 4 + 1]), float_from_bits(a.ptint[i * 4 + 2]), float_from_bits(a.ptint[i * 4 + 3])) }
|
||||
else { u_v3(ap.l_tint, a.tint) }
|
||||
}
|
||||
gpu_tex_unit(0); gpu_tex_bind(GPU_TEX2D, pr.diff)
|
||||
|
|
@ -218,11 +218,11 @@ function actor_draw_one(a: Actor, ap: AcProg, shadow: bool) -> void {
|
|||
# would have arrived at (for a grounded layer, the terrain height at that instance).
|
||||
property OutlineReq {
|
||||
model: Model,
|
||||
mat: words,
|
||||
width: int = 0,
|
||||
r: int = 0,
|
||||
g: int = 0,
|
||||
b: int = 0
|
||||
mat: floats,
|
||||
width: float = 0.0,
|
||||
r: float = 0.0,
|
||||
g: float = 0.0,
|
||||
b: float = 0.0
|
||||
}
|
||||
var ac_oq: []OutlineReq = null
|
||||
var ac_oq_n: int = 0 # live entries; the array is kept and reused
|
||||
|
|
@ -232,8 +232,8 @@ var ac_oq_n: int = 0 # live entries; the array is kept and reuse
|
|||
# batch rather than clearing it, and the next add opens a new one: every pass in a frame
|
||||
# sees the same requests, and the caller needs no frame hook.
|
||||
var ac_oq_closed: bool = true
|
||||
function outline_model(m: Model, mat: words, width: int, r: int, g: int, b: int) -> void {
|
||||
if m == null or mat == null or width == 0 { return }
|
||||
function outline_model(m: Model, mat: floats, width: float, r: float, g: float, b: float) -> void {
|
||||
if m == null or mat == null or width == 0.0 { return }
|
||||
if ac_oq_closed { ac_oq_n = 0; ac_oq_closed = false }
|
||||
if ac_oq == null { ac_oq = new []OutlineReq }
|
||||
var q: OutlineReq = null
|
||||
|
|
@ -300,19 +300,19 @@ function actor_draw() -> void {
|
|||
# against the scene, so anything in front of the actor hides its rim too.
|
||||
function actor_draw_outlines() -> void {
|
||||
var any = ac_oq_n > 0
|
||||
for i in 0 .. len(ac_actors) { if ac_actors[i].outline != 0 and ac_visible(ac_actors[i], false) { any = true; break } }
|
||||
for i in 0 .. len(ac_actors) { if ac_actors[i].outline != 0.0 and ac_visible(ac_actors[i], false) { any = true; break } }
|
||||
if not any { return }
|
||||
gpu_cull(true)
|
||||
gpu_cull_face(GL_FRONT)
|
||||
for i in 0 .. len(ac_actors) {
|
||||
let a = ac_actors[i]
|
||||
if a.outline == 0 or not ac_visible(a, false) { continue }
|
||||
if a.outline == 0.0 or not ac_visible(a, false) { continue }
|
||||
var ap = ac_out
|
||||
if a.cutout { ap = ac_out_cut }
|
||||
gpu_use_program(ap.prog)
|
||||
u_mat4(ap.l_view, cam_view); u_mat4(ap.l_proj, cam_proj)
|
||||
u_f(ap.l_out, f_mul(a.outline, a.scale))
|
||||
if a.ocol != null { u_v3(ap.l_ocol, a.ocol) } else { u_f3(ap.l_ocol, F_ONE, F_ONE, F_ONE) }
|
||||
u_f(ap.l_out, a.outline * a.scale)
|
||||
if a.ocol != null { u_v3(ap.l_ocol, a.ocol) } else { u_f3(ap.l_ocol, 1.0, 1.0, 1.0) }
|
||||
actor_draw_outline_one(a, ap)
|
||||
}
|
||||
# and whatever asked for a rim without being an actor
|
||||
|
|
@ -324,7 +324,7 @@ function actor_draw_outlines() -> void {
|
|||
u_f(ap.l_out, q.width)
|
||||
u_f3(ap.l_ocol, q.r, q.g, q.b)
|
||||
u_mat4(ap.l_model, q.mat)
|
||||
u_f(ap.l_skin, F_ZERO)
|
||||
u_f(ap.l_skin, 0.0)
|
||||
for k in 0 .. len(q.model.prims) { mesh_draw(q.model.prims[k].mesh) }
|
||||
}
|
||||
ac_oq_closed = true
|
||||
|
|
@ -332,9 +332,9 @@ function actor_draw_outlines() -> void {
|
|||
}
|
||||
function actor_draw_outline_one(a: Actor, ap: AcProg) -> void {
|
||||
u_mat4(ap.l_model, a.mat)
|
||||
var skinned = F_ZERO
|
||||
if a.skin != null { skinned = F_ONE; u_mat4n(ap.l_bones, a.skin.n_joints, a.skin.bones) }
|
||||
else if a.model.skin != null { skinned = F_ONE; u_mat4n(ap.l_bones, a.model.skin.n_joints, a.model.skin.bones) }
|
||||
var skinned = 0.0
|
||||
if a.skin != null { skinned = 1.0; u_mat4n(ap.l_bones, a.skin.n_joints, a.skin.bones) }
|
||||
else if a.model.skin != null { skinned = 1.0; u_mat4n(ap.l_bones, a.model.skin.n_joints, a.model.skin.bones) }
|
||||
u_f(ap.l_skin, skinned)
|
||||
let model = a.model
|
||||
for i in 0 .. len(model.prims) {
|
||||
|
|
@ -349,47 +349,47 @@ function actor_draw_outline_one(a: Actor, ap: AcProg) -> void {
|
|||
# three axis offsets. An actor outside the box casts nothing into that layer: skipping it changes no
|
||||
# depth, only the draw. (Each actor used to draw into every cascade out to 300 m - in town that was
|
||||
# about 1500 skinned shadow draws a frame against 69 in the lit pass.)
|
||||
var ac_lp0: words = null
|
||||
var ac_lpx: words = null
|
||||
var ac_lpy: words = null
|
||||
var ac_lpz: words = null
|
||||
function ac_in_light(vp: words, x: int, y: int, z: int, r: int) -> bool {
|
||||
if ac_lp0 == null { ac_lp0 = words(3); ac_lpx = words(3); ac_lpy = words(3); ac_lpz = words(3) }
|
||||
var ac_lp0: floats = null
|
||||
var ac_lpx: floats = null
|
||||
var ac_lpy: floats = null
|
||||
var ac_lpz: floats = null
|
||||
function ac_in_light(vp: floats, x: float, y: float, z: float, r: float) -> bool {
|
||||
if ac_lp0 == null { ac_lp0 = floats(3); ac_lpx = floats(3); ac_lpy = floats(3); ac_lpz = floats(3) }
|
||||
m4_xform_point(ac_lp0, vp, x, y, z)
|
||||
m4_xform_point(ac_lpx, vp, f_add(x, r), y, z)
|
||||
m4_xform_point(ac_lpy, vp, x, f_add(y, r), z)
|
||||
m4_xform_point(ac_lpz, vp, x, y, f_add(z, r))
|
||||
m4_xform_point(ac_lpx, vp, x + r, y, z)
|
||||
m4_xform_point(ac_lpy, vp, x, y + r, z)
|
||||
m4_xform_point(ac_lpz, vp, x, y, z + r)
|
||||
for k in 0 .. 2 {
|
||||
let c = ac_lp0[k]
|
||||
let e = f_add(f_add(ac_abs(f_sub(ac_lpx[k], c)), ac_abs(f_sub(ac_lpy[k], c))), ac_abs(f_sub(ac_lpz[k], c)))
|
||||
if f_gt(f_sub(ac_abs(c), e), F_ONE) { return false }
|
||||
let e = ac_abs(ac_lpx[k] - c) + ac_abs(ac_lpy[k] - c) + ac_abs(ac_lpz[k] - c)
|
||||
if ac_abs(c) - e > 1.0 { return false }
|
||||
}
|
||||
return true
|
||||
}
|
||||
function ac_abs(v: int) -> int { return f_max(v, f_neg(v)) }
|
||||
function ac_abs(v: float) -> float { return Math.max(v, -v) }
|
||||
|
||||
function actor_draw_casters(light_vp: words) -> void {
|
||||
function actor_draw_casters(light_vp: floats) -> void {
|
||||
if ac_actors == null { return }
|
||||
gpu_use_program(ac_sh.prog); u_mat4(ac_sh.l_lvp, light_vp)
|
||||
gpu_use_program(ac_sh_cut.prog); u_mat4(ac_sh_cut.l_lvp, light_vp)
|
||||
for i in 0 .. len(ac_actors) {
|
||||
let a = ac_actors[i]
|
||||
if not ac_visible(a, true) { continue }
|
||||
if a.radius != 0 {
|
||||
if a.radius != 0.0 {
|
||||
# the radius is the horizontal extent: a person is far taller than wide, so the sphere is
|
||||
# centred at half the model's height and reaches the larger of the two, with a margin for
|
||||
# the pose (a first version centred it at the radius and dropped a head at a cascade's edge)
|
||||
let hh = f_mul(f_mul(a.model.height, F_HALF), a.scale)
|
||||
let r = f_max(f_mul(a.radius, a.scale), hh)
|
||||
if not ac_in_light(light_vp, a.x, f_add(a.y, hh), a.z, f_mul(r, fl(1.5))) { continue }
|
||||
let hh = a.model.height * 0.5 * a.scale
|
||||
let r = Math.max(a.radius * a.scale, hh)
|
||||
if not ac_in_light(light_vp, a.x, a.y + hh, a.z, r * 1.5) { continue }
|
||||
}
|
||||
# Inside the light box is not the same as able to shade anything this cascade covers: every actor
|
||||
# within 300 m sits inside the far cascades' boxes, whose receivers start 250 and 1100 m out - at
|
||||
# the camp 160 actors cast 300 draws into each of those, against 41 into the nearest.
|
||||
let dxa = f_sub(a.x, cam_pos[0]); let dza = f_sub(a.z, cam_pos[2])
|
||||
let da = f_sqrt(f_add(f_mul(dxa, dxa), f_mul(dza, dza)))
|
||||
let ra = f_mul(a.radius, a.scale)
|
||||
if not cast_band_reaches(f_max(f_sub(da, ra), F_ZERO), f_add(da, ra), f_mul(a.model.height, a.scale)) { continue }
|
||||
let dxa = a.x - cam_pos[0]; let dza = a.z - cam_pos[2]
|
||||
let da = Math.sqrt(dxa * dxa + dza * dza)
|
||||
let ra = a.radius * a.scale
|
||||
if not cast_band_reaches(Math.max(da - ra, 0.0), da + ra, a.model.height * a.scale) { continue }
|
||||
var ap = ac_sh
|
||||
if a.cutout { ap = ac_sh_cut }
|
||||
if ac_census_at > 0 and ac_frame == ac_census_at { ac_caster_count(a) }
|
||||
|
|
|
|||
|
|
@ -3,35 +3,35 @@
|
|||
# projection matrices. Float bits throughout; see fmath.ludic.
|
||||
# ============================================================================
|
||||
|
||||
var cam_pos: words = null # x, y, z
|
||||
var cam_yaw: int = 0 # radians, 0 = looking down -z
|
||||
var cam_pitch: int = 0
|
||||
var cam_fov: int = 0 # vertical, radians
|
||||
var cam_near: int = 0
|
||||
var cam_far: int = 0
|
||||
var cam_aspect: int = 0
|
||||
var cam_view: words = null
|
||||
var cam_proj: words = null
|
||||
var cam_vp: words = null
|
||||
var cam_inv_vp: words = null
|
||||
var cam_inv_proj: words = null
|
||||
var cam_vp_clean: words = null # view-projection, kept for depth reconstruction
|
||||
var cam_inv_vp_clean: words = null
|
||||
var cam_fwd: words = null
|
||||
var cam_right: words = null
|
||||
var cam_pos: floats = null # x, y, z
|
||||
var cam_yaw: float = 0.0 # radians, 0 = looking down -z
|
||||
var cam_pitch: float = 0.0
|
||||
var cam_fov: float = 0.0 # vertical, radians
|
||||
var cam_near: float = 0.0
|
||||
var cam_far: float = 0.0
|
||||
var cam_aspect: float = 0.0
|
||||
var cam_view: floats = null
|
||||
var cam_proj: floats = null
|
||||
var cam_vp: floats = null
|
||||
var cam_inv_vp: floats = null
|
||||
var cam_inv_proj: floats = null
|
||||
var cam_vp_clean: floats = null # view-projection, kept for depth reconstruction
|
||||
var cam_inv_vp_clean: floats = null
|
||||
var cam_fwd: floats = null
|
||||
var cam_right: floats = null
|
||||
# the view frustum's four side planes (a, b, c, d), float bits: left, right, bottom, top;
|
||||
# from the clean (unjittered) view-projection, column-major m[col * 4 + row]
|
||||
var cam_planes: words = null
|
||||
var cam_planes: floats = null
|
||||
|
||||
function cam_init(aspect: int) -> void {
|
||||
cam_pos = v3_new(F_ZERO, fi(2), F_ZERO)
|
||||
function cam_init(aspect: float) -> void {
|
||||
cam_pos = v3_new(0.0, 2.0, 0.0)
|
||||
cam_view = m4_new(); cam_proj = m4_new(); cam_vp = m4_new(); cam_inv_vp = m4_new(); cam_inv_proj = m4_new()
|
||||
cam_vp_clean = m4_new(); cam_inv_vp_clean = m4_new()
|
||||
cam_fwd = v3_new(F_ZERO, F_ZERO, f_neg1())
|
||||
cam_right = v3_new(F_ONE, F_ZERO, F_ZERO)
|
||||
cam_fov = f_rad(fi(42))
|
||||
cam_near = fl(0.3)
|
||||
cam_far = fi(14000)
|
||||
cam_fwd = v3_new(0.0, 0.0, -1.0)
|
||||
cam_right = v3_new(1.0, 0.0, 0.0)
|
||||
cam_fov = Math.deg_to_rad(42.0)
|
||||
cam_near = 0.3
|
||||
cam_far = 14000.0
|
||||
cam_aspect = aspect
|
||||
cam_update()
|
||||
}
|
||||
|
|
@ -39,29 +39,29 @@ function cam_begin_frame(n: int, w: int, h: int) -> void {
|
|||
gsl_jitter_frame()
|
||||
cam_update()
|
||||
}
|
||||
function cam_set(x: int, y: int, z: int, yaw_deg: int, pitch_deg: int) -> void {
|
||||
function cam_set(x: float, y: float, z: float, yaw_deg: float, pitch_deg: float) -> void {
|
||||
v3_set(cam_pos, x, y, z)
|
||||
cam_yaw = f_rad(yaw_deg)
|
||||
cam_pitch = f_rad(pitch_deg)
|
||||
cam_yaw = Math.deg_to_rad(yaw_deg)
|
||||
cam_pitch = Math.deg_to_rad(pitch_deg)
|
||||
cam_update()
|
||||
}
|
||||
function cam_update() -> void {
|
||||
let cy = f_cos(cam_yaw); let sy = f_sin(cam_yaw)
|
||||
let cp = f_cos(cam_pitch); let sp = f_sin(cam_pitch)
|
||||
v3_set(cam_fwd, f_neg(f_mul(sy, cp)), sp, f_neg(f_mul(cy, cp)))
|
||||
v3_set(cam_right, cy, F_ZERO, f_neg(sy))
|
||||
let at = words(3)
|
||||
let cy = Math.cos(cam_yaw); let sy = Math.sin(cam_yaw)
|
||||
let cp = Math.cos(cam_pitch); let sp = Math.sin(cam_pitch)
|
||||
v3_set(cam_fwd, -(sy * cp), sp, -(cy * cp))
|
||||
v3_set(cam_right, cy, 0.0, -sy)
|
||||
let at = floats(3)
|
||||
v3_add(at, cam_pos, cam_fwd)
|
||||
let up = v3_new(F_ZERO, F_ONE, F_ZERO)
|
||||
let up = v3_new(0.0, 1.0, 0.0)
|
||||
m4_look_at(cam_view, cam_pos, at, up)
|
||||
m4_perspective(cam_proj, cam_fov, cam_aspect, cam_near, cam_far)
|
||||
m4_mul(cam_vp_clean, cam_proj, cam_view)
|
||||
m4_inverse(cam_inv_vp_clean, cam_vp_clean)
|
||||
# DLSS's sub-pixel jitter (streamline.ludic), in the projection the scene draws with only:
|
||||
# culling, depth reconstruction and last frame's matrix keep the clean one
|
||||
if gsl_jitter_x != 0 or gsl_jitter_y != 0 {
|
||||
cam_proj[8] = f_sub(cam_proj[8], gsl_jitter_x)
|
||||
cam_proj[9] = f_sub(cam_proj[9], gsl_jitter_y)
|
||||
if gsl_jitter_x != 0.0 or gsl_jitter_y != 0.0 {
|
||||
cam_proj[8] = cam_proj[8] - gsl_jitter_x
|
||||
cam_proj[9] = cam_proj[9] - gsl_jitter_y
|
||||
}
|
||||
m4_mul(cam_vp, cam_proj, cam_view)
|
||||
m4_inverse(cam_inv_vp, cam_vp)
|
||||
|
|
@ -70,39 +70,39 @@ function cam_update() -> void {
|
|||
cam_planes_update()
|
||||
}
|
||||
function cam_planes_update() -> void {
|
||||
if cam_planes == null { cam_planes = words(16) }
|
||||
if cam_planes == null { cam_planes = floats(16) }
|
||||
let m = cam_vp_clean
|
||||
for p in 0 .. 4 {
|
||||
var r = 0
|
||||
if p >= 2 { r = 1 }
|
||||
var sg = F_ONE
|
||||
if p == 1 or p == 3 { sg = f_neg(F_ONE) }
|
||||
var a = f_add(m[3], f_mul(sg, m[r]))
|
||||
var b = f_add(m[7], f_mul(sg, m[4 + r]))
|
||||
var c = f_add(m[11], f_mul(sg, m[8 + r]))
|
||||
var d = f_add(m[15], f_mul(sg, m[12 + r]))
|
||||
let inv = f_div(F_ONE, f_sqrt(f_add(f_add(f_mul(a, a), f_mul(b, b)), f_mul(c, c))))
|
||||
cam_planes[p * 4] = f_mul(a, inv); cam_planes[p * 4 + 1] = f_mul(b, inv)
|
||||
cam_planes[p * 4 + 2] = f_mul(c, inv); cam_planes[p * 4 + 3] = f_mul(d, inv)
|
||||
var sg = 1.0
|
||||
if p == 1 or p == 3 { sg = -1.0 }
|
||||
var a = m[3] + sg * m[r]
|
||||
var b = m[7] + sg * m[4 + r]
|
||||
var c = m[11] + sg * m[8 + r]
|
||||
var d = m[15] + sg * m[12 + r]
|
||||
let inv = 1.0 / Math.sqrt(a * a + b * b + c * c)
|
||||
cam_planes[p * 4] = a * inv; cam_planes[p * 4 + 1] = b * inv
|
||||
cam_planes[p * 4 + 2] = c * inv; cam_planes[p * 4 + 3] = d * inv
|
||||
}
|
||||
}
|
||||
# is a sphere (float bits) at least partly inside the side planes of the view?
|
||||
function cam_sphere_visible(x: int, y: int, z: int, r: int) -> bool {
|
||||
function cam_sphere_visible(x: float, y: float, z: float, r: float) -> bool {
|
||||
if cam_planes == null { return true }
|
||||
let nr = f_neg(r)
|
||||
let nr = -r
|
||||
for p in 0 .. 4 {
|
||||
let o = p * 4
|
||||
let dist = f_add(f_add(f_add(f_mul(cam_planes[o], x), f_mul(cam_planes[o + 1], y)), f_mul(cam_planes[o + 2], z)), cam_planes[o + 3])
|
||||
if f_ls(dist, nr) { return false }
|
||||
let dist = cam_planes[o] * x + cam_planes[o + 1] * y + cam_planes[o + 2] * z + cam_planes[o + 3]
|
||||
if dist < nr { return false }
|
||||
}
|
||||
return true
|
||||
}
|
||||
# fly: forward/strafe in metres, turn in radians
|
||||
function cam_move(fwd: int, strafe: int, up: int, dyaw: int, dpitch: int) -> void {
|
||||
cam_yaw = f_add(cam_yaw, dyaw)
|
||||
cam_pitch = f_clamp(f_add(cam_pitch, dpitch), f_neg(fl(1.5)), fl(1.5))
|
||||
function cam_move(fwd: float, strafe: float, up: float, dyaw: float, dpitch: float) -> void {
|
||||
cam_yaw = cam_yaw + dyaw
|
||||
cam_pitch = Math.clamp(cam_pitch + dpitch, -1.5, 1.5)
|
||||
v3_madd(cam_pos, cam_pos, cam_fwd, fwd)
|
||||
v3_madd(cam_pos, cam_pos, cam_right, strafe)
|
||||
cam_pos[1] = f_add(cam_pos[1], up)
|
||||
cam_pos[1] = cam_pos[1] + up
|
||||
cam_update()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,31 +9,31 @@
|
|||
const COL_CELL: int = 16
|
||||
const COL_CAP: int = 120000
|
||||
|
||||
var col_x: words = null
|
||||
var col_z: words = null
|
||||
var col_r: words = null
|
||||
var col_x: floats = null
|
||||
var col_z: floats = null
|
||||
var col_r: floats = null
|
||||
# What a collider occupies VERTICALLY: y0 its base, y1 its top, metres, float bits. A circle used
|
||||
# to be an infinite pillar - you could not climb a boulder, and a knee-high rock stopped you dead,
|
||||
# because there was no height to compare against. col_add keeps that shape (a span from far below
|
||||
# to far above) so every existing caller behaves exactly as it did; col_add_h gives a real one.
|
||||
var col_y0: words = null
|
||||
var col_y1: words = null
|
||||
var col_y0: floats = null
|
||||
var col_y1: floats = null
|
||||
var col_n: int = 0
|
||||
var col_side: int = 0 # cells per side
|
||||
var col_start: words = null # per cell: first index into col_sorted (side*side + 1)
|
||||
var col_sorted: words = null
|
||||
var col_built: bool = false
|
||||
var col_out: words = null # the resolved position (x, z)
|
||||
var col_out: floats = null # the resolved position (x, z)
|
||||
|
||||
const COL_LOW: int = 0xCB800000 # -16777216.0: below any ground
|
||||
const COL_HIGH: int = 0x4B800000 # 16777216.0: above any sky
|
||||
function col_add(x: int, z: int, r: int) -> void { col_add_h(x, z, r, COL_LOW, COL_HIGH) }
|
||||
const COL_LOW: float = -16777216.0 # -16777216.0: below any ground
|
||||
const COL_HIGH: float = 16777216.0 # 16777216.0: above any sky
|
||||
function col_add(x: float, z: float, r: float) -> void { col_add_h(x, z, r, COL_LOW, COL_HIGH) }
|
||||
# a collider that occupies only y0 .. y1: a body above its top walks over it, a body below its base
|
||||
# passes under, and col_top_at reports it as something to stand on
|
||||
function col_add_h(x: int, z: int, r: int, y0: int, y1: int) -> void {
|
||||
function col_add_h(x: float, z: float, r: float, y0: float, y1: float) -> void {
|
||||
if col_x == null {
|
||||
col_x = words(COL_CAP); col_z = words(COL_CAP); col_r = words(COL_CAP)
|
||||
col_y0 = words(COL_CAP); col_y1 = words(COL_CAP); col_out = words(2)
|
||||
col_x = floats(COL_CAP); col_z = floats(COL_CAP); col_r = floats(COL_CAP)
|
||||
col_y0 = floats(COL_CAP); col_y1 = floats(COL_CAP); col_out = floats(2)
|
||||
}
|
||||
if col_n >= COL_CAP { return }
|
||||
col_x[col_n] = x; col_z[col_n] = z; col_r[col_n] = r
|
||||
|
|
@ -41,8 +41,8 @@ function col_add_h(x: int, z: int, r: int, y0: int, y1: int) -> void {
|
|||
col_n += 1
|
||||
col_built = false
|
||||
}
|
||||
function col_cell_of(v: int, origin: int) -> int {
|
||||
var c = f_to_int(f_floor(f_div(f_add(f_sub(v, origin), fi(TERRAIN_HALF)), fi(COL_CELL))))
|
||||
function col_cell_of(v: float, origin: float) -> int {
|
||||
var c = int(Math.floor((v - origin + float(TERRAIN_HALF)) / float(COL_CELL)))
|
||||
if c < 0 { c = 0 }
|
||||
if c > col_side - 1 { c = col_side - 1 }
|
||||
return c
|
||||
|
|
@ -67,10 +67,10 @@ function col_build() -> void {
|
|||
}
|
||||
|
||||
# push (px, pz) with radius pr out of every circle it overlaps; the result is in col_out
|
||||
function col_resolve(px: int, pz: int, pr: int) -> bool { return col_resolve_at(px, pz, pr, COL_LOW, COL_HIGH) }
|
||||
function col_resolve(px: float, pz: float, pr: float) -> bool { return col_resolve_at(px, pz, pr, COL_LOW, COL_HIGH) }
|
||||
# the same, for a body that occupies feet .. head: a collider whose span misses that is not in the
|
||||
# way at all. This is what lets a hiker stand on top of a boulder rather than inside it.
|
||||
function col_resolve_at(px: int, pz: int, pr: int, feet: int, head: int) -> bool {
|
||||
function col_resolve_at(px: float, pz: float, pr: float, feet: float, head: float) -> bool {
|
||||
col_out[0] = px; col_out[1] = pz
|
||||
if not col_built or col_n == 0 { return false }
|
||||
var x = px; var z = pz
|
||||
|
|
@ -86,14 +86,14 @@ function col_resolve_at(px: int, pz: int, pr: int, feet: int, head: int) -> bool
|
|||
let c = zc * col_side + xc
|
||||
for k in col_start[c] .. col_start[c + 1] {
|
||||
let i = col_sorted[k]
|
||||
let ex = f_sub(x, col_x[i]); let ez = f_sub(z, col_z[i])
|
||||
let d2 = f_add(f_mul(ex, ex), f_mul(ez, ez))
|
||||
let rr = f_add(col_r[i], pr)
|
||||
let ex = x - col_x[i]; let ez = z - col_z[i]
|
||||
let d2 = ex * ex + ez * ez
|
||||
let rr = col_r[i] + pr
|
||||
# nothing to push out of if the body is wholly above its top or below its base
|
||||
if not f_ls(feet, col_y1[i]) { continue }
|
||||
if not f_gt(head, col_y0[i]) { continue }
|
||||
if f_ls(d2, f_mul(rr, rr)) {
|
||||
let d = f_sqrt(d2)
|
||||
if not (feet < col_y1[i]) { continue }
|
||||
if not (head > col_y0[i]) { continue }
|
||||
if d2 < rr * rr {
|
||||
let d = Math.sqrt(d2)
|
||||
# The UNIT normal out of this circle. (ex, ez) / d is always unit for d > 0,
|
||||
# because d is its own length - there is nothing to clamp and nothing that can
|
||||
# grow. Exactly at the centre there is no direction to be had, so any one will
|
||||
|
|
@ -105,11 +105,11 @@ function col_resolve_at(px: int, pz: int, pr: int, feet: int, head: int) -> bool
|
|||
# 0.5 m trunk was thrown roughly 800 m across the map instead of 0.85 m clear
|
||||
# of it. Off-centre - which is how anything actually arrives at a trunk - the
|
||||
# arithmetic was right, so it never showed up in play.
|
||||
var ux = F_ONE; var uz = F_ZERO
|
||||
if f_gt(d, F_ZERO) { ux = f_div(ex, d); uz = f_div(ez, d) }
|
||||
let push = f_sub(rr, d)
|
||||
x = f_add(x, f_mul(ux, push))
|
||||
z = f_add(z, f_mul(uz, push))
|
||||
var ux = 1.0; var uz = 0.0
|
||||
if d > 0.0 { ux = ex / d; uz = ez / d }
|
||||
let push = rr - d
|
||||
x = x + ux * push
|
||||
z = z + uz * push
|
||||
moved = true
|
||||
}
|
||||
}
|
||||
|
|
@ -122,11 +122,11 @@ function col_resolve_at(px: int, pz: int, pr: int, feet: int, head: int) -> bool
|
|||
# The highest collider top under (px, pz) that a body at `feet` could be standing on or step up to:
|
||||
# tops above `reach` are a wall, not a step. F_ZERO-safe: returns `floor` when there is nothing, so
|
||||
# a caller can pass the terrain height and use the answer directly as the ground.
|
||||
function col_top_at(px: int, pz: int, pr: int, feet: int, reach: int, floor: int) -> int {
|
||||
function col_top_at(px: float, pz: float, pr: float, feet: float, reach: float, floor: float) -> float {
|
||||
var top = floor
|
||||
if not col_built or col_n == 0 { return top }
|
||||
let cx = col_cell_of(px, ter_ox); let cz = col_cell_of(pz, ter_oz)
|
||||
let limit = f_add(feet, reach)
|
||||
let limit = feet + reach
|
||||
for dz in 0 .. 3 {
|
||||
let zc = cz + dz - 1
|
||||
if zc < 0 or zc >= col_side { continue }
|
||||
|
|
@ -136,24 +136,24 @@ function col_top_at(px: int, pz: int, pr: int, feet: int, reach: int, floor: int
|
|||
let c = zc * col_side + xc
|
||||
for k in col_start[c] .. col_start[c + 1] {
|
||||
let i = col_sorted[k]
|
||||
let ex = f_sub(px, col_x[i]); let ez = f_sub(pz, col_z[i])
|
||||
let d2 = f_add(f_mul(ex, ex), f_mul(ez, ez))
|
||||
let rr = f_add(col_r[i], pr)
|
||||
if not f_ls(d2, f_mul(rr, rr)) { continue }
|
||||
let ex = px - col_x[i]; let ez = pz - col_z[i]
|
||||
let d2 = ex * ex + ez * ez
|
||||
let rr = col_r[i] + pr
|
||||
if not (d2 < rr * rr) { continue }
|
||||
let t = col_y1[i]
|
||||
if f_gt(t, limit) { continue } # too tall to step onto: it is a wall
|
||||
if f_gt(t, top) { top = t }
|
||||
if t > limit { continue } # too tall to step onto: it is a wall
|
||||
if t > top { top = t }
|
||||
}
|
||||
}
|
||||
}
|
||||
return top
|
||||
}
|
||||
# is the segment from (x0,z0) to (x1,z1) clear of every circle (a camera line of sight)?
|
||||
function col_clear(x0: int, z0: int, x1: int, z1: int, r: int) -> bool {
|
||||
function col_clear(x0: float, z0: float, x1: float, z1: float, r: float) -> bool {
|
||||
let steps = 6
|
||||
for s in 0 .. steps + 1 {
|
||||
let t = fr(s, steps)
|
||||
let x = f_lerp(x0, x1, t); let z = f_lerp(z0, z1, t)
|
||||
let t = float(s) / float(steps)
|
||||
let x = Math.lerp(x0, x1, t); let z = Math.lerp(z0, z1, t)
|
||||
if col_resolve(x, z, r) { return false }
|
||||
}
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -10,139 +10,139 @@
|
|||
# ============================================================================
|
||||
|
||||
var day_on: bool = false
|
||||
var day_hours: int = 0 # float bits, 0 .. 24
|
||||
var day_light: int = 0 # 0 night .. 1 full day (float bits)
|
||||
var day_ibl: words = null # rgb scale on the sky's light
|
||||
var day_sun_base: words = null # the HDRI's sun radiance, kept
|
||||
var day_az0: int = 0 # the sun's azimuth at the reference hour (radians, yaw convention)
|
||||
var day_yaw0: int = 0 # the sky yaw the scene was tuned at
|
||||
var day_hour0: int = 0 # the hour the photograph was taken (10.5)
|
||||
var day_dir: int = 0 # +1 / -1: which way the sun travels in yaw
|
||||
var day_az: int = 0
|
||||
var day_el: int = 0
|
||||
var day_hours: float = 0.0 # float bits, 0 .. 24
|
||||
var day_light: float = 0.0 # 0 night .. 1 full day (float bits)
|
||||
var day_ibl: floats = null # rgb scale on the sky's light
|
||||
var day_sun_base: floats = null # the HDRI's sun radiance, kept
|
||||
var day_az0: float = 0.0 # the sun's azimuth at the reference hour (radians, yaw convention)
|
||||
var day_yaw0: float = 0.0 # the sky yaw the scene was tuned at
|
||||
var day_hour0: float = 0.0 # the hour the photograph was taken (10.5)
|
||||
var day_dir: float = 0.0 # +1 / -1: which way the sun travels in yaw
|
||||
var day_az: float = 0.0
|
||||
var day_el: float = 0.0
|
||||
var day_gen: int = 0 # bumps when the light moved enough to rebake the terrain shadow
|
||||
var day_baked_az: int = 0
|
||||
var day_baked_el: int = 0
|
||||
var day_sky_baked: int = 0 # the sky yaw the convolutions were baked at
|
||||
var fire_pos: words = null
|
||||
var fire_color: words = null
|
||||
var day_baked_az: float = 0.0
|
||||
var day_baked_el: float = 0.0
|
||||
var day_sky_baked: float = 0.0 # the sky yaw the convolutions were baked at
|
||||
var fire_pos: floats = null
|
||||
var fire_color: floats = null
|
||||
var day_moon: bool = false
|
||||
# The moon's place in its month, 0 new .. 0.5 full .. 1 new again. It decides the disc's
|
||||
# terminator, how much light reaches the ground, and where in the sky it rides: a full
|
||||
# moon is opposite the sun and rises at sunset, a new one travels with it.
|
||||
var day_moon_phase: int = 0x3F000000 # 0.5: full, which is where the game used to be
|
||||
var day_moon_illum: int = 0x3F800000 # the lit fraction, derived from the phase
|
||||
var day_moon_dir: words = null # toward the moon, whether or not it is up
|
||||
var hand_pos: words = null
|
||||
var hand_color: words = null
|
||||
var hand_dir: words = null
|
||||
var hand_cone: int = 0
|
||||
var hand_reach: int = 0 # metres the hand light reaches (float bits)
|
||||
var day_overcast: int = 0 # 0 clear .. 1 a low grey sky (float bits): dims the sun and the sky's light
|
||||
var day_flash: int = 0 # a lightning flash this frame (0..1): the sun brightens for it
|
||||
var day_fog_mul: int = 0x3F800000 # multiplies the base fog density (rain and snow thicken the air)
|
||||
var day_fog_base: int = 0
|
||||
var day_moon_phase: float = 0.5 # 0.5: full, which is where the game used to be
|
||||
var day_moon_illum: float = 1.0 # the lit fraction, derived from the phase
|
||||
var day_moon_dir: floats = null # toward the moon, whether or not it is up
|
||||
var hand_pos: floats = null
|
||||
var hand_color: floats = null
|
||||
var hand_dir: floats = null
|
||||
var hand_cone: float = 0.0
|
||||
var hand_reach: float = 0.0 # metres the hand light reaches (float bits)
|
||||
var day_overcast: float = 0.0 # 0 clear .. 1 a low grey sky (float bits): dims the sun and the sky's light
|
||||
var day_flash: float = 0.0 # a lightning flash this frame (0..1): the sun brightens for it
|
||||
var day_fog_mul: float = 1.0 # multiplies the base fog density (rain and snow thicken the air)
|
||||
var day_fog_base: float = 0.0
|
||||
|
||||
function daylight_init() -> void {
|
||||
day_ibl = v3_new(F_ONE, F_ONE, F_ONE)
|
||||
day_ibl = v3_new(1.0, 1.0, 1.0)
|
||||
day_sun_base = v3_new(sun_color[0], sun_color[1], sun_color[2])
|
||||
fire_pos = v3_new(F_ZERO, fi(-1000), F_ZERO)
|
||||
fire_color = v3_new(F_ZERO, F_ZERO, F_ZERO)
|
||||
day_moon_dir = v3_new(F_ZERO, F_ONE, F_ZERO)
|
||||
hand_pos = v3_new(F_ZERO, fi(-1000), F_ZERO); hand_color = v3_new(F_ZERO, F_ZERO, F_ZERO); hand_dir = v3_new(F_ZERO, F_ZERO, f_neg(F_ONE)); hand_cone = f_neg(F_TWO); hand_reach = fi(12)
|
||||
day_light = F_ONE
|
||||
fire_pos = v3_new(0.0, -1000.0, 0.0)
|
||||
fire_color = v3_new(0.0, 0.0, 0.0)
|
||||
day_moon_dir = v3_new(0.0, 1.0, 0.0)
|
||||
hand_pos = v3_new(0.0, -1000.0, 0.0); hand_color = v3_new(0.0, 0.0, 0.0); hand_dir = v3_new(0.0, 0.0, -1.0); hand_cone = -2.0; hand_reach = 12.0
|
||||
day_light = 1.0
|
||||
}
|
||||
|
||||
# Start the clock: the scene as tuned (sky yaw `yaw0`) is the photograph's hour `hour0`;
|
||||
# the sun rises and sets toward `set_yaw` (the direction it should be in at 19:00).
|
||||
function daylight_start(yaw0: int, hour0: int, set_yaw: int) -> void {
|
||||
function daylight_start(yaw0: float, hour0: float, set_yaw: float) -> void {
|
||||
day_yaw0 = yaw0; day_hour0 = hour0
|
||||
day_az0 = f_atan2(f_neg(sun_dir[0]), f_neg(sun_dir[2]))
|
||||
day_az0 = Math.atan2(-sun_dir[0], -sun_dir[2])
|
||||
day_sky_baked = yaw0
|
||||
# which way does the sun travel? the way that puts it nearest `set_yaw` at 19:00
|
||||
let step = f_mul(f_sub(fl(19.0), hour0), f_rad(fi(15)))
|
||||
let da = f_abs(day_wrap(f_sub(f_add(day_az0, step), set_yaw)))
|
||||
let db = f_abs(day_wrap(f_sub(f_sub(day_az0, step), set_yaw)))
|
||||
day_dir = F_ONE
|
||||
if f_ls(db, da) { day_dir = f_neg(F_ONE) }
|
||||
let step = (19.0 - hour0) * Math.deg_to_rad(15.0)
|
||||
let da = Math.abs(day_wrap(day_az0 + step - set_yaw))
|
||||
let db = Math.abs(day_wrap(day_az0 - step - set_yaw))
|
||||
day_dir = 1.0
|
||||
if db < da { day_dir = -1.0 }
|
||||
day_on = true
|
||||
day_baked_az = fi(1000)
|
||||
day_baked_az = 1000.0
|
||||
daylight_set(hour0)
|
||||
}
|
||||
function day_wrap(a: int) -> int {
|
||||
let two_pi = f_mul(F_TWO, F_PI)
|
||||
function day_wrap(a: float) -> float {
|
||||
let two_pi = 2.0 * PI
|
||||
var d = a
|
||||
while f_gt(d, F_PI) { d = f_sub(d, two_pi) }
|
||||
while f_ls(d, f_neg(F_PI)) { d = f_add(d, two_pi) }
|
||||
while d > PI { d = d - two_pi }
|
||||
while d < -PI { d = d + two_pi }
|
||||
return d
|
||||
}
|
||||
function smoothf(a: int, b: int, x: int) -> int {
|
||||
let t = f_clamp(f_div(f_sub(x, a), f_sub(b, a)), F_ZERO, F_ONE)
|
||||
return f_mul(f_mul(t, t), f_sub(fi(3), f_mul(F_TWO, t)))
|
||||
function smoothf(a: float, b: float, x: float) -> float {
|
||||
let t = Math.clamp((x - a) / (b - a), 0.0, 1.0)
|
||||
return t * t * (3.0 - 2.0 * t)
|
||||
}
|
||||
|
||||
function daylight_set(hours: int) -> void {
|
||||
var h = f_mod(hours, fi(24))
|
||||
if f_ls(h, F_ZERO) { h = f_add(h, fi(24)) }
|
||||
function daylight_set(hours: float) -> void {
|
||||
var h = hours % 24.0
|
||||
if h < 0.0 { h = h + 24.0 }
|
||||
day_hours = h
|
||||
# the arc: up at 5:30, highest (about 57 degrees) at 12:45, down at 20:00
|
||||
let t = f_mul(f_div(f_sub(h, fl(5.5)), fl(14.5)), F_PI)
|
||||
let el = f_rad(f_add(fi(-6), f_mul(fi(63), f_sin(t))))
|
||||
let az = f_add(day_az0, f_mul(f_mul(f_sub(h, day_hour0), f_rad(fi(15))), day_dir))
|
||||
let t = (h - 5.5) / 14.5 * PI
|
||||
let el = Math.deg_to_rad(-6.0 + 63.0 * Math.sin(t))
|
||||
let az = day_az0 + (h - day_hour0) * Math.deg_to_rad(15.0) * day_dir
|
||||
day_az = az; day_el = el
|
||||
let d = smoothf(f_rad(fi(-8)), f_rad(fi(12)), el)
|
||||
let d = smoothf(Math.deg_to_rad(-8.0), Math.deg_to_rad(12.0), el)
|
||||
day_light = d
|
||||
# the sun, warm and dim near the horizon; past dusk, the moon from across the sky
|
||||
var laz = az; var lel = f_max(el, f_rad(fi(3)))
|
||||
var warm_r = F_ONE; var warm_g = F_ONE; var warm_b = F_ONE
|
||||
let low = smoothf(F_ZERO, f_rad(fi(24)), el)
|
||||
warm_g = f_lerp(fl(0.55), F_ONE, low); warm_b = f_lerp(fl(0.28), F_ONE, low)
|
||||
var laz = az; var lel = Math.max(el, Math.deg_to_rad(3.0))
|
||||
var warm_r = 1.0; var warm_g = 1.0; var warm_b = 1.0
|
||||
let low = smoothf(0.0, Math.deg_to_rad(24.0), el)
|
||||
warm_g = Math.lerp(0.55, 1.0, low); warm_b = Math.lerp(0.28, 1.0, low)
|
||||
# an overcast sky: the sun goes diffuse and grey, a lightning flash brings it back white
|
||||
let oc = f_clamp(day_overcast, F_ZERO, F_ONE)
|
||||
let sunk = f_add(f_sub(F_ONE, f_mul(fl(0.92), oc)), f_mul(fl(2.5), day_flash))
|
||||
var sr = f_mul(day_sun_base[0], f_mul(f_mul(d, warm_r), sunk))
|
||||
var sg = f_mul(day_sun_base[1], f_mul(f_mul(d, warm_g), sunk))
|
||||
var sb = f_mul(day_sun_base[2], f_mul(f_mul(d, warm_b), sunk))
|
||||
let oc = Math.clamp(day_overcast, 0.0, 1.0)
|
||||
let sunk = 1.0 - 0.92 * oc + 2.5 * day_flash
|
||||
var sr = day_sun_base[0] * (d * warm_r * sunk)
|
||||
var sg = day_sun_base[1] * (d * warm_g * sunk)
|
||||
var sb = day_sun_base[2] * (d * warm_b * sunk)
|
||||
# The moon rides a lag behind the sun that is its phase: full is opposite (half a turn),
|
||||
# new is alongside. Its elevation follows the same arc, offset by the same amount, so a
|
||||
# full moon rises as the sun sets and a new moon is up all day and invisible.
|
||||
# The moon is where the sun was `lag` of a day ago: at full that is half a day, so it
|
||||
# rises as the sun sets; at new it is alongside the sun and up all day, invisible. The
|
||||
# sign matters — a waxing crescent has to set AFTER the sun, not before it.
|
||||
let moon_lag = f_mul(f_mul(F_TWO, F_PI), day_moon_phase)
|
||||
let maz = f_sub(az, f_mul(moon_lag, day_dir))
|
||||
let mel = f_rad(f_add(fi(-6), f_mul(fi(63), f_sin(f_sub(t, moon_lag)))))
|
||||
let mce = f_cos(mel)
|
||||
v3_set(day_moon_dir, f_neg(f_mul(f_sin(maz), mce)), f_sin(mel), f_neg(f_mul(f_cos(maz), mce)))
|
||||
let moon_lag = 2.0 * PI * day_moon_phase
|
||||
let maz = az - moon_lag * day_dir
|
||||
let mel = Math.deg_to_rad(-6.0 + 63.0 * Math.sin(t - moon_lag))
|
||||
let mce = Math.cos(mel)
|
||||
v3_set(day_moon_dir, -(Math.sin(maz) * mce), Math.sin(mel), -(Math.cos(maz) * mce))
|
||||
day_moon = false
|
||||
if f_ls(el, f_rad(fi(-7))) {
|
||||
if el < Math.deg_to_rad(-7.0) {
|
||||
day_moon = true
|
||||
laz = maz
|
||||
lel = f_clamp(mel, f_rad(fi(6)), f_rad(fi(70)))
|
||||
let m = smoothf(f_rad(fi(-7)), f_rad(fi(-16)), el)
|
||||
lel = Math.clamp(mel, Math.deg_to_rad(6.0), Math.deg_to_rad(70.0))
|
||||
let m = smoothf(Math.deg_to_rad(-7.0), Math.deg_to_rad(-16.0), el)
|
||||
# what the moon is worth on the ground, by how much of it is lit. A new moon is a
|
||||
# properly dark night, which is what makes a torch and a lantern matter.
|
||||
let up = smoothf(f_rad(fi(-4)), f_rad(fi(8)), mel)
|
||||
let lit = f_mul(f_mul(m, up), f_add(fl(0.06), f_mul(fl(0.94), day_moon_illum)))
|
||||
sr = f_mul(day_sun_base[0], f_mul(fl(0.0130), lit))
|
||||
sg = f_mul(day_sun_base[1], f_mul(fl(0.0165), lit))
|
||||
sb = f_mul(day_sun_base[2], f_mul(fl(0.0250), lit))
|
||||
let up = smoothf(Math.deg_to_rad(-4.0), Math.deg_to_rad(8.0), mel)
|
||||
let lit = m * up * (0.06 + 0.94 * day_moon_illum)
|
||||
sr = day_sun_base[0] * (0.0130 * lit)
|
||||
sg = day_sun_base[1] * (0.0165 * lit)
|
||||
sb = day_sun_base[2] * (0.0250 * lit)
|
||||
}
|
||||
let ce = f_cos(lel)
|
||||
v3_set(sun_dir, f_neg(f_mul(f_sin(laz), ce)), f_sin(lel), f_neg(f_mul(f_cos(laz), ce)))
|
||||
let ce = Math.cos(lel)
|
||||
v3_set(sun_dir, -(Math.sin(laz) * ce), Math.sin(lel), -(Math.cos(laz) * ce))
|
||||
v3_set(sun_color, sr, sg, sb)
|
||||
# the sky's light: full by day, a deep blue by night, amber through the dusk
|
||||
let dusk = f_mul(smoothf(f_rad(fi(-10)), f_rad(fi(2)), el), f_sub(F_ONE, smoothf(f_rad(fi(2)), f_rad(fi(18)), el)))
|
||||
let dusk = smoothf(Math.deg_to_rad(-10.0), Math.deg_to_rad(2.0), el) * (1.0 - smoothf(Math.deg_to_rad(2.0), Math.deg_to_rad(18.0), el))
|
||||
# a full moon lifts the night's own ambient nearly threefold; a new moon leaves it alone
|
||||
var moonlit = F_ONE
|
||||
if day_moon { moonlit = f_add(F_ONE, f_mul(fl(1.8), day_moon_illum)) }
|
||||
v3_set(day_ibl, f_lerp(f_mul(fl(0.020), moonlit), F_ONE, d), f_lerp(f_mul(fl(0.026), moonlit), F_ONE, d), f_lerp(f_mul(fl(0.045), moonlit), F_ONE, d))
|
||||
day_ibl[0] = f_mul(day_ibl[0], f_add(F_ONE, f_mul(fl(0.35), dusk)))
|
||||
day_ibl[2] = f_mul(day_ibl[2], f_sub(F_ONE, f_mul(fl(0.25), dusk)))
|
||||
var moonlit = 1.0
|
||||
if day_moon { moonlit = 1.0 + 1.8 * day_moon_illum }
|
||||
v3_set(day_ibl, Math.lerp(0.020 * moonlit, 1.0, d), Math.lerp(0.026 * moonlit, 1.0, d), Math.lerp(0.045 * moonlit, 1.0, d))
|
||||
day_ibl[0] = day_ibl[0] * (1.0 + 0.35 * dusk)
|
||||
day_ibl[2] = day_ibl[2] * (1.0 - 0.25 * dusk)
|
||||
# clouds: less light, and greyer (the blue and the warmth both fade)
|
||||
let grey = f_add(f_mul(f_add(day_ibl[0], f_add(day_ibl[1], day_ibl[2])), fl(0.3333)), f_mul(fl(0.6), day_flash))
|
||||
let dim = f_sub(F_ONE, f_mul(fl(0.55), oc))
|
||||
for i in 0 .. 3 { day_ibl[i] = f_mul(f_lerp(day_ibl[i], grey, f_mul(fl(0.7), oc)), dim) }
|
||||
let grey = (day_ibl[0] + (day_ibl[1] + day_ibl[2])) * 0.3333 + 0.6 * day_flash
|
||||
let dim = 1.0 - 0.55 * oc
|
||||
for i in 0 .. 3 { day_ibl[i] = Math.lerp(day_ibl[i], grey, 0.7 * oc) * dim }
|
||||
# ---- the air ---------------------------------------------------------------------------
|
||||
# Aerial perspective is a CURVE, not the one constant it was. A low sun is shining through
|
||||
# far more air than a high one, and it is shining ALONG the ground rather than down onto it,
|
||||
|
|
@ -152,25 +152,25 @@ function daylight_set(hours: int) -> void {
|
|||
# gets a dawn's haze with no dawn to justify it. Overcast thickens the air and flattens the
|
||||
# scatter, because a grey sky has no disc to scatter from. The player's slider still
|
||||
# multiplies the result, so nobody loses the setting they chose.
|
||||
if day_fog_base == 0 { day_fog_base = r3d_fog_density }
|
||||
let fog_rise = smoothf(f_rad(fi(-12)), f_rad(fi(1)), el)
|
||||
let fog_high = smoothf(f_rad(fi(2)), f_rad(fi(26)), el)
|
||||
let lowsun = f_mul(fog_rise, f_sub(F_ONE, fog_high))
|
||||
var dens = f_mul(day_fog_base, f_add(F_ONE, f_mul(fl(1.5), lowsun)))
|
||||
dens = f_mul(dens, f_add(F_ONE, f_mul(fl(1.2), oc)))
|
||||
r3d_fog_density = f_mul(f_mul(dens, day_fog_mul), r3d_fog_scale)
|
||||
if day_fog_base == 0.0 { day_fog_base = r3d_fog_density }
|
||||
let fog_rise = smoothf(Math.deg_to_rad(-12.0), Math.deg_to_rad(1.0), el)
|
||||
let fog_high = smoothf(Math.deg_to_rad(2.0), Math.deg_to_rad(26.0), el)
|
||||
let lowsun = fog_rise * (1.0 - fog_high)
|
||||
var dens = day_fog_base * (1.0 + 1.5 * lowsun)
|
||||
dens = dens * (1.0 + 1.2 * oc)
|
||||
r3d_fog_density = dens * day_fog_mul * r3d_fog_scale
|
||||
# how fast the haze thins with height: a settled morning's lies IN the valley, a noon sky is
|
||||
# thin all the way up, so the falloff rises as the sun drops
|
||||
r3d_fog_falloff = f_mul(fl(0.002), f_add(F_ONE, f_mul(fl(1.6), lowsun)))
|
||||
r3d_fog_falloff = 0.002 * (1.0 + 1.6 * lowsun)
|
||||
# the glow a ridge is silhouetted against when you look into a low sun
|
||||
r3d_fog_inscatter = f_mul(f_add(fl(0.012), f_mul(fl(0.16), lowsun)), f_sub(F_ONE, f_mul(fl(0.7), oc)))
|
||||
r3d_fog_inscatter = (0.012 + 0.16 * lowsun) * (1.0 - 0.7 * oc)
|
||||
# and distance takes colour away at every hour, harder under cloud
|
||||
# 1.9 was tuned on a valley whose rock was grey anyway. Maroon's air is thin, dry and at
|
||||
# 2900 m, and the Bells are only a couple of kilometres from the lake - in the photograph
|
||||
# everybody knows, they are still plainly RED at that distance. Desaturating them to a pale
|
||||
# grey-pink is physically defensible and loses the one thing the range is named for, so the
|
||||
# basin's own air gets a gentler figure and overcast still takes colour away faster.
|
||||
r3d_fog_desat = f_add(fl(1.15), f_mul(fl(0.7), oc))
|
||||
r3d_fog_desat = 1.15 + 0.7 * oc
|
||||
# ---- the grade -------------------------------------------------------------------------
|
||||
# The look of an hour is not only how much air is in front of the mountain; it is what colour
|
||||
# the light is and what the shadows are filled with. Noon is the case worth naming: direct
|
||||
|
|
@ -181,31 +181,31 @@ function daylight_set(hours: int) -> void {
|
|||
# `night` is deliberately NOT `1 - d`: d is already falling while the sun is still on the
|
||||
# horizon, so the two curves would fight and the warmest minute of the day would come out
|
||||
# half-cooled. It only begins under the horizon.
|
||||
let night = f_sub(F_ONE, smoothf(f_rad(fi(-14)), f_rad(fi(-4)), el))
|
||||
let night = 1.0 - smoothf(Math.deg_to_rad(-14.0), Math.deg_to_rad(-4.0), el)
|
||||
let hi = fog_high
|
||||
post_wb_r = f_add(fl(1.02), f_sub(f_mul(fl(0.10), lowsun), f_add(f_mul(fl(0.03), hi), f_mul(fl(0.06), night))))
|
||||
post_wb_g = f_add(F_ONE, f_mul(fl(0.012), lowsun))
|
||||
post_wb_b = f_add(fl(0.97), f_sub(f_add(f_mul(fl(0.055), hi), f_mul(fl(0.13), night)), f_mul(fl(0.09), lowsun)))
|
||||
post_wb_r = 1.02 + (0.10 * lowsun - (0.03 * hi + 0.06 * night))
|
||||
post_wb_g = 1.0 + 0.012 * lowsun
|
||||
post_wb_b = 0.97 + (0.055 * hi + 0.13 * night - 0.09 * lowsun)
|
||||
# the shadows' floor: warm and open into a low sun, blue at noon, cold and crushed at night
|
||||
post_lift_r = f_sub(f_add(fl(0.004), f_mul(fl(0.013), lowsun)), f_mul(fl(0.002), f_add(hi, night)))
|
||||
post_lift_g = f_sub(f_add(fl(0.004), f_mul(fl(0.008), lowsun)), f_mul(fl(0.001), night))
|
||||
post_lift_b = f_add(fl(0.012), f_add(f_mul(fl(0.004), lowsun), f_mul(fl(0.013), hi)))
|
||||
post_lift_r = 0.004 + 0.013 * lowsun - 0.002 * (hi + night)
|
||||
post_lift_g = 0.004 + 0.008 * lowsun - 0.001 * night
|
||||
post_lift_b = 0.012 + (0.004 * lowsun + 0.013 * hi)
|
||||
# Gain is left alone on purpose. It looks like a highlight control and is not: the grade is
|
||||
# `c * gain + lift * (1 - c)`, so warming the gain warms the WHOLE frame, and warming it at
|
||||
# noon undid the cool white balance above and turned one o'clock yellower than seven in the
|
||||
# morning - the opposite of the thing this grade exists to do. Midday's punch comes from
|
||||
# contrast, and midday's colour from a cool balance over a blue shadow.
|
||||
post_gain_r = fl(0.99)
|
||||
post_gain_g = fl(0.995)
|
||||
post_gain_b = F_ONE
|
||||
post_contrast = f_sub(f_add(fl(1.12), f_mul(fl(0.11), hi)), f_add(f_mul(fl(0.17), oc), f_mul(fl(0.06), night)))
|
||||
post_saturation = f_sub(f_add(fl(1.04), f_mul(fl(0.12), lowsun)), f_add(f_mul(fl(0.20), oc), f_mul(fl(0.12), night)))
|
||||
post_gain_r = 0.99
|
||||
post_gain_g = 0.995
|
||||
post_gain_b = 1.0
|
||||
post_contrast = 1.12 + 0.11 * hi - (0.17 * oc + 0.06 * night)
|
||||
post_saturation = 1.04 + 0.12 * lowsun - (0.20 * oc + 0.12 * night)
|
||||
|
||||
# The visible sky is relit by the hour (sky.frag). It fades out under the horizon, where the
|
||||
# night's own tint, stars and moon take over, and it eases off under heavy cloud - a relit
|
||||
# overcast is still overcast, and driving a clear-sky model hard through one paints a blue
|
||||
# zenith onto a grey day.
|
||||
r3d_sky_relight = f_mul(smoothf(f_rad(fi(-10)), f_rad(fi(1)), el), f_sub(F_ONE, f_mul(fl(0.75), oc)))
|
||||
r3d_sky_relight = smoothf(Math.deg_to_rad(-10.0), Math.deg_to_rad(1.0), el) * (1.0 - 0.75 * oc)
|
||||
|
||||
# ---- what is IN the air -------------------------------------------------------------
|
||||
# The volumetric march (post.ludic) needs the same story the analytic fog tells, or the
|
||||
|
|
@ -214,64 +214,64 @@ function daylight_set(hours: int) -> void {
|
|||
# the cold at either end of the day, lies ON the ground rather than filling the basin, and
|
||||
# burns off by mid-morning. Cloud thickens the air and kills the shafts, because a shaft
|
||||
# needs a disc to come from.
|
||||
post_vol_density = f_mul(f_add(fl(0.00030), f_mul(fl(0.00070), lowsun)), f_sub(F_ONE, f_mul(fl(0.55), oc)))
|
||||
post_vol_mist = f_mul(f_mul(fl(0.40), lowsun), f_sub(F_ONE, f_mul(fl(0.4), oc)))
|
||||
post_vol_falloff = f_add(fl(0.006), f_mul(fl(0.004), lowsun))
|
||||
post_vol_density = (0.00030 + 0.00070 * lowsun) * (1.0 - 0.55 * oc)
|
||||
post_vol_mist = 0.40 * lowsun * (1.0 - 0.4 * oc)
|
||||
post_vol_falloff = 0.006 + 0.004 * lowsun
|
||||
|
||||
# R3D_NOAIR=1: the air as it was before any of the above - one density, one falloff, the
|
||||
# inscatter the shader used to hard-code, and no distance desaturation at all. It is here
|
||||
# so a before-and-after can be shot from ONE binary at one hour, which is the only kind of
|
||||
# comparison worth looking at, and it joins R3D_NOCLOUD / R3D_NOSHADOW / R3D_NOGI.
|
||||
if r3d_env_has("R3D_NOAIR") {
|
||||
r3d_fog_density = f_mul(f_mul(day_fog_base, day_fog_mul), r3d_fog_scale)
|
||||
r3d_fog_falloff = fl(0.002)
|
||||
r3d_fog_inscatter = fl(0.02)
|
||||
r3d_fog_desat = F_ZERO
|
||||
post_wb_r = fl(1.02); post_wb_g = F_ONE; post_wb_b = fl(0.97)
|
||||
post_lift_r = fl(0.004); post_lift_g = fl(0.004); post_lift_b = fl(0.012)
|
||||
post_gain_r = fl(0.99); post_gain_g = fl(0.995); post_gain_b = F_ONE
|
||||
post_contrast = fl(1.12); post_saturation = fl(1.04)
|
||||
r3d_sky_relight = F_ZERO
|
||||
post_vol_density = F_ZERO; post_vol_mist = F_ZERO
|
||||
r3d_fog_density = day_fog_base * day_fog_mul * r3d_fog_scale
|
||||
r3d_fog_falloff = 0.002
|
||||
r3d_fog_inscatter = 0.02
|
||||
r3d_fog_desat = 0.0
|
||||
post_wb_r = 1.02; post_wb_g = 1.0; post_wb_b = 0.97
|
||||
post_lift_r = 0.004; post_lift_g = 0.004; post_lift_b = 0.012
|
||||
post_gain_r = 0.99; post_gain_g = 0.995; post_gain_b = 1.0
|
||||
post_contrast = 1.12; post_saturation = 1.04
|
||||
r3d_sky_relight = 0.0
|
||||
post_vol_density = 0.0; post_vol_mist = 0.0
|
||||
}
|
||||
# exposure: auto-exposure must not turn the night into day
|
||||
# the ceiling has to move with the moon or auto-exposure eats the difference between a
|
||||
# full-moon night and a new-moon one
|
||||
var night_max = fl(4.5)
|
||||
if day_moon { night_max = f_add(fl(4.5), f_mul(fl(3.5), day_moon_illum)) }
|
||||
post_exposure_max = f_lerp(night_max, fi(20), d)
|
||||
var night_max = 4.5
|
||||
if day_moon { night_max = 4.5 + 3.5 * day_moon_illum }
|
||||
post_exposure_max = Math.lerp(night_max, 20.0, d)
|
||||
# the visible sky turns with the sun (cheap); its convolutions rebake when far off
|
||||
let sky_yaw_now = f_add(day_yaw0, f_mul(f_mul(f_sub(h, day_hour0), f_rad(fi(15))), day_dir))
|
||||
let sky_yaw_now = day_yaw0 + (h - day_hour0) * Math.deg_to_rad(15.0) * day_dir
|
||||
sky_set_rot(sky_yaw_now)
|
||||
if f_gt(f_abs(day_wrap(f_sub(sky_yaw_now, day_sky_baked))), f_rad(fi(35))) and f_gt(d, fl(0.05)) {
|
||||
if Math.abs(day_wrap(sky_yaw_now - day_sky_baked)) > Math.deg_to_rad(35.0) and d > 0.05 {
|
||||
day_sky_baked = sky_yaw_now
|
||||
sky_precompute()
|
||||
}
|
||||
# the terrain's baked shadow follows the light in steps
|
||||
if f_gt(f_abs(day_wrap(f_sub(laz, day_baked_az))), f_rad(fi(4))) or f_gt(f_abs(f_sub(lel, day_baked_el)), f_rad(fi(3))) {
|
||||
if Math.abs(day_wrap(laz - day_baked_az)) > Math.deg_to_rad(4.0) or Math.abs(lel - day_baked_el) > Math.deg_to_rad(3.0) {
|
||||
day_baked_az = laz; day_baked_el = lel
|
||||
day_gen += 1
|
||||
}
|
||||
}
|
||||
|
||||
# where the moon is in its month; the game advances this each morning
|
||||
function daylight_moon(phase: int) -> void {
|
||||
var p = f_mod(phase, F_ONE)
|
||||
if f_ls(p, F_ZERO) { p = f_add(p, F_ONE) }
|
||||
function daylight_moon(phase: float) -> void {
|
||||
var p = phase % 1.0
|
||||
if p < 0.0 { p = p + 1.0 }
|
||||
day_moon_phase = p
|
||||
# illuminated fraction: (1 - cos(2 pi p)) / 2, which is 0 at new and 1 at full
|
||||
day_moon_illum = f_mul(f_sub(F_ONE, f_cos(f_mul(f_mul(F_TWO, F_PI), p))), F_HALF)
|
||||
day_moon_illum = (1.0 - Math.cos(2.0 * PI * p)) * 0.5
|
||||
daylight_set(day_hours)
|
||||
}
|
||||
|
||||
# the weather over the valley: overcast 0..1, a fog multiplier, a lightning flash 0..1
|
||||
function daylight_weather(overcast: int, fog_mul: int, flash: int) -> void {
|
||||
function daylight_weather(overcast: float, fog_mul: float, flash: float) -> void {
|
||||
day_overcast = overcast; day_fog_mul = fog_mul; day_flash = flash
|
||||
}
|
||||
# the campfire: a point light at (x, y, z) of `strength` (0 = out)
|
||||
function daylight_fire(x: int, y: int, z: int, strength: int) -> void {
|
||||
function daylight_fire(x: float, y: float, z: float, strength: float) -> void {
|
||||
v3_set(fire_pos, x, y, z)
|
||||
v3_set(fire_color, f_mul(fl(9.0), strength), f_mul(fl(4.6), strength), f_mul(fl(1.4), strength))
|
||||
v3_set(fire_color, 9.0 * strength, 4.6 * strength, 1.4 * strength)
|
||||
}
|
||||
|
||||
# the light in the hand: a point light (cone < -1) or a cone along dir (cone = cos half-angle)
|
||||
|
|
@ -280,7 +280,7 @@ function daylight_fire(x: int, y: int, z: int, strength: int) -> void {
|
|||
# is what a pool of firelight looks like. It used to be a windowed inverse square with the
|
||||
# window and the scale both hard-coded, so every hand light in every game had the reach of
|
||||
# a candle whatever it was meant to be.
|
||||
function daylight_hand(x: int, y: int, z: int, dx: int, dy: int, dz: int, cone: int, reach: int, r: int, g: int, b: int) -> void {
|
||||
function daylight_hand(x: float, y: float, z: float, dx: float, dy: float, dz: float, cone: float, reach: float, r: float, g: float, b: float) -> void {
|
||||
v3_set(hand_pos, x, y, z); v3_set(hand_dir, dx, dy, dz); hand_cone = cone
|
||||
v3_set(hand_color, r, g, b)
|
||||
hand_reach = reach
|
||||
|
|
|
|||
|
|
@ -16,59 +16,60 @@ const F_ZERO: int = 0x00000000
|
|||
const F_ONE: int = 0x3F800000
|
||||
const F_TWO: int = 0x40000000
|
||||
const F_HALF: int = 0x3F000000
|
||||
const PI: float = 3.1415927
|
||||
const F_PI: int = 0x40490FDB
|
||||
|
||||
function fl(x: fixed) -> int { return fx_to_f32(x) }
|
||||
function fi(n: int) -> int { return f_from_int(n) }
|
||||
function fr(n: int, d: int) -> int { return f_div(f_from_int(n), f_from_int(d)) }
|
||||
function f_neg1() -> int { return f_neg(F_ONE) }
|
||||
function f_clamp(x: int, lo: int, hi: int) -> int { return f_min(f_max(x, lo), hi) }
|
||||
function f_lerp(a: int, b: int, t: int) -> int { return f_add(a, f_mul(f_sub(b, a), t)) }
|
||||
function f_gt(a: int, b: int) -> bool { return f_lt(b, a) != 0 }
|
||||
function f_ls(a: int, b: int) -> bool { return f_lt(a, b) != 0 }
|
||||
function f_rad(deg: int) -> int { return f_mul(deg, f_div(F_PI, fi(180))) }
|
||||
function f_fx(x: int) -> fixed { return f32_to_fx(x) }
|
||||
function fl(x: fixed) -> float { return float(x) }
|
||||
function fi(n: int) -> float { return float(n) }
|
||||
function fr(n: int, d: int) -> float { return float(n) / float(d) }
|
||||
function f_neg1() -> float { return -1.0 }
|
||||
function f_clamp(x: float, lo: float, hi: float) -> float { return Math.min(Math.max(x, lo), hi) }
|
||||
function f_lerp(a: float, b: float, t: float) -> float { return a + (b - a) * t }
|
||||
function f_gt(a: float, b: float) -> bool { return f_lt(b, a) != 0 }
|
||||
function f_ls(a: float, b: float) -> bool { return f_lt(a, b) != 0 }
|
||||
function f_rad(deg: float) -> float { return deg * (PI / 180.0) }
|
||||
function f_fx(x: float) -> fixed { return fixed(x) }
|
||||
|
||||
# ---- vectors ----------------------------------------------------------------
|
||||
function v3_new(x: int, y: int, z: int) -> words {
|
||||
let v = words(3)
|
||||
function v3_new(x: float, y: float, z: float) -> floats {
|
||||
let v = floats(3)
|
||||
v[0] = x
|
||||
v[1] = y
|
||||
v[2] = z
|
||||
return v
|
||||
}
|
||||
function v3_set(v: words, x: int, y: int, z: int) -> void { v[0] = x; v[1] = y; v[2] = z }
|
||||
function v3_copy(o: words, a: words) -> void { o[0] = a[0]; o[1] = a[1]; o[2] = a[2] }
|
||||
function v3_add(o: words, a: words, b: words) -> void {
|
||||
o[0] = f_add(a[0], b[0]); o[1] = f_add(a[1], b[1]); o[2] = f_add(a[2], b[2])
|
||||
function v3_set(v: floats, x: float, y: float, z: float) -> void { v[0] = x; v[1] = y; v[2] = z }
|
||||
function v3_copy(o: floats, a: floats) -> void { o[0] = a[0]; o[1] = a[1]; o[2] = a[2] }
|
||||
function v3_add(o: floats, a: floats, b: floats) -> void {
|
||||
o[0] = a[0] + b[0]; o[1] = a[1] + b[1]; o[2] = a[2] + b[2]
|
||||
}
|
||||
function v3_sub(o: words, a: words, b: words) -> void {
|
||||
o[0] = f_sub(a[0], b[0]); o[1] = f_sub(a[1], b[1]); o[2] = f_sub(a[2], b[2])
|
||||
function v3_sub(o: floats, a: floats, b: floats) -> void {
|
||||
o[0] = a[0] - b[0]; o[1] = a[1] - b[1]; o[2] = a[2] - b[2]
|
||||
}
|
||||
function v3_scale(o: words, a: words, s: int) -> void {
|
||||
o[0] = f_mul(a[0], s); o[1] = f_mul(a[1], s); o[2] = f_mul(a[2], s)
|
||||
function v3_scale(o: floats, a: floats, s: float) -> void {
|
||||
o[0] = a[0] * s; o[1] = a[1] * s; o[2] = a[2] * s
|
||||
}
|
||||
function v3_madd(o: words, a: words, b: words, s: int) -> void { # o = a + b*s
|
||||
o[0] = f_add(a[0], f_mul(b[0], s)); o[1] = f_add(a[1], f_mul(b[1], s)); o[2] = f_add(a[2], f_mul(b[2], s))
|
||||
function v3_madd(o: floats, a: floats, b: floats, s: float) -> void { # o = a + b*s
|
||||
o[0] = a[0] + b[0] * s; o[1] = a[1] + b[1] * s; o[2] = a[2] + b[2] * s
|
||||
}
|
||||
function v3_dot(a: words, b: words) -> int {
|
||||
return f_add(f_add(f_mul(a[0], b[0]), f_mul(a[1], b[1])), f_mul(a[2], b[2]))
|
||||
function v3_dot(a: floats, b: floats) -> float {
|
||||
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
|
||||
}
|
||||
function v3_cross(o: words, a: words, b: words) -> void {
|
||||
let x = f_sub(f_mul(a[1], b[2]), f_mul(a[2], b[1]))
|
||||
let y = f_sub(f_mul(a[2], b[0]), f_mul(a[0], b[2]))
|
||||
let z = f_sub(f_mul(a[0], b[1]), f_mul(a[1], b[0]))
|
||||
function v3_cross(o: floats, a: floats, b: floats) -> void {
|
||||
let x = a[1] * b[2] - a[2] * b[1]
|
||||
let y = a[2] * b[0] - a[0] * b[2]
|
||||
let z = a[0] * b[1] - a[1] * b[0]
|
||||
o[0] = x; o[1] = y; o[2] = z
|
||||
}
|
||||
function v3_len(a: words) -> int { return f_sqrt(v3_dot(a, a)) }
|
||||
function v3_normalize(o: words, a: words) -> void {
|
||||
function v3_len(a: floats) -> float { return Math.sqrt(v3_dot(a, a)) }
|
||||
function v3_normalize(o: floats, a: floats) -> void {
|
||||
let l = v3_len(a)
|
||||
if l == 0 { o[0] = 0; o[1] = 0; o[2] = 0; return }
|
||||
let inv = f_div(F_ONE, l)
|
||||
if l == 0.0 { o[0] = 0.0; o[1] = 0.0; o[2] = 0.0; return }
|
||||
let inv = 1.0 / l
|
||||
v3_scale(o, a, inv)
|
||||
}
|
||||
function v3_dist(a: words, b: words) -> int {
|
||||
let t = words(3)
|
||||
function v3_dist(a: floats, b: floats) -> float {
|
||||
let t = floats(3)
|
||||
v3_sub(t, a, b)
|
||||
let d = v3_len(t)
|
||||
free(t)
|
||||
|
|
@ -76,78 +77,78 @@ function v3_dist(a: words, b: words) -> int {
|
|||
}
|
||||
|
||||
# ---- matrices (column-major, m[col*4 + row]) ------------------------------------
|
||||
function m4_new() -> words { let m = words(16); m4_identity(m); return m }
|
||||
function m4_identity(m: words) -> void {
|
||||
for i in 0 .. 16 { m[i] = F_ZERO }
|
||||
m[0] = F_ONE; m[5] = F_ONE; m[10] = F_ONE; m[15] = F_ONE
|
||||
function m4_new() -> floats { let m = floats(16); m4_identity(m); return m }
|
||||
function m4_identity(m: floats) -> void {
|
||||
for i in 0 .. 16 { m[i] = 0.0 }
|
||||
m[0] = 1.0; m[5] = 1.0; m[10] = 1.0; m[15] = 1.0
|
||||
}
|
||||
function m4_copy(o: words, a: words) -> void { for i in 0 .. 16 { o[i] = a[i] } }
|
||||
function m4_copy(o: floats, a: floats) -> void { for i in 0 .. 16 { o[i] = a[i] } }
|
||||
# o = a * b (o may not alias a or b)
|
||||
function m4_mul(o: words, a: words, b: words) -> void {
|
||||
function m4_mul(o: floats, a: floats, b: floats) -> void {
|
||||
for c in 0 .. 4 {
|
||||
for r in 0 .. 4 {
|
||||
var s = F_ZERO
|
||||
for k in 0 .. 4 { s = f_add(s, f_mul(a[k * 4 + r], b[c * 4 + k])) }
|
||||
var s = 0.0
|
||||
for k in 0 .. 4 { s = s + a[k * 4 + r] * b[c * 4 + k] }
|
||||
o[c * 4 + r] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
function m4_mul_into(a: words, b: words) -> void { # a = a * b
|
||||
let t = words(16)
|
||||
function m4_mul_into(a: floats, b: floats) -> void { # a = a * b
|
||||
let t = floats(16)
|
||||
m4_mul(t, a, b)
|
||||
m4_copy(a, t)
|
||||
free(t)
|
||||
}
|
||||
function m4_translation(m: words, x: int, y: int, z: int) -> void {
|
||||
function m4_translation(m: floats, x: float, y: float, z: float) -> void {
|
||||
m4_identity(m)
|
||||
m[12] = x; m[13] = y; m[14] = z
|
||||
}
|
||||
function m4_scaling(m: words, x: int, y: int, z: int) -> void {
|
||||
function m4_scaling(m: floats, x: float, y: float, z: float) -> void {
|
||||
m4_identity(m)
|
||||
m[0] = x; m[5] = y; m[10] = z
|
||||
}
|
||||
function m4_rotation_y(m: words, angle: int) -> void {
|
||||
function m4_rotation_y(m: floats, angle: float) -> void {
|
||||
m4_identity(m)
|
||||
let c = f_cos(angle); let s = f_sin(angle)
|
||||
m[0] = c; m[2] = f_neg(s); m[8] = s; m[10] = c
|
||||
let c = Math.cos(angle); let s = Math.sin(angle)
|
||||
m[0] = c; m[2] = -s; m[8] = s; m[10] = c
|
||||
}
|
||||
function m4_rotation_x(m: words, angle: int) -> void {
|
||||
function m4_rotation_x(m: floats, angle: float) -> void {
|
||||
m4_identity(m)
|
||||
let c = f_cos(angle); let s = f_sin(angle)
|
||||
m[5] = c; m[6] = s; m[9] = f_neg(s); m[10] = c
|
||||
let c = Math.cos(angle); let s = Math.sin(angle)
|
||||
m[5] = c; m[6] = s; m[9] = -s; m[10] = c
|
||||
}
|
||||
function m4_rotation_z(m: words, angle: int) -> void {
|
||||
function m4_rotation_z(m: floats, angle: float) -> void {
|
||||
m4_identity(m)
|
||||
let c = f_cos(angle); let s = f_sin(angle)
|
||||
m[0] = c; m[1] = s; m[4] = f_neg(s); m[5] = c
|
||||
let c = Math.cos(angle); let s = Math.sin(angle)
|
||||
m[0] = c; m[1] = s; m[4] = -s; m[5] = c
|
||||
}
|
||||
# a model matrix: translate * rotate_y * uniform scale
|
||||
function m4_trs(m: words, x: int, y: int, z: int, yaw: int, s: int) -> void {
|
||||
function m4_trs(m: floats, x: float, y: float, z: float, yaw: float, s: float) -> void {
|
||||
m4_rotation_y(m, yaw)
|
||||
for i in 0 .. 12 { m[i] = f_mul(m[i], s) }
|
||||
for i in 0 .. 12 { m[i] = m[i] * s }
|
||||
m[12] = x; m[13] = y; m[14] = z
|
||||
}
|
||||
# OpenGL clip space (z in [-1, 1]); fovy in radians
|
||||
function m4_perspective(m: words, fovy: int, aspect: int, near: int, far: int) -> void {
|
||||
for i in 0 .. 16 { m[i] = F_ZERO }
|
||||
let f = f_div(F_ONE, f_tan(f_mul(fovy, F_HALF)))
|
||||
m[0] = f_div(f, aspect)
|
||||
function m4_perspective(m: floats, fovy: float, aspect: float, near: float, far: float) -> void {
|
||||
for i in 0 .. 16 { m[i] = 0.0 }
|
||||
let f = 1.0 / Math.tan(fovy * 0.5)
|
||||
m[0] = f / aspect
|
||||
m[5] = f
|
||||
m[10] = f_div(f_add(far, near), f_sub(near, far))
|
||||
m[11] = f_neg(F_ONE)
|
||||
m[14] = f_div(f_mul(f_mul(F_TWO, far), near), f_sub(near, far))
|
||||
m[10] = (far + near) / (near - far)
|
||||
m[11] = -1.0
|
||||
m[14] = 2.0 * far * near / (near - far)
|
||||
}
|
||||
function m4_ortho(m: words, l: int, r: int, b: int, t: int, n: int, f: int) -> void {
|
||||
function m4_ortho(m: floats, l: float, r: float, b: float, t: float, n: float, f: float) -> void {
|
||||
m4_identity(m)
|
||||
m[0] = f_div(F_TWO, f_sub(r, l))
|
||||
m[5] = f_div(F_TWO, f_sub(t, b))
|
||||
m[10] = f_div(f_neg(F_TWO), f_sub(f, n))
|
||||
m[12] = f_neg(f_div(f_add(r, l), f_sub(r, l)))
|
||||
m[13] = f_neg(f_div(f_add(t, b), f_sub(t, b)))
|
||||
m[14] = f_neg(f_div(f_add(f, n), f_sub(f, n)))
|
||||
m[0] = 2.0 / (r - l)
|
||||
m[5] = 2.0 / (t - b)
|
||||
m[10] = -2.0 / (f - n)
|
||||
m[12] = -((r + l) / (r - l))
|
||||
m[13] = -((t + b) / (t - b))
|
||||
m[14] = -((f + n) / (f - n))
|
||||
}
|
||||
function m4_look_at(m: words, eye: words, at: words, up: words) -> void {
|
||||
let fwd = words(3); let side = words(3); let u = words(3); let t = words(3)
|
||||
function m4_look_at(m: floats, eye: floats, at: floats, up: floats) -> void {
|
||||
let fwd = floats(3); let side = floats(3); let u = floats(3); let t = floats(3)
|
||||
v3_sub(t, at, eye)
|
||||
v3_normalize(fwd, t)
|
||||
v3_cross(t, fwd, up)
|
||||
|
|
@ -156,44 +157,44 @@ function m4_look_at(m: words, eye: words, at: words, up: words) -> void {
|
|||
m4_identity(m)
|
||||
m[0] = side[0]; m[4] = side[1]; m[8] = side[2]
|
||||
m[1] = u[0]; m[5] = u[1]; m[9] = u[2]
|
||||
m[2] = f_neg(fwd[0]); m[6] = f_neg(fwd[1]); m[10] = f_neg(fwd[2])
|
||||
m[12] = f_neg(v3_dot(side, eye))
|
||||
m[13] = f_neg(v3_dot(u, eye))
|
||||
m[2] = -fwd[0]; m[6] = -fwd[1]; m[10] = -fwd[2]
|
||||
m[12] = -v3_dot(side, eye)
|
||||
m[13] = -v3_dot(u, eye)
|
||||
m[14] = v3_dot(fwd, eye)
|
||||
free(fwd); free(side); free(u); free(t)
|
||||
}
|
||||
# general 4x4 inverse (cofactor expansion); o may not alias a
|
||||
function m4_inverse(o: words, a: words) -> bool {
|
||||
let inv = words(16)
|
||||
inv[0] = f_add(f_sub(f_add(f_mul(a[5], f_mul(a[10], a[15])), f_neg(f_mul(a[5], f_mul(a[11], a[14])))), f_mul(a[9], f_mul(a[6], a[15]))), f_add(f_mul(a[9], f_mul(a[7], a[14])), f_sub(f_mul(a[13], f_mul(a[6], a[11])), f_mul(a[13], f_mul(a[7], a[10])))))
|
||||
inv[4] = f_add(f_sub(f_add(f_neg(f_mul(a[4], f_mul(a[10], a[15]))), f_mul(a[4], f_mul(a[11], a[14]))), f_mul(a[8], f_mul(a[7], a[14]))), f_add(f_mul(a[8], f_mul(a[6], a[15])), f_sub(f_mul(a[12], f_mul(a[7], a[10])), f_mul(a[12], f_mul(a[6], a[11])))))
|
||||
inv[8] = f_add(f_sub(f_add(f_mul(a[4], f_mul(a[9], a[15])), f_neg(f_mul(a[4], f_mul(a[11], a[13])))), f_mul(a[8], f_mul(a[5], a[15]))), f_add(f_mul(a[8], f_mul(a[7], a[13])), f_sub(f_mul(a[12], f_mul(a[5], a[11])), f_mul(a[12], f_mul(a[7], a[9])))))
|
||||
inv[12] = f_add(f_sub(f_add(f_neg(f_mul(a[4], f_mul(a[9], a[14]))), f_mul(a[4], f_mul(a[10], a[13]))), f_mul(a[8], f_mul(a[6], a[13]))), f_add(f_mul(a[8], f_mul(a[5], a[14])), f_sub(f_mul(a[12], f_mul(a[6], a[9])), f_mul(a[12], f_mul(a[5], a[10])))))
|
||||
inv[1] = f_add(f_sub(f_add(f_neg(f_mul(a[1], f_mul(a[10], a[15]))), f_mul(a[1], f_mul(a[11], a[14]))), f_mul(a[9], f_mul(a[3], a[14]))), f_add(f_mul(a[9], f_mul(a[2], a[15])), f_sub(f_mul(a[13], f_mul(a[3], a[10])), f_mul(a[13], f_mul(a[2], a[11])))))
|
||||
inv[5] = f_add(f_sub(f_add(f_mul(a[0], f_mul(a[10], a[15])), f_neg(f_mul(a[0], f_mul(a[11], a[14])))), f_mul(a[8], f_mul(a[2], a[15]))), f_add(f_mul(a[8], f_mul(a[3], a[14])), f_sub(f_mul(a[12], f_mul(a[2], a[11])), f_mul(a[12], f_mul(a[3], a[10])))))
|
||||
inv[9] = f_add(f_sub(f_add(f_neg(f_mul(a[0], f_mul(a[9], a[15]))), f_mul(a[0], f_mul(a[11], a[13]))), f_mul(a[8], f_mul(a[3], a[13]))), f_add(f_mul(a[8], f_mul(a[1], a[15])), f_sub(f_mul(a[12], f_mul(a[3], a[9])), f_mul(a[12], f_mul(a[1], a[11])))))
|
||||
inv[13] = f_add(f_sub(f_add(f_mul(a[0], f_mul(a[9], a[14])), f_neg(f_mul(a[0], f_mul(a[10], a[13])))), f_mul(a[8], f_mul(a[1], a[14]))), f_add(f_mul(a[8], f_mul(a[2], a[13])), f_sub(f_mul(a[12], f_mul(a[1], a[10])), f_mul(a[12], f_mul(a[2], a[9])))))
|
||||
inv[2] = f_add(f_sub(f_add(f_mul(a[1], f_mul(a[6], a[15])), f_neg(f_mul(a[1], f_mul(a[7], a[14])))), f_mul(a[5], f_mul(a[2], a[15]))), f_add(f_mul(a[5], f_mul(a[3], a[14])), f_sub(f_mul(a[13], f_mul(a[2], a[7])), f_mul(a[13], f_mul(a[3], a[6])))))
|
||||
inv[6] = f_add(f_sub(f_add(f_neg(f_mul(a[0], f_mul(a[6], a[15]))), f_mul(a[0], f_mul(a[7], a[14]))), f_mul(a[4], f_mul(a[3], a[14]))), f_add(f_mul(a[4], f_mul(a[2], a[15])), f_sub(f_mul(a[12], f_mul(a[3], a[6])), f_mul(a[12], f_mul(a[2], a[7])))))
|
||||
inv[10] = f_add(f_sub(f_add(f_mul(a[0], f_mul(a[5], a[15])), f_neg(f_mul(a[0], f_mul(a[7], a[13])))), f_mul(a[4], f_mul(a[1], a[15]))), f_add(f_mul(a[4], f_mul(a[3], a[13])), f_sub(f_mul(a[12], f_mul(a[1], a[7])), f_mul(a[12], f_mul(a[3], a[5])))))
|
||||
inv[14] = f_add(f_sub(f_add(f_neg(f_mul(a[0], f_mul(a[5], a[14]))), f_mul(a[0], f_mul(a[6], a[13]))), f_mul(a[4], f_mul(a[2], a[13]))), f_add(f_mul(a[4], f_mul(a[1], a[14])), f_sub(f_mul(a[12], f_mul(a[2], a[5])), f_mul(a[12], f_mul(a[1], a[6])))))
|
||||
inv[3] = f_add(f_sub(f_add(f_neg(f_mul(a[1], f_mul(a[6], a[11]))), f_mul(a[1], f_mul(a[7], a[10]))), f_mul(a[5], f_mul(a[3], a[10]))), f_add(f_mul(a[5], f_mul(a[2], a[11])), f_sub(f_mul(a[9], f_mul(a[3], a[6])), f_mul(a[9], f_mul(a[2], a[7])))))
|
||||
inv[7] = f_add(f_sub(f_add(f_mul(a[0], f_mul(a[6], a[11])), f_neg(f_mul(a[0], f_mul(a[7], a[10])))), f_mul(a[4], f_mul(a[2], a[11]))), f_add(f_mul(a[4], f_mul(a[3], a[10])), f_sub(f_mul(a[8], f_mul(a[2], a[7])), f_mul(a[8], f_mul(a[3], a[6])))))
|
||||
inv[11] = f_add(f_sub(f_add(f_neg(f_mul(a[0], f_mul(a[5], a[11]))), f_mul(a[0], f_mul(a[7], a[9]))), f_mul(a[4], f_mul(a[3], a[9]))), f_add(f_mul(a[4], f_mul(a[1], a[11])), f_sub(f_mul(a[8], f_mul(a[3], a[5])), f_mul(a[8], f_mul(a[1], a[7])))))
|
||||
inv[15] = f_add(f_sub(f_add(f_mul(a[0], f_mul(a[5], a[10])), f_neg(f_mul(a[0], f_mul(a[6], a[9])))), f_mul(a[4], f_mul(a[1], a[10]))), f_add(f_mul(a[4], f_mul(a[2], a[9])), f_sub(f_mul(a[8], f_mul(a[1], a[6])), f_mul(a[8], f_mul(a[2], a[5])))))
|
||||
let det = f_add(f_add(f_mul(a[0], inv[0]), f_mul(a[1], inv[4])), f_add(f_mul(a[2], inv[8]), f_mul(a[3], inv[12])))
|
||||
if det == 0 { free(inv); return false }
|
||||
let id = f_div(F_ONE, det)
|
||||
for i in 0 .. 16 { o[i] = f_mul(inv[i], id) }
|
||||
function m4_inverse(o: floats, a: floats) -> bool {
|
||||
let inv = floats(16)
|
||||
inv[0] = a[5] * (a[10] * a[15]) + -(a[5] * (a[11] * a[14])) - a[9] * (a[6] * a[15]) + (a[9] * (a[7] * a[14]) + (a[13] * (a[6] * a[11]) - a[13] * (a[7] * a[10])))
|
||||
inv[4] = -(a[4] * (a[10] * a[15])) + a[4] * (a[11] * a[14]) - a[8] * (a[7] * a[14]) + (a[8] * (a[6] * a[15]) + (a[12] * (a[7] * a[10]) - a[12] * (a[6] * a[11])))
|
||||
inv[8] = a[4] * (a[9] * a[15]) + -(a[4] * (a[11] * a[13])) - a[8] * (a[5] * a[15]) + (a[8] * (a[7] * a[13]) + (a[12] * (a[5] * a[11]) - a[12] * (a[7] * a[9])))
|
||||
inv[12] = -(a[4] * (a[9] * a[14])) + a[4] * (a[10] * a[13]) - a[8] * (a[6] * a[13]) + (a[8] * (a[5] * a[14]) + (a[12] * (a[6] * a[9]) - a[12] * (a[5] * a[10])))
|
||||
inv[1] = -(a[1] * (a[10] * a[15])) + a[1] * (a[11] * a[14]) - a[9] * (a[3] * a[14]) + (a[9] * (a[2] * a[15]) + (a[13] * (a[3] * a[10]) - a[13] * (a[2] * a[11])))
|
||||
inv[5] = a[0] * (a[10] * a[15]) + -(a[0] * (a[11] * a[14])) - a[8] * (a[2] * a[15]) + (a[8] * (a[3] * a[14]) + (a[12] * (a[2] * a[11]) - a[12] * (a[3] * a[10])))
|
||||
inv[9] = -(a[0] * (a[9] * a[15])) + a[0] * (a[11] * a[13]) - a[8] * (a[3] * a[13]) + (a[8] * (a[1] * a[15]) + (a[12] * (a[3] * a[9]) - a[12] * (a[1] * a[11])))
|
||||
inv[13] = a[0] * (a[9] * a[14]) + -(a[0] * (a[10] * a[13])) - a[8] * (a[1] * a[14]) + (a[8] * (a[2] * a[13]) + (a[12] * (a[1] * a[10]) - a[12] * (a[2] * a[9])))
|
||||
inv[2] = a[1] * (a[6] * a[15]) + -(a[1] * (a[7] * a[14])) - a[5] * (a[2] * a[15]) + (a[5] * (a[3] * a[14]) + (a[13] * (a[2] * a[7]) - a[13] * (a[3] * a[6])))
|
||||
inv[6] = -(a[0] * (a[6] * a[15])) + a[0] * (a[7] * a[14]) - a[4] * (a[3] * a[14]) + (a[4] * (a[2] * a[15]) + (a[12] * (a[3] * a[6]) - a[12] * (a[2] * a[7])))
|
||||
inv[10] = a[0] * (a[5] * a[15]) + -(a[0] * (a[7] * a[13])) - a[4] * (a[1] * a[15]) + (a[4] * (a[3] * a[13]) + (a[12] * (a[1] * a[7]) - a[12] * (a[3] * a[5])))
|
||||
inv[14] = -(a[0] * (a[5] * a[14])) + a[0] * (a[6] * a[13]) - a[4] * (a[2] * a[13]) + (a[4] * (a[1] * a[14]) + (a[12] * (a[2] * a[5]) - a[12] * (a[1] * a[6])))
|
||||
inv[3] = -(a[1] * (a[6] * a[11])) + a[1] * (a[7] * a[10]) - a[5] * (a[3] * a[10]) + (a[5] * (a[2] * a[11]) + (a[9] * (a[3] * a[6]) - a[9] * (a[2] * a[7])))
|
||||
inv[7] = a[0] * (a[6] * a[11]) + -(a[0] * (a[7] * a[10])) - a[4] * (a[2] * a[11]) + (a[4] * (a[3] * a[10]) + (a[8] * (a[2] * a[7]) - a[8] * (a[3] * a[6])))
|
||||
inv[11] = -(a[0] * (a[5] * a[11])) + a[0] * (a[7] * a[9]) - a[4] * (a[3] * a[9]) + (a[4] * (a[1] * a[11]) + (a[8] * (a[3] * a[5]) - a[8] * (a[1] * a[7])))
|
||||
inv[15] = a[0] * (a[5] * a[10]) + -(a[0] * (a[6] * a[9])) - a[4] * (a[1] * a[10]) + (a[4] * (a[2] * a[9]) + (a[8] * (a[1] * a[6]) - a[8] * (a[2] * a[5])))
|
||||
let det = a[0] * inv[0] + a[1] * inv[4] + (a[2] * inv[8] + a[3] * inv[12])
|
||||
if det == 0.0 { free(inv); return false }
|
||||
let id = 1.0 / det
|
||||
for i in 0 .. 16 { o[i] = inv[i] * id }
|
||||
free(inv)
|
||||
return true
|
||||
}
|
||||
# transform a point (w = 1) by m: o = m * (x, y, z, 1), returns w
|
||||
function m4_xform_point(o: words, m: words, x: int, y: int, z: int) -> int {
|
||||
o[0] = f_add(f_add(f_mul(m[0], x), f_mul(m[4], y)), f_add(f_mul(m[8], z), m[12]))
|
||||
o[1] = f_add(f_add(f_mul(m[1], x), f_mul(m[5], y)), f_add(f_mul(m[9], z), m[13]))
|
||||
o[2] = f_add(f_add(f_mul(m[2], x), f_mul(m[6], y)), f_add(f_mul(m[10], z), m[14]))
|
||||
return f_add(f_add(f_mul(m[3], x), f_mul(m[7], y)), f_add(f_mul(m[11], z), m[15]))
|
||||
function m4_xform_point(o: floats, m: floats, x: float, y: float, z: float) -> float {
|
||||
o[0] = m[0] * x + m[4] * y + (m[8] * z + m[12])
|
||||
o[1] = m[1] * x + m[5] * y + (m[9] * z + m[13])
|
||||
o[2] = m[2] * x + m[6] * y + (m[10] * z + m[14])
|
||||
return m[3] * x + m[7] * y + (m[11] * z + m[15])
|
||||
}
|
||||
|
||||
# ---- uniforms: see gpu.ludic (gpu_uniform and the u_* setters) ----
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ property Prim {
|
|||
}
|
||||
property Model {
|
||||
prims: []Prim,
|
||||
radius: int = 0, # float bits: max horizontal extent from the origin
|
||||
height: int = 0, # float bits: y extent above ymin
|
||||
ymin: int = 0,
|
||||
radius: float = 0.0, # float bits: max horizontal extent from the origin
|
||||
height: float = 0.0, # float bits: y extent above ymin
|
||||
ymin: float = 0.0,
|
||||
tris: int = 0,
|
||||
skin: Skin # the skeleton, for a skinned node (skin.ludic); null for a rigid model
|
||||
}
|
||||
|
|
@ -35,9 +35,9 @@ var gltf_white: int = 0
|
|||
var gltf_flat: int = 0
|
||||
|
||||
# a JSON number as float bits (ints and fixed-point decimals both)
|
||||
function jnum(v: Val) -> int {
|
||||
if v.tag == 2 { return fx_to_f32(v.num) }
|
||||
return f_from_int(v.num)
|
||||
function jnum(v: Val) -> float {
|
||||
if v.tag == 2 { return float(v.num) / 65536.0 }
|
||||
return float(v.num)
|
||||
}
|
||||
function jint(v: Val, key: pointer, fallback: int) -> int {
|
||||
if value_has(v, key) == 0 { return fallback }
|
||||
|
|
@ -197,7 +197,7 @@ function gltf_load(dir: string, file: string, node_name: string) -> Model {
|
|||
model.prims = new []Prim
|
||||
let mesh = value_at(value_get(gltf_doc, "meshes"), mesh_idx)
|
||||
let prims = value_get(mesh, "primitives")
|
||||
var r2 = F_ZERO; var ymin = fi(1000); var ymax = fi(-1000)
|
||||
var r2 = 0.0; var ymin = 1000.0; var ymax = -1000.0
|
||||
for i in 0 .. value_count(prims) {
|
||||
let p = value_at(prims, i)
|
||||
push(model.prims, gltf_prim(p))
|
||||
|
|
@ -205,20 +205,20 @@ function gltf_load(dir: string, file: string, node_name: string) -> Model {
|
|||
# bounds from the accessor min/max
|
||||
let acc = value_at(value_get(gltf_doc, "accessors"), value_as_int(value_get(value_get(p, "attributes"), "POSITION")))
|
||||
let mn = value_get(acc, "min"); let mx = value_get(acc, "max")
|
||||
let x0 = f_abs(jnum(value_at(mn, 0))); let x1 = f_abs(jnum(value_at(mx, 0)))
|
||||
let z0 = f_abs(jnum(value_at(mn, 2))); let z1 = f_abs(jnum(value_at(mx, 2)))
|
||||
let rx = f_max(x0, x1); let rz = f_max(z0, z1)
|
||||
let rr = f_add(f_mul(rx, rx), f_mul(rz, rz))
|
||||
if f_gt(rr, r2) { r2 = rr }
|
||||
let x0 = Math.abs(jnum(value_at(mn, 0))); let x1 = Math.abs(jnum(value_at(mx, 0)))
|
||||
let z0 = Math.abs(jnum(value_at(mn, 2))); let z1 = Math.abs(jnum(value_at(mx, 2)))
|
||||
let rx = Math.max(x0, x1); let rz = Math.max(z0, z1)
|
||||
let rr = rx * rx + rz * rz
|
||||
if rr > r2 { r2 = rr }
|
||||
let y0 = jnum(value_at(mn, 1)); let y1 = jnum(value_at(mx, 1))
|
||||
if f_ls(y0, ymin) { ymin = y0 }
|
||||
if f_gt(y1, ymax) { ymax = y1 }
|
||||
if y0 < ymin { ymin = y0 }
|
||||
if y1 > ymax { ymax = y1 }
|
||||
}
|
||||
if skin_idx >= 0 { model.skin = skin_load(skin_idx) }
|
||||
file_close(gltf_bin)
|
||||
model.radius = f_sqrt(r2)
|
||||
model.radius = Math.sqrt(r2)
|
||||
model.ymin = ymin
|
||||
model.height = f_sub(ymax, ymin)
|
||||
model.height = ymax - ymin
|
||||
print(`gltf: {node_name}: {len(model.prims)} prims, {model.tris} tris`)
|
||||
return model
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,13 +149,13 @@ function gpu_alpha_to_coverage(on: bool) -> void {
|
|||
# Depth bias for the shadow casters; (0, 0) turns it off. factor/units are fixed, as
|
||||
# gl_polygon_offset takes them. A pipeline API bakes the bias into the pipeline.
|
||||
var gpu_s_bias: int = -1
|
||||
var gpu_s_bias_f: int = 0 # float bits, for a backend that bakes the bias into a pipeline
|
||||
var gpu_s_bias_u: int = 0
|
||||
var gpu_s_bias_f: float = 0.0 # float bits, for a backend that bakes the bias into a pipeline
|
||||
var gpu_s_bias_u: float = 0.0
|
||||
function gpu_depth_bias(factor: fixed, units: fixed) -> void {
|
||||
var v = 1
|
||||
if factor == 0.0 and units == 0.0 { v = 0 }
|
||||
if v != gpu_s_bias { gpu_s_bias = v; gpu_gl_cap(GL_POLYGON_OFFSET_FILL, v) }
|
||||
gpu_s_bias_f = fx_to_f32(factor); gpu_s_bias_u = fx_to_f32(units)
|
||||
gpu_s_bias_f = float(factor); gpu_s_bias_u = float(units)
|
||||
if v == 1 and gpu_kind != GPU_VK { gl_polygon_offset(factor, units) }
|
||||
}
|
||||
|
||||
|
|
@ -241,18 +241,18 @@ function gpu_screenshot(path: string) -> bool { if gpu_kind == GPU_VK { return g
|
|||
var gpu_u_tmp: words = null
|
||||
function gpu_tmp() -> words { if gpu_u_tmp == null { gpu_u_tmp = words(4) }; return gpu_u_tmp }
|
||||
# float bits (IEEE singles in an int), like every other number in the renderer
|
||||
function u_f(loc: int, v: int) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = v; gvk_u_set(loc, t, 4, 1); return }; let t = gpu_tmp(); t[0] = v; gl_uniform1fv(loc, 1, t) }
|
||||
function u_f2(loc: int, x: int, y: int) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = x; t[1] = y; gvk_u_set(loc, t, 8, 1); return }; let t = gpu_tmp(); t[0] = x; t[1] = y; gl_uniform2fv(loc, 1, t) }
|
||||
function u_f3(loc: int, x: int, y: int, z: int) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = x; t[1] = y; t[2] = z; gvk_u_set(loc, t, 12, 1); return }; let t = gpu_tmp(); t[0] = x; t[1] = y; t[2] = z; gl_uniform3fv(loc, 1, t) }
|
||||
function u_f4(loc: int, x: int, y: int, z: int, w: int) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = x; t[1] = y; t[2] = z; t[3] = w; gvk_u_set(loc, t, 16, 1); return }; let t = gpu_tmp(); t[0] = x; t[1] = y; t[2] = z; t[3] = w; gl_uniform4fv(loc, 1, t) }
|
||||
function u_v3(loc: int, v: words) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, v, 12, 1); return }; gl_uniform3fv(loc, 1, v) }
|
||||
function u_fv(loc: int, n: int, v: words) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, v, 4, n); return }; gl_uniform1fv(loc, n, v) }
|
||||
function u_f(loc: int, v: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(v); gvk_u_set(loc, t, 4, 1); return }; let t = gpu_tmp(); t[0] = float_bits(v); gl_uniform1fv(loc, 1, t) }
|
||||
function u_f2(loc: int, x: float, y: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); gvk_u_set(loc, t, 8, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); gl_uniform2fv(loc, 1, t) }
|
||||
function u_f3(loc: int, x: float, y: float, z: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); gvk_u_set(loc, t, 12, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); gl_uniform3fv(loc, 1, t) }
|
||||
function u_f4(loc: int, x: float, y: float, z: float, w: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); t[3] = float_bits(w); gvk_u_set(loc, t, 16, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); t[3] = float_bits(w); gl_uniform4fv(loc, 1, t) }
|
||||
function u_v3(loc: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, v, 12, 1); return }; gl_uniform3fv(loc, 1, v) }
|
||||
function u_fv(loc: int, n: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, v, 4, n); return }; gl_uniform1fv(loc, n, v) }
|
||||
# n vec4s from 4n float bits. Not u_fv with 4n: on Vulkan an array element is copied at the size
|
||||
# given and placed at the array's stride, so a vec4 array fed floats got one float per element -
|
||||
# which drew the chunked grass with every tile at a nonsense corner and zero blades a cell.
|
||||
function u_f4v(loc: int, n: int, v: words) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, v, 16, n); return }; gl_uniform4fv(loc, n, v) }
|
||||
function u_mat4(loc: int, m: words) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, m, 64, 1); return }; gl_uniform_matrix4fv(loc, 1, 0, m) }
|
||||
function u_mat4n(loc: int, n: int, m: words) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, m, 64, n); return }; gl_uniform_matrix4fv(loc, n, 0, m) }
|
||||
function u_f4v(loc: int, n: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, v, 16, n); return }; gl_uniform4fv(loc, n, v) }
|
||||
function u_mat4(loc: int, m: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, m, 64, 1); return }; gl_uniform_matrix4fv(loc, 1, 0, m) }
|
||||
function u_mat4n(loc: int, n: int, m: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, m, 64, n); return }; gl_uniform_matrix4fv(loc, n, 0, m) }
|
||||
function u_i(loc: int, v: int) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = v; gvk_u_set(loc, t, 4, 1); return }; gl_uniform1i(loc, v) }
|
||||
|
||||
# ---- what this machine can do ----------------------------------------------------
|
||||
|
|
@ -702,7 +702,7 @@ function gpu_tex_param(kind: int, pname: int, value: int) -> void {
|
|||
function gpu_tex_paramf(kind: int, pname: int, value: fixed) -> void {
|
||||
if gpu_kind != GPU_VK { gl_tex_parameterf(gpu_gl_target(kind), pname, value) }
|
||||
let o = gpu_tx_at(gpu_bound(kind))
|
||||
if o >= 0 and pname == 0x84FE { gpu_tx[o + 11] = fx_to_f32(value) }
|
||||
if o >= 0 and pname == 0x84FE { gpu_tx[o + 11] = float_bits(float(value)) }
|
||||
gpu_glcheck_after("tex paramf")
|
||||
}
|
||||
# the border colour clamp-to-border reads (four fixed values in `rgba`)
|
||||
|
|
@ -939,7 +939,7 @@ function gpu_glcheck_after(what: string) -> void {
|
|||
let e = gl_get_error()
|
||||
if e != 0 { gpu_glcheck_say(`gpu: error {e} from {what} into {gpu_fb_describe(gpu_fb_cur)}`) }
|
||||
}
|
||||
function gpu_clear_color(r: fixed, g: fixed, b: fixed, a: fixed) -> void { if gpu_kind == GPU_VK { gvk_clear_color(fx_to_f32(r), fx_to_f32(g), fx_to_f32(b), fx_to_f32(a)); return }; gl_clear_color(r, g, b, a) }
|
||||
function gpu_clear_color(r: fixed, g: fixed, b: fixed, a: fixed) -> void { if gpu_kind == GPU_VK { gvk_clear_color(float(r), float(g), float(b), float(a)); return }; gl_clear_color(r, g, b, a) }
|
||||
function gpu_clear(mask: int) -> void {
|
||||
if gpu_kind == GPU_VK { gvk_clear(mask, gpu_fb, gpu_fb_at(gvk_fb_cur)); return }
|
||||
gpu_glcheck_before("a clear")
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ function gvk_init() -> bool {
|
|||
gvk_has_hqr = Vk.get_i32(f12, VkPhysicalDeviceVulkan12Features_hostQueryReset) == 1 and Vk.get_i32(props, VkPhysicalDeviceProperties_limits + VkPhysicalDeviceLimits_timestampComputeAndGraphics) == 1
|
||||
if gvk_has_hqr {
|
||||
Vk.put_i32(want12, VkPhysicalDeviceVulkan12Features_hostQueryReset, 1)
|
||||
gvk_ts_period = Vk.get_i32(props, VkPhysicalDeviceProperties_limits + VkPhysicalDeviceLimits_timestampPeriod)
|
||||
gvk_ts_period = float_from_bits(Vk.get_i32(props, VkPhysicalDeviceProperties_limits + VkPhysicalDeviceLimits_timestampPeriod))
|
||||
}
|
||||
|
||||
Vk.put_i32(cnt, 0, 0)
|
||||
|
|
@ -581,7 +581,7 @@ function gvk_shutdown() -> void {
|
|||
# again. On a device without host query reset or timestamps, every read says "not yet" and the report
|
||||
# stays empty.
|
||||
var gvk_has_hqr: bool = false
|
||||
var gvk_ts_period: int = 0 # float bits: nanoseconds per timestamp tick
|
||||
var gvk_ts_period: float = 0.0 # float bits: nanoseconds per timestamp tick
|
||||
var gvk_qpool: long = 0
|
||||
var gvk_q_active: int = -1
|
||||
function gvk_query_new(n: int, ids: words) -> void {
|
||||
|
|
@ -616,6 +616,6 @@ function gvk_query_result(id: int, out: words) -> bool {
|
|||
if Vk.get_query_pool_results(gvk_dev, gvk_qpool, id * 2, 2, size, data, stride, VK_QUERY_RESULT_64_BIT) != VK_SUCCESS { return false }
|
||||
let ticks = Text.to_int(string(Vk.get_i64(data, 8) - Vk.get_i64(data, 0)))
|
||||
if ticks < 0 { return false }
|
||||
out[0] = f_to_int(f_mul(fi(ticks), gvk_ts_period))
|
||||
out[0] = int(float(ticks) * gvk_ts_period)
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -245,19 +245,19 @@ function gvk_compute_probe() -> void {
|
|||
let n = 1000
|
||||
let b = gvk_buf_new()
|
||||
let data = bytes(n * 4)
|
||||
for i in 0 .. n { Vk.put_i32(data, i * 4, fi(i)) }
|
||||
for i in 0 .. n { Vk.put_i32(data, i * 4, float_bits(float(i))) }
|
||||
gvk_buf_upload(b, n * 4, data)
|
||||
gvk_buf_gpu_owned(b)
|
||||
let pr = bytes(8)
|
||||
Vk.put_i32(pr, 0, n)
|
||||
Vk.put_i32(pr, 4, fi(3))
|
||||
Vk.put_i32(pr, 4, float_bits(3.0))
|
||||
let bufs = words(1)
|
||||
bufs[0] = b
|
||||
gvk_dispatch(c, pr, 8, bufs, (n + 63) / 64, 1, 1)
|
||||
gvk_flush()
|
||||
var bad = 0
|
||||
let mp = gvk_buf_map[b]
|
||||
for i in 0 .. n { if Vk.get_i32(mp, i * 4) != fi(i * 3) { bad += 1 } }
|
||||
for i in 0 .. n { if float_from_bits(Vk.get_i32(mp, i * 4)) != float(i * 3) { bad += 1 } }
|
||||
if bad == 0 { print(`r3d: vulkan compute probe OK ({n} values)`) } else { print(`r3d: vulkan compute probe: FAILED ({bad} of {n} wrong)`) }
|
||||
}
|
||||
|
||||
|
|
@ -943,7 +943,7 @@ var gvk_pass_samples: int = 1 # the attachments' sample count, which eve
|
|||
var gvk_pass_col: words = null # the pass's colour attachments: texture, layer + 1
|
||||
var gvk_pass_dep: words = null # its depth attachment: texture, layer + 1
|
||||
var gvk_clear_bits: int = 0 # GL_COLOR_BUFFER_BIT / GL_DEPTH_BUFFER_BIT waiting for the pass
|
||||
var gvk_clear_rgba: words = null # float bits
|
||||
var gvk_clear_rgba: floats = null # float bits
|
||||
var gvk_vp: words = null # x, y, w, h
|
||||
var gvk_sc: words = null # scissor on, x, y (OpenGL rows), w, h
|
||||
var gvk_screen_color: int = 0
|
||||
|
|
@ -977,17 +977,17 @@ function gvk_screen_fmt() -> int { if gvk_hdr_on { return GL_RGB10_A2 }; return
|
|||
# picture's and the interface's white sit, and how far the darkest shade is lifted. Displays differ by
|
||||
# an order of magnitude - a 400-nit monitor and a 2000-nit television - and one fixed curve either
|
||||
# clips the first's highlights flat or leaves the second dim.
|
||||
var r3d_hdr_peak: int = 0 # 0 until r3d_hdr_calibrate: 1000
|
||||
var r3d_hdr_paper: int = 0 # 200
|
||||
var r3d_hdr_black: int = 0 # 0
|
||||
function r3d_hdr_peak_nits() -> int { if r3d_hdr_peak == 0 { return fi(1000) }; return r3d_hdr_peak }
|
||||
function r3d_hdr_paper_nits() -> int { if r3d_hdr_paper == 0 { return fi(200) }; return r3d_hdr_paper }
|
||||
function r3d_hdr_black_nits() -> int { return r3d_hdr_black }
|
||||
function r3d_hdr_calibrate(peak: int, paper: int, black: int) -> void {
|
||||
var pk = f_clamp(peak, fi(100), fi(10000))
|
||||
let pp = f_clamp(paper, fi(80), fi(1000))
|
||||
if f_ls(pk, pp) { pk = pp }
|
||||
let bl = f_clamp(black, F_ZERO, fi(5))
|
||||
var r3d_hdr_peak: float = 0.0 # 0 until r3d_hdr_calibrate: 1000
|
||||
var r3d_hdr_paper: float = 0.0 # 200
|
||||
var r3d_hdr_black: float = 0.0 # 0
|
||||
function r3d_hdr_peak_nits() -> float { if r3d_hdr_peak == 0.0 { return 1000.0 }; return r3d_hdr_peak }
|
||||
function r3d_hdr_paper_nits() -> float { if r3d_hdr_paper == 0.0 { return 200.0 }; return r3d_hdr_paper }
|
||||
function r3d_hdr_black_nits() -> float { return r3d_hdr_black }
|
||||
function r3d_hdr_calibrate(peak: float, paper: float, black: float) -> void {
|
||||
var pk = Math.clamp(peak, 100.0, 10000.0)
|
||||
let pp = Math.clamp(paper, 80.0, 1000.0)
|
||||
if pk < pp { pk = pp }
|
||||
let bl = Math.clamp(black, 0.0, 5.0)
|
||||
if pk == r3d_hdr_peak and pp == r3d_hdr_paper and bl == r3d_hdr_black { return }
|
||||
r3d_hdr_peak = pk; r3d_hdr_paper = pp; r3d_hdr_black = bl
|
||||
# the display is told what the picture now reaches
|
||||
|
|
@ -999,14 +999,14 @@ function gvk_hdr_metadata() -> void {
|
|||
let md = bytes(VkHdrMetadataEXT_sizeof)
|
||||
Vk.zero(md, VkHdrMetadataEXT_sizeof)
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_sType, VK_STRUCTURE_TYPE_HDR_METADATA_EXT)
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryRed + VkXYColorEXT_x, fl(0.64)); Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryRed + VkXYColorEXT_y, fl(0.33))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryGreen + VkXYColorEXT_x, fl(0.30)); Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryGreen + VkXYColorEXT_y, fl(0.60))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryBlue + VkXYColorEXT_x, fl(0.15)); Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryBlue + VkXYColorEXT_y, fl(0.06))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_whitePoint + VkXYColorEXT_x, fl(0.3127)); Vk.put_i32(md, VkHdrMetadataEXT_whitePoint + VkXYColorEXT_y, fl(0.3290))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_maxLuminance, r3d_hdr_peak_nits())
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_minLuminance, fl(0.001))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_maxContentLightLevel, r3d_hdr_peak_nits())
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_maxFrameAverageLightLevel, r3d_hdr_paper_nits())
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryRed + VkXYColorEXT_x, float_bits(0.64)); Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryRed + VkXYColorEXT_y, float_bits(0.33))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryGreen + VkXYColorEXT_x, float_bits(0.30)); Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryGreen + VkXYColorEXT_y, float_bits(0.60))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryBlue + VkXYColorEXT_x, float_bits(0.15)); Vk.put_i32(md, VkHdrMetadataEXT_displayPrimaryBlue + VkXYColorEXT_y, float_bits(0.06))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_whitePoint + VkXYColorEXT_x, float_bits(0.3127)); Vk.put_i32(md, VkHdrMetadataEXT_whitePoint + VkXYColorEXT_y, float_bits(0.3290))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_maxLuminance, float_bits(r3d_hdr_peak_nits()))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_minLuminance, float_bits(0.001))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_maxContentLightLevel, float_bits(r3d_hdr_peak_nits()))
|
||||
Vk.put_i32(md, VkHdrMetadataEXT_maxFrameAverageLightLevel, float_bits(r3d_hdr_paper_nits()))
|
||||
let chains = bytes(8)
|
||||
Vk.put_i64(chains, 0, gvk_swap)
|
||||
Vk.set_hdr_metadata_ext(gvk_dev, 1, chains, md)
|
||||
|
|
@ -1014,7 +1014,7 @@ function gvk_hdr_metadata() -> void {
|
|||
|
||||
function gvk_screen_make(w: int, h: int) -> bool {
|
||||
if gvk_vp == null {
|
||||
gvk_vp = words(4); gvk_sc = words(5); gvk_clear_rgba = words(4)
|
||||
gvk_vp = words(4); gvk_sc = words(5); gvk_clear_rgba = floats(4)
|
||||
gvk_pass_col = words(4); gvk_pass_dep = words(2)
|
||||
gvk_fb_ncolor = words(4096)
|
||||
for i in 0 .. 4096 { gvk_fb_ncolor[i] = 1 }
|
||||
|
|
@ -1131,7 +1131,7 @@ function gvk_pass_begin(rec: words, rec_o: int) -> void {
|
|||
Vk.put_i32(catt, c * aw + VkRenderingAttachmentInfo_storeOp, VK_ATTACHMENT_STORE_OP_STORE)
|
||||
if (gvk_clear_bits & GL_COLOR_BUFFER_BIT) != 0 {
|
||||
Vk.put_i32(catt, c * aw + VkRenderingAttachmentInfo_loadOp, VK_ATTACHMENT_LOAD_OP_CLEAR)
|
||||
for k in 0 .. 4 { Vk.put_i32(catt, c * aw + VkRenderingAttachmentInfo_clearValue + k * 4, gvk_clear_rgba[k]) }
|
||||
for k in 0 .. 4 { Vk.put_i32(catt, c * aw + VkRenderingAttachmentInfo_clearValue + k * 4, float_bits(gvk_clear_rgba[k])) }
|
||||
} else { Vk.put_i32(catt, c * aw + VkRenderingAttachmentInfo_loadOp, VK_ATTACHMENT_LOAD_OP_LOAD) }
|
||||
gvk_pass_cfmt = gvk_tex_vkfmt[tex]
|
||||
gvk_pass_samples = gvk_tex_samples_of(tex)
|
||||
|
|
@ -1193,7 +1193,7 @@ function gvk_clear(mask: int, rec: words, rec_o: int) -> void {
|
|||
for c in 0 .. gvk_pass_ncolor {
|
||||
Vk.put_i32(atts, n * caw + VkClearAttachment_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT)
|
||||
Vk.put_i32(atts, n * caw + VkClearAttachment_colorAttachment, c)
|
||||
for k in 0 .. 4 { Vk.put_i32(atts, n * caw + VkClearAttachment_clearValue + k * 4, gvk_clear_rgba[k]) }
|
||||
for k in 0 .. 4 { Vk.put_i32(atts, n * caw + VkClearAttachment_clearValue + k * 4, float_bits(gvk_clear_rgba[k])) }
|
||||
n += 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1219,10 +1219,10 @@ function gvk_tex_h(tex: int) -> int { return gvk_tex_dims_h[tex] }
|
|||
function gvk_set_view(cb: pointer) -> void {
|
||||
let vp = bytes(VkViewport_sizeof)
|
||||
Vk.zero(vp, VkViewport_sizeof)
|
||||
Vk.put_i32(vp, VkViewport_x, fi(gvk_vp[0]))
|
||||
Vk.put_i32(vp, VkViewport_y, fi(gvk_vp[1]))
|
||||
Vk.put_i32(vp, VkViewport_width, fi(gvk_vp[2]))
|
||||
Vk.put_i32(vp, VkViewport_height, fi(gvk_vp[3]))
|
||||
Vk.put_i32(vp, VkViewport_x, float_bits(float(gvk_vp[0])))
|
||||
Vk.put_i32(vp, VkViewport_y, float_bits(float(gvk_vp[1])))
|
||||
Vk.put_i32(vp, VkViewport_width, float_bits(float(gvk_vp[2])))
|
||||
Vk.put_i32(vp, VkViewport_height, float_bits(float(gvk_vp[3])))
|
||||
Vk.put_i32(vp, VkViewport_maxDepth, 0x3F800000)
|
||||
Vk.cmd_set_viewport(cb, 0, 1, vp)
|
||||
let sc = bytes(VkRect2D_sizeof)
|
||||
|
|
@ -1454,7 +1454,7 @@ function gvk_viewport(x: int, y: int, w: int, h: int) -> void {
|
|||
if gvk_vp == null { return }
|
||||
gvk_vp[0] = x; gvk_vp[1] = y; gvk_vp[2] = w; gvk_vp[3] = h
|
||||
}
|
||||
function gvk_clear_color(r: int, g: int, b: int, a: int) -> void {
|
||||
function gvk_clear_color(r: float, g: float, b: float, a: float) -> void {
|
||||
if gvk_clear_rgba == null { return }
|
||||
gvk_clear_rgba[0] = r; gvk_clear_rgba[1] = g; gvk_clear_rgba[2] = b; gvk_clear_rgba[3] = a
|
||||
}
|
||||
|
|
@ -1487,8 +1487,8 @@ function gvk_state_now() -> GvkState {
|
|||
st.color_write = 1; if gpu_s_color_write == 0 { st.color_write = 0 }
|
||||
st.a2c = 0; if gpu_s_a2c == 1 { st.a2c = 1 }
|
||||
st.bias = 0; if gpu_s_bias == 1 { st.bias = 1 }
|
||||
st.bias_factor = gpu_s_bias_f
|
||||
st.bias_units = gpu_s_bias_u
|
||||
st.bias_factor = float_bits(gpu_s_bias_f)
|
||||
st.bias_units = float_bits(gpu_s_bias_u)
|
||||
st.wireframe = gvk_wireframe
|
||||
return st
|
||||
}
|
||||
|
|
|
|||
|
|
@ -480,19 +480,19 @@ function gvk_compare_op(f: int) -> int {
|
|||
# which is -2.0 at Performance and about -1.6 at Quality. It is applied ONLY while DLSS is live: a
|
||||
# plain spatial upscale has no temporal accumulation to hide the aliasing a negative bias brings,
|
||||
# so biasing there would trade blur for shimmer.
|
||||
function gvk_mip_bias() -> int {
|
||||
function gvk_mip_bias() -> float {
|
||||
# R3D_NO_MIPBIAS=1 puts it back the way it was, so one build can be compared against itself
|
||||
if r3d_env_has("R3D_NO_MIPBIAS") { return F_ZERO }
|
||||
if not r3d_dlss_live() { return F_ZERO }
|
||||
if r3d_env_has("R3D_NO_MIPBIAS") { return 0.0 }
|
||||
if not r3d_dlss_live() { return 0.0 }
|
||||
let rw = r3d_dlss_render_w()
|
||||
if rw <= 0 or gl_w <= 0 or rw >= gl_w { return F_ZERO }
|
||||
if rw <= 0 or gl_w <= 0 or rw >= gl_w { return 0.0 }
|
||||
# log2 from the natural log the runtime has: log2(x) = ln(x) * 1/ln(2)
|
||||
return f_sub(f_mul(f_log(f_div(fi(rw), fi(gl_w))), fl(1.4426950408889634)), F_ONE)
|
||||
return Math.log(float(rw) / float(gl_w)) * 1.4426950408889634 - 1.0
|
||||
}
|
||||
# the sampler for texture tex's parameters, from the texture's own one-entry cache when they have
|
||||
# not changed since it last asked - a draw asks for every texture it binds
|
||||
function gvk_tex_sampler(tex: int, min_f: int, mag_f: int, wrap_s: int, wrap_t: int, compare: int, aniso: int) -> long {
|
||||
let bias = gvk_mip_bias()
|
||||
let bias = float_bits(gvk_mip_bias())
|
||||
var sig = min_f * 31 + mag_f
|
||||
sig = sig * 31 + wrap_s
|
||||
sig = sig * 31 + wrap_t
|
||||
|
|
@ -507,7 +507,7 @@ function gvk_tex_sampler(tex: int, min_f: int, mag_f: int, wrap_s: int, wrap_t:
|
|||
return s
|
||||
}
|
||||
function gvk_sampler(min_f: int, mag_f: int, wrap_s: int, wrap_t: int, compare: int, aniso: int) -> long {
|
||||
let bias = gvk_mip_bias()
|
||||
let bias = float_bits(gvk_mip_bias())
|
||||
let key = `{min_f}/{mag_f}/{wrap_s}/{wrap_t}/{compare}/{aniso}/{bias}`
|
||||
if gvk_smp_keys == null { gvk_smp_keys = new []string; gvk_smp = new []long }
|
||||
for i in 0 .. len(gvk_smp_keys) { if gvk_smp_keys[i] == key { return gvk_smp[i] } }
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const GRASS_CELL: int = 16
|
|||
var grass_prog: int = 0
|
||||
var grass_mesh: Mesh = null
|
||||
var grass_on: bool = true
|
||||
var grass_wind: int = 0
|
||||
var grass_wind: float = 0.0
|
||||
# A photographed blade, as an atlas of straightened blades side by side (the game sets
|
||||
# this; the renderer does not name a game asset). 0 = the procedural gradient, which is
|
||||
# what this was for a year: a two-tone ramp with a hard edge, and every blade in the
|
||||
|
|
@ -27,12 +27,12 @@ var grass_blade_cols: int = 8
|
|||
# in it: you walked through a meadow and every blade ignored you, which is the single most
|
||||
# noticeable thing missing from every step the game asks you to take. The game sets this each
|
||||
# frame; radius 0 means nobody is there.
|
||||
var grass_push_x: int = 0
|
||||
var grass_push_z: int = 0
|
||||
var grass_push_r: int = 0
|
||||
var grass_s0: int = 0 # float bits: blade spacing at the camera (m)
|
||||
var grass_d0: int = 0 # the distance at which the spacing has doubled (m)
|
||||
var grass_radius: int = 0 # no blades past this (m)
|
||||
var grass_push_x: float = 0.0
|
||||
var grass_push_z: float = 0.0
|
||||
var grass_push_r: float = 0.0
|
||||
var grass_s0: float = 0.0 # float bits: blade spacing at the camera (m)
|
||||
var grass_d0: float = 0.0 # the distance at which the spacing has doubled (m)
|
||||
var grass_radius: float = 0.0 # no blades past this (m)
|
||||
var grass_draws: int = 0
|
||||
var grass_dbg: int = 0
|
||||
# Vulkan with multi-draw indirect: every visible tile is a record in one buffer, uploaded once a
|
||||
|
|
@ -43,8 +43,8 @@ const GRASS_CHUNK: int = 256
|
|||
const GRASS_RECS: int = 8192
|
||||
var grass_merge: bool = false
|
||||
var grass_rec: words = null # VkDrawIndexedIndirectCommand records, 5 words each
|
||||
var grass_tv: words = null # per record: corner x, corner z, indices per cell, 0 (float bits)
|
||||
var grass_chunk_tv: words = null
|
||||
var grass_tv: floats = null # per record: corner x, corner z, indices per cell, 0 (float bits)
|
||||
var grass_chunk_tv: floats = null
|
||||
var grass_n: int = 0
|
||||
var grass_cmds: int = 0
|
||||
var grass_band_start: words = null # per band: its first record
|
||||
|
|
@ -81,16 +81,16 @@ function grass_blade_mesh(rows: int) -> Mesh {
|
|||
# grass.mesh carries A COPY of these two lines for the mesh-shader path; change both or
|
||||
# the Windows blades stop matching the ones everywhere else.
|
||||
for r in 0 .. rows {
|
||||
let t = fr(r, rows - 1)
|
||||
let grow = f_min(f_div(t, fl(0.22)), F_ONE)
|
||||
let wide = f_mul(f_add(fl(0.50), f_mul(fl(0.33), grow)), f_sub(F_ONE, f_mul(t, f_mul(t, t))))
|
||||
let taper = f_max(wide, fl(0.05))
|
||||
let bend = f_mul(f_mul(t, t), fl(0.52))
|
||||
let t = float(r) / float(rows - 1)
|
||||
let grow = Math.min(t / 0.22, 1.0)
|
||||
let wide = (0.50 + 0.33 * grow) * (1.0 - t * (t * t))
|
||||
let taper = Math.max(wide, 0.05)
|
||||
let bend = t * t * 0.52
|
||||
for sd in 0 .. 2 {
|
||||
var x = f_neg(F_HALF)
|
||||
if sd == 1 { x = F_HALF }
|
||||
gl_put_bits(v, k, f_mul(x, taper)); gl_put_bits(v, k + 1, t); gl_put_bits(v, k + 2, bend)
|
||||
gl_put_bits(v, k + 3, fi(sd)); gl_put_bits(v, k + 4, t)
|
||||
var x = -0.5
|
||||
if sd == 1 { x = 0.5 }
|
||||
gl_put_bits(v, k, float_bits(x * taper)); gl_put_bits(v, k + 1, float_bits(t)); gl_put_bits(v, k + 2, float_bits(bend))
|
||||
gl_put_bits(v, k + 3, float_bits(float(sd))); gl_put_bits(v, k + 4, float_bits(t))
|
||||
k += 5
|
||||
}
|
||||
}
|
||||
|
|
@ -117,7 +117,7 @@ function grass_init() -> void {
|
|||
var defs = "#define FOLIAGE\n#define BLADE\n"
|
||||
if grass_merge {
|
||||
defs = defs + "#define TILES\n"
|
||||
grass_rec = words(GRASS_RECS * 5); grass_tv = words(GRASS_RECS * 4); grass_chunk_tv = words(GRASS_CHUNK * 4)
|
||||
grass_rec = words(GRASS_RECS * 5); grass_tv = floats(GRASS_RECS * 4); grass_chunk_tv = floats(GRASS_CHUNK * 4)
|
||||
grass_band_start = words(4); grass_band_cells = words(4)
|
||||
grass_cmds = gpu_buffer_new()
|
||||
}
|
||||
|
|
@ -126,30 +126,30 @@ function grass_init() -> void {
|
|||
# five rows, four quads: the arch above needs somewhere to bend, and at four rows a
|
||||
# blade that leans over is three straight segments and shows every join
|
||||
grass_mesh = grass_blade_mesh(5)
|
||||
grass_wind = fl(2.4)
|
||||
grass_wind = 2.4
|
||||
# Matched to the blade's width: a 1 cm blade at 0.11 m spacing covers a third of what a
|
||||
# 2.8 cm blade did, and the meadow goes bare. The game's graphics settings override this
|
||||
# (gfx_grass_spacing), but only once game_init has run - a plain headless render never
|
||||
# gets there, so the two have to agree or a shot shows something no player will see.
|
||||
# That is exactly how the last change measured as "no effect": the render was identical
|
||||
# because this line, not the settings, was deciding.
|
||||
grass_s0 = fl(0.066)
|
||||
grass_d0 = fi(45)
|
||||
grass_radius = fi(1600)
|
||||
grass_s0 = 0.066
|
||||
grass_d0 = 45.0
|
||||
grass_radius = 1600.0
|
||||
if r3d_env_has("R3D_NOBLADES") { grass_on = false }
|
||||
if r3d_env_has("R3D_GRASS_R") { grass_radius = fi(Text.to_int(r3d_env("R3D_GRASS_R"))) }
|
||||
if r3d_env_has("R3D_GRASS_R") { grass_radius = float(Text.to_int(r3d_env("R3D_GRASS_R"))) }
|
||||
if r3d_env_has("R3D_GRASS_DBG") { grass_dbg = Text.to_int(r3d_env("R3D_GRASS_DBG")) }
|
||||
}
|
||||
|
||||
# indices per 16 m cell that could exist at distance d (the count the shader computes)
|
||||
function grass_count_at(d: int) -> int {
|
||||
let spacing = f_mul(grass_s0, f_add(F_ONE, f_div(d, grass_d0)))
|
||||
let n = f_div(fi(GRASS_CELL * GRASS_CELL), f_mul(spacing, spacing))
|
||||
return f_to_int(n) + 1
|
||||
function grass_count_at(d: float) -> int {
|
||||
let spacing = grass_s0 * (1.0 + d / grass_d0)
|
||||
let n = float(GRASS_CELL * GRASS_CELL) / (spacing * spacing)
|
||||
return int(n) + 1
|
||||
}
|
||||
|
||||
# one tile size over one distance band
|
||||
function grass_tiles(size: int, d_min: int, d_max: int) -> void {
|
||||
function grass_tiles(size: int, d_min: float, d_max: float) -> void {
|
||||
let p = grass_prog
|
||||
let cells = size / GRASS_CELL
|
||||
if grass_merge {
|
||||
|
|
@ -157,27 +157,27 @@ function grass_tiles(size: int, d_min: int, d_max: int) -> void {
|
|||
} else {
|
||||
u_i(gpu_uniform(p, "u_tile_cells"), cells)
|
||||
}
|
||||
let sz = fi(size)
|
||||
let half = f_mul(sz, F_HALF)
|
||||
let reach = f_add(d_max, f_mul(half, fl(1.5)))
|
||||
let tx0 = f_to_int(f_floor(f_div(f_sub(cam_pos[0], reach), sz)))
|
||||
let tx1 = f_to_int(f_floor(f_div(f_add(cam_pos[0], reach), sz)))
|
||||
let tz0 = f_to_int(f_floor(f_div(f_sub(cam_pos[2], reach), sz)))
|
||||
let tz1 = f_to_int(f_floor(f_div(f_add(cam_pos[2], reach), sz)))
|
||||
let corner_r = f_mul(half, fl(1.42))
|
||||
let sz = float(size)
|
||||
let half = sz * 0.5
|
||||
let reach = d_max + half * 1.5
|
||||
let tx0 = int(Math.floor((cam_pos[0] - reach) / sz))
|
||||
let tx1 = int(Math.floor((cam_pos[0] + reach) / sz))
|
||||
let tz0 = int(Math.floor((cam_pos[2] - reach) / sz))
|
||||
let tz1 = int(Math.floor((cam_pos[2] + reach) / sz))
|
||||
let corner_r = half * 1.42
|
||||
var tz = tz0
|
||||
while tz <= tz1 {
|
||||
var tx = tx0
|
||||
while tx <= tx1 {
|
||||
let ox = f_mul(fi(tx), sz); let oz = f_mul(fi(tz), sz)
|
||||
let cx = f_add(ox, half); let cz = f_add(oz, half)
|
||||
let dx = f_sub(cx, cam_pos[0]); let dz = f_sub(cz, cam_pos[2])
|
||||
let dc = f_sqrt(f_add(f_mul(dx, dx), f_mul(dz, dz)))
|
||||
let ox = float(tx) * sz; let oz = float(tz) * sz
|
||||
let cx = ox + half; let cz = oz + half
|
||||
let dx = cx - cam_pos[0]; let dz = cz - cam_pos[2]
|
||||
let dc = Math.sqrt(dx * dx + dz * dz)
|
||||
# the tile's nearest and farthest points decide which band it belongs to
|
||||
let dnear = f_max(f_sub(dc, corner_r), F_ZERO)
|
||||
if f_ls(dc, d_min) or not f_ls(dnear, d_max) { tx += 1; continue }
|
||||
let dnear = Math.max(dc - corner_r, 0.0)
|
||||
if dc < d_min or not (dnear < d_max) { tx += 1; continue }
|
||||
let cy = terrain_height(cx, cz)
|
||||
if cam_sphere_visible(cx, cy, cz, f_add(corner_r, fi(6))) {
|
||||
if cam_sphere_visible(cx, cy, cz, corner_r + 6.0) {
|
||||
let per = grass_count_at(dnear)
|
||||
if per > 0 and grass_merge {
|
||||
if grass_n < GRASS_RECS {
|
||||
|
|
@ -187,7 +187,7 @@ function grass_tiles(size: int, d_min: int, d_max: int) -> void {
|
|||
grass_rec[r] = grass_mesh.count; grass_rec[r + 1] = inst; grass_rec[r + 2] = 0; grass_rec[r + 3] = 0
|
||||
grass_rec[r + 4] = ((grass_n - grass_band_start[grass_band_n - 1]) % GRASS_CHUNK) * 65536
|
||||
let t = grass_n * 4
|
||||
grass_tv[t] = ox; grass_tv[t + 1] = oz; grass_tv[t + 2] = fi(per); grass_tv[t + 3] = F_ZERO
|
||||
grass_tv[t] = ox; grass_tv[t + 1] = oz; grass_tv[t + 2] = float(per); grass_tv[t + 3] = 0.0
|
||||
grass_n += 1
|
||||
}
|
||||
} else if per > 0 {
|
||||
|
|
@ -216,28 +216,28 @@ function grass_draw() -> void {
|
|||
# CLAUDE.md records against u_wade and u_flutter, and it costs a day every time
|
||||
let btex = grass_blade_tex
|
||||
let bcols = grass_blade_cols
|
||||
u_f(gpu_uniform(p, "u_blade_cols"), fi(bcols))
|
||||
var bon = F_ZERO
|
||||
if btex != 0 { bon = F_ONE; r3d_bind_2d(p, "u_blade_tex", 12, btex) }
|
||||
u_f(gpu_uniform(p, "u_blade_cols"), float(bcols))
|
||||
var bon = 0.0
|
||||
if btex != 0 { bon = 1.0; r3d_bind_2d(p, "u_blade_tex", 12, btex) }
|
||||
u_f(gpu_uniform(p, "u_blade_tex_on"), bon)
|
||||
u_f3(gpu_uniform(p, "u_push"), grass_push_x, grass_push_z, grass_push_r)
|
||||
u_f(gpu_uniform(p, "u_rough_scale"), F_ONE)
|
||||
u_f(gpu_uniform(p, "u_rough_scale"), 1.0)
|
||||
u_v3(gpu_uniform(p, "u_tint"), sc_blade_tint)
|
||||
u_v3(gpu_uniform(p, "u_blade_base"), sc_blade_base)
|
||||
u_v3(gpu_uniform(p, "u_blade_tip"), sc_blade_tip)
|
||||
u_f(gpu_uniform(p, "u_cull"), grass_radius)
|
||||
u_f(gpu_uniform(p, "u_model_h"), F_ZERO)
|
||||
u_f(gpu_uniform(p, "u_model_h"), 0.0)
|
||||
u_f(gpu_uniform(p, "u_s0"), grass_s0)
|
||||
u_f(gpu_uniform(p, "u_d0"), grass_d0)
|
||||
u_f(gpu_uniform(p, "u_radius"), grass_radius)
|
||||
u_i(gpu_uniform(p, "u_dbg"), grass_dbg)
|
||||
var orthotex = ter_ortho_tex
|
||||
var oon = F_ONE
|
||||
if orthotex == 0 { orthotex = ter_height_tex; oon = F_ZERO }
|
||||
var oon = 1.0
|
||||
if orthotex == 0 { orthotex = ter_height_tex; oon = 0.0 }
|
||||
r3d_bind_2d(p, "u_ortho", 4, orthotex)
|
||||
u_f(gpu_uniform(p, "u_ortho_on"), oon)
|
||||
var lake = fl(-100000.0)
|
||||
if ter_lake_ex != 0 { lake = ter_lake_level }
|
||||
var lake = -100000.0
|
||||
if ter_lake_ex != 0.0 { lake = ter_lake_level }
|
||||
u_f(gpu_uniform(p, "u_lake_level"), lake)
|
||||
var sea = lake
|
||||
if ter_sea_set { sea = ter_sea_level }
|
||||
|
|
@ -252,15 +252,15 @@ function grass_draw() -> void {
|
|||
# at 0.15, 0.70% of a near-ground frame was over 210 of 255; at 0.0 it is 0.05%. A blade
|
||||
# does have a faint sheen, so this is small rather than nothing - the foliage layers have
|
||||
# used 0.05 all along and never showed the fault.
|
||||
u_f(gpu_uniform(p, "u_spec_scale"), fl(0.008))
|
||||
u_f(gpu_uniform(p, "u_spec_scale"), 0.008)
|
||||
gpu_cull(false)
|
||||
grass_draws = 0
|
||||
grass_n = 0
|
||||
grass_band_n = 0
|
||||
gpu_mesh_bind(grass_mesh)
|
||||
grass_tiles(16, F_ZERO, fi(300))
|
||||
grass_tiles(64, fi(300), fi(1200))
|
||||
grass_tiles(256, fi(1200), grass_radius)
|
||||
grass_tiles(16, 0.0, 300.0)
|
||||
grass_tiles(64, 300.0, 1200.0)
|
||||
grass_tiles(256, 1200.0, grass_radius)
|
||||
if grass_merge { grass_flush() }
|
||||
gpu_cull(true)
|
||||
}
|
||||
|
|
@ -287,7 +287,7 @@ function grass_flush() -> void {
|
|||
# chunked path's 2 at 4K on an RTX 3070 Ti)
|
||||
let cells = grass_band_cells[b]
|
||||
for q in 0 .. m {
|
||||
var total = f_to_int(grass_tv[(k + q) * 4 + 2]) * cells * cells
|
||||
var total = int(grass_tv[(k + q) * 4 + 2]) * cells * cells
|
||||
if total > 65535 { total = 65535 }
|
||||
if total > 0 {
|
||||
for c in 0 .. 4 { grass_chunk_tv[c] = grass_tv[(k + q) * 4 + c] }
|
||||
|
|
|
|||
|
|
@ -25,16 +25,16 @@ function mesh_draw_instanced(m: Mesh, n: int) -> void { gpu_draw_mesh_instanced(
|
|||
|
||||
# A flat n x n vertex grid over [-half, half]^2 in x/z, y = 0. Attribute 0 = (x, z).
|
||||
# The terrain vertex shader lifts it with the height map.
|
||||
function mesh_grid(n: int, half: int) -> Mesh {
|
||||
function mesh_grid(n: int, half: float) -> Mesh {
|
||||
let m = gpu_mesh_new()
|
||||
let nv = n * n
|
||||
let v = gl_floats(nv * 2)
|
||||
var k = 0
|
||||
for j in 0 .. n {
|
||||
for i in 0 .. n {
|
||||
let x = f_sub(f_mul(f_mul(fr(i, n - 1), F_TWO), half), half)
|
||||
let z = f_sub(f_mul(f_mul(fr(j, n - 1), F_TWO), half), half)
|
||||
gl_put_bits(v, k, x); gl_put_bits(v, k + 1, z)
|
||||
let x = float(i) / float(n - 1) * 2.0 * half - half
|
||||
let z = float(j) / float(n - 1) * 2.0 * half - half
|
||||
gl_put_bits(v, k, float_bits(x)); gl_put_bits(v, k + 1, float_bits(z))
|
||||
k += 2
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ var ov_buf: pointer = null
|
|||
var ov_n: int = 0
|
||||
var ov_tex: int = 0
|
||||
var ov_mode: int = 2 # what the next quads read: 0 the image texture, 1 the font, 2 a flat colour
|
||||
var ov_voff: int = 0 # float bits: 4 x ov_mode, added to v (overlay.frag)
|
||||
var ov_voff: float = 0.0 # float bits: 4 x ov_mode, added to v (overlay.frag)
|
||||
var ov_white: int = 0
|
||||
var ov_font: int = 0
|
||||
var ov_font_adv: words = null # float bits, em units, one per glyph in the atlas
|
||||
var ov_font_adv: floats = null # float bits, em units, one per glyph in the atlas
|
||||
var ov_font_n: int = 0 # glyphs in the atlas
|
||||
# Text is UTF-8, and the atlas says which code points it holds. font.json's "codes" lists
|
||||
# them in atlas order; an older atlas without it is ASCII from "first" (32) on, which is
|
||||
|
|
@ -38,8 +38,8 @@ var ov_font_cols: int = 16
|
|||
var ov_font_rows: int = 6
|
||||
var ov_font_cell: int = 128 # px per cell in the atlas
|
||||
var ov_font_em: int = 100 # px per em in the atlas
|
||||
var ov_pad_x: int = 0 # float bits, em
|
||||
var ov_base_y: int = 0
|
||||
var ov_pad_x: float = 0.0 # float bits, em
|
||||
var ov_base_y: float = 0.0
|
||||
var ov_ready: bool = false
|
||||
var ov_open: bool = false
|
||||
var ov_dbg: bool = false
|
||||
|
|
@ -61,16 +61,16 @@ var ov_prog_hdr: int = 0
|
|||
# How bright the interface's white is while HDR10 is on, as a multiple of paper white. It is 1 for the
|
||||
# interface; a calibration screen draws its test patches at a number of nits with ov_hdr_nits, which
|
||||
# closes the batch so far - the multiple is one uniform per flush. On an SDR frame it does nothing.
|
||||
var ov_hdr_scale: int = 0 # float bits; 0 until ov_begin sets 1
|
||||
function ov_hdr_nits(nits: int) -> void {
|
||||
var s = F_ONE
|
||||
if nits != 0 { s = f_div(nits, r3d_hdr_paper_nits()) }
|
||||
var ov_hdr_scale: float = 0.0 # float bits; 0 until ov_begin sets 1
|
||||
function ov_hdr_nits(nits: float) -> void {
|
||||
var s = 1.0
|
||||
if nits != 0.0 { s = nits / r3d_hdr_paper_nits() }
|
||||
if s == ov_hdr_scale { return }
|
||||
if ov_open { ov_flush() }
|
||||
ov_hdr_scale = s
|
||||
}
|
||||
# back to the interface's own white
|
||||
function ov_hdr_paper() -> void { ov_hdr_nits(0) }
|
||||
function ov_hdr_paper() -> void { ov_hdr_nits(0.0) }
|
||||
function ov_pick_prog() -> void {
|
||||
if gpu_hdr_active() {
|
||||
if ov_prog_hdr == 0 {
|
||||
|
|
@ -113,8 +113,8 @@ function overlay_font(font_dir: string) -> bool {
|
|||
if tex == 0 { return false }
|
||||
let j = Json.parse(meta)
|
||||
ov_font_cols = jint(j, "cols", 16); ov_font_rows = jint(j, "rows", 6)
|
||||
ov_font_cell = f_to_int(jnum(value_get(j, "cell"))); ov_font_em = f_to_int(jnum(value_get(j, "em")))
|
||||
ov_pad_x = fl(0.14); ov_base_y = fl(0.30)
|
||||
ov_font_cell = int(jnum(value_get(j, "cell"))); ov_font_em = int(jnum(value_get(j, "em")))
|
||||
ov_pad_x = 0.14; ov_base_y = 0.30
|
||||
if value_has(j, "pad_x") != 0 { ov_pad_x = jnum(value_get(j, "pad_x")) }
|
||||
if value_has(j, "base_y") != 0 { ov_base_y = jnum(value_get(j, "base_y")) }
|
||||
let adv = value_get(j, "adv")
|
||||
|
|
@ -123,14 +123,14 @@ function overlay_font(font_dir: string) -> bool {
|
|||
var codes: Val = null
|
||||
if value_has(j, "codes") != 0 { codes = value_get(j, "codes") }
|
||||
ov_font_n = n
|
||||
ov_font_adv = words(n + 1)
|
||||
ov_font_adv = floats(n + 1)
|
||||
for i in 0 .. n { ov_font_adv[i] = jnum(value_at(adv, i)) }
|
||||
ov_font_map = words(OV_MAP_N)
|
||||
for c in 0 .. OV_MAP_N { ov_font_map[c] = 0 }
|
||||
ov_font_hi_cp = words(n + 1); ov_font_hi_g = words(n + 1); ov_font_hi_n = 0
|
||||
for i in 0 .. n {
|
||||
var cp = first + i
|
||||
if codes != null and i < value_count(codes) { cp = f_to_int(jnum(value_at(codes, i))) }
|
||||
if codes != null and i < value_count(codes) { cp = int(jnum(value_at(codes, i))) }
|
||||
if cp >= 0 and cp < OV_MAP_N { ov_font_map[cp] = i + 1 }
|
||||
else if cp >= OV_MAP_N {
|
||||
# kept ascending: the builder writes code points in order, so this is an append
|
||||
|
|
@ -195,12 +195,12 @@ function ov_begin() -> void {
|
|||
gpu_blend_func(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
||||
ov_pick_prog()
|
||||
gpu_use_program(ov_prog)
|
||||
u_f2(gpu_uniform(ov_prog, "u_screen"), fi(gl_w), fi(gl_h))
|
||||
u_f2(gpu_uniform(ov_prog, "u_screen"), float(gl_w), float(gl_h))
|
||||
ov_n = 0; ov_nr = 0; ov_range_start = 0
|
||||
ov_tex = ov_white
|
||||
ov_mode = 2; ov_voff = fi(8)
|
||||
ov_mode = 2; ov_voff = 8.0
|
||||
ov_clip_w = 0
|
||||
ov_hdr_scale = F_ONE
|
||||
ov_hdr_scale = 1.0
|
||||
ov_open = true
|
||||
}
|
||||
# everything drawn until ov_unclip stays inside this rectangle (a scrolling list)
|
||||
|
|
@ -238,7 +238,7 @@ function ov_flush() -> void {
|
|||
if font == 0 { font = ov_white }
|
||||
r3d_bind_2d(ov_prog, "u_font", 1, font)
|
||||
var hs = ov_hdr_scale
|
||||
if hs == 0 { hs = F_ONE }
|
||||
if hs == 0.0 { hs = 1.0 }
|
||||
u_f(gpu_uniform(ov_prog, "u_hdr_paper"), r3d_hdr_paper_nits())
|
||||
u_f(gpu_uniform(ov_prog, "u_hdr_scale"), hs)
|
||||
var last = -1
|
||||
|
|
@ -268,21 +268,21 @@ function ov_end() -> void {
|
|||
# vertex says which it reads (ov_voff). Only a different image texture closes the range. A panel and
|
||||
# its label used to alternate the white and font textures, and the HUD was 77-91 draws a frame.
|
||||
function ov_use_tex(t: int) -> void {
|
||||
if t != 0 and t == ov_font { ov_mode = 1; ov_voff = fi(4); return }
|
||||
if t == ov_white { ov_mode = 2; ov_voff = fi(8); return }
|
||||
ov_mode = 0; ov_voff = F_ZERO
|
||||
if t != 0 and t == ov_font { ov_mode = 1; ov_voff = 4.0; return }
|
||||
if t == ov_white { ov_mode = 2; ov_voff = 8.0; return }
|
||||
ov_mode = 0; ov_voff = 0.0
|
||||
if t != ov_tex { ov_close_range(); ov_tex = t }
|
||||
}
|
||||
|
||||
# one vertex into the batch
|
||||
function ov_vert(k: int, x: int, y: int, u: int, v: int, r: int, g: int, b: int, a: int) -> void {
|
||||
function ov_vert(k: int, x: float, y: float, u: float, v: float, r: float, g: float, b: float, a: float) -> void {
|
||||
let o = k * OV_FLOATS
|
||||
gl_put_bits(ov_buf, o, x); gl_put_bits(ov_buf, o + 1, y)
|
||||
gl_put_bits(ov_buf, o + 2, u); gl_put_bits(ov_buf, o + 3, f_add(v, ov_voff))
|
||||
gl_put_bits(ov_buf, o + 4, r); gl_put_bits(ov_buf, o + 5, g); gl_put_bits(ov_buf, o + 6, b); gl_put_bits(ov_buf, o + 7, a)
|
||||
gl_put_bits(ov_buf, o, float_bits(x)); gl_put_bits(ov_buf, o + 1, float_bits(y))
|
||||
gl_put_bits(ov_buf, o + 2, float_bits(u)); gl_put_bits(ov_buf, o + 3, float_bits(v + ov_voff))
|
||||
gl_put_bits(ov_buf, o + 4, float_bits(r)); gl_put_bits(ov_buf, o + 5, float_bits(g)); gl_put_bits(ov_buf, o + 6, float_bits(b)); gl_put_bits(ov_buf, o + 7, float_bits(a))
|
||||
}
|
||||
# a textured quad, float-bit pixel corners and uvs
|
||||
function ov_quad(x0: int, y0: int, x1: int, y1: int, u0: int, v0: int, u1: int, v1: int, r: int, g: int, b: int, a: int) -> void {
|
||||
function ov_quad(x0: float, y0: float, x1: float, y1: float, u0: float, v0: float, u1: float, v1: float, r: float, g: float, b: float, a: float) -> void {
|
||||
if ov_n >= OV_MAX_QUADS { ov_flush() }
|
||||
let k = ov_n * 6
|
||||
ov_vert(k, x0, y0, u0, v0, r, g, b, a)
|
||||
|
|
@ -295,46 +295,46 @@ function ov_quad(x0: int, y0: int, x1: int, y1: int, u0: int, v0: int, u1: int,
|
|||
}
|
||||
|
||||
# a filled rectangle at integer pixels; colour as float bits 0..1
|
||||
function ov_rect(x: int, y: int, w: int, h: int, r: int, g: int, b: int, a: int) -> void {
|
||||
function ov_rect(x: int, y: int, w: int, h: int, r: float, g: float, b: float, a: float) -> void {
|
||||
ov_use_tex(ov_white)
|
||||
ov_quad(fi(x), fi(y), fi(x + w), fi(y + h), F_ZERO, F_ZERO, F_ONE, F_ONE, r, g, b, a)
|
||||
ov_quad(float(x), float(y), float(x + w), float(y + h), 0.0, 0.0, 1.0, 1.0, r, g, b, a)
|
||||
}
|
||||
function ov_frame(x: int, y: int, w: int, h: int, t: int, r: int, g: int, b: int, a: int) -> void {
|
||||
function ov_frame(x: int, y: int, w: int, h: int, t: int, r: float, g: float, b: float, a: float) -> void {
|
||||
ov_rect(x, y, w, t, r, g, b, a)
|
||||
ov_rect(x, y + h - t, w, t, r, g, b, a)
|
||||
ov_rect(x, y, t, h, r, g, b, a)
|
||||
ov_rect(x + w - t, y, t, h, r, g, b, a)
|
||||
}
|
||||
# a whole texture at integer pixels
|
||||
function ov_image(tex: int, x: int, y: int, w: int, h: int, a: int) -> void {
|
||||
function ov_image(tex: int, x: int, y: int, w: int, h: int, a: float) -> void {
|
||||
ov_use_tex(tex)
|
||||
ov_quad(fi(x), fi(y), fi(x + w), fi(y + h), F_ZERO, F_ZERO, F_ONE, F_ONE, F_ONE, F_ONE, F_ONE, a)
|
||||
ov_quad(float(x), float(y), float(x + w), float(y + h), 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, a)
|
||||
}
|
||||
|
||||
# the width in pixels of `s` at `size` pixels per em
|
||||
function ov_text_w(size: int, s: string) -> int {
|
||||
if ov_font_adv == null { return 0 }
|
||||
var w = F_ZERO
|
||||
var w = 0.0
|
||||
let sp: pointer = s # UTF-8 bytes, not one-character strings
|
||||
let n = len(sp)
|
||||
var i = 0
|
||||
while i < n {
|
||||
let g = ov_glyph(ov_u8(sp, i, n))
|
||||
i += ov_u8_len
|
||||
w = f_add(w, f_mul(ov_font_adv[g], fi(size)))
|
||||
w = w + ov_font_adv[g] * float(size)
|
||||
}
|
||||
return f_to_int(w)
|
||||
return int(w)
|
||||
}
|
||||
# text with its top-left at (x, y); returns the pen x after it
|
||||
function ov_text(x: int, y: int, size: int, s: string, r: int, g: int, b: int, a: int) -> int {
|
||||
function ov_text(x: int, y: int, size: int, s: string, r: float, g: float, b: float, a: float) -> int {
|
||||
if ov_font == 0 { return x }
|
||||
ov_use_tex(ov_font)
|
||||
let k = fr(size, ov_font_em) # atlas px -> screen px
|
||||
let cell = f_mul(fi(ov_font_cell), k)
|
||||
var pen = fi(x)
|
||||
let base = f_add(fi(y), f_mul(fi(size), fl(0.80)))
|
||||
let px = f_mul(f_mul(ov_pad_x, fi(ov_font_em)), k)
|
||||
let py = f_mul(f_mul(ov_base_y, fi(ov_font_em)), k)
|
||||
let k = float(size) / float(ov_font_em) # atlas px -> screen px
|
||||
let cell = float(ov_font_cell) * k
|
||||
var pen = float(x)
|
||||
let base = float(y) + float(size) * 0.80
|
||||
let px = ov_pad_x * float(ov_font_em) * k
|
||||
let py = ov_base_y * float(ov_font_em) * k
|
||||
let sp: pointer = s
|
||||
let n = len(sp)
|
||||
var i = 0
|
||||
|
|
@ -344,27 +344,27 @@ function ov_text(x: int, y: int, size: int, s: string, r: int, g: int, b: int, a
|
|||
if c != ov_font_space {
|
||||
let cx = c - (c / ov_font_cols) * ov_font_cols
|
||||
let cy = c / ov_font_cols
|
||||
let u0 = fr(cx, ov_font_cols); let u1 = fr(cx + 1, ov_font_cols)
|
||||
let v0 = fr(cy, ov_font_rows); let v1 = fr(cy + 1, ov_font_rows)
|
||||
let x0 = f_sub(pen, px); let y1 = f_add(base, py)
|
||||
ov_quad(x0, f_sub(y1, cell), f_add(x0, cell), y1, u0, v0, u1, v1, r, g, b, a)
|
||||
let u0 = float(cx) / float(ov_font_cols); let u1 = float(cx + 1) / float(ov_font_cols)
|
||||
let v0 = float(cy) / float(ov_font_rows); let v1 = float(cy + 1) / float(ov_font_rows)
|
||||
let x0 = pen - px; let y1 = base + py
|
||||
ov_quad(x0, y1 - cell, x0 + cell, y1, u0, v0, u1, v1, r, g, b, a)
|
||||
}
|
||||
pen = f_add(pen, f_mul(ov_font_adv[c], fi(size)))
|
||||
pen = pen + ov_font_adv[c] * float(size)
|
||||
}
|
||||
return f_to_int(pen)
|
||||
return int(pen)
|
||||
}
|
||||
# text with a soft dark shadow under it (HUD over a bright meadow)
|
||||
function ov_text_sh(x: int, y: int, size: int, s: string, r: int, g: int, b: int, a: int) -> int {
|
||||
function ov_text_sh(x: int, y: int, size: int, s: string, r: float, g: float, b: float, a: float) -> int {
|
||||
let d = size / 18 + 1
|
||||
ov_text(x + d, y + d, size, s, F_ZERO, F_ZERO, F_ZERO, f_mul(a, fl(0.7)))
|
||||
ov_text(x + d, y + d, size, s, 0.0, 0.0, 0.0, a * 0.7)
|
||||
return ov_text(x, y, size, s, r, g, b, a)
|
||||
}
|
||||
function ov_text_center(cx: int, y: int, size: int, s: string, r: int, g: int, b: int, a: int) -> void {
|
||||
function ov_text_center(cx: int, y: int, size: int, s: string, r: float, g: float, b: float, a: float) -> void {
|
||||
ov_text(cx - ov_text_w(size, s) / 2, y, size, s, r, g, b, a)
|
||||
}
|
||||
|
||||
# text wrapped at `maxw` pixels on spaces; returns the y after the last line
|
||||
function ov_text_wrap(x: int, y: int, size: int, maxw: int, s: string, r: int, g: int, b: int, a: int) -> int {
|
||||
function ov_text_wrap(x: int, y: int, size: int, maxw: int, s: string, r: float, g: float, b: float, a: float) -> int {
|
||||
let sp: pointer = s
|
||||
let n = len(sp)
|
||||
var first = 0
|
||||
|
|
@ -391,27 +391,27 @@ function ov_text_wrap(x: int, y: int, size: int, maxw: int, s: string, r: int, g
|
|||
|
||||
# ---- more shapes for a game's interface -------------------------------------------------
|
||||
# a sub-rectangle of a texture (uv corners as float bits) tinted, at integer pixels
|
||||
function ov_sub(tex: int, x: int, y: int, w: int, h: int, u0: int, v0: int, u1: int, v1: int, r: int, g: int, b: int, a: int) -> void {
|
||||
function ov_sub(tex: int, x: int, y: int, w: int, h: int, u0: float, v0: float, u1: float, v1: float, r: float, g: float, b: float, a: float) -> void {
|
||||
ov_use_tex(tex)
|
||||
ov_quad(fi(x), fi(y), fi(x + w), fi(y + h), u0, v0, u1, v1, r, g, b, a)
|
||||
ov_quad(float(x), float(y), float(x + w), float(y + h), u0, v0, u1, v1, r, g, b, a)
|
||||
}
|
||||
# a whole texture stretched by nine slices: corners `src` texels wide in a `tw` px square
|
||||
# texture, drawn `dst` pixels wide, so rounded corners keep their shape at any size
|
||||
function ov_nine(tex: int, tw: int, src: int, x: int, y: int, w: int, h: int, dst: int, r: int, g: int, b: int, a: int) -> void {
|
||||
function ov_nine(tex: int, tw: int, src: int, x: int, y: int, w: int, h: int, dst: int, r: float, g: float, b: float, a: float) -> void {
|
||||
ov_use_tex(tex)
|
||||
let s = fr(src, tw)
|
||||
let xs = words(4); let ys = words(4); let us = words(4); let vs = words(4)
|
||||
xs[0] = fi(x); xs[1] = fi(x + dst); xs[2] = fi(x + w - dst); xs[3] = fi(x + w)
|
||||
ys[0] = fi(y); ys[1] = fi(y + dst); ys[2] = fi(y + h - dst); ys[3] = fi(y + h)
|
||||
us[0] = F_ZERO; us[1] = s; us[2] = f_sub(F_ONE, s); us[3] = F_ONE
|
||||
vs[0] = F_ZERO; vs[1] = s; vs[2] = f_sub(F_ONE, s); vs[3] = F_ONE
|
||||
let s = float(src) / float(tw)
|
||||
let xs = floats(4); let ys = floats(4); let us = floats(4); let vs = floats(4)
|
||||
xs[0] = float(x); xs[1] = float(x + dst); xs[2] = float(x + w - dst); xs[3] = float(x + w)
|
||||
ys[0] = float(y); ys[1] = float(y + dst); ys[2] = float(y + h - dst); ys[3] = float(y + h)
|
||||
us[0] = 0.0; us[1] = s; us[2] = 1.0 - s; us[3] = 1.0
|
||||
vs[0] = 0.0; vs[1] = s; vs[2] = 1.0 - s; vs[3] = 1.0
|
||||
for j in 0 .. 3 {
|
||||
for i in 0 .. 3 { ov_quad(xs[i], ys[j], xs[i + 1], ys[j + 1], us[i], vs[j], us[i + 1], vs[j + 1], r, g, b, a) }
|
||||
}
|
||||
free(xs); free(ys); free(us); free(vs)
|
||||
}
|
||||
# an arbitrary quad (float-bit pixel corners, clockwise from top-left) of a texture
|
||||
function ov_quad4(x0: int, y0: int, x1: int, y1: int, x2: int, y2: int, x3: int, y3: int, u0: int, v0: int, u1: int, v1: int, r: int, g: int, b: int, a: int) -> void {
|
||||
function ov_quad4(x0: float, y0: float, x1: float, y1: float, x2: float, y2: float, x3: float, y3: float, u0: float, v0: float, u1: float, v1: float, r: float, g: float, b: float, a: float) -> void {
|
||||
if ov_n >= OV_MAX_QUADS { ov_flush() }
|
||||
let k = ov_n * 6
|
||||
ov_vert(k, x0, y0, u0, v0, r, g, b, a)
|
||||
|
|
@ -423,42 +423,42 @@ function ov_quad4(x0: int, y0: int, x1: int, y1: int, x2: int, y2: int, x3: int,
|
|||
ov_n += 1
|
||||
}
|
||||
# a line of thickness `t` pixels between two points (float-bit pixels)
|
||||
function ov_line(x0: int, y0: int, x1: int, y1: int, t: int, r: int, g: int, b: int, a: int) -> void {
|
||||
function ov_line(x0: float, y0: float, x1: float, y1: float, t: float, r: float, g: float, b: float, a: float) -> void {
|
||||
ov_use_tex(ov_white)
|
||||
let dx = f_sub(x1, x0); let dy = f_sub(y1, y0)
|
||||
let l = f_max(f_sqrt(f_add(f_mul(dx, dx), f_mul(dy, dy))), fl(0.001))
|
||||
let nx = f_mul(f_div(f_neg(dy), l), f_mul(t, F_HALF)); let ny = f_mul(f_div(dx, l), f_mul(t, F_HALF))
|
||||
ov_quad4(f_add(x0, nx), f_add(y0, ny), f_add(x1, nx), f_add(y1, ny), f_sub(x1, nx), f_sub(y1, ny), f_sub(x0, nx), f_sub(y0, ny), F_ZERO, F_ZERO, F_ONE, F_ONE, r, g, b, a)
|
||||
let dx = x1 - x0; let dy = y1 - y0
|
||||
let l = Math.max(Math.sqrt(dx * dx + dy * dy), 0.001)
|
||||
let nx = -dy / l * (t * 0.5); let ny = dx / l * (t * 0.5)
|
||||
ov_quad4(x0 + nx, y0 + ny, x1 + nx, y1 + ny, x1 - nx, y1 - ny, x0 - nx, y0 - ny, 0.0, 0.0, 1.0, 1.0, r, g, b, a)
|
||||
}
|
||||
# a sub-rectangle of a texture rotated by `ang` radians about its centre (cx, cy), `w` x `h` pixels
|
||||
function ov_sub_rot(tex: int, cx: int, cy: int, w: int, h: int, ang: int, u0: int, v0: int, u1: int, v1: int, r: int, g: int, b: int, a: int) -> void {
|
||||
function ov_sub_rot(tex: int, cx: int, cy: int, w: int, h: int, ang: float, u0: float, v0: float, u1: float, v1: float, r: float, g: float, b: float, a: float) -> void {
|
||||
ov_use_tex(tex)
|
||||
let c = f_cos(ang); let s = f_sin(ang)
|
||||
let hw = f_mul(fi(w), F_HALF); let hh = f_mul(fi(h), F_HALF)
|
||||
let fx = fi(cx); let fy = fi(cy)
|
||||
let c = Math.cos(ang); let s = Math.sin(ang)
|
||||
let hw = float(w) * 0.5; let hh = float(h) * 0.5
|
||||
let fx = float(cx); let fy = float(cy)
|
||||
# corners: (-hw,-hh) (hw,-hh) (hw,hh) (-hw,hh) rotated
|
||||
let x0 = f_add(fx, f_sub(f_mul(f_neg(hw), c), f_mul(f_neg(hh), s))); let y0 = f_add(fy, f_add(f_mul(f_neg(hw), s), f_mul(f_neg(hh), c)))
|
||||
let x1 = f_add(fx, f_sub(f_mul(hw, c), f_mul(f_neg(hh), s))); let y1 = f_add(fy, f_add(f_mul(hw, s), f_mul(f_neg(hh), c)))
|
||||
let x2 = f_add(fx, f_sub(f_mul(hw, c), f_mul(hh, s))); let y2 = f_add(fy, f_add(f_mul(hw, s), f_mul(hh, c)))
|
||||
let x3 = f_add(fx, f_sub(f_mul(f_neg(hw), c), f_mul(hh, s))); let y3 = f_add(fy, f_add(f_mul(f_neg(hw), s), f_mul(hh, c)))
|
||||
let x0 = fx + (-hw * c - -hh * s); let y0 = fy + (-hw * s + -hh * c)
|
||||
let x1 = fx + (hw * c - -hh * s); let y1 = fy + (hw * s + -hh * c)
|
||||
let x2 = fx + (hw * c - hh * s); let y2 = fy + (hw * s + hh * c)
|
||||
let x3 = fx + (-hw * c - hh * s); let y3 = fy + (-hw * s + hh * c)
|
||||
ov_quad4(x0, y0, x1, y1, x2, y2, x3, y3, u0, v0, u1, v1, r, g, b, a)
|
||||
}
|
||||
# a filled circle approximated by `n` wedges (float-bit centre and radius)
|
||||
function ov_disc(cx: int, cy: int, rad: int, n: int, r: int, g: int, b: int, a: int) -> void {
|
||||
function ov_disc(cx: float, cy: float, rad: float, n: int, r: float, g: float, b: float, a: float) -> void {
|
||||
ov_use_tex(ov_white)
|
||||
let step = f_div(f_mul(F_TWO, F_PI), fi(n))
|
||||
let step = 2.0 * PI / float(n)
|
||||
for i in 0 .. n {
|
||||
let a0 = f_mul(fi(i), step); let a1 = f_add(a0, step)
|
||||
let ax = f_add(cx, f_mul(f_cos(a0), rad)); let ay = f_add(cy, f_mul(f_sin(a0), rad))
|
||||
let bx = f_add(cx, f_mul(f_cos(a1), rad)); let by = f_add(cy, f_mul(f_sin(a1), rad))
|
||||
ov_quad4(cx, cy, ax, ay, bx, by, cx, cy, F_ZERO, F_ZERO, F_ONE, F_ONE, r, g, b, a)
|
||||
let a0 = float(i) * step; let a1 = a0 + step
|
||||
let ax = cx + Math.cos(a0) * rad; let ay = cy + Math.sin(a0) * rad
|
||||
let bx = cx + Math.cos(a1) * rad; let by = cy + Math.sin(a1) * rad
|
||||
ov_quad4(cx, cy, ax, ay, bx, by, cx, cy, 0.0, 0.0, 1.0, 1.0, r, g, b, a)
|
||||
}
|
||||
}
|
||||
# a ring: `n` segments of thickness `t`, from angle a0 for `span` radians (float bits)
|
||||
function ov_arc(cx: int, cy: int, rad: int, t: int, a0: int, span: int, n: int, r: int, g: int, b: int, a: int) -> void {
|
||||
let step = f_div(span, fi(n))
|
||||
function ov_arc(cx: float, cy: float, rad: float, t: float, a0: float, span: float, n: int, r: float, g: float, b: float, a: float) -> void {
|
||||
let step = span / float(n)
|
||||
for i in 0 .. n {
|
||||
let b0 = f_add(a0, f_mul(fi(i), step)); let b1 = f_add(b0, step)
|
||||
ov_line(f_add(cx, f_mul(f_cos(b0), rad)), f_add(cy, f_mul(f_sin(b0), rad)), f_add(cx, f_mul(f_cos(b1), rad)), f_add(cy, f_mul(f_sin(b1), rad)), t, r, g, b, a)
|
||||
let b0 = a0 + float(i) * step; let b1 = b0 + step
|
||||
ov_line(cx + Math.cos(b0) * rad, cy + Math.sin(b0) * rad, cx + Math.cos(b1) * rad, cy + Math.sin(b1) * rad, t, r, g, b, a)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,19 +14,19 @@ var post_p_down: int = 0
|
|||
var post_p_up: int = 0
|
||||
var post_p_tone: int = 0
|
||||
var post_fs: Mesh = null
|
||||
var post_exposure: int = 0
|
||||
var post_exposure: float = 0.0
|
||||
var post_bloom_strength: int = 0
|
||||
var post_vignette: int = 0
|
||||
var post_saturation: int = 0
|
||||
var post_contrast: int = 0
|
||||
var post_vignette: float = 0.0
|
||||
var post_saturation: float = 0.0
|
||||
var post_contrast: float = 0.0
|
||||
var post_w: int = 0
|
||||
var post_h: int = 0
|
||||
var post_auto: bool = true
|
||||
var post_key: int = 0 # target mean luminance after exposure (float bits)
|
||||
var post_key: float = 0.0 # target mean luminance after exposure (float bits)
|
||||
var post_lum: words = null
|
||||
var post_mips: int = 0
|
||||
var post_adapt: int = 0 # smoothed exposure (float bits)
|
||||
var post_exposure_max: int = 0x41A00000 # 20: the ceiling auto-exposure may reach (night lowers it)
|
||||
var post_adapt: float = 0.0 # smoothed exposure (float bits)
|
||||
var post_exposure_max: float = 20.0 # 20: the ceiling auto-exposure may reach (night lowers it)
|
||||
# ---- the grade ------------------------------------------------------------------------------
|
||||
# White balance, the shadows' floor and the highlights' gain. These were nine literals bound at
|
||||
# the draw, so the game had the same colour at seven in the morning as at one in the afternoon -
|
||||
|
|
@ -35,25 +35,25 @@ var post_exposure_max: int = 0x41A00000 # 20: the ceiling auto-exposure may re
|
|||
# program that never starts a day looks exactly as it did.
|
||||
# They are plain ints rather than a v3 on purpose: a grade is written by daylight_set, which can
|
||||
# run before post_init has allocated anything, and nine ints cannot be null.
|
||||
var post_wb_r: int = 0x3F828F5C # 1.02
|
||||
var post_wb_g: int = 0x3F800000 # 1.0
|
||||
var post_wb_b: int = 0x3F7851EC # 0.97
|
||||
var post_lift_r: int = 0x3B83126F # 0.004
|
||||
var post_lift_g: int = 0x3B83126F # 0.004
|
||||
var post_lift_b: int = 0x3C449BA6 # 0.012
|
||||
var post_gain_r: int = 0x3F7D70A4 # 0.99
|
||||
var post_gain_g: int = 0x3F7EB852 # 0.995
|
||||
var post_gain_b: int = 0x3F800000 # 1.0
|
||||
var post_wb_r: float = 1.02 # 1.02
|
||||
var post_wb_g: float = 1.0 # 1.0
|
||||
var post_wb_b: float = 0.97 # 0.97
|
||||
var post_lift_r: float = 0.004 # 0.004
|
||||
var post_lift_g: float = 0.004 # 0.004
|
||||
var post_lift_b: float = 0.012 # 0.012
|
||||
var post_gain_r: float = 0.99 # 0.99
|
||||
var post_gain_g: float = 0.995 # 0.995
|
||||
var post_gain_b: float = 1.0 # 1.0
|
||||
|
||||
var post_ao: Target = null
|
||||
var post_ao_blur: Target = null
|
||||
var post_p_ao: int = 0
|
||||
var post_p_ao_blur: int = 0
|
||||
var post_ao_radius: int = 0
|
||||
var post_contact: int = 0x3FA00000 # 1.25: how hard a thing is darkened where it meets the ground
|
||||
var post_ao_intensity: int = 0
|
||||
var post_ao_strength: int = 0
|
||||
var post_gi_strength: int = 0x3ECCCCCD # 0.4
|
||||
var post_ao_radius: float = 0.0
|
||||
var post_contact: float = 1.25 # 1.25: how hard a thing is darkened where it meets the ground
|
||||
var post_ao_intensity: float = 0.0
|
||||
var post_ao_strength: float = 0.0
|
||||
var post_gi_strength: float = 0.4 # 0.4
|
||||
var post_no_gi: bool = false
|
||||
var post_ldr: Target = null
|
||||
var post_depth_copy: Target = null
|
||||
|
|
@ -63,13 +63,13 @@ var post_depth_copy: Target = null
|
|||
# and the pass is skipped entirely rather than run at one step.
|
||||
var post_vol: Target = null
|
||||
var post_p_vol: int = 0
|
||||
var post_vol_steps: int = 0x41C00000 # 24
|
||||
var post_vol_density: int = 0x3B03126F # 0.002
|
||||
var post_vol_falloff: int = 0x3BA3D70A # 0.005
|
||||
var post_vol_far: int = 0x44480000 # 800 m
|
||||
var post_vol_g: int = 0x3F19999A # 0.6: air throws light forward
|
||||
var post_vol_mist: int = 0 # the day sets these two
|
||||
var post_vol_mist_h: int = 0x42200000 # 40 m
|
||||
var post_vol_steps: float = 24.0 # 24
|
||||
var post_vol_density: float = 0.002 # 0.002
|
||||
var post_vol_falloff: float = 0.005 # 0.005
|
||||
var post_vol_far: float = 800.0 # 800 m
|
||||
var post_vol_g: float = 0.6 # 0.6: air throws light forward
|
||||
var post_vol_mist: float = 0.0 # the day sets these two
|
||||
var post_vol_mist_h: float = 40.0 # 40 m
|
||||
var post_prev: Target = null # last frame's scene colour, for the SSGI bounce only
|
||||
var post_scene: Target = null # this frame's scene colour before the water, for refraction
|
||||
var post_frame: int = 0
|
||||
|
|
@ -79,20 +79,20 @@ var post_color: int = 0 # the HDR colour the rest of post reads
|
|||
var post_p_sharp: int = 0
|
||||
# Spatial anti-aliasing, in the sharpen pass because that pass already reads this pixel's
|
||||
# neighbourhood and runs last on the LDR image. 1 on, 0 off; the game's setting drives it.
|
||||
var post_fxaa: int = 0x3F800000
|
||||
var post_fxaa: float = 1.0
|
||||
# ---- depth of field ------------------------------------------------------------------------
|
||||
# Off in ordinary play - the pass is skipped whole, not run at zero radius. The game turns it on
|
||||
# behind the viewfinder and says what to focus on.
|
||||
var post_p_dof: int = 0
|
||||
var post_dof: Target = null
|
||||
var post_dof_focus: int = 0x41200000 # 10 m
|
||||
var post_dof_aperture: int = 0 # 0 = no lens, and no pass
|
||||
var post_dof_max: int = 0x41400000 # 12 px
|
||||
var post_dof_focus: float = 10.0 # 10 m
|
||||
var post_dof_aperture: float = 0.0 # 0 = no lens, and no pass
|
||||
var post_dof_max: float = 12.0 # 12 px
|
||||
var post_p_tone_hdr: int = 0 # the tonemap's HDR10 variant, made the first time HDR is on
|
||||
var post_ldr_hdr: bool = false # post_ldr was made for HDR10 output (10-bit)
|
||||
# the LDR image is 10-bit while the output is HDR10: PQ in 8 bits bands
|
||||
function post_ldr_fmt() -> int { if gpu_hdr_active() { return GL_RGB10_A2 }; return GL_RGBA8 }
|
||||
var post_sharpen: int = 0
|
||||
var post_sharpen: float = 0.0
|
||||
var post_grain: int = 0
|
||||
|
||||
# the screen-sized targets go away before post_init makes them at a new size
|
||||
|
|
@ -168,23 +168,23 @@ function post_init(w: int, h: int) -> void {
|
|||
post_prev = target_new(w, h, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, false, GL_LINEAR)
|
||||
post_scene = target_new(w, h, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, false, GL_LINEAR)
|
||||
if post_p_sharp == 0 { post_p_sharp = r3d_program("fullscreen.vert", "sharpen.frag", "") }
|
||||
post_sharpen = fl(1.2)
|
||||
post_grain = fl(0.025)
|
||||
post_ao_radius = fl(0.7)
|
||||
post_ao_intensity = fl(1.4)
|
||||
post_ao_strength = fl(0.8)
|
||||
post_sharpen = 1.2
|
||||
post_grain = float_bits(0.025)
|
||||
post_ao_radius = 0.7
|
||||
post_ao_intensity = 1.4
|
||||
post_ao_strength = 0.8
|
||||
post_fs = mesh_fullscreen()
|
||||
post_exposure = fl(0.36)
|
||||
post_bloom_strength = fl(0.06)
|
||||
post_vignette = fl(0.35)
|
||||
post_saturation = fl(1.04)
|
||||
post_contrast = fl(1.12)
|
||||
post_key = fl(0.19)
|
||||
post_exposure = 0.36
|
||||
post_bloom_strength = float_bits(0.06)
|
||||
post_vignette = 0.35
|
||||
post_saturation = 1.04
|
||||
post_contrast = 1.12
|
||||
post_key = 0.19
|
||||
post_lum = words(4)
|
||||
var m = 1; var sz = max(w, h)
|
||||
while sz > 1 { sz = sz / 2; m += 1 }
|
||||
post_mips = m
|
||||
post_adapt = F_ZERO
|
||||
post_adapt = 0.0
|
||||
}
|
||||
|
||||
# Mean scene luminance from the HDR mip chain -> exposure = key / mean, eased over
|
||||
|
|
@ -217,12 +217,12 @@ function post_measure() -> void {
|
|||
gpu_use_program(post_p_adapt)
|
||||
r3d_bind_2d(post_p_adapt, "u_scene", 0, post_hdr.color)
|
||||
r3d_bind_2d(post_p_adapt, "u_prev", 1, post_adapt_t[post_adapt_i].color)
|
||||
u_f(gpu_uniform(post_p_adapt, "u_lod"), fi(post_mips - 1))
|
||||
u_f(gpu_uniform(post_p_adapt, "u_lod"), float(post_mips - 1))
|
||||
u_f(gpu_uniform(post_p_adapt, "u_key"), post_key)
|
||||
u_f(gpu_uniform(post_p_adapt, "u_max"), post_exposure_max)
|
||||
u_f(gpu_uniform(post_p_adapt, "u_rate"), fl(0.08))
|
||||
var reset = F_ZERO
|
||||
if post_adapt_reset { reset = F_ONE; post_adapt_reset = false }
|
||||
u_f(gpu_uniform(post_p_adapt, "u_rate"), 0.08)
|
||||
var reset = 0.0
|
||||
if post_adapt_reset { reset = 1.0; post_adapt_reset = false }
|
||||
u_f(gpu_uniform(post_p_adapt, "u_reset"), reset)
|
||||
mesh_draw(post_fs)
|
||||
post_adapt_i = next
|
||||
|
|
@ -292,21 +292,21 @@ function post_ssao_pass() -> void {
|
|||
gpu_use_program(post_p_ao)
|
||||
r3d_bind_2d(post_p_ao, "u_depth", 0, post_hdr.depth)
|
||||
r3d_bind_2d(post_p_ao, "u_prev_color", 1, post_prev.color)
|
||||
u_f(gpu_uniform(post_p_ao, "u_frame"), fi(post_frame % 64))
|
||||
u_f(gpu_uniform(post_p_ao, "u_frame"), float(post_frame % 64))
|
||||
u_mat4(gpu_uniform(post_p_ao, "u_inv_proj"), cam_inv_proj)
|
||||
u_mat4(gpu_uniform(post_p_ao, "u_proj"), cam_proj)
|
||||
u_f2(gpu_uniform(post_p_ao, "u_texel"), fr(1, post_w), fr(1, post_h))
|
||||
u_f2(gpu_uniform(post_p_ao, "u_texel"), 1.0 / float(post_w), 1.0 / float(post_h))
|
||||
u_f(gpu_uniform(post_p_ao, "u_radius"), post_ao_radius)
|
||||
u_f(gpu_uniform(post_p_ao, "u_intensity"), post_ao_intensity)
|
||||
var contact = post_contact
|
||||
if r3d_env_has("R3D_NOCONTACT") { contact = F_ZERO }
|
||||
if r3d_env_has("R3D_NOCONTACT") { contact = 0.0 }
|
||||
u_f(gpu_uniform(post_p_ao, "u_contact"), contact)
|
||||
mesh_draw(post_fs)
|
||||
target_bind(post_ao_blur)
|
||||
gpu_use_program(post_p_ao_blur)
|
||||
r3d_bind_2d(post_p_ao_blur, "u_ao", 0, post_ao.color)
|
||||
r3d_bind_2d(post_p_ao_blur, "u_depth", 1, post_hdr.depth)
|
||||
u_f2(gpu_uniform(post_p_ao_blur, "u_texel"), fr(1, post_ao.w), fr(1, post_ao.h))
|
||||
u_f2(gpu_uniform(post_p_ao_blur, "u_texel"), 1.0 / float(post_ao.w), 1.0 / float(post_ao.h))
|
||||
mesh_draw(post_fs)
|
||||
}
|
||||
|
||||
|
|
@ -314,7 +314,7 @@ function post_ssao_pass() -> void {
|
|||
# light is a bright thing in the air and should bloom like one, and compositing it after the
|
||||
# bloom pyramid would give hard-edged rays with no glow at all.
|
||||
function post_volumetric_pass() -> void {
|
||||
if post_vol_steps <= 0 { return }
|
||||
if post_vol_steps <= 0.0 { return }
|
||||
if r3d_env_has("R3D_NOVOL") { return }
|
||||
gpu_depth_test(false)
|
||||
gpu_blend(false)
|
||||
|
|
@ -346,8 +346,8 @@ function post_volumetric_pass() -> void {
|
|||
gpu_blend_func(GL_ONE, GL_ONE)
|
||||
gpu_use_program(post_p_up)
|
||||
r3d_bind_2d(post_p_up, "u_src", 0, post_vol.color)
|
||||
u_f2(gpu_uniform(post_p_up, "u_texel"), fr(1, post_vol.w), fr(1, post_vol.h))
|
||||
u_f(gpu_uniform(post_p_up, "u_radius"), F_ONE)
|
||||
u_f2(gpu_uniform(post_p_up, "u_texel"), 1.0 / float(post_vol.w), 1.0 / float(post_vol.h))
|
||||
u_f(gpu_uniform(post_p_up, "u_radius"), 1.0)
|
||||
mesh_draw(post_fs)
|
||||
gpu_blend(false)
|
||||
}
|
||||
|
|
@ -355,7 +355,7 @@ function post_volumetric_pass() -> void {
|
|||
# The lens, between the scene and the bloom: a blurred highlight should still bloom, and a
|
||||
# bloom smeared by the lens afterwards would be a halo round nothing.
|
||||
function post_dof_pass() -> void {
|
||||
if post_dof_aperture == 0 { return }
|
||||
if post_dof_aperture == 0.0 { return }
|
||||
gpu_depth_test(false)
|
||||
gpu_blend(false)
|
||||
target_bind(post_dof)
|
||||
|
|
@ -363,7 +363,7 @@ function post_dof_pass() -> void {
|
|||
r3d_bind_2d(post_p_dof, "u_src", 0, post_color)
|
||||
r3d_bind_2d(post_p_dof, "u_depth", 1, post_hdr.depth)
|
||||
u_mat4(gpu_uniform(post_p_dof, "u_inv_proj"), cam_inv_proj)
|
||||
u_f2(gpu_uniform(post_p_dof, "u_texel"), fr(1, post_w), fr(1, post_h))
|
||||
u_f2(gpu_uniform(post_p_dof, "u_texel"), 1.0 / float(post_w), 1.0 / float(post_h))
|
||||
u_f(gpu_uniform(post_p_dof, "u_focus"), post_dof_focus)
|
||||
u_f(gpu_uniform(post_p_dof, "u_aperture"), post_dof_aperture)
|
||||
u_f(gpu_uniform(post_p_dof, "u_max_coc"), post_dof_max)
|
||||
|
|
@ -381,9 +381,9 @@ function post_bloom_pass() -> void {
|
|||
let t = post_bloom[i]
|
||||
target_bind(t)
|
||||
r3d_bind_2d(post_p_down, "u_src", 0, src)
|
||||
u_f2(gpu_uniform(post_p_down, "u_texel"), fr(1, sw), fr(1, sh))
|
||||
var th = f_neg1()
|
||||
if i == 0 { th = fl(1.2) }
|
||||
u_f2(gpu_uniform(post_p_down, "u_texel"), 1.0 / float(sw), 1.0 / float(sh))
|
||||
var th = -1.0
|
||||
if i == 0 { th = 1.2 }
|
||||
u_f(gpu_uniform(post_p_down, "u_threshold"), th)
|
||||
mesh_draw(post_fs)
|
||||
src = t.color; sw = t.w; sh = t.h
|
||||
|
|
@ -397,8 +397,8 @@ function post_bloom_pass() -> void {
|
|||
let to = post_bloom[i - 1]
|
||||
target_bind(to)
|
||||
r3d_bind_2d(post_p_up, "u_src", 0, from.color)
|
||||
u_f2(gpu_uniform(post_p_up, "u_texel"), fr(1, from.w), fr(1, from.h))
|
||||
u_f(gpu_uniform(post_p_up, "u_radius"), F_ONE)
|
||||
u_f2(gpu_uniform(post_p_up, "u_texel"), 1.0 / float(from.w), 1.0 / float(from.h))
|
||||
u_f(gpu_uniform(post_p_up, "u_radius"), 1.0)
|
||||
mesh_draw(post_fs)
|
||||
i -= 1
|
||||
}
|
||||
|
|
@ -427,10 +427,10 @@ function post_tonemap(color_tex: int) -> void {
|
|||
u_f(gpu_uniform(prog, "u_ao_strength"), post_ao_strength)
|
||||
u_f(gpu_uniform(prog, "u_gi_strength"), post_gi_strength)
|
||||
u_f(gpu_uniform(prog, "u_exposure"), post_exposure)
|
||||
var auto = F_ZERO
|
||||
if post_auto and post_adapt_t != null { auto = F_ONE; r3d_bind_2d(prog, "u_adapt", 3, post_adapt_t[post_adapt_i].color) }
|
||||
var auto = 0.0
|
||||
if post_auto and post_adapt_t != null { auto = 1.0; r3d_bind_2d(prog, "u_adapt", 3, post_adapt_t[post_adapt_i].color) }
|
||||
u_f(gpu_uniform(prog, "u_auto"), auto)
|
||||
u_f(gpu_uniform(prog, "u_bloom_strength"), post_bloom_strength)
|
||||
u_f(gpu_uniform(prog, "u_bloom_strength"), float_from_bits(post_bloom_strength))
|
||||
u_f(gpu_uniform(prog, "u_vignette"), post_vignette)
|
||||
u_f(gpu_uniform(prog, "u_saturation"), post_saturation)
|
||||
u_f(gpu_uniform(prog, "u_contrast"), post_contrast)
|
||||
|
|
@ -447,15 +447,15 @@ function post_tonemap(color_tex: int) -> void {
|
|||
gpu_viewport(0, 0, gl_w, gl_h)
|
||||
gpu_use_program(post_p_sharp)
|
||||
r3d_bind_2d(post_p_sharp, "u_src", 0, post_ldr.color)
|
||||
u_f2(gpu_uniform(post_p_sharp, "u_texel"), fr(1, post_ldr.w), fr(1, post_ldr.h))
|
||||
u_f2(gpu_uniform(post_p_sharp, "u_texel"), 1.0 / float(post_ldr.w), 1.0 / float(post_ldr.h))
|
||||
u_f(gpu_uniform(post_p_sharp, "u_amount"), post_sharpen)
|
||||
var fx = post_fxaa
|
||||
if r3d_env_has("R3D_NOFXAA") { fx = F_ZERO }
|
||||
if r3d_env_has("R3D_NOFXAA") { fx = 0.0 }
|
||||
u_f(gpu_uniform(post_p_sharp, "u_fxaa"), fx)
|
||||
# R3D_NOGRAIN=1: no film grain, so two frames of a still camera can be compared for what else moves
|
||||
var grain = post_grain
|
||||
if r3d_env_has("R3D_NOGRAIN") { grain = F_ZERO }
|
||||
u_f(gpu_uniform(post_p_sharp, "u_grain"), grain)
|
||||
if r3d_env_has("R3D_NOGRAIN") { grain = float_bits(0.0) }
|
||||
u_f(gpu_uniform(post_p_sharp, "u_grain"), float_from_bits(grain))
|
||||
u_f(gpu_uniform(post_p_sharp, "u_time"), r3d_time)
|
||||
mesh_draw(post_fs)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,92 +4,92 @@
|
|||
# major matrices, o may alias its inputs unless stated.
|
||||
# ============================================================================
|
||||
|
||||
function q_new() -> words { let q = words(4); q_identity(q); return q }
|
||||
function q_identity(q: words) -> void { q[0] = F_ZERO; q[1] = F_ZERO; q[2] = F_ZERO; q[3] = F_ONE }
|
||||
function q_new() -> floats { let q = floats(4); q_identity(q); return q }
|
||||
function q_identity(q: floats) -> void { q[0] = 0.0; q[1] = 0.0; q[2] = 0.0; q[3] = 1.0 }
|
||||
function q_set(q: words, x: int, y: int, z: int, w: int) -> void { q[0] = x; q[1] = y; q[2] = z; q[3] = w }
|
||||
function q_copy(o: words, a: words) -> void { o[0] = a[0]; o[1] = a[1]; o[2] = a[2]; o[3] = a[3] }
|
||||
function q_copy(o: floats, a: floats) -> void { o[0] = a[0]; o[1] = a[1]; o[2] = a[2]; o[3] = a[3] }
|
||||
# the idx-th quaternion of a packed buffer
|
||||
function q_load(o: words, src: words, idx: int) -> void { for i in 0 .. 4 { o[i] = src[idx * 4 + i] } }
|
||||
function q_store(dst: words, idx: int, a: words) -> void { for i in 0 .. 4 { dst[idx * 4 + i] = a[i] } }
|
||||
function q_load(o: floats, src: floats, idx: int) -> void { for i in 0 .. 4 { o[i] = src[idx * 4 + i] } }
|
||||
function q_store(dst: floats, idx: int, a: floats) -> void { for i in 0 .. 4 { dst[idx * 4 + i] = a[i] } }
|
||||
|
||||
# a rotation of `angle` radians about the unit axis (ax, ay, az)
|
||||
function q_axis_angle(o: words, ax: int, ay: int, az: int, angle: int) -> void {
|
||||
let h = f_mul(angle, F_HALF)
|
||||
let s = f_sin(h)
|
||||
o[0] = f_mul(ax, s); o[1] = f_mul(ay, s); o[2] = f_mul(az, s); o[3] = f_cos(h)
|
||||
function q_axis_angle(o: floats, ax: float, ay: float, az: float, angle: float) -> void {
|
||||
let h = angle * 0.5
|
||||
let s = Math.sin(h)
|
||||
o[0] = ax * s; o[1] = ay * s; o[2] = az * s; o[3] = Math.cos(h)
|
||||
}
|
||||
# o = a * b (apply b first, then a); o may alias a or b
|
||||
function q_mul(o: words, a: words, b: words) -> void {
|
||||
function q_mul(o: floats, a: floats, b: floats) -> void {
|
||||
let ax = a[0]; let ay = a[1]; let az = a[2]; let aw = a[3]
|
||||
let bx = b[0]; let by = b[1]; let bz = b[2]; let bw = b[3]
|
||||
let x = f_sub(f_add(f_add(f_mul(aw, bx), f_mul(ax, bw)), f_mul(ay, bz)), f_mul(az, by))
|
||||
let y = f_add(f_add(f_sub(f_mul(aw, by), f_mul(ax, bz)), f_mul(ay, bw)), f_mul(az, bx))
|
||||
let z = f_add(f_sub(f_add(f_mul(aw, bz), f_mul(ax, by)), f_mul(ay, bx)), f_mul(az, bw))
|
||||
let w = f_sub(f_sub(f_sub(f_mul(aw, bw), f_mul(ax, bx)), f_mul(ay, by)), f_mul(az, bz))
|
||||
let x = aw * bx + ax * bw + ay * bz - az * by
|
||||
let y = aw * by - ax * bz + ay * bw + az * bx
|
||||
let z = aw * bz + ax * by - ay * bx + az * bw
|
||||
let w = aw * bw - ax * bx - ay * by - az * bz
|
||||
o[0] = x; o[1] = y; o[2] = z; o[3] = w
|
||||
}
|
||||
function q_conj(o: words, a: words) -> void { o[0] = f_neg(a[0]); o[1] = f_neg(a[1]); o[2] = f_neg(a[2]); o[3] = a[3] }
|
||||
function q_normalize(q: words) -> void {
|
||||
let l = f_sqrt(f_add(f_add(f_mul(q[0], q[0]), f_mul(q[1], q[1])), f_add(f_mul(q[2], q[2]), f_mul(q[3], q[3]))))
|
||||
if l == 0 { q_identity(q); return }
|
||||
let inv = f_div(F_ONE, l)
|
||||
for i in 0 .. 4 { q[i] = f_mul(q[i], inv) }
|
||||
function q_conj(o: floats, a: floats) -> void { o[0] = -a[0]; o[1] = -a[1]; o[2] = -a[2]; o[3] = a[3] }
|
||||
function q_normalize(q: floats) -> void {
|
||||
let l = Math.sqrt(q[0] * q[0] + q[1] * q[1] + (q[2] * q[2] + q[3] * q[3]))
|
||||
if l == 0.0 { q_identity(q); return }
|
||||
let inv = 1.0 / l
|
||||
for i in 0 .. 4 { q[i] = q[i] * inv }
|
||||
}
|
||||
# normalised linear blend from a to b (shortest arc), fine for the small steps a pose takes
|
||||
function q_nlerp(o: words, a: words, b: words, t: int) -> void {
|
||||
var d = f_add(f_add(f_mul(a[0], b[0]), f_mul(a[1], b[1])), f_add(f_mul(a[2], b[2]), f_mul(a[3], b[3])))
|
||||
var sg = F_ONE
|
||||
if f_ls(d, F_ZERO) { sg = f_neg(F_ONE) }
|
||||
for i in 0 .. 4 { o[i] = f_lerp(a[i], f_mul(b[i], sg), t) }
|
||||
function q_nlerp(o: floats, a: floats, b: floats, t: float) -> void {
|
||||
var d = a[0] * b[0] + a[1] * b[1] + (a[2] * b[2] + a[3] * b[3])
|
||||
var sg = 1.0
|
||||
if d < 0.0 { sg = -1.0 }
|
||||
for i in 0 .. 4 { o[i] = Math.lerp(a[i], b[i] * sg, t) }
|
||||
q_normalize(o)
|
||||
}
|
||||
# rotate the vector v by q: o = q v q*
|
||||
function q_rotate(o: words, q: words, v: words) -> void {
|
||||
function q_rotate(o: floats, q: floats, v: floats) -> void {
|
||||
let qx = q[0]; let qy = q[1]; let qz = q[2]; let qw = q[3]
|
||||
# t = 2 * cross(q.xyz, v)
|
||||
let tx = f_mul(F_TWO, f_sub(f_mul(qy, v[2]), f_mul(qz, v[1])))
|
||||
let ty = f_mul(F_TWO, f_sub(f_mul(qz, v[0]), f_mul(qx, v[2])))
|
||||
let tz = f_mul(F_TWO, f_sub(f_mul(qx, v[1]), f_mul(qy, v[0])))
|
||||
let tx = 2.0 * (qy * v[2] - qz * v[1])
|
||||
let ty = 2.0 * (qz * v[0] - qx * v[2])
|
||||
let tz = 2.0 * (qx * v[1] - qy * v[0])
|
||||
# o = v + w t + cross(q.xyz, t)
|
||||
let x = f_add(f_add(v[0], f_mul(qw, tx)), f_sub(f_mul(qy, tz), f_mul(qz, ty)))
|
||||
let y = f_add(f_add(v[1], f_mul(qw, ty)), f_sub(f_mul(qz, tx), f_mul(qx, tz)))
|
||||
let z = f_add(f_add(v[2], f_mul(qw, tz)), f_sub(f_mul(qx, ty), f_mul(qy, tx)))
|
||||
let x = v[0] + qw * tx + (qy * tz - qz * ty)
|
||||
let y = v[1] + qw * ty + (qz * tx - qx * tz)
|
||||
let z = v[2] + qw * tz + (qx * ty - qy * tx)
|
||||
o[0] = x; o[1] = y; o[2] = z
|
||||
}
|
||||
# pitch about X, yaw about Y, roll about Z, composed as yaw * pitch * roll
|
||||
var q_scratch: words = null
|
||||
function q_euler(o: words, pitch: int, yaw: int, roll: int) -> void {
|
||||
if q_scratch == null { q_scratch = words(16) }
|
||||
var q_scratch: floats = null
|
||||
function q_euler(o: floats, pitch: float, yaw: float, roll: float) -> void {
|
||||
if q_scratch == null { q_scratch = floats(16) }
|
||||
let qx = q_scratch; let qy = mem_off(q_scratch, 16); let qz = mem_off(q_scratch, 32); let t = mem_off(q_scratch, 48)
|
||||
q_axis_angle(qx, F_ONE, F_ZERO, F_ZERO, pitch)
|
||||
q_axis_angle(qy, F_ZERO, F_ONE, F_ZERO, yaw)
|
||||
q_axis_angle(qz, F_ZERO, F_ZERO, F_ONE, roll)
|
||||
q_axis_angle(qx, 1.0, 0.0, 0.0, pitch)
|
||||
q_axis_angle(qy, 0.0, 1.0, 0.0, yaw)
|
||||
q_axis_angle(qz, 0.0, 0.0, 1.0, roll)
|
||||
q_mul(t, qy, qx)
|
||||
q_mul(o, t, qz)
|
||||
}
|
||||
# the rotation matrix of q (column-major, translation cleared)
|
||||
function q_to_m4(m: words, q: words) -> void {
|
||||
function q_to_m4(m: floats, q: floats) -> void {
|
||||
let x = q[0]; let y = q[1]; let z = q[2]; let w = q[3]
|
||||
let xx = f_mul(x, x); let yy = f_mul(y, y); let zz = f_mul(z, z)
|
||||
let xy = f_mul(x, y); let xz = f_mul(x, z); let yz = f_mul(y, z)
|
||||
let wx = f_mul(w, x); let wy = f_mul(w, y); let wz = f_mul(w, z)
|
||||
m[0] = f_sub(F_ONE, f_mul(F_TWO, f_add(yy, zz)))
|
||||
m[1] = f_mul(F_TWO, f_add(xy, wz))
|
||||
m[2] = f_mul(F_TWO, f_sub(xz, wy))
|
||||
m[3] = F_ZERO
|
||||
m[4] = f_mul(F_TWO, f_sub(xy, wz))
|
||||
m[5] = f_sub(F_ONE, f_mul(F_TWO, f_add(xx, zz)))
|
||||
m[6] = f_mul(F_TWO, f_add(yz, wx))
|
||||
m[7] = F_ZERO
|
||||
m[8] = f_mul(F_TWO, f_add(xz, wy))
|
||||
m[9] = f_mul(F_TWO, f_sub(yz, wx))
|
||||
m[10] = f_sub(F_ONE, f_mul(F_TWO, f_add(xx, yy)))
|
||||
m[11] = F_ZERO
|
||||
m[12] = F_ZERO; m[13] = F_ZERO; m[14] = F_ZERO; m[15] = F_ONE
|
||||
let xx = x * x; let yy = y * y; let zz = z * z
|
||||
let xy = x * y; let xz = x * z; let yz = y * z
|
||||
let wx = w * x; let wy = w * y; let wz = w * z
|
||||
m[0] = 1.0 - 2.0 * (yy + zz)
|
||||
m[1] = 2.0 * (xy + wz)
|
||||
m[2] = 2.0 * (xz - wy)
|
||||
m[3] = 0.0
|
||||
m[4] = 2.0 * (xy - wz)
|
||||
m[5] = 1.0 - 2.0 * (xx + zz)
|
||||
m[6] = 2.0 * (yz + wx)
|
||||
m[7] = 0.0
|
||||
m[8] = 2.0 * (xz + wy)
|
||||
m[9] = 2.0 * (yz - wx)
|
||||
m[10] = 1.0 - 2.0 * (xx + yy)
|
||||
m[11] = 0.0
|
||||
m[12] = 0.0; m[13] = 0.0; m[14] = 0.0; m[15] = 1.0
|
||||
}
|
||||
# m = translate(t) * rotate(q) * scale(s)
|
||||
function m4_trs_q(m: words, tx: int, ty: int, tz: int, q: words, sx: int, sy: int, sz: int) -> void {
|
||||
function m4_trs_q(m: floats, tx: float, ty: float, tz: float, q: floats, sx: float, sy: float, sz: float) -> void {
|
||||
q_to_m4(m, q)
|
||||
for r in 0 .. 3 { m[r] = f_mul(m[r], sx); m[4 + r] = f_mul(m[4 + r], sy); m[8 + r] = f_mul(m[8 + r], sz) }
|
||||
for r in 0 .. 3 { m[r] = m[r] * sx; m[4 + r] = m[4 + r] * sy; m[8 + r] = m[8 + r] * sz }
|
||||
m[12] = tx; m[13] = ty; m[14] = tz
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,44 +6,44 @@
|
|||
# ============================================================================
|
||||
|
||||
var r3d_sky_prog: int = 0
|
||||
var r3d_fog_density: int = 0
|
||||
var r3d_fog_scale: int = 0x3F800000 # float bits: a setting's multiplier over the density the day sets
|
||||
var r3d_fog_falloff: int = 0
|
||||
var r3d_fog_base: int = 0 # the height fog is measured from (float bits); 0 = y = 0
|
||||
var r3d_fog_density: float = 0.0
|
||||
var r3d_fog_scale: float = 1.0 # float bits: a setting's multiplier over the density the day sets
|
||||
var r3d_fog_falloff: float = 0.0
|
||||
var r3d_fog_base: float = 0.0 # the height fog is measured from (float bits); 0 = y = 0
|
||||
# Both of these are the DAY's, set by daylight_set from the sun's own elevation. The inscatter
|
||||
# is how hard the air scatters the sun forward - nearly nothing at noon, and the glow a ridge
|
||||
# is silhouetted against at dusk. The desaturation is how fast distance takes a surface's own
|
||||
# colour away, which is the term that was missing entirely and is most of why a midday frame
|
||||
# had no depth in it at all.
|
||||
var r3d_fog_inscatter: int = 0x3CA3D70A # 0.02, what the shader used to hard-code
|
||||
var r3d_fog_desat: int = 0x3FACCCCD # 1.35
|
||||
var r3d_fog_inscatter: float = 0.02 # 0.02, what the shader used to hard-code
|
||||
var r3d_fog_desat: float = 1.35 # 1.35
|
||||
# How much of the visible sky's colour comes from the analytic model rather than from the
|
||||
# photograph (sky.frag). The day fades it in: at night the sky has its own tint, a star field
|
||||
# and a moon, and relighting on top of those would only wash them out.
|
||||
var r3d_sky_relight: int = 0
|
||||
var r3d_sky_relight: float = 0.0
|
||||
# What the ground reflects back up, low and high, and the height they cross at. The defaults
|
||||
# are Maroon Lake's - a green basin under a grey-rock treeline at about 480 m over the datum.
|
||||
var r3d_ground_alb_r: int = 0x3E99999A; var r3d_ground_alb_g: int = 0x3EAE147B; var r3d_ground_alb_b: int = 0x3E0F5C29
|
||||
var r3d_ground_hi_r: int = 0x3E93F7CF; var r3d_ground_hi_g: int = 0x3E8F5C29; var r3d_ground_hi_b: int = 0x3E851EB8
|
||||
var r3d_ground_hi_y: int = 0x43F00000 # 480
|
||||
var r3d_ground_hi_w: int = 0x43160000 # 150
|
||||
var r3d_time: int = 0
|
||||
var r3d_ground_alb_r: float = 0.3; var r3d_ground_alb_g: float = 0.34; var r3d_ground_alb_b: float = 0.14
|
||||
var r3d_ground_hi_r: float = 0.289; var r3d_ground_hi_g: float = 0.28; var r3d_ground_hi_b: float = 0.26
|
||||
var r3d_ground_hi_y: float = 480.0 # 480
|
||||
var r3d_ground_hi_w: float = 150.0 # 150
|
||||
var r3d_time: float = 0.0
|
||||
var r3d_ready: bool = false
|
||||
# set before r3d_init to build the landscape from a real height map
|
||||
var r3d_dem_path: string = null
|
||||
var r3d_dem_min: int = 0
|
||||
var r3d_dem_max: int = 0
|
||||
var r3d_dem_base: int = 0
|
||||
var r3d_dem_ox: int = 0
|
||||
var r3d_dem_oz: int = 0
|
||||
var r3d_dem_min: float = 0.0
|
||||
var r3d_dem_max: float = 0.0
|
||||
var r3d_dem_base: float = 0.0
|
||||
var r3d_dem_ox: float = 0.0
|
||||
var r3d_dem_oz: float = 0.0
|
||||
var r3d_ortho_path: string = null
|
||||
var r3d_debug: bool = false
|
||||
var r3d_debug_shadow: bool = false
|
||||
var r3d_debug_max: bool = false
|
||||
|
||||
var r3d_cloud_shadow: int = 0x3F000000 # 0.5
|
||||
var r3d_cloud_shadow: float = 0.5 # 0.5
|
||||
|
||||
var r3d_clip_y: int = 0xCF000000 # -2^31: no clipping
|
||||
var r3d_clip_y: float = -2147483600.0 # -2^31: no clipping
|
||||
# R3D_NOPREPASS=1: light the foliage the old way, every card behind the front one included
|
||||
var r3d_prepass_env: int = -1
|
||||
function r3d_prepass_off() -> bool {
|
||||
|
|
@ -52,7 +52,7 @@ function r3d_prepass_off() -> bool {
|
|||
}
|
||||
function fog_bind(prog: int) -> void {
|
||||
u_f(gpu_uniform(prog, "u_clip_y"), r3d_clip_y)
|
||||
u_f(gpu_uniform(prog, "u_spec_scale"), F_ONE)
|
||||
u_f(gpu_uniform(prog, "u_spec_scale"), 1.0)
|
||||
u_f(gpu_uniform(prog, "u_fog_density"), r3d_fog_density)
|
||||
u_f(gpu_uniform(prog, "u_fog_height_falloff"), r3d_fog_falloff)
|
||||
u_f(gpu_uniform(prog, "u_fog_base"), r3d_fog_base)
|
||||
|
|
@ -63,7 +63,7 @@ function fog_bind(prog: int) -> void {
|
|||
u_f(gpu_uniform(prog, "u_ground_hi_y"), r3d_ground_hi_y)
|
||||
u_f(gpu_uniform(prog, "u_ground_hi_w"), r3d_ground_hi_w)
|
||||
var cs = r3d_cloud_shadow
|
||||
if r3d_env_has("R3D_NOCLOUD") { cs = F_ZERO }
|
||||
if r3d_env_has("R3D_NOCLOUD") { cs = 0.0 }
|
||||
u_f(gpu_uniform(prog, "u_cloud_shadow"), cs)
|
||||
u_f(gpu_uniform(prog, "u_time"), r3d_time)
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ function r3d_env_flags() -> void {
|
|||
r3d_no_refl = r3d_env_has("R3D_NOREFL")
|
||||
if r3d_env_has("R3D_DEBUG") { r3d_debug = true }
|
||||
if r3d_env_has("R3D_DBGSHADOW") { r3d_debug_shadow = true }
|
||||
if r3d_env_has("R3D_NOGI") { post_gi_strength = F_ZERO; post_ao_strength = F_ZERO; post_no_gi = true }
|
||||
if r3d_env_has("R3D_NOGI") { post_gi_strength = 0.0; post_ao_strength = 0.0; post_no_gi = true }
|
||||
if r3d_env_has("R3D_MSAA") { post_ms_samples = Text.to_int(r3d_env("R3D_MSAA")) }
|
||||
sc_skip_blade = r3d_env_has("R3D_NOBLADES")
|
||||
sc_skip_card = r3d_env_has("R3D_NOCARDS")
|
||||
|
|
@ -111,7 +111,7 @@ function r3d_open(w: int, h: int, title: string) -> bool {
|
|||
var renderer: string = gpu_renderer_name()
|
||||
print(`r3d: {gl_w}x{gl_h} on {renderer}`)
|
||||
prof_init()
|
||||
cam_init(fr(gl_w, gl_h))
|
||||
cam_init(float(gl_w) / float(gl_h))
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -144,8 +144,8 @@ function r3d_load_step(i: int) -> bool {
|
|||
# was doing its whole job through colour and could not be raised without the valley going
|
||||
# blue. With the desaturation term carrying the depth, the air can be as thick as real air
|
||||
# at 2900 m and the far ridge recedes instead of tinting.
|
||||
r3d_fog_density = fl(0.00024)
|
||||
r3d_fog_falloff = fl(0.002)
|
||||
r3d_fog_density = 0.00024
|
||||
r3d_fog_falloff = 0.002
|
||||
r3d_ready = true
|
||||
gpu_check("r3d init")
|
||||
}
|
||||
|
|
@ -162,9 +162,9 @@ function r3d_draw_sky() -> void {
|
|||
sky_bind_rot(p)
|
||||
sky_bind_lighting(p)
|
||||
u_mat4(gpu_uniform(p, "u_inv_vp"), cam_inv_vp)
|
||||
u_f(gpu_uniform(p, "u_sky_gain"), fl(0.95))
|
||||
u_f(gpu_uniform(p, "u_sky_gain"), 0.95)
|
||||
u_f(gpu_uniform(p, "u_sky_relight"), r3d_sky_relight)
|
||||
u_f(gpu_uniform(p, "u_sky_sat"), fl(1.35))
|
||||
u_f(gpu_uniform(p, "u_sky_sat"), 1.35)
|
||||
u_f(gpu_uniform(p, "u_time"), r3d_time)
|
||||
mesh_draw(sky_fullscreen)
|
||||
gpu_depth_write(true)
|
||||
|
|
@ -178,7 +178,7 @@ function r3d_present() -> void { gpu_present() }
|
|||
|
||||
# the drawable changed size: the camera's aspect and every screen-sized target follow
|
||||
function r3d_resize() -> void {
|
||||
cam_aspect = fr(gl_w, gl_h)
|
||||
cam_aspect = float(gl_w) / float(gl_h)
|
||||
cam_update()
|
||||
post_free()
|
||||
post_init(gl_w, gl_h)
|
||||
|
|
@ -186,7 +186,7 @@ function r3d_resize() -> void {
|
|||
print(`r3d: resized to {gl_w}x{gl_h}`)
|
||||
}
|
||||
var r3d_cam_log: int = -1
|
||||
function r3d_frame(time: int) -> void {
|
||||
function r3d_frame(time: float) -> void {
|
||||
gpu_glcheck_after("the time between frames")
|
||||
outline_frame()
|
||||
if not r3d_ready { return }
|
||||
|
|
@ -218,7 +218,7 @@ function r3d_frame(time: int) -> void {
|
|||
# camera that moves while the hiker stands still from a picture that shakes on its own
|
||||
if r3d_cam_log < 0 { r3d_cam_log = 0; if r3d_env_has("R3D_CAM_LOG") { r3d_cam_log = 1 } }
|
||||
if r3d_cam_log == 1 {
|
||||
print(`cam {r3d_test_frame} pos {f_to_int(f_mul(cam_pos[0], fi(1000)))} {f_to_int(f_mul(cam_pos[1], fi(1000)))} {f_to_int(f_mul(cam_pos[2], fi(1000)))} yaw {f_to_int(f_mul(cam_yaw, fi(1000)))} pitch {f_to_int(f_mul(cam_pitch, fi(1000)))} jitter {f_to_int(f_mul(gsl_jitter_x, fi(1000000)))} {f_to_int(f_mul(gsl_jitter_y, fi(1000000)))} render {post_w}x{post_h} reset {gsl_reset} evalok {gsl_eval_ok} fresh {gsl_fresh}`)
|
||||
print(`cam {r3d_test_frame} pos {int(cam_pos[0] * 1000.0)} {int(cam_pos[1] * 1000.0)} {int(cam_pos[2] * 1000.0)} yaw {int(cam_yaw * 1000.0)} pitch {int(cam_pitch * 1000.0)} jitter {int(gsl_jitter_x * 1000000.0)} {int(gsl_jitter_y * 1000000.0)} render {post_w}x{post_h} reset {gsl_reset} evalok {gsl_eval_ok} fresh {gsl_fresh}`)
|
||||
}
|
||||
# the height-field shadow rebakes as the light moves in steps (daylight), or with the sky yaw when there is no clock
|
||||
if (not day_on and ter_shadow_yaw != sky_yaw) or ter_shadow_gen != day_gen {
|
||||
|
|
|
|||
|
|
@ -11,30 +11,30 @@ property Impostor {
|
|||
albedo: int = 0,
|
||||
normal: int = 0,
|
||||
tiles: int = 16,
|
||||
radius: int = 0,
|
||||
height: int = 0
|
||||
radius: float = 0.0,
|
||||
height: float = 0.0
|
||||
}
|
||||
property Layer {
|
||||
model: Model,
|
||||
imp: Impostor,
|
||||
foliage: bool = false,
|
||||
wind: int = 0, # float bits
|
||||
flutter: int = 0, # float bits; per-leaf tremble (aspen), 0 = none
|
||||
tint: words,
|
||||
inst: words, # INST_FLOATS per instance
|
||||
wind: float = 0.0, # float bits
|
||||
flutter: float = 0.0, # float bits; per-leaf tremble (aspen), 0 = none
|
||||
tint: floats,
|
||||
inst: floats, # INST_FLOATS per instance
|
||||
count: int = 0,
|
||||
cap: int = 0,
|
||||
near: int = 0, # float bits; instances beyond it draw as impostors (or not at all)
|
||||
cull: int = 0, # float bits; instances beyond it are skipped (0 = never)
|
||||
near: float = 0.0, # float bits; instances beyond it draw as impostors (or not at all)
|
||||
cull: float = 0.0, # float bits; instances beyond it are skipped (0 = never)
|
||||
buf: int = 0,
|
||||
n_near: int = 0,
|
||||
imp_buf: int = 0,
|
||||
sh_buf: int = 0, # every instance, for shadow casting (no cull, no LOD split)
|
||||
n_sh: int = 0,
|
||||
n_far: int = 0,
|
||||
scratch: words,
|
||||
last_cam: words,
|
||||
rough: int = 0,
|
||||
scratch: floats,
|
||||
last_cam: floats,
|
||||
rough: float = 0.0,
|
||||
blade: bool = false,
|
||||
flower: bool = false,
|
||||
card: bool = false,
|
||||
|
|
@ -45,16 +45,16 @@ property Layer {
|
|||
view_gen: int = -1, # sc_view_gen this layer's partition was built for
|
||||
# static layers with many instances are sorted into a cell grid once, and only the
|
||||
# cells inside the view frustum (and within cull) are partitioned each frame
|
||||
gcell: int = 0, # cell size (float bits); 0 = no grid
|
||||
gx0: int = 0,
|
||||
gz0: int = 0,
|
||||
gcell: float = 0.0, # cell size (float bits); 0 = no grid
|
||||
gx0: float = 0.0,
|
||||
gz0: float = 0.0,
|
||||
gnx: int = 0,
|
||||
gnz: int = 0,
|
||||
gstart: words, # per cell: first index into gsorted (ncell + 1 entries)
|
||||
gsorted: words, # the instances, grouped by cell
|
||||
gymin: words, # per cell height range (float bits)
|
||||
gymax: words,
|
||||
vis: words, # the instances gathered from visible cells this frame
|
||||
gsorted: floats, # the instances, grouped by cell
|
||||
gymin: floats, # per cell height range (float bits)
|
||||
gymax: floats,
|
||||
vis: floats, # the instances gathered from visible cells this frame
|
||||
n_vis: int = 0,
|
||||
# A LOD chain: lods[k] is drawn for instances within lod_dist[k] (and beyond lod_dist[k-1]);
|
||||
# past the last level the impostor takes over (or, if the last distance is 0, the last
|
||||
|
|
@ -76,7 +76,7 @@ property Layer {
|
|||
g_base: words, # ... and its first vertex
|
||||
g_model: Model, # the merged meshes as a model the draws take (level 0's height)
|
||||
g_model_sh: Model, # the same with level 2's height, for the shadow LOD
|
||||
lod_dist: words,
|
||||
lod_dist: floats,
|
||||
lod_card: words,
|
||||
lod_buf: words,
|
||||
n_lod: words,
|
||||
|
|
@ -101,12 +101,12 @@ function model_cross_card() -> Model {
|
|||
var k = 0
|
||||
for q in 0 .. 2 {
|
||||
for c in 0 .. 4 {
|
||||
var sx = f_neg(F_HALF); var sy = F_ZERO; var u = F_ZERO; var vv = F_ZERO
|
||||
if c == 1 or c == 2 { sx = F_HALF; u = F_ONE }
|
||||
if c == 2 or c == 3 { sy = F_ONE; vv = F_ONE }
|
||||
if q == 0 { gl_put_bits(v, k, sx); gl_put_bits(v, k + 1, sy); gl_put_bits(v, k + 2, F_ZERO); gl_put_bits(v, k + 3, F_ZERO); gl_put_bits(v, k + 4, F_ZERO); gl_put_bits(v, k + 5, f_neg1()) }
|
||||
else { gl_put_bits(v, k, F_ZERO); gl_put_bits(v, k + 1, sy); gl_put_bits(v, k + 2, sx); gl_put_bits(v, k + 3, f_neg1()); gl_put_bits(v, k + 4, F_ZERO); gl_put_bits(v, k + 5, F_ZERO) }
|
||||
gl_put_bits(v, k + 6, u); gl_put_bits(v, k + 7, vv)
|
||||
var sx = -0.5; var sy = 0.0; var u = 0.0; var vv = 0.0
|
||||
if c == 1 or c == 2 { sx = 0.5; u = 1.0 }
|
||||
if c == 2 or c == 3 { sy = 1.0; vv = 1.0 }
|
||||
if q == 0 { gl_put_bits(v, k, float_bits(sx)); gl_put_bits(v, k + 1, float_bits(sy)); gl_put_bits(v, k + 2, float_bits(0.0)); gl_put_bits(v, k + 3, float_bits(0.0)); gl_put_bits(v, k + 4, float_bits(0.0)); gl_put_bits(v, k + 5, float_bits(-1.0)) }
|
||||
else { gl_put_bits(v, k, float_bits(0.0)); gl_put_bits(v, k + 1, float_bits(sy)); gl_put_bits(v, k + 2, float_bits(sx)); gl_put_bits(v, k + 3, float_bits(-1.0)); gl_put_bits(v, k + 4, float_bits(0.0)); gl_put_bits(v, k + 5, float_bits(0.0)) }
|
||||
gl_put_bits(v, k + 6, float_bits(u)); gl_put_bits(v, k + 7, float_bits(vv))
|
||||
k += 8
|
||||
}
|
||||
}
|
||||
|
|
@ -124,7 +124,7 @@ function model_cross_card() -> Model {
|
|||
if gltf_white == 0 { gltf_white = tex_solid(200, 200, 200, 255); gltf_flat = tex_solid(128, 128, 255, 255) }
|
||||
pr.diff = gltf_white; pr.nrm = gltf_flat; pr.arm = gltf_white
|
||||
push(model.prims, pr)
|
||||
model.radius = F_HALF; model.height = F_ONE; model.tris = 4
|
||||
model.radius = 0.5; model.height = 1.0; model.tris = 4
|
||||
return model
|
||||
}
|
||||
|
||||
|
|
@ -133,9 +133,9 @@ function model_cross_card() -> Model {
|
|||
# layer a flutter and its leaves tremble and flash their pale undersides; everything else
|
||||
# leaves it at zero. It is per layer rather than per instance because a species quakes or
|
||||
# it does not.
|
||||
function layer_flutter(l: Layer, v: int) -> void { l.flutter = v }
|
||||
function layer_cards(scan: Model, cap: int, wind: int, cull: int) -> Layer {
|
||||
let l = layer_new(model_cross_card(), cap, true, wind, F_ZERO, cull)
|
||||
function layer_flutter(l: Layer, v: float) -> void { l.flutter = v }
|
||||
function layer_cards(scan: Model, cap: int, wind: float, cull: float) -> Layer {
|
||||
let l = layer_new(model_cross_card(), cap, true, wind, 0.0, cull)
|
||||
l.card = true
|
||||
l.atlas = impostor_bake(scan, 1, 512, 512)
|
||||
return l
|
||||
|
|
@ -155,36 +155,36 @@ function model_lupine() -> Model {
|
|||
var k = 0
|
||||
var qi = 0
|
||||
for q in 0 .. nq {
|
||||
var w = fl(0.012); var y0 = F_ZERO; var y1 = fl(0.62); var ukind = F_ZERO
|
||||
var ang = F_ZERO
|
||||
var w = 0.012; var y0 = 0.0; var y1 = 0.62; var ukind = 0.0
|
||||
var ang = 0.0
|
||||
if q >= 2 and q < 26 {
|
||||
let tier = (q - 2) / 2
|
||||
let t = fr(tier, 12)
|
||||
w = f_mul(fl(0.05), f_sub(fl(1.1), t))
|
||||
y0 = f_add(fl(0.27), f_mul(t, fl(0.36)))
|
||||
y1 = f_add(y0, fl(0.045))
|
||||
ukind = F_ONE
|
||||
ang = f_mul(fr(tier, 12), fl(2.1))
|
||||
if (q & 1) == 1 { ang = f_add(ang, f_mul(F_PI, F_HALF)) }
|
||||
let t = float(tier) / 12.0
|
||||
w = 0.05 * (1.1 - t)
|
||||
y0 = 0.27 + t * 0.36
|
||||
y1 = y0 + 0.045
|
||||
ukind = 1.0
|
||||
ang = float(tier) / 12.0 * 2.1
|
||||
if (q & 1) == 1 { ang = ang + PI * 0.5 }
|
||||
} else if q >= 26 {
|
||||
# a rosette of three leaves near the ground
|
||||
w = fl(0.09); y0 = fl(0.02); y1 = fl(0.2); ukind = F_TWO
|
||||
ang = f_mul(fr(q - 26, 3), f_mul(F_TWO, F_PI))
|
||||
w = 0.09; y0 = 0.02; y1 = 0.2; ukind = 2.0
|
||||
ang = float(q - 26) / 3.0 * (2.0 * PI)
|
||||
} else {
|
||||
if (q & 1) == 1 { ang = f_add(ang, f_mul(F_PI, F_HALF)) }
|
||||
if (q & 1) == 1 { ang = ang + PI * 0.5 }
|
||||
}
|
||||
let cx = f_mul(f_cos(ang), w); let cz = f_mul(f_sin(ang), w)
|
||||
let cx = Math.cos(ang) * w; let cz = Math.sin(ang) * w
|
||||
for c in 0 .. 4 {
|
||||
var sx = f_neg1(); var sy = y0; var u = F_ZERO
|
||||
if c == 1 or c == 2 { sx = F_ONE; u = F_ONE }
|
||||
var sx = -1.0; var sy = y0; var u = 0.0
|
||||
if c == 1 or c == 2 { sx = 1.0; u = 1.0 }
|
||||
if c == 2 or c == 3 { sy = y1 }
|
||||
gl_put_bits(v, k, f_mul(cx, sx)); gl_put_bits(v, k + 1, sy); gl_put_bits(v, k + 2, f_mul(cz, sx))
|
||||
gl_put_bits(v, k + 3, f_neg(cz)); gl_put_bits(v, k + 4, fl(0.2)); gl_put_bits(v, k + 5, cx)
|
||||
gl_put_bits(v, k + 6, f_add(ukind, u))
|
||||
gl_put_bits(v, k, float_bits(cx * sx)); gl_put_bits(v, k + 1, float_bits(sy)); gl_put_bits(v, k + 2, float_bits(cz * sx))
|
||||
gl_put_bits(v, k + 3, float_bits(-cz)); gl_put_bits(v, k + 4, float_bits(0.2)); gl_put_bits(v, k + 5, float_bits(cx))
|
||||
gl_put_bits(v, k + 6, float_bits(ukind + u))
|
||||
var vv = sy
|
||||
if ukind == F_ONE { vv = f_div(f_sub(sy, fl(0.27)), fl(0.4)) }
|
||||
if ukind == F_TWO { vv = f_div(f_sub(sy, fl(0.02)), fl(0.18)) }
|
||||
gl_put_bits(v, k + 7, vv)
|
||||
if ukind == 1.0 { vv = (sy - 0.27) / 0.4 }
|
||||
if ukind == 2.0 { vv = (sy - 0.02) / 0.18 }
|
||||
gl_put_bits(v, k + 7, float_bits(vv))
|
||||
k += 8
|
||||
}
|
||||
let b = q * 4
|
||||
|
|
@ -202,7 +202,7 @@ function model_lupine() -> Model {
|
|||
if gltf_white == 0 { gltf_white = tex_solid(200, 200, 200, 255); gltf_flat = tex_solid(128, 128, 255, 255) }
|
||||
pr.diff = gltf_white; pr.nrm = gltf_flat; pr.arm = gltf_white
|
||||
push(model.prims, pr)
|
||||
model.radius = fl(0.08); model.height = fl(0.65); model.tris = nq * 2
|
||||
model.radius = 0.08; model.height = 0.65; model.tris = nq * 2
|
||||
return model
|
||||
}
|
||||
|
||||
|
|
@ -221,43 +221,43 @@ function model_lupine_dense() -> Model {
|
|||
var qi = 0
|
||||
seed(5)
|
||||
for q in 0 .. nq {
|
||||
var w = fl(0.008); var y0 = F_ZERO; var y1 = fl(0.66); var ukind = F_ZERO
|
||||
var ang = F_ZERO; var ox = F_ZERO; var oz = F_ZERO; var tilt = F_ZERO
|
||||
var w = 0.008; var y0 = 0.0; var y1 = 0.66; var ukind = 0.0
|
||||
var ang = 0.0; var ox = 0.0; var oz = 0.0; var tilt = 0.0
|
||||
if q >= 2 and q < 2 + nfl {
|
||||
let t = fr(q - 2, nfl)
|
||||
let yy = f_add(fl(0.28), f_mul(t, fl(0.4)))
|
||||
ang = f_mul(fi(q), fl(2.39996)) # golden angle spiral
|
||||
let rad = f_mul(fl(0.055), f_sub(fl(1.05), t))
|
||||
ox = f_mul(f_cos(ang), rad); oz = f_mul(f_sin(ang), rad)
|
||||
w = f_mul(fl(0.028), f_sub(fl(1.1), f_mul(t, fl(0.5))))
|
||||
y0 = f_sub(yy, fl(0.016)); y1 = f_add(yy, fl(0.016))
|
||||
ukind = F_ONE
|
||||
tilt = fl(0.6)
|
||||
let t = float(q - 2) / float(nfl)
|
||||
let yy = 0.28 + t * 0.4
|
||||
ang = float(q) * 2.39996 # golden angle spiral
|
||||
let rad = 0.055 * (1.05 - t)
|
||||
ox = Math.cos(ang) * rad; oz = Math.sin(ang) * rad
|
||||
w = 0.028 * (1.1 - t * 0.5)
|
||||
y0 = yy - 0.016; y1 = yy + 0.016
|
||||
ukind = 1.0
|
||||
tilt = 0.6
|
||||
} else if q >= 2 + nfl {
|
||||
w = fl(0.05); y0 = fl(0.03); y1 = fl(0.16); ukind = F_TWO
|
||||
ang = f_mul(fr(q - 2 - nfl, 5), f_mul(F_TWO, F_PI))
|
||||
ox = f_mul(f_cos(ang), fl(0.05)); oz = f_mul(f_sin(ang), fl(0.05))
|
||||
w = 0.05; y0 = 0.03; y1 = 0.16; ukind = 2.0
|
||||
ang = float(q - 2 - nfl) / 5.0 * (2.0 * PI)
|
||||
ox = Math.cos(ang) * 0.05; oz = Math.sin(ang) * 0.05
|
||||
} else {
|
||||
if (q & 1) == 1 { ang = f_mul(F_PI, F_HALF) }
|
||||
if (q & 1) == 1 { ang = PI * 0.5 }
|
||||
}
|
||||
# the quad faces outward (its normal along the spiral radius), leaning out by `tilt`
|
||||
let nx = f_cos(ang); let nz = f_sin(ang)
|
||||
let tx = f_neg(nz); let tz = nx # tangent (quad width direction)
|
||||
let nx = Math.cos(ang); let nz = Math.sin(ang)
|
||||
let tx = -nz; let tz = nx # tangent (quad width direction)
|
||||
for c in 0 .. 4 {
|
||||
var sx = f_neg1(); var sy = y0; var u = F_ZERO
|
||||
if c == 1 or c == 2 { sx = F_ONE; u = F_ONE }
|
||||
var sx = -1.0; var sy = y0; var u = 0.0
|
||||
if c == 1 or c == 2 { sx = 1.0; u = 1.0 }
|
||||
if c == 2 or c == 3 { sy = y1 }
|
||||
var lean = F_ZERO
|
||||
if c == 2 or c == 3 { lean = f_mul(tilt, w) }
|
||||
gl_put_bits(v, k, f_add(f_add(ox, f_mul(tx, f_mul(sx, w))), f_mul(nx, lean)))
|
||||
gl_put_bits(v, k + 1, sy)
|
||||
gl_put_bits(v, k + 2, f_add(f_add(oz, f_mul(tz, f_mul(sx, w))), f_mul(nz, lean)))
|
||||
gl_put_bits(v, k + 3, nx); gl_put_bits(v, k + 4, fl(0.35)); gl_put_bits(v, k + 5, nz)
|
||||
gl_put_bits(v, k + 6, f_add(ukind, u))
|
||||
var lean = 0.0
|
||||
if c == 2 or c == 3 { lean = tilt * w }
|
||||
gl_put_bits(v, k, float_bits(ox + tx * (sx * w) + nx * lean))
|
||||
gl_put_bits(v, k + 1, float_bits(sy))
|
||||
gl_put_bits(v, k + 2, float_bits(oz + tz * (sx * w) + nz * lean))
|
||||
gl_put_bits(v, k + 3, float_bits(nx)); gl_put_bits(v, k + 4, float_bits(0.35)); gl_put_bits(v, k + 5, float_bits(nz))
|
||||
gl_put_bits(v, k + 6, float_bits(ukind + u))
|
||||
var vv = sy
|
||||
if ukind == F_ONE { vv = f_div(f_sub(sy, fl(0.27)), fl(0.42)) }
|
||||
if ukind == F_TWO { vv = f_div(f_sub(sy, fl(0.03)), fl(0.19)) }
|
||||
gl_put_bits(v, k + 7, vv)
|
||||
if ukind == 1.0 { vv = (sy - 0.27) / 0.42 }
|
||||
if ukind == 2.0 { vv = (sy - 0.03) / 0.19 }
|
||||
gl_put_bits(v, k + 7, float_bits(vv))
|
||||
k += 8
|
||||
}
|
||||
let b = q * 4
|
||||
|
|
@ -275,7 +275,7 @@ function model_lupine_dense() -> Model {
|
|||
if gltf_white == 0 { gltf_white = tex_solid(200, 200, 200, 255); gltf_flat = tex_solid(128, 128, 255, 255) }
|
||||
pr.diff = gltf_white; pr.nrm = gltf_flat; pr.arm = gltf_white
|
||||
push(model.prims, pr)
|
||||
model.radius = fl(0.11); model.height = fl(0.68); model.tris = nq * 2
|
||||
model.radius = 0.11; model.height = 0.68; model.tris = nq * 2
|
||||
return model
|
||||
}
|
||||
|
||||
|
|
@ -289,17 +289,17 @@ function model_blade() -> Model {
|
|||
let v = gl_floats(rows * 2 * 8)
|
||||
var k = 0
|
||||
for r in 0 .. rows {
|
||||
let t = fr(r, rows - 1)
|
||||
let t = float(r) / float(rows - 1)
|
||||
# never a zero-width tip: a sliver triangle extrapolates its attributes wildly
|
||||
let taper = f_max(f_sub(F_ONE, f_mul(t, f_mul(t, f_sqrt(t)))), fl(0.12))
|
||||
let hw = f_mul(fl(0.05), taper)
|
||||
let bend = f_mul(f_mul(t, t), fl(0.28))
|
||||
let taper = Math.max(1.0 - t * (t * Math.sqrt(t)), 0.12)
|
||||
let hw = 0.05 * taper
|
||||
let bend = t * t * 0.28
|
||||
for sd in 0 .. 2 {
|
||||
var x = f_neg(hw)
|
||||
var x = -hw
|
||||
if sd == 1 { x = hw }
|
||||
gl_put_bits(v, k, x); gl_put_bits(v, k + 1, t); gl_put_bits(v, k + 2, bend)
|
||||
gl_put_bits(v, k + 3, F_ZERO); gl_put_bits(v, k + 4, fl(0.3)); gl_put_bits(v, k + 5, F_ONE)
|
||||
gl_put_bits(v, k + 6, fi(sd)); gl_put_bits(v, k + 7, t)
|
||||
gl_put_bits(v, k, float_bits(x)); gl_put_bits(v, k + 1, float_bits(t)); gl_put_bits(v, k + 2, float_bits(bend))
|
||||
gl_put_bits(v, k + 3, float_bits(0.0)); gl_put_bits(v, k + 4, float_bits(0.3)); gl_put_bits(v, k + 5, float_bits(1.0))
|
||||
gl_put_bits(v, k + 6, float_bits(float(sd))); gl_put_bits(v, k + 7, float_bits(t))
|
||||
k += 8
|
||||
}
|
||||
}
|
||||
|
|
@ -323,7 +323,7 @@ function model_blade() -> Model {
|
|||
if gltf_white == 0 { gltf_white = tex_solid(200, 200, 200, 255); gltf_flat = tex_solid(128, 128, 255, 255) }
|
||||
pr.diff = gltf_white; pr.nrm = gltf_flat; pr.arm = gltf_white
|
||||
push(model.prims, pr)
|
||||
model.radius = fl(0.05); model.height = F_ONE; model.tris = ni / 3
|
||||
model.radius = 0.05; model.height = 1.0; model.tris = ni / 3
|
||||
return model
|
||||
}
|
||||
|
||||
|
|
@ -335,9 +335,9 @@ var sc_prog_flower: int = 0
|
|||
var sc_prog_card: int = 0
|
||||
var sc_prog_card_shadow: int = 0
|
||||
var sc_prog_card_cheap: int = 0
|
||||
var sc_blade_base: words = null
|
||||
var sc_blade_tip: words = null
|
||||
var sc_blade_tint: words = null
|
||||
var sc_blade_base: floats = null
|
||||
var sc_blade_tip: floats = null
|
||||
var sc_blade_tint: floats = null
|
||||
var sc_prog_shadow: int = 0
|
||||
var sc_prog_shadow_wind: int = 0
|
||||
var sc_prog_shadow_fol: int = 0 # foliage meshes: alpha-tested casters
|
||||
|
|
@ -374,9 +374,9 @@ function scatter_init() -> void {
|
|||
sc_prog_card_shadow = r3d_program("model.vert", "model.frag", "#define SHADOW_PASS\n#define WIND\n#define CARD\n")
|
||||
sc_prog_card_cheap = r3d_program("model.vert", "model.frag", "#define CARD\n#define CHEAP\n")
|
||||
# a dry alpine meadow: brown-olive roots, straw with a little green at the tips
|
||||
sc_blade_base = v3_new(fl(0.045), fl(0.06), fl(0.025))
|
||||
sc_blade_tip = v3_new(fl(0.22), fl(0.27), fl(0.13))
|
||||
sc_blade_tint = v3_new(F_ONE, F_ONE, F_ONE)
|
||||
sc_blade_base = v3_new(0.045, 0.06, 0.025)
|
||||
sc_blade_tip = v3_new(0.22, 0.27, 0.13)
|
||||
sc_blade_tint = v3_new(1.0, 1.0, 1.0)
|
||||
sc_prog_shadow = r3d_program("model.vert", "shadow.frag", "#define SHADOW_PASS\n")
|
||||
sc_prog_shadow_wind = r3d_program("model.vert", "shadow.frag", "#define SHADOW_PASS\n#define WIND\n")
|
||||
sc_prog_shadow_fol = r3d_program("model.vert", "shadow.frag", "#define SHADOW_PASS\n#define WIND\n#define ALPHA_TEST\n")
|
||||
|
|
@ -388,8 +388,8 @@ function scatter_init() -> void {
|
|||
sc_card = mesh_card()
|
||||
# a single identity instance, for baking
|
||||
let one = gl_floats(INST_FLOATS)
|
||||
for i in 0 .. INST_FLOATS { gl_put_bits(one, i, F_ZERO) }
|
||||
gl_put_bits(one, 3, F_ONE); gl_put_bits(one, 5, F_ONE)
|
||||
for i in 0 .. INST_FLOATS { gl_put_bits(one, i, float_bits(0.0)) }
|
||||
gl_put_bits(one, 3, float_bits(1.0)); gl_put_bits(one, 5, float_bits(1.0))
|
||||
sc_ident_buf = gpu_buffer_new()
|
||||
gpu_buffer_upload(sc_ident_buf, INST_FLOATS * 4, one, GPU_STATIC)
|
||||
free(one)
|
||||
|
|
@ -404,7 +404,7 @@ function scatter_attach(m: Mesh, buf: int) -> void {
|
|||
gpu_mesh_done(m)
|
||||
}
|
||||
|
||||
function layer_new(model: Model, cap: int, foliage: bool, wind: int, near: int, cull: int) -> Layer {
|
||||
function layer_new(model: Model, cap: int, foliage: bool, wind: float, near: float, cull: float) -> Layer {
|
||||
let l = new Layer
|
||||
l.model = model
|
||||
l.cap = cap
|
||||
|
|
@ -412,24 +412,24 @@ function layer_new(model: Model, cap: int, foliage: bool, wind: int, near: int,
|
|||
l.wind = wind
|
||||
l.near = near
|
||||
l.cull = cull
|
||||
l.tint = v3_new(F_ONE, F_ONE, F_ONE)
|
||||
l.inst = words(cap * INST_FLOATS)
|
||||
l.scratch = words(cap * INST_FLOATS)
|
||||
l.last_cam = v3_new(fi(100000), F_ZERO, F_ZERO)
|
||||
l.tint = v3_new(1.0, 1.0, 1.0)
|
||||
l.inst = floats(cap * INST_FLOATS)
|
||||
l.scratch = floats(cap * INST_FLOATS)
|
||||
l.last_cam = v3_new(100000.0, 0.0, 0.0)
|
||||
l.buf = gpu_buffer_new()
|
||||
l.imp_buf = gpu_buffer_new()
|
||||
l.sh_buf = gpu_buffer_new()
|
||||
l.rough = F_ONE
|
||||
l.rough = 1.0
|
||||
for i in 0 .. len(model.prims) { scatter_attach(model.prims[i].mesh, l.buf) }
|
||||
push(sc_layers, l)
|
||||
return l
|
||||
}
|
||||
|
||||
function layer_add(l: Layer, x: int, y: int, z: int, scale: int, yaw: int, seed: int, wind: int) -> void {
|
||||
function layer_add(l: Layer, x: float, y: float, z: float, scale: float, yaw: float, seed: float, wind: float) -> void {
|
||||
if l.count >= l.cap { return }
|
||||
let o = l.count * INST_FLOATS
|
||||
l.inst[o] = x; l.inst[o + 1] = y; l.inst[o + 2] = z; l.inst[o + 3] = scale
|
||||
l.inst[o + 4] = f_sin(yaw); l.inst[o + 5] = f_cos(yaw); l.inst[o + 6] = seed; l.inst[o + 7] = wind
|
||||
l.inst[o + 4] = Math.sin(yaw); l.inst[o + 5] = Math.cos(yaw); l.inst[o + 6] = seed; l.inst[o + 7] = wind
|
||||
l.count += 1
|
||||
}
|
||||
|
||||
|
|
@ -439,7 +439,7 @@ var sc_dump_n: int = 0
|
|||
function impostor_bake(model: Model, tiles: int, tw: int, th: int) -> Impostor {
|
||||
let im = new Impostor
|
||||
im.tiles = tiles
|
||||
im.radius = f_mul(model.radius, fl(1.02))
|
||||
im.radius = model.radius * 1.02
|
||||
im.height = model.height
|
||||
let aw = tiles * tw
|
||||
im.albedo = tex_target(aw, th, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR)
|
||||
|
|
@ -462,24 +462,24 @@ function impostor_bake(model: Model, tiles: int, tw: int, th: int) -> Impostor {
|
|||
# the model's prims temporarily take the identity instance
|
||||
for i in 0 .. len(model.prims) { scatter_attach(model.prims[i].mesh, sc_ident_buf) }
|
||||
let view = m4_new(); let proj = m4_new()
|
||||
let eye = words(3); let at = words(3); let up = v3_new(F_ZERO, F_ONE, F_ZERO)
|
||||
let cy = f_add(model.ymin, f_mul(model.height, F_HALF))
|
||||
let eye = floats(3); let at = floats(3); let up = v3_new(0.0, 1.0, 0.0)
|
||||
let cy = model.ymin + model.height * 0.5
|
||||
let r = im.radius
|
||||
let hh = f_mul(model.height, F_HALF)
|
||||
let hh = model.height * 0.5
|
||||
var bake = sc_bake_prog
|
||||
if sc_bake_flower { bake = sc_bake_flower_prog }
|
||||
gpu_use_program(bake)
|
||||
for t in 0 .. tiles {
|
||||
let a = f_mul(f_mul(F_TWO, F_PI), fr(t, tiles))
|
||||
v3_set(at, F_ZERO, cy, F_ZERO)
|
||||
let a = 2.0 * PI * (float(t) / float(tiles))
|
||||
v3_set(at, 0.0, cy, 0.0)
|
||||
# a touch of elevation (the viewer usually looks slightly down at a tree)
|
||||
v3_set(eye, f_mul(f_sin(a), f_mul(r, fi(4))), f_add(cy, f_mul(r, fl(0.5))), f_neg(f_mul(f_cos(a), f_mul(r, fi(4)))))
|
||||
v3_set(eye, Math.sin(a) * (r * 4.0), cy + r * 0.5, -(Math.cos(a) * (r * 4.0)))
|
||||
m4_look_at(view, eye, at, up)
|
||||
m4_ortho(proj, f_neg(r), r, f_neg(hh), hh, fl(0.1), f_mul(r, fi(9)))
|
||||
m4_ortho(proj, -r, r, -hh, hh, 0.1, r * 9.0)
|
||||
u_mat4(gpu_uniform(bake, "u_view"), view)
|
||||
u_mat4(gpu_uniform(bake, "u_proj"), proj)
|
||||
u_f(gpu_uniform(bake, "u_wind"), F_ZERO)
|
||||
u_f(gpu_uniform(bake, "u_flutter"), F_ZERO)
|
||||
u_f(gpu_uniform(bake, "u_wind"), 0.0)
|
||||
u_f(gpu_uniform(bake, "u_flutter"), 0.0)
|
||||
gpu_viewport(t * tw, 0, tw, th)
|
||||
for i in 0 .. len(model.prims) {
|
||||
let pr = model.prims[i]
|
||||
|
|
@ -510,10 +510,10 @@ function impostor_bake(model: Model, tiles: int, tw: int, th: int) -> Impostor {
|
|||
|
||||
# Give a layer a LOD chain. `dists` (float bits) are the outer distances of each level;
|
||||
# the last one becomes the layer's `near` so the impostor (if any) starts there.
|
||||
function layer_set_lods(l: Layer, models: []Model, dists: words) -> void {
|
||||
function layer_set_lods(l: Layer, models: []Model, dists: floats) -> void {
|
||||
l.lods = models
|
||||
l.n_lods = len(models)
|
||||
l.lod_dist = words(l.n_lods); l.lod_card = words(l.n_lods); l.lod_buf = words(l.n_lods); l.n_lod = words(l.n_lods)
|
||||
l.lod_dist = floats(l.n_lods); l.lod_card = words(l.n_lods); l.lod_buf = words(l.n_lods); l.n_lod = words(l.n_lods)
|
||||
for k in 0 .. l.n_lods {
|
||||
l.lod_dist[k] = dists[k]; l.lod_card[k] = 0; l.n_lod[k] = 0
|
||||
l.lod_buf[k] = gpu_buffer_new()
|
||||
|
|
@ -537,11 +537,11 @@ function layer_set_impostor(l: Layer, im: Impostor) -> void {
|
|||
# camera moved 1.5 m or turned about 2.5 degrees. Everything culled by the frustum keys
|
||||
# off this one counter, so a turn re-gathers the streams and the grids together.
|
||||
var sc_view_gen: int = 1
|
||||
var sc_view_pos: words = null
|
||||
var sc_view_fwd: words = null
|
||||
var sc_view_pos: floats = null
|
||||
var sc_view_fwd: floats = null
|
||||
function scatter_begin_frame() -> void {
|
||||
if sc_view_pos == null { sc_view_pos = v3_new(fi(100000), F_ZERO, F_ZERO); sc_view_fwd = v3_new(F_ZERO, F_ZERO, f_neg(F_ONE)) }
|
||||
if f_gt(v3_dist(sc_view_pos, cam_pos), fl(1.5)) or f_ls(v3_dot(sc_view_fwd, cam_fwd), fl(0.999)) {
|
||||
if sc_view_pos == null { sc_view_pos = v3_new(100000.0, 0.0, 0.0); sc_view_fwd = v3_new(0.0, 0.0, -1.0) }
|
||||
if v3_dist(sc_view_pos, cam_pos) > 1.5 or v3_dot(sc_view_fwd, cam_fwd) < 0.999 {
|
||||
sc_view_gen += 1
|
||||
v3_copy(sc_view_pos, cam_pos)
|
||||
v3_copy(sc_view_fwd, cam_fwd)
|
||||
|
|
@ -707,12 +707,12 @@ function layer_gpu_prepare(l: Layer) -> bool {
|
|||
function layer_gpu_cull(l: Layer) -> void {
|
||||
let pr = words(36)
|
||||
for i in 0 .. 36 { pr[i] = 0 }
|
||||
if cam_planes != null { for i in 0 .. 16 { pr[i] = cam_planes[i] } }
|
||||
pr[16] = cam_pos[0]; pr[17] = cam_pos[1]; pr[18] = cam_pos[2]; pr[19] = l.cull
|
||||
for k in 0 .. l.n_lods { pr[20 + k] = l.lod_dist[k]; pr[24 + k] = len(l.lods[k].prims) }
|
||||
if cam_planes != null { for i in 0 .. 16 { pr[i] = float_bits(cam_planes[i]) } }
|
||||
pr[16] = float_bits(cam_pos[0]); pr[17] = float_bits(cam_pos[1]); pr[18] = float_bits(cam_pos[2]); pr[19] = float_bits(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
|
||||
pr[32] = f_mul(l.lods[0].height, F_TWO); pr[33] = fi(4)
|
||||
pr[32] = float_bits(l.lods[0].height * 2.0); pr[33] = float_bits(4.0)
|
||||
let bufs = words(4)
|
||||
bufs[0] = l.g_src; bufs[1] = l.g_dst; bufs[2] = l.g_cmds; bufs[3] = l.g_counts
|
||||
gpu_dispatch(sc_cull_prog, pr, 144, bufs, 1)
|
||||
|
|
@ -722,36 +722,36 @@ function layer_gpu_cull(l: Layer) -> void {
|
|||
# Sort a static layer's instances into square cells (call once, after placement; a
|
||||
# large layer that was never gridded gets a 96 m grid on its first update). The
|
||||
# shadow buffer is uploaded here once — casters are never culled by the view.
|
||||
function layer_grid_build(l: Layer, cell: int) -> void {
|
||||
function layer_grid_build(l: Layer, cell: float) -> void {
|
||||
if l.count == 0 { return }
|
||||
var minx = l.inst[0]; var maxx = minx; var minz = l.inst[2]; var maxz = minz
|
||||
for i in 0 .. l.count {
|
||||
let o = i * INST_FLOATS
|
||||
minx = f_min(minx, l.inst[o]); maxx = f_max(maxx, l.inst[o])
|
||||
minz = f_min(minz, l.inst[o + 2]); maxz = f_max(maxz, l.inst[o + 2])
|
||||
minx = Math.min(minx, l.inst[o]); maxx = Math.max(maxx, l.inst[o])
|
||||
minz = Math.min(minz, l.inst[o + 2]); maxz = Math.max(maxz, l.inst[o + 2])
|
||||
}
|
||||
l.gcell = cell; l.gx0 = minx; l.gz0 = minz
|
||||
l.gnx = f_to_int(f_div(f_sub(maxx, minx), cell)) + 1
|
||||
l.gnz = f_to_int(f_div(f_sub(maxz, minz), cell)) + 1
|
||||
l.gnx = int((maxx - minx) / cell) + 1
|
||||
l.gnz = int((maxz - minz) / cell) + 1
|
||||
let ncell = l.gnx * l.gnz
|
||||
l.gstart = words(ncell + 1)
|
||||
l.gymin = words(ncell); l.gymax = words(ncell)
|
||||
l.gymin = floats(ncell); l.gymax = floats(ncell)
|
||||
let cellof = words(l.count)
|
||||
for c in 0 .. ncell + 1 { l.gstart[c] = 0 }
|
||||
for i in 0 .. l.count {
|
||||
let o = i * INST_FLOATS
|
||||
let ix = f_to_int(f_div(f_sub(l.inst[o], minx), cell))
|
||||
let iz = f_to_int(f_div(f_sub(l.inst[o + 2], minz), cell))
|
||||
let ix = int((l.inst[o] - minx) / cell)
|
||||
let iz = int((l.inst[o + 2] - minz) / cell)
|
||||
let c = iz * l.gnx + ix
|
||||
cellof[i] = c
|
||||
if l.gstart[c + 1] == 0 { l.gymin[c] = l.inst[o + 1]; l.gymax[c] = l.inst[o + 1] }
|
||||
else { l.gymin[c] = f_min(l.gymin[c], l.inst[o + 1]); l.gymax[c] = f_max(l.gymax[c], l.inst[o + 1]) }
|
||||
else { l.gymin[c] = Math.min(l.gymin[c], l.inst[o + 1]); l.gymax[c] = Math.max(l.gymax[c], l.inst[o + 1]) }
|
||||
l.gstart[c + 1] += 1
|
||||
}
|
||||
for c in 0 .. ncell { l.gstart[c + 1] += l.gstart[c] }
|
||||
let fill = words(ncell)
|
||||
for c in 0 .. ncell { fill[c] = l.gstart[c] }
|
||||
l.gsorted = words(l.count * INST_FLOATS)
|
||||
l.gsorted = floats(l.count * INST_FLOATS)
|
||||
for i in 0 .. l.count {
|
||||
let c = cellof[i]
|
||||
let q = fill[c] * INST_FLOATS
|
||||
|
|
@ -760,7 +760,7 @@ function layer_grid_build(l: Layer, cell: int) -> void {
|
|||
for k in 0 .. INST_FLOATS { l.gsorted[q + k] = l.inst[o + k] }
|
||||
}
|
||||
free(cellof); free(fill)
|
||||
if l.vis == null { l.vis = words(l.cap * INST_FLOATS) }
|
||||
if l.vis == null { l.vis = floats(l.cap * INST_FLOATS) }
|
||||
l.n_sh = l.count
|
||||
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, l.inst, GPU_STATIC)
|
||||
}
|
||||
|
|
@ -768,24 +768,24 @@ function layer_grid_build(l: Layer, cell: int) -> void {
|
|||
# gather the instances of the cells the camera can see (and that are within cull)
|
||||
function layer_grid_gather(l: Layer) -> void {
|
||||
let cell = l.gcell
|
||||
let half = f_mul(cell, F_HALF)
|
||||
let reach = f_add(l.cull, f_mul(cell, fl(0.71)))
|
||||
let half = cell * 0.5
|
||||
let reach = l.cull + cell * 0.71
|
||||
var n = 0
|
||||
for iz in 0 .. l.gnz {
|
||||
let wz = f_add(f_add(l.gz0, f_mul(fi(iz), cell)), half)
|
||||
let wz = l.gz0 + float(iz) * cell + half
|
||||
for ix in 0 .. l.gnx {
|
||||
let c = iz * l.gnx + ix
|
||||
let cnt = l.gstart[c + 1] - l.gstart[c]
|
||||
if cnt == 0 { continue }
|
||||
let wx = f_add(f_add(l.gx0, f_mul(fi(ix), cell)), half)
|
||||
if l.cull != 0 {
|
||||
let dx = f_sub(wx, cam_pos[0]); let dz = f_sub(wz, cam_pos[2])
|
||||
if f_gt(f_sqrt(f_add(f_mul(dx, dx), f_mul(dz, dz))), reach) { continue }
|
||||
let wx = l.gx0 + float(ix) * cell + half
|
||||
if l.cull != 0.0 {
|
||||
let dx = wx - cam_pos[0]; let dz = wz - cam_pos[2]
|
||||
if Math.sqrt(dx * dx + dz * dz) > reach { continue }
|
||||
}
|
||||
let hy = f_mul(f_sub(l.gymax[c], l.gymin[c]), F_HALF)
|
||||
let cy = f_add(l.gymin[c], hy)
|
||||
let hy = (l.gymax[c] - l.gymin[c]) * 0.5
|
||||
let cy = l.gymin[c] + hy
|
||||
# pad by the tallest instance (scale 2 of the model's height) so crowns at the frame's edge stay
|
||||
let r = f_add(f_sqrt(f_add(f_mul(f_mul(half, half), F_TWO), f_mul(hy, hy))), f_add(f_mul(l.model.height, F_TWO), fi(4)))
|
||||
let r = Math.sqrt(half * half * 2.0 + hy * hy) + (l.model.height * 2.0 + 4.0)
|
||||
if not cam_sphere_visible(wx, cy, wz, r) { continue }
|
||||
mem_copy(mem_off(l.vis, n * INST_FLOATS * 4), mem_off(l.gsorted, l.gstart[c] * INST_FLOATS * 4), cnt * INST_FLOATS * 4)
|
||||
n += cnt
|
||||
|
|
@ -796,22 +796,22 @@ function layer_grid_gather(l: Layer) -> void {
|
|||
|
||||
# Sort the gathered instances into their LOD levels (counting sort into the scratch),
|
||||
# the impostor bucket last, and upload one buffer per level.
|
||||
function layer_partition_lods(l: Layer, src: words, total: int) -> void {
|
||||
function layer_partition_lods(l: Layer, src: floats, total: int) -> void {
|
||||
let n = l.n_lods
|
||||
let counts = words(n + 2)
|
||||
for k in 0 .. n + 2 { counts[k] = 0 }
|
||||
let cull2 = f_mul(l.cull, l.cull)
|
||||
let open = l.lod_dist[n - 1] == 0 # the last level runs out to the cull distance
|
||||
let cull2 = l.cull * l.cull
|
||||
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 = f_sub(src[o], cam_pos[0]); let dz = f_sub(src[o + 2], cam_pos[2])
|
||||
let d2 = f_add(f_mul(dx, dx), f_mul(dz, dz))
|
||||
let dx = src[o] - cam_pos[0]; let dz = src[o + 2] - cam_pos[2]
|
||||
let d2 = dx * dx + dz * dz
|
||||
var lv = n + 1 # n + 1 = dropped
|
||||
if l.cull == 0 or not f_gt(d2, cull2) {
|
||||
let d = f_sqrt(d2)
|
||||
if l.cull == 0.0 or not (d2 > cull2) {
|
||||
let d = Math.sqrt(d2)
|
||||
lv = n # n = the impostor bucket
|
||||
var k = 0
|
||||
while k < n { if l.lod_dist[k] != 0 and f_ls(d, l.lod_dist[k]) { lv = k; k = n } else { k += 1 } }
|
||||
while k < n { if l.lod_dist[k] != 0.0 and d < l.lod_dist[k] { lv = k; k = n } else { k += 1 } }
|
||||
if lv == n and open { lv = n - 1 }
|
||||
if lv == n and l.imp == null { lv = n + 1 }
|
||||
}
|
||||
|
|
@ -841,12 +841,12 @@ function layer_partition_lods(l: Layer, src: words, total: int) -> void {
|
|||
}
|
||||
l.n_near = counts[0]
|
||||
l.n_far = counts[n]
|
||||
if sc_dbg_lod and total > 1000 { print(`lod partition: total {total} dropped {counts[n + 1]} far {counts[n]} l0 {counts[0]} l1 {counts[1]} l2 {counts[2]} l3 {counts[3]} dist0 {f_fx(l.lod_dist[0])} dist3 {f_fx(l.lod_dist[n - 1])} cull {f_fx(l.cull)} cam {f_fx(cam_pos[0])} {f_fx(cam_pos[2])} first {f_fx(src[0])} {f_fx(src[2])}`) }
|
||||
if sc_dbg_lod and total > 1000 { print(`lod partition: total {total} dropped {counts[n + 1]} far {counts[n]} l0 {counts[0]} l1 {counts[1]} l2 {counts[2]} l3 {counts[3]} dist0 {fixed(l.lod_dist[0])} dist3 {fixed(l.lod_dist[n - 1])} cull {fixed(l.cull)} cam {fixed(cam_pos[0])} {fixed(cam_pos[2])} first {fixed(src[0])} {fixed(src[2])}`) }
|
||||
if l.n_far > 0 {
|
||||
gpu_buffer_upload(l.imp_buf, l.n_far * INST_FLOATS * 4, mem_off(tmp, start[n] * INST_FLOATS * 4), GPU_DYNAMIC)
|
||||
}
|
||||
# casters: the whole (gathered) set from the shadow buffer, unless the impostor casts
|
||||
if l.gcell == 0 {
|
||||
if l.gcell == 0.0 {
|
||||
l.n_sh = total
|
||||
if total > 0 { gpu_buffer_upload(l.sh_buf, total * INST_FLOATS * 4, src, GPU_DYNAMIC) }
|
||||
}
|
||||
|
|
@ -864,18 +864,18 @@ function layer_update(l: Layer) -> void {
|
|||
let n_lu = l.count
|
||||
# A streamed layer's instances were already gathered per visible chunk: no split, no
|
||||
# per-instance loop — one upload, and the same buffer casts its shadows.
|
||||
if l.streamed and l.imp == null and l.near == 0 and l.n_lods <= 1 {
|
||||
if l.streamed and l.imp == null and l.near == 0.0 and l.n_lods <= 1 {
|
||||
l.n_near = l.count; l.n_far = 0; l.n_sh = l.count
|
||||
gpu_buffer_upload(l.buf, l.count * INST_FLOATS * 4, l.inst, GPU_DYNAMIC)
|
||||
prof_layer_add(gl_now_us() - t_lu, l.count * INST_FLOATS * 4)
|
||||
return
|
||||
}
|
||||
if l.gcell == 0 and not l.streamed and l.count > 2000 { layer_grid_build(l, fi(96)) }
|
||||
if l.gcell == 0.0 and not l.streamed and l.count > 2000 { layer_grid_build(l, 96.0) }
|
||||
var src = l.inst
|
||||
var total = l.count
|
||||
if l.gcell != 0 { layer_grid_gather(l); src = l.vis; total = l.n_vis }
|
||||
let near2 = f_mul(l.near, l.near)
|
||||
let cull2 = f_mul(l.cull, l.cull)
|
||||
if l.gcell != 0.0 { layer_grid_gather(l); src = l.vis; total = l.n_vis }
|
||||
let near2 = l.near * l.near
|
||||
let cull2 = l.cull * l.cull
|
||||
var nn = 0
|
||||
var nf = 0
|
||||
let far_off = l.cap * INST_FLOATS # far instances fill the scratch from its end backwards
|
||||
|
|
@ -888,11 +888,11 @@ function layer_update(l: Layer) -> void {
|
|||
var i = 0
|
||||
while i < total {
|
||||
let o = i * INST_FLOATS
|
||||
let dx = f_sub(src[o], cam_pos[0])
|
||||
let dz = f_sub(src[o + 2], cam_pos[2])
|
||||
let d2 = f_add(f_mul(dx, dx), f_mul(dz, dz))
|
||||
if l.cull != 0 and f_gt(d2, cull2) { i += 1; continue }
|
||||
if l.near == 0 or f_ls(d2, near2) {
|
||||
let dx = src[o] - cam_pos[0]
|
||||
let dz = src[o + 2] - cam_pos[2]
|
||||
let d2 = dx * dx + dz * dz
|
||||
if l.cull != 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] }
|
||||
nn += 1
|
||||
|
|
@ -905,15 +905,15 @@ function layer_update(l: Layer) -> void {
|
|||
}
|
||||
l.n_near = nn
|
||||
l.n_far = nf
|
||||
if l.imp != null and nn > 0 and sc_debug_dump { print(`near full-mesh instances: {nn} (first at {f_fx(tmp[0])} {f_fx(tmp[1])} {f_fx(tmp[2])})`) }
|
||||
if l.imp != null and nn > 0 and sc_debug_dump { print(`near full-mesh instances: {nn} (first at {fixed(tmp[0])} {fixed(tmp[1])} {fixed(tmp[2])})`) }
|
||||
if sc_debug_dump and l.imp != null {
|
||||
print(`layer: near {nn} far {nf}`)
|
||||
for k in 0 .. nn { let q = k * INST_FLOATS; print(` near {f_fx(tmp[q])} {f_fx(tmp[q + 1])} {f_fx(tmp[q + 2])} s {f_fx(tmp[q + 3])}`) }
|
||||
for k in 0 .. nn { let q = k * INST_FLOATS; print(` near {fixed(tmp[q])} {fixed(tmp[q + 1])} {fixed(tmp[q + 2])} s {fixed(tmp[q + 3])}`) }
|
||||
}
|
||||
# Every instance, unculled and unsplit, for the shadow pass. What the camera draws is
|
||||
# allowed to change with distance; what casts must not, or shadows blink in and out as
|
||||
# you walk. This is the whole set, drawn one way, into every cascade.
|
||||
if l.gcell == 0 {
|
||||
if l.gcell == 0.0 {
|
||||
l.n_sh = l.count
|
||||
if l.count > 0 {
|
||||
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, l.inst, GPU_DYNAMIC)
|
||||
|
|
@ -934,7 +934,7 @@ function layer_program(l: Layer, shadow: bool, card: bool) -> int {
|
|||
}
|
||||
if shadow {
|
||||
if l.foliage and not l.blade and not l.flower { return sc_prog_shadow_fol }
|
||||
if l.wind != 0 { return sc_prog_shadow_wind }
|
||||
if l.wind != 0.0 { return sc_prog_shadow_wind }
|
||||
return sc_prog_shadow
|
||||
}
|
||||
if l.blade { return sc_prog_blade }
|
||||
|
|
@ -943,7 +943,7 @@ function layer_program(l: Layer, shadow: bool, card: bool) -> int {
|
|||
if sc_prepass and sc_prog_fol_eq != 0 { return sc_prog_fol_eq }
|
||||
return sc_prog_fol
|
||||
}
|
||||
if l.wind != 0 { return sc_prog_wind }
|
||||
if l.wind != 0.0 { return sc_prog_wind }
|
||||
return sc_prog
|
||||
}
|
||||
|
||||
|
|
@ -951,7 +951,7 @@ var sc_dbg_blade: int = 0
|
|||
# R3D_LODDBG=1 tints each LOD level (red, green, blue, yellow) and impostors magenta
|
||||
var sc_dbg_level: int = -1
|
||||
var sc_dbg_lod: bool = false
|
||||
var sc_dbg_tint: words = null
|
||||
var sc_dbg_tint: floats = null
|
||||
# Can level k's casters (its instances lie between the previous level's distance and its own) put a
|
||||
# shadow on anything the cascade being rendered covers? A receiver in that slice of view depth
|
||||
# [near, far] stands between near - dy and far * K metres away on the ground: dy is the camera's height
|
||||
|
|
@ -963,37 +963,37 @@ var sc_dbg_tint: words = null
|
|||
# level into every cascade, for comparing.
|
||||
var sc_cast_all: int = -1
|
||||
var sc_cast_gen: int = -1
|
||||
var sc_cast_dy: int = 0
|
||||
var sc_cast_k: int = 0
|
||||
var sc_cast_dy: float = 0.0
|
||||
var sc_cast_k: float = 0.0
|
||||
function layer_level_casts_here(l: Layer, k: int) -> bool {
|
||||
if l.lod_dist == null { return true }
|
||||
var dmin = F_ZERO
|
||||
var dmin = 0.0
|
||||
if k > 0 { dmin = l.lod_dist[k - 1] }
|
||||
return cast_band_reaches(dmin, l.lod_dist[k], l.lods[k].height)
|
||||
}
|
||||
# Can something standing between dmin and dmax metres from the camera (dmax 0: no outer limit), this
|
||||
# tall, put a shadow on anything the cascade being rendered covers? The flowers' levels ask it
|
||||
# (layer_level_casts_here), and so does every actor (actor_draw_casters).
|
||||
function cast_band_reaches(dmin: int, dmax: int, height: int) -> bool {
|
||||
function cast_band_reaches(dmin: float, dmax: float, height: float) -> bool {
|
||||
if sc_cast_all < 0 { sc_cast_all = 0; if r3d_env_has("R3D_CAST_ALL") { sc_cast_all = 1 } }
|
||||
if sc_cast_all == 1 or sh_split == null { return true }
|
||||
if sc_cast_gen != sc_view_gen {
|
||||
sc_cast_gen = sc_view_gen
|
||||
sc_cast_dy = f_add(f_abs(f_sub(cam_pos[1], terrain_height(cam_pos[0], cam_pos[2]))), fi(5))
|
||||
let half = f_mul(cam_fov, F_HALF)
|
||||
let t = f_div(f_sin(half), f_cos(half))
|
||||
let ta = f_mul(t, cam_aspect)
|
||||
sc_cast_k = f_mul(f_sqrt(f_add(F_ONE, f_add(f_mul(t, t), f_mul(ta, ta)))), fl(1.1))
|
||||
sc_cast_dy = Math.abs(cam_pos[1] - terrain_height(cam_pos[0], cam_pos[2])) + 5.0
|
||||
let half = cam_fov * 0.5
|
||||
let t = Math.sin(half) / Math.cos(half)
|
||||
let ta = t * cam_aspect
|
||||
sc_cast_k = Math.sqrt(1.0 + (t * t + ta * ta)) * 1.1
|
||||
}
|
||||
let c = sh_cascade
|
||||
var near = cam_near
|
||||
# sunShadow cross-fades into this cascade from 0.85 of the previous one's split (lighting.glsl), so
|
||||
# its receivers start there, not at the split: starting at the split changed 19 pixels in town
|
||||
if c > 0 { near = f_mul(sh_split[c - 1], fl(0.85)) }
|
||||
if c > 0 { near = sh_split[c - 1] * 0.85 }
|
||||
let far = sh_split[c]
|
||||
let reach = f_max(f_min(f_mul(height, fi(6)), fi(40)), fi(8))
|
||||
if dmax != 0 and f_ls(f_add(f_add(dmax, reach), sc_cast_dy), near) { return false }
|
||||
if f_gt(f_sub(dmin, reach), f_mul(far, sc_cast_k)) { return false }
|
||||
let reach = Math.max(Math.min(height * 6.0, 40.0), 8.0)
|
||||
if dmax != 0.0 and dmax + reach + sc_cast_dy < near { return false }
|
||||
if dmin - reach > far * sc_cast_k { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -1001,7 +1001,7 @@ function cast_band_reaches(dmin: int, dmax: int, height: int) -> bool {
|
|||
# partition out of l.buf. A layer with no impostor (the tree crowns' branch cards) has
|
||||
# no cheap stand-in to cast from, so without this its shadow simply began at the near
|
||||
# distance — which is the crown shadow that appeared as you walked up to a tree.
|
||||
function layer_draw_near(l: Layer, shadow: bool, light_vp: words, full: bool) -> void {
|
||||
function layer_draw_near(l: Layer, shadow: bool, light_vp: floats, full: bool) -> void {
|
||||
if l.n_lods > 1 and l.g_on and not shadow {
|
||||
# one draw per material covering all of its levels (the merged meshes)
|
||||
sc_ind_base = 0; sc_ind_n = l.n_lods
|
||||
|
|
@ -1026,26 +1026,26 @@ function layer_draw_near(l: Layer, shadow: bool, light_vp: words, full: bool) ->
|
|||
}
|
||||
|
||||
# draw `cnt` instances of `model` out of instance buffer `vb`, as a mesh or as the layer's card
|
||||
function layer_draw_model(l: Layer, model: Model, vb: int, cnt: int, card: bool, shadow: bool, light_vp: words) -> void {
|
||||
function layer_draw_model(l: Layer, model: Model, vb: int, cnt: int, card: bool, shadow: bool, light_vp: floats) -> void {
|
||||
if cnt == 0 { return }
|
||||
let p = layer_program(l, shadow, card)
|
||||
gpu_use_program(p)
|
||||
# over the prepass: only the fragment the prepass kept, at exactly its depth (a texel
|
||||
# it cut would otherwise pass LEQUAL over the terrain behind and draw the quad solid)
|
||||
if p == sc_prog_fol_eq { gpu_depth_func(GL_EQUAL) }
|
||||
var ground = F_ZERO
|
||||
if l.grounded { ground = F_ONE }
|
||||
var ground = 0.0
|
||||
if l.grounded { ground = 1.0 }
|
||||
u_f(gpu_uniform(p, "u_ground"), ground)
|
||||
if l.grounded { terrain_bind_height(p) }
|
||||
u_f(gpu_uniform(p, "u_time"), r3d_time)
|
||||
u_f(gpu_uniform(p, "u_wind"), l.wind)
|
||||
u_f(gpu_uniform(p, "u_flutter"), l.flutter)
|
||||
var mh = F_ZERO
|
||||
var mh = 0.0
|
||||
if not card and l.foliage { mh = model.height }
|
||||
u_f(gpu_uniform(p, "u_model_h"), mh)
|
||||
if not card and l.foliage and not shadow and sc_a2c { gpu_alpha_to_coverage(true) }
|
||||
if card {
|
||||
u_f(gpu_uniform(p, "u_card_w"), f_mul(l.atlas.radius, F_TWO)); u_f(gpu_uniform(p, "u_card_h"), l.atlas.height)
|
||||
u_f(gpu_uniform(p, "u_card_w"), l.atlas.radius * 2.0); u_f(gpu_uniform(p, "u_card_h"), l.atlas.height)
|
||||
r3d_bind_2d(p, "u_diff", 0, l.atlas.albedo)
|
||||
r3d_bind_2d(p, "u_nrm", 1, l.atlas.normal)
|
||||
gpu_alpha_to_coverage(true)
|
||||
|
|
@ -1056,10 +1056,10 @@ function layer_draw_model(l: Layer, model: Model, vb: int, cnt: int, card: bool,
|
|||
u_mat4(gpu_uniform(p, "u_proj"), cam_proj)
|
||||
u_v3(gpu_uniform(p, "u_tint"), l.tint)
|
||||
if sc_dbg_lod and sc_dbg_level >= 0 {
|
||||
if sc_dbg_tint == null { sc_dbg_tint = v3_new(F_ONE, F_ONE, F_ONE) }
|
||||
if sc_dbg_tint == null { sc_dbg_tint = v3_new(1.0, 1.0, 1.0) }
|
||||
let k = sc_dbg_level
|
||||
var r = F_ZERO; var g = F_ZERO; var b = F_ZERO
|
||||
if k == 0 { r = fi(3) } else if k == 1 { g = fi(3) } else if k == 2 { b = fi(3) } else { r = fi(3); g = fi(3) }
|
||||
var r = 0.0; var g = 0.0; var b = 0.0
|
||||
if k == 0 { r = 3.0 } else if k == 1 { g = 3.0 } else if k == 2 { b = 3.0 } else { r = 3.0; g = 3.0 }
|
||||
v3_set(sc_dbg_tint, r, g, b)
|
||||
u_v3(gpu_uniform(p, "u_tint"), sc_dbg_tint)
|
||||
}
|
||||
|
|
@ -1069,7 +1069,7 @@ function layer_draw_model(l: Layer, model: Model, vb: int, cnt: int, card: bool,
|
|||
sky_bind_lighting(p)
|
||||
shadow_bind(p)
|
||||
fog_bind(p)
|
||||
if l.foliage { u_f(gpu_uniform(p, "u_spec_scale"), fl(0.05)) }
|
||||
if l.foliage { u_f(gpu_uniform(p, "u_spec_scale"), 0.05) }
|
||||
}
|
||||
gpu_cull(false)
|
||||
for i in 0 .. len(model.prims) {
|
||||
|
|
@ -1086,7 +1086,7 @@ function layer_draw_model(l: Layer, model: Model, vb: int, cnt: int, card: bool,
|
|||
if p == sc_prog_fol_eq { gpu_depth_func(GL_LESS) }
|
||||
}
|
||||
|
||||
function layer_draw_far(l: Layer, shadow: bool, light_vp: words) -> void {
|
||||
function layer_draw_far(l: Layer, shadow: bool, light_vp: floats) -> void {
|
||||
if l.imp == null or (l.n_far == 0 and not l.g_on) { return }
|
||||
var p = sc_imp_prog
|
||||
if shadow { p = sc_imp_prog_shadow }
|
||||
|
|
@ -1094,23 +1094,23 @@ function layer_draw_far(l: Layer, shadow: bool, light_vp: words) -> void {
|
|||
let im = l.imp
|
||||
u_f(gpu_uniform(p, "u_radius"), im.radius)
|
||||
u_f(gpu_uniform(p, "u_height"), im.height)
|
||||
u_f(gpu_uniform(p, "u_tiles"), fi(im.tiles))
|
||||
u_f(gpu_uniform(p, "u_tiles"), float(im.tiles))
|
||||
r3d_bind_2d(p, "u_atlas_albedo", 0, im.albedo)
|
||||
if shadow {
|
||||
u_mat4(gpu_uniform(p, "u_light_vp"), light_vp)
|
||||
u_v3(gpu_uniform(p, "u_face_dir"), sun_dir)
|
||||
if r3d_debug_shadow and not sc_printed { sc_printed = true; print(`imp shadow prog {p} face_dir loc {gpu_uniform(p, "u_face_dir")} sun {f_fx(sun_dir[0])} {f_fx(sun_dir[1])} {f_fx(sun_dir[2])} cam {f_fx(cam_pos[0])} {f_fx(cam_pos[1])} {f_fx(cam_pos[2])} n_far {l.n_far}`) }
|
||||
if r3d_debug_shadow and not sc_printed { sc_printed = true; print(`imp shadow prog {p} face_dir loc {gpu_uniform(p, "u_face_dir")} sun {fixed(sun_dir[0])} {fixed(sun_dir[1])} {fixed(sun_dir[2])} cam {fixed(cam_pos[0])} {fixed(cam_pos[1])} {fixed(cam_pos[2])} n_far {l.n_far}`) }
|
||||
u_v3(gpu_uniform(p, "u_cam_pos"), cam_pos)
|
||||
} else {
|
||||
u_mat4(gpu_uniform(p, "u_view"), cam_view)
|
||||
u_mat4(gpu_uniform(p, "u_proj"), cam_proj)
|
||||
u_v3(gpu_uniform(p, "u_tint"), l.tint)
|
||||
if sc_dbg_lod { if sc_dbg_tint == null { sc_dbg_tint = v3_new(F_ONE, F_ONE, F_ONE) }; v3_set(sc_dbg_tint, fi(3), F_ZERO, fi(3)); u_v3(gpu_uniform(p, "u_tint"), sc_dbg_tint) }
|
||||
if sc_dbg_lod { if sc_dbg_tint == null { sc_dbg_tint = v3_new(1.0, 1.0, 1.0) }; v3_set(sc_dbg_tint, 3.0, 0.0, 3.0); u_v3(gpu_uniform(p, "u_tint"), sc_dbg_tint) }
|
||||
r3d_bind_2d(p, "u_atlas_normal", 1, im.normal)
|
||||
sky_bind_lighting(p)
|
||||
shadow_bind(p)
|
||||
fog_bind(p)
|
||||
if l.foliage { u_f(gpu_uniform(p, "u_spec_scale"), fl(0.05)) }
|
||||
if l.foliage { u_f(gpu_uniform(p, "u_spec_scale"), 0.05) }
|
||||
}
|
||||
gpu_cull(false)
|
||||
if not shadow and sc_a2c { gpu_alpha_to_coverage(true) }
|
||||
|
|
@ -1128,14 +1128,14 @@ function layer_draw_far(l: Layer, shadow: bool, light_vp: words) -> void {
|
|||
# swap a scanned mesh in up close; the shadow must not, or a tree's shadow changes shape
|
||||
# as you approach it. The card is also far the cheaper of the two, which is what pays for
|
||||
# casting the whole set into all five cascades.
|
||||
function layer_draw_shadow(l: Layer, light_vp: words) -> void {
|
||||
function layer_draw_shadow(l: Layer, light_vp: floats) -> void {
|
||||
if l.n_sh == 0 or l.imp == null { return }
|
||||
let p = sc_imp_prog_shadow
|
||||
gpu_use_program(p)
|
||||
let im = l.imp
|
||||
u_f(gpu_uniform(p, "u_radius"), im.radius)
|
||||
u_f(gpu_uniform(p, "u_height"), im.height)
|
||||
u_f(gpu_uniform(p, "u_tiles"), fi(im.tiles))
|
||||
u_f(gpu_uniform(p, "u_tiles"), float(im.tiles))
|
||||
r3d_bind_2d(p, "u_atlas_albedo", 0, im.albedo)
|
||||
u_mat4(gpu_uniform(p, "u_light_vp"), light_vp)
|
||||
u_v3(gpu_uniform(p, "u_face_dir"), sun_dir)
|
||||
|
|
@ -1156,8 +1156,8 @@ function layer_draw_depth(l: Layer, model: Model, vb: int, cnt: int) -> void {
|
|||
if cnt == 0 or model == null or vb == 0 { return }
|
||||
let p = sc_prog_fol_depth
|
||||
gpu_use_program(p)
|
||||
var ground = F_ZERO
|
||||
if l.grounded { ground = F_ONE }
|
||||
var ground = 0.0
|
||||
if l.grounded { ground = 1.0 }
|
||||
u_f(gpu_uniform(p, "u_ground"), ground)
|
||||
if l.grounded { terrain_bind_height(p) }
|
||||
u_f(gpu_uniform(p, "u_time"), r3d_time)
|
||||
|
|
@ -1217,7 +1217,7 @@ function scatter_draw() -> void {
|
|||
# whole class of caster vanish at a fixed distance, which is exactly the popping. A layer
|
||||
# with an impostor now casts its entire instance list from the card in every cascade;
|
||||
# only layers that have no impostor at all fall back to the mesh.
|
||||
function scatter_draw_casters(light_vp: words) -> void {
|
||||
function scatter_draw_casters(light_vp: floats) -> void {
|
||||
for i in 0 .. len(sc_layers) {
|
||||
let l = sc_layers[i]
|
||||
if sc_skip_blade and l.blade { continue }
|
||||
|
|
@ -1252,11 +1252,11 @@ function scatter_draw_casters(light_vp: words) -> void {
|
|||
# worlds use: geometry up close, an authored ground texture that matches it beyond).
|
||||
# Returns an RGBA8 texture (alpha = coverage), repeat-wrapped and mipmapped.
|
||||
var cb_state: int = 12345
|
||||
function cb_rnd() -> int {
|
||||
function cb_rnd() -> float {
|
||||
cb_state = (cb_state * 1103515245 + 12345) & 0x7FFFFFFF
|
||||
return fr((cb_state >> 8) & 0xFFFF, 65536)
|
||||
return float((cb_state >> 8) & 0xFFFF) / 65536.0
|
||||
}
|
||||
function carpet_bake(layers: []Layer, count: int, tile: int, res: int) -> int {
|
||||
function carpet_bake(layers: []Layer, count: int, tile: float, res: int) -> int {
|
||||
let tex = tex_target(res, res, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR)
|
||||
let fbo = gpu_fb_new()
|
||||
gpu_fb_bind(fbo)
|
||||
|
|
@ -1273,23 +1273,23 @@ function carpet_bake(layers: []Layer, count: int, tile: int, res: int) -> int {
|
|||
gpu_cull(false)
|
||||
gpu_blend(false)
|
||||
# the clumps, and eight wrapped copies so the tile's edges continue
|
||||
let half = f_mul(tile, F_HALF)
|
||||
let half = tile * 0.5
|
||||
let n9 = count * 9
|
||||
let inst = gl_floats(n9 * INST_FLOATS)
|
||||
cb_state = 977
|
||||
var k = 0
|
||||
for i in 0 .. count {
|
||||
let x = f_sub(f_mul(cb_rnd(), tile), half)
|
||||
let z = f_sub(f_mul(cb_rnd(), tile), half)
|
||||
let sc = f_add(fl(1.5), cb_rnd())
|
||||
let yaw = f_mul(cb_rnd(), f_mul(F_TWO, F_PI))
|
||||
let x = cb_rnd() * tile - half
|
||||
let z = cb_rnd() * tile - half
|
||||
let sc = 1.5 + cb_rnd()
|
||||
let yaw = cb_rnd() * (2.0 * PI)
|
||||
let sd = cb_rnd()
|
||||
for oz in 0 .. 3 {
|
||||
for ox in 0 .. 3 {
|
||||
let px = f_add(x, f_mul(fi(ox - 1), tile))
|
||||
let pz = f_add(z, f_mul(fi(oz - 1), tile))
|
||||
gl_put_bits(inst, k, px); gl_put_bits(inst, k + 1, F_ZERO); gl_put_bits(inst, k + 2, pz); gl_put_bits(inst, k + 3, sc)
|
||||
gl_put_bits(inst, k + 4, f_sin(yaw)); gl_put_bits(inst, k + 5, f_cos(yaw)); gl_put_bits(inst, k + 6, sd); gl_put_bits(inst, k + 7, F_ZERO)
|
||||
let px = x + float(ox - 1) * tile
|
||||
let pz = z + float(oz - 1) * tile
|
||||
gl_put_bits(inst, k, float_bits(px)); gl_put_bits(inst, k + 1, float_bits(0.0)); gl_put_bits(inst, k + 2, float_bits(pz)); gl_put_bits(inst, k + 3, float_bits(sc))
|
||||
gl_put_bits(inst, k + 4, float_bits(Math.sin(yaw))); gl_put_bits(inst, k + 5, float_bits(Math.cos(yaw))); gl_put_bits(inst, k + 6, float_bits(sd)); gl_put_bits(inst, k + 7, float_bits(0.0))
|
||||
k += INST_FLOATS
|
||||
}
|
||||
}
|
||||
|
|
@ -1299,20 +1299,20 @@ function carpet_bake(layers: []Layer, count: int, tile: int, res: int) -> int {
|
|||
free(inst)
|
||||
# straight down: the window is exactly one tile
|
||||
let view = m4_new(); let proj = m4_new()
|
||||
let eye = v3_new(F_ZERO, fi(6), F_ZERO); let at = v3_new(F_ZERO, F_ZERO, F_ZERO); let up = v3_new(F_ZERO, F_ZERO, f_neg1())
|
||||
let eye = v3_new(0.0, 6.0, 0.0); let at = v3_new(0.0, 0.0, 0.0); let up = v3_new(0.0, 0.0, -1.0)
|
||||
m4_look_at(view, eye, at, up)
|
||||
m4_ortho(proj, f_neg(half), half, f_neg(half), half, fl(0.1), fi(12))
|
||||
m4_ortho(proj, -half, half, -half, half, 0.1, 12.0)
|
||||
let bake = sc_bake_card_prog
|
||||
gpu_use_program(bake)
|
||||
u_mat4(gpu_uniform(bake, "u_view"), view)
|
||||
u_mat4(gpu_uniform(bake, "u_proj"), proj)
|
||||
u_f(gpu_uniform(bake, "u_wind"), F_ZERO)
|
||||
u_f(gpu_uniform(bake, "u_flutter"), F_ZERO)
|
||||
u_f(gpu_uniform(bake, "u_time"), F_ZERO)
|
||||
u_f(gpu_uniform(bake, "u_wind"), 0.0)
|
||||
u_f(gpu_uniform(bake, "u_flutter"), 0.0)
|
||||
u_f(gpu_uniform(bake, "u_time"), 0.0)
|
||||
for li in 0 .. len(layers) {
|
||||
let l = layers[li]
|
||||
if l.atlas == null { continue }
|
||||
u_f(gpu_uniform(bake, "u_card_w"), f_mul(l.atlas.radius, F_TWO)); u_f(gpu_uniform(bake, "u_card_h"), l.atlas.height)
|
||||
u_f(gpu_uniform(bake, "u_card_w"), l.atlas.radius * 2.0); u_f(gpu_uniform(bake, "u_card_h"), l.atlas.height)
|
||||
r3d_bind_2d(bake, "u_diff", 0, l.atlas.albedo)
|
||||
r3d_bind_2d(bake, "u_arm", 2, l.atlas.normal)
|
||||
for i in 0 .. len(l.model.prims) {
|
||||
|
|
|
|||
|
|
@ -11,15 +11,15 @@ const SHADOW_CASCADES: int = 5
|
|||
|
||||
var sh_tex: int = 0
|
||||
var sh_fbo: int = 0
|
||||
var sh_vp: words = null # 4 x 16 float bits
|
||||
var sh_split: words = null # view-space far distance of each cascade
|
||||
var sh_range: words = null # 4 light-frustum depth extents (metres)
|
||||
var sh_texel: words = null # 4 shadow texel sizes (metres)
|
||||
var sh_tmp_proj: words = null
|
||||
var sh_tmp_vp: words = null
|
||||
var sh_tmp_inv: words = null
|
||||
var sh_tmp_view: words = null
|
||||
var sh_corner: words = null
|
||||
var sh_vp: floats = null # 4 x 16 float bits
|
||||
var sh_split: floats = null # view-space far distance of each cascade
|
||||
var sh_range: floats = null # 4 light-frustum depth extents (metres)
|
||||
var sh_texel: floats = null # 4 shadow texel sizes (metres)
|
||||
var sh_tmp_proj: floats = null
|
||||
var sh_tmp_vp: floats = null
|
||||
var sh_tmp_inv: floats = null
|
||||
var sh_tmp_view: floats = null
|
||||
var sh_corner: floats = null
|
||||
var sh_cascade: int = 0 # the cascade being rendered (for casters that skip far ones)
|
||||
|
||||
function shadow_init() -> void {
|
||||
|
|
@ -28,14 +28,14 @@ function shadow_init() -> void {
|
|||
gpu_fb_bind(sh_fbo)
|
||||
gpu_fb_no_color()
|
||||
gpu_fb_bind(0)
|
||||
sh_vp = words(16 * SHADOW_CASCADES)
|
||||
sh_split = words(SHADOW_CASCADES)
|
||||
sh_range = words(SHADOW_CASCADES)
|
||||
sh_texel = words(SHADOW_CASCADES)
|
||||
sh_vp = floats(16 * SHADOW_CASCADES)
|
||||
sh_split = floats(SHADOW_CASCADES)
|
||||
sh_range = floats(SHADOW_CASCADES)
|
||||
sh_texel = floats(SHADOW_CASCADES)
|
||||
# the fourth slice keeps a tree-sized texel out to a kilometre; only the massif uses the last
|
||||
sh_split[0] = fi(16); sh_split[1] = fi(60); sh_split[2] = fi(250); sh_split[3] = fi(1100); sh_split[4] = fi(6000)
|
||||
sh_split[0] = 16.0; sh_split[1] = 60.0; sh_split[2] = 250.0; sh_split[3] = 1100.0; sh_split[4] = 6000.0
|
||||
sh_tmp_proj = m4_new(); sh_tmp_vp = m4_new(); sh_tmp_inv = m4_new(); sh_tmp_view = m4_new()
|
||||
sh_corner = words(3)
|
||||
sh_corner = floats(3)
|
||||
}
|
||||
|
||||
# A shadow resolution setting: 1024, 2048 or 4096 per cascade. The depth layers are made again at
|
||||
|
|
@ -83,7 +83,7 @@ function shadow_make_tex() -> void {
|
|||
}
|
||||
|
||||
# light view-projection for the camera-frustum slice [near, far]
|
||||
function shadow_fit(c: int, near: int, far: int) -> void {
|
||||
function shadow_fit(c: int, near: float, far: float) -> void {
|
||||
# Fit the slice in VIEW space, not world space. The bounding sphere of a frustum
|
||||
# slice depends only on near/far/fov/aspect — never on where the camera is pointing —
|
||||
# so computing it here makes the radius a constant per cascade. Doing it in world
|
||||
|
|
@ -92,56 +92,56 @@ function shadow_fit(c: int, near: int, far: int) -> void {
|
|||
# map resampled every frame: that is the crawl and flicker seen while moving.
|
||||
m4_perspective(sh_tmp_proj, cam_fov, cam_aspect, near, far)
|
||||
m4_inverse(sh_tmp_inv, sh_tmp_proj) # NDC -> view space
|
||||
let cview = v3_new(F_ZERO, F_ZERO, F_ZERO)
|
||||
let corners = words(24)
|
||||
let cview = v3_new(0.0, 0.0, 0.0)
|
||||
let corners = floats(24)
|
||||
for i in 0 .. 8 {
|
||||
var x = f_neg1(); var y = f_neg1(); var z = f_neg1()
|
||||
if (i & 1) != 0 { x = F_ONE }
|
||||
if (i & 2) != 0 { y = F_ONE }
|
||||
if (i & 4) != 0 { z = F_ONE }
|
||||
var x = -1.0; var y = -1.0; var z = -1.0
|
||||
if (i & 1) != 0 { x = 1.0 }
|
||||
if (i & 2) != 0 { y = 1.0 }
|
||||
if (i & 4) != 0 { z = 1.0 }
|
||||
let w = m4_xform_point(sh_corner, sh_tmp_inv, x, y, z)
|
||||
let iw = f_div(F_ONE, w)
|
||||
corners[i * 3] = f_mul(sh_corner[0], iw); corners[i * 3 + 1] = f_mul(sh_corner[1], iw); corners[i * 3 + 2] = f_mul(sh_corner[2], iw)
|
||||
cview[0] = f_add(cview[0], corners[i * 3]); cview[1] = f_add(cview[1], corners[i * 3 + 1]); cview[2] = f_add(cview[2], corners[i * 3 + 2])
|
||||
let iw = 1.0 / w
|
||||
corners[i * 3] = sh_corner[0] * iw; corners[i * 3 + 1] = sh_corner[1] * iw; corners[i * 3 + 2] = sh_corner[2] * iw
|
||||
cview[0] = cview[0] + corners[i * 3]; cview[1] = cview[1] + corners[i * 3 + 1]; cview[2] = cview[2] + corners[i * 3 + 2]
|
||||
}
|
||||
v3_scale(cview, cview, fr(1, 8))
|
||||
var radius = F_ZERO
|
||||
v3_scale(cview, cview, 1.0 / 8.0)
|
||||
var radius = 0.0
|
||||
for i in 0 .. 8 {
|
||||
v3_set(sh_corner, corners[i * 3], corners[i * 3 + 1], corners[i * 3 + 2])
|
||||
let d = v3_dist(sh_corner, cview)
|
||||
if f_gt(d, radius) { radius = d }
|
||||
if d > radius { radius = d }
|
||||
}
|
||||
radius = f_mul(radius, fl(1.05))
|
||||
radius = radius * 1.05
|
||||
# the slice centre back into world space
|
||||
m4_inverse(sh_tmp_vp, cam_view)
|
||||
let center = words(3)
|
||||
let center = floats(3)
|
||||
m4_xform_point(center, sh_tmp_vp, cview[0], cview[1], cview[2])
|
||||
free(cview)
|
||||
# light view: from far along the sun direction, looking at the centre
|
||||
let eye = words(3)
|
||||
let eye = floats(3)
|
||||
# casters up to ~900 m toward the sun (a mountain across the valley), and the
|
||||
# slice itself behind the centre: a tight depth range keeps the bias small
|
||||
let back = f_add(radius, fi(900))
|
||||
let back = radius + 900.0
|
||||
v3_madd(eye, center, sun_dir, back)
|
||||
let up = v3_new(F_ZERO, F_ONE, F_ZERO)
|
||||
let up = v3_new(0.0, 1.0, 0.0)
|
||||
m4_look_at(sh_tmp_view, eye, center, up)
|
||||
# snap the ortho window to the shadow texel grid
|
||||
let texel = f_div(f_mul(radius, F_TWO), fi(shadow_res))
|
||||
let texel = radius * 2.0 / float(shadow_res)
|
||||
m4_xform_point(sh_corner, sh_tmp_view, center[0], center[1], center[2])
|
||||
let ox = f_sub(f_mul(f_floor(f_div(sh_corner[0], texel)), texel), sh_corner[0])
|
||||
let oy = f_sub(f_mul(f_floor(f_div(sh_corner[1], texel)), texel), sh_corner[1])
|
||||
let nr = f_neg(radius)
|
||||
let zfar = f_add(f_add(back, radius), fi(100))
|
||||
m4_ortho(sh_tmp_proj, f_add(nr, ox), f_add(radius, ox), f_add(nr, oy), f_add(radius, oy), F_ONE, zfar)
|
||||
sh_range[c] = f_sub(zfar, F_ONE)
|
||||
let ox = Math.floor(sh_corner[0] / texel) * texel - sh_corner[0]
|
||||
let oy = Math.floor(sh_corner[1] / texel) * texel - sh_corner[1]
|
||||
let nr = -radius
|
||||
let zfar = back + radius + 100.0
|
||||
m4_ortho(sh_tmp_proj, nr + ox, radius + ox, nr + oy, radius + oy, 1.0, zfar)
|
||||
sh_range[c] = zfar - 1.0
|
||||
sh_texel[c] = texel
|
||||
let out = words(16)
|
||||
let out = floats(16)
|
||||
m4_mul(out, sh_tmp_proj, sh_tmp_view)
|
||||
for i in 0 .. 16 { sh_vp[c * 16 + i] = out[i] }
|
||||
free(out); free(eye); free(up); free(center); free(corners)
|
||||
}
|
||||
|
||||
function shadow_cascade_vp(c: int) -> words { return mem_off(sh_vp, c * 64) }
|
||||
function shadow_cascade_vp(c: int) -> floats { return mem_off(sh_vp, c * 64) }
|
||||
|
||||
|
||||
# render every cascade; `draw` happens through terrain_draw_shadow + the scene's casters
|
||||
|
|
@ -173,25 +173,25 @@ function shadow_pass() -> void {
|
|||
if r3d_debug_shadow { shadow_dump() }
|
||||
if r3d_debug_shadow and not sh_printed2 {
|
||||
sh_printed2 = true
|
||||
let q = words(3)
|
||||
if sh_probe_x != 0 {
|
||||
let q = floats(3)
|
||||
if sh_probe_x != 0.0 {
|
||||
let vp = shadow_cascade_vp(2)
|
||||
m4_xform_point(q, vp, sh_probe_x, sh_probe_y, sh_probe_z)
|
||||
print(`probe base ndc {f_fx(q[0])} {f_fx(q[1])} {f_fx(q[2])}`)
|
||||
m4_xform_point(q, vp, sh_probe_x, f_add(sh_probe_y, fi(15)), sh_probe_z)
|
||||
print(`probe top ndc {f_fx(q[0])} {f_fx(q[1])} {f_fx(q[2])} -> map texel {f_to_int(f_mul(f_add(f_mul(q[0], F_HALF), F_HALF), fi(shadow_res)))} {f_to_int(f_mul(f_add(f_mul(q[1], F_HALF), F_HALF), fi(shadow_res)))}`)
|
||||
print(`probe base ndc {fixed(q[0])} {fixed(q[1])} {fixed(q[2])}`)
|
||||
m4_xform_point(q, vp, sh_probe_x, sh_probe_y + 15.0, sh_probe_z)
|
||||
print(`probe top ndc {fixed(q[0])} {fixed(q[1])} {fixed(q[2])} -> map texel {int((q[0] * 0.5 + 0.5) * float(shadow_res))} {int((q[1] * 0.5 + 0.5) * float(shadow_res))}`)
|
||||
# where the top's shadow lands on the ground: walk down the sun ray
|
||||
let gx = f_sub(f_add(sh_probe_x, F_ZERO), f_mul(sun_dir[0], f_div(fi(15), sun_dir[1])))
|
||||
let gz = f_sub(sh_probe_z, f_mul(sun_dir[2], f_div(fi(15), sun_dir[1])))
|
||||
let gx = sh_probe_x + 0.0 - sun_dir[0] * (15.0 / sun_dir[1])
|
||||
let gz = sh_probe_z - sun_dir[2] * (15.0 / sun_dir[1])
|
||||
m4_xform_point(q, vp, gx, terrain_height(gx, gz), gz)
|
||||
print(`shadow-of-top ground ndc {f_fx(q[0])} {f_fx(q[1])} {f_fx(q[2])} at {f_fx(gx)} {f_fx(gz)}`)
|
||||
print(`shadow-of-top ground ndc {fixed(q[0])} {fixed(q[1])} {fixed(q[2])} at {fixed(gx)} {fixed(gz)}`)
|
||||
}
|
||||
for c in 0 .. SHADOW_CASCADES {
|
||||
let vp = shadow_cascade_vp(c)
|
||||
# a point 5 m ahead of the camera on the ground
|
||||
let px = f_add(cam_pos[0], f_mul(cam_fwd[0], fi(5))); let pz = f_add(cam_pos[2], f_mul(cam_fwd[2], fi(5)))
|
||||
let px = cam_pos[0] + cam_fwd[0] * 5.0; let pz = cam_pos[2] + cam_fwd[2] * 5.0
|
||||
let w = m4_xform_point(q, vp, px, terrain_height(px, pz), pz)
|
||||
print(`cascade {c}: ndc {f_fx(q[0])} {f_fx(q[1])} {f_fx(q[2])} w {f_fx(w)} m0 {f_fx(vp[0])} m5 {f_fx(vp[5])} m14 {f_fx(vp[14])}`)
|
||||
print(`cascade {c}: ndc {fixed(q[0])} {fixed(q[1])} {fixed(q[2])} w {fixed(w)} m0 {fixed(vp[0])} m5 {fixed(vp[5])} m14 {fixed(vp[14])}`)
|
||||
}
|
||||
free(q)
|
||||
}
|
||||
|
|
@ -201,29 +201,29 @@ var sh_printed3: bool = false
|
|||
var sh_enabled: bool = true
|
||||
var sh_force: int = -1 # R3D_FORCE=<c> pins every pixel to cascade c (debug)
|
||||
var sh_skip_terrain: bool = false
|
||||
var sh_probe_x: int = 0
|
||||
var sh_probe_y: int = 0
|
||||
var sh_probe_z: int = 0
|
||||
var sh_probe_x: float = 0.0
|
||||
var sh_probe_y: float = 0.0
|
||||
var sh_probe_z: float = 0.0
|
||||
|
||||
# Debug: cascade depths as grey PPMs (build/dbg_shadow_<c>.ppm)
|
||||
function shadow_dump() -> void {
|
||||
let n = shadow_res * shadow_res
|
||||
let buf = words(n * SHADOW_CASCADES)
|
||||
let buf = floats(n * SHADOW_CASCADES)
|
||||
gpu_tex_bind(GPU_TEX2D_ARRAY, sh_tex)
|
||||
gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_NONE)
|
||||
gpu_tex_read(GPU_TEX2D_ARRAY, GL_DEPTH_COMPONENT, GL_FLOAT, buf)
|
||||
gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE)
|
||||
if sh_probe_x != 0 {
|
||||
if sh_probe_x != 0.0 {
|
||||
let vp = shadow_cascade_vp(2)
|
||||
let q = words(3)
|
||||
m4_xform_point(q, vp, sh_probe_x, f_add(sh_probe_y, fi(12)), sh_probe_z)
|
||||
let tx = f_to_int(f_mul(f_add(f_mul(q[0], F_HALF), F_HALF), fi(shadow_res)))
|
||||
let ty = f_to_int(f_mul(f_add(f_mul(q[1], F_HALF), F_HALF), fi(shadow_res)))
|
||||
let want = f_add(f_mul(q[2], F_HALF), F_HALF)
|
||||
print(`probe (12 m up) texel {tx} {ty} card depth {f_fx(f_mul(want, fi(1000)))}/1000`)
|
||||
let q = floats(3)
|
||||
m4_xform_point(q, vp, sh_probe_x, sh_probe_y + 12.0, sh_probe_z)
|
||||
let tx = int((q[0] * 0.5 + 0.5) * float(shadow_res))
|
||||
let ty = int((q[1] * 0.5 + 0.5) * float(shadow_res))
|
||||
let want = q[2] * 0.5 + 0.5
|
||||
print(`probe (12 m up) texel {tx} {ty} card depth {fixed(want * 1000.0)}/1000`)
|
||||
for dy in 0 .. 5 {
|
||||
let yy = ty - 40 + dy * 20
|
||||
print(` row {yy}: {f_fx(f_mul(buf[2 * n + yy * shadow_res + tx - 20], fi(1000)))} {f_fx(f_mul(buf[2 * n + yy * shadow_res + tx], fi(1000)))} {f_fx(f_mul(buf[2 * n + yy * shadow_res + tx + 20], fi(1000)))} /1000`)
|
||||
print(` row {yy}: {fixed(buf[2 * n + yy * shadow_res + tx - 20] * 1000.0)} {fixed(buf[2 * n + yy * shadow_res + tx] * 1000.0)} {fixed(buf[2 * n + yy * shadow_res + tx + 20] * 1000.0)} /1000`)
|
||||
}
|
||||
free(q)
|
||||
}
|
||||
|
|
@ -231,10 +231,10 @@ function shadow_dump() -> void {
|
|||
let row = bytes(sm * 3)
|
||||
for c in 0 .. SHADOW_CASCADES {
|
||||
# stretch between the map's own min and max (ignoring the far plane)
|
||||
var lo = F_ONE; var hi = F_ZERO
|
||||
var lo = 1.0; var hi = 0.0
|
||||
var i = 0
|
||||
while i < n { let d = buf[c * n + i]; if f_ls(d, fl(0.999)) { if f_ls(d, lo) { lo = d }; if f_gt(d, hi) { hi = d } }; i += 97 }
|
||||
print(`cascade {c} depth range {f_fx(lo)} .. {f_fx(hi)}`)
|
||||
while i < n { let d = buf[c * n + i]; if d < 0.999 { if d < lo { lo = d }; if d > hi { hi = d } }; i += 97 }
|
||||
print(`cascade {c} depth range {fixed(lo)} .. {fixed(hi)}`)
|
||||
let f = file_open(`build/dbg_shadow_{c}.ppm`, "wb")
|
||||
let hdr = `P6\n{sm} {sm}\n255\n`
|
||||
file_write(f, hdr, len(hdr))
|
||||
|
|
@ -242,7 +242,7 @@ function shadow_dump() -> void {
|
|||
for y in 0 .. sm {
|
||||
for x in 0 .. sm {
|
||||
let d = buf[c * n + (y * st) * shadow_res + x * st]
|
||||
let g = f_to_int(f_mul(f_clamp(f_div(f_sub(d, lo), f_max(f_sub(hi, lo), fl(0.0001))), F_ZERO, F_ONE), fi(255)))
|
||||
let g = int(Math.clamp((d - lo) / Math.max(hi - lo, 0.0001), 0.0, 1.0) * 255.0)
|
||||
row[x * 3] = g; row[x * 3 + 1] = g; row[x * 3 + 2] = g
|
||||
}
|
||||
file_write(f, row, sm * 3)
|
||||
|
|
@ -263,19 +263,19 @@ function shadow_bind(prog: int) -> void {
|
|||
r3d_bind_tex(prog, "u_shadow", 15, GPU_TEX2D_ARRAY, sh_tex)
|
||||
# the height-field shadow (terrain.ludic); a stand-in texture keeps the unit valid before the bake
|
||||
var ts = ter_shadow_tex
|
||||
var ts_on = F_ONE
|
||||
if ts == 0 { ts = ter_height_tex; ts_on = F_ZERO }
|
||||
var ts_on = 1.0
|
||||
if ts == 0 { ts = ter_height_tex; ts_on = 0.0 }
|
||||
r3d_bind_2d(prog, "u_tershadow", 6, ts)
|
||||
terrain_bind_height(prog)
|
||||
u_f(gpu_uniform(prog, "u_ts_on"), ts_on)
|
||||
u_f(gpu_uniform(prog, "u_ts_half"), fi(TERRAIN_HALF))
|
||||
u_f(gpu_uniform(prog, "u_ts_half"), float(TERRAIN_HALF))
|
||||
u_f2(gpu_uniform(prog, "u_ts_origin"), ter_ox, ter_oz)
|
||||
var loc = gpu_uniform(prog, "u_cascade_vp[0]")
|
||||
if loc < 0 { loc = gpu_uniform(prog, "u_cascade_vp") }
|
||||
if r3d_debug_shadow and not sh_printed { sh_printed = true; print(`cascade vp loc {loc} / {gpu_uniform(prog, "u_cascade_vp")} split loc {gpu_uniform(prog, "u_cascade_split")} shadow loc {gpu_uniform(prog, "u_shadow")}`) }
|
||||
u_mat4n(loc, SHADOW_CASCADES, sh_vp)
|
||||
u_fv(sh_loc(prog, "u_cascade_split"), SHADOW_CASCADES, sh_split)
|
||||
if r3d_debug_shadow and not sh_printed3 { sh_printed3 = true; print(`range {f_fx(sh_range[0])} {f_fx(sh_range[1])} {f_fx(sh_range[2])} {f_fx(sh_range[3])} texel*1000 {f_fx(f_mul(sh_texel[0], fi(1000)))} {f_fx(f_mul(sh_texel[1], fi(1000)))} {f_fx(f_mul(sh_texel[2], fi(1000)))} {f_fx(f_mul(sh_texel[3], fi(1000)))} locs {gpu_uniform(prog, "u_cascade_range")} {gpu_uniform(prog, "u_cascade_texel")}`) }
|
||||
if r3d_debug_shadow and not sh_printed3 { sh_printed3 = true; print(`range {fixed(sh_range[0])} {fixed(sh_range[1])} {fixed(sh_range[2])} {fixed(sh_range[3])} texel*1000 {fixed(sh_texel[0] * 1000.0)} {fixed(sh_texel[1] * 1000.0)} {fixed(sh_texel[2] * 1000.0)} {fixed(sh_texel[3] * 1000.0)} locs {gpu_uniform(prog, "u_cascade_range")} {gpu_uniform(prog, "u_cascade_texel")}`) }
|
||||
u_fv(sh_loc(prog, "u_cascade_range"), SHADOW_CASCADES, sh_range)
|
||||
if r3d_env_has("R3D_FORCE") { sh_force = Text.to_int(r3d_env("R3D_FORCE")) }
|
||||
u_i(gpu_uniform(prog, "u_force_cascade"), sh_force)
|
||||
|
|
|
|||
|
|
@ -19,28 +19,28 @@ property Skin {
|
|||
n_nodes: int = 0,
|
||||
par: words, # parent node per node, -1 at a root
|
||||
walk: words, # the nodes ordered parents-first
|
||||
rest_t: words, # 3 per node
|
||||
rest_r: words, # 4 per node (x, y, z, w)
|
||||
rest_s: words, # 3 per node
|
||||
rest_g: words, # 4 per node: the global rest rotation
|
||||
rest_t: floats, # 3 per node
|
||||
rest_r: floats, # 4 per node (x, y, z, w)
|
||||
rest_s: floats, # 3 per node
|
||||
rest_g: floats, # 4 per node: the global rest rotation
|
||||
names: []string,
|
||||
pose_r: words, # 4 per node: the pose rotation, model frame
|
||||
pose_t: words, # 3 per node: the pose offset, model frame (metres)
|
||||
pose_r: floats, # 4 per node: the pose rotation, model frame
|
||||
pose_t: floats, # 3 per node: the pose offset, model frame (metres)
|
||||
gmat: words, # 16 per node: global matrix this pose
|
||||
n_joints: int = 0,
|
||||
joints: words, # node index per joint
|
||||
inv_bind: words, # 16 per joint
|
||||
bones: words, # 16 per joint: what the vertex shader skins with
|
||||
tmp_l: words,
|
||||
tmp_q: words,
|
||||
tmp_a: words,
|
||||
tmp_b: words,
|
||||
tmp_c: words,
|
||||
tmp_v: words
|
||||
bones: floats, # 16 per joint: what the vertex shader skins with
|
||||
tmp_l: floats,
|
||||
tmp_q: floats,
|
||||
tmp_a: floats,
|
||||
tmp_b: floats,
|
||||
tmp_c: floats,
|
||||
tmp_v: floats
|
||||
}
|
||||
|
||||
# a 3-vector of a JSON array (float bits), or a default
|
||||
function skin_jv3(o: words, at: int, nd: Val, key: pointer, dx: int, dy: int, dz: int) -> void {
|
||||
function skin_jv3(o: floats, at: int, nd: Val, key: pointer, dx: float, dy: float, dz: float) -> void {
|
||||
if value_has(nd, key) == 0 { o[at] = dx; o[at + 1] = dy; o[at + 2] = dz; return }
|
||||
let arr = value_get(nd, key)
|
||||
for i in 0 .. 3 { o[at + i] = jnum(value_at(arr, i)) }
|
||||
|
|
@ -75,22 +75,22 @@ function skin_load(idx: int) -> Skin {
|
|||
let n = value_count(nodes)
|
||||
sk.n_nodes = n
|
||||
sk.par = words(n); sk.walk = words(n)
|
||||
sk.rest_t = words(n * 3); sk.rest_r = words(n * 4); sk.rest_s = words(n * 3); sk.rest_g = words(n * 4)
|
||||
sk.pose_r = words(n * 4); sk.pose_t = words(n * 3); sk.gmat = words(n * 16)
|
||||
sk.rest_t = floats(n * 3); sk.rest_r = floats(n * 4); sk.rest_s = floats(n * 3); sk.rest_g = floats(n * 4)
|
||||
sk.pose_r = floats(n * 4); sk.pose_t = floats(n * 3); sk.gmat = words(n * 16)
|
||||
sk.names = new []string
|
||||
sk.tmp_l = m4_new(); sk.tmp_q = q_new(); sk.tmp_a = q_new(); sk.tmp_b = q_new(); sk.tmp_c = q_new(); sk.tmp_v = words(3)
|
||||
sk.tmp_l = m4_new(); sk.tmp_q = q_new(); sk.tmp_a = q_new(); sk.tmp_b = q_new(); sk.tmp_c = q_new(); sk.tmp_v = floats(3)
|
||||
for i in 0 .. n { sk.par[i] = -1 }
|
||||
for i in 0 .. n {
|
||||
let nd = value_at(nodes, i)
|
||||
var nm: string = ""
|
||||
if value_has(nd, "name") != 0 { nm = value_as_str(value_get(nd, "name")) }
|
||||
push(sk.names, nm)
|
||||
skin_jv3(sk.rest_t, i * 3, nd, "translation", F_ZERO, F_ZERO, F_ZERO)
|
||||
skin_jv3(sk.rest_s, i * 3, nd, "scale", F_ONE, F_ONE, F_ONE)
|
||||
skin_jv3(sk.rest_t, i * 3, nd, "translation", 0.0, 0.0, 0.0)
|
||||
skin_jv3(sk.rest_s, i * 3, nd, "scale", 1.0, 1.0, 1.0)
|
||||
if value_has(nd, "rotation") != 0 {
|
||||
let r = value_get(nd, "rotation")
|
||||
for k in 0 .. 4 { sk.rest_r[i * 4 + k] = jnum(value_at(r, k)) }
|
||||
} else { sk.rest_r[i * 4] = F_ZERO; sk.rest_r[i * 4 + 1] = F_ZERO; sk.rest_r[i * 4 + 2] = F_ZERO; sk.rest_r[i * 4 + 3] = F_ONE }
|
||||
} else { sk.rest_r[i * 4] = 0.0; sk.rest_r[i * 4 + 1] = 0.0; sk.rest_r[i * 4 + 2] = 0.0; sk.rest_r[i * 4 + 3] = 1.0 }
|
||||
if value_has(nd, "matrix") != 0 { print(`skin: node {nm} uses a matrix transform (unsupported, treated as identity)`) }
|
||||
if value_has(nd, "children") != 0 {
|
||||
let ch = value_get(nd, "children")
|
||||
|
|
@ -124,7 +124,7 @@ function skin_load(idx: int) -> Skin {
|
|||
sk.n_joints = nj
|
||||
sk.joints = words(nj)
|
||||
sk.inv_bind = words(nj * 16)
|
||||
sk.bones = words(nj * 16)
|
||||
sk.bones = floats(nj * 16)
|
||||
for j in 0 .. nj { sk.joints[j] = value_as_int(value_at(jl, j)) }
|
||||
if value_has(skv, "inverseBindMatrices") != 0 {
|
||||
let ib = gltf_accessor(value_as_int(value_get(skv, "inverseBindMatrices")))
|
||||
|
|
@ -144,24 +144,24 @@ function skin_find(sk: Skin, name: string) -> int {
|
|||
print(`skin: no node {name}`)
|
||||
return -1
|
||||
}
|
||||
function skin_mat(sk: Skin, node: int) -> words { return mem_off(sk.gmat, node * 64) }
|
||||
function skin_mat(sk: Skin, node: int) -> floats { return mem_off(sk.gmat, node * 64) }
|
||||
|
||||
# back to the rest pose
|
||||
function skin_reset(sk: Skin) -> void {
|
||||
for i in 0 .. sk.n_nodes {
|
||||
sk.pose_r[i * 4] = F_ZERO; sk.pose_r[i * 4 + 1] = F_ZERO; sk.pose_r[i * 4 + 2] = F_ZERO; sk.pose_r[i * 4 + 3] = F_ONE
|
||||
sk.pose_t[i * 3] = F_ZERO; sk.pose_t[i * 3 + 1] = F_ZERO; sk.pose_t[i * 3 + 2] = F_ZERO
|
||||
sk.pose_r[i * 4] = 0.0; sk.pose_r[i * 4 + 1] = 0.0; sk.pose_r[i * 4 + 2] = 0.0; sk.pose_r[i * 4 + 3] = 1.0
|
||||
sk.pose_t[i * 3] = 0.0; sk.pose_t[i * 3 + 1] = 0.0; sk.pose_t[i * 3 + 2] = 0.0
|
||||
}
|
||||
}
|
||||
# a node's pose rotation in the model frame: pitch about X, yaw about Y, roll about Z (radians)
|
||||
function skin_set_rot(sk: Skin, node: int, pitch: int, yaw: int, roll: int) -> void {
|
||||
function skin_set_rot(sk: Skin, node: int, pitch: float, yaw: float, roll: float) -> void {
|
||||
if node < 0 { return }
|
||||
q_euler(sk.tmp_q, pitch, yaw, roll)
|
||||
q_store(sk.pose_r, node, sk.tmp_q)
|
||||
}
|
||||
function skin_set_quat(sk: Skin, node: int, q: words) -> void { if node >= 0 { q_store(sk.pose_r, node, q) } }
|
||||
function skin_set_quat(sk: Skin, node: int, q: floats) -> void { if node >= 0 { q_store(sk.pose_r, node, q) } }
|
||||
# a node's pose offset in the model frame (metres)
|
||||
function skin_set_offset(sk: Skin, node: int, x: int, y: int, z: int) -> void {
|
||||
function skin_set_offset(sk: Skin, node: int, x: float, y: float, z: float) -> void {
|
||||
if node < 0 { return }
|
||||
sk.pose_t[node * 3] = x; sk.pose_t[node * 3 + 1] = y; sk.pose_t[node * 3 + 2] = z
|
||||
}
|
||||
|
|
@ -179,12 +179,12 @@ function skin_pose(sk: Skin) -> void {
|
|||
q_conj(sk.tmp_c, sk.tmp_b) # G_p^-1
|
||||
q_mul(sk.tmp_q, sk.tmp_c, sk.tmp_a)
|
||||
q_mul(sk.tmp_a, sk.tmp_q, sk.tmp_b) # G_p^-1 D G_p
|
||||
if ox != 0 or oy != 0 or oz != 0 {
|
||||
if ox != 0.0 or oy != 0.0 or oz != 0.0 {
|
||||
v3_set(sk.tmp_v, ox, oy, oz)
|
||||
q_rotate(sk.tmp_v, sk.tmp_c, sk.tmp_v) # the offset in the parent's frame
|
||||
tx = f_add(tx, sk.tmp_v[0]); ty = f_add(ty, sk.tmp_v[1]); tz = f_add(tz, sk.tmp_v[2])
|
||||
tx = tx + sk.tmp_v[0]; ty = ty + sk.tmp_v[1]; tz = tz + sk.tmp_v[2]
|
||||
}
|
||||
} else { tx = f_add(tx, ox); ty = f_add(ty, oy); tz = f_add(tz, oz) }
|
||||
} else { tx = tx + ox; ty = ty + oy; tz = tz + oz }
|
||||
q_load(sk.tmp_b, sk.rest_r, i)
|
||||
q_mul(sk.tmp_q, sk.tmp_a, sk.tmp_b) # local rotation
|
||||
m4_trs_q(sk.tmp_l, tx, ty, tz, sk.tmp_q, sk.rest_s[i * 3], sk.rest_s[i * 3 + 1], sk.rest_s[i * 3 + 2])
|
||||
|
|
@ -211,9 +211,9 @@ function skin_clone(src: Skin) -> Skin {
|
|||
sk.names = src.names
|
||||
sk.n_joints = src.n_joints; sk.joints = src.joints; sk.inv_bind = src.inv_bind
|
||||
let n = src.n_nodes
|
||||
sk.pose_r = words(n * 4); sk.pose_t = words(n * 3); sk.gmat = words(n * 16)
|
||||
sk.bones = words(src.n_joints * 16)
|
||||
sk.tmp_l = m4_new(); sk.tmp_q = q_new(); sk.tmp_a = q_new(); sk.tmp_b = q_new(); sk.tmp_c = q_new(); sk.tmp_v = words(3)
|
||||
sk.pose_r = floats(n * 4); sk.pose_t = floats(n * 3); sk.gmat = words(n * 16)
|
||||
sk.bones = floats(src.n_joints * 16)
|
||||
sk.tmp_l = m4_new(); sk.tmp_q = q_new(); sk.tmp_a = q_new(); sk.tmp_b = q_new(); sk.tmp_c = q_new(); sk.tmp_v = floats(3)
|
||||
skin_reset(sk)
|
||||
skin_pose(sk)
|
||||
return sk
|
||||
|
|
|
|||
|
|
@ -13,70 +13,70 @@ var sky_h: int = 0
|
|||
var sky_irradiance: int = 0
|
||||
var sky_prefilter: int = 0 # GL_TEXTURE_2D_ARRAY
|
||||
var sky_brdf: int = 0
|
||||
var sun_dir: words = null # toward the sun (float bits)
|
||||
var sun_color: words = null # radiance (float bits)
|
||||
var sun_dir: floats = null # toward the sun (float bits)
|
||||
var sun_color: floats = null # radiance (float bits)
|
||||
var sky_fullscreen: Mesh = null
|
||||
var sky_yaw: int = 0 # radians: the HDRI is turned by this about y
|
||||
var sky_sun_boost: int = 0x40133333 # 2.3: the photograph's thin cloud dims its sun; a crisper day wants more
|
||||
var sky_rot_s: int = 0
|
||||
var sky_rot_c: int = 0
|
||||
var sun_hdri: words = null # the sun direction as found in the file
|
||||
var sky_yaw: float = 0.0 # radians: the HDRI is turned by this about y
|
||||
var sky_sun_boost: float = 2.3 # 2.3: the photograph's thin cloud dims its sun; a crisper day wants more
|
||||
var sky_rot_s: float = 0.0
|
||||
var sky_rot_c: float = 0.0
|
||||
var sun_hdri: floats = null # the sun direction as found in the file
|
||||
# The yaw to bake the light at the first time (radians, float bits). A game that turns the sky at
|
||||
# start sets it before r3d_init, so the image-based light is baked once at that yaw rather than at
|
||||
# 0 and then again - which is what sky_set_yaw at boot used to cost.
|
||||
var sky_start_yaw: int = 0
|
||||
var sky_start_yaw: float = 0.0
|
||||
var sky_baked: bool = false # the IBL set exists, baked at sky_baked_yaw
|
||||
var sky_baked_yaw: int = 0
|
||||
var sky_baked_yaw: float = 0.0
|
||||
var sky_p_irr: int = 0
|
||||
var sky_p_pre: int = 0
|
||||
var sky_p_brdf: int = 0
|
||||
|
||||
# turn the HDRI so its sun sits at world azimuth `yaw` (radians, 0 = toward -z)
|
||||
function sky_set_yaw(yaw: int) -> void {
|
||||
function sky_set_yaw(yaw: float) -> void {
|
||||
sky_set_rot(yaw)
|
||||
# world sun = rotY(sun_hdri, -yaw): the lookup rotates a world direction by +yaw
|
||||
let s = sun_hdri
|
||||
v3_set(sun_dir, f_sub(f_mul(sky_rot_c, s[0]), f_mul(sky_rot_s, s[2])), s[1], f_add(f_mul(sky_rot_s, s[0]), f_mul(sky_rot_c, s[2])))
|
||||
v3_set(sun_dir, sky_rot_c * s[0] - sky_rot_s * s[2], s[1], sky_rot_s * s[0] + sky_rot_c * s[2])
|
||||
# the light is already baked at this yaw: turning to it again bakes nothing
|
||||
if sky_baked and yaw == sky_baked_yaw { return }
|
||||
sky_precompute()
|
||||
}
|
||||
# turn only the visible sky image (cheap, per frame): the light and the convolved
|
||||
# maps stay where they are — daylight.ludic moves those on its own terms
|
||||
function sky_set_rot(yaw: int) -> void {
|
||||
function sky_set_rot(yaw: float) -> void {
|
||||
sky_yaw = yaw
|
||||
sky_rot_s = f_sin(yaw); sky_rot_c = f_cos(yaw)
|
||||
sky_rot_s = Math.sin(yaw); sky_rot_c = Math.cos(yaw)
|
||||
}
|
||||
function sky_bind_rot(prog: int) -> void { u_f2(gpu_uniform(prog, "u_sky_rot"), sky_rot_s, sky_rot_c) }
|
||||
|
||||
# direction for an equirect uv (matches equirectUV in lighting.glsl)
|
||||
function sky_dir_from_uv(o: words, u: int, v: int) -> void {
|
||||
let phi = f_mul(f_sub(u, F_HALF), f_mul(F_TWO, F_PI))
|
||||
let theta = f_mul(v, F_PI)
|
||||
let st = f_sin(theta)
|
||||
v3_set(o, f_mul(st, f_sin(phi)), f_cos(theta), f_neg(f_mul(st, f_cos(phi))))
|
||||
function sky_dir_from_uv(o: floats, u: float, v: float) -> void {
|
||||
let phi = (u - 0.5) * (2.0 * PI)
|
||||
let theta = v * PI
|
||||
let st = Math.sin(theta)
|
||||
v3_set(o, st * Math.sin(phi), Math.cos(theta), -(st * Math.cos(phi)))
|
||||
}
|
||||
|
||||
function sky_load(path: string) -> bool {
|
||||
sky_tex = tex_load_hdr(path)
|
||||
if sky_tex == 0 { return false }
|
||||
sky_w = tex_w; sky_h = tex_h
|
||||
sun_dir = words(3)
|
||||
sun_hdri = words(3)
|
||||
sky_dir_from_uv(sun_hdri, fr(hdr_max_x * 2 + 1, sky_w * 2), fr(hdr_max_y * 2 + 1, sky_h * 2))
|
||||
sun_dir = floats(3)
|
||||
sun_hdri = floats(3)
|
||||
sky_dir_from_uv(sun_hdri, float(hdr_max_x * 2 + 1) / float(sky_w * 2), float(hdr_max_y * 2 + 1) / float(sky_h * 2))
|
||||
v3_copy(sun_dir, sun_hdri)
|
||||
sky_rot_c = F_ONE
|
||||
sky_rot_c = 1.0
|
||||
# the sun's irradiance is what the IBL clip leaves out of the map; lighting it
|
||||
# directly with that keeps sun and sky in the photograph's own proportion
|
||||
sun_color = v3_new(f_mul(hdr_sun_r, sky_sun_boost), f_mul(hdr_sun_g, sky_sun_boost), f_mul(hdr_sun_b, sky_sun_boost))
|
||||
print(`sun irradiance: {f_fx(hdr_sun_r)} {f_fx(hdr_sun_g)} {f_fx(hdr_sun_b)} (Q16.16), clip {f_fx(hdr_clip)}`)
|
||||
sun_color = v3_new(hdr_sun_r * sky_sun_boost, hdr_sun_g * sky_sun_boost, hdr_sun_b * sky_sun_boost)
|
||||
print(`sun irradiance: {fixed(hdr_sun_r)} {fixed(hdr_sun_g)} {fixed(hdr_sun_b)} (Q16.16), clip {fixed(hdr_clip)}`)
|
||||
print(`sky: {sky_w}x{sky_h}, sun at texel {hdr_max_x},{hdr_max_y}`)
|
||||
sky_fullscreen = mesh_fullscreen()
|
||||
if sky_start_yaw != 0 { sky_set_yaw(sky_start_yaw) } else { sky_precompute() }
|
||||
if sky_start_yaw != 0.0 { sky_set_yaw(sky_start_yaw) } else { sky_precompute() }
|
||||
return true
|
||||
}
|
||||
|
||||
function sky_convolve(prog: int, target_tex: int, layer: int, w: int, h: int, rough: int) -> void {
|
||||
function sky_convolve(prog: int, target_tex: int, layer: int, w: int, h: int, rough: float) -> void {
|
||||
let fbo = gpu_fb_new()
|
||||
gpu_fb_bind(fbo)
|
||||
if layer < 0 { gpu_fb_color(0, target_tex) }
|
||||
|
|
@ -87,7 +87,7 @@ function sky_convolve(prog: int, target_tex: int, layer: int, w: int, h: int, ro
|
|||
sky_bind_rot(prog)
|
||||
u_f(gpu_uniform(prog, "u_sun_clip"), hdr_clip)
|
||||
u_f(gpu_uniform(prog, "u_rough"), rough)
|
||||
u_f(gpu_uniform(prog, "u_sky_w"), fi(sky_w))
|
||||
u_f(gpu_uniform(prog, "u_sky_w"), float(sky_w))
|
||||
mesh_draw(sky_fullscreen)
|
||||
gpu_fb_bind(0)
|
||||
gpu_fb_free(fbo)
|
||||
|
|
@ -115,7 +115,7 @@ function sky_precompute() -> void {
|
|||
sky_irradiance = tex_target(128, 64, GL_RGB16F, GL_RGB, GL_FLOAT, GL_LINEAR)
|
||||
gpu_tex_bind(GPU_TEX2D, sky_irradiance)
|
||||
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
|
||||
sky_convolve(p_irr, sky_irradiance, -1, 128, 64, F_ZERO)
|
||||
sky_convolve(p_irr, sky_irradiance, -1, 128, 64, 0.0)
|
||||
# prefiltered specular: 6 roughness levels, sky_prefilter_w x half each, as a 2D array
|
||||
let p_pre = sky_p_pre
|
||||
sky_prefilter = gpu_tex_new()
|
||||
|
|
@ -126,12 +126,12 @@ function sky_precompute() -> void {
|
|||
gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
||||
gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
|
||||
for l in 0 .. SKY_PREFILTER_LEVELS {
|
||||
sky_convolve(p_pre, sky_prefilter, l, sky_prefilter_w, sky_prefilter_w / 2, fr(l, SKY_PREFILTER_LEVELS - 1))
|
||||
sky_convolve(p_pre, sky_prefilter, l, sky_prefilter_w, sky_prefilter_w / 2, float(l) / float(SKY_PREFILTER_LEVELS - 1))
|
||||
}
|
||||
# BRDF LUT
|
||||
let p_brdf = sky_p_brdf
|
||||
sky_brdf = tex_target(256, 256, GL_RG16F, GL_RG, GL_FLOAT, GL_LINEAR)
|
||||
sky_convolve(p_brdf, sky_brdf, -1, 256, 256, F_ZERO)
|
||||
sky_convolve(p_brdf, sky_brdf, -1, 256, 256, 0.0)
|
||||
sky_baked = true
|
||||
sky_baked_yaw = sky_yaw
|
||||
gpu_check("sky precompute")
|
||||
|
|
@ -145,6 +145,6 @@ function sky_bind_lighting(prog: int) -> void {
|
|||
u_v3(gpu_uniform(prog, "u_sun_dir"), sun_dir)
|
||||
u_v3(gpu_uniform(prog, "u_sun_color"), sun_color)
|
||||
u_v3(gpu_uniform(prog, "u_cam_pos"), cam_pos)
|
||||
u_f(gpu_uniform(prog, "u_prefilter_levels"), fi(SKY_PREFILTER_LEVELS))
|
||||
u_f(gpu_uniform(prog, "u_prefilter_levels"), float(SKY_PREFILTER_LEVELS))
|
||||
daylight_bind(prog)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,14 +19,14 @@ property Chunk {
|
|||
used: int = 0, # the walk that last wanted it (for eviction)
|
||||
data: words, # INST_FLOATS per instance
|
||||
count: int = 0,
|
||||
ymin: int = 0, # height range of its instances (float bits), for the frustum test
|
||||
ymax: int = 0
|
||||
ymin: float = 0.0, # height range of its instances (float bits), for the frustum test
|
||||
ymax: float = 0.0
|
||||
}
|
||||
property Stream {
|
||||
layer: Layer,
|
||||
size: int = 0, # chunk size (metres, float bits)
|
||||
reach: int = 0, # radius (metres, float bits)
|
||||
bands: words, # band outer radii (float bits), ascending; 4 of them
|
||||
size: float = 0.0, # chunk size (metres, float bits)
|
||||
reach: float = 0.0, # radius (metres, float bits)
|
||||
bands: floats, # band outer radii (float bits), ascending; 4 of them
|
||||
chunks: []Chunk,
|
||||
keys: words, # parallel to chunks for lookup
|
||||
n: int = 0,
|
||||
|
|
@ -51,7 +51,7 @@ var stream_us_gather: long = 0 # copying cached chunks into the layer buffer
|
|||
var stream_us_walk: long = 0 # the ring walk itself
|
||||
var stream_walks: int = 0 # streams that walked their whole ring this frame
|
||||
|
||||
function stream_new(layer: Layer, size: int, reach: int, b0: int, b1: int, b2: int, b3: int) -> Stream {
|
||||
function stream_new(layer: Layer, size: float, reach: float, b0: float, b1: float, b2: float, b3: float) -> Stream {
|
||||
if not stream_cap_read {
|
||||
stream_cap_read = true
|
||||
if r3d_env_has("R3D_STREAM_CAP") { STREAM_MAX_CHUNKS = Text.to_int(r3d_env("R3D_STREAM_CAP")) }
|
||||
|
|
@ -61,7 +61,7 @@ function stream_new(layer: Layer, size: int, reach: int, b0: int, b1: int, b2: i
|
|||
s.layer = layer; s.size = size; s.reach = reach
|
||||
layer.streamed = true
|
||||
layer.grounded = true
|
||||
s.bands = words(4)
|
||||
s.bands = floats(4)
|
||||
s.bands[0] = b0; s.bands[1] = b1; s.bands[2] = b2; s.bands[3] = b3
|
||||
s.chunks = new []Chunk
|
||||
s.keys = words(STREAM_MAX_CHUNKS)
|
||||
|
|
@ -108,23 +108,23 @@ function stream_remember(s: Stream, key: int, idx: int) -> void {
|
|||
# chunk gets an exactly-sized copy when the fill ends)
|
||||
const STREAM_CHUNK_MAX: int = 262144
|
||||
var stream_debug_n: int = 0
|
||||
var stream_scratch: words = null
|
||||
function stream_emit(s: Stream, x: int, y: int, z: int, scale: int, yaw: int, seed: int, wind: int) -> void {
|
||||
var stream_scratch: floats = null
|
||||
function stream_emit(s: Stream, x: float, y: float, z: float, scale: float, yaw: float, seed: float, wind: float) -> void {
|
||||
let c = s.cur
|
||||
if c.count >= STREAM_CHUNK_MAX { return }
|
||||
if stream_scratch == null { stream_scratch = words(STREAM_CHUNK_MAX * INST_FLOATS) }
|
||||
if c.count == 0 { c.ymin = y; c.ymax = y } else { c.ymin = f_min(c.ymin, y); c.ymax = f_max(c.ymax, y) }
|
||||
if stream_scratch == null { stream_scratch = floats(STREAM_CHUNK_MAX * INST_FLOATS) }
|
||||
if c.count == 0 { c.ymin = y; c.ymax = y } else { c.ymin = Math.min(c.ymin, y); c.ymax = Math.max(c.ymax, y) }
|
||||
let o = c.count * INST_FLOATS
|
||||
stream_scratch[o] = x; stream_scratch[o + 1] = y; stream_scratch[o + 2] = z; stream_scratch[o + 3] = scale
|
||||
stream_scratch[o + 4] = f_sin(yaw); stream_scratch[o + 5] = f_cos(yaw); stream_scratch[o + 6] = seed; stream_scratch[o + 7] = wind
|
||||
stream_scratch[o + 4] = Math.sin(yaw); stream_scratch[o + 5] = Math.cos(yaw); stream_scratch[o + 6] = seed; stream_scratch[o + 7] = wind
|
||||
c.count += 1
|
||||
}
|
||||
|
||||
function stream_band(s: Stream, d: int) -> int {
|
||||
if f_ls(d, s.bands[0]) { return 0 }
|
||||
if f_ls(d, s.bands[1]) { return 1 }
|
||||
if f_ls(d, s.bands[2]) { return 2 }
|
||||
if f_ls(d, s.bands[3]) { return 3 }
|
||||
function stream_band(s: Stream, d: float) -> int {
|
||||
if d < s.bands[0] { return 0 }
|
||||
if d < s.bands[1] { return 1 }
|
||||
if d < s.bands[2] { return 2 }
|
||||
if d < s.bands[3] { return 3 }
|
||||
return 4
|
||||
}
|
||||
|
||||
|
|
@ -193,9 +193,9 @@ function stream_evict(s: Stream) -> void {
|
|||
stream_evictions += 1
|
||||
}
|
||||
|
||||
function stream_update(s: Stream, cam_x: int, cam_z: int) -> void {
|
||||
let ccx = f_to_int(f_floor(f_div(cam_x, s.size)))
|
||||
let ccz = f_to_int(f_floor(f_div(cam_z, s.size)))
|
||||
function stream_update(s: Stream, cam_x: float, cam_z: float) -> void {
|
||||
let ccx = int(Math.floor(cam_x / s.size))
|
||||
let ccz = int(Math.floor(cam_z / s.size))
|
||||
if ccx == s.last_cx and ccz == s.last_cz and not s.pending and s.view_gen == sc_view_gen { return }
|
||||
let first = s.last_cx == 999999
|
||||
s.view_gen = sc_view_gen
|
||||
|
|
@ -206,7 +206,7 @@ function stream_update(s: Stream, cam_x: int, cam_z: int) -> void {
|
|||
stream_walks += 1
|
||||
stream_walk_no += 1
|
||||
let tw = gl_now_us()
|
||||
let r = f_to_int(f_div(s.reach, s.size)) + 1
|
||||
let r = int(s.reach / s.size) + 1
|
||||
# rings outward from the camera's cell: the nearest chunks are generated first
|
||||
var ring = 0
|
||||
while ring <= r {
|
||||
|
|
@ -216,12 +216,12 @@ function stream_update(s: Stream, cam_x: int, cam_z: int) -> void {
|
|||
while cx <= ccx + ring {
|
||||
let edge = (cz == ccz - ring) or (cz == ccz + ring) or (cx == ccx - ring) or (cx == ccx + ring)
|
||||
if edge {
|
||||
let wx = f_mul(f_add(fi(cx), F_HALF), s.size)
|
||||
let wz = f_mul(f_add(fi(cz), F_HALF), s.size)
|
||||
let dx = f_sub(wx, cam_x); let dz = f_sub(wz, cam_z)
|
||||
let d = f_sqrt(f_add(f_mul(dx, dx), f_mul(dz, dz)))
|
||||
let wx = (float(cx) + 0.5) * s.size
|
||||
let wz = (float(cz) + 0.5) * s.size
|
||||
let dx = wx - cam_x; let dz = wz - cam_z
|
||||
let d = Math.sqrt(dx * dx + dz * dz)
|
||||
let band = stream_band(s, d)
|
||||
if band < 4 and band >= s.min_band and f_ls(d, f_add(s.reach, s.size)) {
|
||||
if band < 4 and band >= s.min_band and d < s.reach + s.size {
|
||||
let key = stream_key(cx, cz, band)
|
||||
var c = stream_find(s, key)
|
||||
if c != null { c.used = stream_walk_no }
|
||||
|
|
@ -283,12 +283,12 @@ function stream_update(s: Stream, cam_x: int, cam_z: int) -> void {
|
|||
# height range, padded for the tallest cover and for casters just outside the frame
|
||||
# whose short shadows still fall inside it.
|
||||
function stream_chunk_visible(s: Stream, cx: int, cz: int, c: Chunk) -> bool {
|
||||
let half = f_mul(s.size, F_HALF)
|
||||
let wx = f_add(f_mul(fi(cx), s.size), half)
|
||||
let wz = f_add(f_mul(fi(cz), s.size), half)
|
||||
let hy = f_mul(f_sub(c.ymax, c.ymin), F_HALF)
|
||||
let cy = f_add(c.ymin, hy)
|
||||
let r = f_add(f_sqrt(f_add(f_mul(f_mul(half, half), F_TWO), f_mul(hy, hy))), fi(8))
|
||||
let half = s.size * 0.5
|
||||
let wx = float(cx) * s.size + half
|
||||
let wz = float(cz) * s.size + half
|
||||
let hy = (c.ymax - c.ymin) * 0.5
|
||||
let cy = c.ymin + hy
|
||||
let r = Math.sqrt(half * half * 2.0 + hy * hy) + 8.0
|
||||
return cam_sphere_visible(wx, cy, wz, r)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -237,12 +237,12 @@ var gsl_consts: bytes = null
|
|||
var gsl_tags: bytes = null
|
||||
var gsl_res: bytes = null
|
||||
var gsl_inputs: bytes = null
|
||||
var gsl_prev_vp: words = null
|
||||
var gsl_prev_vp: floats = null
|
||||
var gsl_reset: bool = true
|
||||
var gsl_jitter_x: int = 0 # float bits, NDC offsets the projection carries this frame
|
||||
var gsl_jitter_y: int = 0
|
||||
var gsl_jpx: int = 0 # float bits, the same in pixels
|
||||
var gsl_jpy: int = 0
|
||||
var gsl_jitter_x: float = 0.0 # float bits, NDC offsets the projection carries this frame
|
||||
var gsl_jitter_y: float = 0.0
|
||||
var gsl_jpx: float = 0.0 # float bits, the same in pixels
|
||||
var gsl_jpy: float = 0.0
|
||||
var gsl_eval_ok: bool = true # the last evaluate worked: only then is the next frame jittered
|
||||
|
||||
# R3D_DLSS=0..4 overrides the setting, for a headless take
|
||||
|
|
@ -271,8 +271,8 @@ function gsl_fill_options(w: int, h: int) -> void {
|
|||
Vk.put_i32(gsl_opts, 32, gsl_sl_mode(gsl_dlss_mode))
|
||||
Vk.put_i32(gsl_opts, 36, w)
|
||||
Vk.put_i32(gsl_opts, 40, h)
|
||||
Vk.put_i32(gsl_opts, 48, F_ONE) # preExposure
|
||||
Vk.put_i32(gsl_opts, 52, F_ONE) # exposureScale
|
||||
Vk.put_i32(gsl_opts, 48, float_bits(1.0)) # preExposure
|
||||
Vk.put_i32(gsl_opts, 52, float_bits(1.0)) # exposureScale
|
||||
Vk.put_i32(gsl_opts, 56, 1) # colorBuffersHDR eTrue
|
||||
# The model: preset K (the transformer NVIDIA calls its best image quality) in every mode. The
|
||||
# defaults put Performance on preset M, which on an RTX 3070 Ti at 4K evaluated in 18 ms against
|
||||
|
|
@ -303,14 +303,14 @@ function r3d_dlss_render_w() -> int { if not r3d_dlss_live() { return gl_w }; g
|
|||
function r3d_dlss_render_h() -> int { if not r3d_dlss_live() { return gl_h }; gsl_optimal(); return gsl_rh }
|
||||
|
||||
# a radical-inverse sample in [0, 1), float bits
|
||||
function gsl_halton(i: int, b: int) -> int {
|
||||
var f = F_ONE
|
||||
var r = F_ZERO
|
||||
function gsl_halton(i: int, b: int) -> float {
|
||||
var f = 1.0
|
||||
var r = 0.0
|
||||
var k = i
|
||||
let fb = fi(b)
|
||||
let fb = float(b)
|
||||
while k > 0 {
|
||||
f = f_div(f, fb)
|
||||
r = f_add(r, f_mul(f, fi(k % b)))
|
||||
f = f / fb
|
||||
r = r + f * float(k % b)
|
||||
k = k / b
|
||||
}
|
||||
return r
|
||||
|
|
@ -318,17 +318,17 @@ function gsl_halton(i: int, b: int) -> int {
|
|||
|
||||
# cam_begin_frame: this frame's sub-pixel offset, before the camera builds its matrices
|
||||
function gsl_jitter_frame() -> void {
|
||||
gsl_jitter_x = F_ZERO; gsl_jitter_y = F_ZERO; gsl_jpx = F_ZERO; gsl_jpy = F_ZERO
|
||||
gsl_jitter_x = 0.0; gsl_jitter_y = 0.0; gsl_jpx = 0.0; gsl_jpy = 0.0
|
||||
# a jittered frame nobody resolves shakes on screen however still the camera is: jitter only while
|
||||
# this frame holds a DLSS token and the last evaluate worked. (Not gsl_fresh: gsl_frame_start has
|
||||
# already taken the token and cleared it by the time the camera asks, which turned jitter off.)
|
||||
if not r3d_dlss_live() or not gsl_eval_ok or gsl_token == null or post_w <= 0 or post_h <= 0 { return }
|
||||
# DLSS wants at least 8 x (display / render)^2 phases; 32 covers performance mode
|
||||
let i = (gsl_frame_n % 32) + 1
|
||||
gsl_jpx = f_sub(gsl_halton(i, 2), F_HALF)
|
||||
gsl_jpy = f_sub(gsl_halton(i, 3), F_HALF)
|
||||
gsl_jitter_x = f_div(f_mul(F_TWO, gsl_jpx), fi(post_w))
|
||||
gsl_jitter_y = f_div(f_mul(F_TWO, gsl_jpy), fi(post_h))
|
||||
gsl_jpx = gsl_halton(i, 2) - 0.5
|
||||
gsl_jpy = gsl_halton(i, 3) - 0.5
|
||||
gsl_jitter_x = 2.0 * gsl_jpx / float(post_w)
|
||||
gsl_jitter_y = 2.0 * gsl_jpy / float(post_h)
|
||||
}
|
||||
|
||||
# sl::Resource for one of the renderer's textures, in the layout every pass leaves them in
|
||||
|
|
@ -370,21 +370,21 @@ function gsl_tag(i: int, buffer: int, w: int, h: int) -> void {
|
|||
}
|
||||
|
||||
# a column-major matrix into a row-major sl::float4x4
|
||||
function gsl_put_m4(p: pointer, at: int, m: words) -> void {
|
||||
for r in 0 .. 4 { for c in 0 .. 4 { Vk.put_i32(p, at + (r * 4 + c) * 4, m[c * 4 + r]) } }
|
||||
function gsl_put_m4(p: pointer, at: int, m: floats) -> void {
|
||||
for r in 0 .. 4 { for c in 0 .. 4 { Vk.put_i32(p, at + (r * 4 + c) * 4, float_bits(m[c * 4 + r])) } }
|
||||
}
|
||||
function gsl_put_v3(p: pointer, at: int, v: words) -> void {
|
||||
Vk.put_i32(p, at, v[0]); Vk.put_i32(p, at + 4, v[1]); Vk.put_i32(p, at + 8, v[2])
|
||||
function gsl_put_v3(p: pointer, at: int, v: floats) -> void {
|
||||
Vk.put_i32(p, at, float_bits(v[0])); Vk.put_i32(p, at + 4, float_bits(v[1])); Vk.put_i32(p, at + 8, float_bits(v[2]))
|
||||
}
|
||||
|
||||
# The renderer's clip space is OpenGL's; what Vulkan stores is depth remapped to [0, 1] and
|
||||
# row 0 at NDC y = -1. Streamline reads images with row 0 at the top, so the matrices it is
|
||||
# given carry both: y flipped, z' = (z + w) / 2.
|
||||
function gsl_clip_fix(m: words) -> void {
|
||||
function gsl_clip_fix(m: floats) -> void {
|
||||
m4_identity(m)
|
||||
m[5] = f_neg(F_ONE)
|
||||
m[10] = F_HALF
|
||||
m[14] = F_HALF
|
||||
m[5] = -1.0
|
||||
m[10] = 0.5
|
||||
m[14] = 0.5
|
||||
}
|
||||
|
||||
function gsl_constants() -> void {
|
||||
|
|
@ -414,30 +414,30 @@ function gsl_constants() -> void {
|
|||
# the sample's offset from the pixel centre, in the image Streamline sees: row 0 at the top, so the
|
||||
# vertical offset flips with it. With jy unflipped DLSS resolved the ground into concentric
|
||||
# rings; with jx flipped too, thin stems doubled sideways (PC shots, 2026-09-15).
|
||||
var jx = f_neg(gsl_jpx)
|
||||
var jy = f_neg(gsl_jpy)
|
||||
var jx = -gsl_jpx
|
||||
var jy = -gsl_jpy
|
||||
# R3D_DLSS_JX / R3D_DLSS_JY = -1 flip a sign, to check the convention against the picture
|
||||
if r3d_env_has("R3D_DLSS_JX") and Text.to_int(r3d_env("R3D_DLSS_JX")) < 0 { jx = f_neg(jx) }
|
||||
if r3d_env_has("R3D_DLSS_JY") and Text.to_int(r3d_env("R3D_DLSS_JY")) < 0 { jy = f_neg(jy) }
|
||||
Vk.put_i32(k, 352, jx)
|
||||
Vk.put_i32(k, 356, jy)
|
||||
Vk.put_i32(k, 360, F_ONE) # mvecScale
|
||||
Vk.put_i32(k, 364, F_ONE)
|
||||
if r3d_env_has("R3D_DLSS_JX") and Text.to_int(r3d_env("R3D_DLSS_JX")) < 0 { jx = -jx }
|
||||
if r3d_env_has("R3D_DLSS_JY") and Text.to_int(r3d_env("R3D_DLSS_JY")) < 0 { jy = -jy }
|
||||
Vk.put_i32(k, 352, float_bits(jx))
|
||||
Vk.put_i32(k, 356, float_bits(jy))
|
||||
Vk.put_i32(k, 360, float_bits(1.0)) # mvecScale
|
||||
Vk.put_i32(k, 364, float_bits(1.0))
|
||||
gsl_put_v3(k, 376, cam_pos)
|
||||
let up = words(3)
|
||||
let up = floats(3)
|
||||
v3_cross(up, cam_right, cam_fwd)
|
||||
gsl_put_v3(k, 388, up)
|
||||
gsl_put_v3(k, 400, cam_right)
|
||||
gsl_put_v3(k, 412, cam_fwd)
|
||||
Vk.put_i32(k, 424, cam_near)
|
||||
Vk.put_i32(k, 428, cam_far)
|
||||
Vk.put_i32(k, 432, cam_fov)
|
||||
Vk.put_i32(k, 436, cam_aspect)
|
||||
Vk.put_i32(k, 440, F_ZERO) # motionVectorsInvalidValue
|
||||
Vk.put_i32(k, 424, float_bits(cam_near))
|
||||
Vk.put_i32(k, 428, float_bits(cam_far))
|
||||
Vk.put_i32(k, 432, float_bits(cam_fov))
|
||||
Vk.put_i32(k, 436, float_bits(cam_aspect))
|
||||
Vk.put_i32(k, 440, float_bits(0.0)) # motionVectorsInvalidValue
|
||||
# depthInverted, cameraMotionIncluded, motionVectors3D false; reset on a cut; not orthographic,
|
||||
# not dilated, not jittered
|
||||
if gsl_reset { Vk.put_i32(k, 444, 256 * 256 * 256) }
|
||||
Vk.put_i32(k, 452, fi(40)) # minRelativeLinearDepthObjectSeparation
|
||||
Vk.put_i32(k, 452, float_bits(40.0)) # minRelativeLinearDepthObjectSeparation
|
||||
free(fix); free(proj); free(v2c); free(c2v); free(cur); free(prev); free(inv_cur); free(c2p); free(p2c); free(ident); free(up)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ const CD_G: int = 32 # cells per patch side
|
|||
const CD_LEVELS: int = 9 # 32 m leaves .. 8192 m root
|
||||
const CD_LEAVES: int = 256 # leaf patches per side (8192 / 32)
|
||||
var cd_mesh: Mesh = null
|
||||
var cd_range: words = null # float bits: how far each level is drawn
|
||||
var cd_min: []words = null # per level: min height of each patch (float bits)
|
||||
var cd_max: []words = null
|
||||
var cd_range: floats = null # float bits: how far each level is drawn
|
||||
var cd_min: []floats = null # per level: min height of each patch (float bits)
|
||||
var cd_max: []floats = null
|
||||
var cd_draws: int = 0
|
||||
var cd_far_draws: int = 0
|
||||
var cd_near_draws: int = 0
|
||||
|
|
@ -24,7 +24,7 @@ var ter_force_near: bool = false
|
|||
var ter_skip: bool = false
|
||||
|
||||
var ter_height_tex: int = 0
|
||||
var ter_heights: words = null # CPU copy, float bits, TERRAIN_RES^2
|
||||
var ter_heights: floats = null # CPU copy, float bits, TERRAIN_RES^2
|
||||
var ter_reflect: bool = false # drawing the reflection: the mid mesh is plenty
|
||||
var ter_prog: int = 0
|
||||
# The far tier compiled on its own (FAR_ONLY). A patch that lies entirely beyond the
|
||||
|
|
@ -42,33 +42,33 @@ var ter_sun_pass: bool = false # selection is drawing the visibility pass
|
|||
var ter_sun_tex: int = 0
|
||||
var ter_prog_cur: int = 0 # the program currently bound during selection
|
||||
var ter_smooth: bool = false # generate the analytic test ground instead of a survey
|
||||
var ter_far_split: int = 0 # metres: beyond this the terrain takes its cheap far path (R3D_TFAR)
|
||||
var ter_far_band: int = 0 # half-width of the near/far blend (R3D_TBAND)
|
||||
var ter_snow_line: int = 0
|
||||
var ter_far_split: float = 0.0 # metres: beyond this the terrain takes its cheap far path (R3D_TFAR)
|
||||
var ter_far_band: float = 0.0 # half-width of the near/far blend (R3D_TBAND)
|
||||
var ter_snow_line: float = 0.0
|
||||
# 0 dry .. 1 soaked. The game sets it from the weather and lets it dry out.
|
||||
var ter_wet: int = 0
|
||||
var ter_wet: float = 0.0
|
||||
var ter_tex: words = null # 11 material textures (see terrain_bind)
|
||||
var ter_ox: int = 0 # world x/z of the terrain centre (float bits)
|
||||
var ter_oz: int = 0
|
||||
var ter_ox: float = 0.0 # world x/z of the terrain centre (float bits)
|
||||
var ter_oz: float = 0.0
|
||||
var ter_dem_tex: int = 0 # a real height map (16-bit), or 0 for the procedural valley
|
||||
var ter_dem_blur: int = 0 # gaussian texels applied to the survey (0 for lidar; ~3 for 30 m data)
|
||||
var ter_dem_min: int = 0
|
||||
var ter_dem_max: int = 0
|
||||
var ter_dem_base: int = 0
|
||||
var ter_dem_blur: float = 0.0 # gaussian texels applied to the survey (0 for lidar; ~3 for 30 m data)
|
||||
var ter_dem_min: float = 0.0
|
||||
var ter_dem_max: float = 0.0
|
||||
var ter_dem_base: float = 0.0
|
||||
var ter_ortho_tex: int = 0 # a photograph of the same window, draped with distance
|
||||
var ter_carpet: int = 0 # the distant-grass carpet (carpet_bake), 0 = none
|
||||
var ter_shadow_tex: int = 0 # height-field sun shadow: RG32F (lowest lit height, occluder distance)
|
||||
var ter_shadow_yaw: int = 0x7fffffff # the sky yaw it was baked for
|
||||
var ter_shadow_yaw: float = 1000000000.0 # the sky yaw it was baked for
|
||||
var ter_shadow_gen: int = -1 # the daylight generation it was baked for
|
||||
var ter_shadow_prog: int = 0
|
||||
function terrain_set_carpet(tex: int) -> void { ter_carpet = tex }
|
||||
var ter_lake_level: int = 0 # a lake carved into the height map (float bits; ex = 0 → none)
|
||||
var ter_lake_cx: int = 0
|
||||
var ter_lake_cz: int = 0
|
||||
var ter_lake_ex: int = 0
|
||||
var ter_lake_ez: int = 0
|
||||
var ter_lake_level: float = 0.0 # a lake carved into the height map (float bits; ex = 0 → none)
|
||||
var ter_lake_cx: float = 0.0
|
||||
var ter_lake_cz: float = 0.0
|
||||
var ter_lake_ex: float = 0.0
|
||||
var ter_lake_ez: float = 0.0
|
||||
# Carve a lake bed below `level` inside the ellipse (cx, cz) ± (ex, ez); call before r3d_init.
|
||||
function terrain_lake(level: int, cx: int, cz: int, ex: int, ez: int) -> void {
|
||||
function terrain_lake(level: float, cx: float, cz: float, ex: float, ez: float) -> void {
|
||||
ter_lake_level = level; ter_lake_cx = cx; ter_lake_cz = cz; ter_lake_ex = ex; ter_lake_ez = ez
|
||||
}
|
||||
# Whether the generator carves that bed. A height map that already carries a shaped bed
|
||||
|
|
@ -81,11 +81,11 @@ function terrain_lake_carve(on: bool) -> void { ter_lake_carve = on }
|
|||
# carved lake's. They were one number, which holds only while the lake is at sea level: a
|
||||
# lake eighty metres up the valley drowned the whole coast toward its own surface. Unset,
|
||||
# the sea is the lake exactly as before, so a map with one water line needs no call.
|
||||
var ter_sea_level: int = 0
|
||||
var ter_sea_level: float = 0.0
|
||||
var ter_sea_set: bool = false
|
||||
function terrain_sea(level: int) -> void { ter_sea_level = level; ter_sea_set = true }
|
||||
function terrain_sea(level: float) -> void { ter_sea_level = level; ter_sea_set = true }
|
||||
# the level the generator scales the coast toward (it always read the lake's, set or not)
|
||||
function ter_sea_gen() -> int { if ter_sea_set { return ter_sea_level }; return ter_lake_level }
|
||||
function ter_sea_gen() -> float { if ter_sea_set { return ter_sea_level }; return ter_lake_level }
|
||||
# An island: land out to `r` from (cx, cz), then the terrain scaled down into the water
|
||||
# over `fall` metres and on down to a shelf. Scaling rather than blending to a fixed bed is
|
||||
# what makes the coastline come out of the terrain that is already there — low ground turns
|
||||
|
|
@ -93,15 +93,15 @@ function ter_sea_gen() -> int { if ter_sea_set { return ter_sea_level }; return
|
|||
const TER_ISLE_NONE: int = 0
|
||||
const TER_ISLE_RADIAL: int = 1
|
||||
const TER_ISLE_COAST: int = 2
|
||||
var ter_isle_cx: int = 0
|
||||
var ter_isle_cz: int = 0
|
||||
var ter_isle_r: int = 0
|
||||
var ter_isle_fall: int = 0
|
||||
var ter_isle_cx: float = 0.0
|
||||
var ter_isle_cz: float = 0.0
|
||||
var ter_isle_r: float = 0.0
|
||||
var ter_isle_fall: float = 0.0
|
||||
var ter_isle_mode: int = 0
|
||||
function terrain_island(cx: int, cz: int, r: int, fall: int) -> void {
|
||||
function terrain_island(cx: float, cz: float, r: float, fall: float) -> void {
|
||||
ter_isle_cx = cx; ter_isle_cz = cz; ter_isle_r = r; ter_isle_fall = fall
|
||||
ter_isle_mode = TER_ISLE_RADIAL
|
||||
if r == 0 { ter_isle_mode = TER_ISLE_NONE }
|
||||
if r == 0.0 { ter_isle_mode = TER_ISLE_NONE }
|
||||
}
|
||||
# The other way to make an island, and the one a real survey usually wants: put the sea
|
||||
# around the survey's OWN EDGE rather than cutting a circle out of the middle of it.
|
||||
|
|
@ -116,10 +116,10 @@ function terrain_island(cx: int, cz: int, r: int, fall: int) -> void {
|
|||
#
|
||||
# The band is wobbled by low-frequency noise so the coastline is headlands and bays rather
|
||||
# than the square the data arrived in. `margin = 0` leaves the survey alone.
|
||||
function terrain_coast(cx: int, cz: int, margin: int, fall: int) -> void {
|
||||
function terrain_coast(cx: float, cz: float, margin: float, fall: float) -> void {
|
||||
ter_isle_cx = cx; ter_isle_cz = cz; ter_isle_r = margin; ter_isle_fall = fall
|
||||
ter_isle_mode = TER_ISLE_COAST
|
||||
if margin == 0 { ter_isle_mode = TER_ISLE_NONE }
|
||||
if margin == 0.0 { ter_isle_mode = TER_ISLE_NONE }
|
||||
}
|
||||
|
||||
# Use a real place: a 16-bit PNG height map plus its elevation range (metres). The
|
||||
|
|
@ -129,12 +129,12 @@ var ter_ortho_w: int = 0
|
|||
var ter_ortho_c: int = 3
|
||||
|
||||
# the photograph's colour at world (x, z): packed 0xRRGGBB (0 outside the map)
|
||||
function terrain_ortho(x: int, z: int) -> int {
|
||||
function terrain_ortho(x: float, z: float) -> int {
|
||||
if ter_ortho_px == null { return 0 }
|
||||
if ter_o_scale == 0 { ter_o_scale = fr(ter_ortho_w, TERRAIN_HALF * 2) }
|
||||
if ter_o_scale == 0.0 { ter_o_scale = float(ter_ortho_w) / float(TERRAIN_HALF * 2) }
|
||||
let scale = ter_o_scale
|
||||
var ix = f_to_int(f_floor(f_mul(f_add(f_sub(x, ter_ox), fi(TERRAIN_HALF)), scale)))
|
||||
var iz = f_to_int(f_floor(f_mul(f_add(f_sub(z, ter_oz), fi(TERRAIN_HALF)), scale)))
|
||||
var ix = int(Math.floor((x - ter_ox + float(TERRAIN_HALF)) * scale))
|
||||
var iz = int(Math.floor((z - ter_oz + float(TERRAIN_HALF)) * scale))
|
||||
if ix < 0 { ix = 0 }; if iz < 0 { iz = 0 }
|
||||
if ix > ter_ortho_w - 1 { ix = ter_ortho_w - 1 }; if iz > ter_ortho_w - 1 { iz = ter_ortho_w - 1 }
|
||||
let o = (iz * ter_ortho_w + ix) * ter_ortho_c
|
||||
|
|
@ -144,65 +144,65 @@ function terrain_ortho(x: int, z: int) -> int {
|
|||
# one should fetch the colour once with terrain_ortho() and use the *_of forms — the
|
||||
# cover generator tests all three per candidate, so this is three fetches saved out of
|
||||
# every four in the hottest loop in the program.
|
||||
function ortho_green_of(c: int) -> int {
|
||||
function ortho_green_of(c: int) -> float {
|
||||
let r = (c >> 16) & 255; let g = (c >> 8) & 255; let b = c & 255
|
||||
var v = g - max(r, b)
|
||||
if v < 0 { v = 0 }
|
||||
return f_min(fr(v, 22), F_ONE)
|
||||
return Math.min(float(v) / 22.0, 1.0)
|
||||
}
|
||||
function ortho_scree_of(c: int) -> int {
|
||||
if c == 0 { return F_ZERO }
|
||||
function ortho_scree_of(c: int) -> float {
|
||||
if c == 0 { return 0.0 }
|
||||
let r = (c >> 16) & 255; let g = (c >> 8) & 255; let b = c & 255
|
||||
let mx = max(r, max(g, b))
|
||||
if g - max(r, b) > 2 or mx < 60 { return F_ZERO }
|
||||
return F_ONE
|
||||
if g - max(r, b) > 2 or mx < 60 { return 0.0 }
|
||||
return 1.0
|
||||
}
|
||||
function ortho_forest_of(c: int) -> int {
|
||||
if c == 0 { return F_ZERO }
|
||||
function ortho_forest_of(c: int) -> float {
|
||||
if c == 0 { return 0.0 }
|
||||
let r = (c >> 16) & 255; let g = (c >> 8) & 255; let b = c & 255
|
||||
let mx = max(r, max(g, b))
|
||||
if g - max(r, b) < 3 { return F_ZERO }
|
||||
if mx <= 80 { return F_ONE }
|
||||
if mx <= 105 { return F_HALF }
|
||||
return F_ZERO
|
||||
if g - max(r, b) < 3 { return 0.0 }
|
||||
if mx <= 80 { return 1.0 }
|
||||
if mx <= 105 { return 0.5 }
|
||||
return 0.0
|
||||
}
|
||||
|
||||
# how green the ground is in the photograph (0..1 float bits): meadow / forest vs rock, scree, water
|
||||
function terrain_ortho_green(x: int, z: int) -> int {
|
||||
function terrain_ortho_green(x: float, z: float) -> float {
|
||||
let c = terrain_ortho(x, z)
|
||||
let r = (c >> 16) & 255; let g = (c >> 8) & 255; let b = c & 255
|
||||
var v = g - max(r, b) # green excess
|
||||
if v < 0 { v = 0 }
|
||||
return f_min(fr(v, 22), F_ONE)
|
||||
return Math.min(float(v) / 22.0, 1.0)
|
||||
}
|
||||
# how grey and mid-bright (scree / pebbles / bare rock) the photograph is there (0..1)
|
||||
# Bare ground only where most of a 50 m neighbourhood is bare: a single 10 m trail pixel
|
||||
# must not place a boulder or bar a tree.
|
||||
function terrain_ortho_scree(x: int, z: int) -> int {
|
||||
function terrain_ortho_scree(x: float, z: float) -> float {
|
||||
var votes = 0
|
||||
for j in 0 .. 5 { for i in 0 .. 5 { if ortho_scree_of(terrain_ortho(f_add(x, fi((i - 2) * 10)), f_add(z, fi((j - 2) * 10)))) != F_ZERO { votes += 1 } } }
|
||||
if votes >= 15 { return F_ONE }
|
||||
return F_ZERO
|
||||
for j in 0 .. 5 { for i in 0 .. 5 { if ortho_scree_of(terrain_ortho(x + float((i - 2) * 10), z + float((j - 2) * 10))) != 0.0 { votes += 1 } } }
|
||||
if votes >= 15 { return 1.0 }
|
||||
return 0.0
|
||||
}
|
||||
function terrain_ortho_scree_pixel(x: int, z: int) -> int {
|
||||
function terrain_ortho_scree_pixel(x: float, z: float) -> float {
|
||||
let c = terrain_ortho(x, z)
|
||||
if c == 0 { return F_ZERO }
|
||||
if c == 0 { return 0.0 }
|
||||
let r = (c >> 16) & 255; let g = (c >> 8) & 255; let b = c & 255
|
||||
let mx = max(r, max(g, b))
|
||||
# bare ground: not green-dominant (grey scree, the maroon rock, the moraine's tan gravel), lit enough not to be water
|
||||
if g - max(r, b) > 2 or mx < 60 { return F_ZERO }
|
||||
return F_ONE
|
||||
if g - max(r, b) > 2 or mx < 60 { return 0.0 }
|
||||
return 1.0
|
||||
}
|
||||
# dense conifer forest in the photograph: green-dominant and dark (the meadows are brighter)
|
||||
function terrain_ortho_forest(x: int, z: int) -> int {
|
||||
function terrain_ortho_forest(x: float, z: float) -> float {
|
||||
let c = terrain_ortho(x, z)
|
||||
if c == 0 { return F_ZERO }
|
||||
if c == 0 { return 0.0 }
|
||||
let r = (c >> 16) & 255; let g = (c >> 8) & 255; let b = c & 255
|
||||
let mx = max(r, max(g, b))
|
||||
if g - max(r, b) < 3 { return F_ZERO }
|
||||
if mx <= 80 { return F_ONE }
|
||||
if mx <= 105 { return F_HALF }
|
||||
return F_ZERO
|
||||
if g - max(r, b) < 3 { return 0.0 }
|
||||
if mx <= 80 { return 1.0 }
|
||||
if mx <= 105 { return 0.5 }
|
||||
return 0.0
|
||||
}
|
||||
|
||||
function terrain_use_ortho(path: string) -> void {
|
||||
|
|
@ -216,7 +216,7 @@ function terrain_use_ortho(path: string) -> void {
|
|||
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
|
||||
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
|
||||
}
|
||||
function terrain_use_dem(path: string, emin: int, emax: int, base: int, ox: int, oz: int) -> void {
|
||||
function terrain_use_dem(path: string, emin: float, emax: float, base: float, ox: float, oz: float) -> void {
|
||||
ter_dem_tex = tex_load(path, false)
|
||||
ter_dem_min = emin; ter_dem_max = emax; ter_dem_base = base
|
||||
ter_ox = ox; ter_oz = oz
|
||||
|
|
@ -237,7 +237,7 @@ function terrain_generate() -> void {
|
|||
gpu_viewport(0, 0, TERRAIN_RES, TERRAIN_RES)
|
||||
gpu_depth_test(false)
|
||||
gpu_use_program(p)
|
||||
u_f(gpu_uniform(p, "u_half"), fi(TERRAIN_HALF))
|
||||
u_f(gpu_uniform(p, "u_half"), float(TERRAIN_HALF))
|
||||
if ter_dem_tex != 0 {
|
||||
r3d_bind_2d(p, "u_dem", 0, ter_dem_tex)
|
||||
u_f(gpu_uniform(p, "u_dem_min"), ter_dem_min)
|
||||
|
|
@ -246,13 +246,13 @@ function terrain_generate() -> void {
|
|||
u_f2(gpu_uniform(p, "u_origin"), ter_ox, ter_oz)
|
||||
u_f4(gpu_uniform(p, "u_lake"), ter_lake_cx, ter_lake_cz, ter_lake_ex, ter_lake_ez)
|
||||
u_f(gpu_uniform(p, "u_lake_level"), ter_lake_level)
|
||||
var carve = F_ONE
|
||||
if not ter_lake_carve { carve = F_ZERO }
|
||||
var carve = 1.0
|
||||
if not ter_lake_carve { carve = 0.0 }
|
||||
u_f(gpu_uniform(p, "u_lake_carve"), carve)
|
||||
u_f(gpu_uniform(p, "u_sea_level"), ter_sea_gen())
|
||||
u_f4(gpu_uniform(p, "u_isle"), ter_isle_cx, ter_isle_cz, ter_isle_r, ter_isle_fall)
|
||||
u_f(gpu_uniform(p, "u_isle_mode"), fi(ter_isle_mode))
|
||||
u_f(gpu_uniform(p, "u_dem_blur"), ter_dem_blur)
|
||||
u_f(gpu_uniform(p, "u_isle_mode"), float(ter_isle_mode))
|
||||
u_f(gpu_uniform(p, "u_dem_blur"), float(ter_dem_blur))
|
||||
}
|
||||
mesh_draw(sky_fullscreen)
|
||||
# second pass: R = height, GBA = the smooth surface normal, baked once (ternormal.frag)
|
||||
|
|
@ -262,12 +262,12 @@ function terrain_generate() -> void {
|
|||
let pn = r3d_program("fullscreen.vert", "ternormal.frag", "")
|
||||
gpu_use_program(pn)
|
||||
r3d_bind_2d(pn, "u_src", 0, raw)
|
||||
u_f(gpu_uniform(pn, "u_half"), fi(TERRAIN_HALF))
|
||||
u_f(gpu_uniform(pn, "u_half"), float(TERRAIN_HALF))
|
||||
mesh_draw(sky_fullscreen)
|
||||
gpu_program_free(pn)
|
||||
gpu_tex_free(raw)
|
||||
# read the heights back for placement
|
||||
ter_heights = words(TERRAIN_RES * TERRAIN_RES)
|
||||
ter_heights = floats(TERRAIN_RES * TERRAIN_RES)
|
||||
gpu_tex_bind(GPU_TEX2D, ter_height_tex)
|
||||
gpu_pixel_store(GL_PACK_ALIGNMENT, 4)
|
||||
gpu_tex_read(GPU_TEX2D, GL_RED, GL_FLOAT, ter_heights)
|
||||
|
|
@ -280,7 +280,7 @@ function terrain_generate() -> void {
|
|||
# The height field for shaders that place things on the ground (model.vert's u_ground)
|
||||
function terrain_bind_height(p: int) -> void {
|
||||
r3d_bind_2d(p, "u_ts_height", 5, ter_height_tex)
|
||||
u_f(gpu_uniform(p, "u_ts_half"), fi(TERRAIN_HALF))
|
||||
u_f(gpu_uniform(p, "u_ts_half"), float(TERRAIN_HALF))
|
||||
u_f2(gpu_uniform(p, "u_ts_origin"), ter_ox, ter_oz)
|
||||
}
|
||||
|
||||
|
|
@ -299,7 +299,7 @@ function terrain_bake_shadow() -> void {
|
|||
gpu_blend(false)
|
||||
gpu_use_program(p)
|
||||
r3d_bind_2d(p, "u_height", 0, ter_height_tex)
|
||||
u_f(gpu_uniform(p, "u_half"), fi(TERRAIN_HALF))
|
||||
u_f(gpu_uniform(p, "u_half"), float(TERRAIN_HALF))
|
||||
u_v3(gpu_uniform(p, "u_sun"), sun_dir)
|
||||
mesh_draw(sky_fullscreen)
|
||||
gpu_fb_bind(0)
|
||||
|
|
@ -313,66 +313,66 @@ function terrain_bake_shadow() -> void {
|
|||
# a fixed-point divide — on every call, and the cover generator calls terrain_height
|
||||
# five times per candidate (once directly, four more inside slope_at) across hundreds
|
||||
# of thousands of candidates per chunk. Hoisted, they cost nothing.
|
||||
var ter_h_scale: int = 0
|
||||
var ter_o_scale: int = 0
|
||||
var ter_h_scale: float = 0.0
|
||||
var ter_o_scale: float = 0.0
|
||||
|
||||
function terrain_height(x: int, z: int) -> int {
|
||||
if ter_h_scale == 0 { ter_h_scale = fr(TERRAIN_RES, TERRAIN_HALF * 2) }
|
||||
function terrain_height(x: float, z: float) -> float {
|
||||
if ter_h_scale == 0.0 { ter_h_scale = float(TERRAIN_RES) / float(TERRAIN_HALF * 2) }
|
||||
let scale = ter_h_scale
|
||||
let fx = f_mul(f_add(f_sub(x, ter_ox), fi(TERRAIN_HALF)), scale)
|
||||
let fz = f_mul(f_add(f_sub(z, ter_oz), fi(TERRAIN_HALF)), scale)
|
||||
var ix = f_to_int(f_floor(fx)); var iz = f_to_int(f_floor(fz))
|
||||
let fx = (x - ter_ox + float(TERRAIN_HALF)) * scale
|
||||
let fz = (z - ter_oz + float(TERRAIN_HALF)) * scale
|
||||
var ix = int(Math.floor(fx)); var iz = int(Math.floor(fz))
|
||||
if ix < 0 { ix = 0 }; if iz < 0 { iz = 0 }
|
||||
if ix > TERRAIN_RES - 2 { ix = TERRAIN_RES - 2 }; if iz > TERRAIN_RES - 2 { iz = TERRAIN_RES - 2 }
|
||||
let tx = f_clamp(f_sub(fx, fi(ix)), F_ZERO, F_ONE)
|
||||
let tz = f_clamp(f_sub(fz, fi(iz)), F_ZERO, F_ONE)
|
||||
let tx = Math.clamp(fx - float(ix), 0.0, 1.0)
|
||||
let tz = Math.clamp(fz - float(iz), 0.0, 1.0)
|
||||
let h00 = ter_heights[iz * TERRAIN_RES + ix]
|
||||
let h10 = ter_heights[iz * TERRAIN_RES + ix + 1]
|
||||
let h01 = ter_heights[(iz + 1) * TERRAIN_RES + ix]
|
||||
let h11 = ter_heights[(iz + 1) * TERRAIN_RES + ix + 1]
|
||||
return f_lerp(f_lerp(h00, h10, tx), f_lerp(h01, h11, tx), tz)
|
||||
return Math.lerp(Math.lerp(h00, h10, tx), Math.lerp(h01, h11, tx), tz)
|
||||
}
|
||||
# the same for Q16.16 callers
|
||||
function terrain_height_fx(x: fixed, z: fixed) -> fixed { return f_fx(terrain_height(fl(x), fl(z))) }
|
||||
function terrain_height_fx(x: fixed, z: fixed) -> fixed { return fixed(terrain_height(float(x), float(z))) }
|
||||
|
||||
# The height the terrain is DRAWN at: the cubic B-spline of the texels (heightSmooth in
|
||||
# terrain.vert), not the bilinear read above. The two differ by up to half a metre on
|
||||
# rough ground, which is the difference between a character standing on the meadow
|
||||
# and one buried to the knee in it. Sixteen taps; for things that move, not for the
|
||||
# thousands of placement queries a chunk makes.
|
||||
var ter_bw: words = null
|
||||
function terrain_height_smooth(x: int, z: int) -> int {
|
||||
if ter_h_scale == 0 { ter_h_scale = fr(TERRAIN_RES, TERRAIN_HALF * 2) }
|
||||
if ter_bw == null { ter_bw = words(8) }
|
||||
var ter_bw: floats = null
|
||||
function terrain_height_smooth(x: float, z: float) -> float {
|
||||
if ter_h_scale == 0.0 { ter_h_scale = float(TERRAIN_RES) / float(TERRAIN_HALF * 2) }
|
||||
if ter_bw == null { ter_bw = floats(8) }
|
||||
let scale = ter_h_scale
|
||||
let fx = f_sub(f_mul(f_add(f_sub(x, ter_ox), fi(TERRAIN_HALF)), scale), F_HALF)
|
||||
let fz = f_sub(f_mul(f_add(f_sub(z, ter_oz), fi(TERRAIN_HALF)), scale), F_HALF)
|
||||
let ix = f_to_int(f_floor(fx)); let iz = f_to_int(f_floor(fz))
|
||||
let tx = f_clamp(f_sub(fx, fi(ix)), F_ZERO, F_ONE)
|
||||
let tz = f_clamp(f_sub(fz, fi(iz)), F_ZERO, F_ONE)
|
||||
let fx = (x - ter_ox + float(TERRAIN_HALF)) * scale - 0.5
|
||||
let fz = (z - ter_oz + float(TERRAIN_HALF)) * scale - 0.5
|
||||
let ix = int(Math.floor(fx)); let iz = int(Math.floor(fz))
|
||||
let tx = Math.clamp(fx - float(ix), 0.0, 1.0)
|
||||
let tz = Math.clamp(fz - float(iz), 0.0, 1.0)
|
||||
# the four cubic B-spline weights of a fraction, over texels i-1 .. i+2
|
||||
for a in 0 .. 2 {
|
||||
var t = tx
|
||||
if a == 1 { t = tz }
|
||||
let t2 = f_mul(t, t); let t3 = f_mul(t2, t)
|
||||
let one_t = f_sub(F_ONE, t)
|
||||
let w0 = f_div(f_mul(f_mul(one_t, one_t), one_t), fi(6))
|
||||
let w1 = f_div(f_add(f_sub(fi(4), f_mul(fi(6), t2)), f_mul(fi(3), t3)), fi(6))
|
||||
let w3 = f_div(t3, fi(6))
|
||||
let w2 = f_sub(f_sub(f_sub(F_ONE, w0), w1), w3)
|
||||
let t2 = t * t; let t3 = t2 * t
|
||||
let one_t = 1.0 - t
|
||||
let w0 = one_t * one_t * one_t / 6.0
|
||||
let w1 = (4.0 - 6.0 * t2 + 3.0 * t3) / 6.0
|
||||
let w3 = t3 / 6.0
|
||||
let w2 = 1.0 - w0 - w1 - w3
|
||||
ter_bw[a * 4] = w0; ter_bw[a * 4 + 1] = w1; ter_bw[a * 4 + 2] = w2; ter_bw[a * 4 + 3] = w3
|
||||
}
|
||||
var h = F_ZERO
|
||||
var h = 0.0
|
||||
for j in 0 .. 4 {
|
||||
var rz = iz - 1 + j
|
||||
if rz < 0 { rz = 0 }; if rz > TERRAIN_RES - 1 { rz = TERRAIN_RES - 1 }
|
||||
var row = F_ZERO
|
||||
var row = 0.0
|
||||
for i in 0 .. 4 {
|
||||
var rx = ix - 1 + i
|
||||
if rx < 0 { rx = 0 }; if rx > TERRAIN_RES - 1 { rx = TERRAIN_RES - 1 }
|
||||
row = f_add(row, f_mul(ter_bw[i], ter_heights[rz * TERRAIN_RES + rx]))
|
||||
row = row + ter_bw[i] * ter_heights[rz * TERRAIN_RES + rx]
|
||||
}
|
||||
h = f_add(h, f_mul(ter_bw[4 + j], row))
|
||||
h = h + ter_bw[4 + j] * row
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
|
@ -445,11 +445,11 @@ function terrain_init_finish() -> void {
|
|||
ter_prog_far = r3d_program("terrain.vert", "terrain.frag", defs + "#define FAR_ONLY\n")
|
||||
ter_prog_near = r3d_program("terrain.vert", "terrain.frag", defs + "#define NEAR_ONLY\n")
|
||||
ter_sun_prog = r3d_program("terrain.vert", "tersun.frag", "")
|
||||
ter_far_split = fi(200)
|
||||
if r3d_env_has("R3D_TFAR") { ter_far_split = fi(Text.to_int(r3d_env("R3D_TFAR"))) }
|
||||
ter_far_band = fi(60)
|
||||
if r3d_env_has("R3D_TBAND") { ter_far_band = fi(Text.to_int(r3d_env("R3D_TBAND"))) }
|
||||
ter_snow_line = fi(880)
|
||||
ter_far_split = 200.0
|
||||
if r3d_env_has("R3D_TFAR") { ter_far_split = float(Text.to_int(r3d_env("R3D_TFAR"))) }
|
||||
ter_far_band = 60.0
|
||||
if r3d_env_has("R3D_TBAND") { ter_far_band = float(Text.to_int(r3d_env("R3D_TBAND"))) }
|
||||
ter_snow_line = 880.0
|
||||
gpu_check("terrain init")
|
||||
}
|
||||
|
||||
|
|
@ -457,7 +457,7 @@ function terrain_init_finish() -> void {
|
|||
# The terrain no longer casts into the shadow map: it shadows itself by marching its
|
||||
# own height field in terrain.frag, which cannot produce the self-shadow grid a depth
|
||||
# map does, and it saves drawing the whole grid five times a frame.
|
||||
function terrain_draw_shadow(light_vp: words) -> void {
|
||||
function terrain_draw_shadow(light_vp: floats) -> void {
|
||||
}
|
||||
|
||||
# Every per-frame uniform of one terrain program. Both tiers are bound up front so
|
||||
|
|
@ -475,19 +475,19 @@ function terrain_bind_prog(p: int) -> void {
|
|||
var orthotex = ter_ortho_tex
|
||||
if orthotex == 0 { orthotex = ter_tex[0] }
|
||||
r3d_bind_2d(p, "u_ortho", 4, orthotex)
|
||||
var oon = F_ZERO
|
||||
if ter_ortho_tex != 0 { oon = F_ONE }
|
||||
var oon = 0.0
|
||||
if ter_ortho_tex != 0 { oon = 1.0 }
|
||||
u_f(gpu_uniform(p, "u_ortho_on"), oon)
|
||||
r3d_bind_2d(p, "u_rock_d", 7, ter_tex[6]); r3d_bind_2d(p, "u_rock_n", 8, ter_tex[7]); r3d_bind_2d(p, "u_rock_a", 9, ter_tex[8])
|
||||
r3d_bind_2d(p, "u_snow_d", 10, ter_tex[9])
|
||||
if ter_carpet != 0 { r3d_bind_2d(p, "u_carpet", 11, ter_carpet); u_f(gpu_uniform(p, "u_carpet_on"), F_ONE) }
|
||||
else { r3d_bind_2d(p, "u_carpet", 11, ter_tex[0]); u_f(gpu_uniform(p, "u_carpet_on"), F_ZERO) }
|
||||
u_f(gpu_uniform(p, "u_half"), fi(TERRAIN_HALF))
|
||||
u_f(gpu_uniform(p, "u_texel"), fr(1, TERRAIN_RES))
|
||||
if ter_carpet != 0 { r3d_bind_2d(p, "u_carpet", 11, ter_carpet); u_f(gpu_uniform(p, "u_carpet_on"), 1.0) }
|
||||
else { r3d_bind_2d(p, "u_carpet", 11, ter_tex[0]); u_f(gpu_uniform(p, "u_carpet_on"), 0.0) }
|
||||
u_f(gpu_uniform(p, "u_half"), float(TERRAIN_HALF))
|
||||
u_f(gpu_uniform(p, "u_texel"), 1.0 / float(TERRAIN_RES))
|
||||
u_f(gpu_uniform(p, "u_snow_line"), ter_snow_line)
|
||||
u_f(gpu_uniform(p, "u_wet"), ter_wet)
|
||||
var lake = fl(-100000.0)
|
||||
if ter_lake_ex != 0 { lake = ter_lake_level }
|
||||
var lake = -100000.0
|
||||
if ter_lake_ex != 0.0 { lake = ter_lake_level }
|
||||
u_f(gpu_uniform(p, "u_lake_level"), lake)
|
||||
# the shoreline, forest and scree gates read the sea; unset it is what they always read
|
||||
var sea = lake
|
||||
|
|
@ -512,9 +512,9 @@ function terrain_bind_prog(p: int) -> void {
|
|||
# Set before fog_bind it measured as EXACTLY zero pixels changed, which is the same
|
||||
# shape of mistake as setting r3d_fog_scale before gfx_apply: the value was right and
|
||||
# something downstream put it back.
|
||||
u_f(gpu_uniform(p, "u_spec_scale"), fl(0.22))
|
||||
u_f(gpu_uniform(p, "u_spec_scale"), 0.22)
|
||||
u_v3(gpu_uniform(p, "u_cam_pos"), cam_pos)
|
||||
u_f(gpu_uniform(p, "u_grid"), fi(CD_G))
|
||||
u_f(gpu_uniform(p, "u_grid"), float(CD_G))
|
||||
# The ground reads its sun visibility out of the buffer tersun.frag filled, and has no
|
||||
# use for the cascade array shadow_bind just put on this unit; leaving both bound under
|
||||
# one unit is undefined ground, so the array comes off first.
|
||||
|
|
@ -573,12 +573,12 @@ function terrain_sun_pass(w: int, h: int, depth: int) -> Target {
|
|||
let p = ter_sun_prog
|
||||
gpu_use_program(p)
|
||||
r3d_bind_2d(p, "u_height", 0, ter_height_tex)
|
||||
u_f(gpu_uniform(p, "u_half"), fi(TERRAIN_HALF))
|
||||
u_f(gpu_uniform(p, "u_half"), float(TERRAIN_HALF))
|
||||
u_mat4(gpu_uniform(p, "u_view"), cam_view)
|
||||
u_mat4(gpu_uniform(p, "u_proj"), cam_proj)
|
||||
u_f2(gpu_uniform(p, "u_origin"), ter_ox, ter_oz)
|
||||
u_v3(gpu_uniform(p, "u_cam_pos"), cam_pos)
|
||||
u_f(gpu_uniform(p, "u_grid"), fi(CD_G))
|
||||
u_f(gpu_uniform(p, "u_grid"), float(CD_G))
|
||||
u_f(gpu_uniform(p, "u_far_split"), ter_far_split)
|
||||
u_f(gpu_uniform(p, "u_far_band"), ter_far_band)
|
||||
u_f(gpu_uniform(p, "u_clip_y"), r3d_clip_y)
|
||||
|
|
@ -648,7 +648,7 @@ function cdlod_init() -> void {
|
|||
let n = CD_G + 1
|
||||
let v = gl_floats(n * n * 2)
|
||||
var k = 0
|
||||
for j in 0 .. n { for i in 0 .. n { gl_put_bits(v, k, fr(i, CD_G)); gl_put_bits(v, k + 1, fr(j, CD_G)); k += 2 } }
|
||||
for j in 0 .. n { for i in 0 .. n { gl_put_bits(v, k, float_bits(float(i) / float(CD_G))); gl_put_bits(v, k + 1, float_bits(float(j) / float(CD_G))); k += 2 } }
|
||||
gpu_mesh_vertices(m, v, gl_bytes_of(n * n * 2), GPU_STATIC)
|
||||
gpu_mesh_attr(m, 0, 2, GPU_F32, 8, 0, false)
|
||||
free(v)
|
||||
|
|
@ -668,28 +668,28 @@ function cdlod_init() -> void {
|
|||
m.count = ni
|
||||
gpu_mesh_done(m)
|
||||
cd_mesh = m
|
||||
cd_range = words(CD_LEVELS)
|
||||
var r = fi(48)
|
||||
if r3d_env_has("R3D_CD_R0") { r = fi(Text.to_int(r3d_env("R3D_CD_R0"))) }
|
||||
for l in 0 .. CD_LEVELS { cd_range[l] = r; r = f_mul(r, F_TWO) }
|
||||
cd_range = floats(CD_LEVELS)
|
||||
var r = 48.0
|
||||
if r3d_env_has("R3D_CD_R0") { r = float(Text.to_int(r3d_env("R3D_CD_R0"))) }
|
||||
for l in 0 .. CD_LEVELS { cd_range[l] = r; r = r * 2.0 }
|
||||
cdlod_bounds()
|
||||
}
|
||||
|
||||
# min/max height per patch at every level, from the CPU copy of the height field
|
||||
function cdlod_bounds() -> void {
|
||||
cd_min = new []words; cd_max = new []words
|
||||
cd_min = new []floats; cd_max = new []floats
|
||||
let t = TERRAIN_RES / CD_LEAVES # texels per leaf patch side
|
||||
var n = CD_LEAVES
|
||||
var lo = words(n * n); var hi = words(n * n)
|
||||
var lo = floats(n * n); var hi = floats(n * n)
|
||||
for j in 0 .. n {
|
||||
for i in 0 .. n {
|
||||
var mn = fi(100000); var mx = fi(-100000)
|
||||
var mn = 100000.0; var mx = -100000.0
|
||||
for y in 0 .. t + 1 {
|
||||
let ty = min(j * t + y, TERRAIN_RES - 1)
|
||||
for x in 0 .. t + 1 {
|
||||
let tx = min(i * t + x, TERRAIN_RES - 1)
|
||||
let h = ter_heights[ty * TERRAIN_RES + tx]
|
||||
mn = f_min(mn, h); mx = f_max(mx, h)
|
||||
mn = Math.min(mn, h); mx = Math.max(mx, h)
|
||||
}
|
||||
}
|
||||
lo[j * n + i] = mn; hi[j * n + i] = mx
|
||||
|
|
@ -698,12 +698,12 @@ function cdlod_bounds() -> void {
|
|||
push(cd_min, lo); push(cd_max, hi)
|
||||
while n > 1 {
|
||||
let m = n / 2
|
||||
let plo = words(m * m); let phi = words(m * m)
|
||||
let plo = floats(m * m); let phi = floats(m * m)
|
||||
for j in 0 .. m {
|
||||
for i in 0 .. m {
|
||||
let a = (2 * j) * n + 2 * i
|
||||
plo[j * m + i] = f_min(f_min(lo[a], lo[a + 1]), f_min(lo[a + n], lo[a + n + 1]))
|
||||
phi[j * m + i] = f_max(f_max(hi[a], hi[a + 1]), f_max(hi[a + n], hi[a + n + 1]))
|
||||
plo[j * m + i] = Math.min(Math.min(lo[a], lo[a + 1]), Math.min(lo[a + n], lo[a + n + 1]))
|
||||
phi[j * m + i] = Math.max(Math.max(hi[a], hi[a + 1]), Math.max(hi[a + n], hi[a + n + 1]))
|
||||
}
|
||||
}
|
||||
push(cd_min, plo); push(cd_max, phi)
|
||||
|
|
@ -715,41 +715,41 @@ function cdlod_bounds() -> void {
|
|||
# map, so a leaf is (2 * TERRAIN_HALF) / CD_LEAVES - 32 m only on the 8192 m map the numbers
|
||||
# were chosen for. Placing patches at a fixed 32 m put every bound in the wrong place on any
|
||||
# other TERRAIN_HALF.
|
||||
function cd_size(level: int) -> int { return f_mul(fi(32 << level), fr(TERRAIN_HALF * 2, 8192)) }
|
||||
function cd_size(level: int) -> float { return float(32 << level) * (float(TERRAIN_HALF * 2) / 8192.0) }
|
||||
|
||||
# does the patch's box come within r of the camera?
|
||||
function cd_within(x0: int, z0: int, size: int, ymin: int, ymax: int, r: int) -> bool {
|
||||
let dx = f_max(f_max(f_sub(x0, cam_pos[0]), f_sub(cam_pos[0], f_add(x0, size))), F_ZERO)
|
||||
let dz = f_max(f_max(f_sub(z0, cam_pos[2]), f_sub(cam_pos[2], f_add(z0, size))), F_ZERO)
|
||||
let dy = f_max(f_max(f_sub(ymin, cam_pos[1]), f_sub(cam_pos[1], ymax)), F_ZERO)
|
||||
return f_ls(f_add(f_add(f_mul(dx, dx), f_mul(dz, dz)), f_mul(dy, dy)), f_mul(r, r))
|
||||
function cd_within(x0: float, z0: float, size: float, ymin: float, ymax: float, r: float) -> bool {
|
||||
let dx = Math.max(Math.max(x0 - cam_pos[0], cam_pos[0] - (x0 + size)), 0.0)
|
||||
let dz = Math.max(Math.max(z0 - cam_pos[2], cam_pos[2] - (z0 + size)), 0.0)
|
||||
let dy = Math.max(Math.max(ymin - cam_pos[1], cam_pos[1] - ymax), 0.0)
|
||||
return dx * dx + dz * dz + dy * dy < r * r
|
||||
}
|
||||
|
||||
# is the patch's box entirely inside r of the camera? (its farthest corner is within r)
|
||||
function cd_inside(x0: int, z0: int, size: int, ymin: int, ymax: int, r: int) -> bool {
|
||||
let x1 = f_add(x0, size)
|
||||
let z1 = f_add(z0, size)
|
||||
let dx = f_max(f_abs(f_sub(cam_pos[0], x0)), f_abs(f_sub(cam_pos[0], x1)))
|
||||
let dz = f_max(f_abs(f_sub(cam_pos[2], z0)), f_abs(f_sub(cam_pos[2], z1)))
|
||||
let dy = f_max(f_abs(f_sub(cam_pos[1], ymin)), f_abs(f_sub(cam_pos[1], ymax)))
|
||||
return f_ls(f_add(f_add(f_mul(dx, dx), f_mul(dz, dz)), f_mul(dy, dy)), f_mul(r, r))
|
||||
function cd_inside(x0: float, z0: float, size: float, ymin: float, ymax: float, r: float) -> bool {
|
||||
let x1 = x0 + size
|
||||
let z1 = z0 + size
|
||||
let dx = Math.max(Math.abs(cam_pos[0] - x0), Math.abs(cam_pos[0] - x1))
|
||||
let dz = Math.max(Math.abs(cam_pos[2] - z0), Math.abs(cam_pos[2] - z1))
|
||||
let dy = Math.max(Math.abs(cam_pos[1] - ymin), Math.abs(cam_pos[1] - ymax))
|
||||
return dx * dx + dz * dz + dy * dy < r * r
|
||||
}
|
||||
|
||||
function cdlod_draw(level: int, ix: int, iz: int) -> void {
|
||||
let size = cd_size(level)
|
||||
let x0 = f_add(f_sub(ter_ox, fi(TERRAIN_HALF)), f_mul(fi(ix), size))
|
||||
let z0 = f_add(f_sub(ter_oz, fi(TERRAIN_HALF)), f_mul(fi(iz), size))
|
||||
let x0 = ter_ox - float(TERRAIN_HALF) + float(ix) * size
|
||||
let z0 = ter_oz - float(TERRAIN_HALF) + float(iz) * size
|
||||
# Which tier can run inside this patch. A patch that never comes within the split takes
|
||||
# the cheap tier at every pixel; one that lies wholly inside it takes the detailed tier
|
||||
# at every pixel. Only a patch that straddles the band needs the program that holds both
|
||||
# and cross-fades between them — and there are few of those, one ring of them.
|
||||
if ter_sun_pass {
|
||||
let t = gl_scratch()
|
||||
let t = ter_scratch()
|
||||
t[0] = x0; t[1] = z0; t[2] = size
|
||||
u_v3(gpu_uniform(ter_sun_prog, "u_node"), t)
|
||||
var st0 = F_ZERO
|
||||
var st0 = 0.0
|
||||
if level > 0 { st0 = cd_range[level - 1] }
|
||||
u_f2(gpu_uniform(ter_sun_prog, "u_morph"), f_lerp(st0, cd_range[level], fl(0.7)), cd_range[level])
|
||||
u_f2(gpu_uniform(ter_sun_prog, "u_morph"), Math.lerp(st0, cd_range[level], 0.7), cd_range[level])
|
||||
gpu_draw_bound_elements(cd_mesh)
|
||||
return
|
||||
}
|
||||
|
|
@ -757,20 +757,20 @@ function cdlod_draw(level: int, ix: int, iz: int) -> void {
|
|||
let ymin = cd_min[level][iz * n + ix]
|
||||
let ymax = cd_max[level][iz * n + ix]
|
||||
var p = ter_prog
|
||||
if not cd_within(x0, z0, size, ymin, ymax, f_add(ter_far_split, ter_far_band)) { p = ter_prog_far }
|
||||
else if cd_inside(x0, z0, size, ymin, ymax, f_sub(ter_far_split, ter_far_band)) { p = ter_prog_near }
|
||||
if not cd_within(x0, z0, size, ymin, ymax, ter_far_split + ter_far_band) { p = ter_prog_far }
|
||||
else if cd_inside(x0, z0, size, ymin, ymax, ter_far_split - ter_far_band) { p = ter_prog_near }
|
||||
if ter_force_far { p = ter_prog_far }
|
||||
if ter_no_split { p = ter_prog }
|
||||
if ter_force_near { p = ter_prog_near }
|
||||
if p == ter_prog_far { cd_far_draws += 1 }
|
||||
if p == ter_prog_near { cd_near_draws += 1 }
|
||||
if p != ter_prog_cur { gpu_use_program(p); ter_prog_cur = p }
|
||||
let t = gl_scratch()
|
||||
let t = ter_scratch()
|
||||
t[0] = x0; t[1] = z0; t[2] = size
|
||||
u_v3(gpu_uniform(p, "u_node"), t)
|
||||
var start = F_ZERO
|
||||
var start = 0.0
|
||||
if level > 0 { start = cd_range[level - 1] }
|
||||
start = f_lerp(start, cd_range[level], fl(0.7))
|
||||
start = Math.lerp(start, cd_range[level], 0.7)
|
||||
u_f2(gpu_uniform(p, "u_morph"), start, cd_range[level])
|
||||
gpu_draw_bound_elements(cd_mesh)
|
||||
cd_draws += 1
|
||||
|
|
@ -781,15 +781,15 @@ function cdlod_draw(level: int, ix: int, iz: int) -> void {
|
|||
function cdlod_select(level: int, ix: int, iz: int) -> bool {
|
||||
let n = CD_LEAVES >> level
|
||||
let size = cd_size(level)
|
||||
let x0 = f_add(f_sub(ter_ox, fi(TERRAIN_HALF)), f_mul(fi(ix), size))
|
||||
let z0 = f_add(f_sub(ter_oz, fi(TERRAIN_HALF)), f_mul(fi(iz), size))
|
||||
let x0 = ter_ox - float(TERRAIN_HALF) + float(ix) * size
|
||||
let z0 = ter_oz - float(TERRAIN_HALF) + float(iz) * size
|
||||
let ymin = cd_min[level][iz * n + ix]
|
||||
let ymax = cd_max[level][iz * n + ix]
|
||||
if not cd_within(x0, z0, size, ymin, ymax, cd_range[level]) { return false }
|
||||
let half = f_mul(size, F_HALF)
|
||||
let cy = f_mul(f_add(ymin, ymax), F_HALF)
|
||||
let rad = f_sqrt(f_add(f_mul(f_mul(half, half), F_TWO), f_mul(f_mul(f_sub(ymax, cy), f_sub(ymax, cy)), F_ONE)))
|
||||
if not cam_sphere_visible(f_add(x0, half), cy, f_add(z0, half), f_add(rad, fi(2))) { return true }
|
||||
let half = size * 0.5
|
||||
let cy = (ymin + ymax) * 0.5
|
||||
let rad = Math.sqrt(half * half * 2.0 + (ymax - cy) * (ymax - cy) * 1.0)
|
||||
if not cam_sphere_visible(x0 + half, cy, z0 + half, rad + 2.0) { return true }
|
||||
if level == 0 { cdlod_draw(0, ix, iz); return true }
|
||||
if not cd_within(x0, z0, size, ymin, ymax, cd_range[level - 1]) { cdlod_draw(level, ix, iz); return true }
|
||||
for c in 0 .. 4 {
|
||||
|
|
@ -817,15 +817,15 @@ function terrain_unload() -> void {
|
|||
cd_min = null; cd_max = null
|
||||
}
|
||||
# derived from TERRAIN_HALF and cached on first use: stale ones would keep the old size
|
||||
ter_h_scale = 0; ter_o_scale = 0
|
||||
ter_h_scale = 0.0; ter_o_scale = 0.0
|
||||
ter_carpet = 0
|
||||
ter_shadow_gen = -1; ter_shadow_yaw = 0x7fffffff
|
||||
ter_shadow_gen = -1; ter_shadow_yaw = 1000000000.0
|
||||
}
|
||||
# Generate another map in place. Call terrain_lake / terrain_coast / terrain_island for it
|
||||
# first - the generator reads them - then this. `half` is the new TERRAIN_HALF in metres.
|
||||
# An empty `dem` generates the analytic ground (with ter_smooth) and an empty `ortho` drapes
|
||||
# no photograph. The shadow is rebaked and the patch bounds rebuilt before it returns.
|
||||
function terrain_reload(dem: string, emin: int, emax: int, base: int, ox: int, oz: int, ortho: string, half: int) -> void {
|
||||
function terrain_reload(dem: string, emin: float, emax: float, base: float, ox: float, oz: float, ortho: string, half: int) -> void {
|
||||
terrain_unload()
|
||||
TERRAIN_HALF = half
|
||||
ter_ox = ox; ter_oz = oz
|
||||
|
|
@ -836,3 +836,9 @@ function terrain_reload(dem: string, emin: int, emax: int, base: int, ox: int, o
|
|||
cdlod_bounds()
|
||||
gpu_check("terrain reload")
|
||||
}
|
||||
|
||||
var ter_scr: floats = null
|
||||
function ter_scratch() -> floats {
|
||||
if ter_scr == null { ter_scr = floats(16) }
|
||||
return ter_scr
|
||||
}
|
||||
|
|
|
|||
|
|
@ -288,15 +288,15 @@ function tex_solid(r: int, g: int, b: int, a: int) -> int {
|
|||
}
|
||||
|
||||
# ---- Radiance .hdr (RGBE, new-style RLE) -> RGB float bits -------------------------
|
||||
var hdr_max_lum: int = 0 # float bits of the brightest texel (sun finding)
|
||||
var hdr_max_lum: float = 0.0 # float bits of the brightest texel (sun finding)
|
||||
var hdr_max_x: int = 0
|
||||
var hdr_max_y: int = 0
|
||||
var hdr_sun_r: int = 0 # irradiance (float bits) of everything above the IBL clip: the sun
|
||||
var hdr_sun_g: int = 0
|
||||
var hdr_sun_b: int = 0
|
||||
var hdr_clip: int = 0 # float bits; texels above this (per channel) feed the sun, not the IBL
|
||||
var hdr_sun_r: float = 0.0 # irradiance (float bits) of everything above the IBL clip: the sun
|
||||
var hdr_sun_g: float = 0.0
|
||||
var hdr_sun_b: float = 0.0
|
||||
var hdr_clip: float = 0.0 # float bits; texels above this (per channel) feed the sun, not the IBL
|
||||
|
||||
function hdr_decode(path: pointer) -> words {
|
||||
function hdr_decode(path: pointer) -> floats {
|
||||
let d = r3d_read_file(path)
|
||||
if d == null { print(`hdr: cannot read {path}`); return null }
|
||||
let size = tex_file_len
|
||||
|
|
@ -315,14 +315,14 @@ function hdr_decode(path: pointer) -> words {
|
|||
while d[i] >= '0' and d[i] <= '9' { w = w * 10 + (d[i] - 48); i += 1 }
|
||||
i += 1
|
||||
if w <= 0 or h <= 0 { free(d); print(`hdr: bad header {path}`); return null }
|
||||
let out = words(w * h * 3)
|
||||
let out = floats(w * h * 3)
|
||||
let line = bytes(w * 4)
|
||||
var maxl = 0
|
||||
if hdr_clip == 0 { hdr_clip = fi(20) }
|
||||
var sr = F_ZERO; var sg = F_ZERO; var sb = F_ZERO
|
||||
var skye = F_ZERO # sky irradiance on an upward face (clipped part only)
|
||||
let dphi = f_div(f_mul(F_TWO, F_PI), fi(w))
|
||||
let dth = f_div(F_PI, fi(h))
|
||||
var maxl = 0.0
|
||||
if hdr_clip == 0.0 { hdr_clip = 20.0 }
|
||||
var sr = 0.0; var sg = 0.0; var sb = 0.0
|
||||
var skye = 0.0 # sky irradiance on an upward face (clipped part only)
|
||||
let dphi = 2.0 * PI / float(w)
|
||||
let dth = PI / float(h)
|
||||
var y = 0
|
||||
while y < h {
|
||||
if d[i] == 2 and d[i + 1] == 2 and (d[i + 2] & 128) == 0 {
|
||||
|
|
@ -347,28 +347,28 @@ function hdr_decode(path: pointer) -> words {
|
|||
for x in 0 .. w { for c in 0 .. 4 { line[x * 4 + c] = d[i + x * 4 + c] } }
|
||||
i += w * 4
|
||||
}
|
||||
let sinth = f_sin(f_mul(f_add(fi(y), F_HALF), dth))
|
||||
let domega = f_mul(f_mul(dphi, dth), sinth)
|
||||
let sinth = Math.sin((float(y) + 0.5) * dth)
|
||||
let domega = dphi * dth * sinth
|
||||
for x in 0 .. w {
|
||||
let e = line[x * 4 + 3]
|
||||
let o = (y * w + x) * 3
|
||||
if e == 0 { out[o] = 0; out[o + 1] = 0; out[o + 2] = 0 }
|
||||
if e == 0 { out[o] = 0.0; out[o + 1] = 0.0; out[o + 2] = 0.0 }
|
||||
else {
|
||||
let sh = e - 136
|
||||
let vr = f_ldexp(f_from_int(line[x * 4]), sh)
|
||||
let vg = f_ldexp(f_from_int(line[x * 4 + 1]), sh)
|
||||
let vb = f_ldexp(f_from_int(line[x * 4 + 2]), sh)
|
||||
let vr = float_from_bits(f_ldexp(float_bits(float(line[x * 4])), sh))
|
||||
let vg = float_from_bits(f_ldexp(float_bits(float(line[x * 4 + 1])), sh))
|
||||
let vb = float_from_bits(f_ldexp(float_bits(float(line[x * 4 + 2])), sh))
|
||||
# the texture is capped at what a half-float holds; the sun is integrated uncapped
|
||||
out[o] = f_min(vr, fi(60000))
|
||||
out[o + 1] = f_min(vg, fi(60000))
|
||||
out[o + 2] = f_min(vb, fi(60000))
|
||||
let lum = f_add(f_add(vr, vg), vb)
|
||||
out[o] = Math.min(vr, 60000.0)
|
||||
out[o + 1] = Math.min(vg, 60000.0)
|
||||
out[o + 2] = Math.min(vb, 60000.0)
|
||||
let lum = vr + vg + vb
|
||||
if f_lt(maxl, lum) != 0 { maxl = lum; hdr_max_x = x; hdr_max_y = y }
|
||||
if y < h / 2 { skye = f_add(skye, f_mul(f_mul(f_min(vg, hdr_clip), f_cos(f_mul(f_add(fi(y), F_HALF), dth))), domega)) }
|
||||
if y < h / 2 { skye = skye + Math.min(vg, hdr_clip) * Math.cos((float(y) + 0.5) * dth) * domega }
|
||||
if f_lt(hdr_clip, vg) != 0 or f_lt(hdr_clip, vr) != 0 {
|
||||
sr = f_add(sr, f_mul(f_max(f_sub(vr, hdr_clip), F_ZERO), domega))
|
||||
sg = f_add(sg, f_mul(f_max(f_sub(vg, hdr_clip), F_ZERO), domega))
|
||||
sb = f_add(sb, f_mul(f_max(f_sub(vb, hdr_clip), F_ZERO), domega))
|
||||
sr = sr + Math.max(vr - hdr_clip, 0.0) * domega
|
||||
sg = sg + Math.max(vg - hdr_clip, 0.0) * domega
|
||||
sb = sb + Math.max(vb - hdr_clip, 0.0) * domega
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -378,7 +378,7 @@ function hdr_decode(path: pointer) -> words {
|
|||
tex_w = w; tex_h = h; tex_channels = 3; tex_depth = 32
|
||||
hdr_max_lum = maxl
|
||||
hdr_sun_r = sr; hdr_sun_g = sg; hdr_sun_b = sb
|
||||
print(`hdr: peak/1000 {f_fx(f_div(maxl, fi(1000)))} sky irradiance(up) {f_fx(skye)} sun irradiance {f_fx(sg)} (Q16.16 = /65536)`)
|
||||
print(`hdr: peak/1000 {fixed(maxl / 1000.0)} sky irradiance(up) {fixed(skye)} sun irradiance {fixed(sg)} (Q16.16 = /65536)`)
|
||||
return out
|
||||
}
|
||||
|
||||
|
|
@ -414,22 +414,22 @@ function tex_target(w: int, h: int, ifmt: int, fmt: int, ty: int, filter: int) -
|
|||
var tex_dump_alpha: bool = false
|
||||
# Debug: the brightest texel of an RGBA float texture and where it is.
|
||||
function tex_max(tex: int, w: int, h: int, tag: pointer) -> void {
|
||||
let buf = words(w * h * 4)
|
||||
let buf = floats(w * h * 4)
|
||||
gpu_tex_bind(GPU_TEX2D, tex)
|
||||
gpu_pixel_store(GL_PACK_ALIGNMENT, 4)
|
||||
gpu_tex_read(GPU_TEX2D, GL_RGBA, GL_FLOAT, buf)
|
||||
var best = F_ZERO; var bx = 0; var by = 0
|
||||
var best = 0.0; var bx = 0; var by = 0
|
||||
var i = 0
|
||||
while i < w * h {
|
||||
let v = f_max(buf[i * 4], f_max(buf[i * 4 + 1], buf[i * 4 + 2]))
|
||||
if f_gt(v, best) { best = v; bx = i % w; by = i / w }
|
||||
let v = Math.max(buf[i * 4], Math.max(buf[i * 4 + 1], buf[i * 4 + 2]))
|
||||
if v > best { best = v; bx = i % w; by = i / w }
|
||||
i += 1
|
||||
}
|
||||
print(`{tag}: max {f_fx(f_div(best, fi(100)))}/100 at {bx} {h - 1 - by} (top-down)`)
|
||||
print(`{tag}: max {fixed(best / 100.0)}/100 at {bx} {h - 1 - by} (top-down)`)
|
||||
let o = (by * w + bx) * 4
|
||||
let big = fi(65000)
|
||||
let finite = f_ls(best, big)
|
||||
print(` rgba (clamped/100): {f_fx(f_div(f_min(buf[o], big), fi(100)))} {f_fx(f_div(f_min(buf[o + 1], big), fi(100)))} {f_fx(f_div(f_min(buf[o + 2], big), fi(100)))} {f_fx(f_min(buf[o + 3], big))} finite {finite} bits {buf[o]}`)
|
||||
let big = 65000.0
|
||||
let finite = best < big
|
||||
print(` rgba (clamped/100): {fixed(Math.min(buf[o], big) / 100.0)} {fixed(Math.min(buf[o + 1], big) / 100.0)} {fixed(Math.min(buf[o + 2], big) / 100.0)} {fixed(Math.min(buf[o + 3], big))} finite {finite} bits {buf[o]}`)
|
||||
free(buf)
|
||||
}
|
||||
# Debug: write a 2D texture's level 0 (RGBA8, alpha dropped) as a binary PPM.
|
||||
|
|
|
|||
|
|
@ -7,20 +7,20 @@
|
|||
|
||||
var water_mesh: Mesh = null
|
||||
var water_prog: int = 0
|
||||
var water_level: int = 0
|
||||
var water_cx: int = 0
|
||||
var water_cz: int = 0
|
||||
var water_ex: int = 0
|
||||
var water_ez: int = 0
|
||||
var water_level: float = 0.0
|
||||
var water_cx: float = 0.0
|
||||
var water_cz: float = 0.0
|
||||
var water_ex: float = 0.0
|
||||
var water_ez: float = 0.0
|
||||
var water_on: bool = false
|
||||
# Where a body is standing in the water and how hard it is disturbing it. The game sets it;
|
||||
# strength 0 means nobody is in the water and the whole term is skipped.
|
||||
var wt_wade_x: int = 0
|
||||
var wt_wade_z: int = 0
|
||||
var wt_wade_s: int = 0
|
||||
var wt_wade_x: float = 0.0
|
||||
var wt_wade_z: float = 0.0
|
||||
var wt_wade_s: float = 0.0
|
||||
var water_refl: Target = null # the world mirrored in the surface, half resolution
|
||||
var water_refl_div: int = 2 # R3D_REFLDIV overrides: 2 = half res, 4 = quarter
|
||||
var water_saved: words = null # the real camera's matrices, restored after the pass
|
||||
var water_saved: floats = null # the real camera's matrices, restored after the pass
|
||||
var water_dumped: bool = false
|
||||
# Several still-water planes, each at its own level over its own bounds: the sea round an
|
||||
# island and a lake a hundred metres above it cannot be one surface. Each draws the same way;
|
||||
|
|
@ -29,11 +29,11 @@ var water_dumped: bool = false
|
|||
# code written against a single plane still reads the surface that reflects.
|
||||
const WATER_MAX: int = 8
|
||||
var wb_n: int = 0
|
||||
var wb_level: words = null
|
||||
var wb_cx: words = null
|
||||
var wb_cz: words = null
|
||||
var wb_ex: words = null
|
||||
var wb_ez: words = null
|
||||
var wb_level: floats = null
|
||||
var wb_cx: floats = null
|
||||
var wb_cz: floats = null
|
||||
var wb_ex: floats = null
|
||||
var wb_ez: floats = null
|
||||
var wb_reflect: words = null
|
||||
var wb_primary: int = -1 # the body the reflection pass mirrors, or -1
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ function water_reflection_pass() -> void {
|
|||
# this reflection is composited into is smaller than the drawable, and a reflection
|
||||
# rendered at the window's size would be paying for pixels the water never samples
|
||||
water_refl = target_new(post_w / water_refl_div, post_h / water_refl_div, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, true, GL_LINEAR)
|
||||
water_saved = words(16 * 4 + 3)
|
||||
water_saved = floats(16 * 4 + 3)
|
||||
}
|
||||
# save the camera
|
||||
m4_copy(water_saved, cam_view)
|
||||
|
|
@ -58,11 +58,11 @@ function water_reflection_pass() -> void {
|
|||
# the mirrored camera: view' = view * R, R reflecting y about the surface (y' = 2L - y).
|
||||
# R has determinant -1, so the winding flips (front faces culled below) and the image
|
||||
# lands exactly where the main camera's pixels expect the reflection.
|
||||
let eye = v3_new(sx, f_sub(f_mul(F_TWO, water_level), sy), sz)
|
||||
let eye = v3_new(sx, 2.0 * water_level - sy, sz)
|
||||
let refl = m4_new()
|
||||
refl[5] = f_neg1()
|
||||
refl[13] = f_mul(F_TWO, water_level)
|
||||
let mv = words(16)
|
||||
refl[5] = -1.0
|
||||
refl[13] = 2.0 * water_level
|
||||
let mv = floats(16)
|
||||
m4_mul(mv, water_saved, refl)
|
||||
m4_copy(cam_view, mv)
|
||||
free(mv); free(refl)
|
||||
|
|
@ -70,7 +70,7 @@ function water_reflection_pass() -> void {
|
|||
m4_mul(cam_vp, cam_proj, cam_view)
|
||||
m4_inverse(cam_inv_vp, cam_vp)
|
||||
v3_copy(cam_pos, eye)
|
||||
r3d_clip_y = f_sub(water_level, fl(0.05))
|
||||
r3d_clip_y = water_level - 0.05
|
||||
target_bind(water_refl)
|
||||
gpu_depth_test(true)
|
||||
gpu_depth_func(GL_LESS)
|
||||
|
|
@ -96,7 +96,7 @@ function water_reflection_pass() -> void {
|
|||
gpu_cull_face(GL_BACK)
|
||||
if r3d_env_has("R3D_DUMP_REFL") and not water_dumped { water_dumped = true; tex_dump(water_refl.color, water_refl.w, water_refl.h, "build/dbg_refl.ppm") }
|
||||
# restore
|
||||
r3d_clip_y = 0xCF000000
|
||||
r3d_clip_y = -2147483600.0
|
||||
m4_copy(cam_view, water_saved)
|
||||
m4_copy(cam_vp, mem_off(water_saved, 64))
|
||||
m4_copy(cam_inv_vp, mem_off(water_saved, 128))
|
||||
|
|
@ -114,7 +114,7 @@ function water_reflection_pass() -> void {
|
|||
# asking, so it errs on the side of running. R3D_REFL_ALWAYS=1 runs it on every frame, for comparing.
|
||||
# Mac, the five shot viewpoints: looking down at the meadow (c) 225 reflection draws -> none; every view
|
||||
# with the lake in it unchanged, byte-identical frames.
|
||||
var wb_cells: words = null # x0, z0, x1, z1 of each rectangle with water showing
|
||||
var wb_cells: floats = null # x0, z0, x1, z1 of each rectangle with water showing
|
||||
var wb_ncells: int = -1 # -1: not built for the current mirrored body
|
||||
var wb_blocks: words = null # x0, z0, x1, z1, first cell, cells past the last - of each block
|
||||
var wb_nblocks: int = 0
|
||||
|
|
@ -141,7 +141,7 @@ function water_reflect_visible() -> bool {
|
|||
var checked = 0
|
||||
for b in 0 .. wb_nblocks {
|
||||
let o = b * 6
|
||||
if not wb_rect_visible(wb_blocks[o], wb_blocks[o + 1], wb_blocks[o + 2], wb_blocks[o + 3]) { continue }
|
||||
if not wb_rect_visible(float_from_bits(wb_blocks[o]), float_from_bits(wb_blocks[o + 1]), float_from_bits(wb_blocks[o + 2]), float_from_bits(wb_blocks[o + 3])) { continue }
|
||||
for i in wb_blocks[o + 4] .. wb_blocks[o + 5] {
|
||||
if not wb_cell_visible(i) { continue }
|
||||
if checked >= 16 { return true }
|
||||
|
|
@ -154,14 +154,14 @@ function water_reflect_visible() -> bool {
|
|||
# A flat rectangle at the water's level against the view's side planes: hidden when all four corners
|
||||
# lie outside one plane. (A bounding sphere was the first version: a 156 m cell's sphere reached into
|
||||
# the view from a lake the camera had its back to, and the pass never skipped.)
|
||||
function wb_rect_visible(x0: int, z0: int, x1: int, z1: int) -> bool {
|
||||
function wb_rect_visible(x0: float, z0: float, x1: float, z1: float) -> bool {
|
||||
if cam_planes == null { return true }
|
||||
for p in 0 .. 4 {
|
||||
let q = p * 4
|
||||
let a = cam_planes[q]; let c = cam_planes[q + 2]
|
||||
let by = f_add(f_mul(cam_planes[q + 1], water_level), cam_planes[q + 3])
|
||||
let ax0 = f_mul(a, x0); let ax1 = f_mul(a, x1); let cz0 = f_mul(c, z0); let cz1 = f_mul(c, z1)
|
||||
if f_ls(f_add(f_add(ax0, cz0), by), F_ZERO) and f_ls(f_add(f_add(ax1, cz0), by), F_ZERO) and f_ls(f_add(f_add(ax0, cz1), by), F_ZERO) and f_ls(f_add(f_add(ax1, cz1), by), F_ZERO) { return false }
|
||||
let by = cam_planes[q + 1] * water_level + cam_planes[q + 3]
|
||||
let ax0 = a * x0; let ax1 = a * x1; let cz0 = c * z0; let cz1 = c * z1
|
||||
if ax0 + cz0 + by < 0.0 and ax1 + cz0 + by < 0.0 and ax0 + cz1 + by < 0.0 and ax1 + cz1 + by < 0.0 { return false }
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -173,17 +173,17 @@ function wb_cell_visible(i: int) -> bool {
|
|||
# sight line is sampled at seven points short of both ends, with half a metre (and a little more with
|
||||
# distance) of margin, so a far ridge the drawn terrain rounds off never hides real water. A camera at
|
||||
# or under the surface hides nothing behind the ground.
|
||||
function wb_point_hidden(x: int, z: int) -> bool {
|
||||
if f_gt(terrain_height(x, z), f_add(water_level, fl(0.05))) { return true }
|
||||
if not cam_sphere_visible(x, water_level, z, F_HALF) { return true }
|
||||
function wb_point_hidden(x: float, z: float) -> bool {
|
||||
if terrain_height(x, z) > water_level + 0.05 { return true }
|
||||
if not cam_sphere_visible(x, water_level, z, 0.5) { return true }
|
||||
let ey = cam_pos[1]
|
||||
if not f_gt(ey, water_level) { return false }
|
||||
let dx = f_sub(x, cam_pos[0]); let dz = f_sub(z, cam_pos[2])
|
||||
let margin = f_add(F_HALF, f_mul(f_sqrt(f_add(f_mul(dx, dx), f_mul(dz, dz))), fl(0.004)))
|
||||
if not (ey > water_level) { return false }
|
||||
let dx = x - cam_pos[0]; let dz = z - cam_pos[2]
|
||||
let margin = 0.5 + Math.sqrt(dx * dx + dz * dz) * 0.004
|
||||
for k in 1 .. 8 {
|
||||
let t = fr(k, 8)
|
||||
let sy = f_add(ey, f_mul(f_sub(water_level, ey), t))
|
||||
if f_gt(terrain_height(f_add(cam_pos[0], f_mul(dx, t)), f_add(cam_pos[2], f_mul(dz, t))), f_add(sy, margin)) { return true }
|
||||
let t = float(k) / 8.0
|
||||
let sy = ey + (water_level - ey) * t
|
||||
if terrain_height(cam_pos[0] + dx * t, cam_pos[2] + dz * t) > sy + margin { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -191,33 +191,33 @@ function wb_point_hidden(x: int, z: int) -> bool {
|
|||
function wb_cell_occluded(i: int) -> bool {
|
||||
let o = i * 4
|
||||
let x0 = wb_cells[o]; let z0 = wb_cells[o + 1]; let x1 = wb_cells[o + 2]; let z1 = wb_cells[o + 3]
|
||||
let cx = f_mul(f_add(x0, x1), F_HALF); let cz = f_mul(f_add(z0, z1), F_HALF)
|
||||
let ox = f_mul(f_sub(x1, x0), fl(0.45)); let oz = f_mul(f_sub(z1, z0), fl(0.45))
|
||||
let cx = (x0 + x1) * 0.5; let cz = (z0 + z1) * 0.5
|
||||
let ox = (x1 - x0) * 0.45; let oz = (z1 - z0) * 0.45
|
||||
if not wb_point_hidden(cx, cz) { return false }
|
||||
if not wb_point_hidden(f_sub(cx, ox), f_sub(cz, oz)) { return false }
|
||||
if not wb_point_hidden(f_add(cx, ox), f_sub(cz, oz)) { return false }
|
||||
if not wb_point_hidden(f_sub(cx, ox), f_add(cz, oz)) { return false }
|
||||
return wb_point_hidden(f_add(cx, ox), f_add(cz, oz))
|
||||
if not wb_point_hidden(cx - ox, cz - oz) { return false }
|
||||
if not wb_point_hidden(cx + ox, cz - oz) { return false }
|
||||
if not wb_point_hidden(cx - ox, cz + oz) { return false }
|
||||
return wb_point_hidden(cx + ox, cz + oz)
|
||||
}
|
||||
function wb_cell_add(x0: int, z0: int, x1: int, z1: int) -> void {
|
||||
function wb_cell_add(x0: float, z0: float, x1: float, z1: float) -> void {
|
||||
let o = wb_ncells * 4
|
||||
wb_cells[o] = x0; wb_cells[o + 1] = z0; wb_cells[o + 2] = x1; wb_cells[o + 3] = z1
|
||||
wb_ncells += 1
|
||||
}
|
||||
function wb_block_add(x0: int, z0: int, x1: int, z1: int, first: int) -> void {
|
||||
function wb_block_add(x0: float, z0: float, x1: float, z1: float, first: int) -> void {
|
||||
if wb_ncells <= first { return }
|
||||
let o = wb_nblocks * 6
|
||||
wb_blocks[o] = x0; wb_blocks[o + 1] = z0; wb_blocks[o + 2] = x1; wb_blocks[o + 3] = z1
|
||||
wb_blocks[o] = float_bits(x0); wb_blocks[o + 1] = float_bits(z0); wb_blocks[o + 2] = float_bits(x1); wb_blocks[o + 3] = float_bits(z1)
|
||||
wb_blocks[o + 4] = first; wb_blocks[o + 5] = wb_ncells
|
||||
wb_nblocks += 1
|
||||
}
|
||||
function wb_wet(x: int, z: int, off: int) -> bool {
|
||||
if f_ls(terrain_height(x, z), water_level) { return true }
|
||||
if off == 0 { return false }
|
||||
if f_ls(terrain_height(f_sub(x, off), f_sub(z, off)), water_level) { return true }
|
||||
if f_ls(terrain_height(f_add(x, off), f_sub(z, off)), water_level) { return true }
|
||||
if f_ls(terrain_height(f_sub(x, off), f_add(z, off)), water_level) { return true }
|
||||
return f_ls(terrain_height(f_add(x, off), f_add(z, off)), water_level)
|
||||
function wb_wet(x: float, z: float, off: float) -> bool {
|
||||
if terrain_height(x, z) < water_level { return true }
|
||||
if off == 0.0 { return false }
|
||||
if terrain_height(x - off, z - off) < water_level { return true }
|
||||
if terrain_height(x + off, z - off) < water_level { return true }
|
||||
if terrain_height(x - off, z + off) < water_level { return true }
|
||||
return terrain_height(x + off, z + off) < water_level
|
||||
}
|
||||
# Over the height map, 32 m rectangles (at most 512 a side) tested at five points and kept in blocks of
|
||||
# 8 x 8, so a block out of view skips its rectangles in one test - looking away from the water was a
|
||||
|
|
@ -225,59 +225,59 @@ function wb_wet(x: int, z: int, off: int) -> bool {
|
|||
# coarse rectangles tested at their centre, in one last block, which only ever matter kilometres away.
|
||||
const WB_BLOCK: int = 8
|
||||
function water_cells_build() -> void {
|
||||
let bx0 = f_sub(water_cx, water_ex); let bx1 = f_add(water_cx, water_ex)
|
||||
let bz0 = f_sub(water_cz, water_ez); let bz1 = f_add(water_cz, water_ez)
|
||||
let th = fi(TERRAIN_HALF)
|
||||
let tx0 = f_max(bx0, f_sub(ter_ox, th)); let tx1 = f_min(bx1, f_add(ter_ox, th))
|
||||
let tz0 = f_max(bz0, f_sub(ter_oz, th)); let tz1 = f_min(bz1, f_add(ter_oz, th))
|
||||
let inside = f_gt(tx1, tx0) and f_gt(tz1, tz0)
|
||||
var cell = fi(32)
|
||||
let bx0 = water_cx - water_ex; let bx1 = water_cx + water_ex
|
||||
let bz0 = water_cz - water_ez; let bz1 = water_cz + water_ez
|
||||
let th = float(TERRAIN_HALF)
|
||||
let tx0 = Math.max(bx0, ter_ox - th); let tx1 = Math.min(bx1, ter_ox + th)
|
||||
let tz0 = Math.max(bz0, ter_oz - th); let tz1 = Math.min(bz1, ter_oz + th)
|
||||
let inside = tx1 > tx0 and tz1 > tz0
|
||||
var cell = 32.0
|
||||
var nx = 0
|
||||
var nz = 0
|
||||
if inside {
|
||||
let span = f_max(f_sub(tx1, tx0), f_sub(tz1, tz0))
|
||||
if f_gt(f_div(span, cell), fi(512)) { cell = f_div(span, fi(512)) }
|
||||
nx = f_to_int(f_div(f_sub(tx1, tx0), cell)) + 1
|
||||
nz = f_to_int(f_div(f_sub(tz1, tz0), cell)) + 1
|
||||
let span = Math.max(tx1 - tx0, tz1 - tz0)
|
||||
if span / cell > 512.0 { cell = span / 512.0 }
|
||||
nx = int((tx1 - tx0) / cell) + 1
|
||||
nz = int((tz1 - tz0) / cell) + 1
|
||||
}
|
||||
if wb_cells != null { free(wb_cells) }
|
||||
if wb_blocks != null { free(wb_blocks) }
|
||||
wb_cells = words((nx * nz + 64 * 64) * 4)
|
||||
wb_cells = floats((nx * nz + 64 * 64) * 4)
|
||||
let nbx = (nx + WB_BLOCK - 1) / WB_BLOCK
|
||||
let nbz = (nz + WB_BLOCK - 1) / WB_BLOCK
|
||||
wb_blocks = words((nbx * nbz + 1) * 6)
|
||||
wb_ncells = 0
|
||||
wb_nblocks = 0
|
||||
let off = f_mul(cell, fl(0.45))
|
||||
let half = f_mul(cell, F_HALF)
|
||||
let off = cell * 0.45
|
||||
let half = cell * 0.5
|
||||
for bz in 0 .. nbz {
|
||||
for bx in 0 .. nbx {
|
||||
let first = wb_ncells
|
||||
var iz = bz * WB_BLOCK
|
||||
while iz < (bz + 1) * WB_BLOCK and iz < nz {
|
||||
let z0 = f_add(tz0, f_mul(fi(iz), cell))
|
||||
let z0 = tz0 + float(iz) * cell
|
||||
var ix = bx * WB_BLOCK
|
||||
while ix < (bx + 1) * WB_BLOCK and ix < nx {
|
||||
let x0 = f_add(tx0, f_mul(fi(ix), cell))
|
||||
if wb_wet(f_add(x0, half), f_add(z0, half), off) { wb_cell_add(x0, z0, f_add(x0, cell), f_add(z0, cell)) }
|
||||
let x0 = tx0 + float(ix) * cell
|
||||
if wb_wet(x0 + half, z0 + half, off) { wb_cell_add(x0, z0, x0 + cell, z0 + cell) }
|
||||
ix += 1
|
||||
}
|
||||
iz += 1
|
||||
}
|
||||
let x0 = f_add(tx0, f_mul(fi(bx * WB_BLOCK), cell)); let z0 = f_add(tz0, f_mul(fi(bz * WB_BLOCK), cell))
|
||||
wb_block_add(x0, z0, f_add(x0, f_mul(fi(WB_BLOCK), cell)), f_add(z0, f_mul(fi(WB_BLOCK), cell)), first)
|
||||
let x0 = tx0 + float(bx * WB_BLOCK) * cell; let z0 = tz0 + float(bz * WB_BLOCK) * cell
|
||||
wb_block_add(x0, z0, x0 + float(WB_BLOCK) * cell, z0 + float(WB_BLOCK) * cell, first)
|
||||
}
|
||||
}
|
||||
let first = wb_ncells
|
||||
let cw = f_div(f_sub(bx1, bx0), fi(64))
|
||||
let ch = f_div(f_sub(bz1, bz0), fi(64))
|
||||
let cw = (bx1 - bx0) / 64.0
|
||||
let ch = (bz1 - bz0) / 64.0
|
||||
for iz in 0 .. 64 {
|
||||
let z0 = f_add(bz0, f_mul(fi(iz), ch)); let z1 = f_add(z0, ch)
|
||||
let z0 = bz0 + float(iz) * ch; let z1 = z0 + ch
|
||||
for ix in 0 .. 64 {
|
||||
let x0 = f_add(bx0, f_mul(fi(ix), cw)); let x1 = f_add(x0, cw)
|
||||
let x0 = bx0 + float(ix) * cw; let x1 = x0 + cw
|
||||
# inside the height map's rectangle: the fine cells above cover it
|
||||
if inside and not f_ls(x0, tx0) and not f_gt(x1, tx1) and not f_ls(z0, tz0) and not f_gt(z1, tz1) { continue }
|
||||
if wb_wet(f_add(x0, f_mul(cw, F_HALF)), f_add(z0, f_mul(ch, F_HALF)), 0) { wb_cell_add(x0, z0, x1, z1) }
|
||||
if inside and not (x0 < tx0) and not (x1 > tx1) and not (z0 < tz0) and not (z1 > tz1) { continue }
|
||||
if wb_wet(x0 + cw * 0.5, z0 + ch * 0.5, 0.0) { wb_cell_add(x0, z0, x1, z1) }
|
||||
}
|
||||
}
|
||||
wb_block_add(bx0, bz0, bx1, bz1, first)
|
||||
|
|
@ -286,10 +286,10 @@ function water_cells_build() -> void {
|
|||
# the program and the plane are the process's, built once; the bodies are the map's
|
||||
function water_setup() -> void {
|
||||
if water_prog != 0 { return }
|
||||
water_mesh = mesh_grid(2, F_HALF)
|
||||
water_mesh = mesh_grid(2, 0.5)
|
||||
water_prog = r3d_program("water.vert", "water.frag", "")
|
||||
wb_level = words(WATER_MAX); wb_cx = words(WATER_MAX); wb_cz = words(WATER_MAX)
|
||||
wb_ex = words(WATER_MAX); wb_ez = words(WATER_MAX); wb_reflect = words(WATER_MAX)
|
||||
wb_level = floats(WATER_MAX); wb_cx = floats(WATER_MAX); wb_cz = floats(WATER_MAX)
|
||||
wb_ex = floats(WATER_MAX); wb_ez = floats(WATER_MAX); wb_reflect = words(WATER_MAX)
|
||||
}
|
||||
function water_bodies_clear() -> void {
|
||||
wb_n = 0
|
||||
|
|
@ -299,7 +299,7 @@ function water_bodies_clear() -> void {
|
|||
}
|
||||
# Add a plane at `level` over (cx, cz) +- (ex, ez); true `reflect` makes it the mirrored one
|
||||
# if none is yet. Returns its index, or -1 once WATER_MAX are in use.
|
||||
function water_body_add(level: int, cx: int, cz: int, ex: int, ez: int, reflect: bool) -> int {
|
||||
function water_body_add(level: float, cx: float, cz: float, ex: float, ez: float, reflect: bool) -> int {
|
||||
water_setup()
|
||||
if wb_n >= WATER_MAX { return -1 }
|
||||
let i = wb_n
|
||||
|
|
@ -317,7 +317,7 @@ function water_body_add(level: int, cx: int, cz: int, ex: int, ez: int, reflect:
|
|||
}
|
||||
# one reflecting plane: what this function always meant, without a new mesh and program
|
||||
# every time it is called
|
||||
function water_init(level: int, cx: int, cz: int, ex: int, ez: int) -> void {
|
||||
function water_init(level: float, cx: float, cz: float, ex: float, ez: float) -> void {
|
||||
water_bodies_clear()
|
||||
water_body_add(level, cx, cz, ex, ez, true)
|
||||
}
|
||||
|
|
@ -334,7 +334,7 @@ function water_draw(depth_tex: int) -> void {
|
|||
# window. They are the same size only at a render scale of 1; at anything less, taking
|
||||
# the window's size sent the refraction and depth reads into the wrong corner of the
|
||||
# frame, and the lake showed a squashed copy of it instead of its own bed.
|
||||
u_f2(gpu_uniform(p, "u_screen"), fi(post_w), fi(post_h))
|
||||
u_f2(gpu_uniform(p, "u_screen"), float(post_w), float(post_h))
|
||||
# Read into locals first. Passing these three globals straight into the call gives the
|
||||
# shader wrong values - the whole lake churns instead of a patch of it - and a single dead
|
||||
# `let junk = wt_wade_x` above the same unchanged call is enough to make it correct again.
|
||||
|
|
@ -347,11 +347,11 @@ function water_draw(depth_tex: int) -> void {
|
|||
let wz = wt_wade_z
|
||||
let ws = wt_wade_s
|
||||
u_f3(gpu_uniform(p, "u_wade"), wx, wz, ws)
|
||||
var ron = F_ZERO
|
||||
var ron = 0.0
|
||||
if water_refl != null and wb_primary >= 0 {
|
||||
# bind on its own unit first: generating the mip chain re-binds the texture on the active unit,
|
||||
# and it must not displace the depth texture the shader reads for the shore
|
||||
r3d_bind_2d(p, "u_refl", 1, water_refl.color); ron = F_ONE
|
||||
r3d_bind_2d(p, "u_refl", 1, water_refl.color); ron = 1.0
|
||||
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
|
||||
gpu_tex_mips(GPU_TEX2D)
|
||||
}
|
||||
|
|
@ -375,15 +375,15 @@ function water_draw(depth_tex: int) -> void {
|
|||
u_f(gpu_uniform(p, "u_level"), wb_level[i])
|
||||
u_f2(gpu_uniform(p, "u_center"), wb_cx[i], wb_cz[i])
|
||||
u_f2(gpu_uniform(p, "u_extent"), wb_ex[i], wb_ez[i])
|
||||
var r = F_ZERO
|
||||
var r = 0.0
|
||||
if i == wb_primary { r = ron }
|
||||
u_f(gpu_uniform(p, "u_refl_on"), r)
|
||||
# Every body is clipped to the ellipse inside its bounds but an unbounded mirrored sea,
|
||||
# whose rectangle is the point. A mirrored LAKE is clipped like any other: a map whose
|
||||
# reflection belongs to its lake (the Bells in Maroon Lake) would otherwise draw that
|
||||
# lake's level over every hollow in the survey.
|
||||
var clip = F_ONE
|
||||
if i == wb_primary and f_gt(f_max(wb_ex[i], wb_ez[i]), fi(10000)) { clip = F_ZERO }
|
||||
var clip = 1.0
|
||||
if i == wb_primary and Math.max(wb_ex[i], wb_ez[i]) > 10000.0 { clip = 0.0 }
|
||||
u_f(gpu_uniform(p, "u_clip_ellipse"), clip)
|
||||
mesh_draw(water_mesh)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue