summaryrefslogtreecommitdiff
path: root/indra/newview/gltf
diff options
context:
space:
mode:
authorDave Parks <davep@lindenlab.com>2024-06-10 10:43:38 -0500
committerGitHub <noreply@github.com>2024-06-10 10:43:38 -0500
commit227e9be06832515fd10eb496d4a2a4528d1ebd92 (patch)
treedfe366ee3ea6c4fe4b08dc080fd07313e279a7d5 /indra/newview/gltf
parent33ddedd6b557ed9130dd8cd3b8327a697614a3ac (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')
-rw-r--r--indra/newview/gltf/buffer_util.h10
-rw-r--r--indra/newview/gltf/primitive.cpp352
-rw-r--r--indra/newview/gltf/primitive.h15
3 files changed, 323 insertions, 54 deletions
diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h
index 548d4ec5d6..bd43f60d19 100644
--- a/indra/newview/gltf/buffer_util.h
+++ b/indra/newview/gltf/buffer_util.h
@@ -171,6 +171,16 @@ namespace LL
}
template<>
+ inline void copyVec4<U16, U64>(U16* src, U64& dst)
+ {
+ U16* data = (U16*)&dst;
+ data[0] = src[0];
+ data[1] = src[1];
+ data[2] = src[2];
+ data[3] = src[3];
+ }
+
+ template<>
inline void copyVec4<U16, LLColor4U>(U16* src, LLColor4U& dst)
{
dst.set(src[0], src[1], src[2], src[3]);
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;
}
diff --git a/indra/newview/gltf/primitive.h b/indra/newview/gltf/primitive.h
index 9914ff3b08..f9d7c63c65 100644
--- a/indra/newview/gltf/primitive.h
+++ b/indra/newview/gltf/primitive.h
@@ -38,13 +38,6 @@ namespace LL
using Value = boost::json::value;
class Asset;
- constexpr U32 ATTRIBUTE_MASK =
- LLVertexBuffer::MAP_VERTEX |
- LLVertexBuffer::MAP_NORMAL |
- LLVertexBuffer::MAP_TEXCOORD0 |
- LLVertexBuffer::MAP_TANGENT |
- LLVertexBuffer::MAP_COLOR;
-
class Primitive
{
public:
@@ -64,12 +57,12 @@ namespace LL
// GPU copy of mesh data
LLPointer<LLVertexBuffer> mVertexBuffer;
- // CPU copy of mesh data
+ // CPU copy of mesh data, keep these as LLVector types for compatibility with raycasting code
std::vector<LLVector2> mTexCoords;
std::vector<LLVector4a> mNormals;
std::vector<LLVector4a> mTangents;
std::vector<LLVector4a> mPositions;
- std::vector<LLVector4a> mJoints;
+ std::vector<U64> mJoints;
std::vector<LLVector4a> mWeights;
std::vector<LLColor4U> mColors;
std::vector<U32> mIndexArray;
@@ -82,6 +75,10 @@ namespace LL
Mode mMode = Mode::TRIANGLES; // default to triangles
LLRender::eGeomModes mGLMode = LLRender::TRIANGLES; // for use with LLRender
S32 mIndices = -1;
+
+ // shader variant according to LLGLSLShader::GLTFVariant flags
+ U8 mShaderVariant = 0;
+
std::unordered_map<std::string, S32> mAttributes;
// create octree based on vertex buffer