feat: add vertex-color-ao example (baked occlusion in a colour attribute)#119
Merged
Conversation
…ute) Most bakes can only be regression-tested against a previous run. This one cannot drift silently, because the hemisphere visibility integral has closed forms: an unoccluded surface integrates to exactly 1, and a point a distance d from an infinitely wide wall of height H integrates to 1 - 0.5(1 - 1/sqrt(1+k^2)) with k = H/d. The check holds a deterministic cosine-weighted integrator to that formula across two decades of k, then runs the same integrator over the asset that ships, so what is in the attribute is the thing that was measured. The storage half is the trap worth shipping. BYTE_COLOR is not linear 8-bit, it is sRGB-encoded 8-bit: a linear 0.735 reads back 0.7379107. The check reproduces the readback with an independent encode/quantise/decode model rather than asserting a captured constant, so an exporter handing raw bytes to an engine expecting linear occlusion is caught by construction. Two authoring hazards are recorded with the geometry that provoked them: bmesh.ops.bevel offsets along cached face normals and flips outward once a transform leaves them more than 90 degrees stale, and point-domain AO needs subdivision or the crevice gradient never reaches the attribute at all. The depsgraph check originally compared zip(src.data, eva.data), which stops at the shorter sequence; a Subdivision modifier resampling the attribute reported a clean 0.0 deviation and the falsification probe exited 0. The length guard exists because the probe failed to fail. Signed-off-by: fOuttaMyPaint <tmhospitalitystrategies@gmail.com> Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
examples/vertex-color-ao/— ambient occlusion baked into a colour attribute, the cheap contact shadows an engine gets for free if it reads vertex colour, witnessed on a reusable stone village well (Well.Stone, 2.5 m, 11 parts, 11 materials, 6966 verts).The point of the example is that this bake cannot be regression-tested into meaninglessness, because the hemisphere visibility integral has closed forms. Integrating the cosine-weighted measure over the directions an infinitely wide wall of height
Hblocks at distanced(the φ integral collapses toπ/√(1+k²)):AOdepends only onH/dand rises strictly withd— "a concave corner darkens monotonically with depth", as an equation. The check holds the integrator to that formula, then runs the same integrator over the asset that ships.Contract and measured values (LIVE RUN)
1 − A(H/d), k = 60.0…0.60FLOAT_COLORround-tripBYTE_COLORvs independent sRGB modelmin z0.00e+00, render attrAOThe
2.5e-03gate is justified rather than tuned: quasi-MC convergence is ~1/√n, measured worst error at 4096 samples is 6.8e-04, so the gate is ~3.5×. Tightening below ~1e-3 would make it a sampling-noise detector.Runtime is ~1 s: the calibration rig gets 4096 samples over six points, the asset a cheap 64.
Falsification (LIVE RUN — every exit code proven to fail)
Probe harness
.scratch/vca_falsify_probe.py; each monkeypatches one thing and runs the realcheck().RAY_EPS = 0so rays self-hitCubedatablock nameBaseline re-run after restoring: exit 0.
The exit-7 probe found a defect in my own check. The depsgraph comparison originally used
zip(src.data, eva.data), which stops at the shorter sequence — a resampled attribute reported a clean 0.0 deviation and the probe exited 0. The length guard exists because the falsification pass failed to fail. This is the single most valuable thing the falsification requirement did in this run.Version witness (LIVE RUN)
.scratch/blender-5.1.2-windows-x64/blender.exe→ reports 5.1.2.scratch/blender-4.5.11-windows-x64/blender.exe→ reports 4.5.11 LTSByte-identical: same 6966 verts, same closed-form errors to every printed digit, same storage deviations, same framing numbers.
Hazards found while authoring
BYTE_COLORis sRGB-encoded 8-bit, not linear 8-bit. Linear 0.735 reads back 0.7379107; peak round-trip error 3.782e-03. Confirmed, not guessed — the check reproduces the readback with an independent encode → quantise → decode model to 3.189e-07.bmesh.ops.beveloffsets along cached face normals. Moving verts does not refresh them; past 90° of staleness the offset flips and grows the solid outward. Measured on a ring of bevelled boxes: 0/36/72° correct, 108° grew by exactly one offset (12 mm) in both z directions, putting geometry below ground. Repro:.scratch/bev_probe3.py.normal_update()before every bevel is the fix; the hygiene check caught it.color_attributesenumeration order is not portable —BYTE_COLORfirst on 4.5.11,FLOAT_COLORfirst on 5.1.2 for the same mesh. Look up by name.I also audited the just-merged
socket-attach-pointsfor hazard 2 and for a suspected BMVert-identity aliasing issue: all 18 parts are byte-identical under an isolated-primitive rebuild, so no follow-up fix is needed there.Gates
touch=none cross=none.Stone@0.27;edge900.152; compactness 137.8 (informational).docs/gallery/contact-sheets/vertex-color-ao-contact-sheet.webp. Mean luminance 63.4 (armature-bend 71.1, bmesh-gear 77.7, damped-track-aim 43.9 — mid-range, deliberately not a fifth bright card; the first framing-clean draft measured 74.8 and the key/fill/wedge were pulled back for this reason). Clip fraction 0.000%. Stage darkness ✅, wedge warmth ✅, subject fill ✅, saturationdocs/gallery/asset-sheets/vertex-color-ao.webpvs the pinned reference set. Verdict: pass — structured masonry with per-stone joint detail and visible baked occlusion even under the neutral rig; not the least-designed object.altand witnesses callout carry the full strings —1 - 0.5(1 - 1/sqrt(1+k^2)),6.760e-04,3.189e-07all survive untruncated; em dashes are U+2014.socket-attach-pointsand belowmodular-kit-snap/lightmap-uv-channeland reads distinctly from all three (pale stone vs dark drone vs two warm scenes).featured_rank
Not assigned. The current featured set is stronger at card size; this subject's value is in the numbers, not the silhouette.