From 3291fb6a1ba0b2b0b94ecca2da36f07276aba37e Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Thu, 9 May 2019 15:34:30 -0700 Subject: [PATCH] Introduce gltf_baker tool. --- .gitignore | 1 + libs/filagui/include/filagui/ImGuiHelper.h | 3 + libs/gltfio/include/gltfio/SimpleViewer.h | 47 +- libs/image/include/image/LinearImage.h | 1 + samples/CMakeLists.txt | 3 + samples/app/FilamentApp.cpp | 6 + samples/app/FilamentApp.h | 1 + samples/gltf_baker.cpp | 592 +++++++++++++++++++++ samples/materials/aoPreview.mat | 27 + 9 files changed, 663 insertions(+), 18 deletions(-) create mode 100644 samples/gltf_baker.cpp create mode 100644 samples/materials/aoPreview.mat diff --git a/.gitignore b/.gitignore index 3f03a117c3..a82aa82f8b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ dist-* toolchains filament/docs/html/** .vscode +gltf_baker.ini diff --git a/libs/filagui/include/filagui/ImGuiHelper.h b/libs/filagui/include/filagui/ImGuiHelper.h index 6279be47ca..8a7ea336fb 100644 --- a/libs/filagui/include/filagui/ImGuiHelper.h +++ b/libs/filagui/include/filagui/ImGuiHelper.h @@ -60,6 +60,9 @@ public: // Helper method called after resolving fontPath; public so fonts can be added by caller. void createAtlasTexture(filament::Engine* engine); + // Return the ImGui view, useful for drawing 2D overlays. + filament::View* getView() const { return mView; } + private: void renderDrawData(ImDrawData* imguiData); void createBuffers(int numRequiredBuffers); diff --git a/libs/gltfio/include/gltfio/SimpleViewer.h b/libs/gltfio/include/gltfio/SimpleViewer.h index 3a4abff05f..5c96d18751 100644 --- a/libs/gltfio/include/gltfio/SimpleViewer.h +++ b/libs/gltfio/include/gltfio/SimpleViewer.h @@ -48,13 +48,17 @@ namespace gltfio { */ class SimpleViewer { public: + + static constexpr uint32_t FLAG_COLLAPSED = (1 << 0); + /** * Constructs a SimpleViewer that has a fixed association with the given Filament objects. * * Upon construction, the simple viewer may create some additional Filament objects (such as * light sources) that it owns. */ - SimpleViewer(filament::Engine* engine, filament::Scene* scene, filament::View* view); + SimpleViewer(filament::Engine* engine, filament::Scene* scene, filament::View* view, + uint32_t flags = 0); /** * Destroys the SimpleViewer and any Filament entities that it owns. @@ -145,6 +149,7 @@ private: bool mEnableMsaa = true; bool mEnableSsao = true; int mSidebarWidth = INITIAL_SIDEBAR_WIDTH; + uint32_t mFlags; }; filament::math::mat4f fitIntoUnitCube(const filament::Aabb& bounds); @@ -181,10 +186,12 @@ filament::math::mat4f fitIntoUnitCube(const filament::Aabb& bounds) { return mat4f::scaling(float3(scaleFactor)) * mat4f::translation(-center); } -SimpleViewer::SimpleViewer(filament::Engine* engine, filament::Scene* scene, filament::View* view) : +SimpleViewer::SimpleViewer(filament::Engine* engine, filament::Scene* scene, filament::View* view, + uint32_t flags) : mEngine(engine), mScene(scene), mView(view), mNames(new utils::NameComponentManager(utils::EntityManager::get())), - mSunlight(utils::EntityManager::get().create()) { + mSunlight(utils::EntityManager::get().create()), + mFlags(flags) { using namespace filament; LightManager::Builder(LightManager::Type::SUN) .color(Color::toLinear({0.98, 0.92, 0.89})) @@ -260,6 +267,8 @@ void SimpleViewer::applyAnimation(double currentTime) { void SimpleViewer::updateUserInterface() { using namespace filament; + ImGuiTreeNodeFlags headerFlags = (mFlags & FLAG_COLLAPSED) ? 0 : ImGuiTreeNodeFlags_DefaultOpen; + auto& tm = mEngine->getTransformManager(); auto& rm = mEngine->getRenderableManager(); auto& lm = mEngine->getLightManager(); @@ -361,7 +370,7 @@ void SimpleViewer::updateUserInterface() { mView->setAmbientOcclusion( mEnableSsao ? View::AmbientOcclusion::SSAO : View::AmbientOcclusion::NONE); - if (ImGui::CollapsingHeader("Light", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader("Light", headerFlags)) { ImGui::SliderFloat("IBL intensity", &mIblIntensity, 0.0f, 100000.0f); ImGui::SliderAngle("IBL rotation", &mIblRotation); ImGui::SliderFloat("Sun intensity", &mSunlightIntensity, 50000.0, 150000.0f); @@ -378,23 +387,25 @@ void SimpleViewer::updateUserInterface() { mScene->remove(mSunlight); } - if (ImGui::CollapsingHeader("Model", ImGuiTreeNodeFlags_DefaultOpen)) { - if (mAnimator->getAnimationCount() > 0) { - intptr_t animationsNodeId = -1; - ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_DefaultOpen; - if (ImGui::TreeNodeEx((const void*) animationsNodeId, flags, "Animations")) { - animationsTreeItem(); - ImGui::TreePop(); + if (mAsset != nullptr) { + if (ImGui::CollapsingHeader("Model", headerFlags)) { + if (mAnimator->getAnimationCount() > 0) { + intptr_t animationsNodeId = -1; + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_DefaultOpen; + if (ImGui::TreeNodeEx((const void*) animationsNodeId, flags, "Animations")) { + animationsTreeItem(); + ImGui::TreePop(); + } } + ImGui::Checkbox("Wireframe", &mShowWireframe); + treeNode(mAsset->getRoot()); } - ImGui::Checkbox("Wireframe", &mShowWireframe); - treeNode(mAsset->getRoot()); - } - if (mShowWireframe) { - mScene->addEntity(mAsset->getWireframe()); - } else { - mScene->remove(mAsset->getWireframe()); + if (mShowWireframe) { + mScene->addEntity(mAsset->getWireframe()); + } else { + mScene->remove(mAsset->getWireframe()); + } } mSidebarWidth = ImGui::GetWindowWidth(); diff --git a/libs/image/include/image/LinearImage.h b/libs/image/include/image/LinearImage.h index a099221051..b534631928 100644 --- a/libs/image/include/image/LinearImage.h +++ b/libs/image/include/image/LinearImage.h @@ -59,6 +59,7 @@ public: * Creates an empty (invalid) image. */ LinearImage() : mDataRef(nullptr), mData(nullptr), mWidth(0), mHeight(0), mChannels(0) {} + operator bool() const { return mData != nullptr; } /** * Gets a pointer to the underlying pixel data. diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index df61bed283..cc7eeec635 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -32,6 +32,7 @@ add_mesh("assets/models/monkey/monkey.obj" "suzanne.filamesh") set(MATERIAL_SRCS materials/bakedColor.mat materials/bakedTexture.mat + materials/aoPreview.mat materials/aiDefaultMat.mat materials/aiDefaultTrans.mat materials/depthVisualizer.mat @@ -256,11 +257,13 @@ if (NOT ANDROID) add_filamesh_demo(sample_normal_map) add_filamesh_demo(suzanne) add_filamesh_demo(gltf_viewer) + add_filamesh_demo(gltf_baker) # Sample app specific target_link_libraries(frame_generator PRIVATE imageio) 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) endif() # ================================================================================================== diff --git a/samples/app/FilamentApp.cpp b/samples/app/FilamentApp.cpp index 200d2d2de9..77ffc004b8 100644 --- a/samples/app/FilamentApp.cpp +++ b/samples/app/FilamentApp.cpp @@ -64,6 +64,10 @@ FilamentApp::~FilamentApp() { SDL_Quit(); } +View* FilamentApp::getGuiView() const noexcept { + return mImGuiHelper->getView(); +} + void FilamentApp::run(const Config& config, SetupCallback setupCallback, CleanupCallback cleanupCallback, ImGuiCallback imguiCallback, PreRenderCallback preRender, PostRenderCallback postRender, @@ -515,6 +519,8 @@ FilamentApp::Window::Window(FilamentApp* filamentApp, mMainCameraMan.lookAt(at + double3{ 0, 0, 4 }, at); mDebugCameraMan.lookAt(at + double3{ 0, 0, 4 }, at); mOrthoCameraMan.lookAt(at + double3{ 0, 0, 4 }, at); + + mMainCamera->lookAt({4, 0, -4}, {0, 0, -4}, {0, 1, 0}); } FilamentApp::Window::~Window() { diff --git a/samples/app/FilamentApp.h b/samples/app/FilamentApp.h index dfa2474305..41bd8e79b6 100644 --- a/samples/app/FilamentApp.h +++ b/samples/app/FilamentApp.h @@ -75,6 +75,7 @@ public: filament::Material const* getDefaultMaterial() const noexcept { return mDefaultMaterial; } filament::Material const* getTransparentMaterial() const noexcept { return mTransparentMaterial; } IBL* getIBL() const noexcept { return mIBL.get(); } + filament::View* getGuiView() const noexcept; void close() { mClosed = true; } diff --git a/samples/gltf_baker.cpp b/samples/gltf_baker.cpp new file mode 100644 index 0000000000..443e5c8188 --- /dev/null +++ b/samples/gltf_baker.cpp @@ -0,0 +1,592 @@ +/* + * 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. + */ + +#define GLTFIO_SIMPLEVIEWER_IMPLEMENTATION + +#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 "generated/resources/gltf.h" +#include "generated/resources/resources.h" + +using namespace filament; +using namespace gltfio; +using namespace utils; + +enum AppState { + EMPTY, + LOADED, + RENDERING, + PREPPING, + PREPPED, + BAKING, + BAKED, +}; + +struct App { + Engine* engine; + Camera* camera; + SimpleViewer* viewer; + Config config; + AssetLoader* loader; + FilamentAsset* asset = nullptr; + NameComponentManager* names; + MaterialProvider* materials; + bool actualSize = false; + AppState state = EMPTY; + utils::Path filename; + image::LinearImage renderedImage; + bool showOverlay = false; + View* overlayView = nullptr; + Scene* overlayScene = nullptr; + VertexBuffer* overlayVb = nullptr; + IndexBuffer* overlayIb = nullptr; + Texture* overlayTexture = nullptr; + MaterialInstance* overlayMaterial = nullptr; + utils::Entity overlayEntity; + AppState pushedState; + std::atomic requestOverlayUpdate; + std::atomic requestStatePop; +}; + +struct OverlayVertex { + filament::math::float2 position; + filament::math::float2 uv; +}; + +static OverlayVertex OVERLAY_VERTICES[4] = { + {{0, 0}, {0, 0}}, + {{ 1000, 0}, {1, 0}}, + {{0, 1000}, {0, 1}}, + {{ 1000, 1000}, {1, 1}}, +}; + +static const char* DEFAULT_IBL = "envs/venetian_crossroads"; + +static const char* INI_FILENAME = "gltf_baker.ini"; + +static void printUsage(char* name) { + std::string exec_name(Path(name).getName()); + std::string usage( + "SHOWCASE can perform AO baking on the specified glTF file. If no file is specified," + "it loads the most recently-used glTF file.\n" + "Usage:\n" + " SHOWCASE [options] [gltf path]\n" + "Options:\n" + " --help, -h\n" + " Prints this message\n\n" + " --actual-size, -s\n" + " Do not scale the model to fit into a unit cube\n\n" + ); + const std::string from("SHOWCASE"); + for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) { + usage.replace(pos, from.length(), exec_name); + } + std::cout << usage; +} + +static int handleCommandLineArguments(int argc, char* argv[], App* app) { + static constexpr const char* OPTSTR = "ha:i:us"; + static const struct option OPTIONS[] = { + { "help", no_argument, nullptr, 'h' }, + { "actual-size", no_argument, nullptr, 's' }, + { nullptr, 0, nullptr, 0 } + }; + int opt; + int option_index = 0; + while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &option_index)) >= 0) { + std::string arg(optarg ? optarg : ""); + switch (opt) { + default: + case 'h': + printUsage(argv[0]); + exit(0); + case 's': + app->actualSize = true; + break; + } + } + return optind; +} + +static std::ifstream::pos_type getFileSize(const char* filename) { + std::ifstream in(filename, std::ifstream::ate | std::ifstream::binary); + return in.tellg(); +} + +static void saveIniFile(App& app) { + std::ofstream out(INI_FILENAME); + out << "[recent]\n"; + out << "filename=" << app.filename.c_str() << "\n"; +} + +static void loadIniFile(App& app) { + utils::Path iniPath(INI_FILENAME); + if (!app.filename.isEmpty() || !iniPath.isFile()) { + return; + } + std::ifstream infile(INI_FILENAME); + std::string line; + while (std::getline(infile, line)) { + size_t sep = line.find('='); + if (sep != std::string::npos) { + std::string lhs = line.substr(0, sep); + std::string rhs = line.substr(sep + 1); + if (lhs == "filename") { + utils::Path gltf = rhs; + if (gltf.isFile()) { + app.filename = rhs; + } + } + } + } +} + +static void updateOverlayVerts(App& app) { + auto viewportSize = ImGui::GetIO().DisplaySize; + viewportSize.x -= app.viewer->getSidebarWidth(); + OVERLAY_VERTICES[0].position.x = app.viewer->getSidebarWidth(); + OVERLAY_VERTICES[2].position.x = app.viewer->getSidebarWidth(); + OVERLAY_VERTICES[1].position.x = app.viewer->getSidebarWidth() + viewportSize.x; + OVERLAY_VERTICES[3].position.x = app.viewer->getSidebarWidth() + viewportSize.x; + OVERLAY_VERTICES[2].position.y = viewportSize.y; + OVERLAY_VERTICES[3].position.y = viewportSize.y; +}; + +static void updateOverlay(App& app) { + auto& rcm = app.engine->getRenderableManager(); + auto vb = app.overlayVb; + auto ib = app.overlayIb; + updateOverlayVerts(app); + vb->setBufferAt(*app.engine, 0, + VertexBuffer::BufferDescriptor(OVERLAY_VERTICES, 64, nullptr)); + rcm.destroy(app.overlayEntity); + RenderableManager::Builder(1) + .boundingBox({{ 0, 0, 0 }, { 1000, 1000, 1 }}) + .material(0, app.overlayMaterial) + .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vb, ib, 0, 6) + .culling(false) + .receiveShadows(false) + .castShadows(false) + .build(*app.engine, app.overlayEntity); +} + +static void updateOverlayTexture(App& app) { + Engine& engine = *app.engine; + int w = app.renderedImage.getWidth(); + int h = app.renderedImage.getHeight(); + void* data = app.renderedImage.getPixelRef(); + Texture::PixelBufferDescriptor buffer(data, size_t(w * h * 4), + Texture::Format::R, Texture::Type::FLOAT); + app.overlayTexture->setImage(engine, 0, std::move(buffer)); +} + +static void createOverlayTexture(App& app) { + Engine& engine = *app.engine; + using MinFilter = TextureSampler::MinFilter; + using MagFilter = TextureSampler::MagFilter; + + int w = app.renderedImage.getWidth(); + int h = app.renderedImage.getHeight(); + void* data = app.renderedImage.getPixelRef(); + Texture::PixelBufferDescriptor buffer(data, size_t(w * h * 4), + Texture::Format::R, Texture::Type::FLOAT); + auto tex = Texture::Builder() + .width(uint32_t(w)) + .height(uint32_t(h)) + .levels(1) + .sampler(Texture::Sampler::SAMPLER_2D) + .format(Texture::InternalFormat::R8) + .build(engine); + tex->setImage(engine, 0, std::move(buffer)); + + TextureSampler sampler(MinFilter::LINEAR, MagFilter::LINEAR); + app.overlayMaterial->setParameter("luma", tex, sampler); + + engine.destroy(app.overlayTexture); + app.overlayTexture = tex; +} + +static void createOverlay(App& app) { + Engine& engine = *app.engine; + + static constexpr uint16_t OVERLAY_INDICES[6] = { 0, 1, 2, 3, 2, 1 }; + + auto vb = VertexBuffer::Builder() + .vertexCount(4) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 16) + .attribute(VertexAttribute::UV0, 0, VertexBuffer::AttributeType::FLOAT2, 8, 16) + .build(engine); + auto ib = IndexBuffer::Builder() + .indexCount(6) + .bufferType(IndexBuffer::IndexType::USHORT) + .build(engine); + ib->setBuffer(engine, + IndexBuffer::BufferDescriptor(OVERLAY_INDICES, 12, nullptr)); + auto mat = Material::Builder() + .package(RESOURCES_AOPREVIEW_DATA, RESOURCES_AOPREVIEW_SIZE) + .build(engine); + auto matInstance = mat->createInstance(); + + app.overlayVb = vb; + app.overlayIb = ib; + app.overlayEntity = EntityManager::get().create(); + app.overlayMaterial = matInstance; +} + +static void loadAsset(App& app) { + std::cout << "Loading " << app.filename << "..." << std::endl; + + if (app.filename.getExtension() == "glb") { + std::cerr << "GLB files are not yet supported." << std::endl; + exit(1); + } + + // Peek at the file size to allow pre-allocation. + long contentSize = static_cast(getFileSize(app.filename.c_str())); + if (contentSize <= 0) { + std::cerr << "Unable to open " << app.filename << std::endl; + exit(1); + } + + // Consume the glTF file. + std::ifstream in(app.filename.c_str(), std::ifstream::in); + std::vector buffer(static_cast(contentSize)); + if (!in.read((char*) buffer.data(), contentSize)) { + std::cerr << "Unable to read " << app.filename << std::endl; + exit(1); + } + + // Parse the glTF file and create Filament entities. + app.asset = app.loader->createAssetFromJson(buffer.data(), buffer.size()); + buffer.clear(); + buffer.shrink_to_fit(); + + if (!app.asset) { + std::cerr << "Unable to parse " << app.filename << std::endl; + exit(1); + } + + // Load external textures and buffers. + gltfio::ResourceLoader({ + .engine = app.engine, + .gltfPath = app.filename.getAbsolutePath(), + .normalizeSkinningWeights = true, + .recomputeBoundingBoxes = false + }).loadResources(app.asset); + + // Load animation data then free the source hierarchy. + app.asset->getAnimator(); + app.asset->releaseSourceData(); + + // Add the renderables to the scene. + app.viewer->setAsset(app.asset, app.names, !app.actualSize); + + app.viewer->setIndirectLight(FilamentApp::get().getIBL()->getIndirectLight()); + + app.state = LOADED; +} + +static void renderAsset(App& app) { + app.pushedState = app.state; + app.state = RENDERING; + + // Reload the asset from disk because we haven't bothered to retain it. + gltfio::AssetPipeline pipeline; + gltfio::AssetPipeline::AssetHandle asset = pipeline.load(app.filename); + if (!asset) { + std::cerr << "Unable to read " << app.filename << std::endl; + exit(1); + } + + // Allocate the render target for the path tracer as well as a GPU texture to display it. + auto viewportSize = ImGui::GetIO().DisplaySize; + viewportSize.x -= app.viewer->getSidebarWidth(); + image::LinearImage image(viewportSize.x, viewportSize.y, 1); + app.renderedImage = image; + app.showOverlay = true; + createOverlayTexture(app); + + // Compute the camera paramaeters for the path tracer. + // --------------------------------------------------- + // The path tracer does not know about the top-level Filament transform that we use to fit the + // model into a unit cube (see the -s option), so here we do little trick by temporarily + // transforming the Filament camera before grabbing its lookAt vectors. + auto& tcm = app.engine->getTransformManager(); + auto root = tcm.getInstance(app.asset->getRoot()); + auto cam = tcm.getInstance(app.camera->getEntity()); + filament::math::mat4f prev = tcm.getTransform(root); + tcm.setTransform(root, inverse(prev)); + tcm.setParent(cam, root); + SimpleCamera camera = { + .aspectRatio = viewportSize.x / viewportSize.y, + .eyePosition = app.camera->getPosition(), + .targetPosition = app.camera->getPosition() + app.camera->getForwardVector(), + .upVector = app.camera->getUpVector(), + .vfovDegrees = 45, // NOTE: fov is not queryable, must match with FilamentApp + }; + tcm.setParent(cam, {}); + tcm.setTransform(root, prev); + + // Finally, set up some callbacks and invoke the path tracer. + using filament::math::ushort2; + auto onRenderTile = [](image::LinearImage, ushort2, ushort2, void* userData) { + App* app = (App*) userData; + app->requestOverlayUpdate = true; + }; + auto onRenderDone = [](image::LinearImage image, void* userData) { + App* app = (App*) userData; + app->requestStatePop = true; + }; + pipeline.renderAmbientOcclusion(asset, image, camera, onRenderTile, onRenderDone, &app); +} + +static void prepAsset(App& app) { + app.state = PREPPING; + + std::cout << "Prepping..." << std::endl; + + gltfio::AssetPipeline pipeline; + gltfio::AssetPipeline::AssetHandle asset = pipeline.load(app.filename); + if (!asset) { + std::cerr << "Unable to read " << app.filename << std::endl; + exit(1); + } + + asset = pipeline.flatten(asset); + if (!asset) { + std::cerr << "Unable to flatten " << app.filename << std::endl; + exit(1); + } + + asset = pipeline.parameterize(asset); + if (!asset) { + std::cerr << "Unable to parameterize " << app.filename << std::endl; + exit(1); + } + + const utils::Path folder = app.filename.getAbsolutePath().getParent(); + const utils::Path binPath = folder + "baked.bin"; + const utils::Path outPath = folder + "baked.gltf"; + + pipeline.save(asset, outPath, binPath); + std::cout << "Generated " << outPath << " and " << binPath << std::endl; + + app.filename = outPath; + loadAsset(app); + + app.state = PREPPED; +} + +static void bakeAsset(App& app) { + app.state = BAKING; + std::cout << "Baking..." << std::endl; + + gltfio::AssetPipeline pipeline; + gltfio::AssetPipeline::AssetHandle asset = pipeline.load(app.filename); + + using filament::math::ushort2; + image::LinearImage image(1024, 1024, 1); + + auto onRenderTile = [](image::LinearImage, ushort2, ushort2, void* userData) { + App* app = (App*) userData; + app->requestOverlayUpdate = true; + }; + auto onRenderDone = [](image::LinearImage image, void* userData) { + App* app = (App*) userData; + app->requestStatePop = true; + }; + pipeline.bakeAmbientOcclusion(asset, image, onRenderTile, onRenderDone, &app); + +} + +int main(int argc, char** argv) { + App app; + + app.config.title = "gltf_baker"; + app.config.iblDirectory = FilamentApp::getRootPath() + DEFAULT_IBL; + app.requestOverlayUpdate = false; + app.requestStatePop = false; + + int option_index = handleCommandLineArguments(argc, argv, &app); + int num_args = argc - option_index; + if (num_args >= 1) { + app.filename = argv[option_index]; + if (!app.filename.exists()) { + std::cerr << "file " << app.filename << " not found!" << std::endl; + return 1; + } + if (app.filename.isDirectory()) { + auto files = app.filename.listContents(); + for (auto file : files) { + if (file.getExtension() == "gltf") { + app.filename = file; + break; + } + } + if (app.filename.isDirectory()) { + std::cerr << "no glTF file found in " << app.filename << std::endl; + return 1; + } + } + } + + loadIniFile(app); + + auto setup = [&](Engine* engine, View* view, Scene* scene) { + app.engine = engine; + app.names = new NameComponentManager(EntityManager::get()); + app.viewer = new SimpleViewer(engine, scene, view, SimpleViewer::FLAG_COLLAPSED); + app.materials = createMaterialGenerator(engine); + app.loader = AssetLoader::create({engine, app.materials, app.names }); + app.camera = &view->getCamera(); + + if (!app.filename.isEmpty()) { + loadAsset(app); + saveIniFile(app); + } + + createOverlay(app); + + app.viewer->setUiCallback([&app, scene] () { + const ImVec4 disabled = ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]; + const ImVec4 enabled = ImGui::GetStyle().Colors[ImGuiCol_Text]; + + // Prep action (flattening and parameterizing). + const bool canPrep = app.state == LOADED; + ImGui::PushStyleColor(ImGuiCol_Text, canPrep ? enabled : disabled); + if (ImGui::Button("Prep", ImVec2(100, 50)) && canPrep) { + prepAsset(app); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Flattens the asset and generates a new set of UV coordinates."); + } + ImGui::PopStyleColor(); + ImGui::SameLine(); + + // Render action (invokes path tracer). + const bool canRender = app.state == PREPPED; + ImGui::PushStyleColor(ImGuiCol_Text, canRender ? enabled : disabled); + if (ImGui::Button("Render", ImVec2(100, 50)) && canRender) { + renderAsset(app); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Renders the asset using a pathtracer."); + } + ImGui::PopStyleColor(); + ImGui::SameLine(); + + // Bake action (invokes path tracer). + const bool canBake = app.state == PREPPED; + ImGui::PushStyleColor(ImGuiCol_Text, canBake ? enabled : disabled); + if (ImGui::Button("Bake", ImVec2(100, 50)) && canBake) { + bakeAsset(app); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Invokes an embree-based pathtracer."); + } + ImGui::PopStyleColor(); + + if (app.renderedImage) { + ImGui::Checkbox("Show embree result", &app.showOverlay); + } + }); + + // Leave FXAA enabled but we also enable MSAA for a nice result. The wireframe looks + // much better with MSAA enabled. + view->setSampleCount(4); + }; + + auto cleanup = [&app](Engine* engine, View*, Scene*) { + Fence::waitAndDestroy(engine->createFence()); + delete app.viewer; + app.loader->destroyAsset(app.asset); + app.materials->destroyMaterials(); + delete app.materials; + AssetLoader::destroy(&app.loader); + delete app.names; + }; + + auto animate = [&app](Engine* engine, View* view, double now) { + if (app.state != EMPTY) { + app.viewer->applyAnimation(now); + } + if (!app.overlayScene && app.showOverlay) { + app.overlayView = FilamentApp::get().getGuiView(); + app.overlayScene = app.overlayView->getScene(); + } + if (app.overlayScene) { + app.overlayScene->remove(app.overlayEntity); + if (app.showOverlay) { + updateOverlay(app); + app.overlayScene->addEntity(app.overlayEntity); + } + } + if (app.requestOverlayUpdate) { + updateOverlayTexture(app); + app.requestOverlayUpdate = false; + } + if (app.requestStatePop) { + app.state = app.pushedState; + app.pushedState = EMPTY; + app.requestStatePop = false; + } + }; + + auto gui = [&app](filament::Engine* engine, filament::View* view) { + app.viewer->updateUserInterface(); + FilamentApp::get().setSidebarWidth(app.viewer->getSidebarWidth()); + }; + + FilamentApp& filamentApp = FilamentApp::get(); + filamentApp.animate(animate); + + filamentApp.setDropHandler([&] (std::string path) { + app.viewer->removeAsset(); + app.loader->destroyAsset(app.asset); + app.filename = path; + loadAsset(app); + saveIniFile(app); + }); + + filamentApp.run(app.config, setup, cleanup, gui); + + return 0; +} diff --git a/samples/materials/aoPreview.mat b/samples/materials/aoPreview.mat new file mode 100644 index 0000000000..8c22f590ed --- /dev/null +++ b/samples/materials/aoPreview.mat @@ -0,0 +1,27 @@ +material { + name : aoPreview, + parameters : [ + { + type : sampler2d, + name : luma + } + ], + requires : [ + uv0 + ], + shadingModel : unlit, + culling : none, + depthCulling: false, + blending : transparent +} + +fragment { + void material(inout MaterialInputs material) { + prepareMaterial(material); + float L = texture(materialParams_luma, getUV0()).r; + if (L == 0.0) { + discard; + } + material.baseColor.rgb = vec3(L); + } +}