diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index a173109cfd..eb9132cad3 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,6 +6,8 @@ A new header is inserted each time a *tag* is created. # Release notes +- Introduced `RenderTarget` API that allows View to reference an offscreen render target. +- Added `lucy_bloom` sample to demonstrate the new `RenderTarget` API. - Added Screen Space Ambient Occlusion support (SAO) - New blending modes: `multiply` and `screen` - Fixed an issue when sorting blended objects with different blending modes diff --git a/filament/include/filament/RenderableManager.h b/filament/include/filament/RenderableManager.h index 762156cfe8..0d2f877e0d 100644 --- a/filament/include/filament/RenderableManager.h +++ b/filament/include/filament/RenderableManager.h @@ -76,6 +76,7 @@ public: Builder& material(size_t index, MaterialInstance const* materialInstance) noexcept; // The axis aligned bounding box of the Renderable. Mandatory unless culling is disabled. Builder& boundingBox(const Box& axisAlignedBoundingBox) noexcept; + // See View::setVisibleLayers(). Builder& layerMask(uint8_t select, uint8_t values) noexcept; // The priority is clamped to the range [0..7], defaults to 4; 7 is lowest priority Builder& priority(uint8_t priority) noexcept; @@ -130,7 +131,10 @@ public: void destroy(utils::Entity e) noexcept; void setAxisAlignedBoundingBox(Instance instance, const Box& aabb) noexcept; + + // See View::setVisibleLayers void setLayerMask(Instance instance, uint8_t select, uint8_t values) noexcept; + void setPriority(Instance instance, uint8_t priority) noexcept; void setCastShadows(Instance instance, bool enable) noexcept; void setReceiveShadows(Instance instance, bool enable) noexcept; diff --git a/filament/src/details/Texture.h b/filament/src/details/Texture.h index 59a9cc261f..b41face538 100644 --- a/filament/src/details/Texture.h +++ b/filament/src/details/Texture.h @@ -50,6 +50,7 @@ public: size_t getLevels() const noexcept { return mLevels; } Sampler getTarget() const noexcept { return mTarget; } InternalFormat getFormat() const noexcept { return mFormat; } + Usage getUsage() const noexcept { return mUsage; } bool isRgbm() const noexcept { return mRgbm; } void setImage(FEngine& engine, size_t level, diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index bd3b2bf883..fda364986b 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -149,13 +149,18 @@ endif() add_library(suzanne-resources ${DUMMY_SRC} ${RESGEN_SOURCE}) # ================================================================================================== -# Resources for gltf_viewer +# Resources for the gltf_viewer and lucy demos # ================================================================================================== get_resgen_vars(${RESOURCE_DIR} gltf) set(GLTF_RESOURCE_FILES ${ROOT_DIR}/third_party/models/DamagedHelmet/DamagedHelmet.glb + ${ROOT_DIR}/third_party/models/lucy/lucy.glb + ${ROOT_DIR}/third_party/textures/Blue_tiles_01/Blue_tiles_01_AO.png + ${ROOT_DIR}/third_party/textures/Blue_tiles_01/Blue_tiles_01_Color.png + ${ROOT_DIR}/third_party/textures/Blue_tiles_01/Blue_tiles_01_Normal.png + ${ROOT_DIR}/third_party/textures/Blue_tiles_01/Blue_tiles_01_Roughness.png ) add_custom_command( @@ -263,6 +268,10 @@ if (NOT ANDROID) target_link_libraries(suzanne PRIVATE suzanne-resources) target_link_libraries(gltf_viewer PRIVATE gltf-resources gltfio) target_link_libraries(gltf_baker PRIVATE gltf-resources gltfio imageio) + + add_executable(lucy_bloom lucy_bloom.cpp lucy_utils.h lucy_utils.cpp) + target_compile_options(lucy_bloom PRIVATE ${COMPILER_FLAGS}) + target_link_libraries(lucy_bloom PRIVATE ${APP_LIBS} gltf-resources gltfio) endif() # ================================================================================================== diff --git a/samples/app/Config.h b/samples/app/Config.h index 8efe32d3e0..5145573179 100644 --- a/samples/app/Config.h +++ b/samples/app/Config.h @@ -27,6 +27,7 @@ struct Config { float scale = 1.0f; bool splitView = false; filament::Engine::Backend backend = filament::Engine::Backend::OPENGL; + bool resizeable = true; }; #endif // TNT_FILAMENT_SAMPLE_CONFIG_H diff --git a/samples/app/FilamentApp.cpp b/samples/app/FilamentApp.cpp index edec2dd357..b2d5c528a6 100644 --- a/samples/app/FilamentApp.cpp +++ b/samples/app/FilamentApp.cpp @@ -371,6 +371,9 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback, } if (renderer->beginFrame(window->getSwapChain())) { + for (filament::View* offscreenView : mOffscreenViews) { + renderer->render(offscreenView); + } for (auto const& view : window->mViews) { renderer->render(view->getView()); } @@ -442,7 +445,10 @@ FilamentApp::Window::Window(FilamentApp* filamentApp, : mFilamentApp(filamentApp) { const int x = SDL_WINDOWPOS_CENTERED; const int y = SDL_WINDOWPOS_CENTERED; - const uint32_t windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; + uint32_t windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI; + if (config.resizeable) { + windowFlags |= SDL_WINDOW_RESIZABLE; + } mWindow = SDL_CreateWindow(title.c_str(), x, y, (int) w, (int) h, windowFlags); // Create the Engine after the window in case this happens to be a single-threaded platform. diff --git a/samples/app/FilamentApp.h b/samples/app/FilamentApp.h index 82880d31e8..0f19251622 100644 --- a/samples/app/FilamentApp.h +++ b/samples/app/FilamentApp.h @@ -82,6 +82,8 @@ public: void setSidebarWidth(int width) { mSidebarWidth = width; } void setWindowTitle(const char* title) { mWindowTitle = title; } + void addOffscreenView(filament::View* view) { mOffscreenViews.push_back(view); } + size_t getSkippedFrameCount() const { return mSkippedFrames; } FilamentApp(const FilamentApp& rhs) = delete; @@ -214,6 +216,7 @@ private: int mSidebarWidth = 0; size_t mSkippedFrames = 0; std::string mWindowTitle; + std::vector mOffscreenViews; }; #endif // TNT_FILAMENT_SAMPLE_FILAMENTAPP_H diff --git a/samples/gltf_baker.cpp b/samples/gltf_baker.cpp index c05f7507f8..74d1add8e4 100644 --- a/samples/gltf_baker.cpp +++ b/samples/gltf_baker.cpp @@ -253,7 +253,7 @@ static void createQuadRenderable(BakerApp& app) { static OverlayVertex kVertices[4] = { {{0, 0}, {0, 0}}, {{ 1000, 0}, {1, 0}}, {{0, 1000}, {0, 1}}, {{ 1000, 1000}, {1, 1}} }; - static constexpr uint16_t kInidices[6] = { 0, 1, 2, 3, 2, 1 }; + static constexpr uint16_t kIndices[6] = { 0, 1, 2, 3, 2, 1 }; if (!app.overlayQuad.entity) { app.overlayQuad.vb = VertexBuffer::Builder() @@ -267,7 +267,7 @@ static void createQuadRenderable(BakerApp& app) { .bufferType(IndexBuffer::IndexType::USHORT) .build(engine); app.overlayQuad.ib->setBuffer(engine, - IndexBuffer::BufferDescriptor(kInidices, 12, nullptr)); + IndexBuffer::BufferDescriptor(kIndices, 12, nullptr)); auto mat = Material::Builder() .package(RESOURCES_AOPREVIEW_DATA, RESOURCES_AOPREVIEW_SIZE) .build(engine); diff --git a/samples/lucy_bloom.cpp b/samples/lucy_bloom.cpp new file mode 100644 index 0000000000..6a07958724 --- /dev/null +++ b/samples/lucy_bloom.cpp @@ -0,0 +1,404 @@ +/* + * 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 "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 = 40000; + 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() + .color(app.reflection.color) .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() + .color(app.primary.color) .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().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().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); + 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); + 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 = "envs/pillars"; + + 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); +} diff --git a/samples/lucy_utils.cpp b/samples/lucy_utils.cpp new file mode 100644 index 0000000000..2aa3475fe1 --- /dev/null +++ b/samples/lucy_utils.cpp @@ -0,0 +1,382 @@ +/* + * 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 "lucy_utils.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace filament; +using namespace filament::math; +using namespace utils; + +using filament::geometry::SurfaceOrientation; + +#define FILTER_SIZE 9 + +namespace LucyUtils { + +constexpr float M_PIf = float(M_PI); + +Entity createQuad(Engine* engine, Texture* tex, ImageOp op, Texture* secondary) { + + static VertexBuffer* vb = [](Engine& engine) { + struct OverlayVertex { + float2 position; + float2 uv; + quath tangent; + }; + static OverlayVertex verts[4] = { + {{0, 0}, {0, 0}, {0, 0, 0, 1}}, + {{1, 0}, {1, 0}, {0, 0, 0, 1}}, + {{0, 1}, {0, 1}, {0, 0, 0, 1}}, + {{1, 1}, {1, 1}, {0, 0, 0, 1}} + }; + static float3 normals[4] = { + {0, 0, 1}, + {0, 0, 1}, + {0, 0, 1}, + {0, 0, 1}, + }; + SurfaceOrientation::Builder() + .vertexCount(4) + .normals(normals) + .build() + .getQuats(2 + (quath*) verts, 4, 24); + auto vb = VertexBuffer::Builder() + .vertexCount(4) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 24) + .attribute(VertexAttribute::UV0, 0, VertexBuffer::AttributeType::FLOAT2, 8, 24) + .attribute(VertexAttribute::TANGENTS, 0, VertexBuffer::AttributeType::HALF4, 16, 24) + .build(engine); + vb->setBufferAt(engine, 0, VertexBuffer::BufferDescriptor(verts, sizeof(verts), nullptr)); + return vb; + }(*engine); + + static IndexBuffer* ib = [](Engine& engine) { + static constexpr uint16_t indices[6] = { 2, 1, 0, 1, 2, 3 }; + auto ib = IndexBuffer::Builder() + .indexCount(6) + .bufferType(IndexBuffer::IndexType::USHORT) + .build(engine); + ib->setBuffer(engine, IndexBuffer::BufferDescriptor(indices, sizeof(indices), nullptr)); + return ib; + }(*engine); + + static Material* blit = [](Engine& engine) { + filamat::Package pkg = filamat::MaterialBuilder() + .name("blit") + .material(R"SHADER( + void material(inout MaterialInputs material) { + prepareMaterial(material); + material.baseColor = texture(materialParams_color, getUV0()); + } + )SHADER") + .require(VertexAttribute::UV0) + .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "color") + .targetApi(filamat::MaterialBuilder::TargetApi::OPENGL) + .shading(Shading::UNLIT) + .depthWrite(false) + .depthCulling(false) + .build(); + return Material::Builder().package(pkg.getData(), pkg.getSize()).build(engine); + }(*engine); + + static Material* mix = [](Engine& engine) { + filamat::Package pkg = filamat::MaterialBuilder() + .name("mix") + .material(R"SHADER( + void material(inout MaterialInputs material) { + prepareMaterial(material); + vec4 primary = texture(materialParams_color, getUV0()); + vec4 blurred = texture(materialParams_secondary, getUV0()); + // HACK: this is a crude bloom effect + float L = max(0.0, max(blurred.r, max(blurred.g, blurred.b)) - 1.0); + material.baseColor = mix(primary, blurred, min(1.0, L)); + } + )SHADER") + .require(VertexAttribute::UV0) + .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "color") + .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "secondary") + .targetApi(filamat::MaterialBuilder::TargetApi::OPENGL) + .shading(Shading::UNLIT) + .depthWrite(false) + .depthCulling(false) + .build(); + return Material::Builder().package(pkg.getData(), pkg.getSize()).build(engine); + }(*engine); + + static const float4* weights = []() { + static float4 weights[FILTER_SIZE * 2]; + float4* hweights = weights; + float4* vweights = weights + FILTER_SIZE; + static const float radius = 2; + const auto filter = [](float t) { + t /= 2.0; + if (t >= 1.0) return 0.0f; + const float scale = 1.0f / std::sqrt(0.5f * M_PIf); + return std::exp(-2.0f * t * t) * scale; + }; + constexpr float pixelWidth = 2.0f / float(FILTER_SIZE); + float sum = 0.0f; + for (int i = 0; i < FILTER_SIZE; i++) { + float x = -1.0f + pixelWidth / 2.0f + pixelWidth * i; + hweights[i].x = vweights[i].x = filter(std::abs(x)); + hweights[i].y = vweights[i].z = radius * (i - (FILTER_SIZE - 1) / 2); + hweights[i].z = vweights[i].y = 0.0f; + sum += weights[i].x; + } + for (int i = 0; i < FILTER_SIZE; i++) { + hweights[i].x /= sum; + vweights[i].x /= sum; + } + return weights; + }(); + static const float4* hweights = weights; + static const float4* vweights = weights + FILTER_SIZE; + + static Material* blur = [](Engine& engine) { + std::string txt = R"SHADER( + void material(inout MaterialInputs material) { + prepareMaterial(material); + float2 uv = gl_FragCoord.xy; + vec4 c = vec4(0); + for (int i = 0; i < FILTER_SIZE; i++) { + float2 st = (uv + materialParams.weights[i].yz) * getResolution().zw; + c += texture(materialParams_color, st) * materialParams.weights[i].x; + } + material.baseColor = c; + } + )SHADER"; + const std::string from("FILTER_SIZE"); + const std::string to = std::to_string(FILTER_SIZE); + size_t pos = txt.find(from); + txt.replace(pos, from.length(), to.c_str()); + filamat::Package pkg = filamat::MaterialBuilder() + .name("hblur") + .material(txt.c_str()) + .require(VertexAttribute::UV0) + .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "color") + .parameter(filamat::MaterialBuilder::UniformType::FLOAT4, FILTER_SIZE, "weights") + .targetApi(filamat::MaterialBuilder::TargetApi::OPENGL) + .shading(Shading::UNLIT) + .depthWrite(false) + .depthCulling(false) + .build(); + return Material::Builder().package(pkg.getData(), pkg.getSize()).build(engine); + }(*engine); + + TextureSampler::MinFilter minfilt; + TextureSampler::MagFilter magfilt; + MaterialInstance* matinstance; + switch (op) { + case BLIT: + minfilt = TextureSampler::MinFilter::LINEAR; + magfilt = TextureSampler::MagFilter::LINEAR; + matinstance = blit->createInstance(); + break; + case HBLUR: + minfilt = TextureSampler::MinFilter::NEAREST; + magfilt = TextureSampler::MagFilter::NEAREST; + matinstance = blur->createInstance(); + matinstance->setParameter("weights", hweights, FILTER_SIZE); + break; + case VBLUR: + minfilt = TextureSampler::MinFilter::NEAREST; + magfilt = TextureSampler::MagFilter::NEAREST; + matinstance = blur->createInstance(); + matinstance->setParameter("weights", vweights, FILTER_SIZE); + break; + case MIX: + minfilt = TextureSampler::MinFilter::LINEAR; + magfilt = TextureSampler::MagFilter::LINEAR; + matinstance = mix->createInstance(); + matinstance->setParameter("secondary", secondary, TextureSampler(minfilt, magfilt)); + break; + } + matinstance->setParameter("color", tex, TextureSampler(minfilt, magfilt)); + + Entity entity = EntityManager::get().create(); + RenderableManager::Builder(1) + .boundingBox({{ 0, 0, 0 }, { 9000, 9000, 9000 }}) + .material(0, matinstance) + .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vb, ib) + .build(*engine, entity); + + return entity; +} + +Entity createDisk(Engine* engine, Texture* reflection) { + constexpr int nslices = 64; + constexpr int nverts = nslices + 2; + + static float4 verts[nverts]; + int i = 0; + while (i < nslices + 1) { + float theta = 2.0f * M_PI * i / nslices; + verts[i++] = float4 { + std::cos(theta), std::sin(theta), + 0.5 + 0.5 * std::cos(theta), 0.5 + 0.5 * std::sin(theta) + }; + } + verts[i] = {0.0f, 0.0f, 0.5f, 0.5f}; + + static uint16_t indices[nslices * 3]; + for (int i = 0, j = 0; i < nslices; ++i) { + indices[j++] = nverts - 1; + indices[j++] = i; + indices[j++] = (i + 1) % (nslices + 1); + } + + VertexBuffer* vb = VertexBuffer::Builder() + .vertexCount(nverts) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 16) + .attribute(VertexAttribute::UV0, 0, VertexBuffer::AttributeType::FLOAT2, 8, 16) + .build(*engine); + + vb->setBufferAt(*engine, 0, VertexBuffer::BufferDescriptor(verts, sizeof(verts), nullptr)); + + IndexBuffer* ib = IndexBuffer::Builder() + .indexCount(nslices * 3) + .bufferType(IndexBuffer::IndexType::USHORT) + .build(*engine); + + ib->setBuffer(*engine, IndexBuffer::BufferDescriptor(indices, sizeof(indices), nullptr)); + + filamat::Package pkg = filamat::MaterialBuilder() + .name("podium") + .material(R"SHADER( + void material(inout MaterialInputs material) { + vec3 n = texture(materialParams_normal, getUV0()).xyz * 2.0 - 1.0; + + // The blue tiles normal map is very harsh, so we soften the normal vector. + material.normal = normalize(n + vec3(0, 0, 5)); + + prepareMaterial(material); + material.baseColor = texture(materialParams_color, getUV0()); + material.roughness = texture(materialParams_roughness, getUV0()).r; + material.ambientOcclusion = texture(materialParams_ao, getUV0()).r; + + float2 uv = gl_FragCoord.xy * getResolution().zw; uv.y = 1.0 - uv.y; + vec3 reflectance = texture(materialParams_reflection, uv).xyz; + + // HACK: blend the reflection with the baseColor. + material.baseColor.rgb = mix(reflectance, material.baseColor.rgb, 0.75); + } + )SHADER") + .require(VertexAttribute::UV0) + .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "normal") + .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "color") + .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "roughness") + .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "ao") + .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "reflection") + .targetApi(filamat::MaterialBuilder::TargetApi::OPENGL) + .specularAntiAliasing(true) + .shading(Shading::LIT) + .build(); + + Material* mat = Material::Builder().package(pkg.getData(), pkg.getSize()).build(*engine); + MaterialInstance* matinstance = mat->createInstance(); + + auto createTexture4 = [engine](const uint8_t* data, int size, bool srgb) { + int width, height, nchan; + auto texels = stbi_load_from_memory(data, size, &width, &height, &nchan, 4); + Texture::PixelBufferDescriptor buffer(texels, size_t(width * height * 4), + Texture::Format::RGBA, Texture::Type::UBYTE, + (Texture::PixelBufferDescriptor::Callback) &stbi_image_free); + auto tex = Texture::Builder() + .width(uint32_t(width)).height(uint32_t(height)).levels(1) + .sampler(Texture::Sampler::SAMPLER_2D) + .format(srgb ? Texture::InternalFormat::SRGB8_A8 : Texture::InternalFormat::RGBA8) + .build(*engine); + tex->setImage(*engine, 0, std::move(buffer)); + tex->generateMipmaps(*engine); + return tex; + }; + + auto createTexture1 = [engine](const uint8_t* data, int size) { + int width, height, nchan; + auto texels = stbi_load_from_memory(data, size, &width, &height, &nchan, 1); + Texture::PixelBufferDescriptor buffer(texels, size_t(width * height), + Texture::Format::R, Texture::Type::UBYTE, + (Texture::PixelBufferDescriptor::Callback) &stbi_image_free); + auto tex = Texture::Builder() + .width(uint32_t(width)).height(uint32_t(height)).levels(1) + .sampler(Texture::Sampler::SAMPLER_2D) + .format(Texture::InternalFormat::R8) + .build(*engine); + tex->setImage(*engine, 0, std::move(buffer)); + tex->generateMipmaps(*engine); + return tex; + }; + + auto normal = createTexture4(RESOURCE_ARGS(BLUE_TILES_01_NORMAL), false); + auto color = createTexture4(RESOURCE_ARGS(BLUE_TILES_01_COLOR), true); + auto roughness = createTexture1(RESOURCE_ARGS(BLUE_TILES_01_ROUGHNESS)); + auto ao = createTexture1(RESOURCE_ARGS(BLUE_TILES_01_AO)); + + TextureSampler sampler(TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR, + TextureSampler::MagFilter::LINEAR); + + matinstance->setParameter("normal", normal, sampler); + matinstance->setParameter("color", color, sampler); + matinstance->setParameter("roughness", roughness, sampler); + matinstance->setParameter("ao", ao, sampler); + matinstance->setParameter("reflection", reflection, + TextureSampler(TextureSampler::MagFilter::LINEAR)); + + auto entity = EntityManager::get().create(); + RenderableManager::Builder(1) + .boundingBox({{ -1, -1, 1 }, { 1, 1, 1 }}) + .material(0, matinstance) + .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vb, ib) + .culling(false) + .receiveShadows(true) + .castShadows(false) + .build(*engine, entity); + + auto& tcm = engine->getTransformManager(); + tcm.create(entity); + + return entity; +} + +mat4f fitIntoUnitCube(const Aabb& bounds) { + auto minpt = bounds.min; + auto maxpt = bounds.max; + float maxExtent = 0; + maxExtent = std::max(maxpt.x - minpt.x, maxpt.y - minpt.y); + maxExtent = std::max(maxExtent, maxpt.z - minpt.z); + float scaleFactor = 2.0f / maxExtent; + float3 center = (minpt + maxpt) / 2.0f; + return mat4f::scaling(float3(scaleFactor)) * mat4f::translation(-center); +} + +} // namespace LucyUtils diff --git a/samples/lucy_utils.h b/samples/lucy_utils.h new file mode 100644 index 0000000000..db661a1b27 --- /dev/null +++ b/samples/lucy_utils.h @@ -0,0 +1,38 @@ +/* + * 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. + */ + +#ifndef TNT_FILAMENT_SAMPLES_LUCY_UTILS_H +#define TNT_FILAMENT_SAMPLES_LUCY_UTILS_H + +#include +#include +#include + +#include + +#include "generated/resources/gltf.h" + +#define RESOURCE_ARGS(name) GLTF_##name##_DATA, GLTF_##name##_SIZE + +namespace LucyUtils { + enum ImageOp { BLIT, HBLUR, VBLUR, MIX }; + utils::Entity createQuad(filament::Engine* engine, filament::Texture* tex, ImageOp op, + filament::Texture* secondary = nullptr); + utils::Entity createDisk(filament::Engine* engine, filament::Texture* reflection); + filament::math::mat4f fitIntoUnitCube(const filament::Aabb& bounds); +} + +#endif // #define TNT_FILAMENT_SAMPLES_LUCY_UTILS_H diff --git a/third_party/models/lucy/lucy.glb b/third_party/models/lucy/lucy.glb new file mode 100644 index 0000000000..e7f43d1f25 Binary files /dev/null and b/third_party/models/lucy/lucy.glb differ