From 976d8e162f5eea7320125b347f5f0bc3671b25eb Mon Sep 17 00:00:00 2001 From: poweifeng Date: Thu, 19 Jan 2023 13:44:40 -0800 Subject: [PATCH] geometry: Add TangentSpaceMesh class outline - Defined class with documentation in comments - Implemented algorithm selection - No actual algorithms written yet Part of google/filament#6358 --- libs/geometry/CMakeLists.txt | 2 + .../include/geometry/TangentSpaceMesh.h | 189 +++++++++++ libs/geometry/src/TangentSpaceMesh.cpp | 304 ++++++++++++++++++ 3 files changed, 495 insertions(+) create mode 100644 libs/geometry/include/geometry/TangentSpaceMesh.h create mode 100644 libs/geometry/src/TangentSpaceMesh.cpp diff --git a/libs/geometry/CMakeLists.txt b/libs/geometry/CMakeLists.txt index 5c46eb606c09..b8a43801dbd9 100644 --- a/libs/geometry/CMakeLists.txt +++ b/libs/geometry/CMakeLists.txt @@ -9,11 +9,13 @@ set(PUBLIC_HDR_DIR include) # ================================================================================================== set(PUBLIC_HDRS include/geometry/SurfaceOrientation.h + include/geometry/TangentSpaceMesh.h include/geometry/Transcoder.h ) set(SRCS src/SurfaceOrientation.cpp + src/TangentSpaceMesh.cpp src/Transcoder.cpp ) diff --git a/libs/geometry/include/geometry/TangentSpaceMesh.h b/libs/geometry/include/geometry/TangentSpaceMesh.h new file mode 100644 index 000000000000..efca864c8c47 --- /dev/null +++ b/libs/geometry/include/geometry/TangentSpaceMesh.h @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_GEOMETRY_TANGENTSPACEMESH_H +#define TNT_GEOMETRY_TANGENTSPACEMESH_H + +#include +#include +#include +#include + +namespace filament { +namespace geometry { + +struct TangentSpaceMeshInput; +struct TangentSpaceMeshOutput; + +/** + * For building Filament-style TANGENTS buffers given an input mesh. + * Supercedes the implementation in SurfaceOrientation.h + */ +class UTILS_PUBLIC TangentSpaceMesh { +public: + enum class AlgorithmHint : uint8_t { + /* + * Tries to select the best possible algorithm given the input. The corresponding algorithms + * are detailed in the corresponding enums. + * INPUT ALGORITHM + * ----------------------------------------------------------- + * normals FRISVAD + * positions + indices FLAT_SHADING + * normals + tangents SIGN_OF_W + * normals + uvs + positions + indices MIKKTSPACE + */ + DEFAULT = 0, + + /* + * --- mikktspace --- + * Requires: normals + uvs + positions + indices + * Reference: Mikkelsen, M., 2008. Simulation of wrinkled surfaces revisited. + * https://github.com/mmikk/MikkTSpace + * https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview + * Note: Will remesh + */ + MIKKTSPACE = 1, + + /* + * --- Lengyel's method --- + * Requires: normals + uvs + positions + indices + * Reference: Lengyel, E., 2019. Foundations of Game Engine Development: Rendering. Terathon + * Software LLC.. (Chapter 7) + */ + LENGYEL = 2, + + /* + * --- Hughes-Moller method --- + * Requires: normals + * Reference: Möller, T. and Hughes, J.F., 1999. Efficiently building a matrix to rotate one + * vector to another. Journal of graphics tools, 4(4), pp.1-4. + */ + HUGHES_MOLLER =3, + + /* + * --- Frisvad's method --- + * Requires: normals + * Reference: Frisvad, J.R., 2012. Building an orthonormal basis from a 3D unit vector + * without normalization. Journal of Graphics Tools, 16(3), pp.151-159. + * http://people.compute.dtu.dk/jerf/code/hairy/ + */ + FRISVAD = 4, + + /* + * --- Flat Shading --- + * Requires: positions + indices + * Note: Will remesh + */ + FLAT_SHADING = 5, + + /* + * --- Sign of W --- + * Requires: normals + tangents + * Note: The sign of W determines the orientation of the bitangent. + */ + SIGN_OF_W = 6 + }; + + /* + * Computation of the tangent space is intended to be synchronous (worked on the same thread). + * Client is expected to keep the input immutable and in a good state for the duration of both + * computation *and* query. That is, when querying the result of the tangent spaces, part of the + * result might depend on the input data. + */ + class Builder { + public: + Builder() noexcept; + ~Builder() noexcept; + Builder(Builder&& that) noexcept; + Builder& operator=(Builder&& that) noexcept; + + Builder& vertexCount(size_t vertexCount) noexcept; + + Builder& normals(const filament::math::float3*, size_t stride = 0) noexcept; + Builder& tangents(const filament::math::float4*, size_t stride = 0) noexcept; + Builder& uvs(const filament::math::float2*, size_t stride = 0) noexcept; + Builder& positions(const filament::math::float3*, size_t stride = 0) noexcept; + + Builder& triangleCount(size_t triangleCount) noexcept; + Builder& triangles(const filament::math::uint3*) noexcept; + Builder& triangles(const filament::math::ushort3*) noexcept; + + Builder& algorithmHint(AlgorithmHint) noexcept; + + TangentSpaceMesh* build(); + + private: + Builder(const Builder&) = delete; + Builder& operator=(const Builder&) = delete; + + TangentSpaceMesh* mMesh; + }; + + ~TangentSpaceMesh() noexcept; + TangentSpaceMesh(TangentSpaceMesh&& that) noexcept; + TangentSpaceMesh& operator=(TangentSpaceMesh&& that) noexcept; + + size_t getVertexCount() const noexcept; + + /* + * The following methods for retrieving vertex-correlated data assumes that the *out* param + * provided by the client is at least of *getVertexCount()* length (accounting for stride). + * + * If the chosen algorithm did not remesh the input, the client is advised to just use the + * data they provided instead of querying. For example, if the chosen method is FRISVAD, then + * the client should not need to call "getPositions". We will simply copy from the input + * *positions* in that case. + * + * If the client calls getPositions and positions were not provided as input, we will throw + * and exception. Similar behavior will apply to UVs. + */ + void getPositions(filament::math::float3* out, size_t stride = 0) const; + void getUVs(filament::math::float2* out, size_t stride = 0) const; + void getQuats(filament::math::quatf* out, size_t stride = 0) const noexcept; + void getQuats(filament::math::short4* out, size_t stride = 0) const noexcept; + void getQuats(filament::math::quath* out, size_t stride = 0) const noexcept; + + size_t getTriangleCount() const; + + /* + * The following methods for retrieving triangles assumes that the *out* param provided by the + * client is at least of *getTriangleCount()* length. + * + * If the chosen algorithm did not remesh the input, the client is advised to just use the + * indices they provided instead of querying. For example, if the chosen method is FRISVAD, + * then calling *getTriangles()* will be simply copying from the input *triangles*. + * + * If the client calls getTriangles() and triangles were not provided as input, we will throw + * and exception. + */ + void getTriangles(filament::math::uint3* out) const; + void getTriangles(filament::math::ushort3* out) const; + +private: + TangentSpaceMesh() noexcept; + TangentSpaceMesh(const TangentSpaceMesh&) = delete; + TangentSpaceMesh& operator=(const TangentSpaceMesh&) = delete; + TangentSpaceMeshInput* mInput; + TangentSpaceMeshOutput* mOutput; + + friend class Builder; +}; + +} // namespace geometry +} // namespace filament + + +#endif //TNT_GEOMETRY_TANGENTSPACEMESH_H diff --git a/libs/geometry/src/TangentSpaceMesh.cpp b/libs/geometry/src/TangentSpaceMesh.cpp new file mode 100644 index 000000000000..f914115a7532 --- /dev/null +++ b/libs/geometry/src/TangentSpaceMesh.cpp @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +namespace filament { +namespace geometry { + +using namespace filament::math; +using Builder = TangentSpaceMesh::Builder; +using AlgorithmHint = TangentSpaceMesh::AlgorithmHint; + +struct TangentSpaceMeshInput { + size_t vertexCount = 0; + const float3* normals = nullptr; + const float4* tangents = nullptr; + const float2* uvs = nullptr; + const float3* positions = nullptr; + const ushort3* triangles16 = nullptr; + const uint3* triangles32 = nullptr; + + size_t normalStride = 0; + size_t tangentStride = 0; + size_t uvStride = 0; + size_t positionStride = 0; + size_t triangleCount = 0; + + TangentSpaceMesh::AlgorithmHint hint; +}; + +struct TangentSpaceMeshOutput { + TangentSpaceMesh::AlgorithmHint hint; + + size_t triangleCount = 0; + size_t vertexCount = 0; + + float4* tangents = nullptr; + float2* uvs = nullptr; + float3* positions = nullptr; + uint3* triangles32 = nullptr; + ushort3* triangles16 = nullptr; +}; + +namespace { + +const uint8_t NORMALS_BIT = 0x01; +const uint8_t TANGENTS_BIT = 0x02; +const uint8_t UVS_BIT = 0x04; +const uint8_t POSITIONS_BIT = 0x08; +const uint8_t INDICES_BIT = 0x10; + +// Input types +const uint8_t NORMALS = NORMALS_BIT; +const uint8_t NORMALS_TANGENTS = NORMALS_BIT | TANGENTS_BIT; +const uint8_t POSITIONS_INDICES = POSITIONS_BIT | INDICES_BIT; +const uint8_t NORMALS_UVS_POSITIONS_INDICES = NORMALS_BIT | UVS_BIT | POSITIONS_BIT | INDICES_BIT; + +const char* HintToString(AlgorithmHint hint) { + switch (hint) { + case AlgorithmHint::DEFAULT: + return "DEFAULT"; + case AlgorithmHint::MIKKTSPACE: + return "MIKKTSPACE"; + case AlgorithmHint::LENGYEL: + return "LENGYEL"; + case AlgorithmHint::HUGHES_MOLLER: + return "HUGHES_MOLLER"; + case AlgorithmHint::FRISVAD: + return "FRISVAD"; + case AlgorithmHint::FLAT_SHADING: + return "FLAT_SHADING"; + case AlgorithmHint::SIGN_OF_W: + return "SIGN_OF_W"; + default: + PANIC_POSTCONDITION("Unknown hint %u", static_cast(hint)); + } +} + +AlgorithmHint selectBestDefaultAlgorithm(uint8_t inputType) { + AlgorithmHint outHint; + if (inputType & NORMALS_UVS_POSITIONS_INDICES) { + outHint = AlgorithmHint::MIKKTSPACE; + } else if (inputType & POSITIONS_INDICES) { + outHint = AlgorithmHint::FLAT_SHADING; + } else if (inputType & NORMALS_TANGENTS) { + outHint = AlgorithmHint::SIGN_OF_W; + } else { + ASSERT_PRECONDITION(inputType & NORMALS, + "Must at least have normals or (positions + indices) as input"); + outHint = AlgorithmHint::FRISVAD; + } + return outHint; +} + +AlgorithmHint selectAlgorithm(TangentSpaceMeshInput *input) { + uint8_t inputType = 0; + if (input->normals) { + inputType |= NORMALS_BIT; + } + if (input->tangents) { + inputType |= TANGENTS_BIT; + } + if (input->positions) { + inputType |= POSITIONS_BIT; + } + if (input->uvs) { + inputType |= UVS_BIT; + } + if (input->triangles32 || input->triangles16) { + inputType |= INDICES_BIT; + } + + bool foundHint = false; + AlgorithmHint outHint = AlgorithmHint::DEFAULT; + switch (input->hint) { + case AlgorithmHint::DEFAULT: + outHint = selectBestDefaultAlgorithm(inputType); + foundHint = true; + break; + case AlgorithmHint::MIKKTSPACE: + if (inputType & NORMALS_UVS_POSITIONS_INDICES) { + outHint = AlgorithmHint::MIKKTSPACE; + foundHint = true; + } + break; + case AlgorithmHint::LENGYEL: + if (inputType & NORMALS_UVS_POSITIONS_INDICES) { + outHint = AlgorithmHint::LENGYEL; + foundHint = true; + } + break; + case AlgorithmHint::HUGHES_MOLLER: + if (inputType & NORMALS) { + outHint = AlgorithmHint::HUGHES_MOLLER; + foundHint = true; + } + break; + case AlgorithmHint::FRISVAD: + if (inputType & NORMALS) { + outHint = AlgorithmHint::FRISVAD; + foundHint = true; + } + break; + case AlgorithmHint::FLAT_SHADING: + if (inputType & POSITIONS_INDICES) { + outHint = AlgorithmHint::FLAT_SHADING; + foundHint = true; + } + break; + case AlgorithmHint::SIGN_OF_W: + if (inputType & NORMALS_TANGENTS) { + outHint = AlgorithmHint::SIGN_OF_W; + foundHint = true; + } + break; + default: + PANIC_POSTCONDITION("Unknown hint %u", static_cast(input->hint)); + } + + if (!foundHint) { + outHint = selectBestDefaultAlgorithm(inputType); + utils::slog.w << "Cannot satisfy hint=" << HintToString(input->hint) + << ". Selected hint=" << HintToString(input->hint) << " instead" + << utils::io::endl; + } + + return outHint; +} + +} // anonymous namespace + +Builder::Builder() noexcept + :mMesh(new TangentSpaceMesh()) {} + +Builder::~Builder() noexcept { delete mMesh; } + +Builder::Builder(Builder&& that) noexcept { + std::swap(mMesh, that.mMesh); +} + +Builder& Builder::operator=(Builder&& that) noexcept { + std::swap(mMesh, that.mMesh); + return *this; +} + +Builder& Builder::normals(const float3* normals, size_t stride) noexcept { + mMesh->mInput->normals = normals; + mMesh->mInput->normalStride = stride; + return *this; +} + +Builder& Builder::tangents(const float4* tangents, size_t stride) noexcept { + mMesh->mInput->tangents = tangents; + mMesh->mInput->tangentStride = stride; + return *this; +} + +Builder& Builder::uvs(const float2* uvs, size_t stride) noexcept { + mMesh->mInput->uvs = uvs; + mMesh->mInput->uvStride = stride; + return *this; +} + +Builder& Builder::positions(const float3* positions, size_t stride) noexcept { + mMesh->mInput->positions = positions; + mMesh->mInput->positionStride = stride; + return *this; +} + +Builder& Builder::triangleCount(size_t triangleCount) noexcept { + mMesh->mInput->triangleCount = triangleCount; + return *this; +} + +Builder& Builder::triangles(const uint3* triangle32) noexcept { + mMesh->mInput->triangles32 = triangle32; + return *this; +} + +Builder& Builder::triangles(const ushort3* triangle16) noexcept { + mMesh->mInput->triangles16 = triangle16; + return *this; +} + +Builder& Builder::algorithmHint(AlgorithmHint hint) noexcept { + mMesh->mInput->hint = hint; + return *this; +} + +TangentSpaceMesh* Builder::build() { + mMesh->mOutput->hint = selectAlgorithm(mMesh->mInput); + PANIC_POSTCONDITION("Working in progress. Not for use"); +} + +TangentSpaceMesh::TangentSpaceMesh() noexcept + :mInput(new TangentSpaceMeshInput()), mOutput(new TangentSpaceMeshOutput()) { +} + +TangentSpaceMesh::~TangentSpaceMesh() noexcept {} + +TangentSpaceMesh::TangentSpaceMesh(TangentSpaceMesh&& that) noexcept { + std::swap(mInput, that.mInput); + std::swap(mOutput, that.mOutput); +} + +TangentSpaceMesh& TangentSpaceMesh::operator=(TangentSpaceMesh&& that) noexcept { + std::swap(mInput, that.mInput); + std::swap(mOutput, that.mOutput); + return *this; +} + +size_t TangentSpaceMesh::getVertexCount() const noexcept { + return 0; +} + +void TangentSpaceMesh::getPositions(float3*, size_t) const { + ASSERT_PRECONDITION(mInput->positions, "Must provide input positions"); +} + +void TangentSpaceMesh::getUVs(float2*, size_t) const { + ASSERT_PRECONDITION(mInput->uvs, "Must provide input UVs"); +} + +size_t TangentSpaceMesh::getTriangleCount() const { + return 0; +} + +void TangentSpaceMesh::getTriangles(uint3*) const { + ASSERT_PRECONDITION(mInput->triangles16 || mInput->triangles32, "Must provide input triangles"); +} + +void TangentSpaceMesh::getTriangles(ushort3*) const { + ASSERT_PRECONDITION(mInput->triangles16 || mInput->triangles32, "Must provide input triangles"); +} + +void TangentSpaceMesh::getQuats(quatf*, size_t) const noexcept { +} + +void TangentSpaceMesh::getQuats(short4*, size_t) const noexcept { +} + +void TangentSpaceMesh::getQuats(quath*, size_t) const noexcept { +} + +} +} \ No newline at end of file