diff options
Diffstat (limited to 'indra/newview/gltf/primitive.cpp')
-rw-r--r-- | indra/newview/gltf/primitive.cpp | 630 |
1 files changed, 521 insertions, 109 deletions
diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp index b57a0af18d..e1579374d4 100644 --- a/indra/newview/gltf/primitive.cpp +++ b/indra/newview/gltf/primitive.cpp @@ -9,7 +9,7 @@ * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; - * version 2.1 of the License only. + * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -28,12 +28,295 @@ #include "asset.h" #include "buffer_util.h" +#include "../llviewershadermgr.h" + +#include "mikktspace/mikktspace.hh" + +#include "meshoptimizer/meshoptimizer.h" -#include "../lltinygltfhelper.h" using namespace LL::GLTF; +using namespace boost::json; + -void Primitive::allocateGLResources(Asset& asset) +// Mesh data useful for Mikktspace tangent generation (and flat normal generation) +struct MikktMesh +{ + std::vector<LLVector3> p; //positions + std::vector<LLVector3> n; //normals + std::vector<LLVector4> t; //tangents + std::vector<LLVector2> tc0; //texcoords 0 + std::vector<LLVector2> tc1; //texcoords 1 + std::vector<LLColor4U> c; //colors + std::vector<LLVector4> w; //weights + std::vector<U64> j; //joints + + // 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(); + size_t vert_count = indexed ? prim->mIndexArray.size() : prim->mPositions.size(); + + size_t triangle_count = 0; + + if (prim->mMode == Primitive::Mode::TRIANGLE_STRIP || + prim->mMode == Primitive::Mode::TRIANGLE_FAN) + { + triangle_count = vert_count - 2; + } + else if (prim->mMode == Primitive::Mode::TRIANGLES) + { + triangle_count = vert_count / 3; + } + else + { + LL_WARNS("GLTF") << "Unsupported primitive mode for conversion to triangles: " << (S32)prim->mMode << LL_ENDL; + return false; + } + + vert_count = triangle_count * 3; + llassert(vert_count <= size_t(U32_MAX)); // triangle_count will also naturally be under the limit + + p.resize(vert_count); + n.resize(vert_count); + tc0.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); + } + + bool multi_uv = !prim->mTexCoords1.empty(); + if (multi_uv) + { + tc1.resize(vert_count); + } + + for (U32 tri_idx = 0; tri_idx < U32(triangle_count); ++tri_idx) + { + U32 idx[3]; + + if (prim->mMode == Primitive::Mode::TRIANGLES) + { + idx[0] = tri_idx * 3; + idx[1] = tri_idx * 3 + 1; + idx[2] = tri_idx * 3 + 2; + } + else if (prim->mMode == Primitive::Mode::TRIANGLE_STRIP) + { + idx[0] = tri_idx; + idx[1] = tri_idx + 1; + idx[2] = tri_idx + 2; + + if (tri_idx % 2 != 0) + { + std::swap(idx[1], idx[2]); + } + } + else if (prim->mMode == Primitive::Mode::TRIANGLE_FAN) + { + idx[0] = 0; + idx[1] = tri_idx + 1; + idx[2] = tri_idx + 2; + } + + if (indexed) + { + idx[0] = prim->mIndexArray[idx[0]]; + idx[1] = prim->mIndexArray[idx[1]]; + idx[2] = prim->mIndexArray[idx[2]]; + } + + for (U32 v = 0; v < 3; ++v) + { + U32 i = tri_idx * 3 + v; + p[i].set(prim->mPositions[idx[v]].getF32ptr()); + tc0[i].set(prim->mTexCoords0[idx[v]]); + c[i] = prim->mColors[idx[v]]; + + if (multi_uv) + { + tc1[i].set(prim->mTexCoords1[idx[v]]); + } + + if (has_normals) + { + n[i].set(prim->mNormals[idx[v]].getF32ptr()); + } + + if (rigged) + { + w[i].set(prim->mWeights[idx[v]].getF32ptr()); + j[i] = prim->mJoints[idx[v]]; + } + } + } + + return true; + } + + void genNormals() + { + size_t tri_count = p.size() / 3; + for (size_t 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 + std::vector<meshopt_Stream> mos = + { + { &p[0], sizeof(LLVector3), sizeof(LLVector3) }, + { &n[0], sizeof(LLVector3), sizeof(LLVector3) }, + { &t[0], sizeof(LLVector4), sizeof(LLVector4) }, + { &tc0[0], sizeof(LLVector2), sizeof(LLVector2) }, + { &c[0], sizeof(LLColor4U), sizeof(LLColor4U) } + }; + + if (!w.empty()) + { + mos.push_back({ &w[0], sizeof(LLVector4), sizeof(LLVector4) }); + mos.push_back({ &j[0], sizeof(U64), sizeof(U64) }); + } + + if (!tc1.empty()) + { + mos.push_back({ &tc1[0], sizeof(LLVector2), sizeof(LLVector2) }); + } + + std::vector<U32> remap; + remap.resize(p.size()); + + size_t stream_count = mos.size(); + + size_t vert_count = meshopt_generateVertexRemapMulti(&remap[0], nullptr, p.size(), p.size(), mos.data(), stream_count); + + prim->mTexCoords0.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); + } + if (!tc1.empty()) + { + prim->mTexCoords1.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->mTexCoords0[dst_idx] = tc0[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]; + } + + if (!tc1.empty()) + { + prim->mTexCoords1[dst_idx] = tc1[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 = tc0[face_num * 3 + vert_num].mV; + return mikk::float3(uv[0], 1.f-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); + } +}; + + +static void vertical_flip(std::vector<LLVector2>& texcoords) +{ + for (auto& tc : texcoords) + { + tc[1] = 1.f - tc[1]; + } +} + +bool Primitive::prep(Asset& asset) { // allocate vertex buffer // We diverge from the intent of the GLTF format here to work with our existing render pipeline @@ -66,7 +349,11 @@ void Primitive::allocateGLResources(Asset& asset) } else if (attribName == "TEXCOORD_0") { - copy(asset, accessor, mTexCoords); + copy(asset, accessor, mTexCoords0); + } + else if (attribName == "TEXCOORD_1") + { + copy(asset, accessor, mTexCoords1); } else if (attribName == "JOINTS_0") { @@ -83,96 +370,223 @@ void Primitive::allocateGLResources(Asset& asset) { Accessor& accessor = asset.mAccessors[mIndices]; copy(asset, accessor, mIndexArray); - } - U32 mask = ATTRIBUTE_MASK; - - if (!mWeights.empty()) - { - mask |= LLVertexBuffer::MAP_WEIGHT4; + for (auto& idx : mIndexArray) + { + if (idx >= mPositions.size()) + { + LL_WARNS("GLTF") << "Invalid index array" << LL_ENDL; + return false; + } + } + } + else + { //everything must be indexed at runtime + mIndexArray.resize(mPositions.size()); + for (U32 i = 0; i < mPositions.size(); ++i) + { + mIndexArray[i] = i; + } } - mVertexBuffer = new LLVertexBuffer(mask); - mVertexBuffer->allocateBuffer(mPositions.size(), mIndexArray.size()*2); // double the size of the index buffer for 32-bit indices + U32 mask = LLVertexBuffer::MAP_VERTEX; - mVertexBuffer->setBuffer(); - mVertexBuffer->setPositionData(mPositions.data()); + mShaderVariant = 0; - if (!mIndexArray.empty()) + if (!mWeights.empty()) { - mVertexBuffer->setIndexData(mIndexArray.data()); + mShaderVariant |= LLGLSLShader::GLTFVariant::RIGGED; + mask |= LLVertexBuffer::MAP_WEIGHT4; + mask |= LLVertexBuffer::MAP_JOINT; } - if (mTexCoords.empty()) + if (mTexCoords0.empty()) { - mTexCoords.resize(mPositions.size()); + mTexCoords0.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()); + mask |= LLVertexBuffer::MAP_TEXCOORD0; - for (auto& tc : mTexCoords) + if (!mTexCoords1.empty()) { - tc[1] = 1.f - tc[1]; + mask |= LLVertexBuffer::MAP_TEXCOORD1; } if (mColors.empty()) { mColors.resize(mPositions.size(), LLColor4U::white); } - + + mask |= LLVertexBuffer::MAP_COLOR; + + bool unlit = false; + // bake material basecolor into color array if (mMaterial != INVALID_INDEX) { const Material& material = asset.mMaterials[mMaterial]; - LLColor4 baseColor = material.mMaterial->mBaseColor; + LLColor4 baseColor(glm::value_ptr(material.mPbrMetallicRoughness.mBaseColorFactor)); for (auto& dst : mColors) { dst = LLColor4U(baseColor * LLColor4(dst)); } + + if (material.mUnlit.mPresent) + { // material uses KHR_materials_unlit + mShaderVariant |= LLGLSLShader::GLTFVariant::UNLIT; + unlit = true; + } + + if (material.isMultiUV()) + { + mShaderVariant |= LLGLSLShader::GLTFVariant::MULTI_UV; + } + } + + if (mNormals.empty() && !unlit) + { + 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; + unlit = true; + } + 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); + } } - mVertexBuffer->setColorData(mColors.data()); + 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); + } + } + + if (!mNormals.empty()) + { + mask |= LLVertexBuffer::MAP_NORMAL; + } - if (mNormals.empty()) + if (!mTangents.empty()) { - mNormals.resize(mPositions.size(), LLVector4a(0, 0, 1, 0)); + mask |= LLVertexBuffer::MAP_TANGENT; } - - mVertexBuffer->setNormalData(mNormals.data()); - if (mTangents.empty()) + mAttributeMask = mask; + + if (mMaterial != INVALID_INDEX) { - // TODO: generate tangents if needed - mTangents.resize(mPositions.size(), LLVector4a(1, 0, 0, 1)); + Material& material = asset.mMaterials[mMaterial]; + if (material.mAlphaMode == Material::AlphaMode::BLEND) + { + mShaderVariant |= LLGLSLShader::GLTFVariant::ALPHA_BLEND; + } } - mVertexBuffer->setTangentData(mTangents.data()); + createOctree(); + + return true; +} + +void Primitive::upload(LLVertexBuffer* buffer) +{ + mVertexBuffer = buffer; + // we store these buffer sizes as S32 elsewhere + llassert(mPositions.size() <= size_t(S32_MAX)); + llassert(mIndexArray.size() <= size_t(S32_MAX / 2)); + + llassert(mVertexBuffer != nullptr); + + // assert that buffer can hold this primitive + llassert(mVertexBuffer->getNumVerts() >= mPositions.size() + mVertexOffset); + llassert(mVertexBuffer->getNumIndices() >= mIndexArray.size() + mIndexOffset); + llassert(mVertexBuffer->getTypeMask() == mAttributeMask); + + U32 offset = mVertexOffset; + U32 count = getVertexCount(); + + mVertexBuffer->setPositionData(mPositions.data(), offset, count); + mVertexBuffer->setColorData(mColors.data(), offset, count); + + if (!mNormals.empty()) + { + mVertexBuffer->setNormalData(mNormals.data(), offset, count); + } + if (!mTangents.empty()) + { + mVertexBuffer->setTangentData(mTangents.data(), offset, count); + } if (!mWeights.empty()) { - std::vector<LLVector4a> weight_data; - weight_data.resize(mWeights.size()); + mVertexBuffer->setWeight4Data(mWeights.data(), offset, count); + mVertexBuffer->setJointData(mJoints.data(), offset, count); + } - 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) + vertical_flip(mTexCoords0); + mVertexBuffer->setTexCoord0Data(mTexCoords0.data(), offset, count); + vertical_flip(mTexCoords0); - mVertexBuffer->setWeight4Data(weight_data.data()); + if (!mTexCoords1.empty()) + { + vertical_flip(mTexCoords1); + mVertexBuffer->setTexCoord1Data(mTexCoords1.data(), offset, count); + vertical_flip(mTexCoords1); + } + + if (!mIndexArray.empty()) + { + std::vector<U32> index_array; + index_array.resize(mIndexArray.size()); + for (U32 i = 0; i < mIndexArray.size(); ++i) + { + index_array[i] = mIndexArray[i] + mVertexOffset; + } + mVertexBuffer->setIndexData(index_array.data(), mIndexOffset, getIndexCount()); } - - createOctree(); - - mVertexBuffer->unbind(); } void initOctreeTriangle(LLVolumeTriangle* tri, F32 scaler, S32 i0, S32 i1, S32 i2, const LLVector4a& v0, const LLVector4a& v1, const LLVector4a& v2) @@ -218,9 +632,9 @@ void Primitive::createOctree() F32 scaler = 0.25f; - if (mMode == TINYGLTF_MODE_TRIANGLES) + if (mMode == Mode::TRIANGLES) { - const U32 num_triangles = mVertexBuffer->getNumIndices() / 3; + const U32 num_triangles = getIndexCount() / 3; // Initialize all the triangles we need mOctreeTriangles.resize(num_triangles); @@ -235,16 +649,16 @@ void Primitive::createOctree() const LLVector4a& v0 = mPositions[i0]; const LLVector4a& v1 = mPositions[i1]; const LLVector4a& v2 = mPositions[i2]; - + initOctreeTriangle(tri, scaler, i0, i1, i2, v0, v1, v2); - + //insert mOctree->insert(tri); } } - else if (mMode == TINYGLTF_MODE_TRIANGLE_STRIP) + else if (mMode == Mode::TRIANGLE_STRIP) { - const U32 num_triangles = mVertexBuffer->getNumIndices() - 2; + const U32 num_triangles = getIndexCount() - 2; // Initialize all the triangles we need mOctreeTriangles.resize(num_triangles); @@ -266,9 +680,9 @@ void Primitive::createOctree() mOctree->insert(tri); } } - else if (mMode == TINYGLTF_MODE_TRIANGLE_FAN) + else if (mMode == Mode::TRIANGLE_FAN) { - const U32 num_triangles = mVertexBuffer->getNumIndices() - 2; + const U32 num_triangles = getIndexCount() - 2; // Initialize all the triangles we need mOctreeTriangles.resize(num_triangles); @@ -290,14 +704,14 @@ void Primitive::createOctree() mOctree->insert(tri); } } - else if (mMode == TINYGLTF_MODE_POINTS || - mMode == TINYGLTF_MODE_LINE || - mMode == TINYGLTF_MODE_LINE_LOOP || - mMode == TINYGLTF_MODE_LINE_STRIP) + else if (mMode == Mode::POINTS || + mMode == Mode::LINES || + mMode == Mode::LINE_LOOP || + mMode == Mode::LINE_STRIP) { // nothing to do, no volume... maybe add some collision geometry around these primitive types? } - + else { LL_ERRS() << "Unsupported Primitive mode" << LL_ENDL; @@ -327,13 +741,13 @@ const LLVolumeTriangle* Primitive::lineSegmentIntersect(const LLVector4a& start, //create a proxy LLVolumeFace for the raycast LLVolumeFace face; face.mPositions = mPositions.data(); - face.mTexCoords = mTexCoords.data(); + face.mTexCoords = mTexCoords0.data(); face.mNormals = mNormals.data(); face.mTangents = mTangents.data(); face.mIndices = nullptr; // unreferenced - face.mNumIndices = mIndexArray.size(); - face.mNumVertices = mPositions.size(); + face.mNumIndices = S32(mIndexArray.size()); + face.mNumVertices = S32(mPositions.size()); LLOctreeTriangleRayIntersect intersect(start, dir, &face, &closest_t, intersection, tex_coord, normal, tangent_out); intersect.traverse(mOctree); @@ -351,50 +765,48 @@ Primitive::~Primitive() mOctree = nullptr; } - -const Primitive& Primitive::operator=(const tinygltf::Primitive& src) +LLRender::eGeomModes gltf_mode_to_gl_mode(Primitive::Mode mode) { - // load material - mMaterial = src.material; - - // load mode - mMode = src.mode; - - // load indices - mIndices = src.indices; - - // load attributes - for (auto& it : src.attributes) - { - mAttributes[it.first] = it.second; - } - - switch (mMode) - { - case TINYGLTF_MODE_POINTS: - mGLMode = LLRender::POINTS; - break; - case TINYGLTF_MODE_LINE: - mGLMode = LLRender::LINES; - break; - case TINYGLTF_MODE_LINE_LOOP: - mGLMode = LLRender::LINE_LOOP; - break; - case TINYGLTF_MODE_LINE_STRIP: - mGLMode = LLRender::LINE_STRIP; - break; - case TINYGLTF_MODE_TRIANGLES: - mGLMode = LLRender::TRIANGLES; - break; - case TINYGLTF_MODE_TRIANGLE_STRIP: - mGLMode = LLRender::TRIANGLE_STRIP; - break; - case TINYGLTF_MODE_TRIANGLE_FAN: - mGLMode = LLRender::TRIANGLE_FAN; - break; + switch (mode) + { + case Primitive::Mode::POINTS: + return LLRender::POINTS; + case Primitive::Mode::LINES: + return LLRender::LINES; + case Primitive::Mode::LINE_LOOP: + return LLRender::LINE_LOOP; + case Primitive::Mode::LINE_STRIP: + return LLRender::LINE_STRIP; + case Primitive::Mode::TRIANGLES: + return LLRender::TRIANGLES; + case Primitive::Mode::TRIANGLE_STRIP: + return LLRender::TRIANGLE_STRIP; + case Primitive::Mode::TRIANGLE_FAN: + return LLRender::TRIANGLE_FAN; default: - mGLMode = GL_TRIANGLES; + return LLRender::TRIANGLES; } +} + +void Primitive::serialize(boost::json::object& dst) const +{ + write(mMaterial, "material", dst, -1); + write(mMode, "mode", dst, Primitive::Mode::TRIANGLES); + write(mIndices, "indices", dst, INVALID_INDEX); + write(mAttributes, "attributes", dst); +} +const Primitive& Primitive::operator=(const Value& src) +{ + if (src.is_object()) + { + copy(src, "material", mMaterial); + copy(src, "mode", mMode); + copy(src, "indices", mIndices); + copy(src, "attributes", mAttributes); + + mGLMode = gltf_mode_to_gl_mode(mMode); + } return *this; } + |