diff --git a/Meshconvert/MeshOBJ.cpp b/Meshconvert/MeshOBJ.cpp index be43f754..c01cae98 100644 --- a/Meshconvert/MeshOBJ.cpp +++ b/Meshconvert/MeshOBJ.cpp @@ -80,6 +80,7 @@ HRESULT LoadFromOBJ(const wchar_t* szFilename, std::unique_ptr& inMesh, st if (FAILED(hr)) return hr; + // These layouts are only used to extract data, so the offsets match WaveFrontReader::Vertex static const D3D11_INPUT_ELEMENT_DESC s_vboLayout[] = { { "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 }, @@ -91,10 +92,33 @@ HRESULT LoadFromOBJ(const wchar_t* szFilename, std::unique_ptr& inMesh, st { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 }, }; + // Vertex colors are read from a second stream (slot 1) alongside the Vertex data + 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_R32G32B32A32_FLOAT, 1, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, 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_R32G32B32A32_FLOAT, 1, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + }; + const D3D11_INPUT_ELEMENT_DESC* layout = s_vboLayout; size_t nDecl = std::size(s_vboLayout); - if (!wfReader.hasNormals && !wfReader.hasTexcoords) + const bool hasColors = wfReader.hasVertexColors && (wfReader.vertexColors.size() == wfReader.vertices.size()); + + if (hasColors) + { + layout = (wfReader.hasNormals) ? s_vboLayoutVC : s_vboLayoutAltVC; + nDecl = (wfReader.hasNormals) ? std::size(s_vboLayoutVC) : std::size(s_vboLayoutAltVC); + if (!wfReader.hasTexcoords) + --nDecl; + } + else if (!wfReader.hasNormals && !wfReader.hasTexcoords) { nDecl = 1; } @@ -117,6 +141,21 @@ HRESULT LoadFromOBJ(const wchar_t* szFilename, std::unique_ptr& inMesh, st if (FAILED(hr)) return hr; + std::vector colors; + if (hasColors) + { + // OBJ vertex colors have no alpha, so use opaque + colors.reserve(wfReader.vertexColors.size()); + for (const auto& c : wfReader.vertexColors) + { + colors.emplace_back(c.x, c.y, c.z, 1.f); + } + + hr = vbr.AddStream(colors.data(), colors.size(), 1, sizeof(XMFLOAT4)); + if (FAILED(hr)) + return hr; + } + hr = inMesh->SetVertexData(vbr, wfReader.vertices.size()); if (FAILED(hr)) return hr; diff --git a/Utilities/WaveFrontReader.h b/Utilities/WaveFrontReader.h index 6633c041..d7794abe 100644 --- a/Utilities/WaveFrontReader.h +++ b/Utilities/WaveFrontReader.h @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -75,7 +76,8 @@ namespace DX WaveFrontReader() noexcept : hasNormals(false), - hasTexcoords(false) + hasTexcoords(false), + hasVertexColors(false) {} HRESULT Load(_In_z_ const wchar_t* szFileName, bool ccw = true, bool loadmtl = true) @@ -107,6 +109,7 @@ namespace DX std::vector positions; std::vector normals; std::vector texCoords; + std::vector positionColors; VertexCache vertexCache; @@ -152,6 +155,32 @@ namespace DX float x, y, z; InFile >> x >> y >> z; positions.emplace_back(XMFLOAT3(x, y, z)); + + // Some tools (e.g. MeshLab) write a per-vertex color after the position as + // "v x y z r g b". This is not part of the OBJ specification, so only exactly + // three extra values are treated as a color; a single extra value is the + // optional 'w' coordinate and is ignored. + XMFLOAT3 color(1.f, 1.f, 1.f); + + std::wstring rest; + std::getline(InFile, rest, L'\n'); + InFile.putback(L'\n'); + + std::wistringstream extra(rest); + extra.imbue(std::locale::classic()); + + float values[4] = {}; + size_t count = 0; + while (count < std::size(values) && extra >> values[count]) + ++count; + + if (count == 3) + { + color = XMFLOAT3(values[0], values[1], values[2]); + hasVertexColors = true; + } + + positionColors.emplace_back(color); } else if (0 == wcscmp(strCommand.c_str(), L"vt")) { @@ -284,6 +313,12 @@ namespace DX if (index == uint32_t(-1)) return E_OUTOFMEMORY; + if (index == vertexColors.size()) + { + // New vertex: its color comes from the position it was built from + vertexColors.emplace_back(positionColors[vertexIndex]); + } + constexpr uint32_t maxIndex = (sizeof(index_t) == 2) ? UINT16_MAX : UINT32_MAX; if (index >= maxIndex) { @@ -399,6 +434,11 @@ namespace DX if (positions.empty()) return E_FAIL; + if (!hasVertexColors) + { + vertexColors.clear(); + } + // Cleanup InFile.close(); @@ -617,9 +657,11 @@ namespace DX indices.clear(); attributes.clear(); materials.clear(); + vertexColors.clear(); name.clear(); - hasNormals = false; - hasTexcoords = false; + hasNormals = false; + hasTexcoords = false; + hasVertexColors = false; bounds.Center.x = bounds.Center.y = bounds.Center.z = 0.f; bounds.Extents.x = bounds.Extents.y = bounds.Extents.z = 0.f; @@ -739,9 +781,14 @@ namespace DX std::vector attributes; std::vector materials; + // Per-vertex colors from the non-standard "v x y z r g b" extension, one per entry in + // 'vertices'. Only filled in when hasVertexColors is true. + std::vector vertexColors; + std::wstring name; bool hasNormals; bool hasTexcoords; + bool hasVertexColors; DirectX::BoundingBox bounds;