/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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({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); 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::getRootAssetsPath() + 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; }