Files
filament/docs/samples/web/suzanne.html
Filament Bot 0c9be998d2 [automated] Updating /docs due to commit a73957a
Full commit hash is a73957a590

DOCS_ALLOW_DIRECT_EDITS
2026-03-26 00:21:11 +00:00

604 lines
31 KiB
HTML

<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>suzanne - Filament</title>
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="shortcut icon" href="../../favicon.png">
<link rel="stylesheet" href="../../css/variables.css">
<link rel="stylesheet" href="../../css/general.css">
<link rel="stylesheet" href="../../css/chrome.css">
<!-- Fonts -->
<link rel="stylesheet" href="../../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../../highlight.css">
<link rel="stylesheet" href="../../tomorrow-night.css">
<link rel="stylesheet" href="../../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- MathJax -->
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<!-- Provide site root to javascript -->
<script>
var path_to_root = "../../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
</script>
<!-- Start loading toc.js asap -->
<script src="../../toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div style="display:flex;align-items:center;justify-content:center">
<img class="flogo" src="../../images/filament_logo_small.png"></img>
</div>
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<!-- Filament: disable themes because the markdeep part does not look good for dark themes -->
<!--
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
-->
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Filament</h1>
<div class="right-buttons">
<a href="https://github.com/google/filament" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<div class="demo_frame" style="width:100%; height:400px; border: 1px solid black; position: relative;">
<canvas id="demo-canvas" style="width:100%; height:100%; touch-action: none;"></canvas>
</div>
<script src="../../web/lib/filament.js"></script>
<script src="../../web/lib/gl-matrix-min.js"></script>
<script src="../../web/lib/gltumble.min.js"></script>
<script>
// We wrap the demo code so it applies to demo-canvas instead of generic canvas
(function() {
const originalGetElementsByTagName = document.getElementsByTagName;
document.getElementsByTagName = function(tag) {
if (tag === 'canvas') return [document.getElementById('demo-canvas')];
return originalGetElementsByTagName.call(document, tag);
};
//
const albedo_suffix = Filament.getSupportedFormatSuffix('astc s3tc_srgb');
const texture_suffix = Filament.getSupportedFormatSuffix('etc');
//
const ibl_url = '../../web/assets/suzanne/venetian_crossroads_2k/venetian_crossroads_2k_ibl.ktx';
const sky_small_url = '../../web/assets/suzanne/venetian_crossroads_2k/venetian_crossroads_2k_skybox_tiny.ktx';
const sky_large_url = '../../web/assets/suzanne/venetian_crossroads_2k/venetian_crossroads_2k_skybox.ktx';
const albedo_url = `../../web/assets/suzanne/albedo.ktx2`;
const ao_url = `../../web/assets/suzanne/ao.ktx2`;
const metallic_url = `../../web/assets/suzanne/metallic.ktx2`;
const normal_url = `../../web/assets/suzanne/normal.ktx2`;
const roughness_url = `../../web/assets/suzanne/roughness.ktx2`;
const filamat_url = '../../web/assets/suzanne/textured.filamat';
const filamesh_url = '../../web/assets/suzanne/suzanne.filamesh';
//
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;
//
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);
this.trackball = new Trackball(canvas, {startSpin: 0.035});
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);
});
//
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() {
const tcm = this.engine.getTransformManager();
const inst = tcm.getInstance(this.suzanne);
tcm.setTransform(inst, this.trackball.getMatrix());
inst.delete();
this.renderer.render(this.swapChain, this.view);
window.requestAnimationFrame(this.render);
}
//
resize() {
const dpr = window.devicePixelRatio;
const width = this.canvas.width = this.canvas.clientWidth * dpr;
const height = this.canvas.height = this.canvas.clientHeight * 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);
}
}
//
})();
</script>
<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 [previous tutorial], 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"><a class="header" href="#bake-environment-map">Bake environment map</a></h2>
<p>Much like the [previous tutorial] 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 [previous
tutorial]. 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">const albedo_suffix = Filament.getSupportedFormatSuffix('astc s3tc_srgb');
const texture_suffix = Filament.getSupportedFormatSuffix('etc');
const ibl_url = '../../web/assets/suzanne/venetian_crossroads_2k/venetian_crossroads_2k_ibl.ktx';
const sky_small_url = '../../web/assets/suzanne/venetian_crossroads_2k/venetian_crossroads_2k_skybox_tiny.ktx';
const sky_large_url = '../../web/assets/suzanne/venetian_crossroads_2k/venetian_crossroads_2k_skybox.ktx';
const albedo_url = `../../web/assets/suzanne/albedo.ktx2`;
const ao_url = `../../web/assets/suzanne/ao.ktx2`;
const metallic_url = `../../web/assets/suzanne/metallic.ktx2`;
const normal_url = `../../web/assets/suzanne/normal.ktx2`;
const roughness_url = `../../web/assets/suzanne/roughness.ktx2`;
const filamat_url = '../../web/assets/suzanne/textured.filamat';
const filamesh_url = '../../web/assets/suzanne/suzanne.filamesh';
Filament.init([ filamat_url, filamesh_url, sky_small_url, ibl_url ], () =&gt; {
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;
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);
this.trackball = new Trackball(canvas, {startSpin: 0.035});
Filament.fetch([sky_large_url, albedo_url, roughness_url, metallic_url, normal_url, ao_url], () =&gt; {
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);
});
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() {
const tcm = this.engine.getTransformManager();
const inst = tcm.getInstance(this.suzanne);
tcm.setTransform(inst, this.trackball.getMatrix());
inst.delete();
this.renderer.render(this.swapChain, this.view);
window.requestAnimationFrame(this.render);
}
resize() {
const dpr = window.devicePixelRatio;
const width = this.canvas.width = this.canvas.clientWidth * dpr;
const height = this.canvas.height = this.canvas.clientHeight * dpr;
this.view.setViewport([0, 0, width, height]);
const aspect = width / height;
const Fov = Filament.Camera$Fov, fov = aspect &lt; 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">const albedo_suffix = Filament.getSupportedFormatSuffix('astc s3tc_srgb');
const texture_suffix = Filament.getSupportedFormatSuffix('etc');
const ibl_url = '../../web/assets/suzanne/venetian_crossroads_2k/venetian_crossroads_2k_ibl.ktx';
const sky_small_url = '../../web/assets/suzanne/venetian_crossroads_2k/venetian_crossroads_2k_skybox_tiny.ktx';
const sky_large_url = '../../web/assets/suzanne/venetian_crossroads_2k/venetian_crossroads_2k_skybox.ktx';
const albedo_url = `../../web/assets/suzanne/albedo.ktx2`;
const ao_url = `../../web/assets/suzanne/ao.ktx2`;
const metallic_url = `../../web/assets/suzanne/metallic.ktx2`;
const normal_url = `../../web/assets/suzanne/normal.ktx2`;
const roughness_url = `../../web/assets/suzanne/roughness.ktx2`;
const filamat_url = '../../web/assets/suzanne/textured.filamat';
const filamesh_url = '../../web/assets/suzanne/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">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">Filament.fetch([sky_large_url, albedo_url, roughness_url, metallic_url, normal_url, ao_url], () =&gt; {
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">&lt;script src="//unpkg.com/gltumble"&gt;&lt;/script&gt;
</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">this.trackball = new Trackball(canvas, {startSpin: 0.035});
</code></pre>
<pre><code class="language-js">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.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../../samples/web/redball.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../samples/web/samples.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../../samples/web/redball.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../samples/web/samples.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="../../elasticlunr.min.js"></script>
<script src="../../mark.min.js"></script>
<script src="../../searcher.js"></script>
<script src="../../clipboard.min.js"></script>
<script src="../../highlight.js"></script>
<script src="../../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>