render3d: a grass kind - the GPU blades' per-kind numbers are a GrassKind record the game hands over (grass_kind_set), not constants
GrassKind (grass_kind.ludic) carries what grass.ludic, grass_gpu.ludic, grass.vert, grass_cull.comp and model.frag's BLADE path held as constants: the cell, s0 / d0 / radius, the row bands and rows, the blade profile (neck, root, swell, tip, bend), the heights, clumps, widths and arch, where it grows (slope, snow, the photograph's test), the root / tip / gone-to-seed colours, the far tufts, the root occlusion, the roughness, the sheen and the wind. Its defaults are those constants exactly, so a game that sets nothing draws as before. The shaders read them as u_gk_* (the cull as five more Params vec4s, 288 bytes); grass_reset.comp writes each band's index count so a kind of other rows reaches the draw in order. grass_quality holds the kind to a settings tier (never denser, never farther); R3D_GRASS_* still win. grass_profile_w / _bend are the one profile, for the meshes and a game's preview. grass.mesh takes only the cell: the rest of it is an older copy that already differs from grass.vert, left as it draws. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d9a73d63bd
commit
e908672034
25 changed files with 335 additions and 75 deletions
|
|
@ -10,7 +10,7 @@
|
|||
# draw count down — the cells and their hashes are the same in every tile size.
|
||||
# ============================================================================
|
||||
|
||||
const GRASS_CELL: int = 4
|
||||
# The cell (GRASS_CELL until the kinds) is the kind's now: render3d_st.grass_cell (grass_kind.ludic).
|
||||
# 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
|
||||
|
|
@ -39,6 +39,7 @@ function grass_mesh_live(render3d_st: Render3dState) -> bool { return render3d_s
|
|||
|
||||
# a blade: `rows` rows of 2 vertices (x across, y along, z bend), attribute 2 = uv
|
||||
function grass_blade_mesh(render3d_st: mut Render3dState, rows: int) -> Mesh {
|
||||
let gk = grass_kind(render3d_st)
|
||||
let m = gpu_mesh_new(render3d_st)
|
||||
let v = gl_floats(rows * 2 * 5)
|
||||
var k = 0
|
||||
|
|
@ -53,14 +54,12 @@ function grass_blade_mesh(render3d_st: mut Render3dState, rows: int) -> Mesh {
|
|||
# so the point is a point and not a cut-off stub, but not so low that the last quad is
|
||||
# degenerate.
|
||||
#
|
||||
# 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.
|
||||
# The numbers are the kind's (grass_profile_w / _bend). grass.mesh carries A COPY of the
|
||||
# default profile for the mesh-shader path; change both or the Windows blades stop matching.
|
||||
for r in 0 .. rows {
|
||||
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
|
||||
let taper = grass_profile_w(gk, t)
|
||||
let bend = grass_profile_bend(gk, t)
|
||||
for sd in 0 .. 2 {
|
||||
var x = -0.5
|
||||
if sd == 1 { x = 0.5 }
|
||||
|
|
@ -108,12 +107,7 @@ function grass_init__t(render3d_st: mut Render3dState) -> void {
|
|||
}
|
||||
render3d_st.grass_prog = r3d_program(render3d_st, "grass.vert", "model.frag", defs)
|
||||
if render3d_st.grass_merge and gpu_has_mesh(render3d_st) { render3d_st.grass_mesh_prog = r3d_program(render3d_st, "grass.mesh", "model.frag", "#define FOLIAGE\n#define BLADE\n#define MESH\n") }
|
||||
# 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
|
||||
render3d_st.grass_mesh = grass_blade_mesh(render3d_st, 5)
|
||||
render3d_st.grass_mesh3 = grass_blade_mesh(render3d_st, 3)
|
||||
render3d_st.grass_mesh2 = grass_blade_mesh(render3d_st, 2)
|
||||
render3d_st.grass_wind = 2.4
|
||||
grass_meshes_build(render3d_st)
|
||||
# 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
|
||||
|
|
@ -122,17 +116,30 @@ function grass_init__t(render3d_st: mut Render3dState) -> void {
|
|||
# because this line, not the settings, was deciding.
|
||||
# The spacing doubles every 18 m and the blades stop at 70 m: at 45 m and 1600 m a 1 cm blade
|
||||
# was under a pixel from 20 m on, costing a full blade's vertices to shimmer (5 ms of 9.7 on GL).
|
||||
render3d_st.grass_s0 = 0.066
|
||||
render3d_st.grass_d0 = 18.0
|
||||
render3d_st.grass_radius = 70.0
|
||||
# The spacing, its doubling distance and the reach are the kind's (0.066, 18 and 70 by default),
|
||||
# held to the settings' tier (grass_quality) and then to R3D_GRASS_S0 / _D0 / _R (grass_derive).
|
||||
grass_derive(render3d_st)
|
||||
if r3d_env_has(render3d_st, "R3D_NOBLADES") { render3d_st.grass_on = false }
|
||||
if r3d_env_has(render3d_st, "R3D_GRASS_R") { render3d_st.grass_radius = float(Text.to_int(r3d_env(render3d_st, "R3D_GRASS_R"))) }
|
||||
if r3d_env_has(render3d_st, "R3D_GRASS_S0") { render3d_st.grass_s0 = float(Text.to_int(r3d_env(render3d_st, "R3D_GRASS_S0"))) / 1000.0 }
|
||||
if r3d_env_has(render3d_st, "R3D_GRASS_D0") { render3d_st.grass_d0 = float(Text.to_int(r3d_env(render3d_st, "R3D_GRASS_D0"))) }
|
||||
if r3d_env_has(render3d_st, "R3D_GRASS_DBG") { render3d_st.grass_dbg = Text.to_int(r3d_env(render3d_st, "R3D_GRASS_DBG")) }
|
||||
gg_init(render3d_st)
|
||||
}
|
||||
|
||||
# The three blades, at the kind's rows: five, four quads, near (the arch needs somewhere to bend, and
|
||||
# at four rows a blade that leans over is three straight segments); three; and one quad far out.
|
||||
# Made again only when a kind with other rows or another profile is set (grass_kind_set).
|
||||
@alloc_ok("start-up, and a kind of another shape set at a world build: the blade meshes are made once")
|
||||
function grass_meshes_build(render3d_st: mut Render3dState) -> void {
|
||||
if render3d_st.grass_prog == 0 { return }
|
||||
let k = grass_kind(render3d_st)
|
||||
mesh_free(render3d_st, render3d_st.grass_mesh)
|
||||
mesh_free(render3d_st, render3d_st.grass_mesh3)
|
||||
mesh_free(render3d_st, render3d_st.grass_mesh2)
|
||||
render3d_st.grass_mesh = grass_blade_mesh(render3d_st, k.rows_near)
|
||||
render3d_st.grass_mesh3 = grass_blade_mesh(render3d_st, k.rows_mid)
|
||||
render3d_st.grass_mesh2 = grass_blade_mesh(render3d_st, k.rows_far)
|
||||
if render3d_st.gg_on { gg_meshes_build(render3d_st) }
|
||||
}
|
||||
|
||||
# indices per 16 m cell that could exist at distance d (the count the shader computes)
|
||||
# Under a fog wall the blades end at three quarters of it. A blade's tip stands against ground
|
||||
# much farther off, and a dark blade 85% fogged read as a dark band against fully fogged ground
|
||||
|
|
@ -146,14 +153,14 @@ function grass_reach(render3d_st: Render3dState) -> float {
|
|||
|
||||
function grass_count_at(render3d_st: Render3dState, d: float) -> int {
|
||||
let spacing = render3d_st.grass_s0 * (1.0 + d / render3d_st.grass_d0)
|
||||
let n = float(GRASS_CELL * GRASS_CELL) / (spacing * spacing)
|
||||
let n = float(render3d_st.grass_cell * render3d_st.grass_cell) / (spacing * spacing)
|
||||
return int(n) + 1
|
||||
}
|
||||
|
||||
# one tile size over one distance band
|
||||
function grass_tiles(render3d_st: mut Render3dState, size: int, d_min: float, d_max: float, blade: Mesh) -> void {
|
||||
let p = render3d_st.grass_prog
|
||||
let cells = size / GRASS_CELL
|
||||
let cells = size / render3d_st.grass_cell
|
||||
if d_min >= grass_reach(render3d_st) { return }
|
||||
if render3d_st.grass_merge {
|
||||
render3d_st.grass_band_start[render3d_st.grass_band_n] = render3d_st.grass_n; render3d_st.grass_band_cells[render3d_st.grass_band_n] = cells
|
||||
|
|
@ -231,7 +238,8 @@ function grass_bind(render3d_st: mut Render3dState, p: int) -> void {
|
|||
u_mat4(render3d_st, gpu_uniform(render3d_st, p, "u_view"), render3d_st.cam_view)
|
||||
u_mat4(render3d_st, gpu_uniform(render3d_st, p, "u_proj"), render3d_st.cam_proj)
|
||||
u_mat4(render3d_st, gpu_uniform(render3d_st, p, "u_vp"), render3d_st.cam_vp_clean)
|
||||
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_wind"), render3d_st.grass_wind)
|
||||
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_wind"), grass_kind(render3d_st).wind)
|
||||
grass_bind_kind(render3d_st, p)
|
||||
# copied into a local first: a global reaching a uniform call is the codegen fault
|
||||
# CLAUDE.md records against u_wade and u_flutter, and it costs a day every time
|
||||
let btex = render3d_st.grass_blade_tex
|
||||
|
|
@ -274,17 +282,20 @@ function grass_bind(render3d_st: mut Render3dState, p: int) -> void {
|
|||
# caught the sun, and a white blade of grass is the one thing grass is never. Measured:
|
||||
# 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(render3d_st, gpu_uniform(render3d_st, p, "u_spec_scale"), 0.008)
|
||||
# used 0.05 all along and never showed the fault. The kind's `spec` (0.008).
|
||||
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_spec_scale"), grass_kind(render3d_st).spec)
|
||||
}
|
||||
|
||||
# the per-vertex path's tiles, in four bands
|
||||
function grass_bands(render3d_st: mut Render3dState) -> void {
|
||||
# four bands: the tile grows and the blade loses rows with distance (a band past the radius is
|
||||
# skipped); a tile asks for what its NEAREST point needs, so a wide tile far out wastes the rest
|
||||
grass_tiles(render3d_st, 4, 0.0, 6.0, render3d_st.grass_mesh)
|
||||
grass_tiles(render3d_st, 4, 6.0, 16.0, render3d_st.grass_mesh3)
|
||||
grass_tiles(render3d_st, 8, 16.0, 40.0, render3d_st.grass_mesh2)
|
||||
# the kind's row bands, inside the 40 m where the tiles grow to 16 m
|
||||
let mid = Math.min(grass_kind(render3d_st).band_mid, 40.0)
|
||||
let near = Math.min(grass_kind(render3d_st).band_near, mid)
|
||||
grass_tiles(render3d_st, 4, 0.0, near, render3d_st.grass_mesh)
|
||||
grass_tiles(render3d_st, 4, near, mid, render3d_st.grass_mesh3)
|
||||
grass_tiles(render3d_st, 8, mid, 40.0, render3d_st.grass_mesh2)
|
||||
grass_tiles(render3d_st, 16, 40.0, grass_reach(render3d_st), render3d_st.grass_mesh2)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue