Skip to content

Commit

Permalink
Add tangents utility to fix #532.
Browse files Browse the repository at this point in the history
I tested this by writing a quick-and-dirty Sphere demo.
  • Loading branch information
prideout committed Nov 28, 2018
1 parent 47d1068 commit 91abaf5
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 26 deletions.
55 changes: 53 additions & 2 deletions filament/include/filament/VertexBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class FVertexBuffer;

class Engine;

/**
* Holds a set of buffers that define the geometry of a Renderable.
*/
class UTILS_PUBLIC VertexBuffer : public FilamentAPI {
struct BuilderDetails;

Expand Down Expand Up @@ -88,14 +91,62 @@ class UTILS_PUBLIC VertexBuffer : public FilamentAPI {
};
};

// Return the vertex count
/**
* Returns the vertex count.
*/
size_t getVertexCount() const noexcept;

// noop if bufferIndex >= bufferCount
/**
* Moves the given buffer data into the slot at the given index.
*
* Does nothing if bufferIndex >= bufferCount.
*/
void setBufferAt(Engine& engine, uint8_t bufferIndex,
BufferDescriptor&& buffer,
uint32_t byteOffset = 0,
uint32_t byteSize = 0);

/**
* Specifies the quaternion type for the "populateTangentQuaternions" utility.
*/
enum QuatType {
HALF4, // 2 bytes per component as half-floats (8 bytes per quat)
SHORT4, // 2 bytes per component as normalized integers (8 bytes per quat)
FLOAT4, // 4 bytes per component as floats (16 bytes per quat)
};

/**
* Specifies the parameters for the "populateTangentQuaternions" utility.
*/
struct QuatTangentContext {
QuatType quatType; // required
size_t quatCount; // required
void* outBuffer; // required
size_t outStride; // required stride in bytes
const math::float3* normals; // required source data
size_t normalsStride; // optional stride in bytes (assumes packed)
const math::float4* tangents; // optional source data
size_t tangentsStride; // optional stride in bytes (assumes packed)
};

/**
* Convenience function that consumes normal vectors (and, optionally, tangent vectors) and
* produces quaternions that can be passed into a TANGENTS buffer.
*
* The given output buffer must be preallocated with at least quatCount * outStride bytes.
*
* Normals are required but tangents are optional, in which case this function tries to generate
* reasonable tangents. The given normals should be unit length.
*
* If supplied, the tangent vectors should be unit length and should be orthogonal to the
* normals. The w component of the tangent is a sign (-1 or +1) indicating handedness of the
* basis.
*
* Note that some applications and file formats (e.g. Blender and glTF) use mikktspace, which
* consumes full topology information and produces an unindexed mesh, so it cannot be used here.
* This function exists for simple use cases only.
*/
static void populateTangentQuaternions(const QuatTangentContext& ctx);
};

} // namespace filament
Expand Down
65 changes: 65 additions & 0 deletions filament/src/VertexBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,18 @@

#include "FilamentAPI-impl.h"

#include <math/mat3.h>
#include <math/norm.h>
#include <math/quat.h>
#include <math/vec3.h>
#include <math/vec4.h>

#include <utils/Panic.h>

namespace filament {

using namespace details;
using namespace math;

struct VertexBuffer::BuilderDetails {
VertexBuffer::Builder::AttributeData mAttributes[MAX_ATTRIBUTE_BUFFERS_COUNT];
Expand Down Expand Up @@ -193,4 +200,62 @@ void VertexBuffer::setBufferAt(Engine& engine, uint8_t bufferIndex,
std::move(buffer), byteOffset, byteSize);
}

void VertexBuffer::populateTangentQuaternions(const QuatTangentContext& ctx) {
if (!ASSERT_PRECONDITION_NON_FATAL(ctx.normals, "Normals must be provided")) {
return;
}

// Define a small lambda that converts fp32 into the desired output format.
void (*writeQuat)(math::quatf, uint8_t*);
switch (ctx.quatType) {
case HALF4:
writeQuat = [] (quatf inquat, uint8_t* outquat) {
*((quath*) outquat) = quath(inquat);
};
break;
case SHORT4:
writeQuat = [] (quatf inquat, uint8_t* outquat) {
*((short4*) outquat) = packSnorm16(inquat.xyzw);
};
break;
case FLOAT4:
writeQuat = [] (quatf inquat, uint8_t* outquat) {
*((quatf*) outquat) = inquat;
};
break;
}

const float3* normal = ctx.normals;
const size_t nstride = ctx.normalsStride ? ctx.normalsStride : sizeof(math::float3);
uint8_t* outquat = (uint8_t*) ctx.outBuffer;

// If tangents are not provided, simply cross N with arbitrary vector (1, 0, 0)
if (!ctx.tangents) {
for (size_t qindex = 0, qcount = ctx.quatCount; qindex < qcount; ++qindex) {
float3 n = *normal;
float3 b = normalize(cross(n, float3{1, 0, 0}));
float3 t = cross(n, b);
writeQuat(mat3f::packTangentFrame({t, b, n}), outquat);
normal = (const float3*) (((const uint8_t*) normal) + nstride);
outquat += ctx.outStride;
}
return;
}

const float3* tanvec = &ctx.tangents->xyz;
const float* tandir = &ctx.tangents->w;
const size_t tstride = ctx.tangentsStride ? ctx.tangentsStride : sizeof(math::float4);

for (size_t qindex = 0, qcount = ctx.quatCount; qindex < qcount; ++qindex) {
float3 n = *normal;
float3 t = *tanvec;
float3 b = *tandir > 0 ? cross(t, n) : cross(n, t);
writeQuat(mat3f::packTangentFrame({t, b, n}), outquat);
normal = (const float3*) (((const uint8_t*) normal) + nstride);
tanvec = (const float3*) (((const uint8_t*) tanvec) + tstride);
tandir = (const float*) (((const uint8_t*) tandir) + tstride);
outquat += ctx.outStride;
}
}

} // namespace filament
32 changes: 16 additions & 16 deletions libs/math/include/math/norm.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,67 +25,67 @@

namespace math {

static uint16_t packUnorm16(float v) noexcept {
inline uint16_t packUnorm16(float v) noexcept {
return static_cast<uint16_t>(std::round(clamp(v, 0.0f, 1.0f) * 65535.0f));
}

static ushort4 packUnorm16(float4 v) noexcept {
inline ushort4 packUnorm16(float4 v) noexcept {
return ushort4{packUnorm16(v.x), packUnorm16(v.y), packUnorm16(v.z), packUnorm16(v.w)};
}

static int16_t packSnorm16(float v) noexcept {
inline int16_t packSnorm16(float v) noexcept {
return static_cast<int16_t>(std::round(clamp(v, -1.0f, 1.0f) * 32767.0f));
}

static short4 packSnorm16(float4 v) noexcept {
inline short4 packSnorm16(float4 v) noexcept {
return short4{packSnorm16(v.x), packSnorm16(v.y), packSnorm16(v.z), packSnorm16(v.w)};
}

static float unpackUnorm16(uint16_t v) noexcept {
inline float unpackUnorm16(uint16_t v) noexcept {
return v / 65535.0f;
}

static float4 unpackUnorm16(ushort4 v) noexcept {
inline float4 unpackUnorm16(ushort4 v) noexcept {
return float4{unpackUnorm16(v.x), unpackUnorm16(v.y), unpackUnorm16(v.z), unpackUnorm16(v.w)};
}

static float unpackSnorm16(int16_t v) noexcept {
inline float unpackSnorm16(int16_t v) noexcept {
return clamp(v / 32767.0f, -1.0f, 1.0f);
}

static float4 unpackSnorm16(short4 v) noexcept {
inline float4 unpackSnorm16(short4 v) noexcept {
return float4{unpackSnorm16(v.x), unpackSnorm16(v.y), unpackSnorm16(v.z), unpackSnorm16(v.w)};
}

static uint8_t packUnorm8(float v) noexcept {
inline uint8_t packUnorm8(float v) noexcept {
return static_cast<uint8_t>(std::round(clamp(v, 0.0f, 1.0f) * 255.0));
}

static ubyte4 packUnorm8(float4 v) noexcept {
inline ubyte4 packUnorm8(float4 v) noexcept {
return ubyte4{packUnorm8(v.x), packUnorm8(v.y), packUnorm8(v.z), packUnorm8(v.w)};
}

static int8_t packSnorm8(float v) noexcept {
inline int8_t packSnorm8(float v) noexcept {
return static_cast<int8_t>(std::round(clamp(v, -1.0f, 1.0f) * 127.0));
}

static byte4 packSnorm8(float4 v) noexcept {
inline byte4 packSnorm8(float4 v) noexcept {
return byte4{packSnorm8(v.x), packSnorm8(v.y), packSnorm8(v.z), packSnorm8(v.w)};
}

static float unpackUnorm8(uint8_t v) noexcept {
inline float unpackUnorm8(uint8_t v) noexcept {
return v / 255.0f;
}

static float4 unpackUnorm8(ubyte4 v) noexcept {
inline float4 unpackUnorm8(ubyte4 v) noexcept {
return float4{unpackUnorm8(v.x), unpackUnorm8(v.y), unpackUnorm8(v.z), unpackUnorm8(v.w)};
}

static float unpackSnorm8(int8_t v) noexcept {
inline float unpackSnorm8(int8_t v) noexcept {
return clamp(v / 127.0f, -1.0f, 1.0f);
}

static float4 unpackSnorm8(byte4 v) noexcept {
inline float4 unpackSnorm8(byte4 v) noexcept {
return float4{unpackSnorm8(v.x), unpackSnorm8(v.y), unpackSnorm8(v.z), unpackSnorm8(v.w)};
}

Expand Down
18 changes: 10 additions & 8 deletions samples/app/Sphere.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,16 @@ Sphere::Sphere(Engine& engine, Material const* material, bool culling)
auto const& vertices = geometry->sphere.getVertices();
uint32_t indexCount = (uint32_t)(indices.size() * 3);

for (auto const& vertice : vertices) {
float3 n = vertice;
// todo produce correct u,v
float3 b = cross(n, float3{ 1, 0, 0 });
float3 t = cross(b, n);
quatf q = mat3f::packTangentFrame({ t, b, n });
geometry->tangents.push_back(packSnorm16(q.xyzw));
}
geometry->tangents.resize(vertices.size());
VertexBuffer::populateTangentQuaternions(VertexBuffer::QuatTangentContext {
.quatType = VertexBuffer::SHORT4,
.quatCount = vertices.size(),
.outBuffer = geometry->tangents.data(),
.outStride = sizeof(math::short4),
.normals = vertices.data()
});

// todo produce correct u,v

geometry->vertexBuffer = VertexBuffer::Builder()
.vertexCount((uint32_t)vertices.size())
Expand Down

0 comments on commit 91abaf5

Please sign in to comment.