Skip to content

Latest commit

 

History

History
48 lines (31 loc) · 9.67 KB

materials.md

File metadata and controls

48 lines (31 loc) · 9.67 KB

Meshes, Materials, and Shading Models

Render data is split three ways. A Mesh is geometry: it knows which material slot each triangle uses, never how a slot shades. A Material describes how one slot's surface looks, through one shading model. A shading model is a WGSL program the renderer composes into every pass that draws its materials. The records live in joy-render-types (joy_engine::render_types) so FORM, DCC importers, physics, tools, and games produce them without a GPU; joy-renderer turns them into shared MeshAsset and MaterialAsset handles.

Meshes

Mesh holds double-precision local Y-up positions (DVec3), triangle-list indices, and optional vertex-aligned normals (Vec3), uvs (Vec2, top-left origin), and straight-alpha colors (LinearColor). triangle_slots assigns each triangle a material slot (all slot 0 when absent), material_slots names the slots, and metadata carries producer-defined JSON. Mesh::validate checks alignment, finite values, complete triangles, and in-range indices and slots; bounds, vertex_count, triangle_count, and slot_count describe it.

A missing normal channel draws faceted, a missing color channel draws white, and missing UVs sample every map at its origin. Positions narrow to f32 once, when the asset is built; values outside f32 range are rejected.

MeshDocument is the saved form: {"version": 1, "mesh": {…}, "materials": [{…}, …]}, the Mesh and one Material per slot exactly as they serialize on their own. Baked FORM output, tool exports, and hand-authored props use it; MeshAsset::from_document validates a document and resolves its texture sources.

Mesh Assets and Instances

MeshAsset::new(&mesh) uploads with the default material (opaque white lit) in every slot; MeshAsset::with_materials supplies one MaterialAsset per slot. Assets group triangles into one Submesh per used slot, so each slot draws as its own range. Assets are immutable and cheap to clone; the GPU copy is created on first draw and released when every handle drops.

MeshInstance draws an asset with a transform, tint, opacity, shadow flags, and render pass. with_material overrides every slot and with_slot_material/set_slot_material override one; MeshInstance::material(slot) reports what a slot draws with. Share override materials between instances: the renderer batches instances by shading model, material, and mesh, so a fresh MaterialAsset per instance splits batches and uploads a uniform per instance.

Materials

Material carries a name, its shading, an alpha_mode (Opaque, Mask with alpha_cutoff, or Blend), and the cutoff. Blended materials, and instances with opacity below one, draw in the transparent pass; masked materials alpha-test in the depth pre-pass, the lit pass, and shadow casters. MaterialAsset::new validates a material without textures and MaterialAsset::with_textures resolves texture sources (for example embedded data: URLs) to decoded TextureAssets. TextureAsset reads PNG, JPEG, and WebP; color maps are sRGB and sampled with generated mips.

Shading selects the model:

  • Shading::Layered(Surface): physically based layered surfaces, below.
  • Shading::Lit(LitSurface): the engine's flat, faceted, double-sided stylized lighting. Faces light by their rasterized normals and both sides respond.
  • Shading::Unlit(UnlitSurface): color without lighting or shadows.
  • Shading::Custom(CustomShading): a model a game registered, by name, with named parameter values and texture sources.

ShadingModel names a model without its parameters (Layered, Lit, Unlit, or Custom(name)); producers such as compile_form_mesh and FormAssetLibrary take one to decide which materials to build.

Layered Surfaces

A SurfaceLayer is one physically based interface over diffusely scattering material, in the spirit of Unreal Engine's Substrate slabs. It carries base_color (diffuse albedo for dielectrics, F0 for metals), metallic, dielectric f0 and f90 reflectance, perceptual roughness, emissive, and maps (base color, normal, emissive, metallic, roughness, and occlusion). Optional lobes extend it: haze (a second, rougher specular lobe), fuzz (Charlie sheen for cloth and dust), coat (a clear or tinted coat with its own roughness and F0), and thin_film (Belcour-Barla iridescence from a film thickness and index of refraction). Metal and dielectric authoring meet in one layer, so glTF and FORM metallic-roughness data and direct F0/F90 authoring share the model.

Surface composes layers into a tree: Coat stacks one surface over another, Blend mixes two side by side, Add sums their responses, and Weight scales coverage. MaterialAsset collapses the tree once, by blending parameters, into the single layer the shader evaluates; the tree stays the authored record, so a future renderer can evaluate it per layer without changing documents.

Shading Models and the WGSL Contract

The renderer keeps a registry of shading models: lit, layered, and unlit are built in, and Renderer::register_shading_model adds a game's ShadingModelDefinition (its name, WGSL source, vec4f parameters after the coverage header, and textures). Registration composes the model with the engine's scene contract and validates it with naga before any pipeline compiles, so a broken model reports an error instead of failing inside the device. Materials that name an unregistered model are skipped and counted in RendererDiagnostics::unavailable_materials.

A model file declares its material group (group 3: binding 0 the parameters, then each texture, then one filtering sampler) and implements the functions in surface-contract.wgsl: MATERIAL_RECEIVES_LIGHT, materialSurface, materialOpacity, materialAlpha, materialDirect (one light), materialIndirect (ambient and image lighting), materialEmission, and materialAttributes. MaterialParameters starts with coverage (alpha cutoff, alpha mode, model index). The engine owns everything else: vertex transforms, the light loop, directional and point shadows, the skylight, image-based lighting, alpha testing, motion vectors, scene-effect attachments, and debug views. Models read only their inputs, their material group, and the environment helpers, so one model file compiles into the lit pass, the depth pre-pass, and the masked shadow caster. A new shading model therefore needs no renderer change; see the built-in models in shaders/models/ for complete examples.

A model that also implements materialEncode (its surface as three u32 words) and materialDecode (those words, the shading normal, and the view direction back into the surface materialDirect reads) supports deferred lighting: its opaque surfaces write a G-buffer texel beside their emission and indirect light, and one full-screen pass per model adds direct light. The G-buffer encoding helpers (packRgb9e5, packUnorm, packOctahedral, and their unpacking counterparts) are available to every model. Neither function may read the material group, which the lighting pass does not bind; registration checks this and reports a model that defines only one of them. The lit and layered models support deferred lighting; layered materials whose features exceed the G-buffer shade forward per material.

Every model reports SurfaceAttributes (normal, roughness, diffuse albedo, specular color, metallic, emission, and occlusion), which scene effects and debug views read whatever the shading.

Image Lighting

Renderer::prepare_environment_map turns an equirectangular environment (eight-bit or linear HDR pixels) into image-based lighting that models read through the environment helpers (environmentDiffuse, environmentSpecular, and environmentBrdf), and FrameInputs::image_lighting applies it per frame with an intensity and a rotation about the vertical axis. Specular light is a cube with one perceptual roughness per mip, from the mirror image down to roughness one at four texels per face: the source is captured into a radiance cube (averaging every source pixel under each texel, so a small HDR sun keeps its energy) and each level is prefiltered with GGX filtered importance sampling, which reads coarser radiance mips for wider samples and stays free of noise and fireflies at 64 samples. Diffuse light is L2 spherical-harmonic irradiance integrated on the CPU from the source pixels and smoothed only as much as keeps it non-negative opposite a bright sun. The renderer integrates one split-sum BRDF table, with the height-correlated Smith visibility the direct lights use, and shares it across environments. Preparation submits GPU work; do it at setup or when the environment changes.

Debug Views

Renderer::set_debug_view draws mesh surfaces as one attribute instead of the lit scene: base color, diffuse albedo, specular color, roughness, metallic, normals, emissive, ambient occlusion, lighting only, shading model, shadow cascades, texture coordinates, or light complexity, the number of point lights each surface's light cluster lists (DebugView::ALL lists them with labels). DebugView::Lit restores the lit scene. Debug views shade forward; terrain, sky, and particles keep drawing normally.

Browser Support

Every built-in model compiles for WebGPU and translates to WebGL 2 GLSL without shadow samplers or extensions; unit tests validate each model's scene, depth, and caster modules on both, and the deferred models' G-buffer and lighting modules for WebGPU. WebGL 2 shades every surface forward. Custom models must stay within WebGL 2's limits to run there; the layered model uses all sixteen sampled textures a WebGL 2 fragment stage allows.