Skip to content

Sync fork develop with upstream: material bake and glass (#1307, #1308) - #71

Merged
miogds merged 6 commits into
developfrom
sync/upstream_develop_1007
Oct 7, 2026
Merged

miogds merged 6 commits into
developfrom
sync/upstream_develop_1007

Conversation

@miogds

@miogds miogds commented Oct 7, 2026

Copy link
Copy Markdown
Member

Brings the fork's develop up to upstream develop at 4ae99ca2 ("Remove hardcoded AWS/CloudFront streaming URLs from demos and tests (untoldengine#1317)"). The fork was five commits behind; after this merge it differs from upstream by the 13 proposal documents under docs/proposals only.

What it brings

Merge

One merge commit of upstream/develop, no conflicts: the fork's side since the last sync (#70) is the proposal documents only.

Verified locally on the merge commit

  • Exporter: 270 tests (python3 -m unittest discover -s scripts/tests -t .); the two Blender check scripts (texture_write_checks.py 7, material_bake_checks.py 9) pass on upstream's tip, which this merge takes unchanged.
  • Core: UntoldEngineTests 1,628 tests, 2 skipped, 0 failures (run from a checkout folder named UntoldEngine, so the external render extension package test runs too).
  • Component kit: 59 tests.
  • Render: the glass and material shading classes (GlassShadingTests, BaseColorShadingTests, EmissiveShadingTests, EnvironmentReflectionShadingTests, NormalMapShadingTests, HeightMaterialAPITests, NativeFormatMaterialTests): 53 tests, 0 failures, PSNR threshold 33.5. The rest of the render suite is upstream's code and passed upstream's CI at 4ae99ca2.
  • Strict concurrency (scripts/strict-concurrency-guardrails.sh): build passes, 0 warnings.
  • Editor fork develop (aba6ce4) built against this merge commit through a local path dependency: builds, 1,071 tests, 1 skipped, 0 failures.

The user's editor gets the engine with Xcode's "Update to Latest Package Versions" after the merge.

miogds and others added 6 commits October 6, 2026 22:45
…ntoldengine#1307)

A Principled input driven by procedural nodes (a Noise or Brick texture,
node math, a Bump from a procedural height) has no image to export, and was
written as the socket's slider value, which Blender ignores once the socket
is linked. Solar cells came out as the default grey and galvanised steel as
a non-metal.

The command-line export now bakes such inputs with Cycles on a flat swatch:
what the material shows on a plane.

- A pattern laid out by object coordinates or world positions becomes one
  set of repeating textures per material (base color, occlusion-roughness-
  metallic, normal). The meshes that use it get texture coordinates
  projected from their positions, so they need no UV map and copies of a
  mesh still export as one model.
- Anything else (a pattern on UV, generated or camera coordinates, or image
  textures in the same material) keeps the mesh's UVs and exports the value
  the input averages to over the swatch.

The swatch faces the way most of the material's surface does. A coarse bake
finds the tile to cut: a whole number of periods for bricks, tiles and
cells (found from the pattern's edges, so bricks of random colours count),
2 m for noise, with a band along the cut blended so it repeats without a
seam. Each image is baked at the size its detail needs, up to 1024 texels.

A flat face is mapped in its own plane, without stretch; a smooth surface
along the nearest axis. A pattern laid out in the world is mapped from
world positions on a mesh placed once, so it continues from one object to
the next as in Blender; copies of one mesh share one mapping at the
pattern's world size.

--no-material-bake switches it off; --material-bake-size and
--material-bake-tile set the texture size and the longest tile. The tile
pipeline and the Blender add-on's export do not bake.

Co-authored-by: Javier Segura <javier@miolabs.com>
…d keeps its reflections (untoldengine#1308)

* [Feature] Glass: a material's transmission shows what is behind it

Glass had to be stood in for by a blended surface. A surface that is a
tenth there shows a tenth of its reflections and of its glow, and
covers what is behind it with a lit film of its own color: windows
came out as milky panes without a sheen, tinted glass as paint.

A material now has a transmission, from 0 (a solid surface) to 1:

- UntoldMaterialRecordV1.transmissionFactor, kept in the record's
  second reserved word, which every file written before leaves at
  zero. No new format version.
- Material.transmission, read and set with getMaterialTransmission
  and updateMaterialTransmission, and kept in a saved scene.
- A material that lets light through is drawn in the transparency
  pass whatever its alpha mode (Material.hasTransparency). One that
  is all metal or frosted lets nothing through and stays solid, with
  its depth, its shadow and its batch (Material.transmitsLight).

For each pixel the transparency pass now gives the blender the light
the surface adds and the share of what is already there that it lets
through, for each of red, green and blue:

- Glass adds its reflections and its glow whole and scatters none of
  the light that falls on it.
- It lets through what it does not reflect, by the table the
  reflections use, tinted by the square root of the base color at
  each face: a pane seen through its two faces shows its base color,
  as in Blender.
- Nothing is bent or blurred behind it. Rough glass shows less of
  what is behind it (all of it up to a roughness of 0.05, none from
  0.5) and glows with the light that comes from behind it instead:
  the environment straight through the glass, blurred, and the sun
  on its far side.
- The alpha stays what it was, how much of the surface is there, and
  a blended material without transmission draws as before.

The pass's pipeline blends with two sources to keep the three shares
(PipelineBlendMode.premultipliedOverFilteredDestination). Where that
cannot be created, and in the simulator, it falls back to
premultiplied alpha: tinted glass then darkens what is behind it by
the brightness of its color without coloring it.

The triangles of a mesh are drawn in the order they come, so glass is
drawn in two goes, its faces turned away from the viewer and then the
ones turned towards the viewer, each lit on the side the viewer sees:
the far side of a pane never comes out over its near side.

Glass casts no shadow: the shadow passes leave it out. A blended
material still casts the shadow of a solid one.

* [Feature] Export Blender's transmission as the material's transmission

The exporter wrote glass as an opacity: the Principled BSDF's
transmission, its tint, its metal and its roughness folded into one
alpha. It now writes the Transmission Weight as the material's
transmission and leaves the surface whole, so the engine draws glass
with its reflections and tints what crosses it by the base color,
texel by texel.

- exported_transmission: the weight as it stands, or none when under
  5 % of what is behind the surface would show through it
  (MIN_TRANSMITTANCE), counting its transmission, the brightness of
  its base color, its metallic share and its roughness. Such a
  surface is exported solid and keeps its depth, its shadow and its
  batch: black glass, and the metallic or matte paints of an
  imported car left with a Transmission of 1.
- roughness_sharpness repeats the engine's rule for rough glass.
- A Principled BSDF covers its surface whatever its transmission. A
  Transparent BSDF mixed in still lowers the opacity by its share.
- The material fidelity report lists the surfaces kept solid, rough
  glass and a transmission it cannot follow. Plain glass is no longer
  an approximation, and is not listed.

* [Chores] Rebuild the shader libraries

The seven libraries, from the shaders of the two commits before.

---------

Co-authored-by: Javier Segura <javier@miolabs.com>
…whole run (untoldengine#1312)

testAReaderOnAnotherThreadNeverGetsAReleasedComponent failed the core tests
of a pull request that changes only the exporter (untoldengine#1307): 0 reads, where
the test asks for more than 0.

The reader thread read the 64 entities the test starts with. The writer
destroys each of them within its first three rounds and keeps the new ones
to itself, so from then on the reader asked for entities that no longer
exist. A reader thread that was slow to start read nothing, and the test
failed. When it passed, the reader had read during three rounds of 2,000:
about 2,000 reads, then 240,000 failed lookups, each one an "Entity is
missing" line in the log.

The reader is now told which entities there are, and the changes start once
it has read them all. It reads through a copy of the scene, so an entity
destroyed since is skipped without an error: one pass through a copy it
keeps, as a render pass reads, the next with a copy for each read, as a
call through `scene` reads. The writer asks for the release right after a
removal too, while the slot still shows the component that left.

The reader now makes about 5 million reads spread over the run, and the
test logs no error. With the check on the copies taken out of the release,
the test fails under Address Sanitizer in 5 runs of 5 (a read of a released
component); the old one passed all 5.

Co-authored-by: Javier Segura <javier@miolabs.com>
ComponentSlot.store exchanges what a slot holds for the new component, so
it reads the slot before it writes it. A chunk came from the allocator as
it was, and the first component of an entity was exchanged against memory
that nothing had initialized. The exchange still came out right, because it
takes whatever value it finds, but it read uninitialized memory.

A chunk of a pool of objects is now cleared when it is allocated. The first
exchange reads no reference, and a slot that never held a component reads
as none instead of as whatever the memory held. A chunk of values is left
as it comes: its slots are written with initialize(to:), which reads
nothing.

Found by CodeRabbit in the review of untoldengine#1302.

Co-authored-by: Javier Segura <javier@miolabs.com>
…ntoldengine#1317)

Drop the remote-scene streaming options from ShowcaseDemo and
LargeSceneStreamingDemo that pointed at public CloudFront manifests, and
disable RemoteStreamFlyThroughTests until a replacement manifest host is
available.
@miogds
miogds merged commit e49e795 into develop Oct 7, 2026
5 checks passed
@miogds
miogds deleted the sync/upstream_develop_1007 branch October 7, 2026 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants