Add bloom demo and release notes for #119.

This commit is contained in:
Philip Rideout
2019-06-06 17:17:45 -07:00
parent 961ad36443
commit 8142ffdfb7
12 changed files with 854 additions and 4 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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,

View File

@@ -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()
# ==================================================================================================

View File

@@ -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

View File

@@ -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.

View File

@@ -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<filament::View*> mOffscreenViews;
};
#endif // TNT_FILAMENT_SAMPLE_FILAMENTAPP_H

View File

@@ -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);

404
samples/lucy_bloom.cpp Normal file
View File

@@ -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 <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 = 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<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);
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);
}

382
samples/lucy_utils.cpp Normal file
View File

@@ -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 <filamat/MaterialBuilder.h>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/IndirectLight.h>
#include <filament/MaterialInstance.h>
#include <filament/RenderableManager.h>
#include <filament/Scene.h>
#include <filament/TransformManager.h>
#include <filament/VertexBuffer.h>
#include <geometry/SurfaceOrientation.h>
#include <stb_image.h>
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

38
samples/lucy_utils.h Normal file
View File

@@ -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 <filament/Engine.h>
#include <filament/Texture.h>
#include <filament/Box.h>
#include <math/mat4.h>
#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

BIN
third_party/models/lucy/lucy.glb vendored Normal file

Binary file not shown.