diff options
author | Dave Parks <davep@lindenlab.com> | 2024-06-10 10:43:38 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-10 10:43:38 -0500 |
commit | 227e9be06832515fd10eb496d4a2a4528d1ebd92 (patch) | |
tree | dfe366ee3ea6c4fe4b08dc080fd07313e279a7d5 /indra/newview/gltf/primitive.cpp | |
parent | 33ddedd6b557ed9130dd8cd3b8327a697614a3ac (diff) |
#1654 generate normals and tangents according to gltf specification (#1662)
* Disable unloading of objects in background.
* Add unlit GLTF shader variant
Diffstat (limited to 'indra/newview/gltf/primitive.cpp')
-rw-r--r-- | indra/newview/gltf/primitive.cpp | 352 |
1 files changed, 307 insertions, 45 deletions
diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp index 886b8f79c9..8d9880951b 100644 --- a/indra/newview/gltf/primitive.cpp +++ b/indra/newview/gltf/primitive.cpp @@ -30,9 +30,204 @@ #include "buffer_util.h" #include "../llviewershadermgr.h" +#include "mikktspace/mikktspace.hh" + +#include "meshoptimizer/meshoptimizer.h" + + using namespace LL::GLTF; using namespace boost::json; + +// Mesh data useful for Mikktspace tangent generation (and flat normal generation) +struct MikktMesh +{ + std::vector<LLVector3> p; + std::vector<LLVector3> n; + std::vector<LLVector2> tc; + std::vector<LLVector4> w; + std::vector<LLVector4> t; + std::vector<LLColor4U> c; + std::vector<U64> j; + + // initialize from src primitive and make an unrolled triangle list + // returns false if the Primitive cannot be converted to a triangle list + bool copy(const Primitive* prim) + { + bool indexed = !prim->mIndexArray.empty(); + U32 vert_count = indexed ? prim->mIndexArray.size() : prim->mPositions.size(); + + if (prim->mMode != Primitive::Mode::TRIANGLES) + { + LL_WARNS("GLTF") << "Unsupported primitive mode for conversion to triangles: " << (S32) prim->mMode << LL_ENDL; + return false; + } + + p.resize(vert_count); + n.resize(vert_count); + tc.resize(vert_count); + c.resize(vert_count); + + bool has_normals = !prim->mNormals.empty(); + if (has_normals) + { + n.resize(vert_count); + } + bool has_tangents = !prim->mTangents.empty(); + if (has_tangents) + { + t.resize(vert_count); + } + bool rigged = !prim->mWeights.empty(); + if (rigged) + { + w.resize(vert_count); + j.resize(vert_count); + } + + for (int i = 0; i < vert_count; ++i) + { + U32 idx = indexed ? prim->mIndexArray[i] : i; + + p[i].set(prim->mPositions[idx].getF32ptr()); + tc[i].set(prim->mTexCoords[idx]); + c[i] = prim->mColors[idx]; + + if (has_normals) + { + n[i].set(prim->mNormals[idx].getF32ptr()); + } + + if (rigged) + { + w[i].set(prim->mWeights[idx].getF32ptr()); + j[i] = prim->mJoints[idx]; + } + } + + return true; + } + + void genNormals() + { + U32 tri_count = p.size() / 3; + for (U32 i = 0; i < tri_count; ++i) + { + LLVector3 v0 = p[i * 3]; + LLVector3 v1 = p[i * 3 + 1]; + LLVector3 v2 = p[i * 3 + 2]; + + LLVector3 normal = (v1 - v0) % (v2 - v0); + normal.normalize(); + + n[i * 3] = normal; + n[i * 3 + 1] = normal; + n[i * 3 + 2] = normal; + } + } + + void genTangents() + { + t.resize(p.size()); + mikk::Mikktspace ctx(*this); + ctx.genTangSpace(); + } + + // write to target primitive as an indexed triangle list + // Only modifies runtime data, does not modify the original GLTF data + void write(Primitive* prim) const + { + //re-weld + meshopt_Stream mos[] = + { + { &p[0], sizeof(LLVector3), sizeof(LLVector3) }, + { &n[0], sizeof(LLVector3), sizeof(LLVector3) }, + { &t[0], sizeof(LLVector4), sizeof(LLVector4) }, + { &tc[0], sizeof(LLVector2), sizeof(LLVector2) }, + { &c[0], sizeof(LLColor4U), sizeof(LLColor4U) }, + { w.empty() ? nullptr : &w[0], sizeof(LLVector4), sizeof(LLVector4) }, + { j.empty() ? nullptr : &j[0], sizeof(U64), sizeof(U64) } + }; + + std::vector<U32> remap; + remap.resize(p.size()); + + U32 stream_count = w.empty() ? 5 : 7; + + size_t vert_count = meshopt_generateVertexRemapMulti(&remap[0], nullptr, p.size(), p.size(), mos, stream_count); + + prim->mTexCoords.resize(vert_count); + prim->mNormals.resize(vert_count); + prim->mTangents.resize(vert_count); + prim->mPositions.resize(vert_count); + prim->mColors.resize(vert_count); + if (!w.empty()) + { + prim->mWeights.resize(vert_count); + prim->mJoints.resize(vert_count); + } + + prim->mIndexArray.resize(remap.size()); + + for (int i = 0; i < remap.size(); ++i) + { + U32 src_idx = i; + U32 dst_idx = remap[i]; + + prim->mIndexArray[i] = dst_idx; + + prim->mPositions[dst_idx].load3(p[src_idx].mV); + prim->mNormals[dst_idx].load3(n[src_idx].mV); + prim->mTexCoords[dst_idx] = tc[src_idx]; + prim->mTangents[dst_idx].loadua(t[src_idx].mV); + prim->mColors[dst_idx] = c[src_idx]; + + if (!w.empty()) + { + prim->mWeights[dst_idx].loadua(w[src_idx].mV); + prim->mJoints[dst_idx] = j[src_idx]; + } + } + + prim->mGLMode = LLRender::TRIANGLES; + } + + uint32_t GetNumFaces() + { + return uint32_t(p.size()/3); + } + + uint32_t GetNumVerticesOfFace(const uint32_t face_num) + { + return 3; + } + + mikk::float3 GetPosition(const uint32_t face_num, const uint32_t vert_num) + { + F32* v = p[face_num * 3 + vert_num].mV; + return mikk::float3(v); + } + + mikk::float3 GetTexCoord(const uint32_t face_num, const uint32_t vert_num) + { + F32* uv = tc[face_num * 3 + vert_num].mV; + return mikk::float3(uv[0], uv[1], 1.0f); + } + + mikk::float3 GetNormal(const uint32_t face_num, const uint32_t vert_num) + { + F32* normal = n[face_num * 3 + vert_num].mV; + return mikk::float3(normal); + } + + void SetTangentSpace(const uint32_t face_num, const uint32_t vert_num, mikk::float3 T, bool orientation) + { + S32 i = face_num * 3 + vert_num; + t[i].set(T.x, T.y, T.z, orientation ? 1.0f : -1.0f); + } +}; + + bool Primitive::prep(Asset& asset) { // allocate vertex buffer @@ -94,26 +289,12 @@ bool Primitive::prep(Asset& asset) } } - U32 mask = ATTRIBUTE_MASK; + U32 mask = LLVertexBuffer::MAP_VERTEX; if (!mWeights.empty()) { mask |= LLVertexBuffer::MAP_WEIGHT4; - } - - if (LLGLSLShader::sCurBoundShaderPtr == nullptr) - { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer - gDebugProgram.bind(); - } - mVertexBuffer = new LLVertexBuffer(mask); - mVertexBuffer->allocateBuffer(mPositions.size(), mIndexArray.size()*2); // double the size of the index buffer for 32-bit indices - - mVertexBuffer->setBuffer(); - mVertexBuffer->setPositionData(mPositions.data()); - - if (!mIndexArray.empty()) - { - mVertexBuffer->setIndexData(mIndexArray.data()); + mask |= LLVertexBuffer::MAP_JOINT; } if (mTexCoords.empty()) @@ -121,23 +302,17 @@ bool Primitive::prep(Asset& asset) mTexCoords.resize(mPositions.size()); } - // flip texcoord y, upload, then flip back (keep the off-spec data in vram only) - for (auto& tc : mTexCoords) - { - tc[1] = 1.f - tc[1]; - } - mVertexBuffer->setTexCoordData(mTexCoords.data()); - - for (auto& tc : mTexCoords) - { - tc[1] = 1.f - tc[1]; - } + // TODO: support more than one texcoord set (or no texcoords) + mask |= LLVertexBuffer::MAP_TEXCOORD0; if (mColors.empty()) { mColors.resize(mPositions.size(), LLColor4U::white); } + // TODO: support colorless vertex buffers + mask |= LLVertexBuffer::MAP_COLOR; + // bake material basecolor into color array if (mMaterial != INVALID_INDEX) { @@ -149,44 +324,131 @@ bool Primitive::prep(Asset& asset) } } - mVertexBuffer->setColorData(mColors.data()); + mShaderVariant = 0; if (mNormals.empty()) { - mNormals.resize(mPositions.size(), LLVector4a(0, 0, 1, 0)); + mTangents.clear(); + + if (mMode == Mode::POINTS || mMode == Mode::LINES || mMode == Mode::LINE_LOOP || mMode == Mode::LINE_STRIP) + { //no normals and no surfaces, this primitive is unlit + mTangents.clear(); + mShaderVariant |= LLGLSLShader::GLTFVariant::UNLIT; + } + else + { + // unroll into non-indexed array of flat shaded triangles + MikktMesh data; + if (!data.copy(this)) + { + return false; + } + + data.genNormals(); + data.genTangents(); + data.write(this); + } + } + + bool unlit = (mShaderVariant & LLGLSLShader::GLTFVariant::UNLIT) != 0; + + if (mTangents.empty() && !unlit) + { // NOTE: must be done last because tangent generation rewrites the other arrays + // adapted from usage of Mikktspace in llvolume.cpp + if (mMode == Mode::POINTS || mMode == Mode::LINES || mMode == Mode::LINE_LOOP || mMode == Mode::LINE_STRIP) + { + // for points and lines, just make sure tangent is perpendicular to normal + mTangents.resize(mNormals.size()); + LLVector4a up(0.f, 0.f, 1.f, 0.f); + LLVector4a left(1.f, 0.f, 0.f, 0.f); + for (U32 i = 0; i < mNormals.size(); ++i) + { + if (fabsf(mNormals[i].getF32ptr()[2]) < 0.999f) + { + mTangents[i] = up.cross3(mNormals[i]); + } + else + { + mTangents[i] = left.cross3(mNormals[i]); + } + + mTangents[i].getF32ptr()[3] = 1.f; + } + } + else + { + MikktMesh data; + if (!data.copy(this)) + { + return false; + } + + data.genTangents(); + data.write(this); + } } - mVertexBuffer->setNormalData(mNormals.data()); - if (mTangents.empty()) + if (!unlit) { - // TODO: generate tangents if needed - mTangents.resize(mPositions.size(), LLVector4a(1, 0, 0, 1)); + mask |= LLVertexBuffer::MAP_NORMAL; + mask |= LLVertexBuffer::MAP_TANGENT; + } + + if (LLGLSLShader::sCurBoundShaderPtr == nullptr) + { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer + gDebugProgram.bind(); } - mVertexBuffer->setTangentData(mTangents.data()); + mVertexBuffer = new LLVertexBuffer(mask); + mVertexBuffer->allocateBuffer(mPositions.size(), mIndexArray.size() * 2); // double the size of the index buffer for 32-bit indices + + mVertexBuffer->setBuffer(); + mVertexBuffer->setPositionData(mPositions.data()); + mVertexBuffer->setColorData(mColors.data()); + + if (!mNormals.empty()) + { + mVertexBuffer->setNormalData(mNormals.data()); + } + if (!mTangents.empty()) + { + mVertexBuffer->setTangentData(mTangents.data()); + } if (!mWeights.empty()) { - std::vector<LLVector4a> weight_data; - weight_data.resize(mWeights.size()); + mShaderVariant |= LLGLSLShader::GLTFVariant::RIGGED; + mVertexBuffer->setWeight4Data(mWeights.data()); + mVertexBuffer->setJointData(mJoints.data()); + } - F32 max_weight = 1.f - FLT_EPSILON*100.f; - LLVector4a maxw(max_weight, max_weight, max_weight, max_weight); - for (U32 i = 0; i < mWeights.size(); ++i) - { - LLVector4a& w = weight_data[i]; - w.setMin(mWeights[i], maxw); - w.add(mJoints[i]); - }; + // flip texcoord y, upload, then flip back (keep the off-spec data in vram only) + for (auto& tc : mTexCoords) + { + tc[1] = 1.f - tc[1]; + } + mVertexBuffer->setTexCoordData(mTexCoords.data()); + for (auto& tc : mTexCoords) + { + tc[1] = 1.f - tc[1]; + } - mVertexBuffer->setWeight4Data(weight_data.data()); + if (!mIndexArray.empty()) + { + mVertexBuffer->setIndexData(mIndexArray.data()); } createOctree(); mVertexBuffer->unbind(); + Material& material = asset.mMaterials[mMaterial]; + if (material.mAlphaMode == Material::AlphaMode::BLEND) + { + mShaderVariant |= LLGLSLShader::GLTFVariant::ALPHA_BLEND; + } + return true; } |