3808 lines
235 KiB
HTML
3808 lines
235 KiB
HTML
<!DOCTYPE HTML>
|
||
<html lang="en" class="light" dir="ltr">
|
||
<head>
|
||
<!-- Book generated using mdBook -->
|
||
<meta charset="UTF-8">
|
||
<title>Filament</title>
|
||
<meta name="robots" content="noindex">
|
||
|
||
|
||
<!-- 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="icon" href="favicon.svg">
|
||
<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">
|
||
<link rel="stylesheet" href="css/print.css" media="print">
|
||
|
||
<!-- 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>
|
||
</head>
|
||
<body class="sidebar-visible no-js">
|
||
<div id="body-container">
|
||
<!-- Provide site root to javascript -->
|
||
<script>
|
||
var path_to_root = "";
|
||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
||
</script>
|
||
|
||
<!-- 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; }
|
||
var html = document.querySelector('html');
|
||
html.classList.remove('light')
|
||
html.classList.add(theme);
|
||
var body = document.querySelector('body');
|
||
body.classList.remove('no-js')
|
||
body.classList.add('js');
|
||
</script>
|
||
|
||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||
|
||
<!-- Hide / unhide sidebar before it is displayed -->
|
||
<script>
|
||
var body = document.querySelector('body');
|
||
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';
|
||
body.classList.remove('sidebar-visible');
|
||
body.classList.add("sidebar-" + sidebar);
|
||
</script>
|
||
|
||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||
<div class="sidebar-scrollbox">
|
||
<ol class="chapter"><li class="chapter-item expanded "><a href="core.html"><strong aria-hidden="true">1.</strong> Core Documentation</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="core-design.html"><strong aria-hidden="true">1.1.</strong> Filament Design</a></li><li class="chapter-item expanded "><a href="core-materials.html"><strong aria-hidden="true">1.2.</strong> Materials Overview</a></li></ol></li><li class="chapter-item expanded "><a href="api.html"><strong aria-hidden="true">2.</strong> API Reference</a></li><li class="chapter-item expanded "><a href="ios.html"><strong aria-hidden="true">3.</strong> iOS</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="cocoapods.html"><strong aria-hidden="true">3.1.</strong> CocoaPods Hello Triangle</a></li></ol></li><li class="chapter-item expanded "><a href="web.html"><strong aria-hidden="true">4.</strong> Web</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="web-tutorials.html"><strong aria-hidden="true">4.1.</strong> Tutorials</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="web-tutorial-triangle.html"><strong aria-hidden="true">4.1.1.</strong> Triangle Tutorial</a></li><li class="chapter-item expanded "><a href="web-tutorial-redball.html"><strong aria-hidden="true">4.1.2.</strong> Redball Tutorial</a></li><li class="chapter-item expanded "><a href="web-tutorial-suzanne.html"><strong aria-hidden="true">4.1.3.</strong> Suzanne Tutorial</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="dev.html"><strong aria-hidden="true">5.</strong> Developing Filament</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="dev-debugging.html"><strong aria-hidden="true">5.1.</strong> Filament Debugging</a></li><li class="chapter-item expanded "><a href="dev-spirv-debugging.html"><strong aria-hidden="true">5.2.</strong> Investigating SPIRV Issues</a></li><li class="chapter-item expanded "><a href="dev-versioning.html"><strong aria-hidden="true">5.3.</strong> Versioning</a></li><li class="chapter-item expanded "><a href="dev-vulkan.html"><strong aria-hidden="true">5.4.</strong> Vulkan Backend</a></li></ol></li></ol>
|
||
</div>
|
||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||
</nav>
|
||
|
||
<!-- Track and set sidebar scroll position -->
|
||
<script>
|
||
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
|
||
sidebarScrollbox.addEventListener('click', function(e) {
|
||
if (e.target.tagName === 'A') {
|
||
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
|
||
}
|
||
}, { passive: true });
|
||
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
|
||
sessionStorage.removeItem('sidebar-scroll');
|
||
if (sidebarScrollTop) {
|
||
// preserve sidebar scroll position when navigating via links within sidebar
|
||
sidebarScrollbox.scrollTop = sidebarScrollTop;
|
||
} else {
|
||
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
|
||
var activeSection = document.querySelector('#sidebar .active');
|
||
if (activeSection) {
|
||
activeSection.scrollIntoView({ block: 'center' });
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<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>
|
||
<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="print.html" title="Print this book" aria-label="Print this book">
|
||
<i id="print-button" class="fa fa-print"></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="core-documentation"><a class="header" href="#core-documentation">Core Documentation</a></h1>
|
||
<h2 id="about"><a class="header" href="#about">About</a></h2>
|
||
<p>Filament is a real-time physically based rendering engine for Android, iOS,
|
||
Linux, macOS, Windows, and WebGL. It is designed to be as small as possible and
|
||
as efficient as possible on Android.</p>
|
||
<p>This book contains demos, tutorials, and links to important reference materials
|
||
for developing software with Filament.</p>
|
||
<p>In addition to the contents in this book, the following external resources are
|
||
also available.</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://github.com/google/filament">GitHub Project</a></p>
|
||
<p>Contains build instructions, instructions for contributors, etc.</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="./Material%20Properties.pdf">Materials Ref Sheet</a></p>
|
||
<p>A visual guide that demonstrates the effects of tweaking various PBR
|
||
parameters.</p>
|
||
</li>
|
||
</ul>
|
||
<p>To report errors in this book, please refer to the Filament project's
|
||
<a href="https://github.com/google/filament/issues">issue tracker</a>.</p>
|
||
<h2 id="authors"><a class="header" href="#authors">Authors</a></h2>
|
||
<ul>
|
||
<li><a href="https://github.com/romainguy">Romain Guy</a>,
|
||
<a href="https://twitter.com/romainguy">@romainguy</a></li>
|
||
<li><a href="https://github.com/pixelflinger">Mathias Agopian</a>,
|
||
<a href="https://twitter.com/darthmoosious">@darthmoosious</a></li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="filament-design"><a class="header" href="#filament-design">Filament Design</a></h1>
|
||
<p>The <a href="./Filament.html">Filament Design</a> doc gives a high level overview of the
|
||
PBR rendering techniques used in Filament.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="overview"><a class="header" href="#overview">Overview</a></h1>
|
||
<p>Filament is a physically based rendering (PBR) engine for Android. Filament
|
||
offers a customizable material system that you can use to create both simple and
|
||
complex materials. This document describes all the features available to
|
||
materials and how to create your own material.</p>
|
||
<h2 id="core-concepts"><a class="header" href="#core-concepts">Core concepts</a></h2>
|
||
<p>Material
|
||
: A material defines the visual appearance of a surface. To completely
|
||
describe and render a surface, a material provides the following
|
||
information:
|
||
- Material model
|
||
- Set of use-controllable named parameters
|
||
- Raster state (blending mode, backface culling, etc.)
|
||
- Vertex shader code
|
||
- Fragment shader code</p>
|
||
<p>Material model
|
||
: Also called <em>shading model</em> or <em>lighting model</em>, the material model defines
|
||
the intrinsic properties of a surface. These properties have a direct
|
||
influence on the way lighting is computed and therefore on the appearance of
|
||
a surface.</p>
|
||
<p>Material definition
|
||
: A text file that describes all the information required by a material. This
|
||
is the file that you will directly author to create new materials.</p>
|
||
<p>Material package
|
||
: At runtime, materials are loaded from <em>material packages</em> compiled from
|
||
material definitions using the <code>matc</code> tool. A material package contains all
|
||
the information required to describe a material, and shaders generated for
|
||
the target runtime platforms. This is necessary because different platforms
|
||
(Android, macOS, Linux, etc.) use different graphics APIs or different
|
||
variants of similar graphics APIs (OpenGL vs OpenGL ES for instance).</p>
|
||
<p>Material instance
|
||
: A material instance is a reference to a material and a set of values for the
|
||
different values of that material. Material instances are not covered in
|
||
this document as they are created and manipulated directly from code using
|
||
Filament's APIs.</p>
|
||
<h1 id="material-models"><a class="header" href="#material-models">Material models</a></h1>
|
||
<p>Filament materials can use one of the following material models: - Lit (or
|
||
standard) - Subsurface - Cloth - Unlit - Specular glossiness (legacy)</p>
|
||
<h2 id="lit-model"><a class="header" href="#lit-model">Lit model</a></h2>
|
||
<p>The lit model is Filament's standard material model. This physically-based
|
||
shading model was designed after to offer good interoperability with other
|
||
common tools and engines such as <em>Unity 5</em>, <em>Unreal Engine 4</em>, <em>Substance
|
||
Designer</em> or <em>Marmoset Toolbag</em>.</p>
|
||
<p>This material model can be used to describe many non-metallic surfaces
|
||
(<em>dielectrics</em>) or metallic surfaces (<em>conductors</em>).</p>
|
||
<p>The appearance of a material using the standard model is controlled using the
|
||
properties described in table [standardProperties].</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: right">Property</th><th style="text-align: left">Definition</th></tr></thead><tbody>
|
||
<tr><td style="text-align: right"><strong>baseColor</strong></td><td style="text-align: left">Diffuse albedo for non-metallic surfaces, and specular color for metallic surfaces</td></tr>
|
||
<tr><td style="text-align: right"><strong>metallic</strong></td><td style="text-align: left">Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1)</td></tr>
|
||
<tr><td style="text-align: right"><strong>roughness</strong></td><td style="text-align: left">Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections</td></tr>
|
||
<tr><td style="text-align: right"><strong>reflectance</strong></td><td style="text-align: left">Fresnel reflectance at normal incidence for dielectric surfaces. This directly controls the strength of the reflections</td></tr>
|
||
<tr><td style="text-align: right"><strong>sheenColor</strong></td><td style="text-align: left">Strength of the sheen layer</td></tr>
|
||
<tr><td style="text-align: right"><strong>sheenRoughness</strong></td><td style="text-align: left">Perceived smoothness or roughness of the sheen layer</td></tr>
|
||
<tr><td style="text-align: right"><strong>clearCoat</strong></td><td style="text-align: left">Strength of the clear coat layer</td></tr>
|
||
<tr><td style="text-align: right"><strong>clearCoatRoughness</strong></td><td style="text-align: left">Perceived smoothness or roughness of the clear coat layer</td></tr>
|
||
<tr><td style="text-align: right"><strong>anisotropy</strong></td><td style="text-align: left">Amount of anisotropy in either the tangent or bitangent direction</td></tr>
|
||
<tr><td style="text-align: right"><strong>anisotropyDirection</strong></td><td style="text-align: left">Local surface direction in tangent space</td></tr>
|
||
<tr><td style="text-align: right"><strong>ambientOcclusion</strong></td><td style="text-align: left">Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0</td></tr>
|
||
<tr><td style="text-align: right"><strong>normal</strong></td><td style="text-align: left">A detail normal used to perturb the surface using <em>bump mapping</em> (<em>normal mapping</em>)</td></tr>
|
||
<tr><td style="text-align: right"><strong>bentNormal</strong></td><td style="text-align: left">A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality</td></tr>
|
||
<tr><td style="text-align: right"><strong>clearCoatNormal</strong></td><td style="text-align: left">A detail normal used to perturb the clear coat layer using <em>bump mapping</em> (<em>normal mapping</em>)</td></tr>
|
||
<tr><td style="text-align: right"><strong>emissive</strong></td><td style="text-align: left">Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass</td></tr>
|
||
<tr><td style="text-align: right"><strong>postLightingColor</strong></td><td style="text-align: left">Additional color that can be blended with the result of the lighting computations. See <code>postLightingBlending</code></td></tr>
|
||
<tr><td style="text-align: right"><strong>ior</strong></td><td style="text-align: left">Index of refraction, either for refractive objects or as an alternative to reflectance</td></tr>
|
||
<tr><td style="text-align: right"><strong>transmission</strong></td><td style="text-align: left">Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is</td></tr>
|
||
<tr><td style="text-align: right"><strong>absorption</strong></td><td style="text-align: left">Absorption factor for refractive objects</td></tr>
|
||
<tr><td style="text-align: right"><strong>microThickness</strong></td><td style="text-align: left">Thickness of the thin layer of refractive objects</td></tr>
|
||
<tr><td style="text-align: right"><strong>thickness</strong></td><td style="text-align: left">Thickness of the solid volume of refractive objects</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>[Table [standardProperties]: Properties of the standard model]</p>
|
||
<p>The type and range of each property is described in table
|
||
[standardPropertiesTypes].</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: right">Property</th><th style="text-align: center">Type</th><th style="text-align: center">Range</th><th style="text-align: left">Note</th></tr></thead><tbody>
|
||
<tr><td style="text-align: right"><strong>baseColor</strong></td><td style="text-align: center">float4</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Pre-multiplied linear RGB</td></tr>
|
||
<tr><td style="text-align: right"><strong>metallic</strong></td><td style="text-align: center">float</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Should be 0 or 1</td></tr>
|
||
<tr><td style="text-align: right"><strong>roughness</strong></td><td style="text-align: center">float</td><td style="text-align: center">[0..1]</td><td style="text-align: left"></td></tr>
|
||
<tr><td style="text-align: right"><strong>reflectance</strong></td><td style="text-align: center">float</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Prefer values > 0.35</td></tr>
|
||
<tr><td style="text-align: right"><strong>sheenColor</strong></td><td style="text-align: center">float3</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Linear RGB</td></tr>
|
||
<tr><td style="text-align: right"><strong>sheenRoughness</strong></td><td style="text-align: center">float</td><td style="text-align: center">[0..1]</td><td style="text-align: left"></td></tr>
|
||
<tr><td style="text-align: right"><strong>clearCoat</strong></td><td style="text-align: center">float</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Should be 0 or 1</td></tr>
|
||
<tr><td style="text-align: right"><strong>clearCoatRoughness</strong></td><td style="text-align: center">float</td><td style="text-align: center">[0..1]</td><td style="text-align: left"></td></tr>
|
||
<tr><td style="text-align: right"><strong>anisotropy</strong></td><td style="text-align: center">float</td><td style="text-align: center">[-1..1]</td><td style="text-align: left">Anisotropy is in the tangent direction when this value is positive</td></tr>
|
||
<tr><td style="text-align: right"><strong>anisotropyDirection</strong></td><td style="text-align: center">float3</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Linear RGB, encodes a direction vector in tangent space</td></tr>
|
||
<tr><td style="text-align: right"><strong>ambientOcclusion</strong></td><td style="text-align: center">float</td><td style="text-align: center">[0..1]</td><td style="text-align: left"></td></tr>
|
||
<tr><td style="text-align: right"><strong>normal</strong></td><td style="text-align: center">float3</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Linear RGB, encodes a direction vector in tangent space</td></tr>
|
||
<tr><td style="text-align: right"><strong>bentNormal</strong></td><td style="text-align: center">float3</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Linear RGB, encodes a direction vector in tangent space</td></tr>
|
||
<tr><td style="text-align: right"><strong>clearCoatNormal</strong></td><td style="text-align: center">float3</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Linear RGB, encodes a direction vector in tangent space</td></tr>
|
||
<tr><td style="text-align: right"><strong>emissive</strong></td><td style="text-align: center">float4</td><td style="text-align: center">rgb=[0..n], a=[0..1]</td><td style="text-align: left">Linear RGB intensity in nits, alpha encodes the exposure weight</td></tr>
|
||
<tr><td style="text-align: right"><strong>postLightingColor</strong></td><td style="text-align: center">float4</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Pre-multiplied linear RGB</td></tr>
|
||
<tr><td style="text-align: right"><strong>ior</strong></td><td style="text-align: center">float</td><td style="text-align: center">[1..n]</td><td style="text-align: left">Optional, usually deduced from the reflectance</td></tr>
|
||
<tr><td style="text-align: right"><strong>transmission</strong></td><td style="text-align: center">float</td><td style="text-align: center">[0..1]</td><td style="text-align: left"></td></tr>
|
||
<tr><td style="text-align: right"><strong>absorption</strong></td><td style="text-align: center">float3</td><td style="text-align: center">[0..n]</td><td style="text-align: left"></td></tr>
|
||
<tr><td style="text-align: right"><strong>microThickness</strong></td><td style="text-align: center">float</td><td style="text-align: center">[0..n]</td><td style="text-align: left"></td></tr>
|
||
<tr><td style="text-align: right"><strong>thickness</strong></td><td style="text-align: center">float</td><td style="text-align: center">[0..n]</td><td style="text-align: left"></td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>[Table [standardPropertiesTypes]: Range and type of the standard model's properties]</p>
|
||
<p>!!! Note: About linear RGB Several material model properties expect RGB colors.
|
||
Filament materials use RGB colors in linear space and you must take proper care
|
||
of supplying colors in that space. See the Linear colors section for more
|
||
information.</p>
|
||
<p>!!! Note: About pre-multiplied RGB Filament materials expect colors to use
|
||
pre-multiplied alpha. See the Pre-multiplied alpha section for more information.</p>
|
||
<p>!!! Note: About <code>absorption</code> The light attenuation through the material is
|
||
defined as \(e^{-absorption \cdot distance}\), and the distance depends on the
|
||
<code>thickness</code> parameter. If <code>thickness</code> is not provided, then the <code>absorption</code>
|
||
parameter is used directly and the light attenuation through the material
|
||
becomes \(1 - absorption\). To obtain a certain color at a desired distance, the
|
||
above equation can be inverted such as $absorption =
|
||
-\frac{ln(color)}{distance}$.</p>
|
||
<p>!!! Note: About <code>ior</code> and <code>reflectance</code> The index of refraction (IOR) and the
|
||
reflectance represent the same physical attribute, therefore they don't need to
|
||
be both specified. Typically, only the reflectance is specified, and the IOR is
|
||
deduced automatically. When only the IOR is specified, the reflectance is then
|
||
deduced automatically. It is possible to specify both, in which case their
|
||
values are kept as-is, which can lead to physically impossible materials,
|
||
however, this might be desirable for artistic reasons.</p>
|
||
<p>!!! Note: About <code>thickness</code> and <code>microThickness</code> for refraction <code>thickness</code>
|
||
represents the thickness of solid objects in the direction of the normal, for
|
||
satisfactory results, this should be provided per fragment (e.g.: as a texture)
|
||
or at least per vertex. <code>microThickness</code> represent the thickness of the thin
|
||
layer of an object, and can generally be provided as a constant value. For
|
||
example, a 1mm thin hollow sphere of radius 1m, would have a <code>thickness</code> of 1
|
||
and a <code>microThickness</code> of 0.001. Currently <code>thickness</code> is not used when
|
||
<code>refractionType</code> is set to <code>thin</code>.</p>
|
||
<h3 id="base-color"><a class="header" href="#base-color">Base color</a></h3>
|
||
<p>The <code>baseColor</code> property defines the perceived color of an object (sometimes
|
||
called albedo). The effect of <code>baseColor</code> depends on the nature of the surface,
|
||
controlled by the <code>metallic</code> property explained in the Metallic section.</p>
|
||
<p>Non-metals (dielectrics)
|
||
: Defines the diffuse color of the surface. Real-world values are typically
|
||
found in the range \([10..240]\) if the value is encoded between 0 and 255, or
|
||
in the range \([0.04..0.94]\) between 0 and 1. Several examples of base colors
|
||
for non-metallic surfaces can be found in table [baseColorsDielectrics].</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: right">Metal</th><th style="text-align: center">sRGB</th><th style="text-align: center">Hexadecimal</th><th>Color</th></tr></thead><tbody>
|
||
<tr><td style="text-align: right">Coal</td><td style="text-align: center">0.19, 0.19, 0.19</td><td style="text-align: center">#323232</td><td><div style="background-color: #323232; width: 60px"> </div></td></tr>
|
||
<tr><td style="text-align: right">Rubber</td><td style="text-align: center">0.21, 0.21, 0.21</td><td style="text-align: center">#353535</td><td><div style="background-color: #353535; width: 60px"> </div></td></tr>
|
||
<tr><td style="text-align: right">Mud</td><td style="text-align: center">0.33, 0.24, 0.19</td><td style="text-align: center">#553d31</td><td><div style="background-color: #875c3c; width: 60px"> </div></td></tr>
|
||
<tr><td style="text-align: right">Wood</td><td style="text-align: center">0.53, 0.36, 0.24</td><td style="text-align: center">#875c3c</td><td><div style="background-color: #c4c6c6; width: 60px"> </div></td></tr>
|
||
<tr><td style="text-align: right">Vegetation</td><td style="text-align: center">0.48, 0.51, 0.31</td><td style="text-align: center">#7b824e</td><td><div style="background-color: #7b824e; width: 60px"> </div></td></tr>
|
||
<tr><td style="text-align: right">Brick</td><td style="text-align: center">0.58, 0.49, 0.46</td><td style="text-align: center">#947d75</td><td><div style="background-color: #947d75; width: 60px"> </div></td></tr>
|
||
<tr><td style="text-align: right">Sand</td><td style="text-align: center">0.69, 0.66, 0.52</td><td style="text-align: center">#b1a884</td><td><div style="background-color: #b1a884; width: 60px"> </div></td></tr>
|
||
<tr><td style="text-align: right">Concrete</td><td style="text-align: center">0.75, 0.75, 0.73</td><td style="text-align: center">#c0bfbb</td><td><div style="background-color: #c0bfbb; width: 60px"> </div></td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>[Table [baseColorsDielectrics]: <code>baseColor</code> for common non-metals]</p>
|
||
<p>Metals (conductors)
|
||
: Defines the specular color of the surface. Real-world values are typically found in the range
|
||
\([170..255]\) if the value is encoded between 0 and 255, or in the range \([0.66..1.0]\)
|
||
between 0 and 1. Several examples of base colors for metallic surfaces can be found in table
|
||
[baseColorsConductors].</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: right">Metal</th><th style="text-align: center">sRGB</th><th style="text-align: center">Hexadecimal</th><th>Color</th></tr></thead><tbody>
|
||
<tr><td style="text-align: right">Silver</td><td style="text-align: center">0.97, 0.96, 0.91</td><td style="text-align: center">#f7f4e8</td><td><div style="background-color: #faf9f5; width: 60px"> </div></td></tr>
|
||
<tr><td style="text-align: right">Aluminum</td><td style="text-align: center">0.91, 0.92, 0.92</td><td style="text-align: center">#e8eaea</td><td><div style="background-color: #f4f5f5; width: 60px"> </div></td></tr>
|
||
<tr><td style="text-align: right">Titanium</td><td style="text-align: center">0.76, 0.73, 0.69</td><td style="text-align: center">#c1baaf</td><td><div style="background-color: #cec8c2; width: 60px"> </div></td></tr>
|
||
<tr><td style="text-align: right">Iron</td><td style="text-align: center">0.77, 0.78, 0.78</td><td style="text-align: center">#c4c6c6</td><td><div style="background-color: #c0bdba; width: 60px"> </div></td></tr>
|
||
<tr><td style="text-align: right">Platinum</td><td style="text-align: center">0.83, 0.81, 0.78</td><td style="text-align: center">#d3cec6</td><td><div style="background-color: #d6d1c8; width: 60px"> </div></td></tr>
|
||
<tr><td style="text-align: right">Gold</td><td style="text-align: center">1.00, 0.85, 0.57</td><td style="text-align: center">#ffd891</td><td><div style="background-color: #fedc9d; width: 60px"> </div></td></tr>
|
||
<tr><td style="text-align: right">Brass</td><td style="text-align: center">0.98, 0.90, 0.59</td><td style="text-align: center">#f9e596</td><td><div style="background-color: #f4e4ad; width: 60px"> </div></td></tr>
|
||
<tr><td style="text-align: right">Copper</td><td style="text-align: center">0.97, 0.74, 0.62</td><td style="text-align: center">#f7bc9e</td><td><div style="background-color: #fbd8b8; width: 60px"> </div></td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>[Table [baseColorsConductors]: <code>baseColor</code> for common metals]</p>
|
||
<h3 id="metallic"><a class="header" href="#metallic">Metallic</a></h3>
|
||
<p>The <code>metallic</code> property defines whether the surface is a metallic (<em>conductor</em>)
|
||
or a non-metallic (<em>dielectric</em>) surface. This property should be used as a
|
||
binary value, set to either 0 or 1. Intermediate values are only truly useful to
|
||
create transitions between different types of surfaces when using textures.</p>
|
||
<p>This property can dramatically change the appearance of a surface. Non-metallic
|
||
surfaces have chromatic diffuse reflection and achromatic specular reflection
|
||
(reflected light does not change color). Metallic surfaces do not have any
|
||
diffuse reflection and chromatic specular reflection (reflected light takes on
|
||
the color of the surfaced as defined by <code>baseColor</code>).</p>
|
||
<p>The effect of <code>metallic</code> is shown in figure
|
||
[metallicProperty](click on the image to see a larger version).</p>
|
||
<p><img src="images/materials/metallic.png" alt="Figure [metallicProperty]: metallic varying from 0.0 (left) to 1.0 (right)" /></p>
|
||
<h3 id="roughness"><a class="header" href="#roughness">Roughness</a></h3>
|
||
<p>The <code>roughness</code> property controls the perceived smoothness of the surface. When
|
||
<code>roughness</code> is set to 0, the surface is perfectly smooth and highly glossy. The
|
||
rougher a surface is, the "blurrier" the reflections are. This property is often
|
||
called <em>glossiness</em> in other engines and tools, and is simply the opposite of
|
||
the roughness (<code>roughness = 1 - glossiness</code>).</p>
|
||
<h3 id="non-metals"><a class="header" href="#non-metals">Non-metals</a></h3>
|
||
<p>The effect of <code>roughness</code> on non-metallic surfaces is shown in figure
|
||
[roughnessProperty](click on the image to see a larger version).</p>
|
||
<p><img src="images/materials/dielectric_roughness.png" alt="Figure [roughnessProperty]: Dielectric roughness varying from 0.0 (left) to 1.0 (right)" /></p>
|
||
<h3 id="metals"><a class="header" href="#metals">Metals</a></h3>
|
||
<p>The effect of <code>roughness</code> on metallic surfaces is shown in figure
|
||
[roughnessConductorProperty](click on the image to see a larger version).</p>
|
||
<p><img src="images/materials/conductor_roughness.png" alt="Figure [roughnessConductorProperty]: Conductor roughness varying from 0.0 (left) to 1.0 (right)" /></p>
|
||
<h3 id="refraction"><a class="header" href="#refraction">Refraction</a></h3>
|
||
<p>When refraction through an object is enabled (using a <code>refractonType</code> of <code>thin</code>
|
||
or <code>solid</code>), the <code>roughness</code> property will also affect the refractions, as shown
|
||
in figure
|
||
[roughnessRefractionProperty](click on the image to see a larger version).</p>
|
||
<p><img src="images/materials/refraction_roughness.png" alt="Figure [roughnessRefractionProperty]: Refractive sphere with roughness varying from 0.0 (left) to 1.0 (right)" /></p>
|
||
<h3 id="reflectance"><a class="header" href="#reflectance">Reflectance</a></h3>
|
||
<p>The <code>reflectance</code> property only affects non-metallic surfaces. This property can
|
||
be used to control the specular intensity and index of refraction of materials.
|
||
This value is defined between 0 and 1 and represents a remapping of a percentage
|
||
of reflectance. For instance, the default value of 0.5 corresponds to a
|
||
reflectance of 4%. Values below 0.35 (2% reflectance) should be avoided as no
|
||
real-world materials have such low reflectance.</p>
|
||
<p>The effect of <code>reflectance</code> on non-metallic surfaces is shown in figure
|
||
[reflectanceProperty](click on the image to see a larger version).</p>
|
||
<p><img src="images/materials/reflectance.png" alt="Figure [reflectanceProperty]: reflectance varying from 0.0 (left) to 1.0 (right)" /></p>
|
||
<p>Figure [reflectance] shows common values and how they relate to the mapping
|
||
function.</p>
|
||
<p><img src="images/diagram_reflectance.png" alt="Figure [reflectance]: Common reflectance values" /></p>
|
||
<p>Table [commonMatReflectance] describes acceptable reflectance values for various
|
||
types of materials (no real world material has a value under 2%).</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: right">Material</th><th style="text-align: left">Reflectance</th><th style="text-align: left">IOR</th><th style="text-align: left">Linear value</th></tr></thead><tbody>
|
||
<tr><td style="text-align: right">Water</td><td style="text-align: left">2%</td><td style="text-align: left">1.33</td><td style="text-align: left">0.35</td></tr>
|
||
<tr><td style="text-align: right">Fabric</td><td style="text-align: left">4% to 5.6%</td><td style="text-align: left">1.5 to 1.62</td><td style="text-align: left">0.5 to 0.59</td></tr>
|
||
<tr><td style="text-align: right">Common liquids</td><td style="text-align: left">2% to 4%</td><td style="text-align: left">1.33 to 1.5</td><td style="text-align: left">0.35 to 0.5</td></tr>
|
||
<tr><td style="text-align: right">Common gemstones</td><td style="text-align: left">5% to 16%</td><td style="text-align: left">1.58 to 2.33</td><td style="text-align: left">0.56 to 1.0</td></tr>
|
||
<tr><td style="text-align: right">Plastics, glass</td><td style="text-align: left">4% to 5%</td><td style="text-align: left">1.5 to 1.58</td><td style="text-align: left">0.5 to 0.56</td></tr>
|
||
<tr><td style="text-align: right">Other dielectric materials</td><td style="text-align: left">2% to 5%</td><td style="text-align: left">1.33 to 1.58</td><td style="text-align: left">0.35 to 0.56</td></tr>
|
||
<tr><td style="text-align: right">Eyes</td><td style="text-align: left">2.5%</td><td style="text-align: left">1.38</td><td style="text-align: left">0.39</td></tr>
|
||
<tr><td style="text-align: right">Skin</td><td style="text-align: left">2.8%</td><td style="text-align: left">1.4</td><td style="text-align: left">0.42</td></tr>
|
||
<tr><td style="text-align: right">Hair</td><td style="text-align: left">4.6%</td><td style="text-align: left">1.55</td><td style="text-align: left">0.54</td></tr>
|
||
<tr><td style="text-align: right">Teeth</td><td style="text-align: left">5.8%</td><td style="text-align: left">1.63</td><td style="text-align: left">0.6</td></tr>
|
||
<tr><td style="text-align: right">Default value</td><td style="text-align: left">4%</td><td style="text-align: left">1.5</td><td style="text-align: left">0.5</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>[Table [commonMatReflectance]: Reflectance of common materials]</p>
|
||
<p>Note that the <code>reflectance</code> property also defines the index of refraction of the
|
||
surface. When this property is defined it is not necessary to define the <code>ior</code>
|
||
property. Setting either of these properties will automatically compute the
|
||
other property. It is possible to specify both, in which case their values are
|
||
kept as-is, which can lead to physically impossible materials, however, this
|
||
might be desirable for artistic reasons.</p>
|
||
<p>The <code>reflectance</code> property is designed as a normalized property in the range
|
||
0..1 which makes it easy to define from a texture.</p>
|
||
<p>See section [Index of refraction] for more information about the <code>ior</code> property
|
||
and refractive indices.</p>
|
||
<h3 id="sheen-color"><a class="header" href="#sheen-color">Sheen color</a></h3>
|
||
<p>The sheen color controls the color appearance and strength of an optional sheen
|
||
layer on top of the base layer described by the properties above. The sheen
|
||
layer always sits below the clear coat layer if such a layer is present.</p>
|
||
<p>The sheen layer can be used to represent cloth and fabric materials. Please
|
||
refer to section [Cloth model] for more information about cloth and fabric
|
||
materials.</p>
|
||
<p>The effect of <code>sheenColor</code> is shown in figure
|
||
[materialSheenColor](click on the image to see a larger version).</p>
|
||
<p><img src="images/screenshot_sheen_color.png" alt="Figure [materialSheenColor]: Different sheen colors" /></p>
|
||
<p>!!! Note If you do not need the other properties offered by the standard lit
|
||
material model but want to create a cloth-like or fabric-like appearance, it is
|
||
more efficient to use the dedicated cloth model described in section [Cloth
|
||
model].</p>
|
||
<h3 id="sheen-roughness"><a class="header" href="#sheen-roughness">Sheen roughness</a></h3>
|
||
<p>The <code>sheenRoughness</code> property is similar to the <code>roughness</code> property but applies
|
||
only to the sheen layer.</p>
|
||
<p>The effect of <code>sheenRoughness</code> on a rough metal is shown in figure
|
||
[sheenRoughnessProperty](click on the image to see a larger version). In this
|
||
picture, the base layer is a dark blue, with <code>metallic</code> set to <code>0.0</code> and
|
||
<code>roughness</code> set to <code>1.0</code>.</p>
|
||
<p><img src="images/materials/sheen_roughness.png" alt="Figure [sheenRoughnessProperty]: sheenRoughness varying from 0.0 (left) to 1.0 (right)" /></p>
|
||
<h3 id="clear-coat"><a class="header" href="#clear-coat">Clear coat</a></h3>
|
||
<p>Multi-layer materials are fairly common, particularly materials with a thin
|
||
translucent layer over a base layer. Real world examples of such materials
|
||
include car paints, soda cans, lacquered wood and acrylic.</p>
|
||
<p>The <code>clearCoat</code> property can be used to describe materials with two layers. The
|
||
clear coat layer will always be isotropic and dielectric.</p>
|
||
<p><img src="images/material_carbon_fiber.png" alt="Figure [clearCoat]: Comparison of a carbon-fiber material under the standard material model (left) and the clear coat model (right)" /></p>
|
||
<p>The <code>clearCoat</code> property controls the strength of the clear coat layer. This
|
||
should be treated as a binary value, set to either 0 or 1. Intermediate values
|
||
are useful to control transitions between parts of the surface that have a clear
|
||
coat layers and parts that don't.</p>
|
||
<p>The effect of <code>clearCoat</code> on a rough metal is shown in figure
|
||
[clearCoatProperty](click on the image to see a larger version).</p>
|
||
<p><img src="images/materials/clear_coat.png" alt="Figure [clearCoatProperty]: clearCoat varying from 0.0 (left) to 1.0 (right)" /></p>
|
||
<p>!!! Warning The clear coat layer effectively doubles the cost of specular
|
||
computations. Do not assign a value, even 0.0, to the clear coat property if you
|
||
don't need this second layer.</p>
|
||
<p>!!! Note The clear coat layer is added on top of the sheen layer if present.</p>
|
||
<h3 id="clear-coat-roughness"><a class="header" href="#clear-coat-roughness">Clear coat roughness</a></h3>
|
||
<p>The <code>clearCoatRoughness</code> property is similar to the <code>roughness</code> property but
|
||
applies only to the clear coat layer.</p>
|
||
<p>The effect of <code>clearCoatRoughness</code> on a rough metal is shown in figure
|
||
[clearCoatRoughnessProperty](click on the image to see a larger version).</p>
|
||
<p><img src="images/materials/clear_coat_roughness.png" alt="Figure [clearCoatRoughnessProperty]: clearCoatRoughness varying from 0.0 (left) to 1.0 (right)" /></p>
|
||
<h3 id="anisotropy"><a class="header" href="#anisotropy">Anisotropy</a></h3>
|
||
<p>Many real-world materials, such as brushed metal, can only be replicated using
|
||
an anisotropic reflectance model. A material can be changed from the default
|
||
isotropic model to an anisotropic model by using the <code>anisotropy</code> property.</p>
|
||
<p><img src="images/material_anisotropic.png" alt="Figure [anisotropic]: Comparison of isotropic material (left) and anistropic material (right)" /></p>
|
||
<p>The effect of <code>anisotropy</code> on a rough metal is shown in figure
|
||
[anisotropyProperty](click on the image to see a larger version).</p>
|
||
<p><img src="images/materials/anisotropy.png" alt="Figure [anisotropyProperty]: anisotropy varying from 0.0 (left) to 1.0 (right)" /></p>
|
||
<p>The figure [anisotropyDir] below shows how the direction of the anisotropic
|
||
highlights can be controlled by using either positive or negative values:
|
||
positive values define anisotropy in the tangent direction and negative values
|
||
in the bitangent direction.</p>
|
||
<p><img src="images/screenshot_anisotropy_direction.png" alt="Figure [anisotropyDir]: Positive (left) vs negative (right) anisotropy values" /></p>
|
||
<p>!!! Tip The anisotropic material model is slightly more expensive than the
|
||
standard material model. Do not assign a value (even 0.0) to the <code>anisotropy</code>
|
||
property if you don't need anisotropy.</p>
|
||
<h3 id="anisotropy-direction"><a class="header" href="#anisotropy-direction">Anisotropy direction</a></h3>
|
||
<p>The <code>anisotropyDirection</code> property defines the direction of the surface at a
|
||
given point and thus control the shape of the specular highlights. It is
|
||
specified as vector of 3 values that usually come from a texture, encoding the
|
||
directions local to the surface in tangent space. Because the direction is in
|
||
tangent space, the Z component should be set to 0.</p>
|
||
<p>The effect of <code>anisotropyDirection</code> on a metal is shown in figure
|
||
[anisotropyDirectionProperty](click on the image to see a larger version).</p>
|
||
<p><img src="images/screenshot_anisotropy.png" alt="Figure [anisotropyDirectionProperty]: Anisotropic metal rendered with a direction map" /></p>
|
||
<p>The result shown in figure [anisotropyDirectionProperty] was obtained using the
|
||
direction map shown in figure [anisotropyDirectionProperty].</p>
|
||
<p><img src="images/screenshot_anisotropy_map.jpg" alt="Figure [anisotropyDirectionProperty]: Example of Lighting: specularAmbientOcclusiona direction map" /></p>
|
||
<h3 id="ambient-occlusion"><a class="header" href="#ambient-occlusion">Ambient occlusion</a></h3>
|
||
<p>The <code>ambientOcclusion</code> property defines how much of the ambient light is
|
||
accessible to a surface point. It is a per-pixel shadowing factor between 0.0
|
||
(fully shadowed) and 1.0 (fully lit). This property only affects diffuse
|
||
indirect lighting (image-based lighting), not direct lights such as directional,
|
||
point and spot lights, nor specular lighting.</p>
|
||
<p><img src="images/screenshot_ao.jpg" alt="Figure [aoExample]: Comparison of materials without diffuse ambient occlusion (left) and with (right)" /></p>
|
||
<h3 id="normal"><a class="header" href="#normal">Normal</a></h3>
|
||
<p>The <code>normal</code> property defines the normal of the surface at a given point. It
|
||
usually comes from a <em>normal map</em> texture, which allows to vary the property
|
||
per-pixel. The normal is supplied in tangent space, which means that +Z points
|
||
outside of the surface.</p>
|
||
<p>For example, let's imagine that we want to render a piece of furniture covered
|
||
in tufted leather. Modeling the geometry to accurately represent the tufted
|
||
pattern would require too many triangles so we instead bake a high-poly mesh
|
||
into a normal map. Once the base map is applied to a simplified mesh, we get the
|
||
result in figure [normalMapped].</p>
|
||
<p>Note that the <code>normal</code> property affects the <em>base layer</em> and not the clear coat
|
||
layer.</p>
|
||
<p><img src="images/screenshot_normal_mapping.jpg" alt="Figure [normalMapped]: Low-poly mesh without normal mapping (left) and with (right)" /></p>
|
||
<p>!!! Warning Using a normal map increases the runtime cost of the material model.</p>
|
||
<h3 id="bent-normal"><a class="header" href="#bent-normal">Bent normal</a></h3>
|
||
<p>The <code>bentNormal</code> property defines the average unoccluded direction at a point on
|
||
the surface. It is used to improve the accuracy of indirect lighting. Bent
|
||
normals can also improve the quality of specular ambient occlusion (see section
|
||
[Lighting: specularAmbientOcclusion] about <code>specularAmbientOcclusion</code>).</p>
|
||
<p>Bent normals can greatly increase the visual fidelity of an asset with various
|
||
cavities and concave areas, as shown in figure [bentNormalMapped]. See the areas
|
||
of the ears, nostrils and eyes for instance.</p>
|
||
<p><img src="images/material_bent_normal.gif" alt="Figure [bentNormalMapped]: Example of a model rendered with and without a bent normal map. Both versions use the same ambient occlusion map." /></p>
|
||
<h3 id="clear-coat-normal"><a class="header" href="#clear-coat-normal">Clear coat normal</a></h3>
|
||
<p>The <code>clearCoatNormal</code> property defines the normal of the clear coat layer at a
|
||
given point. It behaves otherwise like the <code>normal</code> property.</p>
|
||
<p><img src="images/screenshot_clear_coat_normal.jpg" alt="Figure [clearCoatNormalMapped]: A material with a clear coat normal map and a surface normal map" /></p>
|
||
<p>!!! Warning Using a clear coat normal map increases the runtime cost of the
|
||
material model.</p>
|
||
<h3 id="emissive"><a class="header" href="#emissive">Emissive</a></h3>
|
||
<p>The <code>emissive</code> property can be used to simulate additional light emitted by the
|
||
surface. It is defined as a <code>float4</code> value that contains an RGB intensity in
|
||
nits as well as an exposure weight (in the alpha channel).</p>
|
||
<p>The intensity in nits allows an emissive surface to function as a light and can
|
||
be used to recreate real world surfaces. For instance a computer display has an
|
||
intensity between 200 and 1,000 nits.</p>
|
||
<p>If you prefer to work in EV (or f-stops), you can simplify multiply your
|
||
emissive color by the output of the API <code>filament::Exposure::luminance(ev)</code>.
|
||
This API returns the luminance in nits of the specific EV. You can perform this
|
||
conversion yourself using the following formula, where \(L\) is the final
|
||
intensity in nits: \( L = 2^{EV - 3} \).</p>
|
||
<p>The exposure weight carried in the alpha channel can be used to undo the camera
|
||
exposure, and thus force an emissive surface to bloom. When the exposure weight
|
||
is set to 0, the emissive intensity is not affected by the camera exposure. When
|
||
the weight is set to 1, the intensity is multiplied by the camera exposure like
|
||
with any regular light.</p>
|
||
<h3 id="post-lighting-color"><a class="header" href="#post-lighting-color">Post-lighting color</a></h3>
|
||
<p>The <code>postLightingColor</code> can be used to modify the surface color after lighting
|
||
computations. This property has no physical meaning and only exists to implement
|
||
specific effects or to help with debugging. This property is defined as a
|
||
<code>float4</code> value containing a pre-multiplied RGB color in linear space.</p>
|
||
<p>The post-lighting color is blended with the result of lighting according to the
|
||
blending mode specified by the <code>postLightingBlending</code> material option. Please
|
||
refer to the documentation of this option for more information.</p>
|
||
<p>!!! Tip <code>postLightingColor</code> can be used as a simpler <code>emissive</code> property by
|
||
setting <code>postLightingBlending</code> to <code>add</code> and by providing an RGB color with alpha
|
||
set to <code>0.0</code>.</p>
|
||
<h3 id="index-of-refraction"><a class="header" href="#index-of-refraction">Index of refraction</a></h3>
|
||
<p>The <code>ior</code> property only affects non-metallic surfaces. This property can be used
|
||
to control the index of refraction and the specular intensity of materials. The
|
||
<code>ior</code> property is intended to be used with refractive (transmissive) materials,
|
||
which are enabled when the <code>refractionMode</code> is set to <code>cubemap</code> or
|
||
<code>screenspace</code>. It can also be used on non-refractive objects as an alternative
|
||
to setting the reflectance.</p>
|
||
<p>The index of refraction (or refractive index) of a material is a dimensionless
|
||
number that describes how fast light travels through that material. The higher
|
||
the number, the slower light travels through the medium. More importantly for
|
||
rendering materials, the refractive index determines how the path light travels
|
||
is bent when entering the material. Higher indices of refraction will cause
|
||
light to bend further away from the initial path.</p>
|
||
<p>Table [commonMatIOR] describes acceptable refractive indices for various types
|
||
of materials.</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: right">Material</th><th style="text-align: left">IOR</th></tr></thead><tbody>
|
||
<tr><td style="text-align: right">Air</td><td style="text-align: left">1.0</td></tr>
|
||
<tr><td style="text-align: right">Water</td><td style="text-align: left">1.33</td></tr>
|
||
<tr><td style="text-align: right">Common liquids</td><td style="text-align: left">1.33 to 1.5</td></tr>
|
||
<tr><td style="text-align: right">Common gemstones</td><td style="text-align: left">1.58 to 2.33</td></tr>
|
||
<tr><td style="text-align: right">Plastics, glass</td><td style="text-align: left">1.5 to 1.58</td></tr>
|
||
<tr><td style="text-align: right">Other dielectric materials</td><td style="text-align: left">1.33 to 1.58</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>[Table [commonMatIOR]: Index of refraction of common materials]</p>
|
||
<p>The appearance of a refractive material will greatly depend on the
|
||
<code>refractionType</code> and <code>refractionMode</code> settings of the material. Refer to section
|
||
[Blending and transparency: refractionType] and section [Blending and
|
||
transparency: refractionMode] for more information.</p>
|
||
<p>The effect of <code>ior</code> when <code>refractionMode</code> is set to <code>cubemap</code> and
|
||
<code>refractionType</code> is set to <code>solid</code> can be seen in figure
|
||
[iorProperty2](click on the image to see a larger version).</p>
|
||
<p><img src="images/materials/ior.png" alt="Figure [iorProperty2]: transmission varying from 1.0 (left) to 1.5 (right)" /></p>
|
||
<p>Figure [iorProperty] shows the comparison of a sphere of <code>ior</code> 1.0 with a sphere
|
||
of <code>ior</code> 1.33, with the <code>refractionMode</code> set to <code>screenspace</code> and the
|
||
<code>refractionType</code> set to <code>solid</code> (click on the image to see a larger version).</p>
|
||
<p><img src="images/material_ior.png" alt="Figure [iorProperty]: ior of 1.0 (left) and 1.33 (right)" /></p>
|
||
<p>Note that the <code>ior</code> property also defines the reflectance (or specular
|
||
intensity) of the surface. When this property is defined it is not necessary to
|
||
define the <code>reflectance</code> property. Setting either of these properties will
|
||
automatically compute the other property. It is possible to specify both, in
|
||
which case their values are kept as-is, which can lead to physically impossible
|
||
materials, however, this might be desirable for artistic reasons.</p>
|
||
<p>See the Reflectance section for more information on the <code>reflectance</code> property.</p>
|
||
<p>!!! Tip Refractive materials are affected by the <code>roughness</code> property. Rough
|
||
materials will scatter light, creating a diffusion effect useful to recreate
|
||
"blurry" appearances such as frosted glass, certain plastics, etc.</p>
|
||
<h3 id="transmission"><a class="header" href="#transmission">Transmission</a></h3>
|
||
<p>The <code>transmission</code> property defines what ratio of diffuse light is transmitted
|
||
through a refractive material. This property only affects materials with a
|
||
<code>refractionMode</code> set to <code>cubemap</code> or <code>screenspace</code>.</p>
|
||
<p>When <code>transmission</code> is set to 0, no amount of light is transmitted and the
|
||
diffuse component of the surface is 100% visible. When <code>transmission</code> is set to
|
||
1, all the light is transmitted and the diffuse component is not visible
|
||
anymore, only the specular component is.</p>
|
||
<p>The effect of <code>transmission</code> on a glossy dielectric (<code>ior</code> of 1.5,
|
||
<code>refractionMode</code> set to <code>cubemap</code>, <code>refractionType</code> set to <code>solid</code>) is shown in
|
||
figure [transmissionProperty](click on the image to see a larger version).</p>
|
||
<p><img src="images/materials/transmission.png" alt="Figure [transmissionProperty]: transmission varying from 0.0 (left) to 1.0 (right)" /></p>
|
||
<p>!!! Tip The <code>transmission</code> property is useful to create decals, paint, etc. at
|
||
the surface of refractive materials.</p>
|
||
<h3 id="absorption"><a class="header" href="#absorption">Absorption</a></h3>
|
||
<p>The <code>absorption</code> property defines the absorption coefficients of light
|
||
transmitted through the material. Figure [absorptionExample] shows the effect of
|
||
<code>absorption</code> on a refracting object with an index of refraction of 1.5 and a
|
||
base color set to white.</p>
|
||
<p><img src="images/material_absorption.png" alt="Figure [absorptionExample]: Refracting object without (left) and with (right) absorption" /></p>
|
||
<p>Transmittance through a volume is exponential with respect to the optical depth
|
||
(defined either with <code>microThickness</code> or <code>thickness</code>). The computed color
|
||
follows the following formula:</p>
|
||
<p>$$color \cdot e^{-absorption \cdot distance}$$</p>
|
||
<p>Where <code>distance</code> is either <code>microThickness</code> or <code>thickness</code>, that is the distance
|
||
light will travel through the material at a given point. If no
|
||
thickness/distance is specified, the computed color follows this formula
|
||
instead:</p>
|
||
<p>$$color \cdot (1 - absorption)$$</p>
|
||
<p>The effect of varying the <code>absorption</code> coefficients is shown in figure
|
||
[absorptionProperty](click on the image to see a larger version). In this
|
||
picture, the object has a fixed <code>thickness</code> of 4.5 and an index of refraction
|
||
set to 1.3.</p>
|
||
<p><img src="images/materials/absorption.png" alt="Figure [absorptionProperty]: absorption varying from (0.0, 0.02, 0.14) (left) to (0.0, 0.36, 2.3) (right)" /></p>
|
||
<p>Setting the absorption coefficients directly can be unintuitive which is why we
|
||
recommend working with a <em>transmittance color</em> and a <em>"at distance"</em> factor
|
||
instead. These two parameters allow an artist to specify the precise color the
|
||
material should have at a specified distance through the volume. The value to
|
||
pass to <code>absorption</code> can be computed this way:</p>
|
||
<p>$$absorption = -\frac{ln(transmittanceColor)}{atDistance}$$</p>
|
||
<p>While this computation can be done in the material itself we recommend doing it
|
||
offline whenever possible. Filament provides an API for this purpose,
|
||
<code>Color::absorptionAtDistance()</code>.</p>
|
||
<h3 id="micro-thickness-and-thickness"><a class="header" href="#micro-thickness-and-thickness">Micro-thickness and thickness</a></h3>
|
||
<p>The <code>microThickness</code> and <code>thickness</code> properties define the optical depth of the
|
||
material of a refracting object. <code>microThickness</code> is used when <code>refractionType</code>
|
||
is set to <code>thin</code>, and <code>thickness</code> is used when <code>refractionType</code> is set to
|
||
<code>volume</code>.</p>
|
||
<p><code>thickness</code> represents the thickness of solid objects in the direction of the
|
||
normal, for satisfactory results, this should be provided per fragment (e.g.: as
|
||
a texture) or at least per vertex.</p>
|
||
<p><code>microThickness</code> represent the thickness of the thin layer (shell) of an object,
|
||
and can generally be provided as a constant value. For example, a 1mm thin
|
||
hollow sphere of radius 1m, would have a <code>thickness</code> of 1 and a <code>microThickness</code>
|
||
of 0.001. Currently <code>thickness</code> is not used when <code>refractionType</code> is set to
|
||
<code>thin</code>. Both properties are made available for possible future use.</p>
|
||
<p>Both <code>thickness</code> and <code>microThickness</code> are used to compute the transmitted color
|
||
of the material when the <code>absorption</code> property is set. In solid volumes,
|
||
<code>thickness</code> will also affect how light rays are refracted.</p>
|
||
<p>The effect <code>thickness</code> in a solid volume with <code>refractionMode</code> set to
|
||
<code>screenSpace</code> is shown in figure
|
||
[thicknessProperty](click on the image to see a larger version). Note how the
|
||
<code>thickness</code> value not only changes the effect of <code>absorption</code> but also modifies
|
||
the direction of the refracted light.</p>
|
||
<p><img src="images/materials/thickness.png" alt="Figure [thicknessProperty]: thickness varying from 0.0 (left) to 2.0 (right)" /></p>
|
||
<p>Figure [varyingThickness] shows what a prism with spatially varying <code>thickness</code>
|
||
looks like when the <code>refractionType</code> is set to <code>solid</code> and <code>absorption</code>
|
||
coefficients are set.</p>
|
||
<p><img src="images/material_thickness.png" alt="Figure [varyingThickness]: thickness varying from 0.0 at the top of the prism to 3.0 at the bottom of the prism" /></p>
|
||
<h2 id="subsurface-model"><a class="header" href="#subsurface-model">Subsurface model</a></h2>
|
||
<h3 id="thickness"><a class="header" href="#thickness">Thickness</a></h3>
|
||
<h3 id="subsurface-color"><a class="header" href="#subsurface-color">Subsurface color</a></h3>
|
||
<h3 id="subsurface-power"><a class="header" href="#subsurface-power">Subsurface power</a></h3>
|
||
<h2 id="cloth-model"><a class="header" href="#cloth-model">Cloth model</a></h2>
|
||
<p>All the material models described previously are designed to simulate dense
|
||
surfaces, both at a macro and at a micro level. Clothes and fabrics are however
|
||
often made of loosely connected threads that absorb and scatter incident light.
|
||
When compared to hard surfaces, cloth is characterized by a softer specular lob
|
||
with a large falloff and the presence of fuzz lighting, caused by
|
||
forward/backward scattering. Some fabrics also exhibit two-tone specular colors
|
||
(velvets for instance).</p>
|
||
<p>Figure [materialCloth] shows how the standard material model fails to capture
|
||
the appearance of a sample of denim fabric. The surface appears rigid (almost
|
||
plastic-like), more similar to a tarp than a piece of clothing. This figure also
|
||
shows how important the softer specular lobe caused by absorption and scattering
|
||
is to the faithful recreation of the fabric.</p>
|
||
<p><img src="images/screenshot_cloth.png" alt="Figure [materialCloth]: Comparison of denim fabric rendered using the standard model (left) and the cloth model (right)" /></p>
|
||
<p>Velvet is an interesting use case for a cloth material model. As shown in figure
|
||
[materialVelvet] this type of fabric exhibits strong rim lighting due to forward
|
||
and backward scattering. These scattering events are caused by fibers standing
|
||
straight at the surface of the fabric. When the incident light comes from the
|
||
direction opposite to the view direction, the fibers will forward scatter the
|
||
light. Similarly, when the incident light from the same direction as the view
|
||
direction, the fibers will scatter the light backward.</p>
|
||
<p><img src="images/screenshot_cloth_velvet.png" alt="Figure [materialVelvet]: Velvet fabric showcasing forward and backward scattering" /></p>
|
||
<p>It is important to note that there are types of fabrics that are still best
|
||
modeled by hard surface material models. For instance, leather, silk and satin
|
||
can be recreated using the standard or anisotropic material models.</p>
|
||
<p>The cloth material model encompasses all the parameters previously defined for
|
||
the standard material mode except for <em>metallic</em> and <em>reflectance</em>. Two extra
|
||
parameters described in table [clothProperties] are also available.</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: right">Parameter</th><th style="text-align: left">Definition</th></tr></thead><tbody>
|
||
<tr><td style="text-align: right"><strong>sheenColor</strong></td><td style="text-align: left">Specular tint to create two-tone specular fabrics (defaults to \(\sqrt{baseColor}\))</td></tr>
|
||
<tr><td style="text-align: right"><strong>subsurfaceColor</strong></td><td style="text-align: left">Tint for the diffuse color after scattering and absorption through the material</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>[Table [clothProperties]: Cloth model parameters]</p>
|
||
<p>The type and range of each property is described in table
|
||
[clothPropertiesTypes].</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: right">Property</th><th style="text-align: center">Type</th><th style="text-align: center">Range</th><th style="text-align: left">Note</th></tr></thead><tbody>
|
||
<tr><td style="text-align: right"><strong>sheenColor</strong></td><td style="text-align: center">float3</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Linear RGB</td></tr>
|
||
<tr><td style="text-align: right"><strong>subsurfaceColor</strong></td><td style="text-align: center">float3</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Linear RGB</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>[Table [clothPropertiesTypes]: Range and type of the cloth model's properties]</p>
|
||
<p>To create a velvet-like material, the base color can be set to black (or a dark
|
||
color). Chromaticity information should instead be set on the sheen color. To
|
||
create more common fabrics such as denim, cotton, etc. use the base color for
|
||
chromaticity and use the default sheen color or set the sheen color to the
|
||
luminance of the base color.</p>
|
||
<p>!!! Tip To see the effect of the <code>roughness</code> parameter make sure the
|
||
<code>sheenColor</code> is brighter than <code>baseColor</code>. This can be used to create a fuzz
|
||
effect. Taking the luminance of <code>baseColor</code> as the <code>sheenColor</code> will produce a
|
||
fairly natural effect that works for common cloth. A dark <code>baseColor</code> combined
|
||
with a bright/saturated <code>sheenColor</code> can be used to create velvet.</p>
|
||
<p>!!! Tip The <code>subsurfaceColor</code> parameter should be used with care. High values
|
||
can interfere with shadows in some areas. It is best suited for subtle
|
||
transmission effects through the material.</p>
|
||
<h3 id="sheen-color-1"><a class="header" href="#sheen-color-1">Sheen color</a></h3>
|
||
<p>The <code>sheenColor</code> property can be used to directly modify the specular
|
||
reflectance. It offers better control over the appearance of cloth and gives
|
||
give the ability to create two-tone specular materials.</p>
|
||
<p>The effect of <code>sheenColor</code> is shown in figure
|
||
[materialClothSheen](click on the image to see a larger version).</p>
|
||
<p><img src="images/screenshot_cloth_sheen.png" alt="Figure [materialClothSheen]: Blue fabric without (left) and with (right) sheen" /></p>
|
||
<h3 id="subsurface-color-1"><a class="header" href="#subsurface-color-1">Subsurface color</a></h3>
|
||
<p>The <code>subsurfaceColor</code> property is not physically-based and can be used to
|
||
simulate the scattering, partial absorption and re-emission of light in certain
|
||
types of fabrics. This is particularly useful to create softer fabrics.</p>
|
||
<p>!!! Warning The cloth material model is more expensive to compute when the
|
||
<code>subsurfaceColor</code> property is used.</p>
|
||
<p>The effect of <code>subsurfaceColor</code> is shown in figure
|
||
[materialClothSubsurface](click on the image to see a larger version).</p>
|
||
<p><img src="images/screenshot_cloth_subsurface.png" alt="Figure [materialClothSubsurface]: White cloth (left column) vs white cloth with brown subsurface scatting (right)" /></p>
|
||
<h2 id="unlit-model"><a class="header" href="#unlit-model">Unlit model</a></h2>
|
||
<p>The unlit material model can be used to turn off all lighting computations. Its
|
||
primary purpose is to render pre-lit elements such as a cubemap, external
|
||
content (such as a video or camera stream), user interfaces,
|
||
visualization/debugging etc. The unlit model exposes only two properties
|
||
described in table [unlitProperties].</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: right">Property</th><th style="text-align: left">Definition</th></tr></thead><tbody>
|
||
<tr><td style="text-align: right"><strong>baseColor</strong></td><td style="text-align: left">Surface diffuse color</td></tr>
|
||
<tr><td style="text-align: right"><strong>emissive</strong></td><td style="text-align: left">Additional diffuse color to simulate emissive surfaces. This property is mostly useful in an HDR pipeline with a bloom pass</td></tr>
|
||
<tr><td style="text-align: right"><strong>postLightingColor</strong></td><td style="text-align: left">Additional color to blend with base color and emissive</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>[Table [unlitProperties]: Properties of the standard model]</p>
|
||
<p>The type and range of each property is described in table
|
||
[unlitPropertiesTypes].</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: right">Property</th><th style="text-align: center">Type</th><th style="text-align: center">Range</th><th style="text-align: left">Note</th></tr></thead><tbody>
|
||
<tr><td style="text-align: right"><strong>baseColor</strong></td><td style="text-align: center">float4</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Pre-multiplied linear RGB</td></tr>
|
||
<tr><td style="text-align: right"><strong>emissive</strong></td><td style="text-align: center">float4</td><td style="text-align: center">rgb=[0..n], a=[0..1]</td><td style="text-align: left">Linear RGB intensity in nits, alpha encodes the exposure weight</td></tr>
|
||
<tr><td style="text-align: right"><strong>postLightingColor</strong></td><td style="text-align: center">float4</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Pre-multiplied linear RGB</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>[Table [unlitPropertiesTypes]: Range and type of the unlit model's properties]</p>
|
||
<p>The value of <code>postLightingColor</code> is blended with the sum of <code>emissive</code> and
|
||
<code>baseColor</code> according to the blending mode specified by the
|
||
<code>postLightingBlending</code> material option.</p>
|
||
<p>Figure [materialUnlit] shows an example of the unlit material model (click on
|
||
the image to see a larger version).</p>
|
||
<p><img src="images/screenshot_unlit.jpg" alt="Figure [materialUnlit]: The unlit model is used to render debug information" /></p>
|
||
<h2 id="specular-glossiness"><a class="header" href="#specular-glossiness">Specular glossiness</a></h2>
|
||
<p>This alternative lighting model exists to comply with legacy standards. Since it
|
||
is not a physically-based formulation, we do not recommend using it except when
|
||
loading legacy assets.</p>
|
||
<p>This model encompasses the parameters previously defined for the standard lit
|
||
mode except for <em>metallic</em>, <em>reflectance</em>, and <em>roughness</em>. It adds parameters
|
||
for <em>specularColor</em> and <em>glossiness</em>.</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: right">Parameter</th><th style="text-align: left">Definition</th></tr></thead><tbody>
|
||
<tr><td style="text-align: right"><strong>baseColor</strong></td><td style="text-align: left">Surface diffuse color</td></tr>
|
||
<tr><td style="text-align: right"><strong>specularColor</strong></td><td style="text-align: left">Specular tint (defaults to black)</td></tr>
|
||
<tr><td style="text-align: right"><strong>glossiness</strong></td><td style="text-align: left">Glossiness (defaults to 0.0)</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>[Table [glossinessProperties]: Properties of the specular-glossiness shading
|
||
model]</p>
|
||
<p>The type and range of each property is described in table
|
||
[glossinessPropertiesTypes].</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: right">Property</th><th style="text-align: center">Type</th><th style="text-align: center">Range</th><th style="text-align: left">Note</th></tr></thead><tbody>
|
||
<tr><td style="text-align: right"><strong>baseColor</strong></td><td style="text-align: center">float4</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Pre-multiplied linear RGB</td></tr>
|
||
<tr><td style="text-align: right"><strong>specularColor</strong></td><td style="text-align: center">float3</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Linear RGB</td></tr>
|
||
<tr><td style="text-align: right"><strong>glossiness</strong></td><td style="text-align: center">float</td><td style="text-align: center">[0..1]</td><td style="text-align: left">Inverse of roughness</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>[Table [glossinessPropertiesTypes]: Range and type of the specular-glossiness
|
||
model's properties]</p>
|
||
<h1 id="material-definitions"><a class="header" href="#material-definitions">Material definitions</a></h1>
|
||
<p>A material definition is a text file that describes all the information required
|
||
by a material:</p>
|
||
<ul>
|
||
<li>Name</li>
|
||
<li>User parameters</li>
|
||
<li>Material model</li>
|
||
<li>Required attributes</li>
|
||
<li>Interpolants (called <em>variables</em>)</li>
|
||
<li>Raster state (blending mode, etc.)</li>
|
||
<li>Shader code (fragment shader, optionally vertex shader)</li>
|
||
</ul>
|
||
<h2 id="format"><a class="header" href="#format">Format</a></h2>
|
||
<p>The material definition format is a format loosely based on
|
||
<a href="https://www.json.org/">JSON</a> that we call <em>JSONish</em>. At the top level a
|
||
material definition is composed of 3 different blocks that use the JSON object
|
||
notation:</p>
|
||
<pre><code class="language-json">material {
|
||
// material properties
|
||
}
|
||
|
||
vertex {
|
||
// vertex shader, optional
|
||
}
|
||
|
||
fragment {
|
||
// fragment shader
|
||
}
|
||
</code></pre>
|
||
<p>A minimum viable material definition must contain a <code>material</code> preamble and a
|
||
<code>fragment</code> block. The <code>vertex</code> block is optional.</p>
|
||
<h3 id="differences-with-json"><a class="header" href="#differences-with-json">Differences with JSON</a></h3>
|
||
<p>In JSON, an object is made of key/value <em>pairs</em>. A JSON pair has the following
|
||
syntax:</p>
|
||
<pre><code class="language-json">"key" : value
|
||
</code></pre>
|
||
<p>Where value can be a string, number, object, array or a literal (<code>true</code>, <code>false</code>
|
||
or <code>null</code>). While this syntax is perfectly valid in a material definition, a
|
||
variant without quotes around strings is also accepted in JSONish:</p>
|
||
<pre><code class="language-json">key : value
|
||
</code></pre>
|
||
<p>Quotes remain mandatory when the string contains spaces.</p>
|
||
<p>The <code>vertex</code> and <code>fragment</code> blocks contain unescaped, unquoted GLSL code, which
|
||
is not valid in JSON.</p>
|
||
<p>Single-line C++-style comments are allowed.</p>
|
||
<p>The key of a pair is case-sensitive.</p>
|
||
<p>The value of a pair is not case-sensitive.</p>
|
||
<h3 id="example"><a class="header" href="#example">Example</a></h3>
|
||
<p>The following code listing shows an example of a valid material definition. This
|
||
definition uses the <em>lit</em> material model (see Lit model section), uses the
|
||
default opaque blending mode, requires that a set of UV coordinates be presented
|
||
in the rendered mesh and defines 3 user parameters. The following sections of
|
||
this document describe the <code>material</code> and <code>fragment</code> blocks in detail.</p>
|
||
<pre><code class="language-json">material {
|
||
name : "Textured material",
|
||
parameters : [
|
||
{
|
||
type : sampler2d,
|
||
name : texture
|
||
},
|
||
{
|
||
type : float,
|
||
name : metallic
|
||
},
|
||
{
|
||
type : float,
|
||
name : roughness
|
||
}
|
||
],
|
||
requires : [
|
||
uv0
|
||
],
|
||
shadingModel : lit,
|
||
blending : opaque
|
||
}
|
||
|
||
fragment {
|
||
void material(inout MaterialInputs material) {
|
||
prepareMaterial(material);
|
||
material.baseColor = texture(materialParams_texture, getUV0());
|
||
material.metallic = materialParams.metallic;
|
||
material.roughness = materialParams.roughness;
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="material-block"><a class="header" href="#material-block">Material block</a></h2>
|
||
<p>The material block is mandatory block that contains a list of property pairs to
|
||
describe all non-shader data.</p>
|
||
<h3 id="general-name"><a class="header" href="#general-name">General: name</a></h3>
|
||
<p>Type
|
||
: <code>string</code></p>
|
||
<p>Value
|
||
: Any string. Double quotes are required if the name contains spaces.</p>
|
||
<p>Description
|
||
: Sets the name of the material. The name is retained at runtime for debugging
|
||
purpose.</p>
|
||
<pre><code class="language-json">material {
|
||
name : stone
|
||
}
|
||
|
||
material {
|
||
name : "Wet pavement"
|
||
}
|
||
</code></pre>
|
||
<h3 id="general-featurelevel"><a class="header" href="#general-featurelevel">General: featureLevel</a></h3>
|
||
<p>Type
|
||
: <code>number</code></p>
|
||
<p>Value
|
||
: An integer value, either 1, 2 or 3. Defaults to 1.</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: right">Feature Level</th><th style="text-align: left">Guaranteed features</th></tr></thead><tbody>
|
||
<tr><td style="text-align: right">1</td><td style="text-align: left">9 textures per material</td></tr>
|
||
<tr><td style="text-align: right">2</td><td style="text-align: left">9 textures per material, cubemap arrays, ESSL 3.10</td></tr>
|
||
<tr><td style="text-align: right">3</td><td style="text-align: left">12 textures per material, cubemap arrays, ESSL 3.10</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>[Table [featureLevels]: Feature levels]</p>
|
||
<p>Description
|
||
: Sets the feature level of the material. Each feature level defines a set of
|
||
features the material can use. If the material uses a feature not supported
|
||
by the selected level, <code>matc</code> will generate an error during compilation. A
|
||
given feature level is guaranteed to support all features of lower feature
|
||
levels.</p>
|
||
<pre><code class="language-json">material {
|
||
featureLevel : 2
|
||
}
|
||
</code></pre>
|
||
<p>Bugs
|
||
: <code>matc</code> doesn't verify that a material is not using features above its
|
||
selected feature level.</p>
|
||
<h3 id="general-shadingmodel"><a class="header" href="#general-shadingmodel">General: shadingModel</a></h3>
|
||
<p>Type
|
||
: <code>string</code></p>
|
||
<p>Value
|
||
: Any of <code>lit</code>, <code>subsurface</code>, <code>cloth</code>, <code>unlit</code>, <code>specularGlossiness</code>. Defaults
|
||
to <code>lit</code>.</p>
|
||
<p>Description
|
||
: Selects the material model as described in the Material models section.</p>
|
||
<pre><code class="language-json">material {
|
||
shadingModel : unlit
|
||
}
|
||
|
||
material {
|
||
shadingModel : "subsurface"
|
||
}
|
||
</code></pre>
|
||
<h3 id="general-parameters"><a class="header" href="#general-parameters">General: parameters</a></h3>
|
||
<p>Type
|
||
: array of parameter objects</p>
|
||
<p>Value
|
||
: Each entry is an object with the properties <code>name</code> and <code>type</code>, both of
|
||
<code>string</code> type. The name must be a valid GLSL identifier. Entries also have
|
||
an optional <code>precision</code>, which can be one of <code>default</code> (best precision for
|
||
the platform, typically <code>high</code> on desktop, <code>medium</code> on mobile), <code>low</code>,
|
||
<code>medium</code>, <code>high</code>. The type must be one of the types described in table
|
||
[materialParamsTypes].</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: right">Type</th><th style="text-align: left">Description</th></tr></thead><tbody>
|
||
<tr><td style="text-align: right">bool</td><td style="text-align: left">Single boolean</td></tr>
|
||
<tr><td style="text-align: right">bool2</td><td style="text-align: left">Vector of 2 booleans</td></tr>
|
||
<tr><td style="text-align: right">bool3</td><td style="text-align: left">Vector of 3 booleans</td></tr>
|
||
<tr><td style="text-align: right">bool4</td><td style="text-align: left">Vector of 4 booleans</td></tr>
|
||
<tr><td style="text-align: right">float</td><td style="text-align: left">Single float</td></tr>
|
||
<tr><td style="text-align: right">float2</td><td style="text-align: left">Vector of 2 floats</td></tr>
|
||
<tr><td style="text-align: right">float3</td><td style="text-align: left">Vector of 3 floats</td></tr>
|
||
<tr><td style="text-align: right">float4</td><td style="text-align: left">Vector of 4 floats</td></tr>
|
||
<tr><td style="text-align: right">int</td><td style="text-align: left">Single integer</td></tr>
|
||
<tr><td style="text-align: right">int2</td><td style="text-align: left">Vector of 2 integers</td></tr>
|
||
<tr><td style="text-align: right">int3</td><td style="text-align: left">Vector of 3 integers</td></tr>
|
||
<tr><td style="text-align: right">int4</td><td style="text-align: left">Vector of 4 integers</td></tr>
|
||
<tr><td style="text-align: right">uint</td><td style="text-align: left">Single unsigned integer</td></tr>
|
||
<tr><td style="text-align: right">uint2</td><td style="text-align: left">Vector of 2 unsigned integers</td></tr>
|
||
<tr><td style="text-align: right">uint3</td><td style="text-align: left">Vector of 3 unsigned integers</td></tr>
|
||
<tr><td style="text-align: right">uint4</td><td style="text-align: left">Vector of 4 unsigned integers</td></tr>
|
||
<tr><td style="text-align: right">float3x3</td><td style="text-align: left">Matrix of 3x3 floats</td></tr>
|
||
<tr><td style="text-align: right">float4x4</td><td style="text-align: left">Matrix of 4x4 floats</td></tr>
|
||
<tr><td style="text-align: right">sampler2d</td><td style="text-align: left">2D texture</td></tr>
|
||
<tr><td style="text-align: right">sampler2dArray</td><td style="text-align: left">Array of 2D textures</td></tr>
|
||
<tr><td style="text-align: right">samplerExternal</td><td style="text-align: left">External texture (platform-specific)</td></tr>
|
||
<tr><td style="text-align: right">samplerCubemap</td><td style="text-align: left">Cubemap texture</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>[Table [materialParamsTypes]: Material parameter types]</p>
|
||
<p>Samplers
|
||
: Sampler types can also specify a <code>format</code> which can be either <code>int</code> or
|
||
<code>float</code> (defaults to <code>float</code>).</p>
|
||
<p>Arrays
|
||
: A parameter can define an array of values by appending <code>[size]</code> after the
|
||
type name, where <code>size</code> is a positive integer. For instance: <code>float[9]</code>
|
||
declares an array of nine <code>float</code> values. This syntax does not apply to
|
||
samplers as arrays are treated as separate types.</p>
|
||
<p>Description
|
||
: Lists the parameters required by your material. These parameters can be set
|
||
at runtime using Filament's material API. Accessing parameters from the
|
||
shaders varies depending on the type of parameter:</p>
|
||
<pre><code>- **Samplers types**: use the parameter name prefixed with
|
||
`materialParams_`. For instance, `materialParams_myTexture`.
|
||
- **Other types**: use the parameter name as the field of a structure
|
||
called `materialParams`. For instance, `materialParams.myColor`.
|
||
</code></pre>
|
||
<pre><code class="language-json">material {
|
||
parameters : [
|
||
{
|
||
type : float4,
|
||
name : albedo
|
||
},
|
||
{
|
||
type : sampler2d,
|
||
format : float,
|
||
precision : high,
|
||
name : roughness
|
||
},
|
||
{
|
||
type : float2,
|
||
name : metallicReflectance
|
||
}
|
||
],
|
||
requires : [
|
||
uv0
|
||
],
|
||
shadingModel : lit,
|
||
}
|
||
|
||
fragment {
|
||
void material(inout MaterialInputs material) {
|
||
prepareMaterial(material);
|
||
material.baseColor = materialParams.albedo;
|
||
material.roughness = texture(materialParams_roughness, getUV0());
|
||
material.metallic = materialParams.metallicReflectance.x;
|
||
material.reflectance = materialParams.metallicReflectance.y;
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="general-constants"><a class="header" href="#general-constants">General: constants</a></h3>
|
||
<p>Type
|
||
: array of constant objects</p>
|
||
<p>Value
|
||
: Each entry is an object with the properties <code>name</code> and <code>type</code>, both of
|
||
<code>string</code> type. The name must be a valid GLSL identifier. Entries also have
|
||
an optional <code>default</code>, which can either be a <code>bool</code> or <code>number</code>, depending
|
||
on the <code>type</code> of the constant. The type must be one of the types described
|
||
in table [materialConstantsTypes].</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: right">Type</th><th style="text-align: left">Description</th><th style="text-align: left">Default</th></tr></thead><tbody>
|
||
<tr><td style="text-align: right">int</td><td style="text-align: left">A signed, 32 bit GLSL int</td><td style="text-align: left"></td></tr>
|
||
<tr><td style="text-align: right">float</td><td style="text-align: left">A single-precision GLSL float</td><td style="text-align: left">0.0</td></tr>
|
||
<tr><td style="text-align: right">bool</td><td style="text-align: left">A GLSL bool</td><td style="text-align: left">false</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>[Table [materialConstantsTypes]: Material constants types]</p>
|
||
<p>Description
|
||
: Lists the constant parameters accepted by your material. These constants can
|
||
be set, or "specialized", at runtime when loading a material package.
|
||
Multiple materials can be loaded from the same material package with
|
||
differing constant parameter specializations. Once a material is loaded from
|
||
a material package, its constant parameters cannot be changed. Compared to
|
||
regular parameters, constant parameters allow the compiler to generate more
|
||
efficient code. Access constant parameters from the shader by prefixing the
|
||
name with <code>materialConstant_</code>. For example, a constant parameter named
|
||
<code>myConstant</code> is accessed in the shader as <code>materialConstant_myConstant</code>. If
|
||
a constant parameter is not set at runtime, the default is used.</p>
|
||
<pre><code class="language-json">material {
|
||
constants : [
|
||
{
|
||
name : overrideAlpha,
|
||
type : bool
|
||
},
|
||
{
|
||
name : customAlpha,
|
||
type : float,
|
||
default : 0.5
|
||
}
|
||
],
|
||
shadingModel : lit,
|
||
blending : transparent,
|
||
}
|
||
|
||
fragment {
|
||
void material(inout MaterialInputs material) {
|
||
prepareMaterial(material);
|
||
if (materialConstants_overrideAlpha) {
|
||
material.baseColor.a = materialConstants_customAlpha;
|
||
material.baseColor.rgb *= material.baseColor.a;
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="general-variantfilter"><a class="header" href="#general-variantfilter">General: variantFilter</a></h3>
|
||
<p>Type
|
||
: array of <code>string</code></p>
|
||
<p>Value
|
||
: Each entry must be any of <code>dynamicLighting</code>, <code>directionalLighting</code>,
|
||
<code>shadowReceiver</code>, <code>skinning</code>, <code>ssr</code>, or <code>stereo</code>.</p>
|
||
<p>Description
|
||
: Used to specify a list of shader variants that the application guarantees
|
||
will never be needed. These shader variants are skipped during the code
|
||
generation phase, thus reducing the overall size of the material. Note that
|
||
some variants may automatically be filtered out. For instance, all lighting
|
||
related variants (<code>directionalLighting</code>, etc.) are filtered out when
|
||
compiling an <code>unlit</code> material. Use the variant filter with caution,
|
||
filtering out a variant required at runtime may lead to crashes.</p>
|
||
<p>Description of the variants: - <code>directionalLighting</code>, used when a directional
|
||
light is present in the scene - <code>dynamicLighting</code>, used when a non-directional
|
||
light (point, spot, etc.) is present in the scene - <code>shadowReceiver</code>, used when
|
||
an object can receive shadows - <code>skinning</code>, used when an object is animated
|
||
using GPU skinning - <code>fog</code>, used when global fog is applied to the scene -
|
||
<code>vsm</code>, used when VSM shadows are enabled and the object is a shadow receiver -
|
||
<code>ssr</code>, used when screen-space reflections are enabled in the View - <code>stereo</code>,
|
||
used when stereoscopic rendering is enabled in the View</p>
|
||
<pre><code class="language-json">material {
|
||
name : "Invisible shadow plane",
|
||
shadingModel : unlit,
|
||
shadowMultiplier : true,
|
||
blending : transparent,
|
||
variantFilter : [ skinning ]
|
||
}
|
||
</code></pre>
|
||
<h3 id="general-flipuv"><a class="header" href="#general-flipuv">General: flipUV</a></h3>
|
||
<p>Type
|
||
: <code>boolean</code></p>
|
||
<p>Value
|
||
: <code>true</code> or <code>false</code>. Defaults to <code>true</code>.</p>
|
||
<p>Description
|
||
: When set to <code>true</code> (default value), the Y coordinate of UV attributes will
|
||
be flipped when read by this material's vertex shader. Flipping is
|
||
equivalent to <code>y = 1.0 - y</code>. When set to <code>false</code>, flipping is disabled and
|
||
the UV attributes are read as is.</p>
|
||
<pre><code class="language-json">material {
|
||
flipUV : false
|
||
}
|
||
</code></pre>
|
||
<h3 id="general-quality"><a class="header" href="#general-quality">General: quality</a></h3>
|
||
<p>Type
|
||
: <code>string</code></p>
|
||
<p>Value
|
||
: Any of <code>low</code>, <code>normal</code>, <code>high</code>, <code>default</code>. Defaults to <code>default</code>.</p>
|
||
<p>Description
|
||
: Set some global quality parameters of the material. <code>low</code> enables
|
||
optimizations that can slightly affect correctness and is the default on
|
||
mobile platforms. <code>normal</code> does not affect correctness and is otherwise
|
||
similar to <code>low</code>. <code>high</code> enables quality settings that can adversely affect
|
||
performance and is the default on desktop platforms.</p>
|
||
<pre><code class="language-json">material {
|
||
quality : default
|
||
}
|
||
</code></pre>
|
||
<h3 id="general-instanced"><a class="header" href="#general-instanced">General: instanced</a></h3>
|
||
<p>Type
|
||
: <code>boolean</code></p>
|
||
<p>Value
|
||
: <code>true</code> or <code>false</code>. Defaults to <code>false</code>.</p>
|
||
<p>Description
|
||
: Allows a material to access the instance index (i.e.:
|
||
<strong><code>gl_InstanceIndex</code></strong>) of instanced primitives using <code>getInstanceIndex()</code>
|
||
in the material's shader code. Never use <strong><code>gl_InstanceIndex</code></strong> directly.
|
||
This is typically used with <code>RenderableManager::Builder::instances()</code>.
|
||
<code>getInstanceIndex()</code> is available in both the vertex and fragment shader.</p>
|
||
<pre><code class="language-json">material {
|
||
instanced : true
|
||
}
|
||
</code></pre>
|
||
<h3 id="general-vertexdomaindevicejittered"><a class="header" href="#general-vertexdomaindevicejittered">General: vertexDomainDeviceJittered</a></h3>
|
||
<p>Type
|
||
: <code>boolean</code></p>
|
||
<p>Value
|
||
: <code>true</code> or <code>false</code>. Defaults to <code>false</code>.</p>
|
||
<p>Description
|
||
: Only meaningful for <code>vertexDomain:Device</code> materials, this parameter
|
||
specifies whether the filament clip-space transforms need to be applied or
|
||
not, which affects TAA and guard bands. Generally it needs to be applied
|
||
because by definition <code>vertexDomain:Device</code> materials vertices are not
|
||
transformed and used <em>as is</em>. However, if the vertex shader uses for
|
||
instance <code>getViewFromClipMatrix()</code> (or other matrices based on the
|
||
projection), the clip-space transform is already applied. Setting this
|
||
parameter incorrectly can prevent TAA or the guard bands to work correctly.</p>
|
||
<pre><code class="language-json">material {
|
||
vertexDomainDeviceJittered : true
|
||
}
|
||
</code></pre>
|
||
<h3 id="vertex-and-attributes-requires"><a class="header" href="#vertex-and-attributes-requires">Vertex and attributes: requires</a></h3>
|
||
<p>Type
|
||
: array of <code>string</code></p>
|
||
<p>Value
|
||
: Each entry must be any of <code>uv0</code>, <code>uv1</code>, <code>color</code>, <code>position</code>, <code>tangents</code>,
|
||
<code>custom0</code> through <code>custom7</code>.</p>
|
||
<p>Description
|
||
: Lists the vertex attributes required by the material. The <code>position</code>
|
||
attribute is always required and does not need to be specified. The
|
||
<code>tangents</code> attribute is automatically required when selecting any shading
|
||
model that is not <code>unlit</code>. See the shader sections of this document for more
|
||
information on how to access these attributes from the shaders.</p>
|
||
<pre><code class="language-json">material {
|
||
parameters : [
|
||
{
|
||
type : sampler2d,
|
||
name : texture
|
||
},
|
||
],
|
||
requires : [
|
||
uv0,
|
||
custom0
|
||
],
|
||
shadingModel : lit,
|
||
}
|
||
|
||
fragment {
|
||
void material(inout MaterialInputs material) {
|
||
prepareMaterial(material);
|
||
material.baseColor = texture(materialParams_texture, getUV0());
|
||
material.baseColor.rgb *= getCustom0().rgb;
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="vertex-and-attributes-variables"><a class="header" href="#vertex-and-attributes-variables">Vertex and attributes: variables</a></h3>
|
||
<p>Type
|
||
: array of <code>string</code></p>
|
||
<p>Value
|
||
: Up to 4 strings, each must be a valid GLSL identifier.</p>
|
||
<p>Description
|
||
: Defines custom interpolants (or variables) that are output by the material's
|
||
vertex shader. Each entry of the array defines the name of an interpolant.
|
||
The full name in the fragment shader is the name of the interpolant with the
|
||
<code>variable_</code> prefix. For instance, if you declare a variable called
|
||
<code>eyeDirection</code> you can access it in the fragment shader using
|
||
<code>variable_eyeDirection</code>. In the vertex shader, the interpolant name is
|
||
simply a member of the <code>MaterialVertexInputs</code> structure
|
||
(<code>material.eyeDirection</code> in your example). Each interpolant is of type
|
||
<code>float4</code> (<code>vec4</code>) in the shaders.</p>
|
||
<pre><code class="language-json">material {
|
||
name : Skybox,
|
||
parameters : [
|
||
{
|
||
type : samplerCubemap,
|
||
name : skybox
|
||
}
|
||
],
|
||
variables : [
|
||
eyeDirection
|
||
],
|
||
vertexDomain : device,
|
||
depthWrite : false,
|
||
shadingModel : unlit
|
||
}
|
||
|
||
fragment {
|
||
void material(inout MaterialInputs material) {
|
||
prepareMaterial(material);
|
||
float3 sky = texture(materialParams_skybox, variable_eyeDirection.xyz).rgb;
|
||
material.baseColor = vec4(sky, 1.0);
|
||
}
|
||
}
|
||
|
||
vertex {
|
||
void materialVertex(inout MaterialVertexInputs material) {
|
||
float3 p = getPosition().xyz;
|
||
float3 u = mulMat4x4Float3(getViewFromClipMatrix(), p).xyz;
|
||
material.eyeDirection.xyz = mulMat3x3Float3(getWorldFromViewMatrix(), u);
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="vertex-and-attributes-vertexdomain"><a class="header" href="#vertex-and-attributes-vertexdomain">Vertex and attributes: vertexDomain</a></h3>
|
||
<p>Type
|
||
: <code>string</code></p>
|
||
<p>Value
|
||
: Any of <code>object</code>, <code>world</code>, <code>view</code>, <code>device</code>. Defaults to <code>object</code>.</p>
|
||
<p>Description
|
||
: Defines the domain (or coordinate space) of the rendered mesh. The domain
|
||
influences how the vertices are transformed in the vertex shader. The
|
||
possible domains are:</p>
|
||
<pre><code>- **Object**: the vertices are defined in the object (or model) coordinate
|
||
space. The vertices are transformed using the rendered object's
|
||
transform matrix
|
||
- **World**: the vertices are defined in world coordinate space. The
|
||
vertices are not transformed using the rendered object's transform.
|
||
- **View**: the vertices are defined in view (or eye or camera) coordinate
|
||
space. The vertices are not transformed using the rendered object's
|
||
transform.
|
||
- **Device**: the vertices are defined in normalized device (or clip)
|
||
coordinate space. The vertices are not transformed using the rendered
|
||
object's transform.
|
||
</code></pre>
|
||
<pre><code class="language-json">material {
|
||
vertexDomain : device
|
||
}
|
||
</code></pre>
|
||
<h3 id="vertex-and-attributes-interpolation"><a class="header" href="#vertex-and-attributes-interpolation">Vertex and attributes: interpolation</a></h3>
|
||
<p>Type
|
||
: <code>string</code></p>
|
||
<p>Value
|
||
: Any of <code>smooth</code>, <code>flat</code>. Defaults to <code>smooth</code>.</p>
|
||
<p>Description
|
||
: Defines how interpolants (or variables) are interpolated between vertices.
|
||
When this property is set to <code>smooth</code>, a perspective correct interpolation
|
||
is performed on each interpolant. When set to <code>flat</code>, no interpolation is
|
||
performed and all the fragments within a given triangle will be shaded the
|
||
same.</p>
|
||
<pre><code class="language-json">material {
|
||
interpolation : flat
|
||
}
|
||
</code></pre>
|
||
<h3 id="blending-and-transparency-blending"><a class="header" href="#blending-and-transparency-blending">Blending and transparency: blending</a></h3>
|
||
<p>Type
|
||
: <code>string</code></p>
|
||
<p>Value
|
||
: Any of <code>opaque</code>, <code>transparent</code>, <code>fade</code>, <code>add</code>, <code>masked</code>, <code>multiply</code>,
|
||
<code>screen</code>. Defaults to <code>opaque</code>.</p>
|
||
<p>Description
|
||
: Defines how/if the rendered object is blended with the content of the render
|
||
target. The possible blending modes are:</p>
|
||
<pre><code>- **Opaque**: blending is disabled, the alpha channel of the material's
|
||
output is ignored.
|
||
- **Transparent**: blending is enabled. The material's output is alpha
|
||
composited with the render target, using Porter-Duff's `source over`
|
||
rule. This blending mode assumes pre-multiplied alpha.
|
||
- **Fade**: acts as `transparent` but transparency is also applied to
|
||
specular lighting. In `transparent` mode, the material's alpha values
|
||
only applies to diffuse lighting. This blending mode is useful to fade
|
||
lit objects in and out.
|
||
- **Add**: blending is enabled. The material's output is added to the
|
||
content of the render target.
|
||
- **Multiply**: blending is enabled. The material's output is multiplied
|
||
with the content of the render target, darkening the content.
|
||
- **Screen**: blending is enabled. Effectively the opposite of the
|
||
`multiply`, the content of the render target is brightened.
|
||
- **Masked**: blending is disabled. This blending mode enables alpha
|
||
masking. The alpha channel of the material's output defines whether a
|
||
fragment is discarded or not. Additionally, ALPHA_TO_COVERAGE is enabled
|
||
for non-translucent views. See the maskThreshold section for more
|
||
information.
|
||
</code></pre>
|
||
<p>!!! Note When <code>blending</code> is set to <code>masked</code>, alpha to coverage is automatically
|
||
enabled for the material. If this behavior is undesirable, refer to the
|
||
Rasterization: alphaToCoverage section to turn alpha to coverage off using the
|
||
<code>alphaToCoverage</code> property.</p>
|
||
<pre><code class="language-json">material {
|
||
blending : transparent
|
||
}
|
||
</code></pre>
|
||
<h3 id="blending-and-transparency-postlightingblending"><a class="header" href="#blending-and-transparency-postlightingblending">Blending and transparency: postLightingBlending</a></h3>
|
||
<p>Type
|
||
: <code>string</code></p>
|
||
<p>Value
|
||
: Any of <code>opaque</code>, <code>transparent</code>, <code>add</code>. Defaults to <code>transparent</code>.</p>
|
||
<p>Description
|
||
: Defines how the <code>postLightingColor</code> material property is blended with the
|
||
result of the lighting computations. The possible blending modes are:</p>
|
||
<pre><code>- **Opaque**: blending is disabled, the material will output
|
||
`postLightingColor` directly.
|
||
- **Transparent**: blending is enabled. The material's computed color is
|
||
alpha composited with the `postLightingColor`, using Porter-Duff's
|
||
`source over` rule. This blending mode assumes pre-multiplied alpha.
|
||
- **Add**: blending is enabled. The material's computed color is added to
|
||
`postLightingColor`.
|
||
- **Multiply**: blending is enabled. The material's computed color is
|
||
multiplied with `postLightingColor`.
|
||
- **Screen**: blending is enabled. The material's computed color is
|
||
inverted and multiplied with `postLightingColor`, and the result is
|
||
added to the material's computed color.
|
||
</code></pre>
|
||
<pre><code class="language-json">material {
|
||
postLightingBlending : add
|
||
}
|
||
</code></pre>
|
||
<h3 id="blending-and-transparency-transparency"><a class="header" href="#blending-and-transparency-transparency">Blending and transparency: transparency</a></h3>
|
||
<p>Type
|
||
: <code>string</code></p>
|
||
<p>Value
|
||
: Any of <code>default</code>, <code>twoPassesOneSide</code> or <code>twoPassesTwoSides</code>. Defaults to
|
||
<code>default</code>.</p>
|
||
<p>Description
|
||
: Controls how transparent objects are rendered. It is only valid when the
|
||
<code>blending</code> mode is not <code>opaque</code> and <code>refractionMode</code> is <code>none</code>. None of
|
||
these methods can accurately render concave geometry, but in practice they
|
||
are often good enough.</p>
|
||
<p>The three possible transparency modes are: - <code>default</code>: the transparent object
|
||
is rendered normally (as seen in figure [transparencyDefault]), honoring the
|
||
<code>culling</code> mode, etc. - <code>twoPassesOneSide</code>: the transparent object is first
|
||
rendered in the depth buffer, then again in the color buffer, honoring the
|
||
<code>culling</code> mode. This effectively renders only half of the transparent object as
|
||
shown in figure [transparencyTwoPassesOneSide]. - <code>twoPassesTwoSides</code>: the
|
||
transparent object is rendered twice in the color buffer: first with its back
|
||
faces, then with its front faces. This mode lets you render both set of faces
|
||
while reducing or eliminating sorting issues, as shown in figure
|
||
[transparencyTwoPassesTwoSides]. <code>twoPassesTwoSides</code> can be combined with
|
||
<code>doubleSided</code> for better effect.</p>
|
||
<pre><code class="language-json">material {
|
||
transparency : twoPassesOneSide
|
||
}
|
||
</code></pre>
|
||
<p><img src="images/screenshot_transparency_default.png" alt="Figure [transparencyDefault]: This double sided model shows the type of sorting issues transparent objects can be subject to in default mode" /></p>
|
||
<p><img src="images/screenshot_twopasses_oneside.png" alt="Figure [transparencyTwoPassesOneSide]: In twoPassesOneSide mode, only one set of faces is visible and correctly sorted" /></p>
|
||
<p><img src="images/screenshot_twopasses_twosides.png" alt="Figure [transparencyTwoPassesTwoSides]: In twoPassesTwoSides mode, both set of faces are visible and sorting issues are minimized or eliminated" /></p>
|
||
<h3 id="blending-and-transparency-maskthreshold"><a class="header" href="#blending-and-transparency-maskthreshold">Blending and transparency: maskThreshold</a></h3>
|
||
<p>Type
|
||
: <code>number</code></p>
|
||
<p>Value
|
||
: A value between <code>0.0</code> and <code>1.0</code>. Defaults to <code>0.4</code>.</p>
|
||
<p>Description
|
||
: Sets the minimum alpha value a fragment must have to not be discarded when
|
||
the <code>blending</code> mode is set to <code>masked</code>. If the fragment is not discarded,
|
||
its source alpha is set to 1. When the blending mode is not <code>masked</code>, this
|
||
value is ignored. This value can be used to controlled the appearance of
|
||
alpha-masked objects.</p>
|
||
<pre><code class="language-json">material {
|
||
blending : masked,
|
||
maskThreshold : 0.5
|
||
}
|
||
</code></pre>
|
||
<h3 id="blending-and-transparency-refractionmode"><a class="header" href="#blending-and-transparency-refractionmode">Blending and transparency: refractionMode</a></h3>
|
||
<p>Type
|
||
: <code>string</code></p>
|
||
<p>Value
|
||
: Any of <code>none</code>, <code>cubemap</code>, <code>screenspace</code>. Defaults to <code>none</code>.</p>
|
||
<p>Description
|
||
: Activates refraction when set to anything but <code>none</code>. A value of <code>cubemap</code>
|
||
will only use the IBL cubemap as source of refraction, while this is
|
||
significantly more efficient, no scene objects will be refracted, only the
|
||
distant environment encoded in the cubemap. This mode is adequate for an
|
||
object viewer for instance. A value of <code>screenspace</code> will employ the more
|
||
advanced screen-space refraction algorithm which allows opaque objects in
|
||
the scene to be refracted. In <code>cubemap</code> mode, refracted rays are assumed to
|
||
emerge from the center of the object and the <code>thickness</code> parameter is only
|
||
used for computing the absorption, but has no impact on the refraction
|
||
itself. In <code>screenspace</code> mode, refracted rays are assumed to travel parallel
|
||
to the view direction when they exit the refractive medium.</p>
|
||
<pre><code class="language-json">material {
|
||
refractionMode : cubemap,
|
||
}
|
||
</code></pre>
|
||
<h3 id="blending-and-transparency-refractiontype"><a class="header" href="#blending-and-transparency-refractiontype">Blending and transparency: refractionType</a></h3>
|
||
<p>Type
|
||
: <code>string</code></p>
|
||
<p>Value
|
||
: Any of <code>solid</code>, <code>thin</code>. Defaults to <code>solid</code>.</p>
|
||
<p>Description
|
||
: This is only meaningful when <code>refractionMode</code> is set to anything but <code>none</code>.
|
||
<code>refractionType</code> defines the refraction model used. <code>solid</code> is used for
|
||
thick objects such as a crystal ball, an ice cube or as sculpture. <code>thin</code> is
|
||
used for thin objects such as a window, an ornament ball or a soap bubble.
|
||
In <code>solid</code> mode all refracive objects are assumed to be a sphere tangent to
|
||
the entry point and of radius <code>thickness</code>. In <code>thin</code> mode, all refractive
|
||
objects are assumed to be flat and thin and of thickness <code>thickness</code>.</p>
|
||
<pre><code class="language-json">material {
|
||
refractionMode : cubemap,
|
||
refractionType : thin,
|
||
}
|
||
</code></pre>
|
||
<h3 id="rasterization-culling"><a class="header" href="#rasterization-culling">Rasterization: culling</a></h3>
|
||
<p>Type
|
||
: <code>string</code></p>
|
||
<p>Value
|
||
: Any of <code>none</code>, <code>front</code>, <code>back</code>, <code>frontAndBack</code>. Defaults to <code>back</code>.</p>
|
||
<p>Description
|
||
: Defines which triangles should be culled: none, front-facing triangles,
|
||
back-facing triangles or all.</p>
|
||
<pre><code class="language-json">material {
|
||
culling : none
|
||
}
|
||
</code></pre>
|
||
<h3 id="rasterization-colorwrite"><a class="header" href="#rasterization-colorwrite">Rasterization: colorWrite</a></h3>
|
||
<p>Type
|
||
: <code>boolean</code></p>
|
||
<p>Value
|
||
: <code>true</code> or <code>false</code>. Defaults to <code>true</code>.</p>
|
||
<p>Description
|
||
: Enables or disables writes to the color buffer.</p>
|
||
<pre><code class="language-json">material {
|
||
colorWrite : false
|
||
}
|
||
</code></pre>
|
||
<h3 id="rasterization-depthwrite"><a class="header" href="#rasterization-depthwrite">Rasterization: depthWrite</a></h3>
|
||
<p>Type
|
||
: <code>boolean</code></p>
|
||
<p>Value
|
||
: <code>true</code> or <code>false</code>. Defaults to <code>true</code> for opaque materials, <code>false</code> for
|
||
transparent materials.</p>
|
||
<p>Description
|
||
: Enables or disables writes to the depth buffer.</p>
|
||
<pre><code class="language-json">material {
|
||
depthWrite : false
|
||
}
|
||
</code></pre>
|
||
<h3 id="rasterization-depthculling"><a class="header" href="#rasterization-depthculling">Rasterization: depthCulling</a></h3>
|
||
<p>Type
|
||
: <code>boolean</code></p>
|
||
<p>Value
|
||
: <code>true</code> or <code>false</code>. Defaults to <code>true</code>.</p>
|
||
<p>Description
|
||
: Enables or disables depth testing. When depth testing is disabled, an object
|
||
rendered with this material will always appear on top of other opaque
|
||
objects.</p>
|
||
<pre><code class="language-json">material {
|
||
depthCulling : false
|
||
}
|
||
</code></pre>
|
||
<h3 id="rasterization-doublesided"><a class="header" href="#rasterization-doublesided">Rasterization: doubleSided</a></h3>
|
||
<p>Type
|
||
: <code>boolean</code></p>
|
||
<p>Value
|
||
: <code>true</code> or <code>false</code>. Defaults to <code>false</code>.</p>
|
||
<p>Description
|
||
: Enables two-sided rendering and its capability to be toggled at run time.
|
||
When set to <code>true</code>, <code>culling</code> is automatically set to <code>none</code>; if the
|
||
triangle is back-facing, the triangle's normal is flipped to become
|
||
front-facing. When explicitly set to <code>false</code>, this allows the
|
||
double-sidedness to be toggled at run time.</p>
|
||
<pre><code class="language-json">material {
|
||
name : "Double sided material",
|
||
shadingModel : lit,
|
||
doubleSided : true
|
||
}
|
||
|
||
fragment {
|
||
void material(inout MaterialInputs material) {
|
||
prepareMaterial(material);
|
||
material.baseColor = materialParams.albedo;
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="rasterization-alphatocoverage"><a class="header" href="#rasterization-alphatocoverage">Rasterization: alphaToCoverage</a></h3>
|
||
<p>Type
|
||
: <code>boolean</code></p>
|
||
<p>Value
|
||
: <code>true</code> or <code>false</code>. Defaults to <code>false</code>.</p>
|
||
<p>Description
|
||
: Enables or disables alpha to coverage. When alpha to coverage is enabled,
|
||
the coverage of fragment is derived from its alpha. This property is only
|
||
meaningful when MSAA is enabled. Note: setting <code>blending</code> to <code>masked</code>
|
||
automatically enables alpha to coverage. If this is not desired, you can
|
||
override this behavior by setting alpha to coverage to false as in the
|
||
example below.</p>
|
||
<pre><code class="language-json">material {
|
||
name : "Alpha to coverage",
|
||
shadingModel : lit,
|
||
blending : masked,
|
||
alphaToCoverage : false
|
||
}
|
||
|
||
fragment {
|
||
void material(inout MaterialInputs material) {
|
||
prepareMaterial(material);
|
||
material.baseColor = materialParams.albedo;
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="lighting-reflections"><a class="header" href="#lighting-reflections">Lighting: reflections</a></h3>
|
||
<p>Type
|
||
: <code>string</code></p>
|
||
<p>Value
|
||
: <code>default</code> or <code>screenspace</code>. Defaults to <code>default</code>.</p>
|
||
<p>Description
|
||
: Controls the source of specular reflections for this material. When this
|
||
property is set to <code>default</code>, reflections only come image-based lights. When
|
||
this property is set to <code>screenspace</code>, reflections come from the screen
|
||
space's color buffer in addition to image-based lights.</p>
|
||
<pre><code class="language-json">material {
|
||
name : "Glossy metal",
|
||
reflections : screenspace
|
||
}
|
||
</code></pre>
|
||
<h3 id="lighting-shadowmultiplier"><a class="header" href="#lighting-shadowmultiplier">Lighting: shadowMultiplier</a></h3>
|
||
<p>Type
|
||
: <code>boolean</code></p>
|
||
<p>Value
|
||
: <code>true</code> or <code>false</code>. Defaults to <code>false</code>.</p>
|
||
<p>Description
|
||
: Only available in the <code>unlit</code> shading model. If this property is enabled,
|
||
the final color computed by the material is multiplied by the shadowing
|
||
factor (or visibility). This allows to create transparent shadow-receiving
|
||
objects (for instance an invisible ground plane in AR). This is only
|
||
supported with shadows from directional lights.</p>
|
||
<pre><code class="language-json">material {
|
||
name : "Invisible shadow plane",
|
||
shadingModel : unlit,
|
||
shadowMultiplier : true,
|
||
blending : transparent
|
||
}
|
||
|
||
fragment {
|
||
void material(inout MaterialInputs material) {
|
||
prepareMaterial(material);
|
||
// baseColor defines the color and opacity of the final shadow
|
||
material.baseColor = vec4(0.0, 0.0, 0.0, 0.7);
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="lighting-transparentshadow"><a class="header" href="#lighting-transparentshadow">Lighting: transparentShadow</a></h3>
|
||
<p>Type
|
||
: <code>boolean</code></p>
|
||
<p>Value
|
||
: <code>true</code> or <code>false</code>. Defaults to <code>false</code>.</p>
|
||
<p>Description
|
||
: Enables transparent shadows on this material. When this feature is enabled,
|
||
Filament emulates transparent shadows using a dithering pattern: they work
|
||
best with variance shadow maps (VSM) and blurring enabled. The opacity of
|
||
the shadow derives directly from the alpha channel of the material's
|
||
<code>baseColor</code> property. Transparent shadows can be enabled on opaque objects,
|
||
making them compatible with refractive/transmissive objects that are
|
||
otherwise considered opaque.</p>
|
||
<pre><code class="language-json">material {
|
||
name : "Clear plastic with stickers",
|
||
transparentShadow : true,
|
||
blending : transparent,
|
||
// ...
|
||
}
|
||
|
||
fragment {
|
||
void material(inout MaterialInputs material) {
|
||
prepareMaterial(material);
|
||
material.baseColor = texture(materialParams_baseColor, getUV0());
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><img src="images/screenshot_transparent_shadows.jpg" alt="Figure [transparentShadow]: Objects rendered with transparent shadows and blurry VSM with a radius of 4. Model Bottle of Water by T-Art." /></p>
|
||
<h3 id="lighting-clearcoatiorchange"><a class="header" href="#lighting-clearcoatiorchange">Lighting: clearCoatIorChange</a></h3>
|
||
<p>Type
|
||
: <code>boolean</code></p>
|
||
<p>Value
|
||
: <code>true</code> or <code>false</code>. Defaults to <code>true</code>.</p>
|
||
<p>Description
|
||
: When adding a clear coat layer, the change in index of refraction (IoR) is
|
||
taken into account to modify the specular color of the base layer. This
|
||
appears to darken <code>baseColor</code>. When this effect is disabled, <code>baseColor</code> is
|
||
left unmodified. See figure [clearCoatIorChange] for an example of how this
|
||
property can affect a red metallic base layer.</p>
|
||
<pre><code class="language-json">material {
|
||
clearCoatIorChange : false
|
||
}
|
||
</code></pre>
|
||
<p><img src="images/screenshot_clear_coat_ior_change.jpg" alt="Figure [clearCoatIorChange]: The same rough metallic ball with a clear coat layer rendered with clearCoatIorChange enabled (left) and disabled (right)." /></p>
|
||
<h3 id="lighting-multibounceambientocclusion"><a class="header" href="#lighting-multibounceambientocclusion">Lighting: multiBounceAmbientOcclusion</a></h3>
|
||
<p>Type
|
||
: <code>boolean</code></p>
|
||
<p>Value
|
||
: <code>true</code> or <code>false</code>. Defaults to <code>false</code> on mobile, <code>true</code> on desktop.</p>
|
||
<p>Description
|
||
: Multi-bounce ambient occlusion takes into account interreflections when
|
||
applying ambient occlusion to image-based lighting. Turning this feature on
|
||
avoids over-darkening occluded areas. It also takes the surface color into
|
||
account to generate colored ambient occlusion. Figure [multiBounceAO]
|
||
compares the ambient occlusion term of a surface with and without
|
||
multi-bounce ambient occlusion. Notice how multi-bounce ambient occlusion
|
||
introduces color in the occluded areas. Figure [multiBounceAOAnimated]
|
||
toggles between multi-bounce ambient occlusion on and off on a lit brick
|
||
material to highlight the effects of this property.</p>
|
||
<pre><code class="language-json">material {
|
||
multiBounceAmbientOcclusion : true
|
||
}
|
||
</code></pre>
|
||
<p><img src="images/screenshot_multi_bounce_ao.jpg" alt="Figure [multiBounceAO]: Brick texture amient occlusion map rendered with multi-bounce ambient occclusion enabled (left) and disabled (right)." /></p>
|
||
<p><img src="images/screenshot_multi_bounce_ao.gif" alt="Figure [multiBounceAOAnimated]: Brick texture rendered with multi-bounce ambient occclusion enabled and disabled." /></p>
|
||
<h3 id="lighting-specularambientocclusion"><a class="header" href="#lighting-specularambientocclusion">Lighting: specularAmbientOcclusion</a></h3>
|
||
<p>Type
|
||
: <code>string</code></p>
|
||
<p>Value
|
||
: <code>none</code>, <code>simple</code> or <code>bentNormals</code>. Defaults to <code>none</code> on mobile, <code>simple</code> on
|
||
desktop. For compatibility reasons, <code>true</code> and <code>false</code> are also accepted and
|
||
map respectively to <code>simple</code> and <code>none</code>.</p>
|
||
<p>Description
|
||
: Static ambient occlusion maps and dynamic ambient occlusion (SSAO, etc.)
|
||
apply to diffuse indirect lighting. When setting this property to other than
|
||
<code>none</code>, a new ambient occlusion term is derived from the surface roughness
|
||
and applied to specular indirect lighting. This effect helps remove unwanted
|
||
specular reflections as shown in figure [specularAO]. When this value is set
|
||
to <code>simple</code>, Filament uses a cheap but approximate method of computing the
|
||
specular ambient occlusion term. If this value is set to <code>bentNormals</code>,
|
||
Filament will use a much more accurate but much more expensive method.</p>
|
||
<pre><code class="language-json">material {
|
||
specularAmbientOcclusion : simple
|
||
}
|
||
</code></pre>
|
||
<p><img src="images/screenshot_specular_ao.gif" alt="Figure [specularAO]: Comparison of specular ambient occlusion on and off. The effect is particularly visible under the hose." /></p>
|
||
<h3 id="anti-aliasing-specularantialiasing"><a class="header" href="#anti-aliasing-specularantialiasing">Anti-aliasing: specularAntiAliasing</a></h3>
|
||
<p>Type
|
||
: <code>boolean</code></p>
|
||
<p>Value
|
||
: <code>true</code> or <code>false</code>. Defaults to <code>false</code>.</p>
|
||
<p>Description
|
||
: Reduces specular aliasing and preserves the shape of specular highlights as
|
||
an object moves away from the camera. This anti-aliasing solution is
|
||
particularly effective on glossy materials (low roughness) but increases the
|
||
cost of the material. The strength of the anti-aliasing effect can be
|
||
controlled using two other properties: <code>specularAntiAliasingVariance</code> and
|
||
<code>specularAntiAliasingThreshold</code>.</p>
|
||
<pre><code class="language-json">material {
|
||
specularAntiAliasing : true
|
||
}
|
||
</code></pre>
|
||
<h3 id="anti-aliasing-specularantialiasingvariance"><a class="header" href="#anti-aliasing-specularantialiasingvariance">Anti-aliasing: specularAntiAliasingVariance</a></h3>
|
||
<p>Type
|
||
: <code>float</code></p>
|
||
<p>Value
|
||
: A value between 0 and 1, set to 0.15 by default.</p>
|
||
<p>Description
|
||
: Sets the screen space variance of the filter kernel used when applying
|
||
specular anti-aliasing. Higher values will increase the effect of the filter
|
||
but may increase roughness in unwanted areas.</p>
|
||
<pre><code class="language-json">material {
|
||
specularAntiAliasingVariance : 0.2
|
||
}
|
||
</code></pre>
|
||
<h3 id="anti-aliasing-specularantialiasingthreshold"><a class="header" href="#anti-aliasing-specularantialiasingthreshold">Anti-aliasing: specularAntiAliasingThreshold</a></h3>
|
||
<p>Type
|
||
: <code>float</code></p>
|
||
<p>Value
|
||
: A value between 0 and 1, set to 0.2 by default.</p>
|
||
<p>Description
|
||
: Sets the clamping threshold used to suppress estimation errors when applying
|
||
specular anti-aliasing. When set to 0, specular anti-aliasing is disabled.</p>
|
||
<pre><code class="language-json">material {
|
||
specularAntiAliasingThreshold : 0.1
|
||
}
|
||
</code></pre>
|
||
<h3 id="shading-customsurfaceshading"><a class="header" href="#shading-customsurfaceshading">Shading: customSurfaceShading</a></h3>
|
||
<p>Type
|
||
: <code>bool</code></p>
|
||
<p>Value
|
||
: <code>true</code> or <code>false</code>. Defaults to <code>false</code>.</p>
|
||
<p>Description
|
||
: Enables custom surface shading when set to true. When surface shading is
|
||
enabled, the fragment shader must provide an extra function that will be
|
||
invoked for every light in the scene that may influence the current
|
||
fragment. Please refer to the Custom surface shading section below for more
|
||
information.</p>
|
||
<pre><code class="language-json">material {
|
||
customSurfaceShading : true
|
||
}
|
||
</code></pre>
|
||
<h2 id="vertex-block"><a class="header" href="#vertex-block">Vertex block</a></h2>
|
||
<p>The vertex block is optional and can be used to control the vertex shading stage
|
||
of the material. The vertex block must contain valid
|
||
<a href="https://www.khronos.org/registry/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf">ESSL 3.0</a>
|
||
code (the version of GLSL supported in OpenGL ES 3.0). You are free to create
|
||
multiple functions inside the vertex block but you <strong>must</strong> declare the
|
||
<code>materialVertex</code> function:</p>
|
||
<pre><code class="language-glsl">vertex {
|
||
void materialVertex(inout MaterialVertexInputs material) {
|
||
// vertex shading code
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>This function will be invoked automatically at runtime by the shading system and
|
||
gives you the ability to read and modify material properties using the
|
||
<code>MaterialVertexInputs</code> structure. This full definition of the structure can be
|
||
found in the Material vertex inputs section.</p>
|
||
<p>You can use this structure to compute your custom variables/interpolants or to
|
||
modify the value of the attributes. For instance, the following vertex blocks
|
||
modifies both the color and the UV coordinates of the vertex over time:</p>
|
||
<pre><code class="language-glsl">material {
|
||
requires : [uv0, color]
|
||
}
|
||
vertex {
|
||
void materialVertex(inout MaterialVertexInputs material) {
|
||
material.color *= sin(getUserTime().x);
|
||
material.uv0 *= sin(getUserTime().x);
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>In addition to the <code>MaterialVertexInputs</code> structure, your vertex shading code
|
||
can use all the public APIs listed in the Shader public APIs section.</p>
|
||
<h3 id="material-vertex-inputs"><a class="header" href="#material-vertex-inputs">Material vertex inputs</a></h3>
|
||
<pre><code class="language-glsl">struct MaterialVertexInputs {
|
||
float4 color; // if the color attribute is required
|
||
float2 uv0; // if the uv0 attribute is required
|
||
float2 uv1; // if the uv1 attribute is required
|
||
float3 worldNormal; // only if the shading model is not unlit
|
||
float4 worldPosition; // always available (see note below about world-space)
|
||
|
||
mat4 clipSpaceTransform; // default: identity, transforms the clip-space position, only available for `vertexDomain:device`
|
||
|
||
// variable* names are replaced with actual names
|
||
float4 variable0; // if 1 or more variables is defined
|
||
float4 variable1; // if 2 or more variables is defined
|
||
float4 variable2; // if 3 or more variables is defined
|
||
float4 variable3; // if 4 or more variables is defined
|
||
};
|
||
</code></pre>
|
||
<p>!!! TIP: worldPosition To achieve good precision, the <code>worldPosition</code> coordinate
|
||
in the vertex shader is shifted by the camera position. To get the true
|
||
world-space position, users can use <code>getUserWorldPosition()</code>, however be aware
|
||
that the true world-position might not be able to fit in a <code>float</code> or might be
|
||
represented with severely reduced precision.</p>
|
||
<p>!!! TIP: UV attributes By default the vertex shader of a material will flip the
|
||
Y coordinate of the UV attributes of the current mesh: <code>material.uv0 = vec2(mesh_uv0.x, 1.0 - mesh_uv0.y)</code>. You can control this behavior using the
|
||
<code>flipUV</code> property and setting it to <code>false</code>.</p>
|
||
<h3 id="custom-vertex-attributes"><a class="header" href="#custom-vertex-attributes">Custom vertex attributes</a></h3>
|
||
<p>You can use up to 8 custom vertex attributes, all of type <code>float4</code>. These
|
||
attributes can be accessed using the vertex block shader functions
|
||
<code>getCustom0()</code> to <code>getCustom7()</code>. However, before using custom attributes, you
|
||
<em>must</em> declare those attributes as required in the <code>requires</code> property of the
|
||
material:</p>
|
||
<pre><code class="language-json">material {
|
||
requires : [
|
||
custom0,
|
||
custom1,
|
||
custom2
|
||
]
|
||
}
|
||
</code></pre>
|
||
<h2 id="fragment-block"><a class="header" href="#fragment-block">Fragment block</a></h2>
|
||
<p>The fragment block must be used to control the fragment shading stage of the
|
||
material. The fragment block must contain valid
|
||
<a href="https://www.khronos.org/registry/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf">ESSL 3.0</a>
|
||
code (the version of GLSL supported in OpenGL ES 3.0). You are free to create
|
||
multiple functions inside the fragment block but you <strong>must</strong> declare the
|
||
<code>material</code> function:</p>
|
||
<pre><code class="language-glsl">fragment {
|
||
void material(inout MaterialInputs material) {
|
||
prepareMaterial(material);
|
||
// fragment shading code
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>This function will be invoked automatically at runtime by the shading system and
|
||
gives you the ability to read and modify material properties using the
|
||
<code>MaterialInputs</code> structure. This full definition of the structure can be found
|
||
in the Material fragment inputs section. The full definition of the various
|
||
members of the structure can be found in the Material models section of this
|
||
document.</p>
|
||
<p>The goal of the <code>material()</code> function is to compute the material properties
|
||
specific to the selected shading model. For instance, here is a fragment block
|
||
that creates a glossy red metal using the standard lit shading model:</p>
|
||
<pre><code class="language-glsl">fragment {
|
||
void material(inout MaterialInputs material) {
|
||
prepareMaterial(material);
|
||
material.baseColor.rgb = vec3(1.0, 0.0, 0.0);
|
||
material.metallic = 1.0;
|
||
material.roughness = 0.0;
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="preparematerial-function"><a class="header" href="#preparematerial-function">prepareMaterial function</a></h3>
|
||
<p>Note that you <strong>must</strong> call <code>prepareMaterial(material)</code> before exiting the
|
||
<code>material()</code> function. This <code>prepareMaterial</code> function sets up the internal
|
||
state of the material model. Some of the APIs described in the Fragment APIs
|
||
section - like <code>shading_normal</code> for instance - can only be accessed <em>after</em>
|
||
invoking <code>prepareMaterial()</code>.</p>
|
||
<p>It is also important to remember that the <code>normal</code> property - as described in
|
||
the Material fragment inputs section - only has an effect when modified <em>before</em>
|
||
calling <code>prepareMaterial()</code>. Here is an example of a fragment shader that
|
||
properly modifies the <code>normal</code> property to implement a glossy red plastic with
|
||
bump mapping:</p>
|
||
<pre><code class="language-glsl">fragment {
|
||
void material(inout MaterialInputs material) {
|
||
// fetch the normal in tangent space
|
||
vec3 normal = texture(materialParams_normalMap, getUV0()).xyz;
|
||
material.normal = normal * 2.0 - 1.0;
|
||
|
||
// prepare the material
|
||
prepareMaterial(material);
|
||
|
||
// from now on, shading_normal, etc. can be accessed
|
||
material.baseColor.rgb = vec3(1.0, 0.0, 0.0);
|
||
material.metallic = 0.0;
|
||
material.roughness = 1.0;
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="material-fragment-inputs"><a class="header" href="#material-fragment-inputs">Material fragment inputs</a></h3>
|
||
<pre><code class="language-glsl">struct MaterialInputs {
|
||
float4 baseColor; // default: float4(1.0)
|
||
float4 emissive; // default: float4(0.0, 0.0, 0.0, 1.0)
|
||
float4 postLightingColor; // default: float4(0.0)
|
||
|
||
// no other field is available with the unlit shading model
|
||
float roughness; // default: 1.0
|
||
float metallic; // default: 0.0, not available with cloth or specularGlossiness
|
||
float reflectance; // default: 0.5, not available with cloth or specularGlossiness
|
||
float ambientOcclusion; // default: 0.0
|
||
|
||
// not available when the shading model is subsurface or cloth
|
||
float3 sheenColor; // default: float3(0.0)
|
||
float sheenRoughness; // default: 0.0
|
||
float clearCoat; // default: 1.0
|
||
float clearCoatRoughness; // default: 0.0
|
||
float3 clearCoatNormal; // default: float3(0.0, 0.0, 1.0)
|
||
float anisotropy; // default: 0.0
|
||
float3 anisotropyDirection; // default: float3(1.0, 0.0, 0.0)
|
||
|
||
// only available when the shading model is subsurface or refraction is enabled
|
||
float thickness; // default: 0.5
|
||
|
||
// only available when the shading model is subsurface
|
||
float subsurfacePower; // default: 12.234
|
||
float3 subsurfaceColor; // default: float3(1.0)
|
||
|
||
// only available when the shading model is cloth
|
||
float3 sheenColor; // default: sqrt(baseColor)
|
||
float3 subsurfaceColor; // default: float3(0.0)
|
||
|
||
// only available when the shading model is specularGlossiness
|
||
float3 specularColor; // default: float3(0.0)
|
||
float glossiness; // default: 0.0
|
||
|
||
// not available when the shading model is unlit
|
||
// must be set before calling prepareMaterial()
|
||
float3 normal; // default: float3(0.0, 0.0, 1.0)
|
||
|
||
// only available when refraction is enabled
|
||
float transmission; // default: 1.0
|
||
float3 absorption; // default float3(0.0, 0.0, 0.0)
|
||
float ior; // default: 1.5
|
||
float microThickness; // default: 0.0, not available with refractionType "solid"
|
||
}
|
||
</code></pre>
|
||
<h3 id="custom-surface-shading"><a class="header" href="#custom-surface-shading">Custom surface shading</a></h3>
|
||
<p>When <code>customSurfaceShading</code> is set to <code>true</code> in the material block, the fragment
|
||
block <strong>must</strong> declare and implement the <code>surfaceShading</code> function:</p>
|
||
<pre><code class="language-glsl">fragment {
|
||
void material(inout MaterialInputs material) {
|
||
prepareMaterial(material);
|
||
// prepare material inputs
|
||
}
|
||
|
||
vec3 surfaceShading(
|
||
const MaterialInputs materialInputs,
|
||
const ShadingData shadingData,
|
||
const LightData lightData
|
||
) {
|
||
return vec3(1.0); // output of custom lighting
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>This function will be invoked for every light (directional, spot or point) in
|
||
the scene that may influence the current fragment. The <code>surfaceShading</code> is
|
||
invoked with 3 sets of data:</p>
|
||
<ul>
|
||
<li><code>MaterialInputs</code>, as described in the Material fragment inputs section and
|
||
prepared in the <code>material</code> function explained above</li>
|
||
<li><code>ShadingData</code>, a structure containing values derived from <code>MaterialInputs</code>
|
||
(see below)</li>
|
||
<li><code>LightData</code>, a structure containing values specific to the light being
|
||
currently evaluated (see below)</li>
|
||
</ul>
|
||
<p>The <code>surfaceShading</code> function must return an RGB color in linear sRGB. Alpha
|
||
blending and alpha masking are handled outside of this function and must
|
||
therefore be ignored.</p>
|
||
<p>!!! Note: About shadowed fragments The <code>surfaceShading</code> function is invoked even
|
||
when a fragment is known to be fully in the shadow of the current light
|
||
(<code>lightData.NdotL <= 0.0</code> or <code>lightData.visibility <= 0.0</code>). This gives more
|
||
flexibility to the <code>surfaceShading</code> function as it provides a simple way to
|
||
handle constant ambient lighting for instance.</p>
|
||
<p>!!! Warning: Shading models Custom surface shading only works with the <code>lit</code>
|
||
shading model. Attempting to use any other model will result in an error.</p>
|
||
<h4 id="shading-data-structure"><a class="header" href="#shading-data-structure">Shading data structure</a></h4>
|
||
<pre><code class="language-glsl">struct ShadingData {
|
||
// The material's diffuse color, as derived from baseColor and metallic.
|
||
// This color is pre-multiplied by alpha and in the linear sRGB color space.
|
||
vec3 diffuseColor;
|
||
|
||
// The material's specular color, as derived from baseColor and metallic.
|
||
// This color is pre-multiplied by alpha and in the linear sRGB color space.
|
||
vec3 f0;
|
||
|
||
// The perceptual roughness is the roughness value set in MaterialInputs,
|
||
// with extra processing:
|
||
// - Clamped to safe values
|
||
// - Filtered if specularAntiAliasing is enabled
|
||
// This value is between 0.0 and 1.0.
|
||
float perceptualRoughness;
|
||
|
||
// The roughness value expected by BRDFs. This value is the square of
|
||
// perceptualRoughness. This value is between 0.0 and 1.0.
|
||
float roughness;
|
||
};
|
||
</code></pre>
|
||
<h4 id="light-data-structure"><a class="header" href="#light-data-structure">Light data structure</a></h4>
|
||
<pre><code class="language-glsl">struct LightData {
|
||
// The color (.rgb) and pre-exposed intensity (.w) of the light.
|
||
// The color is an RGB value in the linear sRGB color space.
|
||
// The pre-exposed intensity is the intensity of the light multiplied by
|
||
// the camera's exposure value.
|
||
vec4 colorIntensity;
|
||
|
||
// The normalized light vector, in world space (direction from the
|
||
// current fragment's position to the light).
|
||
vec3 l;
|
||
|
||
// The dot product of the shading normal (with normal mapping applied)
|
||
// and the light vector. This value is equal to the result of
|
||
// saturate(dot(getWorldSpaceNormal(), lightData.l)).
|
||
// This value is always between 0.0 and 1.0. When the value is <= 0.0,
|
||
// the current fragment is not visible from the light and lighting
|
||
// computations can be skipped.
|
||
float NdotL;
|
||
|
||
// The position of the light in world space.
|
||
vec3 worldPosition;
|
||
|
||
// Attenuation of the light based on the distance from the current
|
||
// fragment to the light in world space. This value between 0.0 and 1.0
|
||
// is computed differently for each type of light (it's always 1.0 for
|
||
// directional lights).
|
||
float attenuation;
|
||
|
||
// Visibility factor computed from shadow maps or other occlusion data
|
||
// specific to the light being evaluated. This value is between 0.0 and
|
||
// 1.0.
|
||
float visibility;
|
||
};
|
||
</code></pre>
|
||
<h4 id="example-1"><a class="header" href="#example-1">Example</a></h4>
|
||
<p>The material below shows how to use custom surface shading to implement a
|
||
simplified toon shader:</p>
|
||
<pre><code class="language-glsl">material {
|
||
name : Toon,
|
||
shadingModel : lit,
|
||
parameters : [
|
||
{
|
||
type : float3,
|
||
name : baseColor
|
||
}
|
||
],
|
||
customSurfaceShading : true
|
||
}
|
||
|
||
fragment {
|
||
void material(inout MaterialInputs material) {
|
||
prepareMaterial(material);
|
||
material.baseColor.rgb = materialParams.baseColor;
|
||
}
|
||
|
||
vec3 surfaceShading(
|
||
const MaterialInputs materialInputs,
|
||
const ShadingData shadingData,
|
||
const LightData lightData
|
||
) {
|
||
// Number of visible shade transitions
|
||
const float shades = 5.0;
|
||
// Ambient intensity
|
||
const float ambient = 0.1;
|
||
|
||
float toon = max(ceil(lightData.NdotL * shades) / shades, ambient);
|
||
|
||
// Shadowing and attenuation
|
||
toon *= lightData.visibility * lightData.attenuation;
|
||
|
||
// Color and intensity
|
||
vec3 light = lightData.colorIntensity.rgb * lightData.colorIntensity.w;
|
||
|
||
return shadingData.diffuseColor * light * toon;
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>The result can be seen in figure [toonShading].</p>
|
||
<p><img src="images/screenshot_toon_shading.png" alt="Figure [toonShading]: simple toon shading implemented with custom surface shading" /></p>
|
||
<h2 id="shader-public-apis"><a class="header" href="#shader-public-apis">Shader public APIs</a></h2>
|
||
<h3 id="types"><a class="header" href="#types">Types</a></h3>
|
||
<p>While GLSL types can be used directly (<code>vec4</code> or <code>mat4</code>) we recommend the use of
|
||
the following type aliases:</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: left">Name</th><th style="text-align: center">GLSL type</th><th style="text-align: left">Description</th></tr></thead><tbody>
|
||
<tr><td style="text-align: left"><strong>bool2</strong></td><td style="text-align: center">bvec2</td><td style="text-align: left">A vector of 2 booleans</td></tr>
|
||
<tr><td style="text-align: left"><strong>bool3</strong></td><td style="text-align: center">bvec3</td><td style="text-align: left">A vector of 3 booleans</td></tr>
|
||
<tr><td style="text-align: left"><strong>bool4</strong></td><td style="text-align: center">bvec4</td><td style="text-align: left">A vector of 4 booleans</td></tr>
|
||
<tr><td style="text-align: left"><strong>int2</strong></td><td style="text-align: center">ivec2</td><td style="text-align: left">A vector of 2 integers</td></tr>
|
||
<tr><td style="text-align: left"><strong>int3</strong></td><td style="text-align: center">ivec3</td><td style="text-align: left">A vector of 3 integers</td></tr>
|
||
<tr><td style="text-align: left"><strong>int4</strong></td><td style="text-align: center">ivec4</td><td style="text-align: left">A vector of 4 integers</td></tr>
|
||
<tr><td style="text-align: left"><strong>uint2</strong></td><td style="text-align: center">uvec2</td><td style="text-align: left">A vector of 2 unsigned integers</td></tr>
|
||
<tr><td style="text-align: left"><strong>uint3</strong></td><td style="text-align: center">uvec3</td><td style="text-align: left">A vector of 3 unsigned integers</td></tr>
|
||
<tr><td style="text-align: left"><strong>uint4</strong></td><td style="text-align: center">uvec4</td><td style="text-align: left">A vector of 4 unsigned integers</td></tr>
|
||
<tr><td style="text-align: left"><strong>float2</strong></td><td style="text-align: center">float2</td><td style="text-align: left">A vector of 2 floats</td></tr>
|
||
<tr><td style="text-align: left"><strong>float3</strong></td><td style="text-align: center">float3</td><td style="text-align: left">A vector of 3 floats</td></tr>
|
||
<tr><td style="text-align: left"><strong>float4</strong></td><td style="text-align: center">float4</td><td style="text-align: left">A vector of 4 floats</td></tr>
|
||
<tr><td style="text-align: left"><strong>float4x4</strong></td><td style="text-align: center">mat4</td><td style="text-align: left">A 4x4 float matrix</td></tr>
|
||
<tr><td style="text-align: left"><strong>float3x3</strong></td><td style="text-align: center">mat3</td><td style="text-align: left">A 3x3 float matrix</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="math"><a class="header" href="#math">Math</a></h3>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: left">Name</th><th style="text-align: center">Type</th><th style="text-align: left">Description</th></tr></thead><tbody>
|
||
<tr><td style="text-align: left"><strong>PI</strong></td><td style="text-align: center">float</td><td style="text-align: left">A constant that represent \(\pi\)</td></tr>
|
||
<tr><td style="text-align: left"><strong>HALF_PI</strong></td><td style="text-align: center">float</td><td style="text-align: left">A constant that represent \(\frac{\pi}{2}\)</td></tr>
|
||
<tr><td style="text-align: left"><strong>saturate(float x)</strong></td><td style="text-align: center">float</td><td style="text-align: left">Clamps the specified value between 0.0 and 1.0</td></tr>
|
||
<tr><td style="text-align: left"><strong>pow5(float x)</strong></td><td style="text-align: center">float</td><td style="text-align: left">Computes \(x^5\)</td></tr>
|
||
<tr><td style="text-align: left"><strong>sq(float x)</strong></td><td style="text-align: center">float</td><td style="text-align: left">Computes \(x^2\)</td></tr>
|
||
<tr><td style="text-align: left"><strong>max3(float3 v)</strong></td><td style="text-align: center">float</td><td style="text-align: left">Returns the maximum value of the specified <code>float3</code></td></tr>
|
||
<tr><td style="text-align: left"><strong>mulMat4x4Float3(float4x4 m, float3 v)</strong></td><td style="text-align: center">float4</td><td style="text-align: left">Returns \(m * v\)</td></tr>
|
||
<tr><td style="text-align: left"><strong>mulMat3x3Float3(float4x4 m, float3 v)</strong></td><td style="text-align: center">float4</td><td style="text-align: left">Returns \(m * v\)+</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="matrices"><a class="header" href="#matrices">Matrices</a></h3>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: left">Name</th><th style="text-align: center">Type</th><th style="text-align: left">Description</th></tr></thead><tbody>
|
||
<tr><td style="text-align: left"><strong>getViewFromWorldMatrix()</strong></td><td style="text-align: center">float4x4</td><td style="text-align: left">Matrix that converts from world space to view/eye space</td></tr>
|
||
<tr><td style="text-align: left"><strong>getWorldFromViewMatrix()</strong></td><td style="text-align: center">float4x4</td><td style="text-align: left">Matrix that converts from view/eye space to world space</td></tr>
|
||
<tr><td style="text-align: left"><strong>getClipFromViewMatrix()</strong></td><td style="text-align: center">float4x4</td><td style="text-align: left">Matrix that converts from view/eye space to clip (NDC) space</td></tr>
|
||
<tr><td style="text-align: left"><strong>getViewFromClipMatrix()</strong></td><td style="text-align: center">float4x4</td><td style="text-align: left">Matrix that converts from clip (NDC) space to view/eye space</td></tr>
|
||
<tr><td style="text-align: left"><strong>getClipFromWorldMatrix()</strong></td><td style="text-align: center">float4x4</td><td style="text-align: left">Matrix that converts from world to clip (NDC) space</td></tr>
|
||
<tr><td style="text-align: left"><strong>getWorldFromClipMatrix()</strong></td><td style="text-align: center">float4x4</td><td style="text-align: left">Matrix that converts from clip (NDC) space to world space</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="frame-constants"><a class="header" href="#frame-constants">Frame constants</a></h3>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: left">Name</th><th style="text-align: center">Type</th><th style="text-align: left">Description</th></tr></thead><tbody>
|
||
<tr><td style="text-align: left"><strong>getResolution()</strong></td><td style="text-align: center">float4</td><td style="text-align: left">Dimensions of the view's effective (physical) viewport in pixels: <code>width</code>, <code>height</code>, <code>1 / width</code>, <code>1 / height</code>. This might be different from <code>View::getViewport()</code> for instance because of added rendering guard-bands.</td></tr>
|
||
<tr><td style="text-align: left"><strong>getWorldCameraPosition()</strong></td><td style="text-align: center">float3</td><td style="text-align: left">Position of the camera/eye in world space (see note below)</td></tr>
|
||
<tr><td style="text-align: left"><strong>getWorldOffset()</strong></td><td style="text-align: center">float3</td><td style="text-align: left">[deprecated] The shift required to obtain API-level world space. Use getUserWorldPosition() instead</td></tr>
|
||
<tr><td style="text-align: left"><strong>getUserWorldFromWorldMatrix()</strong></td><td style="text-align: center">float4x4</td><td style="text-align: left">Matrix that converts from world space to API-level (user) world space.</td></tr>
|
||
<tr><td style="text-align: left"><strong>getTime()</strong></td><td style="text-align: center">float</td><td style="text-align: left">Current time as a remainder of 1 second. Yields a value between 0 and 1</td></tr>
|
||
<tr><td style="text-align: left"><strong>getUserTime()</strong></td><td style="text-align: center">float4</td><td style="text-align: left">Current time in seconds: <code>time</code>, <code>(double)time - time</code>, <code>0</code>, <code>0</code></td></tr>
|
||
<tr><td style="text-align: left"><strong>getUserTimeMod(float m)</strong></td><td style="text-align: center">float</td><td style="text-align: left">Current time modulo m in seconds</td></tr>
|
||
<tr><td style="text-align: left"><strong>getExposure()</strong></td><td style="text-align: center">float</td><td style="text-align: left">Photometric exposure of the camera</td></tr>
|
||
<tr><td style="text-align: left"><strong>getEV100()</strong></td><td style="text-align: center">float</td><td style="text-align: left"><a href="https://en.wikipedia.org/wiki/Exposure_value">Exposure value at ISO 100</a> of the camera</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>!!! TIP: world space To achieve good precision, the "world space" in Filament's
|
||
shading system does not necessarily match the API-level world space. To obtain
|
||
the position of the API-level camera, custom materials can use
|
||
<code>getUserWorldFromWorldMatrix()</code> to transform <code>getWorldCameraPosition()</code>.</p>
|
||
<h3 id="material-globals"><a class="header" href="#material-globals">Material globals</a></h3>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: left">Name</th><th style="text-align: center">Type</th><th style="text-align: left">Description</th></tr></thead><tbody>
|
||
<tr><td style="text-align: left"><strong>getMaterialGlobal0()</strong></td><td style="text-align: center">float4</td><td style="text-align: left">A vec4 visible by all materials, its value is set by <code>View::setMaterialGlobal(0, float4)</code>. Its default value is {0,0,0,1}.</td></tr>
|
||
<tr><td style="text-align: left"><strong>getMaterialGlobal1()</strong></td><td style="text-align: center">float4</td><td style="text-align: left">A vec4 visible by all materials, its value is set by <code>View::setMaterialGlobal(1, float4)</code>. Its default value is {0,0,0,1}.</td></tr>
|
||
<tr><td style="text-align: left"><strong>getMaterialGlobal2()</strong></td><td style="text-align: center">float4</td><td style="text-align: left">A vec4 visible by all materials, its value is set by <code>View::setMaterialGlobal(2, float4)</code>. Its default value is {0,0,0,1}.</td></tr>
|
||
<tr><td style="text-align: left"><strong>getMaterialGlobal3()</strong></td><td style="text-align: center">float4</td><td style="text-align: left">A vec4 visible by all materials, its value is set by <code>View::setMaterialGlobal(3, float4)</code>. Its default value is {0,0,0,1}.</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="vertex-only"><a class="header" href="#vertex-only">Vertex only</a></h3>
|
||
<p>The following APIs are only available from the vertex block:</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: left">Name</th><th style="text-align: center">Type</th><th style="text-align: left">Description</th></tr></thead><tbody>
|
||
<tr><td style="text-align: left"><strong>getPosition()</strong></td><td style="text-align: center">float4</td><td style="text-align: left">Vertex position in the domain defined by the material (default: object/model space)</td></tr>
|
||
<tr><td style="text-align: left"><strong>getCustom0()</strong> to <strong>getCustom7()</strong></td><td style="text-align: center">float4</td><td style="text-align: left">Custom vertex attribute</td></tr>
|
||
<tr><td style="text-align: left"><strong>getWorldFromModelMatrix()</strong></td><td style="text-align: center">float4x4</td><td style="text-align: left">Matrix that converts from model (object) space to world space</td></tr>
|
||
<tr><td style="text-align: left"><strong>getWorldFromModelNormalMatrix()</strong></td><td style="text-align: center">float3x3</td><td style="text-align: left">Matrix that converts normals from model (object) space to world space</td></tr>
|
||
<tr><td style="text-align: left"><strong>getVertexIndex()</strong></td><td style="text-align: center">int</td><td style="text-align: left">Index of the current vertex</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="fragment-only"><a class="header" href="#fragment-only">Fragment only</a></h3>
|
||
<p>The following APIs are only available from the fragment block:</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: left">Name</th><th style="text-align: center">Type</th><th style="text-align: left">Description</th></tr></thead><tbody>
|
||
<tr><td style="text-align: left"><strong>getWorldTangentFrame()</strong></td><td style="text-align: center">float3x3</td><td style="text-align: left">Matrix containing in each column the <code>tangent</code> (<code>frame[0]</code>), <code>bi-tangent</code> (<code>frame[1]</code>) and <code>normal</code> (<code>frame[2]</code>) of the vertex in world space. If the material does not compute a tangent space normal for bump mapping or if the shading is not anisotropic, only the <code>normal</code> is valid in this matrix.</td></tr>
|
||
<tr><td style="text-align: left"><strong>getWorldPosition()</strong></td><td style="text-align: center">float3</td><td style="text-align: left">Position of the fragment in world space (see note below about world-space)</td></tr>
|
||
<tr><td style="text-align: left"><strong>getUserWorldPosition()</strong></td><td style="text-align: center">float3</td><td style="text-align: left">Position of the fragment in API-level (user) world-space (see note below about world-space)</td></tr>
|
||
<tr><td style="text-align: left"><strong>getWorldViewVector()</strong></td><td style="text-align: center">float3</td><td style="text-align: left">Normalized vector in world space from the fragment position to the eye</td></tr>
|
||
<tr><td style="text-align: left"><strong>getWorldNormalVector()</strong></td><td style="text-align: center">float3</td><td style="text-align: left">Normalized normal in world space, after bump mapping (must be used after <code>prepareMaterial()</code>)</td></tr>
|
||
<tr><td style="text-align: left"><strong>getWorldGeometricNormalVector()</strong></td><td style="text-align: center">float3</td><td style="text-align: left">Normalized normal in world space, before bump mapping (can be used before <code>prepareMaterial()</code>)</td></tr>
|
||
<tr><td style="text-align: left"><strong>getWorldReflectedVector()</strong></td><td style="text-align: center">float3</td><td style="text-align: left">Reflection of the view vector about the normal (must be used after <code>prepareMaterial()</code>)</td></tr>
|
||
<tr><td style="text-align: left"><strong>getNormalizedViewportCoord()</strong></td><td style="text-align: center">float3</td><td style="text-align: left">Normalized user viewport position (i.e. NDC coordinates normalized to [0, 1] for the position, [1, 0] for the depth), can be used before <code>prepareMaterial()</code>). Because the user viewport is smaller than the actual physical viewport, these coordinates can be negative or superior to 1 in the non-visible area of the physical viewport.</td></tr>
|
||
<tr><td style="text-align: left"><strong>getNdotV()</strong></td><td style="text-align: center">float</td><td style="text-align: left">The result of <code>dot(normal, view)</code>, always strictly greater than 0 (must be used after <code>prepareMaterial()</code>)</td></tr>
|
||
<tr><td style="text-align: left"><strong>getColor()</strong></td><td style="text-align: center">float4</td><td style="text-align: left">Interpolated color of the fragment, if the color attribute is required</td></tr>
|
||
<tr><td style="text-align: left"><strong>getUV0()</strong></td><td style="text-align: center">float2</td><td style="text-align: left">First interpolated set of UV coordinates, only available if the uv0 attribute is required</td></tr>
|
||
<tr><td style="text-align: left"><strong>getUV1()</strong></td><td style="text-align: center">float2</td><td style="text-align: left">First interpolated set of UV coordinates, only available if the uv1 attribute is required</td></tr>
|
||
<tr><td style="text-align: left"><strong>getMaskThreshold()</strong></td><td style="text-align: center">float</td><td style="text-align: left">Returns the mask threshold, only available when <code>blending</code> is set to <code>masked</code></td></tr>
|
||
<tr><td style="text-align: left"><strong>inverseTonemap(float3)</strong></td><td style="text-align: center">float3</td><td style="text-align: left">Applies the inverse tone mapping operator to the specified linear sRGB color and returns a linear sRGB color. This operation may be an approximation and works best with the "Filmic" tone mapping operator</td></tr>
|
||
<tr><td style="text-align: left"><strong>inverseTonemapSRGB(float3)</strong></td><td style="text-align: center">float3</td><td style="text-align: left">Applies the inverse tone mapping operator to the specified non-linear sRGB color and returns a linear sRGB color. This operation may be an approximation and works best with the "Filmic" tone mapping operator</td></tr>
|
||
<tr><td style="text-align: left"><strong>luminance(float3)</strong></td><td style="text-align: center">float</td><td style="text-align: left">Computes the luminance of the specified linear sRGB color</td></tr>
|
||
<tr><td style="text-align: left"><strong>ycbcrToRgb(float, float2)</strong></td><td style="text-align: center">float3</td><td style="text-align: left">Converts a luminance and CbCr pair to a sRGB color</td></tr>
|
||
<tr><td style="text-align: left"><strong>uvToRenderTargetUV(float2)</strong></td><td style="text-align: center">float2</td><td style="text-align: left">Transforms a UV coordinate to allow sampling from a <code>RenderTarget</code> attachment</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>!!! TIP: world-space To obtain API-level world-space coordinates, custom
|
||
materials should use <code>getUserWorldPosition()</code> or use
|
||
<code>getUserWorldFromWorldMatrix()</code>. Note that API-level world-space coordinates
|
||
should never or rarely be used because they may not fit in a float3 or have
|
||
severely reduced precision.</p>
|
||
<p>!!! TIP: sampling from render targets When sampling from a <code>filament::Texture</code>
|
||
that is attached to a <code>filament::RenderTarget</code> for materials in the surface
|
||
domain, please use <code>uvToRenderTargetUV</code> to transform the texture coordinate.
|
||
This will flip the coordinate depending on which backend is being used.</p>
|
||
<h1 id="compiling-materials"><a class="header" href="#compiling-materials">Compiling materials</a></h1>
|
||
<p>Material packages can be compiled from material definitions using the command
|
||
line tool called <code>matc</code>. The simplest way to use <code>matc</code> is to specify an input
|
||
material definition (<code>car_paint.mat</code> in the example below) and an output
|
||
material package (<code>car_paint.filamat</code> in the example below):</p>
|
||
<pre><code class="language-text">$ matc -o ./materials/bin/car_paint.filamat ./materials/src/car_paint.mat
|
||
</code></pre>
|
||
<h2 id="shader-validation"><a class="header" href="#shader-validation">Shader validation</a></h2>
|
||
<p><code>matc</code> attempts to validate shaders when compiling a material package. The
|
||
example below shows an example of an error message generated when compiling a
|
||
material definition containing a typo in the fragment shader (<code>metalic</code> instead
|
||
of <code>metallic</code>). The reported line numbers are line numbers in the source
|
||
material definition file.</p>
|
||
<pre><code class="language-text">ERROR: 0:13: 'metalic' : no such field in structure
|
||
ERROR: 0:13: '' : compilation terminated
|
||
ERROR: 2 compilation errors. No code generated.
|
||
|
||
Could not compile material metal.mat
|
||
</code></pre>
|
||
<h2 id="flags"><a class="header" href="#flags">Flags</a></h2>
|
||
<p>The command line flags relevant to application development are described in
|
||
table [matcFlags].</p>
|
||
<div class="table-wrapper"><table><thead><tr><th style="text-align: right">Flag</th><th style="text-align: center">Value</th><th style="text-align: left">Usage</th></tr></thead><tbody>
|
||
<tr><td style="text-align: right"><strong>-o</strong>, <strong>--output</strong></td><td style="text-align: center">[path]</td><td style="text-align: left">Specify the output file path</td></tr>
|
||
<tr><td style="text-align: right"><strong>-p</strong>, <strong>--platform</strong></td><td style="text-align: center">desktop/mobile/all</td><td style="text-align: left">Select the target platform(s)</td></tr>
|
||
<tr><td style="text-align: right"><strong>-a</strong>, <strong>--api</strong></td><td style="text-align: center">opengl/vulkan/all</td><td style="text-align: left">Specify the target graphics API</td></tr>
|
||
<tr><td style="text-align: right"><strong>-S</strong>, <strong>--optimize-size</strong></td><td style="text-align: center">N/A</td><td style="text-align: left">Optimize compiled material for size instead of just performance</td></tr>
|
||
<tr><td style="text-align: right"><strong>-r</strong>, <strong>--reflect</strong></td><td style="text-align: center">parameters</td><td style="text-align: left">Outputs the specified metadata as JSON</td></tr>
|
||
<tr><td style="text-align: right"><strong>-v</strong>, <strong>--variant-filter</strong></td><td style="text-align: center">[variant]</td><td style="text-align: left">Filters out the specified, comma-separated variants</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>[Table [matcFlags]: List of <code>matc</code> flags]</p>
|
||
<p><code>matc</code> offers a few other flags that are irrelevant to application developers
|
||
and for internal use only.</p>
|
||
<h3 id="--platform"><a class="header" href="#--platform">--platform</a></h3>
|
||
<p>By default, <code>matc</code> generates material packages containing shaders for all
|
||
supported platforms. If you wish to reduce the size of your material packages,
|
||
it is recommended to select only the appropriate target platform. For instance,
|
||
to compile a material package for Android only, run the following command:</p>
|
||
<pre><code class="language-text">$ matc -p mobile -o ./materials/bin/car_paint.filamat ./materials/src/car_paint.mat
|
||
</code></pre>
|
||
<h3 id="--api"><a class="header" href="#--api">--api</a></h3>
|
||
<p>By default, <code>matc</code> generates material packages containing shaders for the OpenGL
|
||
API. You can choose to generate shaders for the Vulkan API in addition to the
|
||
OpenGL shaders. If you intend on targeting only Vulkan capable devices, you can
|
||
reduce the size of the material packages by generating only the set of Vulkan
|
||
shaders:</p>
|
||
<pre><code class="language-text">$ matc -a vulkan -o ./materials/bin/car_paint.filamat ./materials/src/car_paint.mat
|
||
</code></pre>
|
||
<h3 id="--optimize-size"><a class="header" href="#--optimize-size">--optimize-size</a></h3>
|
||
<p>This flag applies fewer optimization techniques to try and keep the final
|
||
material as small as possible. If the compiled material is deemed too large by
|
||
default, using this flag might be a good compromise between runtime performance
|
||
and size.</p>
|
||
<h3 id="--reflect"><a class="header" href="#--reflect">--reflect</a></h3>
|
||
<p>This flag was designed to help build tools around <code>matc</code>. It allows you to print
|
||
out specific metadata in JSON format. The example below prints out the list of
|
||
parameters defined in Filament's standard skybox material. It produces a list of
|
||
2 parameters, named <code>showSun</code> and <code>skybox</code>, respectively a boolean and a cubemap
|
||
texture.</p>
|
||
<pre><code class="language-text">$ matc --reflect parameters filament/src/materials/skybox.mat
|
||
{
|
||
"parameters": [
|
||
{
|
||
"name": "showSun",
|
||
"type": "bool",
|
||
"size": "1"
|
||
},
|
||
{
|
||
"name": "skybox",
|
||
"type": "samplerCubemap",
|
||
"format": "float",
|
||
"precision": "default"
|
||
}
|
||
]
|
||
}
|
||
</code></pre>
|
||
<h3 id="--variant-filter"><a class="header" href="#--variant-filter">--variant-filter</a></h3>
|
||
<p>This flag can be used to further reduce the size of a compiled material. It is
|
||
used to specify a list of shader variants that the application guarantees will
|
||
never be needed. These shader variants are skipped during the code generation
|
||
phase of <code>matc</code>, thus reducing the overall size of the material.</p>
|
||
<p>The variants must be specified as a comma-separated list, using one of the
|
||
following available variants:</p>
|
||
<ul>
|
||
<li><code>directionalLighting</code>, used when a directional light is present in the scene</li>
|
||
<li><code>dynamicLighting</code>, used when a non-directional light (point, spot, etc.) is
|
||
present in the scene</li>
|
||
<li><code>shadowReceiver</code>, used when an object can receive shadows</li>
|
||
<li><code>skinning</code>, used when an object is animated using GPU skinning or vertex
|
||
morphing</li>
|
||
<li><code>fog</code>, used when global fog is applied to the scene</li>
|
||
<li><code>vsm</code>, used when VSM shadows are enabled and the object is a shadow receiver</li>
|
||
<li><code>ssr</code>, used when screen-space reflections are enabled in the View</li>
|
||
</ul>
|
||
<p>Example: <code>--variant-filter=skinning,shadowReceiver</code></p>
|
||
<p>Note that some variants may automatically be filtered out. For instance, all
|
||
lighting related variants (<code>directionalLighting</code>, etc.) are filtered out when
|
||
compiling an <code>unlit</code> material.</p>
|
||
<p>When this flag is used, the specified variant filters are merged with the
|
||
variant filters specified in the material itself.</p>
|
||
<p>Use this flag with caution, filtering out a variant required at runtime may lead
|
||
to crashes.</p>
|
||
<h1 id="handling-colors"><a class="header" href="#handling-colors">Handling colors</a></h1>
|
||
<h2 id="linear-colors"><a class="header" href="#linear-colors">Linear colors</a></h2>
|
||
<p>If the color data comes from a texture, simply make sure you use an sRGB texture
|
||
to benefit from automatic hardware conversion from sRGB to linear. If the color
|
||
data is passed as a parameter to the material you can convert from sRGB to
|
||
linear by running the following algorithm on each color channel:</p>
|
||
<pre><code class="language-glsl">float sRGB_to_linear(float color) {
|
||
return color <= 0.04045 ? color / 12.92 : pow((color + 0.055) / 1.055, 2.4);
|
||
}
|
||
</code></pre>
|
||
<p>Alternatively you can use one of the two cheaper but less accurate versions
|
||
shown below:</p>
|
||
<pre><code class="language-glsl">// Cheaper
|
||
linearColor = pow(color, 2.2);
|
||
// Cheapest
|
||
linearColor = color * color;
|
||
</code></pre>
|
||
<h2 id="pre-multiplied-alpha"><a class="header" href="#pre-multiplied-alpha">Pre-multiplied alpha</a></h2>
|
||
<p>A color uses pre-multiplied alpha if its RGB components are multiplied by the
|
||
alpha channel:</p>
|
||
<pre><code class="language-glsl">// Compute pre-multiplied color
|
||
color.rgb *= color.a;
|
||
</code></pre>
|
||
<p>If the color is sampled from a texture, you can simply ensure that the texture
|
||
data is pre-multiplied ahead of time. On Android, any texture uploaded from a
|
||
<a href="https://developer.android.com/reference/android/graphics/Bitmap.html">Bitmap</a>
|
||
will be pre-multiplied by default.</p>
|
||
<h1 id="sampler-usage-in-materials"><a class="header" href="#sampler-usage-in-materials">Sampler usage in Materials</a></h1>
|
||
<p>The number of usable sampler parameters (e.g.: type is <code>sampler2d</code>) in materials
|
||
is limited and depends on the material properties, shading model, feature level
|
||
and variant filter.</p>
|
||
<h2 id="feature-level-1-and-2"><a class="header" href="#feature-level-1-and-2">Feature level 1 and 2</a></h2>
|
||
<p><code>unlit</code> materials can use up to 12 samplers by default.</p>
|
||
<p><code>lit</code> materials can use up to 9 samplers by default, however if <code>refractionMode</code>
|
||
or <code>reflectionMode</code> is set to <code>screenspace</code> that number is reduced to 8.</p>
|
||
<p>Finally if <code>variantFilter</code> contains the <code>fog</code> filter, an extra sampler is made
|
||
available, such that <code>unlit</code> materials can use up to 13 and <code>lit</code> materials up
|
||
to 10 samplers by default.</p>
|
||
<h2 id="feature-level-3"><a class="header" href="#feature-level-3">Feature level 3</a></h2>
|
||
<p>16 samplers are available.</p>
|
||
<p>!!! TIP: external samplers Be aware that <code>external</code> samplers account for 2
|
||
regular samplers.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="api-reference"><a class="header" href="#api-reference">API Reference</a></h1>
|
||
<p>Refer to the following for a complete reference to the Filament API.</p>
|
||
<ul>
|
||
<li><a href="TODO">C++</a></li>
|
||
<li><a href="TODO">Java</a></li>
|
||
<li><a href="TODO">JavaScript</a></li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="ios"><a class="header" href="#ios">iOS</a></h1>
|
||
<p>The following section contains information and tutorials on building iOS apps
|
||
with Filament.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="cocoapods-hello-triangle"><a class="header" href="#cocoapods-hello-triangle">CocoaPods Hello Triangle</a></h1>
|
||
<p>As of release 1.8.0, you can install Filament in your iOS application using
|
||
CocoaPods.</p>
|
||
<p>This guide will walk you through creating a basic "hello triangle" iOS
|
||
application using Filament and the Metal backend.</p>
|
||
<p><img src="./cocoapods/rotating-triangle.gif" alt="a rotating triangle" /></p>
|
||
<p>The full source for this example is
|
||
<a href="https://github.com/google/filament/tree/main/ios/samples/HelloCocoaPods">here</a>.
|
||
If you're just looking to get something up and running quickly, download the
|
||
project, <code>pod install</code>, build, and run.</p>
|
||
<p>We'll be walking through 7 steps to get the rotating triangle up and running.
|
||
All of the code we'll be writing will be in a single ViewController.mm file, and
|
||
you can follow along
|
||
<a href="https://github.com/google/filament/blob/main/ios/samples/HelloCocoaPods/HelloCocoaPods/ViewController.mm">here</a>.</p>
|
||
<ol>
|
||
<li><a href="cocoapods.html#creating-a-boilerplate-app-with-filament">Creating a Boilerplate App</a></li>
|
||
<li><a href="cocoapods.html#instantiating-the-filament-engine">Instantiating Filament</a></li>
|
||
<li><a href="cocoapods.html#creating-a-swapchain">Creating a SwapChain</a></li>
|
||
<li><a href="cocoapods.html#clearing-the-screen">Clearing the Screen</a></li>
|
||
<li><a href="cocoapods.html#drawing-a-triangle">Drawing a Triangle</a></li>
|
||
<li><a href="cocoapods.html#compiling-a-custom-material">Compiling a Custom Material</a></li>
|
||
<li><a href="cocoapods.html#animating-the-triangle">Animating the Triangle</a></li>
|
||
</ol>
|
||
<h2 id="creating-a-boilerplate-app-with-filament"><a class="header" href="#creating-a-boilerplate-app-with-filament">Creating a Boilerplate App with Filament</a></h2>
|
||
<p>We'll start fresh by creating a new Single View App in Xcode.</p>
|
||
<p><img src="./cocoapods/single-view-app.png" alt="create a single view app in Xcodde" /></p>
|
||
<p>Give your app a name, and use the default options.</p>
|
||
<p><img src="./cocoapods/default-options.png" alt="use the default options in Xcode" /></p>
|
||
<p>If you haven't used CocoaPods before, I recommend watching
|
||
<a href="https://www.youtube.com/watch?v=iEAjvNRdZa0">this Route 85 video</a> to help you
|
||
get set up.</p>
|
||
<p>Create a Podfile in the Xcode project directory with the following:</p>
|
||
<pre><code class="language-text">platform :ios, '11.0'
|
||
|
||
target 'HelloCocoaPods' do
|
||
pod 'Filament'
|
||
end
|
||
</code></pre>
|
||
<p>Then run:</p>
|
||
<pre><code class="language-shell">pod install
|
||
</code></pre>
|
||
<p>Close the project and then re-open the newly created HelloCocoaPods.xcworkspace
|
||
file.</p>
|
||
<h2 id="instantiating-the-filament-engine"><a class="header" href="#instantiating-the-filament-engine">Instantiating the Filament Engine</a></h2>
|
||
<p>Before we do anything with Filament, we first need to include the appropriate
|
||
headers. Filament exposes a C++ API, so any files that include Filament headers
|
||
need to be compiled in a variant of C++. We'll be using Objective-C++.</p>
|
||
<p>You should be able to simply change the extension of the default ViewController
|
||
from .m to .mm, though I've found Xcode to be buggy with this on occasion. To
|
||
make sure Xcode recognizes it as an Objective-C++ file, check that the type of
|
||
file is "Objective-C++ Source".</p>
|
||
<p><img src="./cocoapods/obj-cpp.png" alt="change the type of ViewController.m to Objective-C++" /></p>
|
||
<p>Then, add the following to the top of ViewController.</p>
|
||
<pre><code class="language-obj-c">#include <filament/Engine.h>
|
||
|
||
using namespace filament;
|
||
</code></pre>
|
||
<p>We'll need to keep track of a few Filament objects, so let's add a section for
|
||
private instance variables and add a pointer for our <code>Engine</code> instance.</p>
|
||
<pre><code class="language-obj-c">@implementation Viewcontroller {
|
||
Engine* _engine;
|
||
}
|
||
</code></pre>
|
||
<p>The Filament <code>Engine</code> is our main entrypoint into Filament. We start by
|
||
instantiating it inside <code>viewDidLoad</code>.</p>
|
||
<pre><code class="language-obj-c">- (void)viewDidLoad {
|
||
[super viewDidLoad];
|
||
|
||
_engine = Engine::create(Engine::Backend::METAL);
|
||
}
|
||
</code></pre>
|
||
<p>We specify <code>Engine::Backend::METAL</code> to select the Metal backend. Filament also
|
||
supports OpenGL on iOS, but we strongly recommend sticking to Metal.</p>
|
||
<p>Every Filament object we create must also be destroyed. Add the <code>dealloc</code> method
|
||
and the following:</p>
|
||
<pre><code class="language-obj-c">- (void)dealloc {
|
||
_engine->destroy(&_engine);
|
||
}
|
||
</code></pre>
|
||
<p>If you compile and run the app now you should see output similar to the
|
||
following:</p>
|
||
<pre><code>FEngine (64 bits) created at 0x10ab94000 (threading is enabled)
|
||
FEngine resolved backend: Metal
|
||
</code></pre>
|
||
<h2 id="creating-a-swapchain"><a class="header" href="#creating-a-swapchain">Creating a SwapChain</a></h2>
|
||
<p>Before we can render anything, we'll first need to create a <code>SwapChain</code>. The
|
||
<code>SwapChain</code> represents a platform-specific surface that can be rendered into. On
|
||
iOS with Metal, it's a
|
||
<a href="https://developer.apple.com/documentation/quartzcore/cametallayer"><code>CAMetalLayer</code></a>.</p>
|
||
<p>We could set up our own <code>CAMetalLayer</code> if we wanted to, but Apple provides a
|
||
<code>MTKView</code> that is already backed by a <code>CAMetalLayer</code>. It also has a delegate
|
||
protocol with some methods that will make things easier for us.</p>
|
||
<p>Inside Main.storyboard, change the type of ViewController's view to a <code>MTKView</code>.</p>
|
||
<p><img src="./cocoapods/view.png" alt="ViewController view" /></p>
|
||
<p><img src="./cocoapods/mtkview.gif" alt="change type of MTKView" /></p>
|
||
<p>Include the SwapChain.h and MTKView.h headers and make the <code>ViewController</code>
|
||
conform to the <code>MTKViewDelegate</code> protocol.</p>
|
||
<pre><code class="language-obj-c">#include <filament/SwapChain.h>
|
||
|
||
#import <MetalKit/MTKView.h>
|
||
|
||
@interface ViewController () <MTKViewDelegate>
|
||
|
||
@end
|
||
</code></pre>
|
||
<p>Add a new private var:</p>
|
||
<pre><code class="language-obj-c">SwapChain* _swapChain;
|
||
</code></pre>
|
||
<p>Inside <code>viewDidLoad</code>, we'll set our <code>ViewController</code> as the <code>MTKView</code> delegate
|
||
and instantiate our <code>SwapChain</code>. To instantiate the <code>SwapChain</code>, we pass in
|
||
<code>view.layer</code> which, because we set our <code>View</code> to a <code>MTKView</code>, will be a
|
||
<code>CAMetalLayer</code>. Filament's API is platform-agnostic, which is why we need to
|
||
cast the layer to a <code>void*</code>.</p>
|
||
<pre><code class="language-obj-c">MTKView* mtkView = (MTKView*) self.view;
|
||
mtkView.delegate = self;
|
||
_swapChain = _engine->createSwapChain((__bridge void*) mtkView.layer);
|
||
</code></pre>
|
||
<p>The <code>SwapChain</code> needs to be destroyed in our <code>dealloc</code> function. We'll destroy
|
||
the objects in the reverse order we created them; the <code>Engine</code> object should
|
||
always be the the last object we destroy.</p>
|
||
<pre><code class="language-obj-c">_engine->destroy(_swapChain);
|
||
_engine->destroy(&_engine);
|
||
</code></pre>
|
||
<p>Finally, add stubs for some <code>MTKViewDelegate</code> methods, which we'll fill in
|
||
later.</p>
|
||
<pre><code class="language-obj-c">- (void)mtkView:(nonnull MTKView*)view drawableSizeWillChange:(CGSize)size {
|
||
// todo
|
||
}
|
||
|
||
- (void)drawInMTKView:(nonnull MTKView*)view {
|
||
// todo
|
||
}
|
||
</code></pre>
|
||
<h2 id="clearing-the-screen"><a class="header" href="#clearing-the-screen">Clearing The Screen</a></h2>
|
||
<p>We now have a Filament <code>Engine</code> and <code>SwapChain</code> set up. We'll need a few more
|
||
objects before we can render anything.</p>
|
||
<p>A Filament <code>Renderer</code> gives us an API to render frames into the <code>SwapChain</code>. It
|
||
takes a <code>View</code>, which defines a <code>Viewport</code>, <code>Scene</code> and <code>Camera</code> for rendering.
|
||
The <code>Camera</code> represents a vantage point into a <code>Scene</code>, which contains
|
||
references to all the entities we want to render.</p>
|
||
<p>Creating these are objects is straightforward. First, include the appropriate
|
||
headers</p>
|
||
<pre><code class="language-c++">#include <filament/Renderer.h>
|
||
#include <filament/View.h>
|
||
#include <filament/Camera.h>
|
||
#include <filament/Scene.h>
|
||
#include <filament/Viewport.h>
|
||
|
||
#include <utils/Entity.h>
|
||
#include <utils/EntityManager.h>
|
||
|
||
using namespace utils;
|
||
</code></pre>
|
||
<p>add the following private vars</p>
|
||
<pre><code class="language-obj-c">Renderer* _renderer;
|
||
View* _view;
|
||
Scene* _scene;
|
||
Camera* _camera;
|
||
Entity _cameraEntity;
|
||
</code></pre>
|
||
<p>and then instantiate them</p>
|
||
<pre><code class="language-obj-c">_renderer = _engine->createRenderer();
|
||
_view = _engine->createView();
|
||
_scene = _engine->createScene();
|
||
</code></pre>
|
||
<p>The camera is a bit special. Filament uses an entity-component system, so we'll
|
||
first need to create an <code>Entity</code> which we then attach a <code>Camera</code> component to.</p>
|
||
<pre><code class="language-obj-c">_cameraEntity = EntityManager::get().create();
|
||
_camera = _engine->createCamera(_cameraEntity);
|
||
</code></pre>
|
||
<p>Let's also inform our <code>Renderer</code> to clear to a light blue clear color, so we can
|
||
know everything is working.</p>
|
||
<pre><code class="language-obj-c">_renderer->setClearOptions({
|
||
.clearColor = {0.25f, 0.5f, 1.0f, 1.0f},
|
||
.clear = true
|
||
});
|
||
</code></pre>
|
||
<p>The <code>Camera</code> and <code>Scene</code> need to be wired up to the <code>View</code>.</p>
|
||
<pre><code class="language-obj-c">_view->setScene(_scene);
|
||
_view->setCamera(_camera);
|
||
</code></pre>
|
||
<p>Our newly created objects get cleaned up inside <code>dealloc</code>.</p>
|
||
<pre><code class="language-obj-c">_engine->destroyCameraComponent(_cameraEntity);
|
||
EntityManager::get().destroy(_cameraEntity);
|
||
_engine->destroy(_scene);
|
||
_engine->destroy(_view);
|
||
_engine->destroy(_renderer);
|
||
</code></pre>
|
||
<p>We need to set the <code>Viewport</code> on our <code>View</code>, which we want to do whenever the
|
||
size of our <code>SwapChain</code> changes. We'll also update the projection matrix on our
|
||
camera.</p>
|
||
<p>Let's create a new method, <code>resize:</code>, which will update the <code>Viewport</code> on our
|
||
<code>View</code> to a given size. We'll call it in the <code>mtkView:drawableSizeWillChange:</code>
|
||
delegate method, and at the end of <code>viewDidLoad</code>:</p>
|
||
<pre><code class="language-obj-c">- (void)resize:(CGSize)size {
|
||
_view->setViewport({0, 0, (uint32_t) size.width, (uint32_t) size.height});
|
||
|
||
const double aspect = size.width / size.height;
|
||
const double left = -2.0 * aspect;
|
||
const double right = 2.0 * aspect;
|
||
const double bottom = -2.0;
|
||
const double top = 2.0;
|
||
const double near = 0.0;
|
||
const double far = 1.0;
|
||
_camera->setProjection(Camera::Projection::ORTHO, left, right, bottom, top, near, far);
|
||
}
|
||
|
||
- (void)viewDidLoad {
|
||
...
|
||
|
||
// Give our View a starting size based on the drawable size.
|
||
[self resize:mtkView.drawableSize];
|
||
}
|
||
|
||
- (void)mtkView(nonnull MTKView*)view drawableSizeWillChange:(CGSize)size {
|
||
[self resize:size];
|
||
}
|
||
</code></pre>
|
||
<p>Lastly, in order to render, we'll call a few Filament API methods inside the
|
||
<code>drawInMTKView:</code> method:</p>
|
||
<pre><code class="language-obj-c">- (void)drawInMTKView:(nonnull MTKView*)view {
|
||
if (_renderer->beginFrame(_swapChain)) {
|
||
_renderer->render(_view);
|
||
_renderer->endFrame();
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>The <code>beginFrame</code> method instructs Filament to start rendering to our specific
|
||
<code>SwapChain</code> instance. It returns <code>true</code> if the engine is ready for another
|
||
frame. It returns <code>false</code> to signal us to skip this frame, which could happen if
|
||
we're sending frames down too quickly for the GPU to process.</p>
|
||
<p>At this point, you should be able to build and run the app, and you'll see a
|
||
blue screen.</p>
|
||
<p><img src="./cocoapods/blue-screen.png" alt="blue screen after clearing" /></p>
|
||
<h2 id="drawing-a-triangle"><a class="header" href="#drawing-a-triangle">Drawing a Triangle</a></h2>
|
||
<p>In order to draw a triangle, we need to create vertex and index buffers to
|
||
define its geometry. We'll then create a <code>Renderable</code> component.</p>
|
||
<p>We'll start by including some additional headers and adding a few new private
|
||
vars:</p>
|
||
<pre><code class="language-obj-c">#include <filament/VertexBuffer.h>
|
||
#include <filament/IndexBuffer.h>
|
||
#include <filament/RenderableManager.h>
|
||
|
||
...
|
||
|
||
VertexBuffer* _vertexBuffer;
|
||
IndexBuffer* _indexBuffer;
|
||
Entity _triangle;
|
||
</code></pre>
|
||
<p>First, we'll define the data for a single vertex.</p>
|
||
<pre><code class="language-obj-c">struct Vertex {
|
||
math::float2 position;
|
||
math::float3 color;
|
||
};
|
||
</code></pre>
|
||
<p>Creating a <code>VertexBuffer</code> and <code>IndexBuffer</code> is a matter of giving Filament a
|
||
pointer to the data, along with information on its layout and size. Filament
|
||
uses <code>BufferDescriptors</code> to accomplish this.</p>
|
||
<p>Inside <code>viewDidLoad</code>, we'll statically define some verticies and indices and
|
||
create a <code>BufferDescriptor</code> for each.</p>
|
||
<pre><code class="language-obj-c">static const Vertex TRIANGLE_VERTICES[3] = {
|
||
{ { 0.867, -0.500}, {1.0, 0.0, 0.0} },
|
||
{ { 0.000, 1.000}, {0.0, 1.0, 0.0} },
|
||
{ {-0.867, -0.500}, {0.0, 0.0, 1.0} },
|
||
};
|
||
static const uint16_t TRIANGLE_INDICES[3] = { 0, 1, 2 };
|
||
|
||
VertexBuffer::BufferDescriptor vertices(TRIANGLE_VERTICES, sizeof(Vertex) * 3, nullptr);
|
||
IndexBuffer::BufferDescriptor indices(TRIANGLE_INDICES, sizeof(uint16_t) * 3, nullptr);
|
||
</code></pre>
|
||
<p>The last argument is an optional callback function, which will be called after
|
||
Filament is done uploading the data to the GPU. Inside the callback, you'd
|
||
typically release the memory of any buffers via a <code>free</code> or <code>delete</code> call. We
|
||
pass <code>nullptr</code> because we don't need a callback as our vertex and index buffer
|
||
memory is static.</p>
|
||
<p>Now we can instantiate our <code>VertexBuffer</code> and <code>IndexBuffer</code>.</p>
|
||
<pre><code class="language-obj-c">using Type = VertexBuffer::AttributeType;
|
||
|
||
const uint8_t stride = sizeof(Vertex);
|
||
_vertexBuffer = VertexBuffer::Builder()
|
||
.vertexCount(3)
|
||
.bufferCount(1)
|
||
.attribute(VertexAttribute::POSITION, 0, Type::FLOAT2, offsetof(Vertex, position), stride)
|
||
.attribute(VertexAttribute::COLOR, 0, Type::FLOAT3, offsetof(Vertex, color), stride)
|
||
.build(*_engine);
|
||
|
||
_indexBuffer = IndexBuffer::Builder()
|
||
.indexCount(3)
|
||
.bufferType(IndexBuffer::IndexType::USHORT)
|
||
.build(*_engine);
|
||
|
||
_vertexBuffer->setBufferAt(*_engine, 0, std::move(vertices));
|
||
_indexBuffer->setBuffer(*_engine, std::move(indices));
|
||
</code></pre>
|
||
<p>We first create an <code>Entity</code> like we did for our camera. This time, we're
|
||
attaching a <code>Renderable</code> component to the entity. The <code>Renderable</code> component
|
||
takes geometry defined by our vertex and index buffers, and makes the entity
|
||
visible in our scene.</p>
|
||
<pre><code class="language-obj-c">_triangle = utils::EntityManager::get().create();
|
||
|
||
using Primitive = RenderableManager::PrimitiveType;
|
||
RenderableManager::Builder(1)
|
||
.geometry(0, Primitive::TRIANGLES, _vertexBuffer, _indexBuffer, 0, 3)
|
||
.culling(false)
|
||
.receiveShadows(false)
|
||
.castShadows(false)
|
||
.build(*_engine, _triangle);
|
||
|
||
// Add the triangle to the scene.
|
||
_scene->addEntity(_triangle);
|
||
</code></pre>
|
||
<p>Destroy the entity and buffers in <code>dealloc</code>.</p>
|
||
<pre><code class="language-obj-c">_engine->destroy(_triangle);
|
||
EntityManager::get().destroy(_triangle);
|
||
_engine->destroy(_indexBuffer);
|
||
_engine->destroy(_vertexBuffer);
|
||
</code></pre>
|
||
<p>If you build and run the app now, you should see a plain white triangle. When we
|
||
created the renderable, we didn't specify any specific <code>Material</code> to use, so
|
||
Filament used a default, white material. Let's create a custom material to color
|
||
the triangle.</p>
|
||
<p><img src="./cocoapods/white-triangle.png" alt="a white triangle" /></p>
|
||
<h2 id="compiling-a-custom-material"><a class="header" href="#compiling-a-custom-material">Compiling a Custom Material</a></h2>
|
||
<p>For simplicity, we're going to compile a custom material at runtime. For
|
||
production, we recommend using our matc tool to compile materials offline. You
|
||
can download it as part of one of our
|
||
<a href="https://github.com/google/filament/releases">releases</a>.</p>
|
||
<p>First, add a few more headers. We'll be using Filament's filamat library to
|
||
compile a custom material.</p>
|
||
<pre><code class="language-c++">#include <filament/Material.h>
|
||
#include <filament/MaterialInstance.h>
|
||
|
||
#include <filamat/MaterialBuilder.h>
|
||
</code></pre>
|
||
<p>We'll store our material in a new private var. We'll also need one to store a
|
||
material <em>instance</em>. You can think of a material as a "template", where a
|
||
material instance is an instantiation of the template (similar to OOP classes
|
||
and instances). For more information on Filament materials, read the
|
||
<a href="https://google.github.io/filament/Materials.html">Filament Materials Guide</a>.</p>
|
||
<pre><code class="language-obj-c">Material* _material;
|
||
MaterialInstance* _materialInstance;
|
||
</code></pre>
|
||
<p>We'll use the filamat library to compile a material into a package, which we can
|
||
then load into Filament. The material will be simple; it will load the
|
||
interpolated color attribute and set it as the <code>baseColor</code>.</p>
|
||
<p>Make sure to insert this code into <code>viewDidLoad</code> <em>before</em> we create our
|
||
<code>Renderable</code>.</p>
|
||
<pre><code class="language-obj-c">// init must be called before we can build any materials.
|
||
filamat::MaterialBuilder::init();
|
||
|
||
// Compile a custom material to use on the triangle.
|
||
filamat::Package pkg = filamat::MaterialBuilder()
|
||
// The material name, only used for debugging purposes.
|
||
.name("Triangle material")
|
||
// Use the unlit shading mode, because we don't have any lights in our scene.
|
||
.shading(filamat::MaterialBuilder::Shading::UNLIT)
|
||
// Expose the COLOR attribute visible to our shader code.
|
||
.require(VertexAttribute::COLOR)
|
||
// Custom GLSL fragment shader
|
||
.material("void material (inout MaterialInputs material) {"
|
||
" prepareMaterial(material);"
|
||
" material.baseColor = getColor();"
|
||
"}")
|
||
// Compile for Metal on mobile platforms.
|
||
.targetApi(filamat::MaterialBuilder::TargetApi::METAL)
|
||
.platform(filamat::MaterialBuilder::Platform::MOBILE)
|
||
.build();
|
||
assert(pkg.isValid());
|
||
|
||
// shutdown should be called after all materials are built.
|
||
filamat::MaterialBuilder::shutdown();
|
||
</code></pre>
|
||
<p>Now that we have a <code>filamat::Package</code> representing the material, we can use it
|
||
to instantiate a Filament <code>Material</code>. Note that again, we recommend using the
|
||
matc command-line tool to compile material packages during your app's
|
||
compilation phase if possible, instead of at run-time.</p>
|
||
<pre><code class="language-obj-c">// Create a Filament material from the Package.
|
||
_material = Material::Builder()
|
||
.package(pkg.getData(), pkg.getSize())
|
||
.build(*_engine);
|
||
_materialInstance = _material->getDefaultInstance();
|
||
</code></pre>
|
||
<p>Now we can use the <code>MaterialInstance</code> when creating our <code>Renderable</code>.</p>
|
||
<pre><code class="language-obj-c">// Create a renderable using our geometry and material.
|
||
using Primitive = RenderableManager::PrimitiveType;
|
||
RenderableManager::Builder(1)
|
||
.geometry(0, Primitive::TRIANGLES, _vertexBuffer, _indexBuffer, 0, 3)
|
||
// Use the MaterialInstance we just created.
|
||
.material(0, _materialInstance)
|
||
.culling(false)
|
||
.receiveShadows(false)
|
||
.castShadows(false)
|
||
.build(*_engine, _triangle);
|
||
</code></pre>
|
||
<p>Lastly, we make sure to destroy everything inside <code>dealloc</code>.</p>
|
||
<pre><code class="language-obj-c">_engine->destroy(_materialInstance);
|
||
_engine->destroy(_material);
|
||
</code></pre>
|
||
<p>Build and run. You should see the same triangle, but with colors.</p>
|
||
<p><img src="./cocoapods/colored-triangle.png" alt="the triangle with our custom material" /></p>
|
||
<h2 id="animating-the-triangle"><a class="header" href="#animating-the-triangle">Animating the Triangle</a></h2>
|
||
<p>We'll do this by animating a transform on our triangle entity. First, include a
|
||
new header.</p>
|
||
<pre><code class="language-obj-c">#include <filament/TransformManager.h>
|
||
</code></pre>
|
||
<p>When we create our triangle entity, we'll also attach a transform component.
|
||
We've already seen two other components: <code>Renderable</code> and <code>Camera</code>. The
|
||
<code>Transform</code> component allows us to set world-space transformations on entities.</p>
|
||
<p>Inside <code>viewDidLoad</code>, after we create the triangle entity's <code>Renderable</code>
|
||
component, we'll also attach a <code>Transform</code> component.</p>
|
||
<pre><code class="language-obj-c">// Add a Transform component to the triangle, so we can animate it.
|
||
_engine->getTransformManager().create(_triangle);
|
||
</code></pre>
|
||
<p>Create a new function, <code>update</code>, and add call it inside the <code>drawInMTKView:</code>
|
||
method.</p>
|
||
<pre><code class="language-obj-c">- (void)update {
|
||
auto& tm = _engine->getTransformManager();
|
||
auto i = tm.getInstance(_triangle);
|
||
const auto time = CACurrentMediaTime();
|
||
tm.setTransform(i, math::mat4f::rotation(time, math::float3 {0.0, 0.0, 1.0}));
|
||
}
|
||
|
||
- (void)drawInMTKView:(nonnull MTKView*)view {
|
||
[self update];
|
||
if (_renderer->beginFrame(_swapChain)) {
|
||
_renderer->render(_view);
|
||
_renderer->endFrame();
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>Now we should see the triangle rotate around its z axis.</p>
|
||
<p><img src="./cocoapods/rotating-triangle.gif" alt="a rotating triangle" /></p>
|
||
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
|
||
<p>In this guide we've covered how to install Filament with CocoaPods and get
|
||
rendering using the Metal backend. We also compiled a custom material. Again,
|
||
here's the
|
||
<a href="https://github.com/google/filament/tree/main/ios/samples/HelloCocoaPods">complete sample code</a>
|
||
for the app. If you're interesting in learning more, check out Filament's
|
||
additional
|
||
<a href="https://github.com/google/filament/tree/main/ios/samples">iOS samples</a>. If you
|
||
have any problems, feel free to open an
|
||
<a href="https://github.com/google/filament/issues">issue</a>.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="web"><a class="header" href="#web">Web</a></h1>
|
||
<p>The following section contains information, demos, and tutorials for building
|
||
Filament web apps.</p>
|
||
<p>In addition to the contents of this book, you may also find the following
|
||
external links helpful.</p>
|
||
<ul>
|
||
<li><a href="https://prideout.net/slides/filawasm">WebGL Meetup Slides</a> (2018)</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="web-tutorials"><a class="header" href="#web-tutorials">Web Tutorials</a></h1>
|
||
<p>The markdown source for these tutorials are not only used to generate this book;
|
||
they're also used to generate the JavaScript for the above demo. We use a small
|
||
Python script for weaving (generating HTML) and tangling (generating JS). In the
|
||
code samples, you'll often see <code>// TODO: <some task></code>. These are special markers
|
||
that get replaced by subsequent code blocks.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="triangle-tutorial"><a class="header" href="#triangle-tutorial">Triangle Tutorial</a></h1>
|
||
<h2 id="start-your-project"><a class="header" href="#start-your-project">Start your project</a></h2>
|
||
<p>First, create a text file called <code>triangle.html</code> and fill it with the following HTML. This creates
|
||
a mobile-friendly page with a full-screen canvas.</p>
|
||
<pre><code class="language-html"><!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<title>Filament Tutorial</title>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
|
||
<style>
|
||
body { margin: 0; overflow: hidden; }
|
||
canvas { touch-action: none; width: 100%; height: 100%; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<canvas></canvas>
|
||
<script src="filament.js"></script>
|
||
<script src="//unpkg.com/gl-matrix@2.8.1"></script>
|
||
<script src="triangle.js"></script>
|
||
</body>
|
||
</html>
|
||
</code></pre>
|
||
<p>The above HTML loads three JavaScript files:</p>
|
||
<ul>
|
||
<li><code>filament.js</code> does a couple things:
|
||
<ul>
|
||
<li>Downloads assets and compiles the Filament WASM module.</li>
|
||
<li>Contains high-level utilities, e.g. to simplify loading KTX textures from JavaScript.</li>
|
||
</ul>
|
||
</li>
|
||
<li><code>gl-matrix-min.js</code> is a small library that provides vector math functionality.</li>
|
||
<li><code>triangle.js</code> will contain your application code.</li>
|
||
</ul>
|
||
<p>Go ahead and create <code>triangle.js</code> with the following content.</p>
|
||
<pre><code class="language-js {fragment="root"}">class App {
|
||
constructor() {
|
||
// TODO: create entities
|
||
this.render = this.render.bind(this);
|
||
this.resize = this.resize.bind(this);
|
||
window.addEventListener('resize', this.resize);
|
||
window.requestAnimationFrame(this.render);
|
||
}
|
||
render() {
|
||
// TODO: render scene
|
||
window.requestAnimationFrame(this.render);
|
||
}
|
||
resize() {
|
||
// TODO: adjust viewport and canvas
|
||
}
|
||
}
|
||
|
||
Filament.init(['triangle.filamat'], () => { window.app = new App() } );
|
||
</code></pre>
|
||
<p>The two calls to <code>bind()</code> allow us to pass instance methods as callbacks for animation and resize
|
||
events.</p>
|
||
<p><code>Filament.init()</code> consumes two things: a list of asset URLs and a callback.</p>
|
||
<p>The callback will be triggered only after all assets finish downloading and the Filament module has
|
||
become ready. In our callback, we simply instantiated the <code>App</code> object, since we'll do most of the
|
||
work in its constructor. We also set the app instance into a <code>Window</code> property to make it accessible
|
||
from the developer console.</p>
|
||
<p>Go ahead and download <a href="triangle.filamat">triangle.filamat</a> and place it in your project folder.
|
||
This is a <em>material package</em>, which is a binary file that contains shaders and other bits of data
|
||
that define a PBR material. We'll learn more about material packages in the next tutorial.</p>
|
||
<h2 id="spawn-a-local-server"><a class="header" href="#spawn-a-local-server">Spawn a local server</a></h2>
|
||
<p>Because of CORS restrictions, your web app cannot fetch the material package directly from the
|
||
file system. One way around this is to create a temporary server using Python or node:</p>
|
||
<pre><code class="language-bash">python3 -m http.server # Python 3
|
||
python -m SimpleHTTPServer # Python 2.7
|
||
npx http-server -p 8000 # nodejs
|
||
</code></pre>
|
||
<p>To see if this works, navigate to <a href="http://localhost:8000">http://localhost:8000</a> and check if you
|
||
can load the page without any errors appearing in the developer console.</p>
|
||
<p>Take care not to use Python's simple server in production since it does not serve WebAssembly files
|
||
with the correct MIME type.</p>
|
||
<h2 id="create-the-engine-and-scene"><a class="header" href="#create-the-engine-and-scene">Create the Engine and Scene</a></h2>
|
||
<p>We now have a basic skeleton that can respond to paint and resize events. Let's start adding
|
||
Filament objects to the app. Insert the following code into the top of the app constructor.</p>
|
||
<pre><code class="language-js {fragment="create entities"}">this.canvas = document.getElementsByTagName('canvas')[0];
|
||
const engine = this.engine = Filament.Engine.create(this.canvas);
|
||
</code></pre>
|
||
<p>The above snippet creates the <code>Engine</code> by passing it a canvas DOM object. The engine needs the
|
||
canvas in order to create a WebGL 2.0 context in its contructor.</p>
|
||
<p>The engine is a factory for many Filament entities, including <code>Scene</code>, which is a flat container of
|
||
entities. Let's go ahead and create a scene, then add a blank entity called <code>triangle</code> into the
|
||
scene.</p>
|
||
<pre><code class="language-js {fragment="create entities"}">this.scene = engine.createScene();
|
||
this.triangle = Filament.EntityManager.get().create();
|
||
this.scene.addEntity(this.triangle);
|
||
</code></pre>
|
||
<p>Filament uses an <a href="//en.wikipedia.org/wiki/Entity-component-system">Entity-Component System</a>.
|
||
The triangle entity in the above snippet does not yet have an associated component. Later in the
|
||
tutorial we will make it into a <em>renderable</em>. Renderables are entities that have associated draw
|
||
calls.</p>
|
||
<h2 id="construct-typed-arrays"><a class="header" href="#construct-typed-arrays">Construct typed arrays</a></h2>
|
||
<p>Next we'll create two typed arrays: a positions array with XY coordinates for each vertex, and a
|
||
colors array with a 32-bit word for each vertex.</p>
|
||
<pre><code class="language-js {fragment="create entities"}">const TRIANGLE_POSITIONS = new Float32Array([
|
||
1, 0,
|
||
Math.cos(Math.PI * 2 / 3), Math.sin(Math.PI * 2 / 3),
|
||
Math.cos(Math.PI * 4 / 3), Math.sin(Math.PI * 4 / 3),
|
||
]);
|
||
|
||
const TRIANGLE_COLORS = new Uint32Array([0xffff0000, 0xff00ff00, 0xff0000ff]);
|
||
</code></pre>
|
||
<p>Next we'll use the positions and colors buffers to create a single <code>VertexBuffer</code> object.</p>
|
||
<pre><code class="language-js {fragment="create entities"}">const VertexAttribute = Filament.VertexAttribute;
|
||
const AttributeType = Filament.VertexBuffer$AttributeType;
|
||
this.vb = Filament.VertexBuffer.Builder()
|
||
.vertexCount(3)
|
||
.bufferCount(2)
|
||
.attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT2, 0, 8)
|
||
.attribute(VertexAttribute.COLOR, 1, AttributeType.UBYTE4, 0, 4)
|
||
.normalized(VertexAttribute.COLOR)
|
||
.build(engine);
|
||
|
||
this.vb.setBufferAt(engine, 0, TRIANGLE_POSITIONS);
|
||
this.vb.setBufferAt(engine, 1, TRIANGLE_COLORS);
|
||
</code></pre>
|
||
<p>The above snippet first creates aliases for two enum types, then constructs the vertex buffer using
|
||
its <code>Builder</code> method. After that, it pushes two buffer objects into the appropriate slots using
|
||
<code>setBufferAt</code>.</p>
|
||
<p>In the Filament API, the above builder pattern is often used for constructing objects in lieu of
|
||
long argument lists. The daisy chain of function calls allows the client code to be somewhat
|
||
self-documenting.</p>
|
||
<p>Our app sets up two buffer slots in the vertex buffer, and each slot is associated with a single
|
||
attribute. Alternatively, we could have interleaved or concatenated these attributes into a single
|
||
buffer slot.</p>
|
||
<p>Next we'll construct an index buffer. The index buffer for our triangle is trivial: it simply holds
|
||
the integers 0,1,2.</p>
|
||
<pre><code class="language-js {fragment="create entities"}">this.ib = Filament.IndexBuffer.Builder()
|
||
.indexCount(3)
|
||
.bufferType(Filament.IndexBuffer$IndexType.USHORT)
|
||
.build(engine);
|
||
|
||
this.ib.setBuffer(engine, new Uint16Array([0, 1, 2]));
|
||
</code></pre>
|
||
<p>Note that constructing an index buffer is similar to constructing a vertex buffer, but it only has
|
||
one buffer slot, and it can only contain two types of data (USHORT or UINT).</p>
|
||
<h2 id="finish-up-initialization"><a class="header" href="#finish-up-initialization">Finish up initialization</a></h2>
|
||
<p>Next let's construct an actual <code>Material</code> from the material package that was downloaded (the
|
||
material is an object; the package is just a binary blob), then extract the default
|
||
<code>MaterialInstance</code> from the material object. Material instances have concrete values for their
|
||
parameters, and they can be bound to renderables. We'll learn more about material instances in the
|
||
next tutorial.</p>
|
||
<p>After extracting the material instance, we can finally create a renderable component for the
|
||
triangle by setting up a bounding box and passing in the vertex and index buffers.</p>
|
||
<pre><code class="language-js {fragment="create entities"}">const mat = engine.createMaterial('triangle.filamat');
|
||
const matinst = mat.getDefaultInstance();
|
||
Filament.RenderableManager.Builder(1)
|
||
.boundingBox({ center: [-1, -1, -1], halfExtent: [1, 1, 1] })
|
||
.material(0, matinst)
|
||
.geometry(0, Filament.RenderableManager$PrimitiveType.TRIANGLES, this.vb, this.ib)
|
||
.build(engine, this.triangle);
|
||
</code></pre>
|
||
<p>Next let's wrap up the initialization routine by creating the swap chain, renderer, camera, and
|
||
view.</p>
|
||
<pre><code class="language-js {fragment="create entities"}">this.swapChain = engine.createSwapChain();
|
||
this.renderer = engine.createRenderer();
|
||
this.camera = engine.createCamera(Filament.EntityManager.get().create());
|
||
this.view = engine.createView();
|
||
this.view.setCamera(this.camera);
|
||
this.view.setScene(this.scene);
|
||
|
||
// Set up a blue-green background:
|
||
this.renderer.setClearOptions({clearColor: [0.0, 0.1, 0.2, 1.0], clear: true});
|
||
|
||
// Adjust the initial viewport:
|
||
this.resize();
|
||
</code></pre>
|
||
<p>At this point, we're done creating all Filament entities, and the code should run without errors.
|
||
However the canvas is still blank!</p>
|
||
<h2 id="render-and-resize-handlers"><a class="header" href="#render-and-resize-handlers">Render and resize handlers</a></h2>
|
||
<p>Recall that our App class has a skeletal render method, which the browser calls every time it needs
|
||
to repaint. Often this is 60 times a second.</p>
|
||
<pre><code class="language-js">render() {
|
||
// TODO: render scene
|
||
window.requestAnimationFrame(this.render);
|
||
}
|
||
</code></pre>
|
||
<p>Let's flesh this out by rotating the triangle and invoking the Filament renderer. Add the following
|
||
code to the top of the render method.</p>
|
||
<pre><code class="language-js {fragment="render scene"}">// Rotate the triangle.
|
||
const radians = Date.now() / 1000;
|
||
const transform = mat4.fromRotation(mat4.create(), radians, [0, 0, 1]);
|
||
const tcm = this.engine.getTransformManager();
|
||
const inst = tcm.getInstance(this.triangle);
|
||
tcm.setTransform(inst, transform);
|
||
inst.delete();
|
||
|
||
// Render the frame.
|
||
this.renderer.render(this.swapChain, this.view);
|
||
</code></pre>
|
||
<p>The first half of our render method obtains the transform component of the triangle entity and uses
|
||
gl-matrix to generate a rotation matrix.</p>
|
||
<p>The second half of our render method invokes the Filament renderer on the view, and tells the
|
||
Filament engine to execute its internal command buffer. The Filament renderer can tell the app
|
||
that it wants to skip a frame, hence the <code>if</code> statement.</p>
|
||
<p>One last step. Add the following code to the resize method. This adjusts the resolution of the
|
||
rendering surface when the window size changes, taking <code>devicePixelRatio</code> into account for high-DPI
|
||
displays. It also adjusts the camera frustum accordingly.</p>
|
||
<pre><code class="language-js {fragment="adjust viewport and canvas"}">const dpr = window.devicePixelRatio;
|
||
const width = this.canvas.width = window.innerWidth * dpr;
|
||
const height = this.canvas.height = window.innerHeight * dpr;
|
||
this.view.setViewport([0, 0, width, height]);
|
||
|
||
const aspect = width / height;
|
||
const Projection = Filament.Camera$Projection;
|
||
this.camera.setProjection(Projection.ORTHO, -aspect, aspect, -1, 1, 0, 1);
|
||
</code></pre>
|
||
<p>You should now have a spinning triangle! The completed JavaScript is available
|
||
<a href="tutorial_triangle.js">here</a>.</p>
|
||
<p>In the <a href="tutorial_redball.html">next tutorial</a>, we'll take a closer look at Filament materials and 3D rendering.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="suzanne-tutorial"><a class="header" href="#suzanne-tutorial">Suzanne Tutorial</a></h1>
|
||
<p>This tutorial will describe how to create the <strong>redball</strong> demo, introducing you to materials and
|
||
textures.</p>
|
||
<p>For starters, create a text file called <code>redball.html</code> and copy over the HTML that we used in the
|
||
<a href="tutorial_triangle.html">previous tutorial</a>. Change the last script tag from <code>triangle.js</code> to <code>redball.js</code>.</p>
|
||
<p>Next you'll need to get a couple command-line tools: <code>matc</code> and <code>cmgen</code>. You can find these in the
|
||
appropriate <a href="//github.com/google/filament/releases">Filament release</a>. You should choose the
|
||
archive that corresponds to your development machine rather than the one for web, and the version
|
||
that matches the <code>unpkg.com/filament@x.x.x</code> url in the script tag of <code>redball.html</code> (you may check
|
||
out the last available release of <a href="https://www.npmjs.com/package/filament">filament on npm</a>).</p>
|
||
<h2 id="define-plastic-material"><a class="header" href="#define-plastic-material">Define plastic material</a></h2>
|
||
<p>The <code>matc</code> tool consumes a text file containing a high-level description of a PBR material, and
|
||
produces a binary material package that contains shader code and associated metadata. For more
|
||
information, see the official document describing the <a href="https://google.github.io/filament/Materials.md.html">Filament Material System</a>.</p>
|
||
<p>Let's try out <code>matc</code>. Create the following file in your favorite text editor and call it
|
||
<code>plastic.mat</code>.</p>
|
||
<pre><code class="language-text">material {
|
||
name : Lit,
|
||
shadingModel : lit,
|
||
parameters : [
|
||
{ type : float3, name : baseColor },
|
||
{ type : float, name : roughness },
|
||
{ type : float, name : clearCoat },
|
||
{ type : float, name : clearCoatRoughness }
|
||
],
|
||
}
|
||
|
||
fragment {
|
||
void material(inout MaterialInputs material) {
|
||
prepareMaterial(material);
|
||
material.baseColor.rgb = materialParams.baseColor;
|
||
material.roughness = materialParams.roughness;
|
||
material.clearCoat = materialParams.clearCoat;
|
||
material.clearCoatRoughness = materialParams.clearCoatRoughness;
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>Next, invoke <code>matc</code> as follows.</p>
|
||
<pre><code class="language-bash">matc -a opengl -p mobile -o plastic.filamat plastic.mat
|
||
</code></pre>
|
||
<p>You should now have a material archive in your working directory, which we'll use later in the
|
||
tutorial.</p>
|
||
<h2 id="bake-environment-map"><a class="header" href="#bake-environment-map">Bake environment map</a></h2>
|
||
<p>Next we'll use Filament's <code>cmgen</code> tool to consume a HDR environment map in latlong format, and
|
||
produce two cubemap files: a mipmapped IBL and a blurry skybox.</p>
|
||
<p>Download <a href="//github.com/google/filament/blob/main/third_party/environments/pillars_2k.hdr">pillars_2k.hdr</a>, then invoke the following command in your terminal.</p>
|
||
<pre><code class="language-bash">cmgen -x pillars_2k --format=ktx --size=256 --extract-blur=0.1 pillars_2k.hdr
|
||
</code></pre>
|
||
<p>You should now have a <code>pillars_2k</code> folder containing a couple KTX files for the IBL and skybox, as
|
||
well as a text file with spherical harmonics coefficients. You can discard the text file because the
|
||
IBL KTX contains these coefficients in its metadata.</p>
|
||
<h2 id="create-javascript"><a class="header" href="#create-javascript">Create JavaScript</a></h2>
|
||
<p>Next, create <code>redball.js</code> with the following content.</p>
|
||
<pre><code class="language-js {fragment="root"}">const environ = 'pillars_2k';
|
||
const ibl_url = `${environ}/${environ}_ibl.ktx`;
|
||
const sky_url = `${environ}/${environ}_skybox.ktx`;
|
||
const filamat_url = 'plastic.filamat'
|
||
|
||
Filament.init([ filamat_url, ibl_url, sky_url ], () => {
|
||
// Create some global aliases to enums for convenience.
|
||
window.VertexAttribute = Filament.VertexAttribute;
|
||
window.AttributeType = Filament.VertexBuffer$AttributeType;
|
||
window.PrimitiveType = Filament.RenderableManager$PrimitiveType;
|
||
window.IndexType = Filament.IndexBuffer$IndexType;
|
||
window.Fov = Filament.Camera$Fov;
|
||
window.LightType = Filament.LightManager$Type;
|
||
|
||
// Obtain the canvas DOM object and pass it to the App.
|
||
const canvas = document.getElementsByTagName('canvas')[0];
|
||
window.app = new App(canvas);
|
||
} );
|
||
|
||
class App {
|
||
constructor(canvas) {
|
||
this.canvas = canvas;
|
||
const engine = this.engine = Filament.Engine.create(canvas);
|
||
const scene = engine.createScene();
|
||
|
||
// TODO: create material
|
||
// TODO: create sphere
|
||
// TODO: create lights
|
||
// TODO: create IBL
|
||
// TODO: create skybox
|
||
|
||
this.swapChain = engine.createSwapChain();
|
||
this.renderer = engine.createRenderer();
|
||
this.camera = engine.createCamera(Filament.EntityManager.get().create());
|
||
this.view = engine.createView();
|
||
this.view.setCamera(this.camera);
|
||
this.view.setScene(scene);
|
||
this.resize();
|
||
this.render = this.render.bind(this);
|
||
this.resize = this.resize.bind(this);
|
||
window.addEventListener('resize', this.resize);
|
||
window.requestAnimationFrame(this.render);
|
||
}
|
||
|
||
render() {
|
||
const eye = [0, 0, 4], center = [0, 0, 0], up = [0, 1, 0];
|
||
const radians = Date.now() / 10000;
|
||
vec3.rotateY(eye, eye, center, radians);
|
||
this.camera.lookAt(eye, center, up);
|
||
this.renderer.render(this.swapChain, this.view);
|
||
window.requestAnimationFrame(this.render);
|
||
}
|
||
|
||
resize() {
|
||
const dpr = window.devicePixelRatio;
|
||
const width = this.canvas.width = window.innerWidth * dpr;
|
||
const height = this.canvas.height = window.innerHeight * dpr;
|
||
this.view.setViewport([0, 0, width, height]);
|
||
this.camera.setProjectionFov(45, width / height, 1.0, 10.0, Fov.VERTICAL);
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>The above boilerplate should be familiar to you from the previous tutorial, although it loads in a
|
||
new set of assets. We also added some animation to the camera.</p>
|
||
<p>Next let's create a material instance from the package that we built at the beginning the tutorial.
|
||
Replace the <strong>create material</strong> comment with the following snippet.</p>
|
||
<pre><code class="language-js {fragment="create material"}">const material = engine.createMaterial(filamat_url);
|
||
const matinstance = material.createInstance();
|
||
|
||
const red = [0.8, 0.0, 0.0];
|
||
matinstance.setColor3Parameter('baseColor', Filament.RgbType.sRGB, red);
|
||
matinstance.setFloatParameter('roughness', 0.5);
|
||
matinstance.setFloatParameter('clearCoat', 1.0);
|
||
matinstance.setFloatParameter('clearCoatRoughness', 0.3);
|
||
</code></pre>
|
||
<p>The next step is to create a renderable for the sphere. To help with this, we'll use the <code>IcoSphere</code>
|
||
utility class, whose constructor takes a LOD. Its job is to subdivide an icosadedron, producing
|
||
three arrays:</p>
|
||
<ul>
|
||
<li><code>icosphere.vertices</code> Float32Array of XYZ coordinates.</li>
|
||
<li><code>icosphere.tangents</code> Uint16Array (interpreted as half-floats) encoding the surface orientation
|
||
as quaternions.</li>
|
||
<li><code>icosphere.triangles</code> Uint16Array with triangle indices.</li>
|
||
</ul>
|
||
<p>Let's go ahead use these arrays to build the vertex buffer and index buffer. Replace <strong>create
|
||
sphere</strong> with the following snippet.</p>
|
||
<pre><code class="language-js {fragment="create sphere"}">const renderable = Filament.EntityManager.get().create();
|
||
scene.addEntity(renderable);
|
||
|
||
const icosphere = new Filament.IcoSphere(5);
|
||
|
||
const vb = Filament.VertexBuffer.Builder()
|
||
.vertexCount(icosphere.vertices.length / 3)
|
||
.bufferCount(2)
|
||
.attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT3, 0, 0)
|
||
.attribute(VertexAttribute.TANGENTS, 1, AttributeType.SHORT4, 0, 0)
|
||
.normalized(VertexAttribute.TANGENTS)
|
||
.build(engine);
|
||
|
||
const ib = Filament.IndexBuffer.Builder()
|
||
.indexCount(icosphere.triangles.length)
|
||
.bufferType(IndexType.USHORT)
|
||
.build(engine);
|
||
|
||
vb.setBufferAt(engine, 0, icosphere.vertices);
|
||
vb.setBufferAt(engine, 1, icosphere.tangents);
|
||
ib.setBuffer(engine, icosphere.triangles);
|
||
|
||
Filament.RenderableManager.Builder(1)
|
||
.boundingBox({ center: [-1, -1, -1], halfExtent: [1, 1, 1] })
|
||
.material(0, matinstance)
|
||
.geometry(0, PrimitiveType.TRIANGLES, vb, ib)
|
||
.build(engine, renderable);
|
||
</code></pre>
|
||
<p>At this point, the app is rendering a sphere, but it is black so it doesn't show up. To prove that
|
||
the sphere is there, you can try changing the background color to blue via <code>setClearColor</code>, like we
|
||
did in the first tutorial.</p>
|
||
<h2 id="add-lighting"><a class="header" href="#add-lighting">Add lighting</a></h2>
|
||
<p>In this section we will create some directional light sources, as well as an image-based light (IBL)
|
||
defined by one of the KTX files we built at the start of the demo. First, replace the <strong>create
|
||
lights</strong> comment with the following snippet.</p>
|
||
<pre><code class="language-js {fragment="create lights"}">const sunlight = Filament.EntityManager.get().create();
|
||
scene.addEntity(sunlight);
|
||
Filament.LightManager.Builder(LightType.SUN)
|
||
.color([0.98, 0.92, 0.89])
|
||
.intensity(110000.0)
|
||
.direction([0.6, -1.0, -0.8])
|
||
.sunAngularRadius(1.9)
|
||
.sunHaloSize(10.0)
|
||
.sunHaloFalloff(80.0)
|
||
.build(engine, sunlight);
|
||
|
||
const backlight = Filament.EntityManager.get().create();
|
||
scene.addEntity(backlight);
|
||
Filament.LightManager.Builder(LightType.DIRECTIONAL)
|
||
.direction([-1, 0, 1])
|
||
.intensity(50000.0)
|
||
.build(engine, backlight);
|
||
</code></pre>
|
||
<p>The <code>SUN</code> light source is similar to the <code>DIRECTIONAL</code> light source, but has some extra
|
||
parameters because Filament will automatically draw a disk into the skybox.</p>
|
||
<p>Next we need to create an <code>IndirectLight</code> object from the KTX IBL. One way of doing this is the
|
||
following (don't type this out, there's an easier way).</p>
|
||
<pre><code class="language-js">const format = Filament.PixelDataFormat.RGB;
|
||
const datatype = Filament.PixelDataType.UINT_10F_11F_11F_REV;
|
||
|
||
// Create a Texture object for the mipmapped cubemap.
|
||
const ibl_package = Filament.Buffer(Filament.assets[ibl_url]);
|
||
const iblktx = new Filament.Ktx1Bundle(ibl_package);
|
||
|
||
const ibltex = Filament.Texture.Builder()
|
||
.width(iblktx.info().pixelWidth)
|
||
.height(iblktx.info().pixelHeight)
|
||
.levels(iblktx.getNumMipLevels())
|
||
.sampler(Filament.Texture$Sampler.SAMPLER_CUBEMAP)
|
||
.format(Filament.Texture$InternalFormat.RGBA8)
|
||
.build(engine);
|
||
|
||
for (let level = 0; level < iblktx.getNumMipLevels(); ++level) {
|
||
const uint8array = iblktx.getCubeBlob(level).getBytes();
|
||
const pixelbuffer = Filament.PixelBuffer(uint8array, format, datatype);
|
||
ibltex.setImageCube(engine, level, pixelbuffer);
|
||
}
|
||
|
||
// Parse the spherical harmonics metadata.
|
||
const shstring = iblktx.getMetadata('sh');
|
||
const shfloats = shstring.split(/\s/, 9 * 3).map(parseFloat);
|
||
|
||
// Build the IBL object and insert it into the scene.
|
||
const indirectLight = Filament.IndirectLight.Builder()
|
||
.reflections(ibltex)
|
||
.irradianceSh(3, shfloats)
|
||
.intensity(50000.0)
|
||
.build(engine);
|
||
|
||
scene.setIndirectLight(indirectLight);
|
||
</code></pre>
|
||
<p>Filament provides a JavaScript utility to make this simpler,
|
||
simply replace the <strong>create IBL</strong> comment with the following snippet.</p>
|
||
<pre><code class="language-js {fragment="create IBL"}">const indirectLight = engine.createIblFromKtx1(ibl_url);
|
||
indirectLight.setIntensity(50000);
|
||
scene.setIndirectLight(indirectLight);
|
||
</code></pre>
|
||
<h2 id="add-background"><a class="header" href="#add-background">Add background</a></h2>
|
||
<p>At this point you can run the demo and you should see a red plastic ball against a black background.
|
||
Without a skybox, the reflections on the ball are not representative of its surroundings.
|
||
Here's one way to create a texture for the skybox:</p>
|
||
<pre><code class="language-js">const sky_package = Filament.Buffer(Filament.assets[sky_url]);
|
||
const skyktx = new Filament.Ktx1Bundle(sky_package);
|
||
const skytex = Filament.Texture.Builder()
|
||
.width(skyktx.info().pixelWidth)
|
||
.height(skyktx.info().pixelHeight)
|
||
.levels(1)
|
||
.sampler(Filament.Texture$Sampler.SAMPLER_CUBEMAP)
|
||
.format(Filament.Texture$InternalFormat.RGBA8)
|
||
.build(engine);
|
||
|
||
const uint8array = skyktx.getCubeBlob(0).getBytes();
|
||
const pixelbuffer = Filament.PixelBuffer(uint8array, format, datatype);
|
||
skytex.setImageCube(engine, 0, pixelbuffer);
|
||
</code></pre>
|
||
<p>Filament provides a Javascript utility to make this easier.
|
||
Replace <strong>create skybox</strong> with the following.</p>
|
||
<pre><code class="language-js {fragment="create skybox"}">const skybox = engine.createSkyFromKtx1(sky_url);
|
||
scene.setSkybox(skybox);
|
||
</code></pre>
|
||
<p>That's it, we now have a shiny red ball floating in an environment! The complete JavaScript file is
|
||
available <a href="tutorial_redball.js">here</a>.</p>
|
||
<p>In the <a href="tutorial_suzanne.html">next tutorial</a>, we'll take a closer look at textures and interaction.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="suzanne-tutorial-1"><a class="header" href="#suzanne-tutorial-1">Suzanne Tutorial</a></h1>
|
||
<p>This tutorial will describe how to create the <strong>suzanne</strong> demo, introducing you to compressed
|
||
textures, mipmap generation, asynchronous texture loading, and trackball rotation.</p>
|
||
<p>Much like the <a href="tutorial_redball.html">previous tutorial</a>, you'll need to use the command-line tools that can be found in
|
||
the appropriate <a href="//github.com/google/filament/releases">Filament release</a> for your development machine. In addition to <code>matc</code> and <code>cmgen</code>,
|
||
we'll also be using <code>filamesh</code> and <code>mipgen</code>.</p>
|
||
<h2 id="create-filamesh-file"><a class="header" href="#create-filamesh-file">Create filamesh file</a></h2>
|
||
<p>Filament does not have an asset loading system, but it does provide a binary mesh format
|
||
called <code>filamesh</code> for simple use cases. Let's create a compressed filamesh file for suzanne by
|
||
converting <a href="https://github.com/google/filament/blob/main/assets/models/monkey/monkey.obj">this OBJ file</a>:</p>
|
||
<pre><code class="language-bash">filamesh --compress monkey.obj suzanne.filamesh
|
||
</code></pre>
|
||
<h2 id="create-mipmapped-textures"><a class="header" href="#create-mipmapped-textures">Create mipmapped textures</a></h2>
|
||
<p>Next, let's create mipmapped KTX files using filament's <code>mipgen</code> tool. We'll create compressed and
|
||
non-compressed variants for each texture, since not all platforms support the same compression
|
||
formats. First copy over the PNG files from the <a href="https://github.com/google/filament/blob/main/assets/models/monkey">monkey folder</a>, then do:</p>
|
||
<pre><code class="language-bash"># Create mipmaps for base color
|
||
mipgen albedo.png albedo.ktx2
|
||
mipgen --compression=uastc albedo.png albedo.ktx2
|
||
|
||
# Create mipmaps for the normal map and a compressed variant.
|
||
mipgen --strip-alpha --kernel=NORMALS --linear normal.png normal.ktx
|
||
mipgen --strip-alpha --kernel=NORMALS --linear --compression=uastc_normals \
|
||
normal.png normal.ktx2
|
||
|
||
# Create mipmaps for the single-component roughness map and a compressed variant.
|
||
mipgen --grayscale roughness.png roughness.ktx
|
||
mipgen --grayscale --compression=uastc roughness.png roughness.ktx2
|
||
|
||
# Create mipmaps for the single-component metallic map and a compressed variant.
|
||
mipgen --grayscale metallic.png metallic.ktx
|
||
mipgen --grayscale --compression=uastc metallic.png metallic.ktx2
|
||
|
||
# Create mipmaps for the single-component occlusion map and a compressed variant.
|
||
mipgen --grayscale ao.png ao.ktx
|
||
mipgen --grayscale --compression=uastc ao.png ao.ktx2
|
||
</code></pre>
|
||
<p>For more information on mipgen's arguments and supported formats, do <code>mipgen --help</code>.</p>
|
||
<p>In a production setting, you'd want to invoke these commands with a script or build system.</p>
|
||
<h2 id="bake-environment-map-1"><a class="header" href="#bake-environment-map-1">Bake environment map</a></h2>
|
||
<p>Much like the <a href="tutorial_redball.html">previous tutorial</a> we need to use Filament's <code>cmgen</code> tool to produce cubemap files.</p>
|
||
<p>Download <a href="//github.com/google/filament/blob/main/third_party/environments/venetian_crossroads_2k.hdr">venetian_crossroads_2k.hdr</a>, then invoke the following commands in your terminal.</p>
|
||
<pre><code class="language-bash">cmgen -x . --format=ktx --size=64 --extract-blur=0.1 venetian_crossroads_2k.hdr
|
||
cd venetian* ; mv venetian*_ibl.ktx venetian_crossroads_2k_skybox_tiny.ktx ; cd -
|
||
|
||
cmgen -x . --format=ktx --size=256 --extract-blur=0.1 venetian_crossroads_2k.hdr
|
||
cmgen -x . --format=ktx --size=256 --extract-blur=0.1 venetian_crossroads_2k.hdr
|
||
cmgen -x . --format=ktx --size=256 --extract-blur=0.1 venetian_crossroads_2k.hdr
|
||
</code></pre>
|
||
<h2 id="define-textured-material"><a class="header" href="#define-textured-material">Define textured material</a></h2>
|
||
<p>You might recall the <code>filamat</code> file we generated in the previous tutorial for red plastic. For this
|
||
demo, we'll create a material that uses textures for several parameters.</p>
|
||
<p>Create the following text file and call it <code>textured.mat</code>. Note that our material definition now
|
||
requires a <code>uv0</code> attribute.</p>
|
||
<pre><code class="language-text">material {
|
||
name : textured,
|
||
requires : [ uv0 ],
|
||
shadingModel : lit,
|
||
parameters : [
|
||
{ type : sampler2d, name : albedo },
|
||
{ type : sampler2d, name : roughness },
|
||
{ type : sampler2d, name : metallic },
|
||
{ type : float, name : clearCoat },
|
||
{ type : sampler2d, name : normal },
|
||
{ type : sampler2d, name : ao }
|
||
],
|
||
}
|
||
|
||
fragment {
|
||
void material(inout MaterialInputs material) {
|
||
material.normal = texture(materialParams_normal, getUV0()).xyz * 2.0 - 1.0;
|
||
prepareMaterial(material);
|
||
material.baseColor = texture(materialParams_albedo, getUV0());
|
||
material.roughness = texture(materialParams_roughness, getUV0()).r;
|
||
material.metallic = texture(materialParams_metallic, getUV0()).r;
|
||
material.clearCoat = materialParams.clearCoat;
|
||
material.ambientOcclusion = texture(materialParams_ao, getUV0()).r;
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>Next, invoke <code>matc</code> as follows.</p>
|
||
<pre><code class="language-bash">matc -a opengl -p mobile -o textured.filamat textured.mat
|
||
</code></pre>
|
||
<p>You should now have a material archive in your working directory. For the suzanne asset, the normal
|
||
map adds scratches, the albedo map paints the eyes white, and so on. For more information on
|
||
materials, consult the official document describing the <a href="https://google.github.io/filament/Materials.md.html">Filament Material System</a>.</p>
|
||
<h2 id="create-app-skeleton"><a class="header" href="#create-app-skeleton">Create app skeleton</a></h2>
|
||
<p>Create a text file called <code>suzanne.html</code> and copy over the HTML that we used in the <a href="tutorial_redball.html">previous
|
||
tutorial</a>. Change the last script tag from <code>redball.js</code> to <code>suzanne.js</code>. Next, create <code>suzanne.js</code>
|
||
with the following content.</p>
|
||
<pre><code class="language-js {fragment="root"}">// TODO: declare asset URLs
|
||
|
||
Filament.init([ filamat_url, filamesh_url, sky_small_url, ibl_url ], () => {
|
||
window.app = new App(document.getElementsByTagName('canvas')[0]);
|
||
});
|
||
|
||
class App {
|
||
constructor(canvas) {
|
||
this.canvas = canvas;
|
||
this.engine = Filament.Engine.create(canvas);
|
||
this.scene = this.engine.createScene();
|
||
|
||
const material = this.engine.createMaterial(filamat_url);
|
||
this.matinstance = material.createInstance();
|
||
|
||
const filamesh = this.engine.loadFilamesh(filamesh_url, this.matinstance);
|
||
this.suzanne = filamesh.renderable;
|
||
|
||
// TODO: create sky box and IBL
|
||
// TODO: initialize gltumble
|
||
// TODO: fetch larger assets
|
||
|
||
this.swapChain = this.engine.createSwapChain();
|
||
this.renderer = this.engine.createRenderer();
|
||
this.camera = this.engine.createCamera(Filament.EntityManager.get().create());
|
||
this.view = this.engine.createView();
|
||
this.view.setCamera(this.camera);
|
||
this.view.setScene(this.scene);
|
||
this.render = this.render.bind(this);
|
||
this.resize = this.resize.bind(this);
|
||
window.addEventListener('resize', this.resize);
|
||
|
||
const eye = [0, 0, 4], center = [0, 0, 0], up = [0, 1, 0];
|
||
this.camera.lookAt(eye, center, up);
|
||
|
||
this.resize();
|
||
window.requestAnimationFrame(this.render);
|
||
}
|
||
|
||
render() {
|
||
// TODO: apply gltumble matrix
|
||
this.renderer.render(this.swapChain, this.view);
|
||
window.requestAnimationFrame(this.render);
|
||
}
|
||
|
||
resize() {
|
||
const dpr = window.devicePixelRatio;
|
||
const width = this.canvas.width = window.innerWidth * dpr;
|
||
const height = this.canvas.height = window.innerHeight * dpr;
|
||
this.view.setViewport([0, 0, width, height]);
|
||
|
||
const aspect = width / height;
|
||
const Fov = Filament.Camera$Fov, fov = aspect < 1 ? Fov.HORIZONTAL : Fov.VERTICAL;
|
||
this.camera.setProjectionFov(45, aspect, 1.0, 10.0, fov);
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>Our app will only require a subset of assets to be present for <code>App</code> construction. We'll download
|
||
the other assets after construction. By using a progressive loading strategy, we can reduce the
|
||
perceived load time.</p>
|
||
<p>Next we need to supply the URLs for various assets. This is actually a bit tricky, because different
|
||
clients have different capabilities for compressed textures.</p>
|
||
<p>To help you download only the texture assets that you need, Filament provides a
|
||
<code>getSupportedFormatSuffix</code> function. This takes a space-separated list of desired format types
|
||
(<code>etc</code>, <code>s3tc</code>, or <code>astc</code>) that the app developer knows is available from the server. The function
|
||
performs an intersection of the <em>desired</em> set with the <em>supported</em> set, then returns an appropriate
|
||
string -- which might be empty.</p>
|
||
<p>In our case, we know that our web server will have <code>astc</code> and <code>s3tc</code> variants for albedo, and <code>etc</code>
|
||
variants for the other textures. The uncompressed variants (empty string) are always available as a
|
||
last resort. Go ahead and replace the <strong>declare asset URLs</strong> comment with the following snippet.</p>
|
||
<pre><code class="language-js {fragment="declare asset URLs"}">const albedo_suffix = Filament.getSupportedFormatSuffix('astc s3tc_srgb');
|
||
const texture_suffix = Filament.getSupportedFormatSuffix('etc');
|
||
|
||
const environ = 'venetian_crossroads_2k'
|
||
const ibl_url = `${environ}/${environ}_ibl.ktx`;
|
||
const sky_small_url = `${environ}/${environ}_skybox_tiny.ktx`;
|
||
const sky_large_url = `${environ}/${environ}_skybox.ktx`;
|
||
const albedo_url = `albedo${albedo_suffix}.ktx`;
|
||
const ao_url = `ao${texture_suffix}.ktx`;
|
||
const metallic_url = `metallic${texture_suffix}.ktx`;
|
||
const normal_url = `normal${texture_suffix}.ktx`;
|
||
const roughness_url = `roughness${texture_suffix}.ktx`;
|
||
const filamat_url = 'textured.filamat';
|
||
const filamesh_url = 'suzanne.filamesh';
|
||
</code></pre>
|
||
<h2 id="create-skybox-and-ibl"><a class="header" href="#create-skybox-and-ibl">Create skybox and IBL</a></h2>
|
||
<p>Next, let's create the low-resolution skybox and IBL in the <code>App</code> constructor.</p>
|
||
<pre><code class="language-js {fragment="create sky box and IBL"}">this.skybox = this.engine.createSkyFromKtx1(sky_small_url);
|
||
this.scene.setSkybox(this.skybox);
|
||
this.indirectLight = this.engine.createIblFromKtx1(ibl_url);
|
||
this.indirectLight.setIntensity(100000);
|
||
this.scene.setIndirectLight(this.indirectLight);
|
||
</code></pre>
|
||
<p>This allows users to see a reasonable background fairly quickly, before larger assets have finished
|
||
loading in.</p>
|
||
<h2 id="fetch-assets-asychronously"><a class="header" href="#fetch-assets-asychronously">Fetch assets asychronously</a></h2>
|
||
<p>Next we'll invoke the <code>Filament.fetch</code> function from within the app constructor. This function is
|
||
very similar to <code>Filament.init</code>. It takes a list of asset URLs and a callback function that triggers
|
||
when the assets have finished downloading.</p>
|
||
<p>In our callback, we'll make several <code>setTextureParameter</code> calls on the material instance, then we'll
|
||
recreate the skybox using a higher-resolution texture. As a last step we unhide the renderable that
|
||
was created in the app constructor.</p>
|
||
<pre><code class="language-js {fragment="fetch larger assets"}">Filament.fetch([sky_large_url, albedo_url, roughness_url, metallic_url, normal_url, ao_url], () => {
|
||
const albedo = this.engine.createTextureFromKtx2(albedo_url, {srgb: true});
|
||
const roughness = this.engine.createTextureFromKtx2(roughness_url);
|
||
const metallic = this.engine.createTextureFromKtx2(metallic_url);
|
||
const normal = this.engine.createTextureFromKtx2(normal_url);
|
||
const ao = this.engine.createTextureFromKtx2(ao_url);
|
||
|
||
const sampler = new Filament.TextureSampler(
|
||
Filament.MinFilter.LINEAR_MIPMAP_LINEAR,
|
||
Filament.MagFilter.LINEAR,
|
||
Filament.WrapMode.CLAMP_TO_EDGE);
|
||
|
||
this.matinstance.setTextureParameter('albedo', albedo, sampler);
|
||
this.matinstance.setTextureParameter('roughness', roughness, sampler);
|
||
this.matinstance.setTextureParameter('metallic', metallic, sampler);
|
||
this.matinstance.setTextureParameter('normal', normal, sampler);
|
||
this.matinstance.setTextureParameter('ao', ao, sampler);
|
||
|
||
// Replace low-res skybox with high-res skybox.
|
||
this.engine.destroySkybox(this.skybox);
|
||
this.skybox = this.engine.createSkyFromKtx1(sky_large_url);
|
||
this.scene.setSkybox(this.skybox);
|
||
|
||
this.scene.addEntity(this.suzanne);
|
||
});
|
||
</code></pre>
|
||
<h2 id="introduce-trackball-rotation"><a class="header" href="#introduce-trackball-rotation">Introduce trackball rotation</a></h2>
|
||
<p>Add the following script tag to your HTML file. This imports a small third-party library that
|
||
listens for drag events and computes a rotation matrix.</p>
|
||
<pre><code class="language-html"><script src="//unpkg.com/gltumble"></script>
|
||
</code></pre>
|
||
<p>Next, replace the <strong>initialize gltumble</strong> and <strong>apply gltumble matrix</strong> comments with the following
|
||
two code snippets.</p>
|
||
<pre><code class="language-js {fragment="initialize gltumble"}">this.trackball = new Trackball(canvas, {startSpin: 0.035});
|
||
</code></pre>
|
||
<pre><code class="language-js {fragment="apply gltumble matrix"}">const tcm = this.engine.getTransformManager();
|
||
const inst = tcm.getInstance(this.suzanne);
|
||
tcm.setTransform(inst, this.trackball.getMatrix());
|
||
inst.delete();
|
||
</code></pre>
|
||
<p>That's it, we now have a fast-loading interactive demo. The complete JavaScript file is available
|
||
<a href="tutorial_suzanne.js">here</a>.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="developing-filament"><a class="header" href="#developing-filament">Developing Filament</a></h1>
|
||
<p>The following section is aimed at contributors to the Filament project, but may
|
||
contain insights for downstream users as well.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="filament-debugging"><a class="header" href="#filament-debugging">Filament Debugging</a></h1>
|
||
<h2 id="debugging-vulkan-on-linux"><a class="header" href="#debugging-vulkan-on-linux">Debugging Vulkan on Linux</a></h2>
|
||
<h3 id="enable-validation-logs"><a class="header" href="#enable-validation-logs">Enable Validation Logs</a></h3>
|
||
<p>Simply install the LunarG SDK (it's fast and easy), then make sure you've got the following
|
||
environment variables set up in your <strong>bashrc</strong> file. For example:</p>
|
||
<pre><code>export VULKAN_SDK='/path_to_home/VulkanSDK/1.3.216.0/x86_64'
|
||
export VK_LAYER_PATH="$VULKAN_SDK/etc/explicit_layer.d"
|
||
export PATH="$VULKAN_SDK/bin:$PATH"
|
||
</code></pre>
|
||
<p>As long as you're running a debug build of Filament, you should now see extra debugging spew in your
|
||
console if there are any errors or performance issues being caught by validation.</p>
|
||
<h3 id="frame-capture-in-renderdoc"><a class="header" href="#frame-capture-in-renderdoc">Frame Capture in RenderDoc</a></h3>
|
||
<p>The following instructions assume you've already installed the LunarG SDK and therefore have the
|
||
<code>VK_LAYER_PATH</code> environment variable.</p>
|
||
<ol>
|
||
<li>Modify <code>VulkanDriver.cpp</code> by defining <code>ENABLE_RENDERDOC</code></li>
|
||
<li>Download the RenderDoc tarball for Linux and unzip it somewhere.</li>
|
||
<li>Find <code>renderdoc_capture.json</code> in the unzipped folders and copy it to <code>VK_LAYER_PATH</code>. For
|
||
example:</li>
|
||
</ol>
|
||
<pre><code>cp ~/Downloads/renderdoc_1.0/etc/vulkan/implicit_layer.d/renderdoc_capture.json ${VK_LAYER_PATH}
|
||
</code></pre>
|
||
<ol>
|
||
<li>Edit <code>${VK_LAYER_PATH}/renderdoc_capture.json</code> and update the <code>library_path</code> attribute.</li>
|
||
<li>Launch RenderDoc by running <code>renderdoc_1.0/bin/qrenderdoc</code>.</li>
|
||
<li>Go to the <strong>Launch Application</strong> tab and click the ellipses next to <strong>Environment Variables</strong>.</li>
|
||
<li>Add VK_LAYER_PATH so that it matches whatever you've got set in your <strong>bashrc</strong>.</li>
|
||
<li>Save yourself some time in the future by clicking <strong>Save Settings</strong> after setting up the working
|
||
directory, executable path, etc.</li>
|
||
<li>Click <strong>Launch</strong> in RenderDoc, then press <strong>F12</strong> in your app. You should see a new capture show up in
|
||
RenderDoc.</li>
|
||
</ol>
|
||
<h2 id="enable-metal-validation"><a class="header" href="#enable-metal-validation">Enable Metal Validation</a></h2>
|
||
<p>To enable the Metal validation layers when running a sample through the command-line, set the
|
||
following environment variable:</p>
|
||
<pre><code>export METAL_DEVICE_WRAPPER_TYPE=1
|
||
</code></pre>
|
||
<p>You should then see the following output when running a sample with the Metal backend:</p>
|
||
<pre><code>2020-10-13 18:01:44.101 gltf_viewer[73303:4946828] Metal API Validation Enabled
|
||
</code></pre>
|
||
<h2 id="metal-frame-capture-from-gltf_viewer"><a class="header" href="#metal-frame-capture-from-gltf_viewer">Metal Frame Capture from gltf_viewer</a></h2>
|
||
<p>To capture Metal frames from within gltf_viewer:</p>
|
||
<h3 id="1-create-an-infoplist-file"><a class="header" href="#1-create-an-infoplist-file">1. Create an Info.plist file</a></h3>
|
||
<p>Create an <code>Info.plist</code> file in the same directory as <code>gltf_viewer</code> (<code>cmake/samples</code>). Set its
|
||
contents to:</p>
|
||
<pre><code><?xml version="1.0" encoding="UTF-8"?>
|
||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||
<plist version="1.0">
|
||
<dict>
|
||
<key>MetalCaptureEnabled</key>
|
||
<true/>
|
||
</dict>
|
||
</plist>
|
||
</code></pre>
|
||
<h3 id="2-capture-a-frame"><a class="header" href="#2-capture-a-frame">2. Capture a frame</a></h3>
|
||
<p>Run gltf_viewer as normal, and hit the "Capture frame" button under the Debug menu. The captured
|
||
frame will be saved to <code>filament.gputrace</code> in the current working directory. This file can then be
|
||
opened with Xcode for inspection.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="investigating-spirv-cross--spirv-tools-issues"><a class="header" href="#investigating-spirv-cross--spirv-tools-issues">Investigating SPIRV-Cross / SPIRV-Tools issues</a></h1>
|
||
<p>There are 4 repositories at play here:</p>
|
||
<ul>
|
||
<li><a href="https://github.com/KhronosGroup/glslang">KhronosGroup/glslang</a></li>
|
||
<li><a href="https://github.com/KhronosGroup/SPIRV-Tools">KhronosGroup/spirv-tools</a></li>
|
||
<li><a href="https://github.com/KhronosGroup/SPIRV-Cross">KhronosGroup/spirv-cross</a></li>
|
||
<li><a href="https://github.com/KhronosGroup/SPIRV-Headers">KhronosGroup/SPIRV-Headers</a></li>
|
||
</ul>
|
||
<p>Typically, the bug is present either in spirv-tools or spirv-cross.</p>
|
||
<h2 id="build-and-install-command-line-tools-on-path"><a class="header" href="#build-and-install-command-line-tools-on-path">Build and install command-line tools on PATH</a></h2>
|
||
<p>The goal is to replicate the bug outside of Filament, so we're going to use command-line versions of
|
||
the SPIRV tools.</p>
|
||
<h3 id="clone-and-build-each-repo"><a class="header" href="#clone-and-build-each-repo">Clone and build each repo</a></h3>
|
||
<p>Note: Filament checks-out versions of these repositories inside <code>third_party/</code>; however, I've found
|
||
it easiser to check out fresh copies separately so I can simply <code>git pull</code> to get the latest
|
||
versions. Furthermore, Filament has modified some of these repositories locally for its own use
|
||
case. Checking them out separately "proves" that the issue isn't Filament-specific.</p>
|
||
<pre><code>git clone git@github.com:KhronosGroup/SPIRV-Tools.git
|
||
git clone git@github.com:KhronosGroup/SPIRV-Cross.git
|
||
git clone git@github.com:KhronosGroup/glslang.git
|
||
git clone git@github.com:KhronosGroup/SPIRV-Headers.git SPIRV-Tools/external/SPIRV-Headers
|
||
|
||
cd SPIRV-Tools/
|
||
mkdir build && cmake . -G Ninja -B build
|
||
ninja -C build
|
||
cd ..
|
||
|
||
cd SPIRV-Cross/
|
||
mkdir build && cmake . -G Ninja -B build
|
||
ninja -C build
|
||
cd ..
|
||
|
||
cd glslang/
|
||
mkdir build && cmake . -G Ninja -B build
|
||
ninja -C build
|
||
cd ..
|
||
</code></pre>
|
||
<h3 id="add-directories-to-path"><a class="header" href="#add-directories-to-path">Add directories to PATH</a></h3>
|
||
<pre><code>export PATH=`pwd`/SPIRV-Tools/build/tools:$PATH
|
||
export PATH=`pwd`/glslang/build/StandAlone:$PATH
|
||
export PATH=`pwd`/spirv-cross/build:$PATH
|
||
</code></pre>
|
||
<p>Ensure the following tools now exist on your PATH:</p>
|
||
<ol>
|
||
<li><code>glslangValidator</code></li>
|
||
<li><code>spiv-opt</code></li>
|
||
<li><code>spirv-val</code></li>
|
||
<li><code>spirv-cross</code></li>
|
||
</ol>
|
||
<h2 id="isolate-the-problematic-glsl-shader"><a class="header" href="#isolate-the-problematic-glsl-shader">Isolate the problematic GLSL shader</a></h2>
|
||
<p>First determine the Filament material and variant that causes the problem.</p>
|
||
<p>What we want is the "raw" GLSL version of the shader, before any optimizations / cross-compilation
|
||
happens.</p>
|
||
<p>The easiest way I've found to do this is by adding a <code>std::cout</code> statement inside <code>MaterialBuilder</code>,
|
||
right after <code>sg.createFragmentProgram</code> or <code>sg.createVertexProgram</code> is called. For example,</p>
|
||
<pre><code>if (v.variant.key == 8u) { std::cout << shader << std::endl; }
|
||
</code></pre>
|
||
<p>Then, I invoke <code>matc</code> and redirect stdout to a .frag or .vert file.</p>
|
||
<p>Note that gltfio material "templates" first go through a build step. After building gltfio, the
|
||
gltfio Filament materials are output to:</p>
|
||
<pre><code>out/cmake-release/libs/gltfio/*.mat
|
||
</code></pre>
|
||
<p>One of these materials can be compiled with the following command:</p>
|
||
<pre><code>matc \
|
||
-TCUSTOM_PARAMS="// no custom params" \
|
||
-TCUSTOM_VERTEX="// no custom vertex" \
|
||
-TCUSTOM_FRAGMENT="// no custom fragment" \
|
||
-TDOUBLESIDED=false \
|
||
-TTRANSPARENCY=default \
|
||
-TSHADINGMODEL=unlit \
|
||
-TBLENDING=opaque \
|
||
--platform mobile --api metal -o temp.filamat \
|
||
unlit_opaque.mat
|
||
</code></pre>
|
||
<h2 id="reproduce-the-compilation-error"><a class="header" href="#reproduce-the-compilation-error">Reproduce the compilation error</a></h2>
|
||
<p>The goal is to generate a .spv file that doesn't pass validation (through the spirv-val tool).</p>
|
||
<p>Reproducing the error usually involves a few steps:</p>
|
||
<ol>
|
||
<li>Compile the raw GLSL shader into SPIR-V.</li>
|
||
</ol>
|
||
<pre><code>glslangValidator -V -o unoptimized.spv in.frag
|
||
</code></pre>
|
||
<ol start="2">
|
||
<li>Optimize for performance.</li>
|
||
</ol>
|
||
<pre><code>spirv-opt -Oconfig=optimizations.cfg unoptimized.spv -o optimized.spv
|
||
</code></pre>
|
||
<p>See <a href="optimizations.cfg">optimizations.cfg</a> for a template. This file should contain the same list of optimizations that
|
||
Filament employs. This should match the same optimizations specified in <code>GLSLPostProcessor</code>, for
|
||
example, <code>GLSLPostProcessor::registerPerformancePasses</code> or <code>GLSLPostProcessor::registerSizePasses</code>.</p>
|
||
<ol start="3">
|
||
<li>For shaders targeting Metal, convert relaxed ops to half.</li>
|
||
</ol>
|
||
<pre><code>spirv-opt \
|
||
--convert-relaxed-to-half \
|
||
--simplify-instructions \
|
||
--redundancy-elimination \
|
||
--eliminate-dead-code-aggressive \
|
||
optimized.spv \
|
||
-o half.spv
|
||
</code></pre>
|
||
<ol start="4">
|
||
<li>Finally, validate the final SPIR-V.</li>
|
||
</ol>
|
||
<pre><code>spirv-val half.spv
|
||
</code></pre>
|
||
<ol start="5">
|
||
<li>Sometimes validation will still pass, but still generate invalid shaders after cross-compiling.
|
||
In these cases, you'll need to cross compile to the target language and manually pick out errors
|
||
in the generated shader.</li>
|
||
</ol>
|
||
<pre><code># for OpenGL
|
||
spirv-cross optimized.spv > optimized.frag
|
||
|
||
# for OpenGL ES
|
||
spirv-cross --es optimized.spv > optimized.frag
|
||
|
||
# for MSL
|
||
spirv-cross --msl optimized.spv > optimized.metal
|
||
</code></pre>
|
||
<p>To invoke Apple's compiler to compile MSL, you can run:</p>
|
||
<pre><code>xcrun -sdk macosx metal -c optimized.metal -o /dev/null
|
||
</code></pre>
|
||
<h2 id="clean-up-the-shader-for-a-bug-report"><a class="header" href="#clean-up-the-shader-for-a-bug-report">Clean up the shader for a bug report</a></h2>
|
||
<p>These commands will run the preprocessor only on <code>in.frag</code>, and remove any empty lines.</p>
|
||
<pre><code>glslangValidator -E in.frag > preprocessed.frag
|
||
sed '/^$/d' preprocessed.frag > preprocessed_small.frag
|
||
</code></pre>
|
||
<p>You can also run <code>clang-format</code> on the preprocessed shader to make it easier to read:</p>
|
||
<pre><code>clang-format -i preprocessed_small.frag
|
||
</code></pre>
|
||
<p>I always try to "whittle down" the shader to a smaller version that still reproduces the error. This
|
||
might make it a bit easier on the Khronos team to diagnose the issue. I typically follow these steps
|
||
in a loop until I'm satisfied:</p>
|
||
<ol>
|
||
<li>Delete an unnecessary part of the shader</li>
|
||
<li>Run the steps to reproduce the error</li>
|
||
<li>If the error still reproduces, repeat</li>
|
||
<li>Otherwise, undo the change and make a smaller change</li>
|
||
</ol>
|
||
<p>There's also a <a href="https://github.com/KhronosGroup/SPIRV-Tools#reducer">Reducer</a> tool that's part of
|
||
SPIRV-Tools which can be used to automate these steps. I haven't experimented much with this, but it
|
||
seems promising.</p>
|
||
<h2 id="submit-an-issue-with-the-relevant-khronos-repository"><a class="header" href="#submit-an-issue-with-the-relevant-khronos-repository">Submit an Issue with the relevant Khronos repository</a></h2>
|
||
<p>See some example issues that have been filed in the past:</p>
|
||
<ul>
|
||
<li>https://github.com/KhronosGroup/SPIRV-Cross/issues/1935</li>
|
||
<li>https://github.com/KhronosGroup/SPIRV-Cross/issues/1088</li>
|
||
<li>https://github.com/KhronosGroup/SPIRV-Cross/issues/1026</li>
|
||
<li>https://github.com/KhronosGroup/SPIRV-Tools/issues/4452</li>
|
||
<li>https://github.com/KhronosGroup/SPIRV-Tools/issues/3406</li>
|
||
<li>https://github.com/KhronosGroup/SPIRV-Tools/issues/3099</li>
|
||
<li>https://github.com/KhronosGroup/SPIRV-Tools/issues/5044</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="versioning"><a class="header" href="#versioning">Versioning</a></h1>
|
||
<p>Filament uses a 3-number versioning scheme that superficially resembles a <a href="https://semver.org/">semantic
|
||
version</a> but is actually more interesting because of our material system. Here
|
||
are the guidelines:</p>
|
||
<ul>
|
||
<li>Increment the <strong>most significant</strong> number only when making a non-backwards compatible API change,
|
||
or when introducing a major new API.</li>
|
||
<li>Increment the <strong>middle number</strong> only when making a non-backwards compatible change to the material
|
||
system. When this number gets bumped, users need to rebuild their mat files. Reset the middle
|
||
number to zero when the most significant number has been incremented.</li>
|
||
<li>Increment the <strong>least significant</strong> number each time a new release is published. Reset this number
|
||
to zero if one of the other two numbers have been incremented.</li>
|
||
</ul>
|
||
<h2 id="material-versioning"><a class="header" href="#material-versioning">Material Versioning</a></h2>
|
||
<p>Additionally, the Filament renderer and material compiler internally contain a standalone integer
|
||
called <code>MATERIAL_VERSION</code>, defined in <code>MaterialEnums.h</code>. This should be incremented every time we
|
||
change the middle number in the public-facing version.</p>
|
||
<p>When a material version mismatch is detected at run time, a panic is triggered, even in release
|
||
builds. Therefore we should increment this only when making a serious breaking change to the
|
||
material system (e.g. changing the size of a uniform block). Cosmetic shader changes usually do
|
||
not merit a change to the material version number.</p>
|
||
<p>Currently our material archives have two version chunks, one for "normal" materials and one for
|
||
post-process materials. However for now these two numbers must be set to the same value.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="vulkan-backend"><a class="header" href="#vulkan-backend">Vulkan Backend</a></h1>
|
||
<p>The <a href="./Vulkan.html">Vulkan Backend</a> document describes the state of Vulkan
|
||
support in Filament. It is written in
|
||
<a href="https://casual-effects.com/markdeep/">Markdeep</a> and cannot be included directly
|
||
in this book.</p>
|
||
|
||
</main>
|
||
|
||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||
<!-- Mobile navigation buttons -->
|
||
|
||
|
||
<div style="clear: both"></div>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
|
||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||
|
||
</nav>
|
||
|
||
</div>
|
||
|
||
<!-- Livereload script (if served using the cli tool) -->
|
||
<script>
|
||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
|
||
const socket = new WebSocket(wsAddress);
|
||
socket.onmessage = function (event) {
|
||
if (event.data === "reload") {
|
||
socket.close();
|
||
location.reload();
|
||
}
|
||
};
|
||
|
||
window.onbeforeunload = function() {
|
||
socket.close();
|
||
}
|
||
</script>
|
||
|
||
|
||
|
||
<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 -->
|
||
|
||
<script>
|
||
window.addEventListener('load', function() {
|
||
MathJax.Hub.Register.StartupHook('End', function() {
|
||
window.setTimeout(window.print, 100);
|
||
});
|
||
});
|
||
</script>
|
||
|
||
</div>
|
||
</body>
|
||
</html>
|