Files
filament/samples/lucy_bloom.cpp
2019-09-04 13:35:34 -07:00

417 lines
17 KiB
C++

/*
* Copyright (C) 2019 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 "app/Config.h"
#include "app/FilamentApp.h"
#include "app/IBL.h"
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/IndirectLight.h>
#include <filament/LightManager.h>
#include <filament/MaterialInstance.h>
#include <filament/RenderableManager.h>
#include <filament/RenderTarget.h>
#include <filament/Scene.h>
#include <filament/Skybox.h>
#include <filament/TransformManager.h>
#include <filament/VertexBuffer.h>
#include <filament/View.h>
#include <gltfio/AssetLoader.h>
#include <gltfio/FilamentAsset.h>
#include <gltfio/ResourceLoader.h>
#include <getopt/getopt.h>
#include <imgui.h>
#include <string>
#include "lucy_utils.h"
using namespace filament;
using namespace filament::math;
using namespace gltfio;
using namespace utils;
static constexpr uint32_t FBO_WIDTH = 1024;
static constexpr uint32_t FBO_HEIGHT = 1024;
static const uint32_t WINDOW_WIDTH = 800;
static const uint32_t WINDOW_HEIGHT = 800;
struct Framebuffer {
Camera* camera = nullptr;
View* view = nullptr;
Scene* scene = nullptr;
Texture* color = nullptr;
Texture* depth = nullptr;
RenderTarget* target = nullptr;
};
struct LucyApp {
Config config;
bool showQuads;
bool showImgui;
float iblIntensity = 15000;
float iblRotation = M_PI * 1.5f;
AssetLoader* loader;
FilamentAsset* asset;
MaterialProvider* materials;
Entity rotationRoot;
Entity hudQuads[4];
Entity finalQuad;
Entity hblurQuad;
Entity vblurQuad;
Framebuffer reflection;
Framebuffer primary;
Framebuffer hblur;
Framebuffer vblur;
Camera* finalCamera;
};
static void setup(LucyApp& app, Engine* engine, View* finalView, Scene* finalScene) {
app.reflection.color = Texture::Builder()
.width(FBO_WIDTH).height(FBO_HEIGHT).levels(1)
.usage(Texture::Usage::COLOR_ATTACHMENT | Texture::Usage::SAMPLEABLE)
.format(Texture::InternalFormat::RGBA8).build(*engine);
app.reflection.depth = Texture::Builder()
.width(FBO_WIDTH).height(FBO_HEIGHT).levels(1)
.usage(Texture::Usage::DEPTH_ATTACHMENT)
.format(Texture::InternalFormat::DEPTH24).build(*engine);
app.reflection.target = RenderTarget::Builder()
.texture(RenderTarget::COLOR, app.reflection.color)
.texture(RenderTarget::DEPTH, app.reflection.depth)
.build(*engine);
app.primary.color = Texture::Builder()
.width(FBO_WIDTH).height(FBO_HEIGHT).levels(1)
.usage(Texture::Usage::COLOR_ATTACHMENT | Texture::Usage::SAMPLEABLE)
.format(Texture::InternalFormat::RGBA16F).build(*engine);
app.primary.depth = Texture::Builder()
.width(FBO_WIDTH).height(FBO_HEIGHT).levels(1)
.usage(Texture::Usage::DEPTH_ATTACHMENT)
.format(Texture::InternalFormat::DEPTH24).build(*engine);
app.primary.target = RenderTarget::Builder()
.texture(RenderTarget::COLOR, app.primary.color)
.texture(RenderTarget::DEPTH, app.primary.depth)
.build(*engine);
app.hblur.color = Texture::Builder()
.width(FBO_WIDTH).height(FBO_HEIGHT).levels(1)
.usage(Texture::Usage::COLOR_ATTACHMENT | Texture::Usage::SAMPLEABLE)
.format(Texture::InternalFormat::RGBA16F).build(*engine);
app.hblur.target = RenderTarget::Builder()
.texture(RenderTarget::COLOR, app.hblur.color)
.build(*engine);
app.vblur.color = Texture::Builder()
.width(FBO_WIDTH).height(FBO_HEIGHT).levels(1)
.usage(Texture::Usage::COLOR_ATTACHMENT | Texture::Usage::SAMPLEABLE)
.format(Texture::InternalFormat::RGBA16F).build(*engine);
app.vblur.target = RenderTarget::Builder()
.texture(RenderTarget::COLOR, app.vblur.color)
.build(*engine);
// Create lights.
auto* ibl = FilamentApp::get().getIBL();
ibl->getIndirectLight()->setIntensity(app.iblIntensity);
ibl->getIndirectLight()->setRotation(mat3f::rotation(app.iblRotation, float3{ 0, 1, 0 }));
ibl->getSkybox()->setLayerMask(0xff, 0xff);
auto sunlight = EntityManager::get().create();
LightManager::Builder(LightManager::Type::SUN)
.color(Color::toLinear<ACCURATE>({0.98, 0.92, 0.89}))
.intensity(50000)
.direction(normalize(float3 {0.6, -1.0, -0.8}))
.castShadows(true)
.sunAngularRadius(1.9)
.sunHaloSize(10.0)
.sunHaloFalloff(80.0)
.build(*engine, sunlight);
// Create filament entities for the embedded glTF model.
app.materials = createMaterialGenerator(engine);
app.loader = AssetLoader::create({engine, app.materials });
app.asset = app.loader->createAssetFromBinary(RESOURCE_ARGS(LUCY));
gltfio::ResourceLoader({ .engine = engine }).loadResources(app.asset);
app.asset->releaseSourceData();
// Tweak the model materials.
auto begin = app.asset->getMaterialInstances();
auto end = begin + app.asset->getMaterialInstanceCount();
for (auto iter = begin; iter != end; ++iter) {
(*iter)->setParameter("roughnessFactor", 0.25f);
(*iter)->setParameter("baseColorFactor", float4 {1.0, 0.6, 0.5, 1.0});
}
// Transform the model to fit into the unit cube, then parent it under a rotation node.
auto& tcm = engine->getTransformManager();
auto root = tcm.getInstance(app.asset->getRoot());
mat4f transform = LucyUtils::fitIntoUnitCube(app.asset->getBoundingBox());
tcm.setTransform(root, transform);
app.rotationRoot = EntityManager::get().create();
tcm.create(app.rotationRoot);
tcm.setParent(root, tcm.getInstance(app.rotationRoot));
// Create the podium.
Entity disk = LucyUtils::createDisk(engine, app.reflection.color);
mat4f diskTransform = mat4f::scaling(float3 {1.5f, 1.5, 1.0f});
diskTransform = mat4f::rotation(-M_PI / 2, float3 {1, 0, 0}) * diskTransform;
diskTransform = mat4f::translation(float3 {0, -1, 0}) * diskTransform;
tcm.create(disk, tcm.getInstance(app.rotationRoot), diskTransform);
// Create full-screen quads.
app.finalQuad = LucyUtils::createQuad(engine, app.primary.color, LucyUtils::MIX,
app.vblur.color);
app.hblurQuad = LucyUtils::createQuad(engine, app.primary.color, LucyUtils::HBLUR);
app.vblurQuad = LucyUtils::createQuad(engine, app.hblur.color, LucyUtils::VBLUR);
mat4f quadScale = mat4f::scaling(float3 {FBO_WIDTH, FBO_HEIGHT, 1});
tcm.setTransform(tcm.getInstance(app.hblurQuad), quadScale);
tcm.setTransform(tcm.getInstance(app.vblurQuad), quadScale);
app.reflection.scene = engine->createScene();
app.reflection.scene->setIndirectLight(ibl->getIndirectLight());
app.reflection.scene->addEntities(app.asset->getEntities(), app.asset->getEntityCount());
app.reflection.scene->addEntity(sunlight);
app.primary.scene = engine->createScene();
app.primary.scene->addEntity(disk);
app.primary.scene->addEntities(app.asset->getEntities(), app.asset->getEntityCount());
app.primary.scene->addEntity(sunlight);
app.primary.scene->setSkybox(ibl->getSkybox());
app.primary.scene->setIndirectLight(ibl->getIndirectLight());
app.hblur.scene = engine->createScene();
app.hblur.scene->addEntity(app.hblurQuad);
app.vblur.scene = engine->createScene();
app.vblur.scene->addEntity(app.vblurQuad);
app.reflection.camera = engine->createCamera();
app.reflection.view = engine->createView();
app.reflection.view->setName("reflection");
app.reflection.view->setRenderTarget(app.reflection.target);
app.reflection.view->setClearColor({0, 0, 0, 1});
app.reflection.view->setClearTargets(true, true, false);
app.reflection.view->setViewport(Viewport(0, 0, FBO_WIDTH, FBO_HEIGHT));
app.reflection.view->setScene(app.reflection.scene);
app.reflection.view->setCamera(app.reflection.camera);
app.reflection.view->setToneMapping(View::ToneMapping::LINEAR);
app.reflection.view->setDithering(View::Dithering::NONE);
FilamentApp::get().addOffscreenView(app.reflection.view);
app.primary.camera = engine->createCamera();
app.primary.view = engine->createView();
app.primary.view->setName("primary");
app.primary.view->setRenderTarget(app.primary.target);
app.primary.view->setViewport(Viewport(0, 0, FBO_WIDTH, FBO_HEIGHT));
app.primary.view->setScene(app.primary.scene);
app.primary.view->setCamera(app.primary.camera);
app.primary.view->setToneMapping(View::ToneMapping::LINEAR);
app.primary.view->setDithering(View::Dithering::NONE);
FilamentApp::get().addOffscreenView(app.primary.view);
app.hblur.camera = engine->createCamera();
app.hblur.view = engine->createView();
app.hblur.view->setName("hblur");
app.hblur.view->setRenderTarget(app.hblur.target);
app.hblur.view->setViewport(Viewport(0, 0, FBO_WIDTH, FBO_HEIGHT));
app.hblur.view->setScene(app.hblur.scene);
app.hblur.view->setCamera(app.hblur.camera);
app.hblur.view->setPostProcessingEnabled(false);
FilamentApp::get().addOffscreenView(app.hblur.view);
app.hblur.camera->setProjection(Camera::Projection::ORTHO,
0.0, FBO_WIDTH, FBO_HEIGHT, 0.0, 0.0, 1.0);
app.vblur.camera = engine->createCamera();
app.vblur.view = engine->createView();
app.vblur.view->setName("vblur");
app.vblur.view->setRenderTarget(app.vblur.target);
app.vblur.view->setViewport(Viewport(0, 0, FBO_WIDTH, FBO_HEIGHT));
app.vblur.view->setScene(app.vblur.scene);
app.vblur.view->setCamera(app.vblur.camera);
app.vblur.view->setPostProcessingEnabled(false);
FilamentApp::get().addOffscreenView(app.vblur.view);
app.vblur.camera->setProjection(Camera::Projection::ORTHO,
0.0, FBO_WIDTH, FBO_HEIGHT, 0.0, 0.0, 1.0);
app.finalCamera = engine->createCamera();
finalScene->addEntity(app.finalQuad);
finalScene->setSkybox(nullptr);
finalScene->setIndirectLight(nullptr);
finalView->setSampleCount(1);
finalView->setCamera(app.finalCamera);
};
static float3 reflect(float3 i, float3 n) { return i - 2.0f * dot(n, i) * n; };
static void animate(LucyApp& app, Engine* engine, View* finalView, double now) {
// Rotate the embedded mesh. (use now = -0.5 for a nice screenshot)
const mat4f xform = mat4f::rotation(now, float3 {0, 1, 0});
auto& tcm = engine->getTransformManager();
tcm.setTransform(tcm.getInstance(app.rotationRoot), xform);
// Adjust the projection in case the window has been resized.
const auto viewport = finalView->getViewport();
const float aspect = float(viewport.width) / viewport.height;
app.primary.camera->setProjection(30, aspect, 1.0, 10.0);
app.reflection.camera->setProjection(30, aspect, 1.0, 10.0);
app.finalCamera->setProjection(Camera::Projection::ORTHO,
0.0, viewport.width, viewport.height, 0.0, 0.0, 1.0);
// Adjust the size of the final quad in case the window has been resized.
mat4f finalQuadSize = mat4f::scaling(float3 {viewport.width, viewport.height, 1});
tcm.setTransform(tcm.getInstance(app.finalQuad), finalQuadSize);
// Primary camera. The target point will be moved along the gazing direction to aid reflection.
const float3 eye = {0.0f, 1.5f, 4.0f};
float3 target = {0, 0, 0};
// Find where the central camera ray intersects the podium.
const float3 gaze = normalize(target - eye);
const float planeD = -1;
const float3 planeN = {0, 1, 0};
const float t = dot(planeN * planeD - eye, planeN) / dot(gaze, planeN);
target = eye + t * gaze;
// Set up the primary camera.
const float3 up = cross(gaze, float3 {-1, 0, 0});
app.primary.camera->lookAt(eye, target, up);
// Find the location of the reflected camera.
const float eyedist = length(target - eye);
const float3 newgaze = reflect(gaze, planeN);
const float3 neweye = target - eyedist * newgaze;
const float3 newup = cross(newgaze, float3 {-1, 0, 0});
app.reflection.camera->lookAt(neweye, neweye + newgaze, newup);
};
static void drawHud(LucyApp& app, Engine* engine, View* hudView) {
Scene* hudScene = hudView->getScene();
if (!app.hudQuads[0] && app.showQuads) {
auto& tcm = engine->getTransformManager();
app.hudQuads[0] = LucyUtils::createQuad(engine, app.reflection.color, LucyUtils::BLIT);
app.hudQuads[1] = LucyUtils::createQuad(engine, app.primary.color, LucyUtils::BLIT);
app.hudQuads[2] = LucyUtils::createQuad(engine, app.hblur.color, LucyUtils::BLIT);
app.hudQuads[3] = LucyUtils::createQuad(engine, app.vblur.color, LucyUtils::BLIT);
hudScene->addEntity(app.hudQuads[0]);
hudScene->addEntity(app.hudQuads[1]);
hudScene->addEntity(app.hudQuads[2]);
hudScene->addEntity(app.hudQuads[3]);
mat4f scale = mat4f::scaling(float3 {100, 100, 1});
tcm.create(app.hudQuads[0], {}, mat4f::translation(float3 {10, 10, 0}) * scale);
tcm.create(app.hudQuads[1], {}, mat4f::translation(float3 {10, 120, 0}) * scale);
tcm.create(app.hudQuads[2], {}, mat4f::translation(float3 {10, 230, 0}) * scale);
tcm.create(app.hudQuads[3], {}, mat4f::translation(float3 {10, 340, 0}) * scale);
}
if (app.showImgui) {
ImGui::SetNextWindowSize(ImVec2(305, 90));
ImGui::Begin("Parameters");
ImGui::SliderFloat("ibl", &app.iblIntensity, 0.0f, 50000.0f);
ImGui::SliderAngle("ibl rotation", &app.iblRotation);
ImGui::End();
auto* ibl = FilamentApp::get().getIBL();
ibl->getIndirectLight()->setIntensity(app.iblIntensity);
ibl->getIndirectLight()->setRotation(mat3f::rotation(app.iblRotation, float3{ 0, 1, 0 }));
}
}
static void cleanup(LucyApp& app, Engine* engine) {
engine->destroy(app.hudQuads[0]);
engine->destroy(app.hudQuads[1]);
engine->destroy(app.hudQuads[2]);
engine->destroy(app.hudQuads[3]);
engine->destroy(app.hblurQuad);
engine->destroy(app.finalQuad);
engine->destroy(app.hblur.camera);
engine->destroy(app.hblur.scene);
engine->destroy(app.hblur.view);
engine->destroy(app.hblur.color);
engine->destroy(app.hblur.target);
engine->destroy(app.vblur.camera);
engine->destroy(app.vblur.scene);
engine->destroy(app.vblur.view);
engine->destroy(app.vblur.color);
engine->destroy(app.vblur.target);
engine->destroy(app.reflection.camera);
engine->destroy(app.reflection.scene);
engine->destroy(app.reflection.view);
engine->destroy(app.reflection.color);
engine->destroy(app.reflection.depth);
engine->destroy(app.reflection.target);
engine->destroy(app.primary.camera);
engine->destroy(app.primary.scene);
engine->destroy(app.primary.view);
engine->destroy(app.primary.color);
engine->destroy(app.primary.depth);
engine->destroy(app.primary.target);
engine->destroy(app.finalCamera);
Fence::waitAndDestroy(engine->createFence());
app.loader->destroyAsset(app.asset);
app.materials->destroyMaterials();
delete app.materials;
AssetLoader::destroy(&app.loader);
};
int main(int argc, char** argv) {
static const char* DEFAULT_IBL = "venetian_crossroads_2k";
LucyApp app;
app.config.title = "Lucy";
app.config.iblDirectory = FilamentApp::getRootPath() + DEFAULT_IBL;
app.config.resizeable = false;
app.showQuads = true;
app.showImgui = true;
// Handle arguments.
static constexpr const char* OPTSTR = "i:";
static const struct option OPTIONS[] = {
{ "ibl", required_argument, nullptr, 'i' },
{ nullptr, 0, nullptr, 0 }
};
int opt, option_index = 0;
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &option_index)) >= 0) {
std::string arg(optarg ? optarg : "");
switch (opt) {
case 'i': app.config.iblDirectory = arg; break;
}
}
// Configure the animation callback.
FilamentApp::get().animate(
[&app](Engine* engine, View* view, double now) { animate(app, engine, view, now); });
// Start the app.
FilamentApp::get().run(app.config,
[&app](Engine* engine, View* view, Scene* scene) { setup(app, engine, view, scene); },
[&app](Engine* engine, View*, Scene*) { cleanup(app, engine); },
[&app](Engine* engine, View* view) { drawHud(app, engine, view); },
FilamentApp::PreRenderCallback(),
FilamentApp::PostRenderCallback(),
WINDOW_WIDTH, WINDOW_HEIGHT);
return 0;
}