Fix the Android filamesh file loader (#709)
* 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
This commit is contained in:
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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())
|
||||
@@ -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) }
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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())
|
||||
@@ -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) }
|
||||
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user