604 lines
31 KiB
HTML
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 ], () => {
|
|
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);
|
|
}
|
|
}
|
|
</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], () => {
|
|
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">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>
|