render3d: a glTF material's factors are drawn - base colour, roughness, metallic and emission

gltf_factors reads pbrMetallicRoughness.baseColorFactor, metallicFactor, roughnessFactor and the
emissiveFactor into the primitive (pr.fac), and prim_factors hands them to every program that draws its
textures (actors, the scatter's meshes and levels, the impostor bake) as 1 - factor, so a program never
given them - or a material that gives none (pr.fac null) - multiplies by 1 and draws as before. An
untextured material with a colour (or a metal/roughness) samples a pure white for it, so the factor is
exactly what it draws: horse_cornea is its own colour, not the 200 grey every untextured part had.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 18:52:42 +03:00
parent d2e1f804ce
commit f79390838a
27 changed files with 221 additions and 114 deletions

View file

@ -11,7 +11,8 @@ property Prim {
nrm: int = 0,
arm: int = 0,
verts: int = 0, # vertices in the mesh (each attribute buffer holds this many)
name: string # the material's name (a part to tint: "hk_jacket")
name: string, # the material's name (a part to tint: "hk_jacket")
fac: floats # the material's factors, or null (all 1, no emission): base r g b a, roughness, metallic, emissive r g b
}
property Model {
prims: []Prim,
@ -155,6 +156,7 @@ function gltf_prim(render3d_st: mut Render3dState, p: Val) -> Prim {
if ud != null { pr.diff = gltf_texture(render3d_st, ud, true) }
if un != null { pr.nrm = gltf_texture(render3d_st, un, false) }
if ua != null { pr.arm = gltf_texture(render3d_st, ua, false) }
gltf_factors(render3d_st, pr, mat, ud != null, ua != null)
var mname: string = null
# a copy: the document goes, strings and all, at the next load
if value_has(mat, "name") != 0 { let s = value_as_str(value_get(mat, "name")); mname = s[0 .. len(s)] }
@ -174,6 +176,45 @@ function gltf_prim(render3d_st: mut Render3dState, p: Val) -> Prim {
return pr
}
# The material's factors (glTF's pbrMetallicRoughness and emissiveFactor), which the shaders multiply in;
# a material that gives none keeps pr.fac null and draws as it always has. An untextured material with
# a colour, or a metal/roughness, samples white for it, so the factor is exactly what it draws.
@alloc_ok("a model being loaded: a primitive's factors, once")
function gltf_factors(render3d_st: mut Render3dState, pr: Prim, mat: Val, has_diff: bool, has_arm: bool) -> void {
let pbr = value_get(mat, "pbrMetallicRoughness")
let has_pbr = value_has(mat, "pbrMetallicRoughness") != 0
let has_base = has_pbr and value_has(pbr, "baseColorFactor") != 0
let has_m = has_pbr and value_has(pbr, "metallicFactor") != 0
let has_r = has_pbr and value_has(pbr, "roughnessFactor") != 0
let has_e = value_has(mat, "emissiveFactor") != 0
if not has_base and not has_m and not has_r and not has_e { return }
let f = floats(9)
for k in 0 .. 6 { f[k] = 1.0 }
for k in 6 .. 9 { f[k] = 0.0 }
if has_base { let b = value_get(pbr, "baseColorFactor"); for k in 0 .. min(value_count(b), 4) { f[k] = jnum(value_at(b, k)) } }
if has_r { f[4] = jnum(value_get(pbr, "roughnessFactor")) }
if has_m { f[5] = jnum(value_get(pbr, "metallicFactor")) }
if has_e { let e = value_get(mat, "emissiveFactor"); for k in 0 .. min(value_count(e), 3) { f[6 + k] = jnum(value_at(e, k)) } }
pr.fac = f
if render3d_st.gltf_pure == 0 { render3d_st.gltf_pure = tex_solid(render3d_st, 255, 255, 255, 255) }
if has_base and not has_diff { pr.diff = render3d_st.gltf_pure }
if (has_m or has_r) and not has_arm { pr.arm = render3d_st.gltf_pure }
}
# a primitive's factors handed to program p (as 1 - factor, so a program never given them reads 1)
function prim_factors(render3d_st: mut Render3dState, p: int, pr: Prim) -> void {
let f = pr.fac
if f == null {
u_f4(render3d_st, gpu_uniform(render3d_st, p, "u_fac_base_inv"), 0.0, 0.0, 0.0, 0.0)
u_f2(render3d_st, gpu_uniform(render3d_st, p, "u_fac_mr_inv"), 0.0, 0.0)
u_f3(render3d_st, gpu_uniform(render3d_st, p, "u_fac_emis"), 0.0, 0.0, 0.0)
return
}
u_f4(render3d_st, gpu_uniform(render3d_st, p, "u_fac_base_inv"), 1.0 - f[0], 1.0 - f[1], 1.0 - f[2], 1.0 - f[3])
u_f2(render3d_st, gpu_uniform(render3d_st, p, "u_fac_mr_inv"), 1.0 - f[4], 1.0 - f[5])
u_f3(render3d_st, gpu_uniform(render3d_st, p, "u_fac_emis"), f[6], f[7], f[8])
}
# Load the mesh of the node called `node_name` from dir/file.
# the GPU memory it makes is counted as VKM_MODEL (R3D_VKMEM)
function gltf_load(render3d_st: mut Render3dState, dir: string, file: string, node_name: string) -> Model {