From d3e7f40398eb8b62305e25865aa6c8af33e342e4 Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Wed, 17 Oct 2018 10:56:00 -0700 Subject: [PATCH] Add IBL to jsbinding and testwasm. --- libs/filamentjs/docs/tutorial_triangle.md | 2 +- libs/filamentjs/jsbindings.cpp | 87 ++++++++++++- libs/filamentjs/tests/test.py | 3 +- libs/filamentjs/tests/testwasm.js | 151 ++++++++++++++-------- 4 files changed, 187 insertions(+), 56 deletions(-) diff --git a/libs/filamentjs/docs/tutorial_triangle.md b/libs/filamentjs/docs/tutorial_triangle.md index e929172dc3..01e1da93d6 100644 --- a/libs/filamentjs/docs/tutorial_triangle.md +++ b/libs/filamentjs/docs/tutorial_triangle.md @@ -63,7 +63,7 @@ class App { } } -Filament.init(['bakedColor.filamat'], () => { Window.app = new App() } ); +Filament.init(['bakedColor.filamat'], () => { window.app = new App() } ); ``` The two calls to `bind()` allow us to pass instance methods as callbacks for animation and resize diff --git a/libs/filamentjs/jsbindings.cpp b/libs/filamentjs/jsbindings.cpp index 2c5228c814..9bee6f9782 100644 --- a/libs/filamentjs/jsbindings.cpp +++ b/libs/filamentjs/jsbindings.cpp @@ -35,12 +35,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -88,8 +90,10 @@ namespace emscripten { BIND(TransformManager) BIND(VertexBuffer) BIND(IndexBuffer) + BIND(IndirectLight) BIND(Material) BIND(MaterialInstance) + BIND(Skybox) BIND(Texture) BIND(utils::Entity) BIND(utils::EntityManager) @@ -107,6 +111,8 @@ using IndexBuilder = IndexBuffer::Builder; using MatBuilder = Material::Builder; using TexBuilder = Texture::Builder; using LightBuilder = LightManager::Builder; +using IblBuilder = IndirectLight::Builder; +using SkyBuilder = Skybox::Builder; // We avoid directly exposing driver::BufferDescriptor because embind does not support move // semantics and void* doesn't make sense to JavaScript anyway. This little wrapper class is exposed @@ -207,6 +213,16 @@ value_array("mat4") .element(index< 8>()).element(index< 9>()).element(index<10>()).element(index<11>()) .element(index<12>()).element(index<13>()).element(index<14>()).element(index<15>()); +struct flatmat3 { + math::mat3f m; + float& operator[](int i) { return m[i / 3][i % 3]; } +}; + +value_array("mat3") + .element(index<0>()).element(index<1>()).element(index<2>()) + .element(index<3>()).element(index<4>()).element(index<5>()) + .element(index<6>()).element(index<7>()).element(index<8>()); + // CORE FILAMENT CLASSES // --------------------- @@ -283,7 +299,12 @@ class_("View") .function("setPostProcessingEnabled", &View::setPostProcessingEnabled); class_("Scene") - .function("addEntity", &Scene::addEntity); + .function("addEntity", &Scene::addEntity) + .function("remove", &Scene::remove) + .function("setSkybox", &Scene::setSkybox, allow_raw_pointers()) + .function("setIndirectLight", &Scene::setIndirectLight, allow_raw_pointers()) + .function("getRenderableCount", &Scene::getRenderableCount) + .function("getLightCount", &Scene::getLightCount); class_("Camera") .function("setProjection", EMBIND_LAMBDA(void, (Camera* self, Camera::Projection projection, @@ -441,6 +462,18 @@ class_("Texture") .function("setImage", EMBIND_LAMBDA(void, (Texture* self, Engine* engine, uint8_t level, PixelBufferDescriptor pbd), { self->setImage(*engine, level, std::move(*pbd.pbd)); + }), allow_raw_pointers()) + .function("setImageCube", EMBIND_LAMBDA(void, (Texture* self, + Engine* engine, uint8_t level, PixelBufferDescriptor pbd), { + uint32_t faceSize = pbd.pbd->size / 6; + Texture::FaceOffsets offsets; + offsets.px = faceSize * 0; + offsets.nx = faceSize * 1; + offsets.py = faceSize * 2; + offsets.ny = faceSize * 3; + offsets.pz = faceSize * 4; + offsets.nz = faceSize * 5; + self->setImage(*engine, level, std::move(*pbd.pbd), offsets); }), allow_raw_pointers()); class_("Texture$Builder") @@ -464,6 +497,47 @@ class_("Texture$Builder") .BUILDER_FUNCTION("rgbm", TexBuilder, (TexBuilder* builder, bool rgbm), { return &builder->rgbm(rgbm); }); +class_("IndirectLight") + .class_function("Builder", (IblBuilder (*)()) [] { return IblBuilder(); }) + .function("setIntensity", &IndirectLight::setIntensity); + +class_("IndirectLight$Builder") + .function("build", EMBIND_LAMBDA(IndirectLight*, (IblBuilder* builder, Engine* engine), { + return builder->build(*engine); + }), allow_raw_pointers()) + .BUILDER_FUNCTION("reflections", IblBuilder, (IblBuilder* builder, Texture const* cubemap), { + return &builder->reflections(cubemap); }) + .BUILDER_FUNCTION("irradianceTex", IblBuilder, (IblBuilder* builder, Texture const* cubemap), { + return &builder->irradiance(cubemap); }) + .BUILDER_FUNCTION("irradianceSh", IblBuilder, (IblBuilder* builder, uint8_t nbands, val ta), { + // This is not efficient but consuming a BufferDescriptor would be overkill. + size_t nfloats = ta["length"].as(); + if (nfloats != nbands * nbands * 3) { + printf("Received %zu floats for spherical harmonics, expected %d.", nfloats, nbands); + return builder; + } + std::vector floats(nfloats); + for (size_t i = 0; i < nfloats; i++) { + floats[i] = ta[i].as(); + } + return &builder->irradiance(nbands, (math::float3 const*) floats.data()); }) + .BUILDER_FUNCTION("intensity", IblBuilder, (IblBuilder* builder, float value), { + return &builder->intensity(value); }) + .BUILDER_FUNCTION("rotation", IblBuilder, (IblBuilder* builder, flatmat3 value), { + return &builder->rotation(value.m); }); + +class_("Skybox") + .class_function("Builder", (SkyBuilder (*)()) [] { return SkyBuilder(); }); + +class_("Skybox$Builder") + .function("build", EMBIND_LAMBDA(Skybox*, (SkyBuilder* builder, Engine* engine), { + return builder->build(*engine); + }), allow_raw_pointers()) + .BUILDER_FUNCTION("environment", SkyBuilder, (SkyBuilder* builder, Texture* cubemap), { + return &builder->environment(cubemap); }) + .BUILDER_FUNCTION("showSun", SkyBuilder, (SkyBuilder* builder, bool show), { + return &builder->showSun(show); }); + // UTILS TYPES // ----------- @@ -502,7 +576,16 @@ class_("KtxBundle") self->getBlob(index, &data, &size); return BufferDescriptor(data, size); }), allow_raw_pointers()) - .function("info", &KtxBundle::info); + .function("getCubeBlob", EMBIND_LAMBDA(BufferDescriptor, (KtxBundle* self, uint32_t miplevel), { + uint8_t* data; + uint32_t size; + self->getBlob({miplevel}, &data, &size); + return BufferDescriptor(data, size * 6); + }), allow_raw_pointers()) + .function("info", &KtxBundle::info) + .function("getMetadata", EMBIND_LAMBDA(std::string, (KtxBundle* self, std::string key), { + return std::string(self->getMetadata(key.c_str())); + }), allow_raw_pointers()); class_("KtxInfo") .property("endianness", &KtxInfo::endianness) diff --git a/libs/filamentjs/tests/test.py b/libs/filamentjs/tests/test.py index e3f85b1eb8..f116528d69 100755 --- a/libs/filamentjs/tests/test.py +++ b/libs/filamentjs/tests/test.py @@ -13,11 +13,12 @@ SERVE_DIR = BUILD_DIR + "/libs/filamentjs" GLMATRIX_DIR = ROOT_DIR + "/third_party/gl-matrix" PORT = 8000 -# Copy assets used for testing into the server folder. +# Copy test assets into the server folder. src_assets = ( glob.glob(BUILD_DIR + "/samples/web/public/material/*") + glob.glob(BUILD_DIR + "/samples/web/public/monkey/*") + + glob.glob(BUILD_DIR + "/samples/web/public/pillars_2k/*") + glob.glob(GLMATRIX_DIR + "/*.js") + glob.glob(SCRIPT_DIR + "/testwasm.*")) diff --git a/libs/filamentjs/tests/testwasm.js b/libs/filamentjs/tests/testwasm.js index 9a65b40aa0..02aa027238 100644 --- a/libs/filamentjs/tests/testwasm.js +++ b/libs/filamentjs/tests/testwasm.js @@ -1,21 +1,35 @@ +// Extend gl-matrix. Filament.loadMathExtensions(); -var Fov, VertexAttribute, AttributeType, PrimitiveType, IndexType, LightType; +Filament.init([ + 'bakedColor.filamat', + 'sandboxLit.filamat', + 'texturedLit.filamat', + 'albedo.ktx', + 'pillars_2k_ibl.ktx', + 'pillars_2k_skybox.ktx', +], () => { + // Declare convenient aliases to Filament enums at global scope. + 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; + + // Create the app and store its instance in the global scope to aid debugging. + window.app = new App(); +}); class App { constructor() { - VertexAttribute = Filament.VertexAttribute; - AttributeType = Filament.VertexBuffer$AttributeType; - PrimitiveType = Filament.RenderableManager$PrimitiveType; - IndexType = Filament.IndexBuffer$IndexType; - Fov = Filament.Camera$Fov; - LightType = Filament.LightManager$Type; - const BAKED_COLOR_PACKAGE = Filament.Buffer(Filament.assets['bakedColor.filamat']); const SANDBOX_LIT_PACKAGE = Filament.Buffer(Filament.assets['sandboxLit.filamat']); const TEXTURED_LIT_PACKAGE = Filament.Buffer(Filament.assets['texturedLit.filamat']); const ALBEDO_KTX = Filament.Buffer(Filament.assets['albedo.ktx']); + const IBL_KTX = Filament.Buffer(Filament.assets['pillars_2k_ibl.ktx']); + const SKYBOX_KTX = Filament.Buffer(Filament.assets['pillars_2k_skybox.ktx']); const TRIANGLE_POSITIONS = Filament.Buffer(new Float32Array([ 1, 0, -.5, .86, -.5, -.86 ])); const TRIANGLE_COLORS = Filament.Buffer(new Uint32Array([0xffff0000, 0xff00ff00, 0xff0000ff])); @@ -97,68 +111,108 @@ class App { .build(engine, sunlight); this.scene.addEntity(sunlight); - const albedo = [158 / 255.0, 118 / 255.0, 74 / 255.0]; - sandboxMatInstance.setColorParameter("baseColor", Filament.RgbType.sRGB, albedo); - sandboxMatInstance.setFloatParameter("roughness", 0.6); + sandboxMatInstance.setColorParameter("baseColor", Filament.RgbType.sRGB, [0.8, 0.0, 0.0]); + sandboxMatInstance.setFloatParameter("roughness", 0.5); sandboxMatInstance.setFloatParameter("metallic", 0.0); - sandboxMatInstance.setFloatParameter("reflectance", 0.5); + sandboxMatInstance.setFloatParameter("reflectance", 0.3); sandboxMatInstance.setFloatParameter("clearCoat", 0.7); - sandboxMatInstance.setFloatParameter("clearCoatRoughness", 0.0); + sandboxMatInstance.setFloatParameter("clearCoatRoughness", 0.3); sandboxMatInstance.setFloatParameter("anisotropy", 0.0); - //////////////////////////////////////////////////////////////////////////////////////////////// - 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]); this.resize(); this.render = this.render.bind(this); this.resize = this.resize.bind(this); window.addEventListener("resize", this.resize); window.requestAnimationFrame(this.render); - //////////////////////////////////////////////////////////////////////////////////////////////// - const sandboxInstance = sandboxMaterial.createInstance(); sandboxInstance.setFloatParameter("metallic", 1.0) sandboxInstance.setFloat3Parameter("baseColor", [1.0, 2.0, 3.0]); - const ktx = Window.ktx = new Filament.KtxBundle(ALBEDO_KTX) - const nmips = ktx.getNumMipLevels(); - console.assert(11 == nmips); - console.assert(1024 == ktx.info().pixelWidth) - console.assert(1024 == ktx.info().pixelHeight) - - const tex = Filament.Texture.Builder() - .width(ktx.info().pixelWidth) - .height(ktx.info().pixelHeight) - .levels(nmips) - .sampler(Filament.Texture$Sampler.SAMPLER_2D) - .format(Filament.Texture$InternalFormat.SRGB8) - .build(engine); - - const format = Filament.PixelDataFormat.RGB; + const format = Filament.PixelDataFormat.RGBM; const dtype = Filament.PixelDataType.UBYTE; - for (let level = 0; level < nmips; ++level) { - const ta = ktx.getBlob([level, 0, 0]).getBytes(); - const pbd = Filament.PixelBuffer(ta, format, dtype); - tex.setImage(engine, level, pbd) - } - const texturedMaterial = engine.createMaterial(TEXTURED_LIT_PACKAGE); - const texturedInstance = texturedMaterial.createInstance(); - const sampler = new Filament.TextureSampler(Filament.MinFilter.LINEAR_MIPMAP_LINEAR, - Filament.MagFilter.LINEAR, Filament.WrapMode.REPEAT); - texturedInstance.setTextureParameter("albedo", tex, sampler); + const iblktx = new Filament.KtxBundle(IBL_KTX); + const iblnmips = iblktx.getNumMipLevels(); + const ibltex = Filament.Texture.Builder() + .width(iblktx.info().pixelWidth) + .height(iblktx.info().pixelHeight) + .levels(iblnmips) + .sampler(Filament.Texture$Sampler.SAMPLER_CUBEMAP) + .format(Filament.Texture$InternalFormat.RGBA8) + .rgbm(true) + .build(engine); + for (let level = 0; level < iblnmips; ++level) { + const ta = iblktx.getCubeBlob(level).getBytes(); + ibltex.setImageCube(engine, level, Filament.PixelBuffer(ta, format, dtype)); + } + const shstring = iblktx.getMetadata("sh"); + const shfloats = shstring.split(/\s/, 9 * 3).map(parseFloat); + const indirectLight = Filament.IndirectLight.Builder() + .reflections(ibltex) + .irradianceSh(3, shfloats) + .intensity(30000.0) + .build(engine); + indirectLight.setIntensity(50000); + this.scene.setIndirectLight(indirectLight); + + const skyktx = new Filament.KtxBundle(SKYBOX_KTX); + 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 skyta = skyktx.getCubeBlob(0).getBytes(); + skytex.setImageCube(engine, 0, Filament.PixelBuffer(skyta, format, dtype)); + const skybox = Filament.Skybox.Builder() + .environment(skytex) + .build(engine); + this.scene.setSkybox(skybox); + + // Test normal 2D textures. + (() => { + const ktx = new Filament.KtxBundle(ALBEDO_KTX); + const nmips = ktx.getNumMipLevels(); + console.assert(11 == nmips); + console.assert(1024 == ktx.info().pixelWidth) + console.assert(1024 == ktx.info().pixelHeight) + + const tex = Filament.Texture.Builder() + .width(ktx.info().pixelWidth) + .height(ktx.info().pixelHeight) + .levels(nmips) + .sampler(Filament.Texture$Sampler.SAMPLER_2D) + .format(Filament.Texture$InternalFormat.SRGB8) + .build(engine); + + const format = Filament.PixelDataFormat.RGB; + const dtype = Filament.PixelDataType.UBYTE; + for (let level = 0; level < nmips; ++level) { + const ta = ktx.getBlob([level, 0, 0]).getBytes(); + const pbd = Filament.PixelBuffer(ta, format, dtype); + tex.setImage(engine, level, pbd); + } + + const texturedMaterial = engine.createMaterial(TEXTURED_LIT_PACKAGE); + const texturedInstance = texturedMaterial.createInstance(); + const sampler = new Filament.TextureSampler(Filament.MinFilter.LINEAR_MIPMAP_LINEAR, + Filament.MagFilter.LINEAR, Filament.WrapMode.REPEAT); + texturedInstance.setTextureParameter("albedo", tex, sampler); + })(); } render() { // Test setting transforms. - const transform = mat4.fromTranslation(mat4.create(), [0, 0, -5]); + const transform = mat4.fromTranslation(mat4.create(), [0, 0, -4]); const tcm = this.engine.getTransformManager(); tcm.setTransform(tcm.getInstance(this.sphereEntity), transform); @@ -180,13 +234,6 @@ class App { const eye = [0, 0, 0], center = [0, 0, -1], up = [0, 1, 0]; this.camera.lookAt(eye, center, up); const aspect = width / height; - this.camera.setProjectionFov(45, aspect, 0.1, 50.0, aspect < 1 ? Fov.HORIZONTAL : Fov.VERTICAL); + this.camera.setProjectionFov(45, aspect, 1.0, 10.0, aspect < 1 ? Fov.HORIZONTAL : Fov.VERTICAL); } } - -Filament.init([ - 'bakedColor.filamat', - 'sandboxLit.filamat', - 'texturedLit.filamat', - 'albedo.ktx', -], () => { Window.app = new App() });