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
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue