Skip to content

WaveFrontReader: read per-vertex colors from "v x y z r g b" - #329

Open
Joel Kiptoo (Kiptoo-Deus) wants to merge 5 commits into
microsoft:mainfrom
Kiptoo-Deus:wavefront-vertex-colors
Open

Joel Kiptoo (Kiptoo-Deus) wants to merge 5 commits into
microsoft:mainfrom
Kiptoo-Deus:wavefront-vertex-colors

Conversation

@Kiptoo-Deus

Copy link
Copy Markdown

Fixes #75.

Tools such as MeshLab write a per-vertex color after the position on v lines (v x y z r g b). The reader read x y z and dropped the rest of the line, so these colors were lost.

Changes in WaveFrontReader.h:

  • On a v line, the rest of the line is parsed after x y z. If there are exactly three extra values they are read as an RGB color. A single extra value is still the optional w coordinate from the OBJ spec and is ignored, and any other count is ignored as well. Positions without a color default to white.
  • The colors go into a new public vertexColors array (XMFLOAT3, one entry per entry in vertices) with a hasVertexColors flag, filled in as vertices are created from faces. When the file has no colors the array stays empty.
  • Vertex itself is unchanged, so the .vbo layout used by LoadVBO and existing callers are not affected.
  • The rest of the line is read into a std::wstring rather than a fixed buffer, so an overlong v line can't put the stream into a failed state.

Vertex deduplication is keyed on the position index, so vertices that share a position also share its color and the existing dedup behaviour doesn't change.

Testing: on macOS with clang, using the same small shim as in #324 (std::wifstream from a wchar_t* path is an MSVC extension libc++ doesn't have), I loaded:

  • the MeshLab snippet from the issue: colors 0.984314 0.764706 1.0 etc. come through with the right positions and normals
  • a quad with a different color on each corner, shared between two faces: 4 vertices, 4 colors
  • a file without colors and a file using v x y z w: hasVertexColors is false and vertexColors is empty
  • a file where only some vertices have a color: the others are white
  • CRLF line endings, an end-of-line comment and relative (negative) indices
  • a v line with 1,700 extra values: the rest of the file still loads

I didn't touch meshconvert. If you'd like the colors passed through to its output formats I can follow up on that separately.

Some tools, such as MeshLab, write a per-vertex color after the position
on 'v' lines. The reader ignored everything after x y z, so these colors
were dropped.

When a 'v' line has exactly three extra values they are read as an RGB
color. A single extra value is still treated as the optional 'w'
coordinate and ignored. Positions without a color default to white.

The colors go into a new vertexColors array, one entry per vertex,
alongside a hasVertexColors flag. Vertex itself is unchanged, so the
.vbo format and existing callers are unaffected.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@walbourn Chuck Walbourn (walbourn) added the utility Applies to WaveFrontReader, etc. label Oct 9, 2026
@walbourn

Copy link
Copy Markdown
Collaborator

Do you have an example obj file with this vertex color data?

@Kiptoo-Deus

Copy link
Copy Markdown
Author

Here is a small one: a unit cube with a different color at each corner, in the v x y z r g b form MeshLab writes (colors in 0..1). Save it as colored_cube.obj.

# Unit cube with per-vertex colors ("v x y z r g b", as written by MeshLab)
v -0.5 -0.5 -0.5 0.0 0.0 0.0
v  0.5 -0.5 -0.5 1.0 0.0 0.0
v  0.5  0.5 -0.5 1.0 1.0 0.0
v -0.5  0.5 -0.5 0.0 1.0 0.0
v -0.5 -0.5  0.5 0.0 0.0 1.0
v  0.5 -0.5  0.5 1.0 0.0 1.0
v  0.5  0.5  0.5 1.0 1.0 1.0
v -0.5  0.5  0.5 0.0 1.0 1.0
f 1 4 3
f 1 3 2
f 5 6 7
f 5 7 8
f 1 2 6
f 1 6 5
f 4 8 7
f 4 7 3
f 1 5 8
f 1 8 4
f 2 3 7
f 2 7 6

Loading it with the reader from this PR gives 8 vertices, 36 indices, hasVertexColors == true, and 8 entries in vertexColors, each matching the color of its source position (e.g. the vertex at (-0.5, 0.5, -0.5) is (0, 1, 0)).

@walbourn

Copy link
Copy Markdown
Collaborator

You could also add this case to LoadFromOBJ in meshconvert/meshconvert. I think it would be two more possible layouts:

    static const D3D11_INPUT_ELEMENT_DESC s_vboLayoutVC[] = {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 36, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    };

    static const D3D11_INPUT_ELEMENT_DESC s_vboLayoutAltVC[] = {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    };

There's also a bug here that should be fixed:

    static const D3D11_INPUT_ELEMENT_DESC s_vboLayoutAlt[] = {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12 /* NOT 24 */, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    };

@Kiptoo-Deus

Copy link
Copy Markdown
Author

Thanks, added in a095220.

One difference from your snippet: this PR keeps the colors in their own vertexColors array instead of inside WaveFrontReader::Vertex, so the two new layouts pull COLOR from a second stream (slot 1). NORMAL and TEXCOORD stay at 12 and 24 to match Vertex. I also pass the colors as RGBA with alpha set to 1. Otherwise they'd come through with alpha 0 and end up transparent in the 8-bit color outputs. OBJ files without colors go through the same path as before.

About the s_vboLayoutAlt offset: I think 24 is actually right there. Those offsets point into the Vertex buffer, and textureCoordinate is always at byte 24 in it, even when the file has no normals. I tried it with VBReader: 24 gives back the UVs, while 12 just reads the empty normal. I may be missing something, though. If you'd still like it changed, I'm happy to do it.

@walbourn

Copy link
Copy Markdown
Collaborator

Joel Kiptoo (@Kiptoo-Deus) You are correct. Perhaps there should be a comment in the code there that says `// matches WaveFrontReader::Vertex`` since that's just used for data extraction.

@Kiptoo-Deus

Copy link
Copy Markdown
Author

Thanks for checking. Added that comment above the layouts in 585b1e9.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

utility Applies to WaveFrontReader, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WaveFrontReader update to support per-vertex color extension

2 participants