Skip to content

Commit

Permalink
Fix the Android filamesh file loader (#709)
Browse files Browse the repository at this point in the history
* Fix the Android filamesh file loader

The loader was not updated to support the SNORM16 format now sometimes
used to encode UV sets in filamesh files.

Fixes #708

* Update android/samples/image-based-lighting/app/src/main/java/com/google/android/filament/ibl/MeshLoader.kt
  • Loading branch information
romainguy committed Jan 16, 2019
1 parent b3cd6a7 commit cebc83e
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 15 deletions.
Expand Up @@ -69,12 +69,16 @@ fun loadMesh(assets: AssetManager, name: String,
private const val FILAMESH_FILE_IDENTIFIER = "FILAMESH"
private const val MAX_UINT32 = 4294967295

private const val HEADER_FLAG_INTERLEAVED = 0x1L
private const val HEADER_FLAG_SNORM16_UV = 0x2L
private const val HEADER_FLAG_COMPRESSED = 0x4L

private class Header {
var valid = false
var versionNumber = 0L
var parts = 0L
var aabb = Box()
var interleaved = 0L
var flags = 0L
var posOffset = 0L
var positionStride = 0L
var tangentOffset = 0L
Expand Down Expand Up @@ -121,7 +125,7 @@ private fun readHeader(input: InputStream): Header {
header.aabb = Box(
readFloat32LE(input), readFloat32LE(input), readFloat32LE(input),
readFloat32LE(input), readFloat32LE(input), readFloat32LE(input))
header.interleaved = readUIntLE(input)
header.flags = readUIntLE(input)
header.posOffset = readUIntLE(input)
header.positionStride = readUIntLE(input)
header.tangentOffset = readUIntLE(input)
Expand All @@ -142,7 +146,6 @@ private fun readHeader(input: InputStream): Header {
return header
}


private fun readSizedData(channel: ReadableByteChannel, sizeInBytes: Long): ByteBuffer {
val buffer = ByteBuffer.allocateDirect(sizeInBytes.toInt())
buffer.order(ByteOrder.LITTLE_ENDIAN)
Expand Down Expand Up @@ -190,7 +193,15 @@ private fun createIndexBuffer(engine: Engine, header: Header, data: ByteBuffer):
.apply { setBuffer(engine, data) }
}

private fun uvNormalized(header: Header) = header.flags and HEADER_FLAG_SNORM16_UV != 0L

private fun createVertexBuffer(engine: Engine, header: Header, data: ByteBuffer): VertexBuffer {
val uvType = if (!uvNormalized(header)) {
HALF2
} else {
SHORT2
}

val vertexBufferBuilder = VertexBuffer.Builder()
.bufferCount(1)
.vertexCount(header.totalVertices.toInt())
Expand All @@ -203,11 +214,23 @@ private fun createVertexBuffer(engine: Engine, header: Header, data: ByteBuffer)
.attribute(POSITION, 0, HALF4, header.posOffset.toInt(), header.positionStride.toInt())
.attribute(TANGENTS, 0, SHORT4, header.tangentOffset.toInt(), header.tangentStride.toInt())
.attribute(COLOR, 0, UBYTE4, header.colorOffset.toInt(), header.colorStride.toInt())
.attribute(UV0, 0, HALF2, header.uv0Offset.toInt(), header.uv0Stride.toInt())
// UV coordinates are stored as normalized 16-bit integers or half-floats depending on
// the range they span. When stored as half-float, there is only enough precision for
// sub-pixel addressing in textures that are <= 1024x1024
.attribute(UV0, 0, uvType, header.uv0Offset.toInt(), header.uv0Stride.toInt())

// When UV coordinates are stored as 16-bit integers we must normalize them (we want values
// in the range -1..1)
if (uvNormalized(header)) {
vertexBufferBuilder.normalized(UV0)
}

if (header.uv1Offset != MAX_UINT32 && header.uv1Stride != MAX_UINT32) {
vertexBufferBuilder
.attribute(UV1, 0, HALF2, header.uv1Offset.toInt(), header.uv1Stride.toInt())
.attribute(UV1, 0, uvType, header.uv1Offset.toInt(), header.uv1Stride.toInt())
if (uvNormalized(header)) {
vertexBufferBuilder.normalized(UV1)
}
}

return vertexBufferBuilder.build(engine).apply { setBufferAt(engine, 0, data) }
Expand All @@ -224,7 +247,7 @@ private fun createRenderable(

val builder = RenderableManager.Builder(header.parts.toInt()).boundingBox(header.aabb)

(0 until header.parts.toInt()).forEach { i ->
repeat(header.parts.toInt()) { i ->
builder.geometry(i,
RenderableManager.PrimitiveType.TRIANGLES,
vertexBuffer,
Expand Down
Expand Up @@ -69,12 +69,16 @@ fun loadMesh(assets: AssetManager, name: String,
private const val FILAMESH_FILE_IDENTIFIER = "FILAMESH"
private const val MAX_UINT32 = 4294967295

private const val HEADER_FLAG_INTERLEAVED = 0x1L
private const val HEADER_FLAG_SNORM16_UV = 0x2L
private const val HEADER_FLAG_COMPRESSED = 0x4L

private class Header {
var valid = false
var versionNumber = 0L
var parts = 0L
var aabb = Box()
var interleaved = 0L
var flags = 0L
var posOffset = 0L
var positionStride = 0L
var tangentOffset = 0L
Expand Down Expand Up @@ -121,7 +125,7 @@ private fun readHeader(input: InputStream): Header {
header.aabb = Box(
readFloat32LE(input), readFloat32LE(input), readFloat32LE(input),
readFloat32LE(input), readFloat32LE(input), readFloat32LE(input))
header.interleaved = readUIntLE(input)
header.flags = readUIntLE(input)
header.posOffset = readUIntLE(input)
header.positionStride = readUIntLE(input)
header.tangentOffset = readUIntLE(input)
Expand All @@ -142,7 +146,6 @@ private fun readHeader(input: InputStream): Header {
return header
}


private fun readSizedData(channel: ReadableByteChannel, sizeInBytes: Long): ByteBuffer {
val buffer = ByteBuffer.allocateDirect(sizeInBytes.toInt())
buffer.order(ByteOrder.LITTLE_ENDIAN)
Expand Down Expand Up @@ -190,7 +193,15 @@ private fun createIndexBuffer(engine: Engine, header: Header, data: ByteBuffer):
.apply { setBuffer(engine, data) }
}

private fun uvNormalized(header: Header) = header.flags and HEADER_FLAG_SNORM16_UV != 0L

private fun createVertexBuffer(engine: Engine, header: Header, data: ByteBuffer): VertexBuffer {
val uvType = if (!uvNormalized(header)) {
HALF2
} else {
SHORT2
}

val vertexBufferBuilder = VertexBuffer.Builder()
.bufferCount(1)
.vertexCount(header.totalVertices.toInt())
Expand All @@ -203,13 +214,23 @@ private fun createVertexBuffer(engine: Engine, header: Header, data: ByteBuffer)
.attribute(POSITION, 0, HALF4, header.posOffset.toInt(), header.positionStride.toInt())
.attribute(TANGENTS, 0, SHORT4, header.tangentOffset.toInt(), header.tangentStride.toInt())
.attribute(COLOR, 0, UBYTE4, header.colorOffset.toInt(), header.colorStride.toInt())
// UV coordinates are stored in fp16, which gives sub-pixel precision only
// for textures up to 1024x1024
.attribute(UV0, 0, HALF2, header.uv0Offset.toInt(), header.uv0Stride.toInt())
// UV coordinates are stored as normalized 16-bit integers or half-floats depending on
// the range they span. When stored as half-float, there is only enough precision for
// sub-pixel addressing in textures that are <= 1024x1024
.attribute(UV0, 0, uvType, header.uv0Offset.toInt(), header.uv0Stride.toInt())

// When UV coordinates are stored as 16-bit integeres we must normalize them (we want values
// in the range -1..1)
if (uvNormalized(header)) {
vertexBufferBuilder.normalized(UV0)
}

if (header.uv1Offset != MAX_UINT32 && header.uv1Stride != MAX_UINT32) {
vertexBufferBuilder
.attribute(UV1, 0, HALF2, header.uv1Offset.toInt(), header.uv1Stride.toInt())
.attribute(UV1, 0, uvType, header.uv1Offset.toInt(), header.uv1Stride.toInt())
if (uvNormalized(header)) {
vertexBufferBuilder.normalized(UV1)
}
}

return vertexBufferBuilder.build(engine).apply { setBufferAt(engine, 0, data) }
Expand Down
19 changes: 17 additions & 2 deletions tools/filamesh/README.md
Expand Up @@ -198,6 +198,14 @@ Mesh loadMeshFromFile(filament::Engine* engine, const utils::Path& path,
mesh.indexBuffer->setBuffer(*engine,
IndexBuffer::BufferDescriptor(indices, header->indexSize));

const uint32_t FLAG_SNORM16_UV = 0x2;

VertexBuffer::AttributeType::HALF2 uvType = VertexBuffer::AttributeType::HALF2;
if (header->flags & FLAG_SNORM16_UV) {
uvType = VertexBuffer::AttributeType::SHORT2;
}
bool uvNormalized = header->flags & FLAG_SNORM16_UV;

VertexBuffer::Builder vbb;
vbb.vertexCount(header->vertexCount)
.bufferCount(1)
Expand All @@ -209,13 +217,20 @@ Mesh loadMeshFromFile(filament::Engine* engine, const utils::Path& path,
header->offsetTangents, uint8_t(header->strideTangents))
.attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4,
header->offsetColor, uint8_t(header->strideColor))
.attribute(VertexAttribute::UV0, 0, VertexBuffer::AttributeType::HALF2,
.attribute(VertexAttribute::UV0, 0, uvType,
header->offsetUV0, uint8_t(header->strideUV0));

if (uvNormalized) {
vbb.normalized(VertexAttribute::UV0);
}

if (header->offsetUV1 != std::numeric_limits<uint32_t>::max() &&
header->strideUV1 != std::numeric_limits<uint32_t>::max()) {
vbb.attribute(VertexAttribute::UV1, 0, VertexBuffer::AttributeType::HALF2,
vbb.attribute(VertexAttribute::UV1, 0, uvType,
header->offsetUV1, uint8_t(header->strideUV1));
if (uvNormalized) {
vbb.normalized(VertexAttribute::UV1);
}
}

mesh.vertexBuffer = vbb.build(*engine);
Expand Down

0 comments on commit cebc83e

Please sign in to comment.