Repository navigation
[Bugfix] Evaluate the model pass's surface attributes at the MSAA centroid - #1389
Merged
untoldengine merged 2 commits intoOct 28, 2026
Merged
Conversation
…troid Under MSAA the model fragment runs once per pixel, at the pixel's centre, which along a silhouette lies outside the triangle: the world position, normal, tangent and uv it read were extrapolated past the limb. On a smooth surface the error is small (the normal turns by the curvature times the distance past the limb, a degree or two), but anything decided on those attributes is decided on a point that is not on the surface. The pass's former back-face rule, judged on the interpolated normal, turned a limb normal a hair past 90 degrees right round into the sun, and the partly covered edge texels along a sphere's dark lower limb carried the colour of its sunlit top: 13 of 47 such texels 2.6 to 7.4 times as bright as the surface two texels in, the sparse pale dots the Vision Pro showed along the limb once the RealityKit presenter blended them by their coverage. The rule now judges the face itself (untoldengine#1359); this gives the four attributes [[centroid_perspective]], so that they are evaluated inside the covered samples for whatever is decided on them. On that arc the brightest edge texel goes from 1.35 to 1.06 times the surface beside it; the fully covered interior is unchanged (the centroid of a covered pixel is its centre) and without MSAA nothing changes. The velocity's clip positions stay at the centre: both are extrapolated alike and their difference is the surface's motion either way. The kernels are rebuilt with buildkernels.sh. MSAACentroidEdgeShadingTests draws the presenter's straightness scene (a slab, three cubes, a sphere, the sun overhead) into the headset's eye texture under 4x MSAA from an eye 1.6 m before the sphere and reads its bottom arc off the resolved lit colour: no partly covered edge texel over 1.5 times the texel two rows in, and the brightest within 1.2 times.
miogds
pushed a commit
to miolabs/UntoldEngine
that referenced
this pull request
Oct 27, 2026
…ntroid change The cherry-pick of untoldengine#1389 brought its shader source and test; its metallibs were built from upstream develop and would have dropped this branch's look-pass change (8554445), so the binaries kept this branch's and are rebuilt here from the sources with both (`buildkernels.sh`).
miogds
pushed a commit
to miolabs/UntoldEngine
that referenced
this pull request
Oct 27, 2026
…#1389) and on the presenter branch with the kernels rebuilt
This was referenced Oct 28, 2026
miogds
added a commit
to miolabs/UntoldEngine
that referenced
this pull request
Oct 28, 2026
[Sync] develop with upstream: the model pass's surface attributes at the MSAA centroid (untoldengine#1389)
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.
Summary
On the Vision Pro a smooth sphere lit by the sun from above showed sparse pale dots along its dark lower silhouette, over the passthrough, where a RealityKit sphere beside it showed none. The engine's eye texture rendered on the Mac has them: along the sphere's bottom arc the partly covered edge texels (a quarter or a half of the 4x MSAA samples) carried the colour of the sphere's sunlit top, up to 7.4 times as bright as the surface two texels in.
Under MSAA the model fragment runs once per pixel, at the pixel's centre, which along a silhouette lies outside the triangle: the world position, normal, tangent and uv it reads are extrapolated past the limb. On a smooth surface the extrapolation is small (the normal turns by the curvature times the distance past the limb, a degree or two here), but anything decided on those attributes is decided on a point that is not on the surface. The model pass's former back-face rule, judged on the interpolated normal, turned a limb normal a hair past 90° right round into the sun; develop judges the face itself since #1359, which is why the dots are gone from the same scene on develop while they remain on branches that predate it.
This branch gives the four surface attributes of
VertexOutModel[[centroid_perspective]], so that the GPU evaluates them at the centroid of the covered samples, inside the triangle, for whatever is decided on them. The centroid of a fully covered pixel is its centre, so the opaque interior is unchanged, and without MSAA nothing changes. The velocity's two clip positions stay at the centre: both are extrapolated alike and their difference is the surface's motion either way. The[[position]]is Metal's own. No other stage-in struct shades from normals into the multisampled target (the transparency and gizmo shaders shareVertexOutModel; the sky, grid, environment, outline, wireframe and light-visual structs do not light from normals).The sheet is the eye texture around the sphere, magnified eightfold: the top row composited over a grey room by each texel's coverage (as a compositor shows it), the bottom row each drawn texel's straight colour (as it was shaded). From the left: develop with the attributes at the pixel centre, develop with this branch, the former back-face rule (the branch the headset ran) with the attributes at the pixel centre, which shows the pale texels along the dark bottom and the dark ones along the lit top, and the former rule with this branch's centroid, where both are gone.
Measured
The new test's scene: a slab, three cubes and a sphere of radius 6 cm at (0, 1.45, -1.6), the sun overhead at strength 3, drawn under 4x MSAA into the headset's eye texture (1920 × 1824, the presenter's frustum) from an eye at (0, 1.5, 0); the resolved lit colour is read before the look pass (linear, premultiplied by the coverage, the coverage in alpha). Along the sphere's bottom arc, 47 of its 56 columns have a partly covered edge texel over a fully covered texel two rows in; each edge texel's straight luminance is compared with that texel's.
Changes
Sources/UntoldEngine/Shaders/ShaderStructs.h:worldPosition,tbNormal,tangentanduvCoordsofVertexOutModelcarry[[centroid_perspective]], with the reason in the struct's comment.buildkernels.sh.Tests/UntoldEngineRenderTests/MSAACentroidEdgeShadingTests.swift: the scene above; asserts that no partly covered edge texel along the sphere's dark bottom arc is over 1.5 times as bright as the surface two texels in (the former rule gives 13 of 47) and that the brightest is within 1.2 times (1.35 at the pixel centre, 1.06 at the centroid). It prints the sphere's rows, the columns read, the coverage steps seen, the mean straight colours of the edge and of the surface two in, every texel over the limit, and the worst.UNTOLD_MSAA_EDGE_CROP_DIR=<folder>makes it write the two crops of the sheet.Verification
MSAACentroidEdgeShadingTests: fails on develop's kernels at the brightest edge texel (1.35 > 1.2), passes with the rebuilt kernels (1.06).swift testrun on the branch (an M-series Mac, Xcode 27):UntoldEngineTests: 1727 tests, 3 skipped, 1 failure:ExternalRenderExtensionPackageTests, whose fixture package resolves the engine by its folder name and fails in any checkout not namedUntoldEngine(this one is a worktree); unrelated.UntoldEngineRenderTests: 974 tests passed through the suites up toStreamLodBatchFallbackTests, among them every suite that touches MSAA:MSAARenderingTests(6),PostFXTests(16; the MSAA, FXAA and SMAA PSNR tests pass against the existing references, so none was regenerated),HybridRenderPathTests(5),LookPresentFoldTests(2),OpaqueHiddenSurfaceRemovalTests(5),PerFragmentIndirectLightingTests(3),PerFragmentLightingTests(4),BackFaceFlipTests(2),RenderGraphBuilderTest(103),RenderExtensionResourceDescriptorTest(8) and the new test. 37 failures, all of one kind: the asset-loading drain inBaseRenderSetup.setUptiming out (15 s) inStaticBatchingTestandStreamLodBatchFallbackTests, an hour and a half into the process, with no rendering assertion among them; the run was stopped there. A stage-in qualifier cannot reach the asset loader.TemporalAntiAliasingTests(6),TAAMovingObjectTests(2),TileLightStageTests(4),XRFoveationTests(3),XRImmersionContractTest(4), all passing.