* Turn on shaders optimization by default Release builds of Filament only work well with optimize shaders, turning optimizations on by default will help avoid mismatches. This change also adds -g to disable all optimizations, for debug builds. * Use -g on debug builds * Use -g on debug builds * Update tutorial_redball.md to remove matc's -O * Update tutorial_suzanne.md to remove matc's -O * Use -g in debug builds
13 KiB
This tutorial will describe how to create the suzanne demo, introducing you to compressed textures, mipmap generation, asynchronous texture loading, and trackball rotation.
Much like the previous tutorial, you'll need to use the command-line tools that can be found in
the appropriate Filament release for your development machine. In addition to matc and cmgen,
we'll also be using filamesh and mipgen.
Create filamesh file
Filament does not have an asset loading system, but it does provide a binary mesh format
called filamesh for simple use cases. Let's create a compressed filamesh file for suzanne by
converting this OBJ file:
filamesh --compress monkey.obj suzanne.filamesh
Create mipmapped textures
Next, let's create mipmapped KTX files using filament's mipgen 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 monkey folder, then do:
# Create mipmaps for base color and two compressed variants.
mipgen albedo.png albedo.ktx
mipgen --compression=astc_fast_ldr_4x4 albedo.png albedo_astc.ktx
mipgen --compression=s3tc_rgb_dxt1 albedo.png albedo_s3tc.ktx
# 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=etc_rgb8_normalxyz_40 \
normal.png normal_etc.ktx
# Create mipmaps for the single-component roughness map and a compressed variant.
mipgen --grayscale roughness.png roughness.ktx
mipgen --grayscale --compression=etc_r11_numeric_40 roughness.png roughness_etc.ktx
# Create mipmaps for the single-component metallic map and a compressed variant.
mipgen --grayscale metallic.png metallic.ktx
mipgen --grayscale --compression=etc_r11_numeric_40 metallic.png metallic_etc.ktx
# Create mipmaps for the single-component occlusion map and a compressed variant.
mipgen --grayscale ao.png ao.ktx
mipgen --grayscale --compression=etc_r11_numeric_40 ao.png ao_etc.ktx
For more information on mipgen's arguments and supported formats, do mipgen --help.
In a production setting, you'd want to invoke these commands with a script or build system.
Bake environment map
Much like the previous tutorial we need to use Filament's cmgen tool to produce cubemap files,
but this time we'll create compressed variants.
Download syferfontein_18d_clear_2k.hdr, then invoke the following commands in your terminal.
# Create S3TC variant of the IBL, then rename it to have a _s3tc suffix.
cmgen -x . --format=ktx --size=256 --extract-blur=0.1 --compression=s3tc_rgba_dxt5 \
syferfontein_18d_clear_2k.hdr
cd syfer* ; mv syfer*_ibl.ktx syferfontein_18d_clear_2k_ibl_s3tc.ktx ; cd -
# Create ETC variant of the IBL, then rename it to have a _s3tc suffix.
cmgen -x . --format=ktx --size=256 --extract-blur=0.1 --compression=etc_rgba8_rgba_40 \
syferfontein_18d_clear_2k.hdr
cd syfer* ; mv syfer*_ibl.ktx syferfontein_18d_clear_2k_ibl_etc.ktx ; cd -
# Create small uncompressed Skybox variant, then rename it to have a _tiny suffix.
cmgen -x . --format=ktx --size=64 --extract-blur=0.1 syferfontein_18d_clear_2k.hdr
cd syfer* ; mv syfer*_ibl.ktx syferfontein_18d_clear_2k_skybox_tiny.ktx ; cd -
# Create full-size uncompressed Skybox and IBL
cmgen -x . --format=ktx --size=256 --extract-blur=0.1 syferfontein_18d_clear_2k.hdr
Define textured material
You might recall the filamat file we generated in the previous tutorial for red plastic. For this
demo, we'll create a material that uses textures for several parameters.
Create the following text file and call it textured.mat. Note that our material definition now
requires a uv0 attribute.
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;
}
}
Next, invoke matc as follows.
matc -a opengl -p mobile -o textured.filamat textured.mat
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 Filament Material System.
Create app skeleton
Create a text file called suzanne.html and copy over the HTML that we used in the previous
tutorial. Change the last script tag from redball.js to suzanne.js. Next, create suzanne.js
with the following content.
// 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();
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);
}
}
Our app will use 10 downloaded assets, but it only requires 4 of them to be present for App
construction. We'll download the other 6 assets after construction. By using a progressive loading
strategy, we can reduce the perceived load time.
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.
To help you download only the texture assets that you need, Filament provides a
getSupportedFormatSuffix function. This takes a space-separated list of desired format types
(etc, s3tc, or astc) that the app developer knows is available from the server. The function
performs an intersection of the desired set with the supported set, then returns an appropriate
string -- which might be empty.
In our case, we know that our web server will have etc and s3tc variants for the IBL, astc and
s3tc variants for albedo, and etc variants for the other textures. The uncompressed variants
(empty string) are always available as a last resort. Go ahead and replace the declare asset
URLs comment with the following snippet.
const ibl_suffix = Filament.getSupportedFormatSuffix('etc s3tc');
const albedo_suffix = Filament.getSupportedFormatSuffix('astc s3tc');
const texture_suffix = Filament.getSupportedFormatSuffix('etc');
const environ = 'syferfontein_18d_clear_2k'
const ibl_url = `${environ}/${environ}_ibl${ibl_suffix}.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';
Create skybox and IBL
Next, let's create the low-resolution skybox and IBL in the App constructor.
this.skybox = this.engine.createSkyFromKtx(sky_small_url);
this.scene.setSkybox(this.skybox);
this.indirectLight = this.engine.createIblFromKtx(ibl_url);
this.indirectLight.setIntensity(100000);
this.scene.setIndirectLight(this.indirectLight);
This allows users to see a reasonable background fairly quickly, before larger assets have finished loading in.
Fetch assets asychronously
Next we'll invoke the Filament.fetch function from within the app constructor. This function is
very similar to Filament.init. It takes a list of asset URLs and a callback function that triggers
when the assets have finished downloading.
In our callback, we'll make several setTextureParameter 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.
Filament.fetch([sky_large_url, albedo_url, roughness_url, metallic_url, normal_url, ao_url], () => {
const albedo = this.engine.createTextureFromKtx(albedo_url, {srgb: true});
const roughness = this.engine.createTextureFromKtx(roughness_url);
const metallic = this.engine.createTextureFromKtx(metallic_url);
const normal = this.engine.createTextureFromKtx(normal_url);
const ao = this.engine.createTextureFromKtx(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.createSkyFromKtx(sky_large_url);
this.scene.setSkybox(this.skybox);
this.scene.addEntity(this.suzanne);
});
Introduce trackball rotation
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.
<script src="https://unpkg.com/gltumble"></script>
Next, replace the initialize gltumble and apply gltumble matrix comments with the following two code snippets.
this.trackball = new Trackball(canvas, {startSpin: 0.035});
const tcm = this.engine.getTransformManager();
const inst = tcm.getInstance(this.suzanne);
tcm.setTransform(inst, this.trackball.getMatrix());
inst.delete();
That's it, we now have a fast-loading interactive demo. The complete JavaScript file is available here.