404: Page not found
+Sorry, we've misplaced that URL or it's pointing to something that doesn't exist. Head back home to try finding it again.
+diff --git a/docs/404.html b/docs/404.html new file mode 100644 index 0000000000..1c769dffa1 --- /dev/null +++ b/docs/404.html @@ -0,0 +1,69 @@ + + +
+ + + + + + + +Sorry, we've misplaced that URL or it's pointing to something that doesn't exist. Head back home to try finding it again.
+`, + * or to a parent if there are multiple elements to show. + */ + +.message { + margin-bottom: 1rem; + padding: 1rem; + color: #717171; + background-color: #f9f9f9; +} + + +/* + * Container + * + * Center the page content. + */ + +.container { + max-width: 38rem; + padding-left: 1rem; + padding-right: 1rem; + margin-left: auto; + margin-right: auto; +} + + +/* + * Masthead + * + * Super small header above the content for site name and short description. + */ + +.masthead { + padding-top: 1rem; + padding-bottom: 1rem; + margin-bottom: 3rem; +} +.masthead-title { + margin-top: 0; + margin-bottom: 0; + color: #505050; +} +.masthead-title a { + color: #505050; +} +.masthead-title small { + font-size: 75%; + font-weight: 400; + color: #c0c0c0; + letter-spacing: 0; +} + + +/* + * Posts and pages + * + * Each post is wrapped in `.post` and is used on default and post layouts. Each + * page is wrapped in `.page` and is only used on the page layout. + */ + +.page, +.post { + margin-bottom: 4em; +} + +/* Blog post or page title */ +.page-title, +.post-title, +.post-title a { + color: #303030; +} +.page-title, +.post-title { + margin-top: 0; +} + +/* Meta data line below post title */ +.post-date { + display: block; + margin-top: -.5rem; + margin-bottom: 1rem; + color: #9a9a9a; +} + +/* Related posts */ +.related { + padding-top: 2rem; + padding-bottom: 2rem; + border-top: 1px solid #eee; +} +.related-posts { + padding-left: 0; + list-style: none; +} +.related-posts h3 { + margin-top: 0; +} +.related-posts li small { + font-size: 75%; + color: #999; +} +.related-posts li a:hover { + color: #268bd2; + text-decoration: none; +} +.related-posts li a:hover small { + color: inherit; +} + + +/* + * Pagination + * + * Super lightweight (HTML-wise) blog pagination. `span`s are provide for when + * there are no more previous or next posts to show. + */ + +.pagination { + overflow: hidden; /* clearfix */ + margin-left: -1rem; + margin-right: -1rem; + font-family: "Open Sans", Helvetica, Arial, sans-serif; + color: #ccc; + text-align: center; +} + +/* Pagination items can be `span`s or `a`s */ +.pagination-item { + display: block; + padding: 1rem; + border: 1px solid #eee; +} +.pagination-item:first-child { + margin-bottom: -1px; +} + +/* Only provide a hover state for linked pagination items */ +a.pagination-item:hover { + background-color: #f5f5f5; +} + +@media (min-width: 30em) { + .pagination { + margin: 3rem 0; + } + .pagination-item { + float: left; + width: 50%; + } + .pagination-item:first-child { + margin-bottom: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + } + .pagination-item:last-child { + margin-left: -1px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } +} diff --git a/docs/css/print.css b/docs/css/print.css new file mode 100644 index 0000000000..da8f1dfedd --- /dev/null +++ b/docs/css/print.css @@ -0,0 +1,19 @@ +.sidebar { + display: none !important; +} + +.content { + margin: 0 auto; + width: 100%; + float: none; + display: initial; +} + +.container { + width: 100%; + float: none; + display: initial; + padding-left: 1rem; + padding-right: 1rem; + margin: 0 auto; +} diff --git a/docs/css/syntax.css b/docs/css/syntax.css new file mode 100644 index 0000000000..1264b87f14 --- /dev/null +++ b/docs/css/syntax.css @@ -0,0 +1,66 @@ +.hll { background-color: #ffffcc } + /*{ background: #f0f3f3; }*/ +.c { color: #999; } /* Comment */ +.err { color: #AA0000; background-color: #FFAAAA } /* Error */ +.k { color: #006699; } /* Keyword */ +.o { color: #555555 } /* Operator */ +.cm { color: #0099FF; font-style: italic } /* Comment.Multiline */ +.cp { color: #009999 } /* Comment.Preproc */ +.c1 { color: #999; } /* Comment.Single */ +.cs { color: #999; } /* Comment.Special */ +.gd { background-color: #FFCCCC; border: 1px solid #CC0000 } /* Generic.Deleted */ +.ge { font-style: italic } /* Generic.Emph */ +.gr { color: #FF0000 } /* Generic.Error */ +.gh { color: #003300; } /* Generic.Heading */ +.gi { background-color: #CCFFCC; border: 1px solid #00CC00 } /* Generic.Inserted */ +.go { color: #AAAAAA } /* Generic.Output */ +.gp { color: #000099; } /* Generic.Prompt */ +.gs { } /* Generic.Strong */ +.gu { color: #003300; } /* Generic.Subheading */ +.gt { color: #99CC66 } /* Generic.Traceback */ +.kc { color: #006699; } /* Keyword.Constant */ +.kd { color: #006699; } /* Keyword.Declaration */ +.kn { color: #006699; } /* Keyword.Namespace */ +.kp { color: #006699 } /* Keyword.Pseudo */ +.kr { color: #006699; } /* Keyword.Reserved */ +.kt { color: #007788; } /* Keyword.Type */ +.m { color: #FF6600 } /* Literal.Number */ +.s { color: #d44950 } /* Literal.String */ +.na { color: #4f9fcf } /* Name.Attribute */ +.nb { color: #336666 } /* Name.Builtin */ +.nc { color: #00AA88; } /* Name.Class */ +.no { color: #336600 } /* Name.Constant */ +.nd { color: #9999FF } /* Name.Decorator */ +.ni { color: #999999; } /* Name.Entity */ +.ne { color: #CC0000; } /* Name.Exception */ +.nf { color: #CC00FF } /* Name.Function */ +.nl { color: #9999FF } /* Name.Label */ +.nn { color: #00CCFF; } /* Name.Namespace */ +.nt { color: #2f6f9f; } /* Name.Tag */ +.nv { color: #003333 } /* Name.Variable */ +.ow { color: #000000; } /* Operator.Word */ +.w { color: #bbbbbb } /* Text.Whitespace */ +.mf { color: #FF6600 } /* Literal.Number.Float */ +.mh { color: #FF6600 } /* Literal.Number.Hex */ +.mi { color: #FF6600 } /* Literal.Number.Integer */ +.mo { color: #FF6600 } /* Literal.Number.Oct */ +.sb { color: #CC3300 } /* Literal.String.Backtick */ +.sc { color: #CC3300 } /* Literal.String.Char */ +.sd { color: #CC3300; font-style: italic } /* Literal.String.Doc */ +.s2 { color: #CC3300 } /* Literal.String.Double */ +.se { color: #CC3300; } /* Literal.String.Escape */ +.sh { color: #CC3300 } /* Literal.String.Heredoc */ +.si { color: #AA0000 } /* Literal.String.Interpol */ +.sx { color: #CC3300 } /* Literal.String.Other */ +.sr { color: #33AAAA } /* Literal.String.Regex */ +.s1 { color: #CC3300 } /* Literal.String.Single */ +.ss { color: #FFCC33 } /* Literal.String.Symbol */ +.bp { color: #336666 } /* Name.Builtin.Pseudo */ +.vc { color: #003333 } /* Name.Variable.Class */ +.vg { color: #003333 } /* Name.Variable.Global */ +.vi { color: #003333 } /* Name.Variable.Instance */ +.il { color: #FF6600 } /* Literal.Number.Integer.Long */ + +.css .o, +.css .o + .nt, +.css .nt + .nt { color: #999; } diff --git a/docs/documentation/index.html b/docs/documentation/index.html new file mode 100644 index 0000000000..41e1b33b0d --- /dev/null +++ b/docs/documentation/index.html @@ -0,0 +1,79 @@ + + +
+ + + + + + + ++Filament is a real-time physically-based renderer written in C++. It is mobile-first, but also +multi-platform. +
+ + + ++We are very conscientious about keeping Filament small, fast to load, and focused on +rendering. For example, Filament does not compile materials at run time. Instead, we provide a +command line tool +(matc) +that does this offline. +
+ ++For more information, see the +core documentation +and +JavaScript documentation. +Check out our +README +for downloads and build instructions. +
+All type names in this reference belong to the Filament namespace. For example, init actually refers to Filament.init.
+| + | + |
|---|---|
| BufferDescriptor | +Low level buffer wrapper. | +
| Camera | +Represents the eye through which the scene is viewed. | +
| Engine | +Central manager and resource owner. | +
| Entity | +Handle to an object consisting of a set of components. | +
| EntityManager | +Singleton used for constructing entities in Filament's ECS. | +
| IcoSphere | +Utility class for constructing spheres (requires glMatrix). | +
| IndexBuffer | +Array of 16-bit or 32-bit unsigned integers consumed by the GPU. | +
| KtxBundle | +In-memory representation of a KTX file. | +
| KtxInfo | +Property accessor for KTX header. | +
| PixelBufferDescriptor | +Low level pixel buffer wrapper. | +
| Renderer | +Represents the platform's native window. | +
| Scene | +Flat container of renderables and lights. | +
| SwapChain | +Represents the platform's native rendering surface. | +
| Texture | +2D image or cubemap that can be sampled by the GPU, possibly mipmapped. | +
| TransformManager | +Adds transform components to entities. | +
| TransformManager$Instance | +Component instance returned by TransformManager | +
| VertexBuffer | +Bundle of buffers and associated vertex attributes. | +
| View | +Encompasses all the state needed for rendering a Scene. | +
| + | + |
|---|---|
| Buffer | +Constructs a BufferDescriptor by copying a typed array into the WASM heap. | +
| CompressedPixelBuffer | +Constructs a PixelBufferDescriptor for compressed texture | +
| PixelBuffer | +Constructs a PixelBufferDescriptor by copying a typed array into the WASM heap. | +
| getSupportedFormatSuffix | +Generate a file suffix according to the texture format. | +
| getSupportedFormats | +Queries WebGL to check which compressed formats are supported. | +
| init | +Downloads assets, loads the Filament module, and invokes a callback when done. | +
| loadMathExtensions | +Extends the glMatrix math library. | +
| packSnorm16 | +Converts a float in [-1, +1] into a half-float. | +
| + | + |
|---|---|
| Camera$Fov | ++ |
| Camera$Projection | ++ |
| CompressedPixelDataType | ++ |
| IndexBuffer$IndexType | ++ |
| LightManager$Type | ++ |
| MagFilter | ++ |
| MinFilter | ++ |
| PixelDataFormat | ++ |
| PixelDataType | ++ |
| RenderableManager$PrimitiveType | ++ |
| RgbType | ++ |
| RgbaType | ++ |
| Texture$InternalFormat | ++ |
| Texture$Sampler | ++ |
| Texture$Usage | ++ |
| VertexAttribute | ++ |
| VertexBuffer$AttributeType | ++ |
| View$AntiAliasing | ++ |
| View$DepthPrepass | ++ |
| WrapMode | ++ |
Low level buffer wrapper.
+Clients should use the Buffer helper function to contruct BufferDescriptor objects.
+Represents the eye through which the scene is viewed.
+See also the Engine methods createCamera and destroyCamera.
Central manager and resource owner.
+rgbm boolean is the only option.renderable, vertexBuffer, and indexBuffer. These are of type Entity, VertexBuffer, and IndexBuffer.Handle to an object consisting of a set of components.
+To create an entity with no components, use EntityManager.
+Singleton used for constructing entities in Filament's ECS.
+Utility class for constructing spheres (requires glMatrix).
+The constructor takes an integer subdivision level, with 0 being an icosahedron. +Exposes three arrays as properties:
+icosphere.vertices Float32Array of XYZ coordinates.icosphere.tangents Uint16Array (interpreted as half-floats) encoding the surface orientation
+as quaternions.icosphere.triangles Uint16Array with triangle indices.Array of 16-bit or 32-bit unsigned integers consumed by the GPU.
+ +In-memory representation of a KTX file.
+Most clients should use one of the *fromKtx utility methods in the JavaScript Engine wrapper
+rather than interacting with KtxBundle directly.
Property accessor for KTX header.
+For example, ktxbundle.info().pixelWidth. See the
+KTX spec for the list of
+properties.
Low level pixel buffer wrapper.
+Clients should use the PixelBuffer helper function to contruct PixelBufferDescriptor objects.
+Represents the platform's native window.
+See also the Engine methods createRenderer and destroyRenderer.
Flat container of renderables and lights.
+See also the Engine methods createScene and destroyScene.
Represents the platform's native rendering surface.
+See also the Engine methods createSwapChain and destroySwapChain.
2D image or cubemap that can be sampled by the GPU, possibly mipmapped.
+Adds transform components to entities.
+setTransform.getInstance.Component instance returned by TransformManager
+getInstanceBe sure to call the instance's delete method when you're done with it.
Bundle of buffers and associated vertex attributes.
+ +Encompasses all the state needed for rendering a Scene.
+A view is associated with a particular Scene, Camera, and viewport.
+See also the Engine methods createView and destroyView.
Constructs a BufferDescriptor by copying a typed array into the WASM heap.
+Constructs a PixelBufferDescriptor for compressed texture
+data by copying a typed array into the WASM heap.
+Constructs a PixelBufferDescriptor by copying a typed array into the WASM heap.
+Generate a file suffix according to the texture format.
+Consumes a string describing desired formats and produces a file suffix depending on +which (if any) of the formats are actually supported by the WebGL implementation. This is +useful for compressed textures. For example, some platforms accept ETC and others accept S3TC.
+Queries WebGL to check which compressed formats are supported.
+Downloads assets, loads the Filament module, and invokes a callback when done.
+All JavaScript clients must call the init function, passing in a list of asset URL's and a
+callback. This callback gets invoked only after all assets have been downloaded and the Filament
+WebAssembly module has been loaded. Clients should only pass asset URL's that absolutely must
+be ready at initialization time.
+When the callback is called, each downloaded asset is available in the Filament.assets global
+object, which contains a mapping from URL's to Uint8Array objects.
Extends the glMatrix math library.
+Filament does not require its clients to use glMatrix, but if its usage is detected then
+the init function will automatically call loadMathExtensions.
+This defines the following functions:
Converts a float in [-1, +1] into a half-float.
+This tutorial will describe how to create the redball demo, introducing you to materials and +textures.
+You'll need to use a couple command-line tools: matc and cmgen. You can find these in the
+appropriate Filament release. You should choose the
+archive that corresponds to your development machine rather than the one for web.
The matc 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 Filament Material System.
Let's try out matc. Create the following file in your favorite text editor and call it
+plastic.mat.
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;
+ }
+}
+Next, invoke matc as follows.
matc -O -a opengl -p mobile -o plastic.filamat plastic.mat
+You should now have a material archive in your working directory, which we'll use later in the +tutorial.
+Next we'll use Filament's cmgen tool to consume a HDR environment map in latlong format, and
+produce two cubemap files: a mipmapped IBL and a blurry skybox.
Download pillars_2k.hdr, then invoke the following command in your terminal.
+cmgen -x . --format=ktx --size=256 --extract-blur=0.1 pillars_2k.hdr
+You should now have a pillars_2k folder containing a couple KTX files for the IBL and skybox, as
+well as a text file with spherical harmonics coefficients. Move the KTX files into your project
+folder. You can discard the text file because the IBL KTX contains these coefficients in its
+metadata.
Create a text file called redball.html and copy over the HTML that we used in the previous
+tutorial. Change the last script tag from triangle.js to redball.js.
Next, create redball.js with the following content.
Filament.init([ 'plastic.filamat', 'pillars_2k_ibl.ktx', 'pillars_2k_skybox.ktx' ], () => {
+ // 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();
+ 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);
+ }
+}
+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.
+Next let's create a material instance from the package that we built at the beginning the tutorial. +Replace the create material comment with the following snippet.
+const material = engine.createMaterial('plastic.filamat');
+const matinstance = material.createInstance();
+
+const red = [0.8, 0.0, 0.0];
+matinstance.setColorParameter("baseColor", Filament.RgbType.sRGB, red);
+matinstance.setFloatParameter("roughness", 0.5);
+matinstance.setFloatParameter("clearCoat", 1.0);
+matinstance.setFloatParameter("clearCoatRoughness", 0.3);
+The next step is to create a renderable for the sphere. To help with this, we'll use the IcoSphere
+utility class, whose constructor takes a LOD. Its job is to subdivide an icosadedron, producing
+three arrays:
icosphere.vertices Float32Array of XYZ coordinates.icosphere.tangents Uint16Array (interpreted as half-floats) encoding the surface orientation
+as quaternions.icosphere.triangles Uint16Array with triangle indices.Let's go ahead use these arrays to build the vertex buffer and index buffer. Replace create +sphere with the following snippet.
+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([ [-1, -1, -1], [1, 1, 1] ])
+ .material(0, matinstance)
+ .geometry(0, PrimitiveType.TRIANGLES, vb, ib)
+ .build(engine, renderable);
+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 setClearColor, like we
+did in the first tutorial.
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 create +lights comment with the following snippet.
+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);
+The SUN light source is similar to the DIRECTIONAL light source, but has some extra
+parameters because Filament will automatically draw a disk into the skybox.
Next we need to create an IndirectLight object from the KTX IBL. One way of doing this is the
+following (don't type this out, there's an easier way).
const format = Filament.PixelDataFormat.RGBM;
+const datatype = Filament.PixelDataType.UBYTE;
+
+// Create a Texture object for the mipmapped cubemap.
+const ibl_package = Filament.Buffer(Filament.assets['pillars_2k_ibl.ktx']);
+const iblktx = new Filament.KtxBundle(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)
+ .rgbm(true)
+ .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);
+Filament provides a JavaScript utility to make this simpler, +simply replace the create IBL comment with the following snippet.
+const indirectLight = engine.createIblFromKtx('pillars_2k_ibl.ktx');
+indirectLight.setIntensity(50000);
+scene.setIndirectLight(indirectLight);
+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:
+const sky_package = Filament.Buffer(Filament.assets['pillars_2k_skybox.ktx']);
+const skyktx = new Filament.KtxBundle(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)
+ .rgbm(true)
+ .build(engine);
+
+const uint8array = skyktx.getCubeBlob(0).getBytes();
+const pixelbuffer = Filament.PixelBuffer(uint8array, format, datatype);
+skytex.setImageCube(engine, 0, pixelbuffer);
+Filament provides a Javascript utility to make this easier. +Replace create skybox with the following.
+const skybox = engine.createSkyFromKtx('pillars_2k_skybox.ktx');
+scene.setSkybox(skybox);
+That's it, we now have a shiny red ball floating in an environment! The complete JavaScript +file is available here.
+ + + diff --git a/docs/webgl/tutorial_redball.js b/docs/webgl/tutorial_redball.js new file mode 100644 index 0000000000..edd160a1f8 --- /dev/null +++ b/docs/webgl/tutorial_redball.js @@ -0,0 +1,103 @@ +Filament.init(['plastic.filamat', 'pillars_2k/pillars_2k_ibl.ktx', 'pillars_2k/pillars_2k_skybox.ktx'], () => { + // 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(); + const material = engine.createMaterial('plastic.filamat'); + const matinstance = material.createInstance(); + const red = [0.8, 0.0, 0.0]; + matinstance.setColorParameter("baseColor", Filament.RgbType.sRGB, red); + matinstance.setFloatParameter("roughness", 0.5); + matinstance.setFloatParameter("clearCoat", 1.0); + matinstance.setFloatParameter("clearCoatRoughness", 0.3); + 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([ + [-1, -1, -1], + [1, 1, 1] + ]) + .material(0, matinstance) + .geometry(0, PrimitiveType.TRIANGLES, vb, ib) + .build(engine, renderable); + 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); + const indirectLight = engine.createIblFromKtx('pillars_2k/pillars_2k_ibl.ktx'); + indirectLight.setIntensity(50000); + scene.setIndirectLight(indirectLight); + const skybox = engine.createSkyFromKtx('pillars_2k/pillars_2k_skybox.ktx'); + scene.setSkybox(skybox); + this.swapChain = engine.createSwapChain(); + this.renderer = engine.createRenderer(); + this.camera = engine.createCamera(); + 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); + } +} diff --git a/docs/webgl/tutorial_triangle.html b/docs/webgl/tutorial_triangle.html new file mode 100644 index 0000000000..67f3a69bcc --- /dev/null +++ b/docs/webgl/tutorial_triangle.html @@ -0,0 +1,235 @@ + + + + + + + +The markdown source for this tutorial is not only used to generate this
+web page, it's 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
+// TODO: <some task>. These are special markers that get replaced by
+subsequent code blocks.
First, create a text file called triangle.html and fill it with the following HTML. This creates
+a mobile-friendly page with a full-screen canvas.
<!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="//unpkg.com/filament/filament.js"></script>
+ <script src="//unpkg.com/gl-matrix@2.8.1/dist/gl-matrix-min.js"></script>
+ <script src="triangle.js"></script>
+</body>
+</html>
+The above HTML loads three JavaScript files:
+filament.js does a couple things:
+gl-matrix-min.js is a small library that provides vector math functionality.triangle.js will contain your application code.Go ahead and create triangle.js with the following content.
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() } );
+The two calls to bind() allow us to pass instance methods as callbacks for animation and resize
+events.
Filament.init() consumes two things: a list of asset URLs and a callback.
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 App object, since we'll do most of the
+work in its constructor. We also set the app instance into a Window property to make it accessible
+from the developer console.
Go ahead and download triangle.filamat and place it in your project folder. +This is a material package, 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.
+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:
+python3 -m http.server # Python 3
+python -m SimpleHTTPServer # Python 2.7
+To see if this works, navigate to http://localhost:8000 and check if you +can load the page without any errors appearing in the developer console.
+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.
+this.canvas = document.getElementsByTagName('canvas')[0];
+const engine = this.engine = Filament.Engine.create(this.canvas);
+The above snippet creates the Engine by passing it a canvas DOM object. The engine needs the
+canvas in order to create a WebGL 2.0 context in its contructor.
The engine is a factory for many Filament entities, including Scene, which is a flat container of
+entities. Let's go ahead and create a scene, then add a blank entity called triangle into the
+scene.
this.scene = engine.createScene();
+this.triangle = Filament.EntityManager.get().create();
+this.scene.addEntity(this.triangle);
+Filament uses an Entity-Component System. +The triangle entity in the above snippet does not yet have an associated component. Later in the +tutorial we will make it into a renderable. Renderables are entities that have associated draw +calls.
+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.
+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]);
+Next we'll use the positions and colors buffers to create a single VertexBuffer object.
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);
+The above snippet first creates aliases for two enum types, then constructs the vertex buffer using
+its Builder method. After that, it pushes two buffer objects into the appropriate slots using
+setBufferAt.
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.
+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.
+Next we'll construct an index buffer. The index buffer for our triangle is trivial: it simply holds +the integers 0,1,2.
+this.ib = Filament.IndexBuffer.Builder()
+ .indexCount(3)
+ .bufferType(Filament.IndexBuffer$IndexType.USHORT)
+ .build(engine);
+
+this.ib.setBuffer(engine, new Uint16Array([0, 1, 2]));
+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).
+Next let's construct an actual Material from the material package that was downloaded (the
+material is an object; the package is just a binary blob), then extract the default
+MaterialInstance 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.
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.
+const mat = engine.createMaterial('triangle.filamat');
+const matinst = mat.getDefaultInstance();
+Filament.RenderableManager.Builder(1)
+ .boundingBox([[ -1, -1, -1 ], [ 1, 1, 1 ]])
+ .material(0, matinst)
+ .geometry(0, Filament.RenderableManager$PrimitiveType.TRIANGLES, this.vb, this.ib)
+ .build(engine, this.triangle);
+Next let's wrap up the initialization routine by creating the swap chain, renderer, camera, and +view.
+this.swapChain = engine.createSwapChain();
+this.renderer = engine.createRenderer();
+this.camera = engine.createCamera();
+this.view = engine.createView();
+this.view.setCamera(this.camera);
+this.view.setScene(this.scene);
+this.view.setClearColor([0.1, 0.2, 0.3, 1.0]); // blue-green background
+this.resize(); // adjust the initial viewport
+At this point, we're done creating all Filament entities, and the code should run without errors. +However the canvas is still blank!
+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.
+render() {
+ // TODO: render scene
+ window.requestAnimationFrame(this.render);
+}
+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.
+// 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);
+The first half of our render method obtains the transform component of the triangle entity and uses +gl-matrix to generate a rotation matrix.
+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 if statement.
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 devicePixelRatio into account for high-DPI
+displays. It also adjusts the camera frustum accordingly.
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);
+You should now have a spinning triangle! The completed JavaScript is available +here. In the next tutorial, we'll take a closer look at Filament +materials and 3D rendering.
+ + + diff --git a/docs/webgl/tutorial_triangle.js b/docs/webgl/tutorial_triangle.js new file mode 100644 index 0000000000..92435aa61c --- /dev/null +++ b/docs/webgl/tutorial_triangle.js @@ -0,0 +1,78 @@ +class App { + constructor() { + this.canvas = document.getElementsByTagName('canvas')[0]; + const engine = this.engine = Filament.Engine.create(this.canvas); + this.scene = engine.createScene(); + this.triangle = Filament.EntityManager.get() + .create(); + this.scene.addEntity(this.triangle); + 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]); + 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); + this.ib = Filament.IndexBuffer.Builder() + .indexCount(3) + .bufferType(Filament.IndexBuffer$IndexType.USHORT) + .build(engine); + this.ib.setBuffer(engine, new Uint16Array([0, 1, 2])); + const mat = engine.createMaterial('triangle.filamat'); + const matinst = mat.getDefaultInstance(); + Filament.RenderableManager.Builder(1) + .boundingBox([ + [-1, -1, -1], + [1, 1, 1] + ]) + .material(0, matinst) + .geometry(0, Filament.RenderableManager$PrimitiveType.TRIANGLES, this.vb, this.ib) + .build(engine, this.triangle); + this.swapChain = engine.createSwapChain(); + this.renderer = engine.createRenderer(); + this.camera = engine.createCamera(); + this.view = engine.createView(); + this.view.setCamera(this.camera); + this.view.setScene(this.scene); + this.view.setClearColor([0.1, 0.2, 0.3, 1.0]); // blue-green background + this.resize(); // adjust the initial viewport + this.render = this.render.bind(this); + this.resize = this.resize.bind(this); + window.addEventListener("resize", this.resize); + window.requestAnimationFrame(this.render); + } + render() { + // 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); + 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 Projection = Filament.Camera$Projection; + this.camera.setProjection(Projection.ORTHO, -aspect, aspect, -1, 1, 0, 1); + } +} +Filament.init(['triangle.filamat'], () => { + window.app = new App() +}); diff --git a/docs/webgl/venetian_crossroads_2k/sh.txt b/docs/webgl/venetian_crossroads_2k/sh.txt new file mode 100644 index 0000000000..e27c83b6f6 --- /dev/null +++ b/docs/webgl/venetian_crossroads_2k/sh.txt @@ -0,0 +1,9 @@ +( 0.199599473290091, 0.197587181694140, 0.208682252219075); // L00, irradiance, pre-scaled base +( 0.089495502705593, 0.126984777315514, 0.187462491049106); // L1-1, irradiance, pre-scaled base +( 0.092171090498250, 0.102496785921704, 0.105307785716618); // L10, irradiance, pre-scaled base +(-0.032283343382116, -0.053886007247673, -0.066118108745634); // L11, irradiance, pre-scaled base +(-0.073408100483914, -0.080873118996811, -0.078844649300054); // L2-2, irradiance, pre-scaled base +( 0.062074830012856, 0.085152602505844, 0.100914239211979); // L2-1, irradiance, pre-scaled base +( 0.007634819958563, 0.005643621723757, -0.000848833137690); // L20, irradiance, pre-scaled base +(-0.102654258144892, -0.102815468288420, -0.093088103336565); // L21, irradiance, pre-scaled base +(-0.022778021491272, -0.028188258289506, -0.037725645536498); // L22, irradiance, pre-scaled base diff --git a/docs/webgl/venetian_crossroads_2k/venetian_crossroads_2k_ibl.ktx b/docs/webgl/venetian_crossroads_2k/venetian_crossroads_2k_ibl.ktx new file mode 100644 index 0000000000..058bfdb5db Binary files /dev/null and b/docs/webgl/venetian_crossroads_2k/venetian_crossroads_2k_ibl.ktx differ diff --git a/docs/webgl/venetian_crossroads_2k/venetian_crossroads_2k_ibl_etc.ktx b/docs/webgl/venetian_crossroads_2k/venetian_crossroads_2k_ibl_etc.ktx new file mode 100644 index 0000000000..65b0ad55bb Binary files /dev/null and b/docs/webgl/venetian_crossroads_2k/venetian_crossroads_2k_ibl_etc.ktx differ diff --git a/docs/webgl/venetian_crossroads_2k/venetian_crossroads_2k_ibl_s3tc.ktx b/docs/webgl/venetian_crossroads_2k/venetian_crossroads_2k_ibl_s3tc.ktx new file mode 100644 index 0000000000..a8b0c60c59 Binary files /dev/null and b/docs/webgl/venetian_crossroads_2k/venetian_crossroads_2k_ibl_s3tc.ktx differ diff --git a/docs/webgl/venetian_crossroads_2k/venetian_crossroads_2k_skybox.ktx b/docs/webgl/venetian_crossroads_2k/venetian_crossroads_2k_skybox.ktx new file mode 100644 index 0000000000..8789b869f7 Binary files /dev/null and b/docs/webgl/venetian_crossroads_2k/venetian_crossroads_2k_skybox.ktx differ diff --git a/site/themes/hyde/static/favicon.png b/site/themes/hyde/static/favicon.png index 84cce4dd30..7bc766e494 100644 Binary files a/site/themes/hyde/static/favicon.png and b/site/themes/hyde/static/favicon.png differ