Skip to content

GSOC 26: render multi-material models per part#8955

Open
Nixxx19 wants to merge 13 commits into
processing:mainfrom
Nixxx19:renderer-per-part
Open

GSOC 26: render multi-material models per part#8955
Nixxx19 wants to merge 13 commits into
processing:mainfrom
Nixxx19:renderer-per-part

Conversation

@Nixxx19

@Nixxx19 Nixxx19 commented Jun 24, 2026

Copy link
Copy Markdown
Member

part of my google summer of code 2026 project, "full texture support for .mtl files in p5.js". works toward #6924.

this is phase 3: the renderer side, building on the data + parser foundation in #8879. multi-material models now actually draw, one part per material, each with its own look. public api is unchanged, you still just call loadModel() and model().

project phases for context:

what's in here:

  • the renderer loops over a geometry's parts and draws each one
  • each part applies its own material (fill, specular, ambient, shininess, texture) around its draw, restored after so nothing leaks between parts
  • a single-material geometry is its own part, so that path renders exactly as before, byte for byte
  • p5.GeometryPart gains what the renderer needs to treat it as a drawable (hasFillTransparency, userVertexProperties)

tests:

  • a multi-material model (octa-color, 8 materials) draws one fill per part and passes the instance count through
  • the existing obj+mtl diffuse-colour visual test still matches its reference screenshot
  • full unit suite green, including the webgpu and visual (pixel-compare) tests

stacked on #8879, so it should go in after that one merges.

@Nixxx19 Nixxx19 changed the title render multi-material models per part GSOC 26: render multi-material models per part Jun 24, 2026

@davepagurek davepagurek left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! I left a few comments. One thing that would be good to clarify is what the valid states of a geometry are (should they always have parts?) so that that can be an invariant in the code and simplifies implementation.

Comment thread src/core/p5.Renderer3D.js Outdated
Comment thread src/core/p5.Renderer3D.js Outdated
Comment thread test/unit/webgl/p5.GeometryPart.js Outdated
Comment thread test/unit/webgl/p5.GeometryPart.js Outdated
Comment thread src/webgl/p5.GeometryPart.js Outdated
Comment thread test/unit/webgl/p5.GeometryPart.js Outdated
@Nixxx19

Nixxx19 commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

Looking good! I left a few comments. One thing that would be good to clarify is what the valid states of a geometry are (should they always have parts?) so that that can be an invariant in the code and simplifies implementation.

thanks for the review! on the invariant, yeah a geometry should always have at least one part, the constructor guarantees it (parts = [this]) and loaders replace it. i'll lean into that so the renderer just loops parts with no fallback, like you said, addressing the rest now

@Nixxx19

Nixxx19 commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

heads up, the failing check is just flaky ci, not anything in this pr.

Comment thread src/core/p5.Renderer3D.js Outdated
const state = part.partState;
const hasMaterial = state && (
state.fill || state.texture || state.ambientColor ||
state.specularColor || state.shininess != null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heads up, != will consider 0 to be equivalent to null as both are falsy. Do you want !== here? (Also worth double checking that one of these could have valid falsy values -- to confirm, are fill and ambientColor p5.Color values or arrays?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are all plain arrays, not p5.Color, fill is [r,g,b,a], ambient/specular are [r,g,b], texture's a p5.Image. shininess is the only number, and 0 != null is already true so a 0 still counts as set. made them all != null anyway so it stays consistent

Comment thread src/webgl/loading.js Outdated
if (material.diffuseColor) {
// carry the mtl transparency (d / Tr) in the fill's alpha. opaque materials
// stay rgb so nothing changes for them.
state.fill = material.opacity != null && material.opacity < 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might simplify things further to have the fill always be in the same format. So you could do [...material.diffuseColor, material.opacity ?? 255] assuming a 0-255 range. It's also not obvious what the data type and range should be for each of these so it's maybe worth adding in a comment where part state is first defined what the type and range of each field is, and then ensuring that they are always that type (e.g. [number, number, number, number] | null where the numbers are 0-255)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, fill's always [r,g,b,a] now. one thing, the range is 0..1 not 0..255 (curFillColor's 0..1, and Kd/d parse straight to floats), so it came out [...diffuseColor, opacity ?? 1]. also documented the type/range of each field on createPartState, and dropped the alpha padding in _applyPartState + hasFillTransparency now that fill's 4 wide

@Nixxx19 Nixxx19 force-pushed the renderer-per-part branch from 59b2cce to e861e3c Compare July 14, 2026 05:20
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