In Chrome, this reduces texture load time from 620 ms to 150 ms because it removes the PNG decoding work for the surface textures. We are still using PNG for the envmap, I'll fix that soon. Many web servers, including GitHub Pages, automatically use gzip encoding for these files. Over the wire these files actually are not much bigger than the PNG, even though they contain miplevels. Stay tuned for compressed textures, which should make this better.
218 lines
7.8 KiB
C++
218 lines
7.8 KiB
C++
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "filamesh.h"
|
|
#include "filaweb.h"
|
|
|
|
#include <filament/Engine.h>
|
|
#include <filament/IndexBuffer.h>
|
|
#include <filament/LightManager.h>
|
|
#include <filament/Material.h>
|
|
#include <filament/MaterialInstance.h>
|
|
#include <filament/RenderableManager.h>
|
|
#include <filament/Scene.h>
|
|
#include <filament/TransformManager.h>
|
|
#include <filament/VertexBuffer.h>
|
|
#include <filament/View.h>
|
|
|
|
#include <utils/Entity.h>
|
|
#include <utils/EntityManager.h>
|
|
|
|
#include <math/vec3.h>
|
|
|
|
using namespace filament;
|
|
using namespace math;
|
|
using namespace std;
|
|
using namespace utils;
|
|
|
|
using MagFilter = TextureSampler::MagFilter;
|
|
using WrapMode = TextureSampler::WrapMode;
|
|
using Format = Texture::InternalFormat;
|
|
|
|
struct SuzanneApp {
|
|
Filamesh filamesh;
|
|
Material* mat;
|
|
MaterialInstance* mi;
|
|
Camera* cam;
|
|
Entity sun;
|
|
Entity ptlight[4];
|
|
};
|
|
|
|
static constexpr uint8_t MATERIAL_LIT_PACKAGE[] = {
|
|
#include "generated/material/texturedLit.inc"
|
|
};
|
|
|
|
static SuzanneApp app;
|
|
|
|
static Texture* setTextureParameter(Engine& engine, filaweb::Asset& asset, string name,
|
|
TextureSampler const &sampler) {
|
|
|
|
const auto destructor = [](void* buffer, size_t size, void* user) {
|
|
auto asset = (filaweb::Asset*) user;
|
|
asset->data.reset();
|
|
};
|
|
|
|
Texture::Format format;
|
|
switch (asset.channels) {
|
|
case 1: format = Texture::Format::R; break;
|
|
case 2: format = Texture::Format::RG; break;
|
|
case 3: format = Texture::Format::RGB; break;
|
|
case 4: format = Texture::Format::RGBA; break;
|
|
}
|
|
|
|
Texture::PixelBufferDescriptor pb(
|
|
asset.data.get(), asset.nbytes, format, Texture::Type::UBYTE, destructor, &asset);
|
|
|
|
Format internalFormat;
|
|
switch (asset.channels) {
|
|
case 1: internalFormat = Format::R8; break;
|
|
case 2: internalFormat = Format::RG8; break;
|
|
case 3: internalFormat = Format::SRGB8; break;
|
|
case 4: internalFormat = Format::SRGB8_A8; break;
|
|
}
|
|
|
|
auto texture = Texture::Builder()
|
|
.width(asset.width)
|
|
.height(asset.height)
|
|
.sampler(Texture::Sampler::SAMPLER_2D)
|
|
.format(internalFormat)
|
|
.build(engine);
|
|
|
|
texture->setImage(engine, 0, std::move(pb));
|
|
app.mi->setParameter(name.c_str(), texture, sampler);
|
|
return texture;
|
|
}
|
|
|
|
void setup(Engine* engine, View* view, Scene* scene) {
|
|
|
|
// Create material.
|
|
app.mat = Material::Builder()
|
|
.package((void*) MATERIAL_LIT_PACKAGE, sizeof(MATERIAL_LIT_PACKAGE))
|
|
.build(*engine);
|
|
app.mi = app.mat->createInstance();
|
|
app.mi->setParameter("clearCoat", 0.0f);
|
|
|
|
// Move raw asset data from JavaScript to C++ static storage. Their held data will be freed via
|
|
// BufferDescriptor callbacks after Filament creates the corresponding GPU objects.
|
|
static auto mesh = filaweb::getRawFile("mesh");
|
|
static auto albedo = filaweb::getTexture("albedo");
|
|
static auto metallic = filaweb::getTexture("metallic");
|
|
static auto roughness = filaweb::getTexture("roughness");
|
|
static auto normal = filaweb::getTexture("normal");
|
|
static auto ao = filaweb::getTexture("ao");
|
|
|
|
// Create mesh.
|
|
printf("%s: %d bytes\n", "mesh", mesh.nbytes);
|
|
const uint8_t* mdata = mesh.data.get();
|
|
const auto destructor = [](void* buffer, size_t size, void* user) {
|
|
auto asset = (filaweb::Asset*) user;
|
|
asset->data.reset();
|
|
};
|
|
app.filamesh = decodeMesh(*engine, mdata, 0, app.mi, destructor, &mesh);
|
|
scene->addEntity(app.filamesh->renderable);
|
|
|
|
// Create textures.
|
|
TextureSampler sampler(MagFilter::LINEAR, WrapMode::CLAMP_TO_EDGE);
|
|
auto setTexture = [engine, sampler] (filaweb::Asset& asset, const char* name) {
|
|
printf("%s: %d x %d\n", name, asset.width, asset.height);
|
|
setTextureParameter(*engine, asset, name, sampler);
|
|
};
|
|
setTexture(albedo, "albedo");
|
|
setTexture(metallic, "metallic");
|
|
setTexture(roughness, "roughness");
|
|
setTexture(normal, "normal");
|
|
setTexture(ao, "ao");
|
|
|
|
// Create the sun.
|
|
auto& em = EntityManager::get();
|
|
app.sun = em.create();
|
|
LightManager::Builder(LightManager::Type::SUN)
|
|
.color(Color::toLinear<ACCURATE>({ 0.98f, 0.92f, 0.89f }))
|
|
.intensity(110000)
|
|
.direction({ 0.7, -1, -0.8 })
|
|
.sunAngularRadius(1.2f)
|
|
.castShadows(true)
|
|
.build(*engine, app.sun);
|
|
scene->addEntity(app.sun);
|
|
|
|
// Create point lights.
|
|
em.create(4, app.ptlight);
|
|
LightManager::Builder(LightManager::Type::POINT)
|
|
.color(Color::toLinear<ACCURATE>({0.98f, 0.92f, 0.89f}))
|
|
.intensity(LightManager::EFFICIENCY_LED, 300.0f)
|
|
.position({0.0f, -0.2f, -3.0f})
|
|
.falloff(4.0f)
|
|
.build(*engine, app.ptlight[0]);
|
|
LightManager::Builder(LightManager::Type::POINT)
|
|
.color(Color::toLinear<ACCURATE>({0.98f, 0.12f, 0.19f}))
|
|
.intensity(LightManager::EFFICIENCY_LED, 200.0f)
|
|
.position({0.6f, 0.6f, -3.2f})
|
|
.falloff(2.0f)
|
|
.build(*engine, app.ptlight[1]);
|
|
LightManager::Builder(LightManager::Type::POINT)
|
|
.color(Color::toLinear<ACCURATE>({0.18f, 0.12f, 0.89f}))
|
|
.intensity(LightManager::EFFICIENCY_LED, 200.0f)
|
|
.position({-0.6f, 0.6f, -3.2f})
|
|
.falloff(2.0f)
|
|
.build(*engine, app.ptlight[2]);
|
|
LightManager::Builder(LightManager::Type::POINT)
|
|
.color(Color::toLinear<ACCURATE>({0.88f, 0.82f, 0.29f}))
|
|
.intensity(LightManager::EFFICIENCY_LED, 200.0f)
|
|
.position({0.0f, 1.5f, -3.5f})
|
|
.falloff(2.0f)
|
|
.build(*engine, app.ptlight[3]);
|
|
scene->addEntity(app.ptlight[0]);
|
|
scene->addEntity(app.ptlight[1]);
|
|
scene->addEntity(app.ptlight[2]);
|
|
scene->addEntity(app.ptlight[3]);
|
|
|
|
// Create skybox and image-based light source.
|
|
auto skylight = filaweb::getSkyLight(*engine, "syferfontein_18d_clear_2k");
|
|
scene->setIndirectLight(skylight.indirectLight);
|
|
scene->setSkybox(skylight.skybox);
|
|
skylight.indirectLight->setRotation(
|
|
mat3f::rotate(M_PI, float3{ 0, 1, 0 }));
|
|
|
|
app.cam = engine->createCamera();
|
|
app.cam->setExposure(16.0f, 1 / 125.0f, 100.0f);
|
|
app.cam->lookAt(float3{0}, float3{0, 0, -4});
|
|
view->setCamera(app.cam);
|
|
view->setClearColor({0.1, 0.125, 0.25, 1.0});
|
|
};
|
|
|
|
void animate(Engine* engine, View* view, double now) {
|
|
using Fov = Camera::Fov;
|
|
const uint32_t width = view->getViewport().width;
|
|
const uint32_t height = view->getViewport().height;
|
|
double ratio = double(width) / height;
|
|
app.cam->setProjection(45.0, ratio, 0.1, 50.0, ratio < 1 ? Fov::HORIZONTAL : Fov::VERTICAL);
|
|
auto& tcm = engine->getTransformManager();
|
|
tcm.setTransform(tcm.getInstance(app.filamesh->renderable),
|
|
mat4f{mat3f{1.0}, float3{0.0f, 0.0f, -4.0f}} *
|
|
mat4f::rotate(now, math::float3{0, 1, 0}));
|
|
};
|
|
|
|
// This is called only after the JavaScript layer has created a WebGL 2.0 context and all assets
|
|
// have been downloaded.
|
|
extern "C" void launch() {
|
|
filaweb::Application::get()->run(setup, animate);
|
|
}
|
|
|
|
// The main() entry point is implicitly called after JIT compilation, but potentially before the
|
|
// WebGL context has been created or assets have finished loading.
|
|
int main() { }
|
|
|