465 lines
20 KiB
HTML
465 lines
20 KiB
HTML
<!DOCTYPE HTML>
|
|
<html lang="en" class="light sidebar-visible" dir="ltr">
|
|
<head>
|
|
<!-- Book generated using mdBook -->
|
|
<meta charset="UTF-8">
|
|
<title>filamesh - Filament</title>
|
|
|
|
|
|
<!-- Custom HTML head -->
|
|
|
|
<meta name="description" content="">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="theme-color" content="#ffffff">
|
|
|
|
<link rel="shortcut icon" href="../favicon.png">
|
|
<link rel="stylesheet" href="../css/variables.css">
|
|
<link rel="stylesheet" href="../css/general.css">
|
|
<link rel="stylesheet" href="../css/chrome.css">
|
|
|
|
<!-- Fonts -->
|
|
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
|
<link rel="stylesheet" href="../fonts/fonts.css">
|
|
|
|
<!-- Highlight.js Stylesheets -->
|
|
<link rel="stylesheet" href="../highlight.css">
|
|
<link rel="stylesheet" href="../tomorrow-night.css">
|
|
<link rel="stylesheet" href="../ayu-highlight.css">
|
|
|
|
<!-- Custom theme stylesheets -->
|
|
|
|
<!-- MathJax -->
|
|
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
|
|
|
<!-- Provide site root to javascript -->
|
|
<script>
|
|
var path_to_root = "../";
|
|
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
|
</script>
|
|
<!-- Start loading toc.js asap -->
|
|
<script src="../toc.js"></script>
|
|
</head>
|
|
<body>
|
|
<div id="body-container">
|
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
|
<script>
|
|
try {
|
|
var theme = localStorage.getItem('mdbook-theme');
|
|
var sidebar = localStorage.getItem('mdbook-sidebar');
|
|
|
|
if (theme.startsWith('"') && theme.endsWith('"')) {
|
|
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
|
}
|
|
|
|
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
|
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
|
}
|
|
} catch (e) { }
|
|
</script>
|
|
|
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
|
<script>
|
|
var theme;
|
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|
if (theme === null || theme === undefined) { theme = default_theme; }
|
|
const html = document.documentElement;
|
|
html.classList.remove('light')
|
|
html.classList.add(theme);
|
|
html.classList.add("js");
|
|
</script>
|
|
|
|
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
|
|
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|
<script>
|
|
var sidebar = null;
|
|
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
|
if (document.body.clientWidth >= 1080) {
|
|
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
|
sidebar = sidebar || 'visible';
|
|
} else {
|
|
sidebar = 'hidden';
|
|
}
|
|
sidebar_toggle.checked = sidebar === 'visible';
|
|
html.classList.remove('sidebar-visible');
|
|
html.classList.add("sidebar-" + sidebar);
|
|
</script>
|
|
|
|
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
|
<div style="display:flex;align-items:center;justify-content:center">
|
|
<img class="flogo" src="../images/filament_logo_small.png"></img>
|
|
</div>
|
|
<!-- populated by js -->
|
|
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
|
<noscript>
|
|
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
|
|
</noscript>
|
|
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
|
<div class="sidebar-resize-indicator"></div>
|
|
</div>
|
|
</nav>
|
|
|
|
<div id="page-wrapper" class="page-wrapper">
|
|
|
|
<div class="page">
|
|
<div id="menu-bar-hover-placeholder"></div>
|
|
<div id="menu-bar" class="menu-bar sticky">
|
|
<div class="left-buttons">
|
|
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
|
<i class="fa fa-bars"></i>
|
|
</label>
|
|
<!-- Filament: disable themes because the markdeep part does not look good for dark themes -->
|
|
<!--
|
|
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
|
<i class="fa fa-paint-brush"></i>
|
|
</button>
|
|
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
|
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
|
</ul>
|
|
-->
|
|
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
|
<i class="fa fa-search"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<h1 class="menu-title">Filament</h1>
|
|
|
|
<div class="right-buttons">
|
|
<a href="https://github.com/google/filament" title="Git repository" aria-label="Git repository">
|
|
<i id="git-repository-button" class="fa fa-github"></i>
|
|
</a>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div id="search-wrapper" class="hidden">
|
|
<form id="searchbar-outer" class="searchbar-outer">
|
|
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
|
</form>
|
|
<div id="searchresults-outer" class="searchresults-outer hidden">
|
|
<div id="searchresults-header" class="searchresults-header"></div>
|
|
<ul id="searchresults">
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
|
<script>
|
|
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
|
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
|
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
|
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
|
});
|
|
</script>
|
|
|
|
<div id="content" class="content">
|
|
<main>
|
|
<h1 id="filamesh"><a class="header" href="#filamesh">Filamesh</a></h1>
|
|
<p><code>filamesh</code> converts any mesh file supported by <code>assimp</code> (as configured in this source tree) into a
|
|
custom binary file format. The goal of this binary file format is to allow test applications to
|
|
easily and quickly load meshes.</p>
|
|
<p>The source mesh must have at least one set of UV coordinates.</p>
|
|
<p>The destination mesh will contain vertex positions, one set of UV coordinates and per-vertex
|
|
tangents, bitangents and normals.</p>
|
|
<p>The destination mesh is made of a single vertex buffer and a single index buffer. Mesh parts are
|
|
identified by an offset and count in the index buffer. Each part can have its own material.</p>
|
|
<h2 id="usage"><a class="header" href="#usage">Usage</a></h2>
|
|
<pre><code class="language-shell">filamesh source_mesh destination_mesh
|
|
</code></pre>
|
|
<h2 id="format"><a class="header" href="#format">Format</a></h2>
|
|
<p>Note: the UV1 attribute cannot be used in interleaved mode</p>
|
|
<blockquote>
|
|
<p><strong>Note</strong>
|
|
If you use the hex editor for macOS called <a href="https://hexfiend.com/">Hex Fiend</a>, you can use the
|
|
template found in <code>ide/hexfiend/Templates</code> to inspect filamesh files.</p>
|
|
</blockquote>
|
|
<h3 id="header"><a class="header" href="#header">Header</a></h3>
|
|
<pre><code>char[8] : magic identifier "FILAMESH"
|
|
uint32 : version number
|
|
uint32 : number of parts (sub-meshes or draw calls)
|
|
float3 : center of the total bounding box (AABB)
|
|
float3 : half extent of the total bounding box (AABB)
|
|
uint32 : flags (see below)
|
|
uint32 : offset of the position attribute
|
|
uint32 : stride of the position attribute
|
|
uint32 : offset of the tangents attribute
|
|
uint32 : stride of the tangents attribute
|
|
uint32 : offset of the color attribute
|
|
uint32 : stride of the color attribute
|
|
uint32 : offset of the UV0 attribute
|
|
uint32 : stride of the UV0 attribute
|
|
uint32 : offset of the UV1 attribute (0xffffffff if UV1 is not present)
|
|
uint32 : stride of the UV1 attribute (0xffffffff if UV1 is not present)
|
|
uint32 : total number of vertices
|
|
uint32 : size in bytes occupied by the (compressed) vertices
|
|
uint32 : 0 if indices are stored as uint32, 1 if stored as uint16
|
|
uint32 : total number of indices
|
|
uint32 : size in bytes occupied by the (compressed) indices
|
|
</code></pre>
|
|
<p>The <code>flags</code> field contains the following bits:</p>
|
|
<ul>
|
|
<li>Bit 0: Specifies that vertex attributes are interleaved.</li>
|
|
<li>Bit 1: UV's are 16-bit integers normalized into [-1, +1] rather than half-floats.</li>
|
|
<li>Bit 2: Vertex and index data are compressed using zeux/meshoptimizer.</li>
|
|
</ul>
|
|
<h3 id="vertex-data"><a class="header" href="#vertex-data">Vertex data</a></h3>
|
|
<pre><code>char* : non-interleaved:
|
|
with n = number of vertices
|
|
n * half4: XYZ positions, W set to 1.0
|
|
n * short4: tangent, bitangent and normal as a quaternion (snorm unsigned short)
|
|
n * ubyte4: color
|
|
n * half2: UV texture coordinates
|
|
n * half2: UV texture coordinates (if UV1 offset and stride != 0xffffffff)
|
|
interleaved:
|
|
for each vertex:
|
|
half4: XYZ position, W set to 1.0
|
|
short4: tangent, bitangent and normal as a quaternion (snorm unsigned short)
|
|
ubyte4: color
|
|
half2: UV texture coordinates
|
|
</code></pre>
|
|
<h3 id="index-data"><a class="header" href="#index-data">Index data</a></h3>
|
|
<pre><code>char* : each index is a uint32 or uint16 (see header)
|
|
</code></pre>
|
|
<h3 id="parts"><a class="header" href="#parts">Parts</a></h3>
|
|
<pre><code>for each part:
|
|
uint32: offset of the first index in the index buffer
|
|
uint32: number of indices that compose this part
|
|
uint32: min index referenced by this part (glDrawRangeElements)
|
|
uint32: max index referenced by this part (glDrawRangeElements)
|
|
uint32: material ID (index in list of materials)
|
|
float3: center of the part's bounding box (AABB)
|
|
float3: half extent of the part's bounding box (AABB)
|
|
</code></pre>
|
|
<h3 id="materials"><a class="header" href="#materials">Materials</a></h3>
|
|
<pre><code>uint32 : number of materials
|
|
for each material:
|
|
uint32: length in bytes of the material name's string (not counting terminating \0)
|
|
char* : name of the material (null terminated)
|
|
</code></pre>
|
|
<h2 id="example"><a class="header" href="#example">Example</a></h2>
|
|
<pre><code class="language-c++">struct Mesh {
|
|
utils::Entity renderable;
|
|
VertexBuffer* vertexBuffer = nullptr;
|
|
IndexBuffer* indexBuffer = nullptr;
|
|
};
|
|
|
|
struct Header {
|
|
uint32_t version;
|
|
uint32_t parts;
|
|
Box aabb;
|
|
uint32_t flags;
|
|
uint32_t offsetPosition;
|
|
uint32_t stridePosition;
|
|
uint32_t offsetTangents;
|
|
uint32_t strideTangents;
|
|
uint32_t offsetColor;
|
|
uint32_t strideColor;
|
|
uint32_t offsetUV0;
|
|
uint32_t strideUV0;
|
|
uint32_t offsetUV1;
|
|
uint32_t strideUV1;
|
|
uint32_t vertexCount;
|
|
uint32_t vertexSize;
|
|
uint32_t indexType;
|
|
uint32_t indexCount;
|
|
uint32_t indexSize;
|
|
};
|
|
|
|
struct Vertex {
|
|
half4 position;
|
|
short4 tangents;
|
|
ubyte4 color;
|
|
short2 uv0; // either half-float or snorm int16
|
|
};
|
|
|
|
struct Part {
|
|
uint32_t offset;
|
|
uint32_t indexCount;
|
|
uint32_t minIndex;
|
|
uint32_t maxIndex;
|
|
uint32_t materialID;
|
|
Box aabb;
|
|
};
|
|
|
|
static size_t fileSize(int fd) {
|
|
size_t filesize;
|
|
filesize = (size_t) lseek(fd, 0, SEEK_END);
|
|
lseek(fd, 0, SEEK_SET);
|
|
return filesize;
|
|
}
|
|
|
|
Mesh loadMeshFromFile(filament::Engine* engine, const utils::Path& path,
|
|
const std::map<std::string, filament::MaterialInstance*>& materials) {
|
|
|
|
Mesh mesh;
|
|
|
|
int fd = open(path.c_str(), O_RDONLY);
|
|
|
|
size_t size = fileSize(fd);
|
|
char* data = (char*) mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
|
|
if (data) {
|
|
char *p = data;
|
|
|
|
char magic[9];
|
|
memcpy(magic, (const char*) p, sizeof(char) * 8);
|
|
magic[8] = '\0';
|
|
p += sizeof(char) * 8;
|
|
|
|
if (!strcmp("FILAMESH", magic)) {
|
|
Header* header = (Header*) p;
|
|
p += sizeof(Header);
|
|
|
|
char* vertexData = p;
|
|
p += header->vertexSize;
|
|
|
|
char* indices = p;
|
|
p += header->indexSize;
|
|
|
|
Part* parts = (Part*) p;
|
|
p += header->parts * sizeof(Part);
|
|
|
|
uint32_t materialCount = (uint32_t) *p;
|
|
p += sizeof(uint32_t);
|
|
|
|
std::vector<std::string> partsMaterial;
|
|
partsMaterial.resize(materialCount);
|
|
|
|
for (size_t i = 0; i < materialCount; i++) {
|
|
uint32_t nameLength = (uint32_t) *p;
|
|
p += sizeof(uint32_t);
|
|
|
|
partsMaterial[i] = p;
|
|
p += nameLength + 1; // null terminated
|
|
}
|
|
|
|
mesh.indexBuffer = IndexBuffer::Builder()
|
|
.indexCount(header->indexCount)
|
|
.bufferType(header->indexType ? IndexBuffer::IndexType::USHORT
|
|
: IndexBuffer::IndexType::UINT)
|
|
.build(*engine);
|
|
|
|
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)
|
|
.normalized(VertexAttribute::TANGENTS)
|
|
.normalized(VertexAttribute::COLOR)
|
|
.attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::HALF4,
|
|
header->offsetPosition, uint8_t(header->stridePosition))
|
|
.attribute(VertexAttribute::TANGENTS, 0, VertexBuffer::AttributeType::SHORT4,
|
|
header->offsetTangents, uint8_t(header->strideTangents))
|
|
.attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4,
|
|
header->offsetColor, uint8_t(header->strideColor))
|
|
.attribute(VertexAttribute::UV0, 0, uvType,
|
|
header->offsetUV0, uint8_t(header->strideUV0))
|
|
.normalized(VertexAttribute::UV0, uvNormalized);
|
|
}
|
|
|
|
if (header->offsetUV1 != std::numeric_limits<uint32_t>::max() &&
|
|
header->strideUV1 != std::numeric_limits<uint32_t>::max()) {
|
|
vbb
|
|
.attribute(VertexAttribute::UV1, 0, uvType,
|
|
header->offsetUV1, uint8_t(header->strideUV1))
|
|
.normalized(VertexAttribute::UV1, uvNormalized);
|
|
}
|
|
|
|
mesh.vertexBuffer = vbb.build(*engine);
|
|
|
|
VertexBuffer::BufferDescriptor buffer(vertexData, header->vertexSize);
|
|
mesh.vertexBuffer->setBufferAt(*engine, 0, std::move(buffer));
|
|
|
|
RenderableManager::Builder builder(header->parts);
|
|
builder.boundingBox(header->aabb);
|
|
|
|
for (size_t i = 0; i < header->parts; i++) {
|
|
builder.geometry(i, RenderableManager::PrimitiveType::TRIANGLES,
|
|
mesh.vertexBuffer, mesh.indexBuffer, parts[i].offset,
|
|
parts[i].minIndex, parts[i].maxIndex, parts[i].indexCount);
|
|
auto m = materials.find(partsMaterial[i]);
|
|
if (m != materials.end()) {
|
|
builder.material(i, m->second);
|
|
} else {
|
|
builder.material(i, materials.at("DefaultMaterial"));
|
|
}
|
|
}
|
|
|
|
mesh.renderable = utils::EntityManager::get().create();
|
|
builder.build(*engine, mesh.renderable);
|
|
}
|
|
|
|
Fence::waitAndDestroy(engine->createFence());
|
|
munmap(data, size);
|
|
}
|
|
close(fd);
|
|
|
|
return mesh;
|
|
}
|
|
</code></pre>
|
|
|
|
</main>
|
|
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|
<!-- Mobile navigation buttons -->
|
|
<a rel="prev" href="../dup/cso_lut.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
<i class="fa fa-angle-left"></i>
|
|
</a>
|
|
|
|
<a rel="next prefetch" href="../dup/normal_blending.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
<i class="fa fa-angle-right"></i>
|
|
</a>
|
|
|
|
<div style="clear: both"></div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|
<a rel="prev" href="../dup/cso_lut.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
<i class="fa fa-angle-left"></i>
|
|
</a>
|
|
|
|
<a rel="next prefetch" href="../dup/normal_blending.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
<i class="fa fa-angle-right"></i>
|
|
</a>
|
|
</nav>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
window.playground_copyable = true;
|
|
</script>
|
|
|
|
|
|
<script src="../elasticlunr.min.js"></script>
|
|
<script src="../mark.min.js"></script>
|
|
<script src="../searcher.js"></script>
|
|
|
|
<script src="../clipboard.min.js"></script>
|
|
<script src="../highlight.js"></script>
|
|
<script src="../book.js"></script>
|
|
|
|
<!-- Custom JS scripts -->
|
|
|
|
|
|
</div>
|
|
</body>
|
|
</html>
|