A Godot 4.7 hair-card shading stack with three explicit production quality tiers, viewport-aware coverage, packaged 3D LUT resources, and a shared optical-wetness model.
- Approx / Kajiya-Kay — lightweight fallback for constrained hardware.
- Fast Marschner — Unity HDRP Standard-style Marschner with a preintegrated azimuthal LUT.
- Cinematic Marschner — higher-fidelity Marschner using the conditioned longitudinal LUT while retaining analytic azimuthal/attenuation behavior.
The packaged addon exposes only these three tiers. The analytic Reference Marschner shader remains a development/benchmark validation baseline outside the addon; it is not part of the shipped HairMaterialProfile tier enum.
The tiers remain separate compiled shaders. HairMaterialProfile is the common authoring API and selects the appropriate shader instead of compiling one runtime-branching mega-shader.
- Godot 4.7.
- Hair-card meshes with the groom data maps described below.
- Forward+ is required for TAA. MSAA/A2C is supported by Forward+ and Mobile according to Godot's viewport capabilities.
- Fast and Cinematic use the packaged direct
ImageTexture3DLUT resources. Normal users do not need to generate LUTs.
The production runtime is canonical under:
res://addons/marschner_hair/
assets/hair/ holds demo/reference material: the CC BY-NC demo groom models and the benchmark-only analytic Reference shader. benchmark/ retains the validation suite, Reference/experimental shader variants, and raw LUT fixtures. The generated production shader wrappers under addons/marschner_hair/shaders/ are produced from the canonical templates in tools/templates/ by tools/generate_hair_shaders.py.
Release users should copy only the addon package described by the release README. Development-only raw LUT fixtures and benchmark generators are not runtime dependencies.
Create one HairGroomData resource for each card atlas and assign the two generated textures:
coords_texture
RGB = strand tangent encoded from [-1, 1] into [0, 1]
A = root-to-tip coordinate
attributes_texture
R = coverage / occupancy
G = strand depth
B = deterministic per-strand seed
These textures describe the groom/card atlas, not the hair appearance. Keep them paired with the mesh and UVs that generated them. Lossless import is recommended when compression alters tangent, coverage, depth, or seed values.
Choose a quality_tier and normally leave coverage_mode on Auto. The Inspector hides controls that do not affect the selected tier while preserving their serialized values.
The main authoring groups are:
- Quality — Approx, Fast, or Cinematic.
- Coverage — Auto, Static Bayer, TAA Temporal Bayer, or Alpha-to-Coverage.
- Base Hair — color, longitudinal/azimuthal roughness, specular scale, and cuticle tilt.
- Wetness — optical wetness and its film/fiber-response endpoints.
- Fast Marschner — absorption model and optional azimuthal LUT override.
- Cinematic Marschner — IOR and optional longitudinal LUT override.
- Approx / Kajiya-Kay — primary/secondary lobe controls and wrapped scatter.
Both HairMaterialProfile properties and direct ShaderMaterial uniforms carry Inspector hover documentation.
Pass the owning viewport when creating runtime materials so coverage_mode = Auto resolves immediately:
@export var profile: HairMaterialProfile
@export var groom_data: HairGroomData
@export var hair_mesh: MeshInstance3D
func _ready() -> void:
var material: ShaderMaterial = profile.create_material(groom_data, get_viewport())
hair_mesh.material_override = materialFor callers that need an explicit success result:
var material := ShaderMaterial.new()
if not profile.apply_to(material, groom_data, get_viewport()):
push_error("Hair material setup failed")
return
hair_mesh.material_override = materialapply_to() validates a supplied groom before mutating the material and binds the production LUT required by Fast or Cinematic.
If the viewport's AA configuration can change after material creation, register the material with a HairCoverageController:
coverage_controller.register_material(profile, material, get_viewport())The controller keeps the compiled coverage variant and 16-phase Bayer index in sync with the rendered frame. For the full runtime API reference see the hosted API docs or docs/api.md.
The development preview scene is:
res://demos/HairMaterialProfileEditor.tscn
Assign a profile and groom resource, then change quality_tier, coverage_mode, or wetness while inspecting the result in the editor viewport.
The three screenshots below show the basic resource hand-off:
- Create
HairGroomDataand assign the groom maps. - Create
HairMaterialProfileand choose the quality/appearance settings. - Compose the two resources into a
ShaderMaterialon the hair MeshInstance3D.
Approx / Kajiya-Kay![]() |
Fast Marschner![]() |
|---|---|
Cinematic Marschner![]() |
Reference omitted See the quality-tiers.mp4 release clip |
MP4s: fast-wetness.mp4 and cinematic-wetness.mp4. The Approx column has no separate wetness MP4; its GIFs are standalone previews.
| Approx / Kajiya-Kay | Fast Marschner | Cinematic Marschner | |
|---|---|---|---|
| Wetness 0.00 | ![]() |
![]() |
![]() |
| Wetness 0.33 | ![]() |
![]() |
![]() |
| Wetness 0.67 | ![]() |
![]() |
![]() |
| Wetness 1.00 | ![]() |
![]() |
![]() |
The development branch contains focused tests for the production contracts. The current release-relevant minimum is:
# Coverage policy / 16-phase sequence
godot --headless --path . --script res://benchmark/tests/test_hair_coverage_phase_sequence.gd
godot --headless --path . --script res://benchmark/tests/test_hair_coverage_policy.gd
# Groom/profile interface
godot --headless --path . --script res://benchmark/tests/test_hair_groom_binding.gd
godot --headless --path . --script res://benchmark/tests/test_marschner_production_profile.gd
# Wetness interface
godot --headless --path . --script res://benchmark/tests/test_hair_wetness_interface.gd
# Real-renderer resource/variant checks: do not use --headless
godot --path . --script res://benchmark/tests/test_direct_lut_binding.gd
godot --path . --script res://benchmark/tests/test_hair_coverage_runtime_policy.gd
godot --path . --script res://benchmark/tests/test_hair_wetness_runtime.gdThe direct ImageTexture3D checks require a normal rendering context on the validated Godot 4.7 setup; the headless display path can serialize/load those resources as empty 1x1x1 stubs.
Before publishing a release package, also run the package-level checks in docs/release_validation.md. Those checks exist specifically to catch drift between the canonical addons/marschner_hair/ sources and their templates, plus import/path mistakes in the packaged addon.
The full documentation set is hosted at https://44madfire.github.io/GodotMarschnerHairShader/ (source under docs/).
- Runtime API reference —
HairMaterialProfile,HairGroomData,HairCoveragePolicy, quality tiers, and coverage modes. docs/hair_material_authoring.md— profile/groom ownership, coverage, LUT binding, shader switching, and runtime APIs.docs/hair_wetness.md— optical wetness model, calibrated controls, tier behavior, and validation.docs/direct_lut_storage.md— directImageTexture3Dstorage decision and benchmarks.docs/hair_coverage_benchmark.md— coverage-path benchmark and policy rationale.docs/release_media.md— release/demo media capture workflow and licensing.docs/release_validation.md— release packaging and final validation checklist.

















