├── .clang-format ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── appveyor.yml ├── assets ├── shaders │ ├── base.frag │ ├── base.vert │ ├── downscale.frag │ ├── fullscreen.vert │ ├── pbr.frag │ ├── pbr.vert │ ├── postprocessing.frag │ ├── shadow.vert │ ├── skybox.frag │ ├── skybox.vert │ └── upscale.frag └── textures │ ├── brdf_lut.png │ └── skybox │ ├── cloudy │ ├── negx.jpg │ ├── negy.jpg │ ├── negz.jpg │ ├── posx.jpg │ ├── posy.jpg │ └── posz.jpg │ └── yokohama │ ├── negx.jpg │ ├── negy.jpg │ ├── negz.jpg │ ├── posx.jpg │ ├── posy.jpg │ ├── posz.jpg │ └── readme.txt ├── include ├── common │ ├── error_codes.hpp │ ├── include.hpp │ └── vez.hpp ├── engine.hpp ├── infrastructure │ └── cache.hpp ├── input │ ├── input.hpp │ └── input_system.hpp ├── platform │ ├── platform.hpp │ └── win32_platform.hpp ├── renderer │ ├── backend.hpp │ ├── handles.hpp │ ├── renderer.hpp │ └── vez │ │ ├── vez_backend.hpp │ │ └── vez_context.hpp ├── scene │ ├── attachment.hpp │ ├── attachments │ │ ├── camera.hpp │ │ ├── light.hpp │ │ ├── material.hpp │ │ ├── mesh.hpp │ │ └── texture.hpp │ ├── gen_index.hpp │ ├── loaders │ │ └── assimp_loader.hpp │ ├── node.hpp │ ├── scene.hpp │ └── scene_loader.hpp └── scripting │ ├── script.hpp │ ├── scripting_system.hpp │ └── scripts │ └── fly_camera.hpp ├── src ├── engine.cpp ├── input │ └── input_system.cpp ├── platform │ └── win32_platform.cpp ├── renderer │ ├── renderer.cpp │ └── vez │ │ └── vez_backend.cpp └── scene │ ├── assimp_loader.cpp │ └── scene.cpp ├── tests └── tests.cpp └── third_party └── outcome └── single-header └── outcome.hpp /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | AccessModifierOffset: '-2' 3 | IndentWidth: '4' 4 | Language: Cpp 5 | SortIncludes: false 6 | Standard: Cpp11 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .tags 3 | .tags1 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/assimp"] 2 | path = third_party/assimp 3 | url = https://github.com/assimp/assimp.git 4 | [submodule "third_party/glfw"] 5 | path = third_party/glfw 6 | url = https://github.com/glfw/glfw.git 7 | [submodule "third_party/glm"] 8 | path = third_party/glm 9 | url = https://github.com/g-truc/glm.git 10 | [submodule "third_party/googletest"] 11 | path = third_party/googletest 12 | url = https://github.com/google/googletest.git 13 | [submodule "third_party/volk"] 14 | path = third_party/volk 15 | url = https://github.com/zeux/volk.git 16 | [submodule "third_party/Vulkan-Headers"] 17 | path = third_party/Vulkan-Headers 18 | url = https://github.com/KhronosGroup/Vulkan-Headers.git 19 | [submodule "third_party/V-EZ"] 20 | path = third_party/V-EZ 21 | url = https://github.com/AttilioProvenzano/V-EZ.git 22 | [submodule "third_party/stb"] 23 | path = third_party/stb 24 | url = https://github.com/nothings/stb.git 25 | [submodule "assets/models"] 26 | path = assets/models 27 | url = https://github.com/AttilioProvenzano/goma-models.git 28 | [submodule "third_party/spdlog"] 29 | path = third_party/spdlog 30 | url = https://github.com/gabime/spdlog.git 31 | [submodule "third_party/variant"] 32 | path = third_party/variant 33 | url = https://github.com/mapbox/variant.git 34 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.12) 2 | 3 | project (GomaEngine C CXX) 4 | set(CMAKE_CXX_STANDARD 14) 5 | 6 | if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) 7 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") 8 | endif() 9 | if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY) 10 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") 11 | endif() 12 | if(NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY) 13 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") 14 | endif() 15 | 16 | add_compile_definitions(VK_USE_PLATFORM_WIN32_KHR) 17 | add_compile_definitions(NOMINMAX) 18 | 19 | set(PUBLIC_HEADERS 20 | include/engine.hpp 21 | include/common/error_codes.hpp 22 | include/common/include.hpp 23 | include/common/vez.hpp 24 | include/infrastructure/cache.hpp 25 | include/input/input.hpp 26 | include/input/input_system.hpp 27 | include/renderer/renderer.hpp 28 | include/renderer/handles.hpp 29 | include/renderer/backend.hpp 30 | include/renderer/vez/vez_backend.hpp 31 | include/renderer/vez/vez_context.hpp 32 | include/scene/scene.hpp 33 | include/scene/attachment.hpp 34 | include/scene/attachments/texture.hpp 35 | include/scene/attachments/material.hpp 36 | include/scene/attachments/camera.hpp 37 | include/scene/attachments/light.hpp 38 | include/scene/attachments/mesh.hpp 39 | include/scene/node.hpp 40 | include/scene/gen_index.hpp 41 | include/scene/scene_loader.hpp 42 | include/scene/loaders/assimp_loader.hpp 43 | include/scripting/scripting_system.hpp 44 | include/scripting/script.hpp 45 | include/scripting/scripts/fly_camera.hpp 46 | include/platform/platform.hpp 47 | include/platform/win32_platform.hpp 48 | ) 49 | set(SOURCES 50 | src/engine.cpp 51 | src/input/input_system.cpp 52 | src/renderer/renderer.cpp 53 | src/renderer/vez/vez_backend.cpp 54 | src/scene/scene.cpp 55 | src/scene/assimp_loader.cpp 56 | src/platform/win32_platform.cpp 57 | ) 58 | 59 | set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) 60 | add_library(goma-engine SHARED 61 | ${PUBLIC_HEADERS} 62 | ${SOURCES} 63 | ) 64 | set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF) 65 | 66 | set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "" FORCE) 67 | 68 | # Disable all importers except OBJ, FBX and GLTF 69 | set(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT OFF CACHE BOOL "" FORCE) 70 | foreach(name OBJ FBX GLTF) 71 | set(ASSIMP_BUILD_${name}_IMPORTER ON CACHE BOOL "" FORCE) 72 | endforeach() 73 | 74 | add_subdirectory(third_party/assimp) 75 | foreach(target assimp assimp_cmd IrrXML uninstall UpdateAssimpLibsDebugSymbolsAndDLLs zlib zlibstatic) 76 | if(TARGET ${target}) 77 | set_property(TARGET ${target} PROPERTY FOLDER ThirdParty/Assimp) 78 | endif() 79 | endforeach() 80 | 81 | set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) 82 | set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) 83 | set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) 84 | set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) 85 | add_subdirectory(third_party/glfw) 86 | set_property(TARGET glfw PROPERTY FOLDER ThirdParty) 87 | 88 | set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) 89 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 90 | add_subdirectory(third_party/googletest) 91 | foreach(target gtest gtest_main) 92 | if(TARGET ${target}) 93 | set_property(TARGET ${target} PROPERTY FOLDER ThirdParty/GoogleTest) 94 | endif() 95 | endforeach() 96 | 97 | add_library(volk 98 | third_party/volk/volk.c) 99 | set_property(TARGET volk PROPERTY FOLDER ThirdParty) 100 | 101 | target_include_directories(volk PUBLIC 102 | third_party/Vulkan-Headers/include 103 | third_party/volk) 104 | 105 | # The top-level CMakeLists.txt for V-EZ sets all output 106 | # directories to an inconvenient path. 107 | # We directly include the subdirectories to avoid this. 108 | set(VEZ_COMPILE_SAMPLES OFF CACHE BOOL "" FORCE) 109 | set(VEZ_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/V-EZ) 110 | find_package(Vulkan REQUIRED) 111 | add_subdirectory(third_party/V-EZ/External/SPIRV-Cross) 112 | set(ENABLE_HLSL OFF CACHE BOOL "" FORCE) 113 | add_subdirectory(third_party/V-EZ/External/glslang) 114 | add_subdirectory(third_party/V-EZ/Source) 115 | set_property(TARGET VEZ PROPERTY FOLDER ThirdParty/VEZ) 116 | 117 | foreach(target glslang glslang-default-resource-limits OGLCompiler OSDependent SPIRV SPVRemapper 118 | glslangValidator spirv-remap 119 | ) 120 | if(TARGET ${target}) 121 | set_property(TARGET ${target} PROPERTY FOLDER ThirdParty/VEZ/External/glslang) 122 | endif() 123 | endforeach() 124 | 125 | foreach(target spirv-cross spirv-cross-core spirv-cross-glsl spirv-cross-cpp spirv-cross-hlsl 126 | spirv-cross-msl spirv-cross-reflect spirv-cross-util 127 | ) 128 | if(TARGET ${target}) 129 | set_property(TARGET ${target} PROPERTY FOLDER ThirdParty/VEZ/External/SPIRV-Cross) 130 | endif() 131 | endforeach() 132 | 133 | target_include_directories(goma-engine PUBLIC 134 | include 135 | third_party/glm 136 | third_party/outcome/single-header 137 | third_party/spdlog/include 138 | third_party/stb 139 | third_party/variant/include 140 | third_party/V-EZ/Source 141 | third_party/Vulkan-Headers/include) 142 | 143 | target_link_libraries(goma-engine PUBLIC assimp glfw VEZ volk) 144 | 145 | if(MSVC) 146 | target_compile_options(goma-engine PRIVATE /W3 /WX) 147 | else() 148 | target_compile_options(goma-engine -Wall -Wextra -pedantic) 149 | endif() 150 | 151 | target_compile_definitions(goma-engine PUBLIC 152 | GOMA_ASSETS_DIR="${PROJECT_SOURCE_DIR}/assets/") 153 | 154 | add_executable(goma-tests 155 | tests/tests.cpp 156 | ) 157 | target_link_libraries(goma-tests PUBLIC gtest goma-engine) 158 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Attilio Provenzano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Goma :ocean: 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/kshyaf2ocy3cb0ot/branch/master?svg=true)](https://ci.appveyor.com/project/AttilioProvenzano/goma-engine) 4 | 5 | Goma is a simple 3D C++ game engine with Vulkan support. 6 | 7 | This is a learning project, meant for experimenting with graphics techniques. It currently lacks most basic features of a full-fledged engine, so it should not be used for anything resembling production! :stuck_out_tongue: 8 | 9 | The current plan is to add more features along the way, but I won't be developing/supporting it continuously. If you like the project and would like to contribute, issues/PRs are very welcome! :) 10 | 11 | ![Helmet screenshot](https://user-images.githubusercontent.com/14922868/57889040-a9d8df00-782b-11e9-8428-7735afe3627d.png) 12 | 13 | # Features 14 | 15 | Goma currently supports Windows with a [V-EZ](https://github.com/GPUOpen-LibrariesAndSDKs/V-EZ) backend. I have plans to port it to Android, but it will require a native Vulkan backend first. 16 | 17 | These are some of the features that Goma supports: 18 | 19 | * PBR with IBL for specular 20 | * Runtime shader compilation with variants 21 | * Shader reloading at runtime 22 | * Mipmapping 23 | * MSAA 24 | * Mesh culling and sorting 25 | * Shadow maps with PCF 26 | * DoF 27 | 28 | # Build 29 | 30 | Goma uses the CMake build system. It is tested with Visual Studio 2017 on Windows. 31 | 32 | First clone the submodules of this repo: 33 | 34 | ``` 35 | git submodule update --init --recursive 36 | ``` 37 | 38 | Then run CMake: 39 | 40 | ``` 41 | mkdir build && cd build 42 | cmake .. -G "Visual Studio 2017 Win64" 43 | ``` 44 | 45 | Then you can open the Visual Studio solution inside `build` and build it from there. 46 | 47 | The target `goma-engine` is meant to be included as a shared library by applications. The test suite `goma-tests` shows usage examples. 48 | 49 | # License 50 | 51 | Goma is licensed under the MIT license. Feel free to use it however you like! Contributions are accepted under the same license. 52 | 53 | This project uses third party dependencies, each of which may have independent licensing: 54 | 55 | * [assimp](https://github.com/assimp/assimp): A library to import and export various 3d-model-formats. 56 | * [glfw](https://github.com/glfw/glfw): A multi-platform library for OpenGL, OpenGL ES, Vulkan, window and input. 57 | * [glm](https://github.com/g-truc/glm): A header only C++ mathematics library for graphics software. 58 | * [googletest](https://github.com/google/googletest): A testing framework. 59 | * [outcome](https://github.com/ned14/outcome): Provides lightweight `outcome` and `result`. 60 | * [spdlog](https://github.com/gabime/spdlog): Fast C++ logging library. 61 | * [stb](https://github.com/nothings/stb): Single-file public domain (or MIT licensed) libraries for C/C++. 62 | * [variant](https://github.com/mapbox/variant): An header-only alternative to `boost::variant` for C++11 and C++14. 63 | * [V-EZ](https://github.com/GPUOpen-LibrariesAndSDKs/V-EZ): A wrapper intended to alleviate the inherent complexity of the Vulkan API. 64 | * [volk](https://github.com/zeux/volk): Meta loader for Vulkan API. 65 | * [Vulkan-Headers](https://github.com/KhronosGroup/Vulkan-Headers): Vulkan header files and API registry. 66 | 67 | PBR shaders are taken from [glTF-Sample-Viewer](https://github.com/KhronosGroup/glTF-Sample-Viewer) with modifications to adapt them to Vulkan GLSL. 68 | 69 | 3D models are taken from [glTF-Sample-Models](https://github.com/KhronosGroup/glTF-Sample-Models). Each model may have its own license. 70 | 71 | Other credits: 72 | 73 | * Yokohama cubemap texture from [Humus](http://www.humus.name/index.php?page=Textures) (CC-BY 3.0). 74 | * Cloudy cubemap texture by Spiney from [OpenGameArt](https://opengameart.org/content/cloudy-skyboxes) (CC-BY 3.0). 75 | * Sphere creation function from [Cute deferred shading](https://github.com/Erkaman/cute-deferred-shading). 76 | * BRDF LUT texture from [glTF-Sample-Viewer](https://github.com/KhronosGroup/glTF-Sample-Viewer). 77 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: 2 | - Visual Studio 2017 3 | 4 | platform: x64 5 | 6 | configuration: 7 | # - Debug 8 | - Release 9 | 10 | init: 11 | - cmd: cmake --version 12 | - cmd: msbuild /version 13 | 14 | install: 15 | - cmd: git submodule update --init --recursive 16 | - cmd: if not exist VulkanSDK.exe ( curl -L --retry 5 --silent --show-error --output VulkanSDK.exe https://vulkan.lunarg.com/sdk/download/1.1.106.0/windows/VulkanSDK-1.1.106.0-Installer.exe?Human=true ) 17 | - cmd: if not exist C:\VulkanSDK\1.1.106.0\ ( .\VulkanSDK.exe /S ^ ) 18 | 19 | environment: 20 | VULKAN_SDK: C:\VulkanSDK\1.1.106.0 21 | 22 | build_script: 23 | - cmd: mkdir -p build & exit 0 # ignore errors 24 | - cmd: cd build 25 | - cmd: cmake -G "Visual Studio 15 2017 Win64" -DCMAKE_BUILD_TYPE=%configuration% .. 26 | - cmd: cmake --build . --target ALL_BUILD --config %configuration% 27 | 28 | test_script: 29 | - cmd: dir 30 | - cmd: .\bin\%configuration%\goma-tests.exe 31 | 32 | cache: 33 | - VulkanSDK.exe -> appveyor.yml 34 | 35 | artifacts: 36 | - path: build\bin\%configuration%\goma-tests.exe 37 | name: Tests 38 | -------------------------------------------------------------------------------- /assets/shaders/base.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | #ifdef HAS_DIFFUSE_MAP 5 | layout(set = 0, binding = 0) uniform sampler2D diffuseTex; 6 | #endif 7 | 8 | #ifdef HAS_SPECULAR_MAP 9 | layout(set = 0, binding = 1) uniform sampler2D specularTex; 10 | #endif 11 | 12 | #ifdef HAS_AMBIENT_MAP 13 | layout(set = 0, binding = 2) uniform sampler2D ambientTex; 14 | #endif 15 | 16 | #ifdef HAS_EMISSIVE_MAP 17 | layout(set = 0, binding = 3) uniform sampler2D emissiveTex; 18 | #endif 19 | 20 | #ifdef HAS_METALLIC_ROUGHNESS_MAP 21 | layout(set = 0, binding = 4) uniform sampler2D metallicRoughnessTex; 22 | #endif 23 | 24 | #ifdef HAS_HEIGHT_MAP 25 | layout(set = 0, binding = 5) uniform sampler2D heightTex; 26 | #endif 27 | 28 | #ifdef HAS_NORMAL_MAP 29 | layout(set = 0, binding = 6) uniform sampler2D normalTex; 30 | #endif 31 | 32 | #ifdef HAS_SHININESS_MAP 33 | layout(set = 0, binding = 7) uniform sampler2D shininessTex; 34 | #endif 35 | 36 | #ifdef HAS_OPACITY_MAP 37 | layout(set = 0, binding = 8) uniform sampler2D opacityTex; 38 | #endif 39 | 40 | #ifdef HAS_DISPLACEMENT_MAP 41 | layout(set = 0, binding = 9) uniform sampler2D displacementTex; 42 | #endif 43 | 44 | #ifdef HAS_LIGHT_MAP 45 | layout(set = 0, binding = 10) uniform sampler2D lightTex; 46 | #endif 47 | 48 | #ifdef HAS_REFLECTION_MAP 49 | layout(set = 0, binding = 11) uniform sampler2D reflectionTex; 50 | #endif 51 | 52 | layout(location = 0) in vec2 inUVs; 53 | layout(location = 0) out vec4 outColor; 54 | 55 | void main() { 56 | outColor = texture(diffuseTex, vec2(1.0, -1.0) * inUVs); 57 | } 58 | -------------------------------------------------------------------------------- /assets/shaders/base.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #ifdef HAS_POSITIONS 4 | layout(location = 0) in vec3 inPosition; 5 | #endif 6 | 7 | #ifdef HAS_NORMALS 8 | layout(location = 1) in vec3 inNormal; 9 | #endif 10 | 11 | #ifdef HAS_TANGENTS 12 | layout(location = 2) in vec3 inTangent; 13 | #endif 14 | 15 | #ifdef HAS_BITANGENTS 16 | layout(location = 3) in vec3 inBitangent; 17 | #endif 18 | 19 | #ifdef HAS_COLORS 20 | layout(location = 4) in vec4 inColor; 21 | #endif 22 | 23 | #ifdef HAS_UV0 24 | layout(location = 5) in vec2 inUV0; 25 | #endif 26 | 27 | #ifdef HAS_UV1 28 | layout(location = 6) in vec2 inUV1; 29 | #endif 30 | 31 | #ifdef HAS_UVW 32 | layout(location = 7) in vec2 inUVW; 33 | #endif 34 | 35 | layout(location = 0) out vec2 outUV0; 36 | 37 | layout(set = 0, binding = 12, std140) uniform UBO { 38 | mat4 mvp; 39 | mat4 model; 40 | mat4 normal; 41 | } ubo; 42 | 43 | void main() { 44 | gl_Position = ubo.mvp * vec4(inPosition, 1.0); 45 | outUV0 = inUV0; 46 | } 47 | -------------------------------------------------------------------------------- /assets/shaders/downscale.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(set = 0, binding = 0) uniform sampler2D baseTex; 5 | 6 | layout(location = 0) in vec2 inUVs; 7 | layout(location = 0) out vec4 outColor; 8 | 9 | layout(set = 0, binding = 1, std140) uniform UBO { 10 | vec2 halfPixel; 11 | } ubo; 12 | 13 | void main() { 14 | vec2 hp = ubo.halfPixel; 15 | 16 | vec4 sum = texture(baseTex, inUVs) * 4.0; 17 | sum += texture(baseTex, inUVs - hp.xy); 18 | sum += texture(baseTex, inUVs + hp.xy); 19 | sum += texture(baseTex, inUVs + vec2(hp.x, -hp.y)); 20 | sum += texture(baseTex, inUVs - vec2(hp.x, -hp.y)); 21 | 22 | outColor = sum / 8.0; 23 | } 24 | -------------------------------------------------------------------------------- /assets/shaders/fullscreen.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | const vec2 pos[3] = { 4 | {-1.0, -1.0}, 5 | {-1.0, 3.0}, 6 | { 3.0, -1.0} 7 | }; 8 | 9 | const vec2 uvs[3] = { 10 | {0.0, 0.0}, 11 | {0.0, 2.0}, 12 | {2.0, 0.0} 13 | }; 14 | 15 | layout(location = 0) out vec2 outUVs; 16 | 17 | void main() { 18 | gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0); 19 | outUVs = uvs[gl_VertexIndex]; 20 | } 21 | -------------------------------------------------------------------------------- /assets/shaders/pbr.frag: -------------------------------------------------------------------------------- 1 | // 2 | // This fragment shader defines a reference implementation for Physically Based Shading of 3 | // a microfacet surface material defined by a glTF model. 4 | // 5 | // References: 6 | // [1] Real Shading in Unreal Engine 4 7 | // http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf 8 | // [2] Physically Based Shading at Disney 9 | // http://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf 10 | // [3] README.md - Environment Maps 11 | // https://github.com/KhronosGroup/glTF-WebGL-PBR/#environment-maps 12 | // [4] "An Inexpensive BRDF Model for Physically based Rendering" by Christophe Schlick 13 | // https://www.cs.virginia.edu/~jdl/bib/appearance/analytic%20models/schlick94b.pdf 14 | #version 450 15 | 16 | #ifdef HAS_DIFFUSE_MAP 17 | layout(set = 0, binding = 0) uniform sampler2D diffuseTex; 18 | #endif 19 | 20 | #ifdef HAS_SPECULAR_MAP 21 | layout(set = 0, binding = 1) uniform sampler2D specularTex; 22 | #endif 23 | 24 | #ifdef HAS_AMBIENT_MAP 25 | layout(set = 0, binding = 2) uniform sampler2D ambientTex; 26 | #endif 27 | 28 | #ifdef HAS_EMISSIVE_MAP 29 | layout(set = 0, binding = 3) uniform sampler2D emissiveTex; 30 | #endif 31 | 32 | #ifdef HAS_METALLIC_ROUGHNESS_MAP 33 | layout(set = 0, binding = 4) uniform sampler2D metallicRoughnessTex; 34 | #endif 35 | 36 | #ifdef HAS_HEIGHT_MAP 37 | layout(set = 0, binding = 5) uniform sampler2D heightTex; 38 | #endif 39 | 40 | #ifdef HAS_NORMAL_MAP 41 | layout(set = 0, binding = 6) uniform sampler2D normalTex; 42 | #endif 43 | 44 | #ifdef HAS_SHININESS_MAP 45 | layout(set = 0, binding = 7) uniform sampler2D shininessTex; 46 | #endif 47 | 48 | #ifdef HAS_OPACITY_MAP 49 | layout(set = 0, binding = 8) uniform sampler2D opacityTex; 50 | #endif 51 | 52 | #ifdef HAS_DISPLACEMENT_MAP 53 | layout(set = 0, binding = 9) uniform sampler2D displacementTex; 54 | #endif 55 | 56 | #ifdef HAS_LIGHT_MAP 57 | layout(set = 0, binding = 10) uniform sampler2D lightTex; 58 | #endif 59 | 60 | #ifdef HAS_REFLECTION_MAP 61 | layout(set = 0, binding = 11) uniform samplerCube reflectionTex; 62 | layout(set = 0, binding = 16) uniform sampler2D brdfLUT; 63 | #endif 64 | 65 | layout(set = 0, binding = 14) uniform sampler2DShadow shadowTex; 66 | 67 | const float M_PI = 3.141592653589793; 68 | const float c_MinReflectance = 0.04; 69 | 70 | layout(location = 0) in vec3 inPosition; 71 | 72 | #ifdef HAS_COLORS 73 | layout(location = 4) in vec4 inColor; 74 | #endif 75 | 76 | layout(location = 5) in vec2 inUV0; 77 | layout(location = 6) in vec2 inUV1; 78 | 79 | #ifdef HAS_NORMALS 80 | #ifdef HAS_TANGENTS 81 | layout(location = 8) in mat3 inTBN; 82 | #else 83 | layout(location = 8) in vec3 inNormal; 84 | #endif 85 | #endif 86 | 87 | layout(location = 11) in vec3 inShadowPos; 88 | 89 | layout(location = 0) out vec4 outColor; 90 | 91 | layout(set = 0, binding = 13, std140) uniform FragUBO { 92 | vec4 egmr; // x: exposure / y: gamma / z: metallic / w: roughness 93 | vec4 baseColor; 94 | vec4 cameraAndCutoff; // xyz: camera / w: alpha cutoff 95 | vec2 mipIBL; // x: reflection mipCount / y: IBL strength 96 | } ubo; 97 | 98 | struct Light 99 | { 100 | vec4 directionAndType; 101 | vec4 colorAndIntensity; 102 | vec4 positionAndRange; 103 | vec2 innerAndOuterConeCos; 104 | }; 105 | 106 | const int LightType_Directional = 0; 107 | const int LightType_Point = 1; 108 | const int LightType_Spot = 2; 109 | 110 | const int MAX_LIGHTS = 64; 111 | 112 | layout(set = 0, binding = 15, std140) uniform LightUBO { 113 | vec4 ambientAndNumLights; 114 | vec4 shadowIds; 115 | Light lights[MAX_LIGHTS]; 116 | } lightUbo; 117 | 118 | struct AngularInfo 119 | { 120 | float NdotL; // cos angle between normal and light direction 121 | float NdotV; // cos angle between normal and view direction 122 | float NdotH; // cos angle between normal and half vector 123 | float LdotH; // cos angle between light direction and half vector 124 | 125 | float VdotH; // cos angle between view direction and half vector 126 | 127 | vec3 padding; 128 | }; 129 | 130 | vec4 getVertexColor() 131 | { 132 | vec4 color = vec4(1.0, 1.0, 1.0, 1.0); 133 | 134 | #ifdef HAS_COLORS 135 | color = inColor; 136 | #endif 137 | 138 | return color; 139 | } 140 | 141 | // Find the normal for this fragment, pulling either from a predefined normal map 142 | // or from the interpolated mesh normal and tangent attributes. 143 | vec3 getNormal() 144 | { 145 | vec2 UV = inUV0; 146 | 147 | // Retrieve the tangent space matrix 148 | #ifndef HAS_TANGENTS 149 | vec3 pos_dx = dFdx(inPosition); 150 | vec3 pos_dy = dFdy(inPosition); 151 | vec3 tex_dx = dFdx(vec3(UV, 0.0)); 152 | vec3 tex_dy = dFdy(vec3(UV, 0.0)); 153 | vec3 t = (tex_dy.t * pos_dx - tex_dx.t * pos_dy) / (tex_dx.s * tex_dy.t - tex_dy.s * tex_dx.t); 154 | 155 | #ifdef HAS_NORMALS 156 | vec3 ng = normalize(inNormal); 157 | #else 158 | vec3 ng = cross(pos_dx, pos_dy); 159 | #endif 160 | 161 | t = normalize(t - ng * dot(ng, t)); 162 | vec3 b = normalize(cross(ng, t)); 163 | mat3 tbn = mat3(t, b, ng); 164 | #else // HAS_TANGENTS 165 | mat3 tbn = inTBN; 166 | #endif 167 | 168 | #ifdef HAS_NORMAL_MAP 169 | vec3 n = texture(normalTex, UV).rgb; 170 | n = normalize(tbn * (2.0 * n - 1.0)); 171 | #else 172 | // The tbn matrix is linearly interpolated, so we need to re-normalize 173 | vec3 n = normalize(tbn[2].xyz); 174 | #endif 175 | 176 | return n; 177 | } 178 | 179 | float getPerceivedBrightness(vec3 vector) 180 | { 181 | return sqrt(0.299 * vector.r * vector.r + 0.587 * vector.g * vector.g + 0.114 * vector.b * vector.b); 182 | } 183 | 184 | // https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows/js/three.pbrUtilities.js#L34 185 | float solveMetallic(vec3 diffuse, vec3 specular, float oneMinusSpecularStrength) { 186 | float specularBrightness = getPerceivedBrightness(specular); 187 | 188 | if (specularBrightness < c_MinReflectance) { 189 | return 0.0; 190 | } 191 | 192 | float diffuseBrightness = getPerceivedBrightness(diffuse); 193 | 194 | float a = c_MinReflectance; 195 | float b = diffuseBrightness * oneMinusSpecularStrength / (1.0 - c_MinReflectance) + specularBrightness - 2.0 * c_MinReflectance; 196 | float c = c_MinReflectance - specularBrightness; 197 | float D = b * b - 4.0 * a * c; 198 | 199 | return clamp((-b + sqrt(D)) / (2.0 * a), 0.0, 1.0); 200 | } 201 | 202 | AngularInfo getAngularInfo(vec3 pointToLight, vec3 normal, vec3 view) 203 | { 204 | // Standard one-letter names 205 | vec3 n = normalize(normal); // Outward direction of surface point 206 | vec3 v = normalize(view); // Direction from surface point to view 207 | vec3 l = normalize(pointToLight); // Direction from surface point to light 208 | vec3 h = normalize(l + v); // Direction of the vector between l and v 209 | 210 | float NdotL = clamp(dot(n, l), 0.0, 1.0); 211 | float NdotV = clamp(dot(n, v), 0.0, 1.0); 212 | float NdotH = clamp(dot(n, h), 0.0, 1.0); 213 | float LdotH = clamp(dot(l, h), 0.0, 1.0); 214 | float VdotH = clamp(dot(v, h), 0.0, 1.0); 215 | 216 | return AngularInfo( 217 | NdotL, 218 | NdotV, 219 | NdotH, 220 | LdotH, 221 | VdotH, 222 | vec3(0, 0, 0) 223 | ); 224 | } 225 | 226 | struct MaterialInfo 227 | { 228 | float perceptualRoughness; // roughness value, as authored by the model creator (input to shader) 229 | vec3 reflectance0; // full reflectance color (normal incidence angle) 230 | 231 | float alphaRoughness; // roughness mapped to a more linear change in the roughness (proposed by [2]) 232 | vec3 diffuseColor; // color contribution from diffuse lighting 233 | 234 | vec3 reflectance90; // reflectance color at grazing angle 235 | vec3 specularColor; // color contribution from specular lighting 236 | }; 237 | 238 | // Gamma Correction in Computer Graphics 239 | // see https://www.teamten.com/lawrence/graphics/gamma/ 240 | vec3 gammaCorrection(vec3 color) 241 | { 242 | return pow(color, vec3(1.0 / ubo.egmr.y)); 243 | } 244 | 245 | // sRGB to linear approximation 246 | // see http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html 247 | vec4 SRGBtoLINEAR(vec4 srgbIn) 248 | { 249 | return vec4(pow(srgbIn.xyz, vec3(ubo.egmr.y)), srgbIn.w); 250 | } 251 | 252 | // Uncharted 2 tone map 253 | // see: http://filmicworlds.com/blog/filmic-tonemapping-operators/ 254 | vec3 toneMapUncharted2Impl(vec3 color) 255 | { 256 | const float A = 0.15; 257 | const float B = 0.50; 258 | const float C = 0.10; 259 | const float D = 0.20; 260 | const float E = 0.02; 261 | const float F = 0.30; 262 | return ((color*(A*color+C*B)+D*E)/(color*(A*color+B)+D*F))-E/F; 263 | } 264 | 265 | vec3 toneMapUncharted(vec3 color) 266 | { 267 | const float W = 11.2; 268 | color = toneMapUncharted2Impl(color * 2.0); 269 | vec3 whiteScale = 1.0 / toneMapUncharted2Impl(vec3(W)); 270 | return gammaCorrection(color * whiteScale); 271 | } 272 | 273 | // Hejl Richard tone map 274 | // see: http://filmicworlds.com/blog/filmic-tonemapping-operators/ 275 | vec3 toneMapHejlRichard(vec3 color) 276 | { 277 | color = max(vec3(0.0), color - vec3(0.004)); 278 | return (color*(6.2*color+.5))/(color*(6.2*color+1.7)+0.06); 279 | } 280 | 281 | vec3 toneMap(vec3 color) 282 | { 283 | color *= ubo.egmr.x; 284 | 285 | #ifdef TONEMAP_UNCHARTED 286 | return toneMapUncharted(color); 287 | #endif 288 | 289 | #ifdef TONEMAP_HEJLRICHARD 290 | return toneMapHejlRichard(color); 291 | #endif 292 | 293 | return gammaCorrection(color); 294 | } 295 | 296 | // Lambert lighting 297 | // see https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/ 298 | vec3 diffuse(MaterialInfo materialInfo) 299 | { 300 | return materialInfo.diffuseColor / M_PI; 301 | } 302 | 303 | // The following equation models the Fresnel reflectance term of the spec equation (aka F()) 304 | // Implementation of fresnel from [4], Equation 15 305 | vec3 specularReflection(MaterialInfo materialInfo, AngularInfo angularInfo) 306 | { 307 | return materialInfo.reflectance0 + (materialInfo.reflectance90 - materialInfo.reflectance0) * pow(clamp(1.0 - angularInfo.VdotH, 0.0, 1.0), 5.0); 308 | } 309 | 310 | // Smith Joint GGX 311 | // Note: Vis = G / (4 * NdotL * NdotV) 312 | // see Eric Heitz. 2014. Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs. Journal of Computer Graphics Techniques, 3 313 | // see Real-Time Rendering. Page 331 to 336. 314 | // see https://google.github.io/filament/Filament.md.html#materialsystem/specularbrdf/geometricshadowing(specularg) 315 | float visibilityOcclusion(MaterialInfo materialInfo, AngularInfo angularInfo) 316 | { 317 | float NdotL = angularInfo.NdotL; 318 | float NdotV = angularInfo.NdotV; 319 | float alphaRoughnessSq = materialInfo.alphaRoughness * materialInfo.alphaRoughness; 320 | 321 | float GGXV = NdotL * sqrt(NdotV * NdotV * (1.0 - alphaRoughnessSq) + alphaRoughnessSq); 322 | float GGXL = NdotV * sqrt(NdotL * NdotL * (1.0 - alphaRoughnessSq) + alphaRoughnessSq); 323 | 324 | return 0.5 / (GGXV + GGXL); 325 | } 326 | 327 | // The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D()) 328 | // Implementation from "Average Irregularity Representation of a Roughened Surface for Ray Reflection" by T. S. Trowbridge, and K. P. Reitz 329 | // Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games [1], Equation 3. 330 | float microfacetDistribution(MaterialInfo materialInfo, AngularInfo angularInfo) 331 | { 332 | float alphaRoughnessSq = materialInfo.alphaRoughness * materialInfo.alphaRoughness; 333 | float f = (angularInfo.NdotH * alphaRoughnessSq - angularInfo.NdotH) * angularInfo.NdotH + 1.0; 334 | return alphaRoughnessSq / (M_PI * f * f); 335 | } 336 | 337 | vec3 getPointShade(vec3 pointToLight, MaterialInfo materialInfo, vec3 normal, vec3 view) 338 | { 339 | AngularInfo angularInfo = getAngularInfo(pointToLight, normal, view); 340 | 341 | if (angularInfo.NdotL > 0.0 && angularInfo.NdotV > 0.0) 342 | { 343 | // Calculate the shading terms for the microfacet specular shading model 344 | vec3 F = specularReflection(materialInfo, angularInfo); 345 | float Vis = visibilityOcclusion(materialInfo, angularInfo); 346 | float D = microfacetDistribution(materialInfo, angularInfo); 347 | 348 | // Calculation of analytical lighting contribution 349 | vec3 diffuseContrib = (1.0 - F) * diffuse(materialInfo); 350 | vec3 specContrib = F * Vis * D; 351 | 352 | // Obtain final intensity as reflectance (BRDF) scaled by the energy of the light (cosine law) 353 | return angularInfo.NdotL * (diffuseContrib + specContrib); 354 | } 355 | 356 | return vec3(0.0, 0.0, 0.0); 357 | } 358 | 359 | // https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md#range-property 360 | float getRangeAttenuation(float range, float distance) 361 | { 362 | if (range < 0.0) 363 | { 364 | // negative range means unlimited 365 | return 1.0; 366 | } 367 | return max(min(1.0 - pow(distance / range, 4.0), 1.0), 0.0) / pow(distance, 2.0); 368 | } 369 | 370 | // https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md#inner-and-outer-cone-angles 371 | float getSpotAttenuation(vec3 pointToLight, vec3 spotDirection, float outerConeCos, float innerConeCos) 372 | { 373 | float actualCos = dot(normalize(spotDirection), normalize(-pointToLight)); 374 | if (actualCos > outerConeCos) 375 | { 376 | if (actualCos < innerConeCos) 377 | { 378 | return smoothstep(outerConeCos, innerConeCos, actualCos); 379 | } 380 | return 1.0; 381 | } 382 | return 0.0; 383 | } 384 | 385 | vec3 applyDirectionalLight(Light light, MaterialInfo materialInfo, vec3 normal, vec3 view) 386 | { 387 | vec3 pointToLight = -light.directionAndType.xyz; 388 | vec3 shade = getPointShade(pointToLight, materialInfo, normal, view); 389 | return light.colorAndIntensity.a * light.colorAndIntensity.rgb * shade; 390 | } 391 | 392 | vec3 applyPointLight(Light light, MaterialInfo materialInfo, vec3 normal, vec3 view) 393 | { 394 | vec3 pointToLight = light.positionAndRange.xyz - inPosition; 395 | float distance = length(pointToLight); 396 | float attenuation = getRangeAttenuation(light.positionAndRange.w, distance); 397 | vec3 shade = getPointShade(pointToLight, materialInfo, normal, view); 398 | return attenuation * light.colorAndIntensity.a * light.colorAndIntensity.rgb * shade; 399 | } 400 | 401 | vec3 applySpotLight(Light light, MaterialInfo materialInfo, vec3 normal, vec3 view) 402 | { 403 | vec3 pointToLight = light.positionAndRange.xyz - inPosition; 404 | float distance = length(pointToLight); 405 | float rangeAttenuation = getRangeAttenuation(light.positionAndRange.w, distance); 406 | float spotAttenuation = getSpotAttenuation(pointToLight, light.directionAndType.xyz, 407 | light.innerAndOuterConeCos.y, light.innerAndOuterConeCos.x); 408 | vec3 shade = getPointShade(pointToLight, materialInfo, normal, view); 409 | return rangeAttenuation * spotAttenuation * light.colorAndIntensity.a 410 | * light.colorAndIntensity.rgb * shade; 411 | } 412 | 413 | #ifdef HAS_REFLECTION_MAP 414 | vec3 getIBLContribution(MaterialInfo materialInfo, vec3 n, vec3 v) 415 | { 416 | float NdotV = clamp(dot(n, v), 0.0, 1.0); 417 | float mipCount = ubo.mipIBL.x; 418 | float lod = clamp(materialInfo.perceptualRoughness * mipCount, 0.0, mipCount); 419 | vec3 reflection = normalize(reflect(-v, n)); 420 | 421 | vec2 brdfSamplePoint = clamp(vec2(NdotV, materialInfo.perceptualRoughness), vec2(0.0, 0.0), vec2(1.0, 1.0)); 422 | // retrieve a scale and bias to F0. See [1], Figure 3 423 | vec2 brdf = texture(brdfLUT, brdfSamplePoint).rg; 424 | vec4 specularSample = texture(reflectionTex, reflection, lod); 425 | 426 | vec3 specularLight = SRGBtoLINEAR(specularSample).rgb; 427 | vec3 specular = specularLight * (materialInfo.specularColor * brdf.x + brdf.y); 428 | return specular; 429 | } 430 | #endif 431 | 432 | void main() 433 | { 434 | // Metallic and Roughness material properties are packed together 435 | // In glTF, these factors can be specified by fixed scalar values 436 | // or from a metallic-roughness map 437 | float perceptualRoughness = 0.0; 438 | float metallic = 0.0; 439 | vec4 baseColor = vec4(0.0, 0.0, 0.0, 1.0); 440 | vec3 diffuseColor = vec3(0.0); 441 | vec3 specularColor= vec3(0.0); 442 | vec3 f0 = vec3(0.04); 443 | 444 | #ifdef HAS_METALLIC_ROUGHNESS_MAP 445 | // Roughness is stored in the 'g' channel, metallic is stored in the 'b' channel. 446 | // This layout intentionally reserves the 'r' channel for (optional) occlusion map data 447 | vec4 mrSample = texture(metallicRoughnessTex, inUV0); 448 | perceptualRoughness = mrSample.g * ubo.egmr.w; 449 | metallic = mrSample.b * ubo.egmr.z; 450 | #else 451 | metallic = ubo.egmr.z; 452 | perceptualRoughness = ubo.egmr.w; 453 | #endif 454 | 455 | // The albedo may be defined from a base texture or a flat color 456 | #ifdef HAS_DIFFUSE_MAP 457 | baseColor = SRGBtoLINEAR(texture(diffuseTex, inUV0)) * ubo.baseColor; 458 | #else 459 | baseColor = ubo.baseColor; 460 | #endif 461 | 462 | baseColor *= getVertexColor(); 463 | 464 | diffuseColor = baseColor.rgb * (vec3(1.0) - f0) * (1.0 - metallic); 465 | 466 | specularColor = mix(f0, baseColor.rgb, metallic); 467 | 468 | #ifdef ALPHAMODE_MASK 469 | if(baseColor.a < ubo.cameraAndCutoff.w) 470 | { 471 | discard; 472 | } 473 | baseColor.a = 1.0; 474 | #endif 475 | 476 | #ifdef ALPHAMODE_OPAQUE 477 | baseColor.a = 1.0; 478 | #endif 479 | 480 | #ifdef MATERIAL_UNLIT 481 | outColor = vec4(gammaCorrection(baseColor.rgb), baseColor.a); 482 | return; 483 | #endif 484 | 485 | perceptualRoughness = clamp(perceptualRoughness, 0.0, 1.0); 486 | metallic = clamp(metallic, 0.0, 1.0); 487 | 488 | // Roughness is authored as perceptual roughness; as is convention, 489 | // convert to material roughness by squaring the perceptual roughness [2]. 490 | float alphaRoughness = perceptualRoughness * perceptualRoughness; 491 | 492 | // Compute reflectance. 493 | float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b); 494 | 495 | vec3 specularEnvironmentR0 = specularColor.rgb; 496 | // Anything less than 2% is physically impossible and is instead considered to be shadowing. Compare to "Real-Time-Rendering" 4th editon on page 325. 497 | vec3 specularEnvironmentR90 = vec3(clamp(reflectance * 50.0, 0.0, 1.0)); 498 | 499 | MaterialInfo materialInfo = MaterialInfo( 500 | perceptualRoughness, 501 | specularEnvironmentR0, 502 | alphaRoughness, 503 | diffuseColor, 504 | specularEnvironmentR90, 505 | specularColor 506 | ); 507 | 508 | // LIGHTING 509 | 510 | const vec3 ambientColor = lightUbo.ambientAndNumLights.rgb; 511 | vec3 color = ambientColor * diffuseColor; 512 | vec3 normal = getNormal(); 513 | vec3 view = normalize(ubo.cameraAndCutoff.xyz - inPosition); 514 | 515 | int numLights = floatBitsToInt(lightUbo.ambientAndNumLights.w); 516 | int shadowId = floatBitsToInt(lightUbo.shadowIds[0]); 517 | for (int i = 0; i < numLights; ++i) 518 | { 519 | Light light = lightUbo.lights[i]; 520 | 521 | int type = floatBitsToInt(light.directionAndType.w); 522 | if (type == LightType_Directional) 523 | { 524 | float shadow = 1.0; 525 | if (i == shadowId) 526 | { 527 | shadow = texture(shadowTex, inShadowPos).r; 528 | } 529 | 530 | color += shadow * applyDirectionalLight(light, materialInfo, normal, view); 531 | } 532 | else if (type == LightType_Point) 533 | { 534 | color += applyPointLight(light, materialInfo, normal, view); 535 | } 536 | else if (type == LightType_Spot) 537 | { 538 | color += applySpotLight(light, materialInfo, normal, view); 539 | } 540 | } 541 | 542 | float ao = 1.0; 543 | // Apply optional PBR terms for additional (optional) shading 544 | #ifdef HAS_LIGHT_MAP 545 | // Occlusion map 546 | ao = texture(lightTex, inUV0).r; 547 | color = mix(color, color * ao, 1.0); // Forced occlusionStrength = 1.0 548 | #endif 549 | 550 | vec3 emissive = vec3(0); 551 | #ifdef HAS_EMISSIVE_MAP 552 | emissive = SRGBtoLINEAR(texture(emissiveTex, inUV0)).rgb; 553 | color += emissive; 554 | #endif 555 | 556 | #ifdef HAS_REFLECTION_MAP 557 | color += ubo.mipIBL.y * getIBLContribution(materialInfo, normal, view); 558 | #endif 559 | 560 | #ifndef DEBUG_OUTPUT // no debug 561 | 562 | // regular shading 563 | outColor = vec4(toneMap(color), baseColor.a); 564 | 565 | #else // debug output 566 | 567 | #ifdef DEBUG_METALLIC 568 | outColor.rgb = vec3(metallic); 569 | #endif 570 | 571 | #ifdef DEBUG_ROUGHNESS 572 | outColor.rgb = vec3(perceptualRoughness); 573 | #endif 574 | 575 | #ifdef DEBUG_NORMAL 576 | #ifdef HAS_NORMAL_MAP 577 | outColor.rgb = texture(normalTex, inUV0).rgb; 578 | #else 579 | outColor.rgb = vec3(0.5, 0.5, 1.0); 580 | #endif 581 | #endif 582 | 583 | #ifdef DEBUG_BASECOLOR 584 | outColor.rgb = gammaCorrection(baseColor.rgb); 585 | #endif 586 | 587 | #ifdef DEBUG_OCCLUSION 588 | outColor.rgb = vec3(ao); 589 | #endif 590 | 591 | #ifdef DEBUG_EMISSIVE 592 | outColor.rgb = gammaCorrection(emissive); 593 | #endif 594 | 595 | #ifdef DEBUG_F0 596 | outColor.rgb = vec3(f0); 597 | #endif 598 | 599 | #ifdef DEBUG_ALPHA 600 | outColor.rgb = vec3(baseColor.a); 601 | #endif 602 | 603 | outColor.a = 1.0; 604 | 605 | #endif // !DEBUG_OUTPUT 606 | } 607 | -------------------------------------------------------------------------------- /assets/shaders/pbr.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #ifdef HAS_POSITIONS 4 | layout(location = 0) in vec3 inPosition; 5 | #endif 6 | 7 | #ifdef HAS_NORMALS 8 | layout(location = 1) in vec3 inNormal; 9 | #endif 10 | 11 | #ifdef HAS_TANGENTS 12 | layout(location = 2) in vec3 inTangent; 13 | #endif 14 | 15 | #ifdef HAS_BITANGENTS 16 | layout(location = 3) in vec3 inBitangent; 17 | #endif 18 | 19 | #ifdef HAS_COLORS 20 | layout(location = 4) in vec4 inColor; 21 | #endif 22 | 23 | #ifdef HAS_UV0 24 | layout(location = 5) in vec2 inUV0; 25 | #endif 26 | 27 | #ifdef HAS_UV1 28 | layout(location = 6) in vec2 inUV1; 29 | #endif 30 | 31 | #ifdef HAS_UVW 32 | layout(location = 7) in vec2 inUVW; 33 | #endif 34 | 35 | layout(location = 0) out vec3 outPosition; 36 | 37 | #ifdef HAS_COLORS 38 | layout(location = 4) out vec4 outColor; 39 | #endif 40 | 41 | layout(location = 5) out vec2 outUV0; 42 | layout(location = 6) out vec2 outUV1; 43 | 44 | #ifdef HAS_NORMALS 45 | #ifdef HAS_TANGENTS 46 | layout(location = 8) out mat3 outTBN; 47 | #else 48 | layout(location = 8) out vec3 outNormal; 49 | #endif 50 | #endif 51 | 52 | layout(location = 11) out vec3 outShadowPos; 53 | 54 | layout(set = 0, binding = 12, std140) uniform VertUBO { 55 | mat4 mvp; 56 | mat4 model; 57 | mat4 normal; 58 | mat4 shadowMvp; 59 | } ubo; 60 | 61 | void main() 62 | { 63 | vec4 pos = ubo.model * vec4(inPosition, 1.0); 64 | outPosition = pos.xyz / pos.w; 65 | 66 | #ifdef HAS_NORMALS 67 | #ifdef HAS_TANGENTS 68 | vec3 normalW = normalize(vec3(ubo.normal * vec4(inNormal.xyz, 0.0))); 69 | vec3 tangentW = normalize(vec3(ubo.model * vec4(inTangent.xyz, 0.0))); 70 | vec3 bitangentW = cross(normalW, tangentW); 71 | outTBN = mat3(tangentW, bitangentW, normalW); 72 | #else // !HAS_TANGENTS 73 | outNormal = normalize(vec3(ubo.normal * vec4(inNormal.xyz, 0.0))); 74 | #endif 75 | #endif // !HAS_NORMALS 76 | 77 | outUV0 = vec2(0.0, 0.0); 78 | outUV1 = vec2(0.0, 0.0); 79 | 80 | #ifdef HAS_UV0 81 | outUV0 = vec2(inUV0.x, 1.0 - inUV0.y); 82 | #endif 83 | 84 | #ifdef HAS_UV1 85 | outUV1 = vec2(inUV1.x, 1.0 - inUV1.y); 86 | #endif 87 | 88 | #ifdef HAS_COLORS 89 | outColor = inColor; 90 | #endif 91 | 92 | vec4 shadowPos = ubo.shadowMvp * vec4(inPosition, 1.0); 93 | outShadowPos = shadowPos.xyz / shadowPos.w; 94 | 95 | gl_Position = ubo.mvp * vec4(inPosition, 1.0); 96 | } 97 | -------------------------------------------------------------------------------- /assets/shaders/postprocessing.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(set = 0, binding = 0) uniform sampler2D baseTex; 5 | layout(set = 0, binding = 1) uniform sampler2D blurTex; 6 | layout(set = 0, binding = 2) uniform sampler2DMS depthTex; 7 | 8 | layout(location = 0) in vec2 inUVs; 9 | layout(location = 0) out vec4 outColor; 10 | 11 | layout(set = 0, binding = 3, std140) uniform UBO { 12 | vec4 dofParams; // x: focusDistance / y: focusRange / z: strength 13 | vec2 nearFarPlane; 14 | } ubo; 15 | 16 | float linearizeDepth(float depth, float nearPlane, float farPlane) 17 | { 18 | return nearPlane * farPlane / (farPlane + depth * (nearPlane - farPlane)); 19 | } 20 | 21 | void main() { 22 | float focusDistance = 4.0; // ubo.dofParams.x; 23 | float focusRange = 10.0; // ubo.dofParams.y; 24 | float dofStrength = ubo.dofParams.z; 25 | 26 | // Depth of field 27 | float depth = texelFetch(depthTex, ivec2(gl_FragCoord.xy), 0).r; 28 | float linDepth = linearizeDepth(depth, ubo.nearFarPlane.x, ubo.nearFarPlane.y); 29 | 30 | float coc = clamp(abs(linDepth - focusDistance) / focusRange, 0.0, 1.0) * dofStrength; 31 | outColor = mix(texture(baseTex, inUVs), texture(blurTex, inUVs), coc); 32 | } 33 | -------------------------------------------------------------------------------- /assets/shaders/shadow.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #ifdef HAS_POSITIONS 4 | layout(location = 0) in vec3 inPosition; 5 | #endif 6 | 7 | #ifdef HAS_NORMALS 8 | layout(location = 1) in vec3 inNormal; 9 | #endif 10 | 11 | #ifdef HAS_TANGENTS 12 | layout(location = 2) in vec3 inTangent; 13 | #endif 14 | 15 | #ifdef HAS_BITANGENTS 16 | layout(location = 3) in vec3 inBitangent; 17 | #endif 18 | 19 | #ifdef HAS_COLORS 20 | layout(location = 4) in vec4 inColor; 21 | #endif 22 | 23 | #ifdef HAS_UV0 24 | layout(location = 5) in vec2 inUV0; 25 | #endif 26 | 27 | #ifdef HAS_UV1 28 | layout(location = 6) in vec2 inUV1; 29 | #endif 30 | 31 | #ifdef HAS_UVW 32 | layout(location = 7) in vec2 inUVW; 33 | #endif 34 | 35 | layout(set = 0, binding = 12, std140) uniform UBO { 36 | mat4 mvp; 37 | mat4 model; 38 | mat4 normal; 39 | } ubo; 40 | 41 | void main() { 42 | gl_Position = ubo.mvp * vec4(inPosition, 1.0); 43 | } 44 | -------------------------------------------------------------------------------- /assets/shaders/skybox.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(set = 0, binding = 0) uniform samplerCube diffuseTex; 5 | 6 | layout(location = 0) in vec3 inUVW; 7 | layout(location = 0) out vec4 outColor; 8 | 9 | void main() { 10 | outColor = textureLod(diffuseTex, inUVW, 3.5); 11 | } 12 | -------------------------------------------------------------------------------- /assets/shaders/skybox.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(set = 0, binding = 12, std140) uniform SkyboxUBO { 4 | mat4 vp; 5 | } ubo; 6 | 7 | layout(location = 0) in vec3 inPosition; 8 | layout(location = 0) out vec3 outUVW; 9 | 10 | void main() { 11 | gl_Position = ubo.vp * vec4(inPosition, 0.0); 12 | outUVW = inPosition; 13 | } 14 | -------------------------------------------------------------------------------- /assets/shaders/upscale.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(set = 0, binding = 0) uniform sampler2D baseTex; 5 | 6 | layout(location = 0) in vec2 inUVs; 7 | layout(location = 0) out vec4 outColor; 8 | 9 | layout(set = 0, binding = 1, std140) uniform UBO { 10 | vec2 halfPixel; 11 | } ubo; 12 | 13 | void main() { 14 | vec2 hp = ubo.halfPixel; 15 | 16 | vec4 sum = texture(baseTex, inUVs + vec2(-hp.x * 2.0, 0.0)); 17 | sum += texture(baseTex, inUVs + vec2(-hp.x, hp.y)) * 2.0; 18 | sum += texture(baseTex, inUVs + vec2(0.0, hp.y * 2.0)); 19 | sum += texture(baseTex, inUVs + vec2(hp.x, hp.y)) * 2.0; 20 | sum += texture(baseTex, inUVs + vec2(hp.x * 2.0, 0.0)); 21 | sum += texture(baseTex, inUVs + vec2(hp.x, -hp.y)) * 2.0; 22 | sum += texture(baseTex, inUVs + vec2(0.0, -hp.y * 2.0)); 23 | sum += texture(baseTex, inUVs + vec2(-hp.x, -hp.y)) * 2.0; 24 | 25 | outColor = sum / 12.0; 26 | } 27 | -------------------------------------------------------------------------------- /assets/textures/brdf_lut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AttilioProvenzano/goma-engine/fc61e4e3db2868a70ef0b8b7b21c5fd3b1a37100/assets/textures/brdf_lut.png -------------------------------------------------------------------------------- /assets/textures/skybox/cloudy/negx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AttilioProvenzano/goma-engine/fc61e4e3db2868a70ef0b8b7b21c5fd3b1a37100/assets/textures/skybox/cloudy/negx.jpg -------------------------------------------------------------------------------- /assets/textures/skybox/cloudy/negy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AttilioProvenzano/goma-engine/fc61e4e3db2868a70ef0b8b7b21c5fd3b1a37100/assets/textures/skybox/cloudy/negy.jpg -------------------------------------------------------------------------------- /assets/textures/skybox/cloudy/negz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AttilioProvenzano/goma-engine/fc61e4e3db2868a70ef0b8b7b21c5fd3b1a37100/assets/textures/skybox/cloudy/negz.jpg -------------------------------------------------------------------------------- /assets/textures/skybox/cloudy/posx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AttilioProvenzano/goma-engine/fc61e4e3db2868a70ef0b8b7b21c5fd3b1a37100/assets/textures/skybox/cloudy/posx.jpg -------------------------------------------------------------------------------- /assets/textures/skybox/cloudy/posy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AttilioProvenzano/goma-engine/fc61e4e3db2868a70ef0b8b7b21c5fd3b1a37100/assets/textures/skybox/cloudy/posy.jpg -------------------------------------------------------------------------------- /assets/textures/skybox/cloudy/posz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AttilioProvenzano/goma-engine/fc61e4e3db2868a70ef0b8b7b21c5fd3b1a37100/assets/textures/skybox/cloudy/posz.jpg -------------------------------------------------------------------------------- /assets/textures/skybox/yokohama/negx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AttilioProvenzano/goma-engine/fc61e4e3db2868a70ef0b8b7b21c5fd3b1a37100/assets/textures/skybox/yokohama/negx.jpg -------------------------------------------------------------------------------- /assets/textures/skybox/yokohama/negy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AttilioProvenzano/goma-engine/fc61e4e3db2868a70ef0b8b7b21c5fd3b1a37100/assets/textures/skybox/yokohama/negy.jpg -------------------------------------------------------------------------------- /assets/textures/skybox/yokohama/negz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AttilioProvenzano/goma-engine/fc61e4e3db2868a70ef0b8b7b21c5fd3b1a37100/assets/textures/skybox/yokohama/negz.jpg -------------------------------------------------------------------------------- /assets/textures/skybox/yokohama/posx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AttilioProvenzano/goma-engine/fc61e4e3db2868a70ef0b8b7b21c5fd3b1a37100/assets/textures/skybox/yokohama/posx.jpg -------------------------------------------------------------------------------- /assets/textures/skybox/yokohama/posy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AttilioProvenzano/goma-engine/fc61e4e3db2868a70ef0b8b7b21c5fd3b1a37100/assets/textures/skybox/yokohama/posy.jpg -------------------------------------------------------------------------------- /assets/textures/skybox/yokohama/posz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AttilioProvenzano/goma-engine/fc61e4e3db2868a70ef0b8b7b21c5fd3b1a37100/assets/textures/skybox/yokohama/posz.jpg -------------------------------------------------------------------------------- /assets/textures/skybox/yokohama/readme.txt: -------------------------------------------------------------------------------- 1 | Author 2 | ====== 3 | 4 | This is the work of Emil Persson, aka Humus. 5 | http://www.humus.name 6 | 7 | 8 | 9 | License 10 | ======= 11 | 12 | This work is licensed under a Creative Commons Attribution 3.0 Unported License. 13 | http://creativecommons.org/licenses/by/3.0/ 14 | -------------------------------------------------------------------------------- /include/common/error_codes.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace goma { 7 | 8 | enum class Error { 9 | Success = 0, 10 | OutOfCPUMemory = 1, 11 | OutOfGPUMemory = 2, 12 | DeviceLost = 3, 13 | VulkanLayerNotPresent = 4, 14 | VulkanExtensionNotPresent = 5, 15 | VulkanInitializationFailed = 6, 16 | GenericVulkanError = 7, 17 | GlfwError = 8, 18 | GlfwWindowCreationFailed = 9, 19 | NotFound = 10, 20 | InvalidNode = 11, 21 | InvalidParentNode = 12, 22 | RootNodeCannotBeDeleted = 13, 23 | SceneImportFailed = 14, 24 | DecompressionFailed = 15, 25 | LoadingFailed = 16, 26 | KeyAlreadyExists = 17, 27 | InvalidAttachment = 18, 28 | BufferCannotBeMapped = 19, 29 | NoSceneLoaded = 20, 30 | NoMainCamera = 21, 31 | ConfigNotSupported = 22, 32 | DimensionsNotMatching = 23, 33 | NoRenderPlan = 24, 34 | }; 35 | 36 | } 37 | 38 | namespace std { 39 | 40 | // Tell the C++ 11 STL metaprogramming that our error code 41 | // is registered with the standard error code system 42 | template <> 43 | struct is_error_code_enum : std::true_type {}; 44 | 45 | } // namespace std 46 | 47 | namespace detail { 48 | // Define a custom error code category derived from std::error_category 49 | class GomaError_category : public std::error_category { 50 | public: 51 | // Return a short descriptive name for the category 52 | virtual const char *name() const noexcept override final { 53 | return "GomaError"; 54 | } 55 | 56 | // Return what each enum means in text 57 | virtual std::string message(int c) const override final { 58 | switch (static_cast(c)) { 59 | case goma::Error::Success: 60 | return "success"; 61 | case goma::Error::OutOfCPUMemory: 62 | return "CPU is out of memory"; 63 | case goma::Error::OutOfGPUMemory: 64 | return "GPU is out of memory"; 65 | case goma::Error::DeviceLost: 66 | return "GPU failure (device lost)"; 67 | case goma::Error::VulkanLayerNotPresent: 68 | return "a Vulkan layer was not found"; 69 | case goma::Error::VulkanExtensionNotPresent: 70 | return "a Vulkan extension was not found"; 71 | case goma::Error::VulkanInitializationFailed: 72 | return "Vulkan initialization failed"; 73 | case goma::Error::GenericVulkanError: 74 | return "Vulkan error"; 75 | case goma::Error::GlfwError: 76 | return "GLFW error"; 77 | case goma::Error::GlfwWindowCreationFailed: 78 | return "GLFW could not create a window"; 79 | case goma::Error::NotFound: 80 | return "element not found"; 81 | case goma::Error::InvalidNode: 82 | return "node is invalid"; 83 | case goma::Error::InvalidParentNode: 84 | return "parent node is invalid"; 85 | case goma::Error::RootNodeCannotBeDeleted: 86 | return "root node cannot be deleted"; 87 | case goma::Error::SceneImportFailed: 88 | return "scene could not be imported"; 89 | case goma::Error::DecompressionFailed: 90 | return "decompression failed"; 91 | case goma::Error::LoadingFailed: 92 | return "loading failed"; 93 | case goma::Error::KeyAlreadyExists: 94 | return "the key already exists"; 95 | case goma::Error::InvalidAttachment: 96 | return "invalid attachment"; 97 | case goma::Error::BufferCannotBeMapped: 98 | return "buffer cannot be mapped"; 99 | case goma::Error::NoSceneLoaded: 100 | return "no scene loaded"; 101 | case goma::Error::NoMainCamera: 102 | return "no main camera"; 103 | case goma::Error::ConfigNotSupported: 104 | return "configuration item not supported"; 105 | case goma::Error::DimensionsNotMatching: 106 | return "dimensions not matching"; 107 | case goma::Error::NoRenderPlan: 108 | return "no render plan"; 109 | default: 110 | return "unknown error"; 111 | } 112 | } 113 | }; 114 | } // namespace detail 115 | 116 | // Declare a global function returning a static instance of the custom category 117 | extern inline const detail::GomaError_category &GomaError_category() { 118 | static detail::GomaError_category c; 119 | return c; 120 | } 121 | 122 | namespace std { 123 | 124 | // Overload the global make_error_code() free function with our 125 | // custom enum. It will be found via ADL by the compiler if needed. 126 | inline std::error_code make_error_code(goma::Error e) { 127 | return {static_cast(e), GomaError_category()}; 128 | } 129 | 130 | } // namespace std 131 | -------------------------------------------------------------------------------- /include/common/include.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define GLM_FORCE_RADIANS 7 | #define GLM_FORCE_DEPTH_ZERO_TO_ONE 8 | #define GLM_FORCE_XYZW_ONLY 9 | #define GLM_ENABLE_EXPERIMENTAL 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | namespace outcome = OUTCOME_V2_NAMESPACE; 18 | using outcome::result; 19 | 20 | #include 21 | using mapbox::util::variant; 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #define _USE_MATH_DEFINES 40 | #include 41 | -------------------------------------------------------------------------------- /include/common/vez.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define VK_NO_PROTOTYPES 4 | #include "VEZ.h" 5 | #include -------------------------------------------------------------------------------- /include/engine.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "input/input_system.hpp" 4 | #include "renderer/renderer.hpp" 5 | #include "scripting/scripting_system.hpp" 6 | #include "platform/platform.hpp" 7 | #include "scene/scene.hpp" 8 | #include "scene/attachments/camera.hpp" 9 | #include "scene/attachments/light.hpp" 10 | 11 | namespace goma { 12 | 13 | class Engine { 14 | public: 15 | Engine(); 16 | 17 | result MainLoop(MainLoopFn inner_fn); 18 | result LoadScene(const char* file_path); 19 | 20 | Platform& platform() { return *platform_.get(); } 21 | InputSystem& input_system() { return *input_system_.get(); } 22 | ScriptingSystem& scripting_system() { return *scripting_system_.get(); } 23 | Renderer& renderer() { return *renderer_.get(); } 24 | Scene* scene() { return scene_.get(); } 25 | AttachmentIndex main_camera() { return main_camera_; } 26 | 27 | uint32_t frame_count() { return frame_count_; } 28 | 29 | private: 30 | std::unique_ptr platform_{}; 31 | std::unique_ptr input_system_{}; 32 | std::unique_ptr scripting_system_{}; 33 | std::unique_ptr renderer_{}; 34 | 35 | std::unique_ptr scene_{}; 36 | AttachmentIndex main_camera_{}; 37 | 38 | uint32_t frame_count_{0}; 39 | 40 | uint32_t fps_cap{60}; 41 | std::chrono::duration delta_time_{0.0f}; 42 | std::chrono::time_point 43 | frame_timestamp_{std::chrono::high_resolution_clock::now()}; 44 | 45 | result> CreateDefaultCamera(); 46 | result> CreateDefaultLight(); 47 | }; 48 | 49 | } // namespace goma 50 | -------------------------------------------------------------------------------- /include/infrastructure/cache.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace goma { 7 | 8 | template 9 | class Cache { 10 | public: 11 | template 12 | std::shared_ptr create(KeyT key, Ts&&... params) { 13 | auto p = std::make_shared(std::forward(params)...); 14 | map_[key] = p; 15 | return p; 16 | } 17 | 18 | std::shared_ptr get(KeyT key) { 19 | auto res = map_.find(key); 20 | if (res != map_.end()) { 21 | return res->second.lock(); 22 | } 23 | return {}; 24 | } 25 | 26 | size_t erase(KeyT key) { return map_.erase(key); } 27 | 28 | void clear() { map_.clear(); } 29 | 30 | private: 31 | using MapT = std::unordered_map, KeyHashT>; 32 | MapT map_{}; 33 | }; 34 | 35 | } // namespace goma 36 | -------------------------------------------------------------------------------- /include/input/input.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/include.hpp" 4 | 5 | namespace goma { 6 | 7 | enum class KeyInput { W, A, S, D, C, H, R, Up, Down, Left, Right }; 8 | 9 | struct InputState { 10 | std::set keypresses{}; 11 | }; 12 | 13 | } // namespace goma 14 | -------------------------------------------------------------------------------- /include/input/input_system.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "platform/platform.hpp" 4 | #include "input/input.hpp" 5 | 6 | #include "common/include.hpp" 7 | 8 | namespace goma { 9 | 10 | class InputSystem { 11 | public: 12 | InputSystem(const Platform& platform); 13 | 14 | result AcquireFrameInput(); 15 | 16 | InputState GetFrameInput() { return frame_input_; }; 17 | InputState GetLastFrameInput() { return last_frame_input_; }; 18 | 19 | private: 20 | const Platform& platform_; 21 | 22 | InputState frame_input_{}; 23 | InputState last_frame_input_{}; 24 | }; 25 | 26 | } // namespace goma 27 | -------------------------------------------------------------------------------- /include/platform/platform.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "input/input.hpp" 4 | 5 | #include "common/include.hpp" 6 | #include "common/vez.hpp" 7 | 8 | namespace goma { 9 | 10 | using MainLoopFn = std::function; 11 | 12 | class Platform { 13 | public: 14 | virtual ~Platform() = default; 15 | 16 | virtual result MainLoop(MainLoopFn inner_loop) = 0; 17 | 18 | virtual uint32_t GetWidth() const = 0; 19 | virtual uint32_t GetHeight() const = 0; 20 | 21 | virtual InputState GetInputState() const = 0; 22 | 23 | virtual result InitWindow(int width, int height) = 0; 24 | virtual result CreateVulkanSurface( 25 | VkInstance instance) const = 0; 26 | 27 | virtual void Sleep(uint32_t microseconds) = 0; 28 | }; 29 | 30 | } // namespace goma 31 | -------------------------------------------------------------------------------- /include/platform/win32_platform.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "platform/platform.hpp" 4 | 5 | #include "common/include.hpp" 6 | 7 | struct GLFWwindow; 8 | 9 | namespace goma { 10 | 11 | class Win32Platform : public Platform { 12 | public: 13 | virtual ~Win32Platform() override; 14 | 15 | virtual result MainLoop(MainLoopFn inner_loop) override; 16 | 17 | virtual uint32_t GetWidth() const override; 18 | virtual uint32_t GetHeight() const override; 19 | 20 | virtual InputState GetInputState() const override; 21 | 22 | virtual result InitWindow(int width = 1280, int height = 800) override; 23 | virtual result CreateVulkanSurface( 24 | VkInstance instance) const override; 25 | 26 | virtual void Sleep(uint32_t microseconds) override; 27 | 28 | private: 29 | GLFWwindow* window_{nullptr}; 30 | }; 31 | 32 | } // namespace goma 33 | -------------------------------------------------------------------------------- /include/renderer/backend.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer/handles.hpp" 4 | #include "platform/platform.hpp" 5 | #include "scene/attachments/mesh.hpp" 6 | 7 | #include "common/include.hpp" 8 | #include "common/error_codes.hpp" 9 | 10 | namespace goma { 11 | 12 | class Engine; 13 | 14 | enum class Buffering { Double, Triple }; 15 | enum class FramebufferColorSpace { Linear, Srgb }; 16 | 17 | using FrameIndex = size_t; 18 | using PassFn = std::function(FrameIndex, const RenderPassDesc*)>; 19 | 20 | class Backend { 21 | public: 22 | struct Config { 23 | Buffering buffering{Buffering::Triple}; 24 | FramebufferColorSpace fb_color_space{FramebufferColorSpace::Linear}; 25 | }; 26 | 27 | Backend(const Config& config = {}, const RenderPlan& render_plan = {}) 28 | : config_(config), render_plan_(render_plan) {} 29 | virtual ~Backend() = default; 30 | 31 | const RenderPlan& render_plan() { return render_plan_; } 32 | const Config& config() { return config_; } 33 | 34 | virtual result SetRenderPlan(RenderPlan render_plan) { 35 | render_plan_ = std::move(render_plan); 36 | return outcome::success(); 37 | } 38 | 39 | virtual result SetBuffering(Buffering) { 40 | return Error::ConfigNotSupported; 41 | } 42 | 43 | virtual result SetFramebufferColorSpace( 44 | FramebufferColorSpace fb_color_space) { 45 | return Error::ConfigNotSupported; 46 | } 47 | 48 | virtual result InitContext() = 0; 49 | virtual result InitSurface(Platform& platform) = 0; 50 | virtual result> GetGraphicsPipeline( 51 | const ShaderDesc& vert, const ShaderDesc& frag = {}) = 0; 52 | virtual result ClearShaderCache() = 0; 53 | virtual result> GetVertexInputFormat( 54 | const VertexInputFormatDesc& desc) = 0; 55 | 56 | virtual result> CreateTexture( 57 | const char* name, const TextureDesc& texture_desc, 58 | void* initial_contents = nullptr) = 0; 59 | virtual result> CreateCubemap( 60 | const char* name, const TextureDesc& texture_desc, 61 | const CubemapContents& initial_contents) = 0; 62 | virtual result> GetTexture(const char* name) = 0; 63 | virtual result> GetRenderTarget( 64 | FrameIndex frame_id, const char* name) = 0; 65 | virtual Extent GetAbsoluteExtent(Extent extent) = 0; 66 | 67 | virtual result> CreateUniformBuffer( 68 | BufferType type, const GenIndex& index, const char* name, uint64_t size, 69 | bool gpu_stored = true, void* initial_contents = nullptr) = 0; 70 | virtual result> GetUniformBuffer( 71 | BufferType type, const GenIndex& index, const char* name) = 0; 72 | virtual result> CreateUniformBuffer( 73 | const char* name, uint64_t size, bool gpu_stored = true, 74 | void* initial_contents = nullptr) = 0; 75 | virtual result> GetUniformBuffer( 76 | const char* name) = 0; 77 | 78 | virtual result> CreateVertexBuffer( 79 | const AttachmentIndex& mesh, const char* name, uint64_t size, 80 | bool gpu_stored = true, void* initial_contents = nullptr) = 0; 81 | virtual result> GetVertexBuffer( 82 | const AttachmentIndex& mesh, const char* name) = 0; 83 | virtual result> CreateIndexBuffer( 84 | const AttachmentIndex& mesh, const char* name, uint64_t size, 85 | bool gpu_stored = true, void* initial_contents = nullptr) = 0; 86 | virtual result> GetIndexBuffer( 87 | const AttachmentIndex& mesh, const char* name) = 0; 88 | virtual result UpdateBuffer(const Buffer& buffer, uint64_t offset, 89 | uint64_t size, const void* contents) = 0; 90 | 91 | virtual result RenderFrame(std::vector pass_fns, 92 | const char* present_image) = 0; 93 | 94 | virtual result BindUniformBuffer(const Buffer& buffer, 95 | uint64_t offset, uint64_t size, 96 | uint32_t binding, 97 | uint32_t array_index = 0) = 0; 98 | virtual result BindTexture( 99 | const Image& image, uint32_t binding = 0, 100 | const SamplerDesc* sampler_override = nullptr) = 0; 101 | virtual result BindTextures( 102 | const std::vector& images, uint32_t first_binding = 0, 103 | const SamplerDesc* sampler_override = nullptr) = 0; 104 | virtual result BindVertexBuffer(const Buffer& vertex_buffer, 105 | uint32_t binding = 0, 106 | size_t offset = 0) = 0; 107 | virtual result BindVertexBuffers( 108 | const std::vector& vertex_buffers, uint32_t first_binding = 0, 109 | std::vector offsets = {}) = 0; 110 | virtual result BindIndexBuffer(const Buffer& index_buffer, 111 | size_t offset = 0, 112 | bool short_indices = false) = 0; 113 | 114 | virtual result BindVertexInputFormat( 115 | VertexInputFormat vertex_input_format) = 0; 116 | virtual result BindDepthStencilState( 117 | const DepthStencilState& state) = 0; 118 | virtual result BindColorBlendState(const ColorBlendState& state) = 0; 119 | virtual result BindMultisampleState( 120 | const MultisampleState& state) = 0; 121 | virtual result BindInputAssemblyState( 122 | const InputAssemblyState& state) = 0; 123 | virtual result BindRasterizationState( 124 | const RasterizationState& state) = 0; 125 | virtual result BindViewportState(uint32_t viewport_count) = 0; 126 | 127 | virtual result SetDepthBias(float constant_factor, float clamp, 128 | float slope_factor) = 0; 129 | virtual result SetDepthBounds(float min, float max) = 0; 130 | virtual result SetStencil(StencilFace face, uint32_t reference, 131 | uint32_t write_mask, 132 | uint32_t compare_mask) = 0; 133 | virtual result SetBlendConstants( 134 | const std::array& blend_constants) = 0; 135 | virtual result SetViewport(const std::vector viewports, 136 | uint32_t first_viewport = 0) = 0; 137 | virtual result SetScissor(const std::vector scissors, 138 | uint32_t first_scissor = 0) = 0; 139 | 140 | virtual result BindGraphicsPipeline(Pipeline pipeline) = 0; 141 | virtual result Draw(uint32_t vertex_count, 142 | uint32_t instance_count = 1, 143 | uint32_t first_vertex = 0, 144 | uint32_t first_instance = 0) = 0; 145 | virtual result DrawIndexed(uint32_t index_count, 146 | uint32_t instance_count = 1, 147 | uint32_t first_index = 0, 148 | uint32_t vertex_offset = 0, 149 | uint32_t first_instance = 0) = 0; 150 | 151 | virtual result TeardownContext() = 0; 152 | 153 | protected: 154 | Config config_{}; 155 | RenderPlan render_plan_{}; 156 | }; 157 | 158 | } // namespace goma 159 | -------------------------------------------------------------------------------- /include/renderer/handles.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/include.hpp" 4 | #include "common/vez.hpp" 5 | 6 | namespace goma { 7 | 8 | struct VulkanImage { 9 | VkImage image{VK_NULL_HANDLE}; 10 | VkImageView image_view{VK_NULL_HANDLE}; 11 | VkSampler sampler{VK_NULL_HANDLE}; 12 | }; 13 | 14 | struct Image { 15 | VulkanImage vez{}; 16 | bool valid{false}; 17 | 18 | Image(VulkanImage vez_) : vez(vez_), valid(true) {} 19 | }; 20 | 21 | struct Framebuffer { 22 | VezFramebuffer vez{}; 23 | bool valid{false}; 24 | 25 | Framebuffer(VezFramebuffer vez_) : vez(vez_), valid(true) {} 26 | }; 27 | 28 | struct Pipeline { 29 | VezPipeline vez{VK_NULL_HANDLE}; 30 | bool valid{false}; 31 | 32 | Pipeline(VezPipeline vez_) : vez(vez_), valid(true) {} 33 | }; 34 | 35 | struct Buffer { 36 | VkBuffer vez{VK_NULL_HANDLE}; 37 | bool valid{false}; 38 | 39 | Buffer(VkBuffer vez_) : vez(vez_), valid(true) {} 40 | }; 41 | 42 | struct Viewport { 43 | float width; 44 | float height; 45 | float x{0.0f}; 46 | float y{0.0f}; 47 | float min_depth{0.0f}; 48 | float max_depth{1.0f}; 49 | }; 50 | 51 | struct Scissor { 52 | uint32_t width; 53 | uint32_t height; 54 | int32_t x{0}; 55 | int32_t y{0}; 56 | }; 57 | 58 | struct VertexInputFormat { 59 | const VezVertexInputFormat vez{VK_NULL_HANDLE}; 60 | bool valid{false}; 61 | 62 | VertexInputFormat(VezVertexInputFormat vez_) : vez(vez_), valid(true) {} 63 | }; 64 | 65 | enum class Format { 66 | Undefined, 67 | SwapchainFormat, 68 | UNormRGBA8, 69 | UNormBGRA8, 70 | SrgbRGBA8, 71 | SFloatRGBA32, 72 | SFloatRGB32, 73 | SFloatRG32, 74 | SFloatR32, 75 | DepthOnly, 76 | DepthStencil 77 | }; 78 | 79 | enum class FilterType { Nearest, Linear }; 80 | 81 | enum class TextureType { 82 | Diffuse, // also Albedo for PBR 83 | Specular, 84 | Ambient, 85 | Emissive, 86 | MetallicRoughness, 87 | HeightMap, 88 | NormalMap, 89 | Shininess, 90 | Opacity, 91 | Displacement, 92 | LightMap, // also OcclusionMap 93 | Reflection, 94 | }; 95 | 96 | enum class TextureOp { 97 | Multiply, 98 | Add, 99 | Subtract, 100 | Divide, 101 | SmoothAdd, // (T1 + T2) - (T1 * T2) 102 | SignedAdd // T1 + (T2 - 0.5) 103 | }; 104 | 105 | enum class TextureWrappingMode { Repeat, MirroredRepeat, ClampToEdge, Decal }; 106 | 107 | enum class CompareOp { 108 | Never = 0, 109 | Less = 1, 110 | Equal = 2, 111 | LessOrEqual = 3, 112 | Greater = 4, 113 | NotEqual = 5, 114 | GreaterOrEqual = 6, 115 | Always = 7 116 | }; 117 | 118 | struct SamplerDesc { 119 | FilterType filter_type{FilterType::Linear}; 120 | FilterType mipmap_mode{FilterType::Linear}; 121 | 122 | float min_lod{0.0f}; 123 | float max_lod{std::numeric_limits::max()}; 124 | float lod_bias{0.0f}; 125 | 126 | TextureWrappingMode addressing_mode{TextureWrappingMode::Repeat}; 127 | float anisotropy{16.0f}; 128 | CompareOp compare_op{CompareOp::Never}; 129 | }; 130 | 131 | struct TextureDesc { 132 | uint32_t width; 133 | uint32_t height; 134 | Format format{Format::UNormRGBA8}; 135 | 136 | bool mipmapping{true}; 137 | uint32_t array_layers{1}; 138 | uint32_t samples{1}; 139 | 140 | SamplerDesc sampler{}; 141 | }; 142 | 143 | struct CubemapContents { 144 | void* right{nullptr}; 145 | void* left{nullptr}; 146 | void* up{nullptr}; 147 | void* down{nullptr}; 148 | void* front{nullptr}; 149 | void* back{nullptr}; 150 | 151 | operator bool() const { 152 | return right && left && up && down && front && back; 153 | } 154 | }; 155 | 156 | enum class ExtentType { Absolute, RelativeToSwapchain }; 157 | 158 | struct Extent { 159 | float width{1.0f}; 160 | float height{1.0f}; 161 | ExtentType type{ExtentType::RelativeToSwapchain}; 162 | 163 | inline uint32_t rounded_width() { 164 | return static_cast(round(width)); 165 | } 166 | inline uint32_t rounded_height() { 167 | return static_cast(round(height)); 168 | } 169 | 170 | bool operator==(const Extent& other) const { 171 | return (memcmp(this, &other, sizeof(*this)) == 0); 172 | } 173 | 174 | bool operator!=(const Extent& other) const { return !(*this == other); } 175 | }; 176 | 177 | struct ColorRenderTargetDesc { 178 | Extent extent{}; 179 | uint32_t samples{1}; 180 | uint32_t mip_levels{1}; 181 | uint32_t array_layers{1}; 182 | 183 | Format format{Format::SwapchainFormat}; 184 | SamplerDesc sampler{}; 185 | }; 186 | 187 | struct DepthRenderTargetDesc { 188 | Extent extent{}; 189 | uint32_t samples{1}; 190 | 191 | Format format{Format::DepthStencil}; 192 | SamplerDesc sampler{}; 193 | }; 194 | 195 | struct ColorAttachmentDesc { 196 | std::string rt_name; 197 | bool clear{true}; 198 | bool store{true}; 199 | std::array clear_color{0.0f, 0.0f, 0.0f, 1.0f}; 200 | std::string resolve_to_rt{""}; // resolve to rt at the end of the pass 201 | }; 202 | 203 | struct DepthAttachmentDesc { 204 | std::string rt_name; 205 | bool clear{true}; 206 | bool store{true}; 207 | float clear_depth{1.0f}; 208 | uint32_t clear_stencil{0}; 209 | }; 210 | 211 | struct RenderPassDesc { 212 | std::vector color_attachments; 213 | DepthAttachmentDesc depth_attachment{""}; // empty rt_name for no depth 214 | }; 215 | 216 | struct BlitTargetDesc { 217 | std::string rt; 218 | Extent extent; 219 | Extent offset{0.0f, 0.0f, ExtentType::Absolute}; 220 | 221 | uint32_t mip_level{0}; 222 | uint32_t base_array_layer{0}; 223 | uint32_t layer_count{1}; 224 | }; 225 | 226 | struct BlitDesc { 227 | BlitTargetDesc src; 228 | BlitTargetDesc dst; 229 | }; 230 | 231 | using RenderTargetName = std::string; 232 | using PassName = std::string; 233 | 234 | struct RenderPassEntry { 235 | PassName name; 236 | RenderPassDesc desc; 237 | std::vector blits{}; 238 | }; 239 | 240 | struct GeneralPassEntry { 241 | PassName name; 242 | std::vector blits{}; 243 | }; 244 | 245 | using PassEntry = variant; 246 | 247 | struct RenderPlan { 248 | std::map color_images{}; 249 | std::map depth_images{}; 250 | 251 | std::vector passes{}; 252 | }; 253 | 254 | struct VertexInputBindingDesc { 255 | uint32_t binding; 256 | uint32_t stride; 257 | bool per_instance{false}; 258 | }; 259 | 260 | struct VertexInputAttributeDesc { 261 | uint32_t location; 262 | uint32_t binding; 263 | Format format; 264 | uint32_t offset; 265 | }; 266 | 267 | struct VertexInputFormatDesc { 268 | std::vector bindings; 269 | std::vector attributes; 270 | }; 271 | 272 | enum class StencilFace { Front = 1, Back = 2, FrontAndBack = 3 }; 273 | 274 | enum class StencilOp { 275 | Keep = 0, 276 | Zero = 1, 277 | Replace = 2, 278 | IncrementAndClamp = 3, 279 | DecrementAndClamp = 4, 280 | Invert = 5, 281 | IncrementAndWrap = 6, 282 | DecrementAndWrap = 7 283 | }; 284 | 285 | struct StencilOpState { 286 | StencilOp fail_op{StencilOp::Keep}; 287 | StencilOp pass_op{StencilOp::Keep}; 288 | StencilOp depth_fail_op{StencilOp::Keep}; 289 | CompareOp compare_op{CompareOp::Never}; 290 | }; 291 | 292 | struct DepthStencilState { 293 | bool depth_test{true}; 294 | bool depth_write{true}; 295 | CompareOp depth_compare{CompareOp::Less}; 296 | bool depth_bounds{false}; 297 | 298 | bool stencil_test{false}; 299 | StencilOpState front{}; 300 | StencilOpState back{}; 301 | }; 302 | 303 | enum class LogicOp { 304 | Clear = 0, 305 | And = 1, 306 | AndReverse = 2, 307 | Copy = 3, 308 | AndInverted = 4, 309 | NoOp = 5, 310 | Xor = 6, 311 | Or = 7, 312 | Nor = 8, 313 | Equivalent = 9, 314 | Invert = 10, 315 | OrReverse = 11, 316 | CopyInverted = 12, 317 | OrInverted = 13, 318 | Nand = 14, 319 | Set = 15 320 | }; 321 | 322 | enum class BlendFactor { 323 | Zero = 0, 324 | One = 1, 325 | SrcColor = 2, 326 | OneMinusSrcColor = 3, 327 | DstColor = 4, 328 | OneMinusDstColor = 5, 329 | SrcAlpha = 6, 330 | OneMinusSrcAlpha = 7, 331 | DstAlpha = 8, 332 | OneMinusDstAlpha = 9 333 | }; 334 | 335 | enum class BlendOp { 336 | Add = 0, 337 | Subtract = 1, 338 | ReverseSubtract = 2, 339 | Min = 3, 340 | Max = 4 341 | }; 342 | 343 | enum ColorMaskBits { 344 | ColorMaskR = 1, 345 | ColorMaskG = 2, 346 | ColorMaskB = 4, 347 | ColorMaskA = 8 348 | }; 349 | 350 | struct ColorBlendAttachment { 351 | bool color_blend{true}; 352 | BlendFactor src_color_blend_factor{BlendFactor::OneMinusDstAlpha}; 353 | BlendFactor dst_color_blend_factor{BlendFactor::DstAlpha}; 354 | BlendOp color_blend_op{BlendOp::Add}; 355 | 356 | BlendFactor src_alpha_blend_factor{BlendFactor::One}; 357 | BlendFactor dst_alpha_blend_factor{BlendFactor::Zero}; 358 | BlendOp alpha_blend_op{BlendOp::Add}; 359 | 360 | uint8_t color_write_mask = 361 | ColorMaskR | ColorMaskG | ColorMaskB | ColorMaskA; 362 | }; 363 | 364 | struct ColorBlendState { 365 | VezColorBlendState state; 366 | bool color_blend{false}; 367 | LogicOp logic_op{LogicOp::And}; 368 | std::vector attachments{}; 369 | }; 370 | 371 | struct MultisampleState { 372 | uint32_t samples{1}; 373 | bool sample_shading{false}; 374 | float min_sample_shading{1.0f}; 375 | }; 376 | 377 | enum class PrimitiveTopology { 378 | PointList = 0, 379 | LineList = 1, 380 | LineStrip = 2, 381 | TriangleList = 3, 382 | TriangleStrip = 4, 383 | TriangleFan = 5 384 | }; 385 | 386 | struct InputAssemblyState { 387 | PrimitiveTopology topology{PrimitiveTopology::TriangleList}; 388 | bool primitive_restart{true}; 389 | }; 390 | 391 | enum class PolygonMode { Fill = 0, Line = 1, Point = 2 }; 392 | 393 | enum class CullMode { None = 0, Front = 1, Back = 2, FrontAndBack = 3 }; 394 | 395 | enum class FrontFace { CounterClockwise = 0, Clockwise = 1 }; 396 | 397 | struct RasterizationState { 398 | bool depth_clamp{false}; 399 | bool rasterizer_discard{false}; 400 | PolygonMode polygon_mode{PolygonMode::Fill}; 401 | CullMode cull_mode{CullMode::Back}; 402 | FrontFace front_face{FrontFace::CounterClockwise}; 403 | bool depth_bias{false}; 404 | }; 405 | 406 | enum class ShaderSourceType { Source, Filename }; 407 | 408 | struct ShaderDesc { 409 | std::string source; 410 | ShaderSourceType source_type{ShaderSourceType::Source}; 411 | 412 | std::string preamble{""}; 413 | std::string entry_point{"main"}; 414 | }; 415 | 416 | enum class BufferType { General, PerNode, PerMesh, PerMaterial }; 417 | 418 | struct Box { 419 | glm::vec3 min{glm::vec3(std::numeric_limits::max())}; 420 | glm::vec3 max{glm::vec3(std::numeric_limits::min())}; 421 | }; 422 | 423 | } // namespace goma 424 | -------------------------------------------------------------------------------- /include/renderer/renderer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer/backend.hpp" 4 | 5 | #include "common/include.hpp" 6 | 7 | namespace goma { 8 | 9 | class Engine; 10 | class Scene; 11 | 12 | class Renderer { 13 | public: 14 | Renderer(Engine& engine); 15 | 16 | result Render(); 17 | 18 | result CreateSkybox(); 19 | result CreateSphere(); 20 | result CreateBRDFLut(); 21 | 22 | private: 23 | Engine& engine_; 24 | std::unique_ptr backend_{}; 25 | 26 | std::map vs_preamble_map_{}; 27 | std::map fs_preamble_map_{}; 28 | std::unique_ptr vp_hold{}; 29 | uint32_t downscale_index_{0}; 30 | uint32_t upscale_index_{0}; 31 | uint32_t skybox_mip_count{0}; 32 | 33 | struct RenderSequenceElement { 34 | AttachmentIndex mesh; 35 | NodeIndex node; 36 | glm::vec3 cs_center; 37 | }; 38 | using RenderSequence = std::vector; 39 | 40 | struct LightData { 41 | glm::vec3 direction; 42 | int32_t type; 43 | 44 | glm::vec3 color; 45 | float intensity; 46 | 47 | glm::vec3 position; 48 | float range; 49 | 50 | float innerConeCos; 51 | float outerConeCos; 52 | 53 | glm::vec2 padding; 54 | }; 55 | 56 | static constexpr size_t kMaxLights{64}; 57 | struct LightBufferData { 58 | glm::vec3 ambient_color{glm::vec3(0.0f)}; 59 | int32_t num_lights{0}; 60 | std::array shadow_ids{-1}; 61 | 62 | std::array lights{}; 63 | }; 64 | 65 | // Rendering setup 66 | void CreateMeshBuffers(Scene& scene); 67 | void CreateVertexInputFormats(Scene& scene); 68 | void UploadTextures(Scene& scene); 69 | RenderSequence Cull(Scene& scene, const RenderSequence& render_seq, 70 | const glm::mat4& vp); 71 | LightBufferData GetLightBufferData(Scene& scene); 72 | 73 | // Render passes 74 | result UpdateLightBuffer(FrameIndex frame_id, Scene& scene, 75 | const LightBufferData& light_buffer_data); 76 | result ShadowPass(FrameIndex frame_id, Scene& scene, 77 | const RenderSequence& render_seq, 78 | const glm::mat4& shadow_vp); 79 | result ForwardPass(FrameIndex frame_id, Scene& scene, 80 | const RenderSequence& render_seq, 81 | const glm::vec3& camera_ws_pos, 82 | const glm::mat4& camera_vp, 83 | const glm::mat4& shadow_vp); 84 | result DownscalePass(FrameIndex frame_id, const std::string& src, 85 | const std::string& dst); 86 | result UpscalePass(FrameIndex frame_id, const std::string& src, 87 | const std::string& dst); 88 | result PostprocessingPass(FrameIndex frame_id); 89 | 90 | union VertexShaderPreambleDesc { 91 | struct { 92 | bool has_positions : 1; 93 | bool has_normals : 1; 94 | bool has_tangents : 1; 95 | bool has_bitangents : 1; 96 | bool has_colors : 1; 97 | bool has_uv0 : 1; 98 | bool has_uv1 : 1; 99 | bool has_uvw : 1; 100 | }; 101 | 102 | uint32_t int_repr; 103 | }; 104 | const char* GetVertexShaderPreamble(const VertexShaderPreambleDesc& desc); 105 | const char* GetVertexShaderPreamble(const Mesh& mesh); 106 | 107 | union FragmentShaderPreambleDesc { 108 | struct { 109 | // Mesh 110 | bool has_positions : 1; 111 | bool has_normals : 1; 112 | bool has_tangents : 1; 113 | bool has_bitangents : 1; 114 | bool has_colors : 1; 115 | bool has_uv0 : 1; 116 | bool has_uv1 : 1; 117 | bool has_uvw : 1; 118 | 119 | // Material 120 | bool has_diffuse_map : 1; 121 | bool has_specular_map : 1; 122 | bool has_ambient_map : 1; 123 | bool has_emissive_map : 1; 124 | bool has_metallic_roughness_map : 1; 125 | bool has_height_map : 1; 126 | bool has_normal_map : 1; 127 | bool has_shininess_map : 1; 128 | bool has_opacity_map : 1; 129 | bool has_displacement_map : 1; 130 | bool has_light_map : 1; 131 | bool has_reflection_map : 1; 132 | bool alpha_mask : 1; 133 | }; 134 | 135 | uint32_t int_repr; 136 | }; 137 | const char* GetFragmentShaderPreamble( 138 | const FragmentShaderPreambleDesc& desc); 139 | const char* GetFragmentShaderPreamble(const Mesh& mesh, 140 | const Material& material); 141 | 142 | result BindMeshBuffers(const Mesh& mesh); 143 | result BindMaterialTextures(const Material& material); 144 | }; 145 | 146 | } // namespace goma 147 | -------------------------------------------------------------------------------- /include/renderer/vez/vez_backend.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer/backend.hpp" 4 | #include "renderer/vez/vez_context.hpp" 5 | 6 | #include "common/include.hpp" 7 | #include "common/vez.hpp" 8 | #include "common/error_codes.hpp" 9 | 10 | namespace goma { 11 | 12 | class VezBackend : public Backend { 13 | public: 14 | VezBackend(const Config& config = {}, const RenderPlan& render_plan = {}); 15 | virtual ~VezBackend() override; 16 | virtual result SetRenderPlan(RenderPlan render_plan) override; 17 | virtual result SetBuffering(Buffering buffering) override; 18 | virtual result SetFramebufferColorSpace( 19 | FramebufferColorSpace fb_color_space) override; 20 | 21 | virtual result InitContext() override; 22 | virtual result InitSurface(Platform& platform) override; 23 | virtual result> GetGraphicsPipeline( 24 | const ShaderDesc& vert, const ShaderDesc& frag = {}) override; 25 | virtual result ClearShaderCache() override; 26 | virtual result> GetVertexInputFormat( 27 | const VertexInputFormatDesc& desc) override; 28 | 29 | virtual result> CreateTexture( 30 | const char* name, const TextureDesc& texture_desc, 31 | void* initial_contents = nullptr) override; 32 | virtual result> CreateCubemap( 33 | const char* name, const TextureDesc& texture_desc, 34 | const CubemapContents& initial_contents) override; 35 | virtual result> GetTexture( 36 | const char* name) override; 37 | virtual result> GetRenderTarget( 38 | FrameIndex frame_id, const char* name) override; 39 | virtual Extent GetAbsoluteExtent(Extent extent) override; 40 | 41 | virtual result> CreateUniformBuffer( 42 | BufferType type, const GenIndex& index, const char* name, uint64_t size, 43 | bool gpu_stored = true, void* initial_contents = nullptr) override; 44 | virtual result> GetUniformBuffer( 45 | BufferType type, const GenIndex& index, const char* name) override; 46 | virtual result> CreateUniformBuffer( 47 | const char* name, uint64_t size, bool gpu_stored = true, 48 | void* initial_contents = nullptr) override; 49 | virtual result> GetUniformBuffer( 50 | const char* name) override; 51 | 52 | virtual result> CreateVertexBuffer( 53 | const AttachmentIndex& mesh, const char* name, uint64_t size, 54 | bool gpu_stored = true, void* initial_contents = nullptr) override; 55 | virtual result> GetVertexBuffer( 56 | const AttachmentIndex& mesh, const char* name) override; 57 | virtual result> CreateIndexBuffer( 58 | const AttachmentIndex& mesh, const char* name, uint64_t size, 59 | bool gpu_stored = true, void* initial_contents = nullptr) override; 60 | virtual result> GetIndexBuffer( 61 | const AttachmentIndex& mesh, const char* name) override; 62 | virtual result UpdateBuffer(const Buffer& buffer, uint64_t offset, 63 | uint64_t size, 64 | const void* contents) override; 65 | 66 | virtual result RenderFrame(std::vector pass_fns, 67 | const char* present_image) override; 68 | 69 | virtual result BindUniformBuffer(const Buffer& buffer, 70 | uint64_t offset, uint64_t size, 71 | uint32_t binding, 72 | uint32_t array_index = 0) override; 73 | virtual result BindTexture( 74 | const Image& image, uint32_t binding = 0, 75 | const SamplerDesc* sampler_override = nullptr) override; 76 | virtual result BindTextures( 77 | const std::vector& images, uint32_t first_binding = 0, 78 | const SamplerDesc* sampler_override = nullptr) override; 79 | virtual result BindVertexBuffer(const Buffer& vertex_buffer, 80 | uint32_t binding = 0, 81 | size_t offset = 0) override; 82 | virtual result BindVertexBuffers( 83 | const std::vector& vertex_buffers, uint32_t first_binding = 0, 84 | std::vector offsets = {}) override; 85 | virtual result BindIndexBuffer(const Buffer& index_buffer, 86 | uint64_t offset = 0, 87 | bool short_indices = false) override; 88 | 89 | virtual result BindVertexInputFormat( 90 | VertexInputFormat vertex_input_format) override; 91 | virtual result BindDepthStencilState( 92 | const DepthStencilState& state) override; 93 | virtual result BindColorBlendState( 94 | const ColorBlendState& state) override; 95 | virtual result BindMultisampleState( 96 | const MultisampleState& state) override; 97 | virtual result BindInputAssemblyState( 98 | const InputAssemblyState& state) override; 99 | virtual result BindRasterizationState( 100 | const RasterizationState& state) override; 101 | virtual result BindViewportState(uint32_t viewport_count) override; 102 | 103 | virtual result SetDepthBias(float constant_factor, float clamp, 104 | float slope_factor) override; 105 | virtual result SetDepthBounds(float min, float max) override; 106 | virtual result SetStencil(StencilFace face, uint32_t reference, 107 | uint32_t write_mask, 108 | uint32_t compare_mask) override; 109 | virtual result SetBlendConstants( 110 | const std::array& blend_constants) override; 111 | virtual result SetViewport(const std::vector viewports, 112 | uint32_t first_viewport = 0) override; 113 | virtual result SetScissor(const std::vector scissors, 114 | uint32_t first_scissor = 0) override; 115 | 116 | virtual result BindGraphicsPipeline(Pipeline pipeline) override; 117 | virtual result Draw(uint32_t vertex_count, 118 | uint32_t instance_count = 1, 119 | uint32_t first_vertex = 0, 120 | uint32_t first_instance = 0) override; 121 | virtual result DrawIndexed(uint32_t index_count, 122 | uint32_t instance_count = 1, 123 | uint32_t first_index = 0, 124 | uint32_t vertex_offset = 0, 125 | uint32_t first_instance = 0) override; 126 | 127 | virtual result TeardownContext() override; 128 | 129 | struct PhysicalDevice { 130 | VkPhysicalDevice physical_device; 131 | VkPhysicalDeviceProperties properties; 132 | VkPhysicalDeviceFeatures features; 133 | }; 134 | 135 | result CreateInstance(); 136 | result CreateDebugCallback(VkInstance instance); 137 | result CreatePhysicalDevice(VkInstance instance); 138 | result CreateDevice(VkPhysicalDevice physical_device); 139 | result CreateSwapchain(VkSurfaceKHR surface); 140 | result GetVertexShaderModule(const ShaderDesc& vert); 141 | result GetFragmentShaderModule(const ShaderDesc& frag); 142 | result> CreateBuffer( 143 | VezContext::BufferHash hash, VkDeviceSize size, 144 | VezMemoryFlagsBits storage, VkBufferUsageFlags usage, 145 | void* initial_contents = nullptr); 146 | result> GetBuffer(VezContext::BufferHash hash); 147 | result GetSampler(const SamplerDesc& sampler_desc); 148 | 149 | private: 150 | VezContext context_{}; 151 | bool rp_in_progress_{false}; 152 | 153 | result CreateImage(VezContext::ImageHash hash, 154 | VezImageCreateInfo image_info); 155 | result FillImage(VkImage image, VkExtent2D extent, 156 | std::vector initial_contents); 157 | result GenerateMipmaps(VkImage image, uint32_t layer_count, 158 | VkExtent2D extent); 159 | result GetSetupCommandBuffer(); 160 | result GetActiveCommandBuffer(uint32_t thread = 0); 161 | VkFormat GetVkFormat(Format format); 162 | 163 | result CreateFramebuffer(FrameIndex frame_id, const char* name, 164 | RenderPassDesc fb_desc); 165 | result GetFramebuffer(FrameIndex frame_id, const char* name); 166 | result> CreateRenderTarget( 167 | FrameIndex frame_id, const char* name, 168 | const ColorRenderTargetDesc& desc); 169 | result> CreateRenderTarget( 170 | FrameIndex frame_id, const char* name, 171 | const DepthRenderTargetDesc& desc); 172 | 173 | result StartFrame(uint32_t threads = 1); 174 | result StartRenderPass(Framebuffer fb, RenderPassDesc rp_desc); 175 | result FinishFrame(); 176 | result PresentImage(const char* present_image_name); 177 | 178 | VezContext::BufferHash GetBufferHash(const char* name); 179 | VezContext::BufferHash GetBufferHash(BufferType type, const GenIndex& index, 180 | const char* name); 181 | VezContext::ShaderHash GetShaderHash(const char* source, 182 | const char* preamble, 183 | const char* entry_point); 184 | VezContext::PipelineHash GetGraphicsPipelineHash(VkShaderModule vs, 185 | VkShaderModule fs); 186 | VezContext::VertexInputFormatHash GetVertexInputFormatHash( 187 | const VertexInputFormatDesc& desc); 188 | VezContext::ImageHash GetRenderTargetHash(FrameIndex frame_id, 189 | const char* name); 190 | VezContext::ImageHash GetTextureHash(const char* name); 191 | VezContext::FramebufferHash GetFramebufferHash(FrameIndex frame_id, 192 | const char* name); 193 | VezContext::SamplerHash GetSamplerHash(const SamplerDesc& sampler_desc); 194 | }; 195 | 196 | } // namespace goma 197 | -------------------------------------------------------------------------------- /include/renderer/vez/vez_context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer/handles.hpp" 4 | 5 | #include "common/include.hpp" 6 | #include "common/vez.hpp" 7 | 8 | namespace goma { 9 | 10 | struct VezContext { 11 | using ShaderHash = std::vector; 12 | using ShaderCache = std::map; 13 | 14 | using PipelineHash = std::vector; 15 | using PipelineCache = std::map>; 16 | 17 | using VertexInputFormatHash = std::vector; 18 | using VertexInputFormatCache = 19 | std::map>; 20 | 21 | using BufferHash = std::vector; 22 | using BufferCache = std::map>; 23 | 24 | using ImageHash = std::vector; 25 | using ImageCache = std::map>; 26 | 27 | using SamplerHash = std::vector; 28 | using SamplerCache = std::map; 29 | 30 | using FramebufferHash = std::vector; 31 | using FramebufferCache = std::map; 32 | 33 | VkInstance instance{VK_NULL_HANDLE}; 34 | VkDebugReportCallbackEXT debug_callback{VK_NULL_HANDLE}; 35 | 36 | VkPhysicalDevice physical_device{VK_NULL_HANDLE}; 37 | VkPhysicalDeviceProperties properties{}; 38 | VkPhysicalDeviceFeatures features{}; 39 | 40 | VkDevice device{VK_NULL_HANDLE}; 41 | 42 | VkSurfaceKHR surface{VK_NULL_HANDLE}; 43 | VkSurfaceCapabilitiesKHR capabilities{}; 44 | 45 | VezSwapchain swapchain{VK_NULL_HANDLE}; 46 | VkSurfaceFormatKHR swapchain_format{}; 47 | 48 | ShaderCache vertex_shader_cache{}; 49 | ShaderCache fragment_shader_cache{}; 50 | PipelineCache pipeline_cache{}; 51 | VertexInputFormatCache vertex_input_format_cache{}; 52 | BufferCache buffer_cache{}; 53 | ImageCache fb_image_cache{}; 54 | ImageCache texture_cache{}; 55 | SamplerCache sampler_cache{}; 56 | FramebufferCache framebuffer_cache{}; 57 | 58 | struct PerFrame { 59 | std::vector command_buffers{}; 60 | std::vector command_buffer_active{}; 61 | 62 | VkCommandBuffer setup_command_buffer{VK_NULL_HANDLE}; 63 | bool setup_command_buffer_active{false}; 64 | 65 | VkSemaphore submission_semaphore{VK_NULL_HANDLE}; 66 | VkSemaphore setup_semaphore{VK_NULL_HANDLE}; 67 | VkSemaphore presentation_semaphore{VK_NULL_HANDLE}; 68 | VkFence submission_fence{VK_NULL_HANDLE}; 69 | VkFence setup_fence{VK_NULL_HANDLE}; 70 | 71 | std::vector orphaned_pipelines{}; 72 | }; 73 | std::vector per_frame{}; 74 | size_t current_frame{0}; 75 | }; 76 | 77 | } // namespace goma 78 | -------------------------------------------------------------------------------- /include/scene/attachment.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "scene/gen_index.hpp" 4 | 5 | #include "common/include.hpp" 6 | 7 | namespace goma { 8 | 9 | template 10 | struct Attachment { 11 | AttachmentIndex id; 12 | std::set nodes; 13 | T data{}; 14 | 15 | Attachment(AttachmentIndex id_, const std::set& nodes_, 16 | T&& data_ = T()) 17 | : id(id_), nodes(nodes_), data(std::forward(data_)) {} 18 | 19 | bool operator==(const Attachment& other) const { 20 | return (this->id == other.id); 21 | } 22 | 23 | bool operator!=(const Attachment& other) const { 24 | return (this->id != other.id); 25 | } 26 | 27 | bool valid() const { return id.valid(); } 28 | 29 | friend std::ostream& operator<<(std::ostream& o, const Attachment& n); 30 | }; 31 | 32 | class AttachmentManagerBase { 33 | public: 34 | virtual ~AttachmentManagerBase() = default; 35 | }; 36 | 37 | template 38 | class AttachmentManager : public AttachmentManagerBase { 39 | public: 40 | AttachmentManager() : valid_count_(0) {} 41 | virtual ~AttachmentManager() = default; 42 | 43 | result> Create(const std::set& nodes, 44 | T&& data = T()) { 45 | AttachmentIndex ret_id; 46 | if (!recycled_attachments_.empty()) { 47 | size_t index = recycled_attachments_.front(); 48 | recycled_attachments_.pop(); 49 | 50 | // The last valid generation was stored in id.id 51 | // when the attachment was deleted 52 | size_t new_gen = attachments_[index].id.id + 1; 53 | 54 | attachments_[index] = { 55 | {index, new_gen}, nodes, std::forward(data)}; 56 | ret_id = {index, new_gen}; 57 | } else { 58 | size_t id = attachments_.size(); 59 | attachments_.emplace_back(AttachmentIndex{id}, nodes, 60 | std::forward(data)); 61 | ret_id = {id}; 62 | } 63 | valid_count_++; 64 | return ret_id; 65 | } 66 | 67 | result> Create(T&& data = T()) { 68 | return Create({}, std::forward(data)); 69 | } 70 | 71 | result Register(AttachmentIndex attachment, 72 | const std::string& name, bool overwrite = true) { 73 | if (!overwrite) { 74 | auto result = attachment_map_.find(name); 75 | if (result != attachment_map_.end()) { 76 | return Error::KeyAlreadyExists; 77 | } 78 | } 79 | 80 | attachment_map_[name] = attachment; 81 | return outcome::success(); 82 | } 83 | 84 | void ForEach(std::function&, 85 | const std::set&, T&)> 86 | fun) { 87 | for (auto& a : attachments_) { 88 | if (!a.valid()) { 89 | continue; 90 | } 91 | fun(a.id, a.nodes, a.data); 92 | } 93 | } 94 | 95 | void ForEach(std::function fun) { 96 | for (auto& a : attachments_) { 97 | if (!a.valid()) { 98 | continue; 99 | } 100 | fun(a.data); 101 | } 102 | } 103 | 104 | const std::vector>& GetAll() { return attachments_; } 105 | 106 | result> Get(AttachmentIndex id) { 107 | if (!Validate(id)) { 108 | return Error::InvalidAttachment; 109 | } 110 | return attachments_[id.id].data; 111 | } 112 | 113 | result, std::reference_wrapper>> Find( 114 | const std::string& name) { 115 | auto result = attachment_map_.find(name); 116 | if (result == attachment_map_.end()) { 117 | return Error::NotFound; 118 | } 119 | 120 | OUTCOME_TRY(data, Get(result->second)); 121 | return std::make_pair(result->second, data); 122 | } 123 | 124 | result Attach(AttachmentIndex id, NodeIndex node) { 125 | if (!Validate(id)) { 126 | return Error::InvalidAttachment; 127 | } 128 | attachments_[id.id].nodes.insert(node); 129 | return outcome::success(); 130 | } 131 | 132 | result Detach(AttachmentIndex id, NodeIndex node) { 133 | if (!Validate(id)) { 134 | return Error::InvalidAttachment; 135 | } 136 | attachments_[id.id].nodes.erase(node); 137 | return outcome::success(); 138 | } 139 | 140 | result DetachAll(AttachmentIndex id) { 141 | if (!Validate(id)) { 142 | return Error::InvalidAttachment; 143 | } 144 | attachments_[id.id].nodes.clear(); 145 | return outcome::success(); 146 | } 147 | 148 | result>> GetNodes( 149 | AttachmentIndex id) { 150 | if (!Validate(id)) { 151 | return Error::InvalidAttachment; 152 | } 153 | return attachments_[id.id].nodes; 154 | } 155 | 156 | size_t count() { return valid_count_; } 157 | 158 | private: 159 | std::vector> attachments_; 160 | std::map> attachment_map_; 161 | std::queue recycled_attachments_; 162 | 163 | size_t valid_count_; 164 | 165 | bool Validate(AttachmentIndex id) { 166 | return id.gen != 0 && id.id < attachments_.size() && 167 | attachments_[id.id].id.gen == id.gen; 168 | } 169 | }; 170 | 171 | } // namespace goma 172 | -------------------------------------------------------------------------------- /include/scene/attachments/camera.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/include.hpp" 4 | 5 | namespace goma { 6 | 7 | struct Camera { 8 | std::string name; 9 | 10 | float h_fov{60.0f}; 11 | float near_plane{0.1f}; 12 | float far_plane{1000.0f}; 13 | float aspect_ratio{1.78f}; 14 | 15 | glm::vec3 position{glm::vec3(0.0f)}; 16 | glm::vec3 up{0.0f, 1.0f, 0.0f}; 17 | glm::vec3 look_at{0.0f, 0.0f, 1.0f}; 18 | }; 19 | 20 | } // namespace goma 21 | -------------------------------------------------------------------------------- /include/scene/attachments/light.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/include.hpp" 4 | 5 | namespace goma { 6 | 7 | enum LightType { 8 | Directional = 0, 9 | Point = 1, 10 | Spot = 2, 11 | Ambient = 3, 12 | Area = 4, 13 | }; 14 | 15 | struct Light { 16 | std::string name; 17 | 18 | LightType type{LightType::Directional}; 19 | glm::vec3 position{glm::vec3(0.0f)}; 20 | glm::vec3 direction{0.0f, 0.0f, 1.0f}; 21 | glm::vec3 up{0.0f, 1.0f, 0.0f}; 22 | 23 | float intensity{1.0f}; 24 | glm::vec3 diffuse_color{glm::vec3(1.0f)}; 25 | glm::vec3 specular_color{glm::vec3(1.0f)}; 26 | glm::vec3 ambient_color{glm::vec3(1.0f)}; 27 | 28 | std::array attenuation{1.0f, 1.0f, 1.0f}; 29 | float inner_cone_angle{360.0f}; 30 | float outer_cone_angle{360.0f}; 31 | glm::vec2 area_size{glm::vec2(0.0f)}; 32 | }; 33 | 34 | } // namespace goma 35 | -------------------------------------------------------------------------------- /include/scene/attachments/material.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "scene/gen_index.hpp" 4 | #include "renderer/handles.hpp" 5 | 6 | #include "common/include.hpp" 7 | 8 | namespace goma { 9 | 10 | struct Texture; 11 | 12 | struct TextureBinding { 13 | AttachmentIndex index; 14 | uint32_t uv_index{0}; 15 | float blend{1.0f}; 16 | std::array wrapping{TextureWrappingMode::Repeat, 17 | TextureWrappingMode::Repeat, 18 | TextureWrappingMode::Repeat}; 19 | }; 20 | 21 | using TextureBindingMap = 22 | std::unordered_map>; 23 | 24 | struct Material { 25 | std::string name; 26 | TextureBindingMap texture_bindings; 27 | 28 | glm::vec3 diffuse_color{glm::vec3(0.0f)}; 29 | glm::vec3 specular_color{glm::vec3(0.0f)}; 30 | glm::vec3 ambient_color{glm::vec3(0.0f)}; 31 | glm::vec3 emissive_color{glm::vec3(0.0f)}; 32 | glm::vec3 transparent_color{glm::vec3(0.0f)}; 33 | 34 | bool two_sided{false}; 35 | float opacity{1.0f}; 36 | float alpha_cutoff{1.0f}; 37 | float shininess_exponent{0.0f}; 38 | float specular_strength{1.0f}; 39 | float metallic_factor{0.2f}; 40 | float roughness_factor{0.5f}; 41 | }; 42 | 43 | } // namespace goma 44 | -------------------------------------------------------------------------------- /include/scene/attachments/mesh.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "scene/gen_index.hpp" 4 | #include "renderer/handles.hpp" 5 | 6 | #include "common/include.hpp" 7 | 8 | namespace goma { 9 | 10 | struct Material; 11 | 12 | struct MeshBuffers { 13 | std::shared_ptr vertex; 14 | std::shared_ptr normal; 15 | std::shared_ptr tangent; 16 | std::shared_ptr bitangent; 17 | std::shared_ptr color; 18 | 19 | std::shared_ptr index; 20 | 21 | std::shared_ptr uv0; 22 | std::shared_ptr uv1; 23 | std::shared_ptr uvw; 24 | }; 25 | 26 | struct Mesh { 27 | std::string name; 28 | 29 | std::vector vertices; 30 | std::vector normals; 31 | std::vector tangents; 32 | std::vector bitangents; 33 | 34 | std::vector indices; 35 | 36 | std::vector colors; 37 | std::vector> uv_sets; 38 | std::vector> uvw_sets; 39 | 40 | AttachmentIndex material{}; 41 | 42 | std::shared_ptr vertex_input_format; 43 | std::unique_ptr bounding_box{}; 44 | MeshBuffers buffers{}; 45 | }; 46 | 47 | } // namespace goma 48 | -------------------------------------------------------------------------------- /include/scene/attachments/texture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer/handles.hpp" 4 | 5 | #include "common/include.hpp" 6 | 7 | namespace goma { 8 | 9 | struct Texture { 10 | std::string path; 11 | 12 | uint32_t width; 13 | uint32_t height; 14 | std::vector data; 15 | bool compressed{false}; 16 | 17 | std::shared_ptr image{}; 18 | }; 19 | 20 | } // namespace goma 21 | -------------------------------------------------------------------------------- /include/scene/gen_index.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/include.hpp" 4 | 5 | namespace goma { 6 | 7 | struct GenIndex { 8 | size_t id; 9 | size_t gen; // 0 reserved for invalid elements 10 | 11 | GenIndex() : id(0), gen(0) {} 12 | GenIndex(size_t id_, size_t gen_ = 1) : id(id_), gen(gen_) {} 13 | 14 | bool valid() const { return gen > 0; } 15 | 16 | bool operator==(const GenIndex& other) const { 17 | return (memcmp(this, &other, sizeof(*this)) == 0); 18 | } 19 | 20 | bool operator!=(const GenIndex& other) const { 21 | return (memcmp(this, &other, sizeof(*this)) != 0); 22 | } 23 | 24 | bool operator<(const GenIndex& other) const { 25 | return (memcmp(this, &other, sizeof(*this)) < 0); 26 | } 27 | 28 | friend std::ostream& operator<<(std::ostream& o, const goma::GenIndex& id); 29 | }; 30 | 31 | using NodeIndex = GenIndex; 32 | 33 | template 34 | using AttachmentIndex = GenIndex; 35 | 36 | } // namespace goma 37 | -------------------------------------------------------------------------------- /include/scene/loaders/assimp_loader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "scene/scene_loader.hpp" 4 | #include "scene/attachments/material.hpp" 5 | 6 | #include "assimp/scene.h" 7 | 8 | #include "common/include.hpp" 9 | 10 | namespace goma { 11 | 12 | class AssimpLoader : public SceneLoader { 13 | public: 14 | virtual result> ReadSceneFromFile( 15 | const char* file_path) override; 16 | 17 | private: 18 | result> ConvertScene(const aiScene* ai_scene, 19 | const std::string& base_path); 20 | 21 | result LoadMaterialTexture( 22 | Scene* scene, const aiMaterial* material, const std::string& base_path, 23 | const std::pair& texture_type, 24 | uint32_t texture_index); 25 | }; 26 | 27 | } // namespace goma 28 | -------------------------------------------------------------------------------- /include/scene/node.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "scene/gen_index.hpp" 4 | 5 | #include "common/include.hpp" 6 | 7 | namespace goma { 8 | 9 | struct Transform { 10 | glm::vec3 position; 11 | glm::quat rotation; 12 | glm::vec3 scale; 13 | 14 | Transform(const glm::vec3& pos = glm::vec3(0.0), 15 | const glm::quat& rot = glm::quat(1.0, 0.0, 0.0, 0.0), 16 | const glm::vec3& scale_ = glm::vec3(1.0)) 17 | : position(pos), rotation(rot), scale(scale_) {} 18 | 19 | bool operator==(const Transform& other) const { 20 | return (memcmp(this, &other, sizeof(*this)) == 0); 21 | } 22 | 23 | bool operator!=(const Transform& other) const { 24 | return (memcmp(this, &other, sizeof(*this)) != 0); 25 | } 26 | 27 | friend std::ostream& operator<<(std::ostream& o, const goma::Transform& t); 28 | }; 29 | 30 | struct Node { 31 | NodeIndex id; 32 | NodeIndex parent; // root node has parent {0, 0} 33 | Transform transform{}; 34 | 35 | std::set children{}; 36 | std::unique_ptr cached_model{}; 37 | 38 | Node(NodeIndex id_, NodeIndex parent_, 39 | const Transform& transform_ = Transform()) 40 | : id(id_), parent(parent_), transform(transform_) {} 41 | 42 | bool operator==(const Node& other) const { return (this->id == other.id); } 43 | 44 | bool operator!=(const Node& other) const { return (this->id != other.id); } 45 | 46 | bool valid() const { return id.valid(); } 47 | 48 | friend std::ostream& operator<<(std::ostream& o, const Node& n); 49 | }; 50 | 51 | } // namespace goma 52 | -------------------------------------------------------------------------------- /include/scene/scene.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "scene/node.hpp" 4 | #include "scene/attachment.hpp" 5 | #include "scene/attachments/texture.hpp" 6 | 7 | #include "common/include.hpp" 8 | 9 | template 10 | using TypeMap = std::unordered_map; 11 | 12 | namespace goma { 13 | 14 | class Scene { 15 | public: 16 | Scene(); 17 | 18 | result CreateNode(const NodeIndex parent, 19 | const Transform& transform = Transform()); 20 | NodeIndex GetRootNode() { return nodes_[0].id; } 21 | result DeleteNode(NodeIndex id); 22 | 23 | result GetParent(NodeIndex id); 24 | result> GetChildren(NodeIndex id); 25 | result GetTransform(NodeIndex id); 26 | result GetTransformMatrix(NodeIndex id); 27 | result SetTransform(NodeIndex id, const Transform& transform); 28 | 29 | template 30 | result> CreateAttachment(const NodeIndex& node_id, 31 | T&& data = T()) { 32 | return GetAttachmentManager()->Create({node_id}, 33 | std::forward(data)); 34 | } 35 | 36 | template 37 | result> CreateAttachment(T&& data = T()) { 38 | return GetAttachmentManager()->Create(std::forward(data)); 39 | } 40 | 41 | template 42 | result RegisterAttachment(AttachmentIndex attachment, 43 | const std::string& name, 44 | bool overwrite = true) { 45 | return GetAttachmentManager()->Register(attachment, name, overwrite); 46 | } 47 | 48 | template 49 | void ForEach(std::function, 50 | const std::set&, T&)> 51 | fun) { 52 | return GetAttachmentManager()->ForEach(std::move(fun)); 53 | } 54 | 55 | template 56 | void ForEach(std::function fun) { 57 | return GetAttachmentManager()->ForEach(std::move(fun)); 58 | } 59 | 60 | template 61 | const std::vector>& GetAttachments() { 62 | return GetAttachmentManager()->GetAll(); 63 | } 64 | 65 | template 66 | result> GetAttachment(AttachmentIndex id) { 67 | return GetAttachmentManager()->Get(id); 68 | } 69 | 70 | template 71 | result, std::reference_wrapper>> 72 | FindAttachment(const std::string& name) { 73 | return GetAttachmentManager()->Find(name); 74 | } 75 | 76 | template 77 | size_t GetAttachmentCount() { 78 | return GetAttachmentManager()->count(); 79 | } 80 | 81 | template 82 | result Attach(AttachmentIndex id, NodeIndex node) { 83 | return GetAttachmentManager()->Attach(id, node); 84 | } 85 | 86 | template 87 | result Detach(AttachmentIndex id, NodeIndex node) { 88 | return GetAttachmentManager()->Detach(id, node); 89 | } 90 | 91 | template 92 | result DetachAll(AttachmentIndex id) { 93 | return GetAttachmentManager()->DetachAll(id); 94 | } 95 | 96 | template 97 | result>> GetAttachedNodes( 98 | AttachmentIndex id) { 99 | return GetAttachmentManager()->GetNodes(id); 100 | } 101 | 102 | private: 103 | using AttachmentManagerMap = 104 | TypeMap>; 105 | 106 | std::vector nodes_{}; 107 | std::queue recycled_nodes_{}; 108 | 109 | AttachmentManagerMap attachment_managers_{}; 110 | 111 | template 112 | AttachmentManager* GetAttachmentManager() { 113 | auto type_id = std::type_index(typeid(T)); 114 | auto result = attachment_managers_.find(type_id); 115 | if (result != attachment_managers_.end()) { 116 | return static_cast*>(result->second.get()); 117 | } 118 | 119 | attachment_managers_[type_id] = 120 | std::make_unique>(); 121 | return static_cast*>( 122 | attachment_managers_[type_id].get()); 123 | } 124 | 125 | bool ValidateNode(NodeIndex id); 126 | result ComputeTransformMatrix(NodeIndex id); 127 | }; 128 | 129 | } // namespace goma 130 | -------------------------------------------------------------------------------- /include/scene/scene_loader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "scene/scene.hpp" 4 | 5 | #include "common/include.hpp" 6 | 7 | namespace goma { 8 | 9 | class SceneLoader { 10 | public: 11 | virtual result> ReadSceneFromFile( 12 | const char* file_path) = 0; 13 | }; 14 | 15 | } // namespace goma 16 | -------------------------------------------------------------------------------- /include/scripting/script.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/include.hpp" 4 | 5 | namespace goma { 6 | 7 | class Engine; 8 | 9 | class Script { 10 | public: 11 | virtual void Update(Engine& engine, float delta_time) = 0; 12 | }; 13 | 14 | } // namespace goma 15 | -------------------------------------------------------------------------------- /include/scripting/scripting_system.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "scripting/script.hpp" 4 | 5 | #include "common/include.hpp" 6 | 7 | namespace goma { 8 | 9 | class Engine; 10 | 11 | class ScriptingSystem { 12 | public: 13 | ScriptingSystem(Engine& engine) : engine_(engine) {} 14 | 15 | template 16 | void RegisterScript(T&& script) { 17 | scripts_.push_back(std::make_unique(std::forward(script))); 18 | } 19 | 20 | void Update(float delta_time) { 21 | for (auto& script : scripts_) { 22 | script->Update(engine_, delta_time); 23 | } 24 | }; 25 | 26 | private: 27 | Engine& engine_; 28 | 29 | std::vector> scripts_{}; 30 | }; 31 | 32 | } // namespace goma 33 | -------------------------------------------------------------------------------- /include/scripting/scripts/fly_camera.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "scripting/script.hpp" 4 | #include "scene/attachments/camera.hpp" 5 | 6 | #include "common/include.hpp" 7 | 8 | namespace goma { 9 | 10 | class FlyCamera : public Script { 11 | public: 12 | FlyCamera(const AttachmentIndex camera_id, float speed = 1.0f) 13 | : camera_id_(camera_id), speed_(speed) {} 14 | 15 | virtual void Update(Engine& engine, float delta_time) override { 16 | auto scene = engine.scene(); 17 | 18 | auto camera_res = scene->GetAttachment(camera_id_); 19 | if (!camera_res) { 20 | spdlog::error("FlyCamera: attached camera not found."); 21 | return; 22 | } 23 | 24 | auto& camera = camera_res.value().get(); 25 | auto camera_nodes = scene->GetAttachedNodes(camera_id_); 26 | glm::mat4 camera_transform = glm::mat4(1.0f); 27 | 28 | if (camera_nodes && camera_nodes.value().get().size() > 0) { 29 | auto camera_node = *camera_nodes.value().get().begin(); 30 | 31 | // Update transform based on input 32 | auto transform = scene->GetTransform(camera_node).value(); 33 | auto input_state = engine.input_system().GetFrameInput(); 34 | const auto& keypresses = input_state.keypresses; 35 | 36 | auto has_key = [&keypresses](KeyInput key) { 37 | return keypresses.find(key) != keypresses.end(); 38 | }; 39 | 40 | // https://stackoverflow.com/questions/9857398/quaternion-camera-how-do-i-make-it-rotate-correctly 41 | if (has_key(KeyInput::Up)) { 42 | auto right = glm::cross(camera.look_at, camera.up); 43 | auto new_rotation = 44 | transform.rotation * glm::quat(right * delta_time); 45 | 46 | // Limit camera pitch to +-85 degrees 47 | auto look_at = new_rotation * camera.look_at; 48 | if (glm::dot(look_at, camera.up) < 49 | glm::sin(glm::radians(85.0f))) { 50 | transform.rotation = new_rotation; 51 | } 52 | } 53 | if (has_key(KeyInput::Down)) { 54 | auto right = glm::cross(camera.look_at, camera.up); 55 | auto new_rotation = 56 | transform.rotation * glm::quat(-right * delta_time); 57 | 58 | // Limit camera pitch to +-85 degrees 59 | auto look_at = new_rotation * camera.look_at; 60 | if (glm::dot(look_at, camera.up) > 61 | glm::sin(glm::radians(-85.0f))) { 62 | transform.rotation = new_rotation; 63 | } 64 | } 65 | if (has_key(KeyInput::Left)) { 66 | transform.rotation = 67 | glm::quat(camera.up * delta_time) * transform.rotation; 68 | } 69 | if (has_key(KeyInput::Right)) { 70 | transform.rotation = 71 | glm::quat(-camera.up * delta_time) * transform.rotation; 72 | } 73 | 74 | if (has_key(KeyInput::W)) { 75 | transform.position += 76 | transform.rotation * camera.look_at * speed_ * delta_time; 77 | } 78 | if (has_key(KeyInput::S)) { 79 | transform.position += 80 | transform.rotation * -camera.look_at * speed_ * delta_time; 81 | } 82 | if (has_key(KeyInput::A)) { 83 | transform.position += transform.rotation * 84 | -glm::cross(camera.look_at, camera.up) * 85 | speed_ * delta_time; 86 | } 87 | if (has_key(KeyInput::D)) { 88 | transform.position += transform.rotation * 89 | glm::cross(camera.look_at, camera.up) * 90 | speed_ * delta_time; 91 | } 92 | scene->SetTransform(camera_node, transform); 93 | } 94 | } 95 | 96 | private: 97 | AttachmentIndex camera_id_{}; 98 | float speed_{1.0f}; 99 | }; 100 | 101 | } // namespace goma 102 | -------------------------------------------------------------------------------- /src/engine.cpp: -------------------------------------------------------------------------------- 1 | #include "engine.hpp" 2 | 3 | #include "platform/win32_platform.hpp" 4 | #include "scene/loaders/assimp_loader.hpp" 5 | #include "scripting/scripts/fly_camera.hpp" 6 | 7 | namespace goma { 8 | 9 | Engine::Engine() 10 | : platform_(std::make_unique()), 11 | input_system_(std::make_unique(*platform_.get())), 12 | scripting_system_{std::make_unique(*this)} { 13 | platform_->InitWindow(1280, 800); 14 | renderer_ = std::make_unique(*this); 15 | } 16 | 17 | result Engine::MainLoop(MainLoopFn inner_loop) { 18 | if (platform_) { 19 | platform_->MainLoop([&]() { 20 | if (fps_cap > 0) { 21 | // Limit FPS 22 | std::chrono::duration elapsed = 23 | std::chrono::high_resolution_clock::now() - 24 | frame_timestamp_; 25 | 26 | auto min_frame_time = 1.0f / fps_cap; 27 | if (elapsed.count() < min_frame_time) { 28 | auto wait_us = 1e6 * (min_frame_time - elapsed.count()); 29 | platform_->Sleep(static_cast(wait_us)); 30 | } 31 | } 32 | 33 | auto now = std::chrono::high_resolution_clock::now(); 34 | delta_time_ = now - frame_timestamp_; 35 | frame_timestamp_ = now; 36 | 37 | input_system_->AcquireFrameInput(); 38 | scripting_system_->Update(delta_time_.count()); 39 | renderer_->Render(); 40 | 41 | bool res = false; 42 | if (inner_loop) { 43 | // Inner loop allows for conditional termination 44 | res = inner_loop(); 45 | } 46 | 47 | frame_count_++; 48 | return res; 49 | }); 50 | } 51 | 52 | return outcome::success(); 53 | }; 54 | 55 | result Engine::LoadScene(const char* file_path) { 56 | AssimpLoader loader; 57 | OUTCOME_TRY(scene, loader.ReadSceneFromFile(file_path)); 58 | scene_ = std::move(scene); 59 | 60 | OUTCOME_TRY(main_camera, CreateDefaultCamera()); 61 | main_camera_ = main_camera; 62 | 63 | if (scene_->GetAttachmentCount() == 0) { 64 | CreateDefaultLight(); 65 | } 66 | 67 | FlyCamera fly_camera(main_camera_, 5.0f); 68 | scripting_system_->RegisterScript(std::move(fly_camera)); 69 | 70 | OUTCOME_TRY(renderer_->CreateSkybox()); 71 | 72 | return outcome::success(); 73 | } 74 | 75 | result> Engine::CreateDefaultCamera() { 76 | Camera camera{}; 77 | camera.aspect_ratio = float(platform_->GetWidth()) / platform_->GetHeight(); 78 | 79 | auto new_camera_res = scene_->CreateAttachment(std::move(camera)); 80 | if (!new_camera_res) { 81 | return Error::NoMainCamera; 82 | } 83 | 84 | auto new_camera_id = new_camera_res.value(); 85 | // Create a node for the new camera 86 | auto camera_node = scene_->CreateNode(scene_->GetRootNode()).value(); 87 | scene_->Attach(new_camera_id, camera_node); 88 | 89 | return new_camera_id; 90 | } 91 | 92 | result> Engine::CreateDefaultLight() { 93 | Light light{"default_light"}; 94 | // light.direction = glm::normalize(glm::vec3{-0.25f, -1.0f, -0.25f}); 95 | // light.up = glm::normalize(glm::vec3{-1.0f, 0.25f, -0.25f}); 96 | 97 | // Light facing down 98 | light.direction = {0.0f, -1.0f, 0.0f}; 99 | light.up = {1.0f, 0.0f, 0.0f}; 100 | 101 | // Tilt it a bit 102 | auto rotation = glm::quat({glm::radians(5.0f), 0.0f, glm::radians(5.0f)}); 103 | 104 | OUTCOME_TRY(light_id, scene_->CreateAttachment(std::move(light))); 105 | 106 | // Create a node for the new light 107 | OUTCOME_TRY(light_node, scene_->CreateNode(scene_->GetRootNode(), 108 | {glm::vec3(0.0f), rotation})); 109 | scene_->Attach(light_id, light_node); 110 | 111 | return light_id; 112 | } 113 | 114 | } // namespace goma 115 | -------------------------------------------------------------------------------- /src/input/input_system.cpp: -------------------------------------------------------------------------------- 1 | #include "input/input_system.hpp" 2 | 3 | namespace goma { 4 | 5 | InputSystem::InputSystem(const Platform& platform) : platform_(platform) {} 6 | 7 | result InputSystem::AcquireFrameInput() { 8 | last_frame_input_ = frame_input_; 9 | frame_input_ = platform_.GetInputState(); 10 | 11 | return outcome::success(); 12 | }; 13 | 14 | } // namespace goma 15 | -------------------------------------------------------------------------------- /src/platform/win32_platform.cpp: -------------------------------------------------------------------------------- 1 | #include "platform/win32_platform.hpp" 2 | 3 | #include "common/error_codes.hpp" 4 | 5 | #define GLFW_EXPOSE_NATIVE_WIN32 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace goma { 12 | 13 | Win32Platform::~Win32Platform() { 14 | // glfwTerminate will destroy any remaining windows 15 | glfwTerminate(); 16 | } 17 | 18 | result Win32Platform::MainLoop(MainLoopFn inner_loop) { 19 | if (!window_) { 20 | InitWindow(1280, 800); 21 | } 22 | 23 | while (!glfwWindowShouldClose(window_)) { 24 | glfwPollEvents(); 25 | 26 | if (inner_loop) { 27 | auto should_terminate = inner_loop(); 28 | if (should_terminate) { 29 | break; 30 | } 31 | }; 32 | } 33 | 34 | return outcome::success(); 35 | } 36 | 37 | result Win32Platform::InitWindow(int width, int height) { 38 | if (!glfwInit()) { 39 | return Error::GlfwError; 40 | } 41 | 42 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 43 | glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); 44 | 45 | window_ = glfwCreateWindow(width, height, "Goma Engine", nullptr, nullptr); 46 | glfwSetInputMode(window_, GLFW_STICKY_KEYS, 1); 47 | if (!window_) { 48 | glfwTerminate(); 49 | return Error::GlfwWindowCreationFailed; 50 | } 51 | 52 | return outcome::success(); 53 | } 54 | 55 | result Win32Platform::CreateVulkanSurface( 56 | VkInstance instance) const { 57 | assert(window_ && "Window must be initialized before creating surface"); 58 | 59 | VkWin32SurfaceCreateInfoKHR surface_info = {}; 60 | surface_info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; 61 | surface_info.hinstance = GetModuleHandle(NULL); 62 | surface_info.hwnd = glfwGetWin32Window(window_); 63 | 64 | VkSurfaceKHR surface = VK_NULL_HANDLE; 65 | vkCreateWin32SurfaceKHR(instance, &surface_info, nullptr, &surface); 66 | return surface; 67 | } 68 | 69 | uint32_t Win32Platform::GetWidth() const { 70 | int width; 71 | glfwGetWindowSize(window_, &width, nullptr); 72 | return static_cast(width); 73 | }; 74 | 75 | uint32_t Win32Platform::GetHeight() const { 76 | int height; 77 | glfwGetWindowSize(window_, nullptr, &height); 78 | return static_cast(height); 79 | }; 80 | 81 | InputState Win32Platform::GetInputState() const { 82 | InputState state; 83 | 84 | static const std::unordered_map glfw_to_key{ 85 | {GLFW_KEY_UP, KeyInput::Up}, {GLFW_KEY_DOWN, KeyInput::Down}, 86 | {GLFW_KEY_LEFT, KeyInput::Left}, {GLFW_KEY_RIGHT, KeyInput::Right}, 87 | {GLFW_KEY_W, KeyInput::W}, {GLFW_KEY_A, KeyInput::A}, 88 | {GLFW_KEY_S, KeyInput::S}, {GLFW_KEY_D, KeyInput::D}, 89 | {GLFW_KEY_C, KeyInput::C}, {GLFW_KEY_H, KeyInput::H}, 90 | {GLFW_KEY_R, KeyInput::R}, 91 | }; 92 | 93 | for (const auto& entry : glfw_to_key) { 94 | if (glfwGetKey(window_, entry.first) == GLFW_PRESS) { 95 | state.keypresses.insert(entry.second); 96 | } 97 | } 98 | 99 | return state; 100 | } 101 | 102 | void Win32Platform::Sleep(uint32_t microseconds) { 103 | ::Sleep(microseconds / 1000); 104 | } 105 | 106 | } // namespace goma 107 | -------------------------------------------------------------------------------- /src/renderer/renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "renderer/renderer.hpp" 2 | 3 | #include "engine.hpp" 4 | #include "renderer/vez/vez_backend.hpp" 5 | #include "scene/attachments/camera.hpp" 6 | #include "scene/attachments/light.hpp" 7 | #include "scene/attachments/material.hpp" 8 | #include "scene/attachments/mesh.hpp" 9 | 10 | #include 11 | 12 | #ifndef GOMA_ASSETS_DIR 13 | #define GOMA_ASSETS_DIR "assets/" 14 | #endif 15 | 16 | namespace goma { 17 | 18 | Renderer::Renderer(Engine& engine) 19 | : engine_(engine), backend_(std::make_unique()) { 20 | if (auto result = backend_->InitContext()) { 21 | spdlog::info("Context initialized."); 22 | } else { 23 | throw std::runtime_error(result.error().message()); 24 | } 25 | 26 | if (auto result = backend_->InitSurface(engine_.platform())) { 27 | spdlog::info("Surface initialized."); 28 | } else { 29 | throw std::runtime_error(result.error().message()); 30 | } 31 | 32 | RenderPlan render_plan{}; 33 | 34 | render_plan.color_images = { 35 | {"color", {{}, 4}}, 36 | {"blur_full", {}}, 37 | {"blur_half", {{0.5f, 0.5f}}}, 38 | {"blur_quarter", {{0.25f, 0.25f}}}, 39 | {"resolved_image", {}}, 40 | {"postprocessing", {}}, 41 | }; 42 | 43 | SamplerDesc shadow_sampler; 44 | shadow_sampler.compare_op = CompareOp::Less; 45 | 46 | render_plan.depth_images = { 47 | {"depth", {{}, 4, Format::DepthOnly}}, 48 | { 49 | "shadow_depth", 50 | {{2048.0f, 2048.0f, ExtentType::Absolute}, 51 | 1, 52 | Format::DepthOnly, 53 | shadow_sampler}, 54 | }, 55 | }; 56 | 57 | render_plan.passes = { 58 | GeneralPassEntry{"update_light_buffer"}, 59 | RenderPassEntry{ 60 | "shadow", RenderPassDesc{{}, DepthAttachmentDesc{"shadow_depth"}}}, 61 | RenderPassEntry{ 62 | "forward", 63 | RenderPassDesc{{ColorAttachmentDesc{"color", 64 | true, 65 | true, 66 | {0.1f, 0.1f, 0.1f, 1.0f}, 67 | "resolved_image"}}, 68 | DepthAttachmentDesc{"depth"}}}, 69 | RenderPassEntry{"blur_down_half", 70 | RenderPassDesc{{ColorAttachmentDesc{"blur_half"}}}}, 71 | RenderPassEntry{"blur_down_quarter", 72 | RenderPassDesc{{ColorAttachmentDesc{"blur_quarter"}}}}, 73 | RenderPassEntry{"blur_up_half", 74 | RenderPassDesc{{ColorAttachmentDesc{"blur_half"}}}}, 75 | RenderPassEntry{"blur_up_full", 76 | RenderPassDesc{{ColorAttachmentDesc{"blur_full"}}}}, 77 | RenderPassEntry{ 78 | "postprocessing", 79 | RenderPassDesc{{ColorAttachmentDesc{"postprocessing"}}}}, 80 | }; 81 | 82 | backend_->SetRenderPlan(std::move(render_plan)); 83 | 84 | CreateBRDFLut(); 85 | } 86 | 87 | result Renderer::Render() { 88 | if (!engine_.scene()) { 89 | return Error::NoSceneLoaded; 90 | } 91 | Scene& scene = *engine_.scene(); 92 | 93 | downscale_index_ = 0; 94 | upscale_index_ = 0; 95 | 96 | // Ensure that all meshes have their own buffers 97 | CreateMeshBuffers(scene); 98 | 99 | // Ensure that all meshes have their own vertex input format 100 | CreateVertexInputFormats(scene); 101 | 102 | // Upload textures 103 | UploadTextures(scene); 104 | 105 | // Set up light buffer 106 | auto light_buffer_data = GetLightBufferData(scene); 107 | 108 | // Get the VP matrix 109 | OUTCOME_TRY(camera_ref, scene.GetAttachment(engine_.main_camera())); 110 | auto& camera = camera_ref.get(); 111 | 112 | // Get the camera node 113 | auto camera_nodes = scene.GetAttachedNodes(engine_.main_camera()); 114 | glm::mat4 camera_transform = glm::mat4(1.0f); 115 | 116 | if (camera_nodes && camera_nodes.value().get().size() > 0) { 117 | auto camera_node = *camera_nodes.value().get().begin(); 118 | camera_transform = scene.GetTransformMatrix(camera_node).value(); 119 | } 120 | 121 | float aspect_ratio = 122 | float(engine_.platform().GetWidth()) / engine_.platform().GetHeight(); 123 | auto fovy = camera.h_fov / aspect_ratio; 124 | 125 | // Compute position, look at and up vector in world space 126 | glm::vec3 ws_pos = camera_transform * glm::vec4(camera.position, 1.0f); 127 | glm::vec3 ws_look_at = 128 | glm::normalize(camera_transform * glm::vec4(camera.look_at, 0.0f)); 129 | glm::vec3 ws_up = 130 | glm::normalize(camera_transform * glm::vec4(camera.up, 0.0f)); 131 | glm::mat4 view = glm::lookAt(ws_pos, ws_pos + ws_look_at, ws_up); 132 | 133 | glm::mat4 proj = glm::perspective(glm::radians(fovy), aspect_ratio, 134 | camera.near_plane, camera.far_plane); 135 | proj[1][1] *= -1; // Vulkan-style projection 136 | 137 | glm::mat4 vp = proj * view; 138 | 139 | // Compute transform matrices for shadow maps 140 | auto shadow_vp = glm::mat4(1.0f); 141 | bool shadow_map_found{false}; 142 | int32_t i{0}; 143 | 144 | scene.ForEach([&](auto id, auto nodes, Light& light) { 145 | for (const auto& light_node : nodes) { 146 | auto model = scene.GetTransformMatrix(light_node).value(); 147 | 148 | if (!shadow_map_found && light.type == LightType::Directional) { 149 | shadow_map_found = true; 150 | light_buffer_data.shadow_ids[0] = i; 151 | 152 | glm::vec3 ws_eye = model * glm::vec4(light.position, 1.0f); 153 | glm::vec3 ws_direction = 154 | glm::normalize(model * glm::vec4(light.direction, 0.0f)); 155 | glm::vec3 ws_up = 156 | glm::normalize(model * glm::vec4(light.up, 0.0f)); 157 | auto shadow_view = 158 | glm::lookAt(ws_eye, ws_eye + ws_direction, ws_up); 159 | 160 | constexpr float size = 20.0f; 161 | auto shadow_proj = 162 | glm::ortho(-size, size, -size, size, -size, size); 163 | shadow_vp = shadow_proj * shadow_view; 164 | } 165 | 166 | i++; 167 | } 168 | }); 169 | 170 | // Hold/release the current culling state 171 | const auto keypresses = engine_.input_system().GetFrameInput().keypresses; 172 | const auto last_keypresses = 173 | engine_.input_system().GetLastFrameInput().keypresses; 174 | 175 | if (keypresses.find(KeyInput::H) != keypresses.end()) { 176 | vp_hold = std::make_unique(vp); 177 | } else if (keypresses.find(KeyInput::R) != keypresses.end()) { 178 | vp_hold = {}; 179 | } 180 | 181 | // Clear the shader cache (reload shaders) 182 | if (keypresses.find(KeyInput::C) != keypresses.end() && 183 | last_keypresses.find(KeyInput::C) == last_keypresses.end()) { 184 | spdlog::info("Reloading shaders!"); 185 | backend_->ClearShaderCache(); 186 | } 187 | 188 | // Get main render sequence 189 | RenderSequence render_seq; 190 | render_seq.reserve(scene.GetAttachmentCount()); 191 | 192 | scene.ForEach([&](auto id, auto nodes, Mesh& mesh) { 193 | for (auto& mesh_node : nodes) { 194 | glm::mat4 mvp = vp * scene.GetTransformMatrix(mesh_node).value(); 195 | glm::vec3 bbox_center = 196 | (mesh.bounding_box->min + mesh.bounding_box->max) * 0.5f; 197 | glm::vec4 cs_center = mvp * glm::vec4(bbox_center, 1.0f); 198 | cs_center /= cs_center.w; 199 | render_seq.push_back({id, mesh_node, cs_center}); 200 | } 201 | }); 202 | 203 | // Frustum culling 204 | RenderSequence visible_seq = Cull(scene, render_seq, vp); 205 | 206 | // Sorting 207 | std::sort(visible_seq.begin(), visible_seq.end(), 208 | [](const auto& a, const auto& b) { 209 | return a.cs_center.z < b.cs_center.z; 210 | }); 211 | 212 | backend_->RenderFrame( 213 | { 214 | [this, &scene, &light_buffer_data](FrameIndex frame_id, 215 | const RenderPassDesc*) { 216 | return UpdateLightBuffer(frame_id, scene, light_buffer_data); 217 | }, 218 | [this, &scene, &render_seq, &shadow_vp](FrameIndex frame_id, 219 | const RenderPassDesc*) { 220 | return ShadowPass(frame_id, scene, render_seq, shadow_vp); 221 | }, 222 | [this, &scene, &visible_seq, &ws_pos, &vp, &shadow_vp]( 223 | FrameIndex frame_id, const RenderPassDesc*) { 224 | return ForwardPass(frame_id, scene, visible_seq, ws_pos, vp, 225 | shadow_vp); 226 | }, 227 | [this](FrameIndex frame_id, const RenderPassDesc*) { 228 | return DownscalePass(frame_id, "resolved_image", "blur_half"); 229 | }, 230 | [this](FrameIndex frame_id, const RenderPassDesc*) { 231 | return DownscalePass(frame_id, "blur_half", "blur_quarter"); 232 | }, 233 | [this](FrameIndex frame_id, const RenderPassDesc*) { 234 | return UpscalePass(frame_id, "blur_quarter", "blur_half"); 235 | }, 236 | [this](FrameIndex frame_id, const RenderPassDesc*) { 237 | return UpscalePass(frame_id, "blur_half", "blur_full"); 238 | }, 239 | [this, &scene](FrameIndex frame_id, const RenderPassDesc*) { 240 | return PostprocessingPass(frame_id); 241 | }, 242 | }, 243 | "postprocessing"); 244 | 245 | return outcome::success(); 246 | } 247 | 248 | result Renderer::CreateBRDFLut() { 249 | std::string path{GOMA_ASSETS_DIR "textures/brdf_lut.png"}; 250 | 251 | int width = 0; 252 | int height = 0; 253 | int n; 254 | auto stbi_image = stbi_load(path.c_str(), &width, &height, &n, 4); 255 | 256 | if (!stbi_image) { 257 | spdlog::warn("Decompressing \"{}\" failed with error: {}", path, 258 | stbi_failure_reason()); 259 | stbi_image_free(stbi_image); 260 | return Error::DecompressionFailed; 261 | } 262 | 263 | TextureDesc tex_desc{static_cast(width), 264 | static_cast(height)}; 265 | tex_desc.mipmapping = true; 266 | 267 | auto sampler = SamplerDesc{}; 268 | sampler.addressing_mode = TextureWrappingMode::MirroredRepeat; 269 | tex_desc.sampler = sampler; 270 | 271 | backend_->CreateTexture("brdf_lut", tex_desc, stbi_image); 272 | 273 | stbi_image_free(stbi_image); 274 | 275 | return outcome::success(); 276 | } 277 | 278 | result Renderer::CreateSkybox() { 279 | CreateSphere(); 280 | 281 | const std::vector filenames{"posx.jpg", "negx.jpg", "posy.jpg", 282 | "negy.jpg", "posz.jpg", "negz.jpg"}; 283 | 284 | std::string base_path{GOMA_ASSETS_DIR "textures/skybox/cloudy/"}; 285 | 286 | std::vector stbi_images; 287 | 288 | int width = 0; 289 | int height = 0; 290 | for (auto& filename : filenames) { 291 | // Load skybox using stb_image 292 | int n; 293 | auto full_path = base_path + filename; 294 | auto image_data = stbi_load(full_path.c_str(), &width, &height, &n, 4); 295 | 296 | if (!image_data) { 297 | spdlog::warn("Decompressing \"{}\" failed with error: {}", 298 | full_path, stbi_failure_reason()); 299 | for (auto& stbi_image : stbi_images) { 300 | stbi_image_free(stbi_image); 301 | } 302 | return Error::DecompressionFailed; 303 | } 304 | 305 | stbi_images.push_back(image_data); 306 | } 307 | 308 | TextureDesc tex_desc{static_cast(width), 309 | static_cast(height)}; 310 | tex_desc.mipmapping = true; 311 | 312 | auto res = backend_->CreateCubemap( 313 | "goma_skybox", tex_desc, 314 | {stbi_images[0], stbi_images[1], stbi_images[2], stbi_images[3], 315 | stbi_images[4], stbi_images[5]}); 316 | 317 | for (auto& stbi_image : stbi_images) { 318 | stbi_image_free(stbi_image); 319 | } 320 | 321 | if (!res) { 322 | return res.as_failure(); 323 | } 324 | 325 | uint32_t min_dim = std::min(width, height); 326 | auto mip_levels = static_cast(floor(log2(min_dim) + 1)); 327 | skybox_mip_count = std::max(1U, mip_levels); 328 | 329 | return outcome::success(); 330 | } 331 | 332 | // From https://github.com/Erkaman/cute-deferred-shading 333 | result Renderer::CreateSphere() { 334 | int stacks = 20; 335 | int slices = 20; 336 | const float PI = glm::pi(); 337 | 338 | Mesh mesh{"goma_sphere"}; 339 | 340 | // loop through stacks. 341 | for (int i = 0; i <= stacks; ++i) { 342 | float V = (float)i / (float)stacks; 343 | float phi = V * PI; 344 | 345 | // loop through the slices. 346 | for (int j = 0; j <= slices; ++j) { 347 | float U = (float)j / (float)slices; 348 | float theta = U * (PI * 2); 349 | 350 | // use spherical coordinates to calculate the positions. 351 | float x = cos(theta) * sin(phi); 352 | float y = cos(phi); 353 | float z = sin(theta) * sin(phi); 354 | 355 | mesh.vertices.push_back({x, y, z}); 356 | } 357 | } 358 | 359 | // Calc The Index Positions 360 | for (int i = 0; i < slices * stacks + slices; ++i) { 361 | mesh.indices.push_back(static_cast(i)); 362 | mesh.indices.push_back(static_cast(i + slices + 1)); 363 | mesh.indices.push_back(static_cast(i + slices)); 364 | 365 | mesh.indices.push_back(static_cast(i + slices + 1)); 366 | mesh.indices.push_back(static_cast(i)); 367 | mesh.indices.push_back(static_cast(i + 1)); 368 | } 369 | 370 | OUTCOME_TRY(attachment, 371 | engine_.scene()->CreateAttachment(std::move(mesh))); 372 | engine_.scene()->RegisterAttachment(attachment, "goma_sphere"); 373 | 374 | return outcome::success(); 375 | } 376 | 377 | void Renderer::CreateMeshBuffers(Scene& scene) { 378 | scene.ForEach([&](auto id, auto, Mesh& mesh) { 379 | // Create vertex buffer 380 | if (!mesh.vertices.empty() && 381 | (!mesh.buffers.vertex || !mesh.buffers.vertex->valid)) { 382 | auto vb_result = backend_->CreateVertexBuffer( 383 | id, "vertex", mesh.vertices.size() * sizeof(mesh.vertices[0]), 384 | true, mesh.vertices.data()); 385 | 386 | if (vb_result) { 387 | auto& vb = vb_result.value(); 388 | mesh.buffers.vertex = vb; 389 | } 390 | } 391 | 392 | // Create normal buffer 393 | if (!mesh.normals.empty() && 394 | (!mesh.buffers.normal || !mesh.buffers.normal->valid)) { 395 | auto vb_result = backend_->CreateVertexBuffer( 396 | id, "normal", mesh.normals.size() * sizeof(mesh.normals[0]), 397 | true, mesh.normals.data()); 398 | 399 | if (vb_result) { 400 | auto& vb = vb_result.value(); 401 | mesh.buffers.normal = vb; 402 | } 403 | } 404 | 405 | // Create tangent buffer 406 | if (!mesh.tangents.empty() && 407 | (!mesh.buffers.tangent || !mesh.buffers.tangent->valid)) { 408 | auto vb_result = backend_->CreateVertexBuffer( 409 | id, "tangent", mesh.tangents.size() * sizeof(mesh.tangents[0]), 410 | true, mesh.tangents.data()); 411 | 412 | if (vb_result) { 413 | auto& vb = vb_result.value(); 414 | mesh.buffers.tangent = vb; 415 | } 416 | } 417 | 418 | // Create bitangent buffer 419 | if (!mesh.bitangents.empty() && 420 | (!mesh.buffers.bitangent || !mesh.buffers.bitangent->valid)) { 421 | auto vb_result = backend_->CreateVertexBuffer( 422 | id, "bitangent", 423 | mesh.bitangents.size() * sizeof(mesh.bitangents[0]), true, 424 | mesh.bitangents.data()); 425 | 426 | if (vb_result) { 427 | auto& vb = vb_result.value(); 428 | mesh.buffers.bitangent = vb; 429 | } 430 | } 431 | 432 | // Create index buffer 433 | if (!mesh.indices.empty() && 434 | (!mesh.buffers.index || !mesh.buffers.index->valid)) { 435 | auto ib_result = backend_->CreateIndexBuffer( 436 | id, "index", mesh.indices.size() * sizeof(mesh.indices[0]), 437 | true, mesh.indices.data()); 438 | 439 | if (ib_result) { 440 | auto& ib = ib_result.value(); 441 | mesh.buffers.index = ib; 442 | } 443 | } 444 | 445 | // Create color buffer 446 | if (!mesh.colors.empty() && 447 | (!mesh.buffers.color || !mesh.buffers.color->valid)) { 448 | auto vb_result = backend_->CreateVertexBuffer( 449 | id, "color", mesh.colors.size() * sizeof(mesh.colors[0]), true, 450 | mesh.colors.data()); 451 | 452 | if (vb_result) { 453 | auto& vb = vb_result.value(); 454 | mesh.buffers.color = vb; 455 | } 456 | } 457 | 458 | // Create UV0 buffer 459 | if (mesh.uv_sets.size() > 0 && 460 | (!mesh.buffers.uv0 || !mesh.buffers.uv0->valid)) { 461 | auto vb_result = backend_->CreateVertexBuffer( 462 | id, "uv0", mesh.uv_sets[0].size() * sizeof(mesh.uv_sets[0][0]), 463 | true, mesh.uv_sets[0].data()); 464 | 465 | if (vb_result) { 466 | auto& vb = vb_result.value(); 467 | mesh.buffers.uv0 = vb; 468 | } 469 | } 470 | 471 | // Create UV1 buffer 472 | if (mesh.uv_sets.size() > 1 && 473 | (!mesh.buffers.uv1 || !mesh.buffers.uv1->valid)) { 474 | auto vb_result = backend_->CreateVertexBuffer( 475 | id, "uv1", mesh.uv_sets[1].size() * sizeof(mesh.uv_sets[1][0]), 476 | true, mesh.uv_sets[1].data()); 477 | 478 | if (vb_result) { 479 | auto& vb = vb_result.value(); 480 | mesh.buffers.uv1 = vb; 481 | } 482 | } 483 | 484 | // Create UVW buffer 485 | if (mesh.uvw_sets.size() > 0 && 486 | (!mesh.buffers.uvw || !mesh.buffers.uvw->valid)) { 487 | auto vb_result = backend_->CreateVertexBuffer( 488 | id, "uvw", 489 | mesh.uvw_sets[0].size() * sizeof(mesh.uvw_sets[0][0]), true, 490 | mesh.uvw_sets[0].data()); 491 | 492 | if (vb_result) { 493 | auto& vb = vb_result.value(); 494 | mesh.buffers.uvw = vb; 495 | } 496 | } 497 | }); 498 | } 499 | 500 | void Renderer::CreateVertexInputFormats(Scene& scene) { 501 | scene.ForEach([&](auto, auto, Mesh& mesh) { 502 | VertexInputFormatDesc input_format_desc; 503 | 504 | uint32_t binding_id = 0; 505 | if (!mesh.vertices.empty()) { 506 | input_format_desc.bindings.push_back( 507 | {binding_id, sizeof(mesh.vertices[0])}); 508 | input_format_desc.attributes.push_back( 509 | {binding_id, binding_id, Format::SFloatRGB32, 0}); 510 | } 511 | 512 | binding_id++; 513 | if (!mesh.normals.empty()) { 514 | input_format_desc.bindings.push_back( 515 | {binding_id, sizeof(mesh.normals[0])}); 516 | input_format_desc.attributes.push_back( 517 | {binding_id, binding_id, Format::SFloatRGB32, 0}); 518 | } 519 | 520 | binding_id++; 521 | if (!mesh.tangents.empty()) { 522 | input_format_desc.bindings.push_back( 523 | {binding_id, sizeof(mesh.tangents[0])}); 524 | input_format_desc.attributes.push_back( 525 | {binding_id, binding_id, Format::SFloatRGB32, 0}); 526 | } 527 | 528 | binding_id++; 529 | if (!mesh.bitangents.empty()) { 530 | input_format_desc.bindings.push_back( 531 | {binding_id, sizeof(mesh.bitangents[0])}); 532 | input_format_desc.attributes.push_back( 533 | {binding_id, binding_id, Format::SFloatRGB32, 0}); 534 | } 535 | 536 | binding_id++; 537 | if (!mesh.colors.empty()) { 538 | input_format_desc.bindings.push_back( 539 | {binding_id, sizeof(mesh.colors[0])}); 540 | input_format_desc.attributes.push_back( 541 | {binding_id, binding_id, Format::SFloatRGBA32, 0}); 542 | } 543 | 544 | binding_id++; 545 | if (mesh.uv_sets.size() > 0 && !mesh.uv_sets[0].empty()) { 546 | input_format_desc.bindings.push_back( 547 | {binding_id, sizeof(mesh.uv_sets[0][0])}); 548 | input_format_desc.attributes.push_back( 549 | {binding_id, binding_id, Format::SFloatRG32, 0}); 550 | } 551 | 552 | binding_id++; 553 | if (mesh.uv_sets.size() > 1 && !mesh.uv_sets[1].empty()) { 554 | input_format_desc.bindings.push_back( 555 | {binding_id, sizeof(mesh.uv_sets[1][0])}); 556 | input_format_desc.attributes.push_back( 557 | {binding_id, binding_id, Format::SFloatRG32, 0}); 558 | } 559 | 560 | binding_id++; 561 | if (mesh.uvw_sets.size() > 0 && !mesh.uvw_sets[0].empty()) { 562 | input_format_desc.bindings.push_back( 563 | {binding_id, sizeof(mesh.uvw_sets[0][0])}); 564 | input_format_desc.attributes.push_back( 565 | {binding_id, binding_id, Format::SFloatRGB32, 0}); 566 | } 567 | 568 | auto input_format_res = 569 | backend_->GetVertexInputFormat(input_format_desc); 570 | if (input_format_res) { 571 | mesh.vertex_input_format = input_format_res.value(); 572 | } 573 | }); 574 | } 575 | 576 | void Renderer::UploadTextures(Scene& scene) { 577 | scene.ForEach([&](auto, auto, Material& material) { 578 | const std::vector texture_types = { 579 | TextureType::Diffuse, TextureType::Specular, 580 | TextureType::Ambient, TextureType::Emissive, 581 | TextureType::MetallicRoughness, TextureType::HeightMap, 582 | TextureType::NormalMap, TextureType::Shininess, 583 | TextureType::Opacity, TextureType::Displacement, 584 | TextureType::LightMap, TextureType::Reflection}; 585 | 586 | for (const auto& texture_type : texture_types) { 587 | auto binding = material.texture_bindings.find(texture_type); 588 | 589 | if (binding != material.texture_bindings.end() && 590 | !binding->second.empty()) { 591 | auto texture_res = 592 | scene.GetAttachment(binding->second[0].index); 593 | if (texture_res) { 594 | auto& texture = texture_res.value().get(); 595 | 596 | // Upload texture if necessary 597 | if (!texture.image || !texture.image->valid) { 598 | if (!texture.compressed) { 599 | TextureDesc tex_desc{texture.width, texture.height}; 600 | auto image_res = backend_->CreateTexture( 601 | texture.path.c_str(), tex_desc, 602 | texture.data.data()); 603 | 604 | if (image_res) { 605 | texture.image = image_res.value(); 606 | } 607 | } else { 608 | spdlog::warn( 609 | "Compressed texture \"{}\" not supported.", 610 | texture.path); 611 | } 612 | } 613 | } 614 | } 615 | } 616 | }); 617 | } 618 | 619 | Renderer::RenderSequence Renderer::Cull(Scene& scene, 620 | const RenderSequence& render_seq, 621 | const glm::mat4& vp) { 622 | RenderSequence visible_seq; 623 | visible_seq.reserve(render_seq.size()); 624 | 625 | std::vector cs_vertices(8); 626 | const auto& culling_vp = vp_hold ? *vp_hold : vp; 627 | 628 | std::copy_if( 629 | render_seq.begin(), render_seq.end(), std::back_inserter(visible_seq), 630 | [&culling_vp, &cs_vertices, &scene](const RenderSequenceElement& e) { 631 | glm::mat4 mvp = 632 | culling_vp * scene.GetTransformMatrix(e.node).value(); 633 | auto& mesh = scene.GetAttachment(e.mesh).value().get(); 634 | 635 | cs_vertices[0] = mvp * glm::vec4(mesh.bounding_box->min, 1.0f); 636 | cs_vertices[1] = mvp * glm::vec4(mesh.bounding_box->max, 1.0f); 637 | cs_vertices[2] = mvp * glm::vec4(mesh.bounding_box->min.x, 638 | mesh.bounding_box->min.y, 639 | mesh.bounding_box->max.z, 1.0f); 640 | cs_vertices[3] = mvp * glm::vec4(mesh.bounding_box->min.x, 641 | mesh.bounding_box->max.y, 642 | mesh.bounding_box->max.z, 1.0f); 643 | cs_vertices[4] = mvp * glm::vec4(mesh.bounding_box->min.x, 644 | mesh.bounding_box->max.y, 645 | mesh.bounding_box->min.z, 1.0f); 646 | cs_vertices[5] = mvp * glm::vec4(mesh.bounding_box->max.x, 647 | mesh.bounding_box->max.y, 648 | mesh.bounding_box->min.z, 1.0f); 649 | cs_vertices[6] = mvp * glm::vec4(mesh.bounding_box->max.x, 650 | mesh.bounding_box->min.y, 651 | mesh.bounding_box->min.z, 1.0f); 652 | cs_vertices[7] = mvp * glm::vec4(mesh.bounding_box->max.x, 653 | mesh.bounding_box->min.y, 654 | mesh.bounding_box->max.z, 1.0f); 655 | 656 | bool culled = 657 | std::all_of(cs_vertices.begin(), cs_vertices.end(), 658 | [](const auto& v) { return v.x <= -v.w; }) || 659 | std::all_of(cs_vertices.begin(), cs_vertices.end(), 660 | [](const auto& v) { return v.x >= v.w; }) || 661 | std::all_of(cs_vertices.begin(), cs_vertices.end(), 662 | [](const auto& v) { return v.y <= -v.w; }) || 663 | std::all_of(cs_vertices.begin(), cs_vertices.end(), 664 | [](const auto& v) { return v.y >= v.w; }) || 665 | std::all_of(cs_vertices.begin(), cs_vertices.end(), 666 | [](const auto& v) { return v.z <= 0; }) || 667 | std::all_of(cs_vertices.begin(), cs_vertices.end(), 668 | [](const auto& v) { return v.z >= v.w; }); 669 | 670 | return !culled; 671 | }); 672 | 673 | visible_seq.shrink_to_fit(); 674 | return visible_seq; 675 | } 676 | 677 | Renderer::LightBufferData Renderer::GetLightBufferData(Scene& scene) { 678 | uint32_t num_lights = static_cast( 679 | std::min(kMaxLights, scene.GetAttachmentCount())); 680 | LightBufferData light_buffer_data{glm::vec3(0.05f), 681 | static_cast(num_lights)}; 682 | 683 | size_t i = 0; 684 | auto shadow_vp = glm::mat4(1.0f); 685 | scene.ForEach([&](auto id, auto nodes, Light& light) { 686 | if (i >= num_lights) { 687 | return; 688 | } 689 | 690 | for (const auto& light_node : nodes) { 691 | auto model = scene.GetTransformMatrix(light_node).value(); 692 | 693 | auto& buf_light = light_buffer_data.lights[i]; 694 | buf_light.direction = model * glm::vec4(light.direction, 0.0f); 695 | buf_light.type = static_cast(light.type); 696 | buf_light.color = light.diffuse_color; 697 | buf_light.intensity = light.intensity; 698 | buf_light.position = model * glm::vec4(light.position, 1.0f); 699 | buf_light.range = (100 + light.attenuation[0]) / 700 | light.attenuation[1]; // range for 99% reduction 701 | buf_light.innerConeCos = std::cos(light.inner_cone_angle); 702 | buf_light.outerConeCos = std::cos(light.outer_cone_angle); 703 | 704 | i++; 705 | } 706 | }); 707 | 708 | return light_buffer_data; 709 | } 710 | 711 | result Renderer::UpdateLightBuffer( 712 | FrameIndex frame_id, Scene& scene, 713 | const LightBufferData& light_buffer_data) { 714 | constexpr auto padded_light_size = 715 | (sizeof(LightBufferData) / 256 + 1) * 256; 716 | auto light_buffer_res = backend_->GetUniformBuffer("lights"); 717 | if (!light_buffer_res) { 718 | light_buffer_res = backend_->CreateUniformBuffer( 719 | "lights", 3 * padded_light_size, false); 720 | } 721 | 722 | auto light_buffer = light_buffer_res.value(); 723 | 724 | backend_->UpdateBuffer(*light_buffer, frame_id * padded_light_size, 725 | sizeof(LightBufferData), &light_buffer_data); 726 | 727 | return outcome::success(); 728 | } 729 | 730 | result Renderer::ShadowPass(FrameIndex frame_id, Scene& scene, 731 | const RenderSequence& render_seq, 732 | const glm::mat4& shadow_vp) { 733 | backend_->BindDepthStencilState(DepthStencilState{}); 734 | backend_->BindRasterizationState(RasterizationState{}); 735 | backend_->BindMultisampleState(MultisampleState{}); 736 | 737 | backend_->SetViewport({{2048.0f, 2048.0f}}); 738 | backend_->SetScissor({{2048, 2048}}); 739 | 740 | // Render meshes 741 | AttachmentIndex last_mesh_id{0, 0}; 742 | Mesh* mesh{nullptr}; 743 | for (const auto& seq_entry : render_seq) { 744 | auto mesh_id = seq_entry.mesh; 745 | auto node_id = seq_entry.node; 746 | 747 | if (mesh_id != last_mesh_id) { 748 | auto mesh_res = scene.GetAttachment(mesh_id); 749 | if (!mesh_res) { 750 | spdlog::error("Couldn't find mesh {}.", mesh_id); 751 | continue; 752 | } 753 | 754 | mesh = &mesh_res.value().get(); 755 | last_mesh_id = mesh_id; 756 | 757 | // Bind the new mesh 758 | auto material_result = 759 | scene.GetAttachment(mesh->material); 760 | if (!material_result) { 761 | spdlog::error("Couldn't find material for mesh {}.", 762 | mesh->name); 763 | continue; 764 | } 765 | auto& material = material_result.value().get(); 766 | 767 | auto pipeline_res = backend_->GetGraphicsPipeline( 768 | {GOMA_ASSETS_DIR "shaders/shadow.vert", 769 | ShaderSourceType::Filename, GetVertexShaderPreamble(*mesh)}); 770 | if (!pipeline_res) { 771 | spdlog::error("Couldn't get pipeline for the shadow pass."); 772 | continue; 773 | } 774 | 775 | backend_->BindGraphicsPipeline(*pipeline_res.value()); 776 | 777 | backend_->BindVertexInputFormat(*mesh->vertex_input_format); 778 | BindMeshBuffers(*mesh); 779 | BindMaterialTextures(material); 780 | } 781 | 782 | // Draw the mesh node 783 | auto model = scene.GetTransformMatrix(node_id).value(); 784 | glm::mat4 mvp = shadow_vp * model; 785 | 786 | auto vtx_ubo_res = backend_->GetUniformBuffer( 787 | BufferType::PerNode, node_id, "shadow_vtx_ubo"); 788 | if (!vtx_ubo_res) { 789 | vtx_ubo_res = backend_->CreateUniformBuffer( 790 | BufferType::PerNode, node_id, "shadow_vtx_ubo", 3 * 256, false); 791 | } 792 | 793 | auto vtx_ubo = vtx_ubo_res.value(); 794 | 795 | struct VtxUBO { 796 | glm::mat4 mvp; 797 | glm::mat4 model; 798 | glm::mat4 normals; 799 | }; 800 | VtxUBO vtx_ubo_data{std::move(mvp), model, 801 | glm::inverseTranspose(model)}; 802 | 803 | backend_->UpdateBuffer(*vtx_ubo, frame_id * 256, sizeof(vtx_ubo_data), 804 | &vtx_ubo_data); 805 | backend_->BindUniformBuffer(*vtx_ubo, frame_id * 256, 806 | sizeof(vtx_ubo_data), 12); 807 | 808 | if (!mesh->indices.empty()) { 809 | backend_->DrawIndexed(static_cast(mesh->indices.size())); 810 | } else { 811 | backend_->Draw(static_cast(mesh->vertices.size())); 812 | } 813 | } 814 | 815 | return outcome::success(); 816 | } 817 | 818 | result Renderer::ForwardPass(FrameIndex frame_id, Scene& scene, 819 | const RenderSequence& render_seq, 820 | const glm::vec3& camera_ws_pos, 821 | const glm::mat4& camera_vp, 822 | const glm::mat4& shadow_vp) { 823 | uint32_t sample_count = 1; 824 | auto image = backend_->render_plan().color_images.find("color"); 825 | if (image != backend_->render_plan().color_images.end()) { 826 | sample_count = image->second.samples; 827 | } 828 | 829 | auto w = engine_.platform().GetWidth(); 830 | auto h = engine_.platform().GetHeight(); 831 | backend_->SetViewport({{static_cast(w), static_cast(h)}}); 832 | backend_->SetScissor({{w, h}}); 833 | 834 | backend_->BindDepthStencilState(DepthStencilState{}); 835 | backend_->BindRasterizationState(RasterizationState{}); 836 | backend_->BindMultisampleState( 837 | MultisampleState{sample_count, sample_count > 1}); 838 | 839 | constexpr auto padded_light_size = 840 | (sizeof(LightBufferData) / 256 + 1) * 256; 841 | auto light_buffer_res = backend_->GetUniformBuffer("lights"); 842 | if (light_buffer_res) { 843 | backend_->BindUniformBuffer(*light_buffer_res.value(), 844 | frame_id * padded_light_size, 845 | sizeof(LightBufferData), 15); 846 | } 847 | 848 | // Render meshes 849 | AttachmentIndex last_mesh_id{0, 0}; 850 | Mesh* mesh{nullptr}; 851 | for (const auto& seq_entry : render_seq) { 852 | auto mesh_id = seq_entry.mesh; 853 | auto node_id = seq_entry.node; 854 | 855 | if (mesh_id != last_mesh_id) { 856 | auto mesh_res = scene.GetAttachment(mesh_id); 857 | if (!mesh_res) { 858 | spdlog::error("Couldn't find mesh {}.", mesh_id); 859 | continue; 860 | } 861 | 862 | mesh = &mesh_res.value().get(); 863 | last_mesh_id = mesh_id; 864 | 865 | // Bind the new mesh 866 | auto material_result = 867 | scene.GetAttachment(mesh->material); 868 | if (!material_result) { 869 | spdlog::error("Couldn't find material for mesh {}.", 870 | mesh->name); 871 | continue; 872 | } 873 | auto& material = material_result.value().get(); 874 | 875 | auto pipeline_res = backend_->GetGraphicsPipeline( 876 | {GOMA_ASSETS_DIR "shaders/pbr.vert", ShaderSourceType::Filename, 877 | GetVertexShaderPreamble(*mesh)}, 878 | {GOMA_ASSETS_DIR "shaders/pbr.frag", ShaderSourceType::Filename, 879 | GetFragmentShaderPreamble(*mesh, material)}); 880 | if (!pipeline_res) { 881 | spdlog::error("Couldn't get pipeline for material {}.", 882 | material.name); 883 | continue; 884 | } 885 | 886 | backend_->BindGraphicsPipeline(*pipeline_res.value()); 887 | 888 | backend_->BindVertexInputFormat(*mesh->vertex_input_format); 889 | BindMeshBuffers(*mesh); 890 | BindMaterialTextures(material); 891 | 892 | auto shadow_depth_res = 893 | backend_->GetRenderTarget(frame_id, "shadow_depth"); 894 | if (!shadow_depth_res) { 895 | spdlog::error("Couldn't get the shadow map."); 896 | continue; 897 | } 898 | backend_->BindTexture(*shadow_depth_res.value(), 14); 899 | 900 | auto brdf_res = backend_->GetTexture("brdf_lut"); 901 | if (!shadow_depth_res) { 902 | spdlog::error("Couldn't get the BRDF LUT."); 903 | continue; 904 | } 905 | backend_->BindTexture(*brdf_res.value(), 16); 906 | 907 | auto frag_ubo_res = backend_->GetUniformBuffer(BufferType::PerNode, 908 | node_id, "frag_ubo"); 909 | if (!frag_ubo_res) { 910 | frag_ubo_res = backend_->CreateUniformBuffer( 911 | BufferType::PerNode, node_id, "frag_ubo", 3 * 256, false); 912 | } 913 | 914 | auto frag_ubo = frag_ubo_res.value(); 915 | 916 | struct FragUBO { 917 | float exposure; 918 | float gamma; 919 | float metallic; 920 | float roughness; 921 | glm::vec4 base_color; 922 | glm::vec3 camera; 923 | float alpha_cutoff; 924 | float reflection_mip_count; 925 | float ibl_strength; 926 | }; 927 | FragUBO frag_ubo_data{4.5f, 928 | 2.2f, 929 | material.metallic_factor, 930 | material.roughness_factor, 931 | {material.diffuse_color, 1.0f}, 932 | camera_ws_pos, 933 | material.alpha_cutoff, 934 | static_cast(skybox_mip_count), 935 | 0.4f}; 936 | 937 | backend_->UpdateBuffer(*frag_ubo, frame_id * 256, 938 | sizeof(frag_ubo_data), &frag_ubo_data); 939 | backend_->BindUniformBuffer(*frag_ubo, frame_id * 256, 940 | sizeof(frag_ubo_data), 13); 941 | } 942 | 943 | // Draw the mesh node 944 | auto model = scene.GetTransformMatrix(node_id).value(); 945 | glm::mat4 mvp = camera_vp * model; 946 | 947 | // Shadow correction maps X and Y for the shadow MVP 948 | // to the range [0, 1], useful to sample from the shadow map 949 | const glm::mat4 shadow_correction = {{0.5f, 0.0f, 0.0f, 0.0f}, 950 | {0.0f, 0.5f, 0.0f, 0.0f}, 951 | {0.0f, 0.0f, 1.0f, 0.0f}, 952 | {0.5f, 0.5f, 0.0f, 1.0f}}; 953 | glm::mat4 shadow_mvp = shadow_correction * shadow_vp * model; 954 | 955 | auto vtx_ubo_res = 956 | backend_->GetUniformBuffer(BufferType::PerNode, node_id, "vtx_ubo"); 957 | if (!vtx_ubo_res) { 958 | vtx_ubo_res = backend_->CreateUniformBuffer( 959 | BufferType::PerNode, node_id, "vtx_ubo", 3 * 256, false); 960 | } 961 | 962 | auto vtx_ubo = vtx_ubo_res.value(); 963 | 964 | struct VtxUBO { 965 | glm::mat4 mvp; 966 | glm::mat4 model; 967 | glm::mat4 normals; 968 | glm::mat4 shadow_mvp; 969 | }; 970 | VtxUBO vtx_ubo_data{std::move(mvp), model, glm::inverseTranspose(model), 971 | std::move(shadow_mvp)}; 972 | 973 | backend_->UpdateBuffer(*vtx_ubo, frame_id * 256, sizeof(vtx_ubo_data), 974 | &vtx_ubo_data); 975 | backend_->BindUniformBuffer(*vtx_ubo, frame_id * 256, 976 | sizeof(vtx_ubo_data), 12); 977 | 978 | if (!mesh->indices.empty()) { 979 | backend_->DrawIndexed(static_cast(mesh->indices.size())); 980 | } else { 981 | backend_->Draw(static_cast(mesh->vertices.size())); 982 | } 983 | } 984 | 985 | // Draw skybox 986 | auto pipeline_res = backend_->GetGraphicsPipeline( 987 | {GOMA_ASSETS_DIR "shaders/skybox.vert", ShaderSourceType::Filename}, 988 | {GOMA_ASSETS_DIR "shaders/skybox.frag", ShaderSourceType::Filename}); 989 | if (!pipeline_res) { 990 | spdlog::error("Couldn't get pipeline for skybox."); 991 | return Error::NotFound; 992 | } 993 | 994 | auto sphere_res = scene.FindAttachment("goma_sphere"); 995 | if (!sphere_res) { 996 | spdlog::error("Couldn't find the sphere mesh for skybox."); 997 | return Error::NotFound; 998 | } 999 | auto& sphere = sphere_res.value().second.get(); 1000 | 1001 | backend_->BindGraphicsPipeline(*pipeline_res.value()); 1002 | backend_->BindVertexInputFormat(*sphere.vertex_input_format); 1003 | BindMeshBuffers(sphere); 1004 | 1005 | // Set depth clamp to 1.0f and depth test to Equal 1006 | backend_->BindDepthStencilState({true, false, CompareOp::Equal}); 1007 | backend_->BindRasterizationState( 1008 | RasterizationState{true, false, PolygonMode::Fill, CullMode::None}); 1009 | backend_->SetViewport( 1010 | {{float(engine_.platform().GetWidth()), 1011 | float(engine_.platform().GetHeight()), 0.0f, 0.0f, 1.0f, 1.0f}}); 1012 | 1013 | auto skybox_tex_res = backend_->GetTexture("goma_skybox"); 1014 | if (!skybox_tex_res) { 1015 | spdlog::error("Couldn't get skybox texture."); 1016 | return Error::NotFound; 1017 | } 1018 | 1019 | auto skybox_ubo_res = backend_->GetUniformBuffer("skybox"); 1020 | if (!skybox_ubo_res) { 1021 | skybox_ubo_res = 1022 | backend_->CreateUniformBuffer("skybox", 3 * 256, false); 1023 | } 1024 | auto skybox_ubo = skybox_ubo_res.value(); 1025 | 1026 | backend_->UpdateBuffer(*skybox_ubo, frame_id * 256, sizeof(camera_vp), 1027 | &camera_vp); 1028 | backend_->BindUniformBuffer(*skybox_ubo, frame_id * 256, sizeof(camera_vp), 1029 | 12); 1030 | 1031 | backend_->BindTexture(skybox_tex_res.value()->vez, 0); 1032 | backend_->DrawIndexed(static_cast(sphere.indices.size())); 1033 | 1034 | return outcome::success(); 1035 | } 1036 | 1037 | result Renderer::DownscalePass(FrameIndex frame_id, 1038 | const std::string& src, 1039 | const std::string& dst) { 1040 | auto src_image_res = backend_->GetRenderTarget(frame_id, src.c_str()); 1041 | if (!src_image_res) { 1042 | spdlog::error("Couldn't get the source image."); 1043 | } 1044 | 1045 | auto src_image = src_image_res.value(); 1046 | 1047 | auto src_desc_res = backend_->render_plan().color_images.find(src); 1048 | if (src_desc_res == backend_->render_plan().color_images.end()) { 1049 | spdlog::error("Couldn't get the source image from the render plan."); 1050 | } 1051 | 1052 | auto dst_desc_res = backend_->render_plan().color_images.find(dst); 1053 | if (dst_desc_res == backend_->render_plan().color_images.end()) { 1054 | spdlog::error( 1055 | "Couldn't get the destination image from the render plan."); 1056 | } 1057 | 1058 | auto extent = backend_->GetAbsoluteExtent(dst_desc_res->second.extent); 1059 | backend_->SetViewport({{extent.width, extent.height}}); 1060 | backend_->SetScissor({{static_cast(extent.width), 1061 | static_cast(extent.height)}}); 1062 | 1063 | backend_->BindDepthStencilState(DepthStencilState{}); 1064 | backend_->BindRasterizationState(RasterizationState{}); 1065 | backend_->BindMultisampleState(MultisampleState{}); 1066 | 1067 | auto no_input = backend_->GetVertexInputFormat({}); 1068 | backend_->BindVertexInputFormat(*no_input.value()); 1069 | 1070 | auto pipeline_res = backend_->GetGraphicsPipeline( 1071 | {GOMA_ASSETS_DIR "shaders/fullscreen.vert", ShaderSourceType::Filename}, 1072 | {GOMA_ASSETS_DIR "shaders/downscale.frag", ShaderSourceType::Filename}); 1073 | if (!pipeline_res) { 1074 | spdlog::error("Couldn't get pipeline for downscaling!"); 1075 | } 1076 | 1077 | auto ubo_name = "downscale_" + std::to_string(downscale_index_++); 1078 | auto downscale_ubo_res = backend_->GetUniformBuffer(ubo_name.c_str()); 1079 | if (!downscale_ubo_res) { 1080 | downscale_ubo_res = 1081 | backend_->CreateUniformBuffer(ubo_name.c_str(), 3 * 256, false); 1082 | } 1083 | auto downscale_ubo = downscale_ubo_res.value(); 1084 | 1085 | struct DownscaleUbo { 1086 | glm::vec2 half_pixel; 1087 | } ubo_data; 1088 | 1089 | auto src_extent = backend_->GetAbsoluteExtent(src_desc_res->second.extent); 1090 | ubo_data.half_pixel = {1.0f / (2 * extent.width), 1091 | 1.0f / (2 * extent.height)}; 1092 | 1093 | backend_->UpdateBuffer(*downscale_ubo, frame_id * 256, sizeof(ubo_data), 1094 | &ubo_data); 1095 | backend_->BindUniformBuffer(*downscale_ubo, frame_id * 256, 1096 | sizeof(ubo_data), 1); 1097 | 1098 | backend_->BindGraphicsPipeline(*pipeline_res.value()); 1099 | backend_->BindTexture(src_image->vez, 0); 1100 | backend_->Draw(3); 1101 | 1102 | return outcome::success(); 1103 | } 1104 | 1105 | result Renderer::UpscalePass(FrameIndex frame_id, const std::string& src, 1106 | const std::string& dst) { 1107 | auto src_image_res = backend_->GetRenderTarget(frame_id, src.c_str()); 1108 | if (!src_image_res) { 1109 | spdlog::error("Couldn't get the source image."); 1110 | } 1111 | 1112 | auto src_image = src_image_res.value(); 1113 | 1114 | auto src_desc_res = backend_->render_plan().color_images.find(src); 1115 | if (src_desc_res == backend_->render_plan().color_images.end()) { 1116 | spdlog::error("Couldn't get the source image from the render plan."); 1117 | } 1118 | 1119 | auto dst_desc_res = backend_->render_plan().color_images.find(dst); 1120 | if (dst_desc_res == backend_->render_plan().color_images.end()) { 1121 | spdlog::error( 1122 | "Couldn't get the destination image from the render plan."); 1123 | } 1124 | 1125 | auto extent = backend_->GetAbsoluteExtent(dst_desc_res->second.extent); 1126 | backend_->SetViewport({{extent.width, extent.height}}); 1127 | backend_->SetScissor({{static_cast(extent.width), 1128 | static_cast(extent.height)}}); 1129 | 1130 | backend_->BindDepthStencilState(DepthStencilState{}); 1131 | backend_->BindRasterizationState(RasterizationState{}); 1132 | backend_->BindMultisampleState(MultisampleState{}); 1133 | 1134 | auto no_input = backend_->GetVertexInputFormat({}); 1135 | backend_->BindVertexInputFormat(*no_input.value()); 1136 | 1137 | auto pipeline_res = backend_->GetGraphicsPipeline( 1138 | {GOMA_ASSETS_DIR "shaders/fullscreen.vert", ShaderSourceType::Filename}, 1139 | {GOMA_ASSETS_DIR "shaders/upscale.frag", ShaderSourceType::Filename}); 1140 | if (!pipeline_res) { 1141 | spdlog::error("Couldn't get pipeline for upscaling!"); 1142 | } 1143 | 1144 | auto ubo_name = "upscale_" + std::to_string(upscale_index_++); 1145 | auto upscale_ubo_res = backend_->GetUniformBuffer(ubo_name.c_str()); 1146 | if (!upscale_ubo_res) { 1147 | upscale_ubo_res = 1148 | backend_->CreateUniformBuffer(ubo_name.c_str(), 3 * 256, false); 1149 | } 1150 | auto upscale_ubo = upscale_ubo_res.value(); 1151 | 1152 | struct UpscaleUbo { 1153 | glm::vec2 half_pixel; 1154 | } ubo_data; 1155 | 1156 | auto src_extent = backend_->GetAbsoluteExtent(src_desc_res->second.extent); 1157 | ubo_data.half_pixel = {1.0f / (2 * extent.width), 1158 | 1.0f / (2 * extent.height)}; 1159 | 1160 | backend_->UpdateBuffer(*upscale_ubo, frame_id * 256, sizeof(ubo_data), 1161 | &ubo_data); 1162 | backend_->BindUniformBuffer(*upscale_ubo, frame_id * 256, sizeof(ubo_data), 1163 | 1); 1164 | backend_->BindGraphicsPipeline(*pipeline_res.value()); 1165 | backend_->BindTexture(src_image->vez, 0); 1166 | backend_->Draw(3); 1167 | 1168 | return outcome::success(); 1169 | } 1170 | 1171 | result Renderer::PostprocessingPass(FrameIndex frame_id) { 1172 | Scene* scene = engine_.scene(); 1173 | if (!scene) { 1174 | return Error::NoSceneLoaded; 1175 | } 1176 | 1177 | auto resolved_image_res = 1178 | backend_->GetRenderTarget(frame_id, "resolved_image"); 1179 | if (!resolved_image_res) { 1180 | spdlog::error("Couldn't get the resolved image."); 1181 | } 1182 | 1183 | auto blur_full_res = backend_->GetRenderTarget(frame_id, "blur_full"); 1184 | if (!blur_full_res) { 1185 | spdlog::error("Couldn't get the blur image."); 1186 | } 1187 | 1188 | auto depth_res = backend_->GetRenderTarget(frame_id, "depth"); 1189 | if (!depth_res) { 1190 | spdlog::error("Couldn't get the resolved depth image."); 1191 | } 1192 | 1193 | auto blur_full = blur_full_res.value(); 1194 | auto resolved_image = resolved_image_res.value(); 1195 | auto depth = depth_res.value(); 1196 | 1197 | auto w = engine_.platform().GetWidth(); 1198 | auto h = engine_.platform().GetHeight(); 1199 | backend_->SetViewport({{static_cast(w), static_cast(h)}}); 1200 | backend_->SetScissor({{w, h}}); 1201 | 1202 | backend_->BindDepthStencilState(DepthStencilState{}); 1203 | backend_->BindRasterizationState(RasterizationState{}); 1204 | backend_->BindMultisampleState(MultisampleState{}); 1205 | 1206 | auto no_input = backend_->GetVertexInputFormat({}); 1207 | backend_->BindVertexInputFormat(*no_input.value()); 1208 | 1209 | auto pipeline_res = backend_->GetGraphicsPipeline( 1210 | {GOMA_ASSETS_DIR "shaders/fullscreen.vert", ShaderSourceType::Filename}, 1211 | {GOMA_ASSETS_DIR "shaders/postprocessing.frag", 1212 | ShaderSourceType::Filename}); 1213 | if (!pipeline_res) { 1214 | spdlog::error( 1215 | "Couldn't get pipeline for " 1216 | "postprocessing"); 1217 | } 1218 | 1219 | backend_->BindGraphicsPipeline(*pipeline_res.value()); 1220 | backend_->BindTexture(resolved_image->vez, 0); 1221 | backend_->BindTexture(blur_full->vez, 1); 1222 | backend_->BindTexture(depth->vez, 2); 1223 | 1224 | constexpr auto ubo_name = "postprocessing"; 1225 | auto ubo_res = backend_->GetUniformBuffer(ubo_name); 1226 | if (!ubo_res) { 1227 | ubo_res = backend_->CreateUniformBuffer(ubo_name, 3 * 256, false); 1228 | } 1229 | auto ubo = ubo_res.value(); 1230 | 1231 | struct PostprocessingUbo { 1232 | float focus_distance; 1233 | float focus_range; 1234 | float dof_strength; 1235 | float padding; 1236 | 1237 | float near_plane; 1238 | float far_plane; 1239 | }; 1240 | 1241 | OUTCOME_TRY(camera_ref, 1242 | scene->GetAttachment(engine_.main_camera())); 1243 | auto& camera = camera_ref.get(); 1244 | 1245 | auto ubo_data = PostprocessingUbo{ 1246 | 50.0f, // focus_distance 1247 | 200.0f, // focus_range 1248 | 0.5f, // dof_strength 1249 | {}, // padding 1250 | 1251 | camera.near_plane, // near_plane 1252 | camera.far_plane, // far_plane 1253 | }; 1254 | 1255 | backend_->UpdateBuffer(*ubo, frame_id * 256, sizeof(ubo_data), &ubo_data); 1256 | backend_->BindUniformBuffer(*ubo, frame_id * 256, sizeof(ubo_data), 3); 1257 | 1258 | backend_->Draw(3); 1259 | 1260 | return outcome::success(); 1261 | } 1262 | 1263 | const char* Renderer::GetVertexShaderPreamble( 1264 | const VertexShaderPreambleDesc& desc) { 1265 | auto res = vs_preamble_map_.find(desc.int_repr); 1266 | 1267 | if (res != vs_preamble_map_.end()) { 1268 | return res->second.c_str(); 1269 | } else { 1270 | std::stringstream preamble; 1271 | 1272 | if (desc.has_positions) { 1273 | preamble << "#define HAS_POSITIONS\n"; 1274 | } 1275 | if (desc.has_normals) { 1276 | preamble << "#define HAS_NORMALS\n"; 1277 | } 1278 | if (desc.has_tangents) { 1279 | preamble << "#define HAS_TANGENTS\n"; 1280 | } 1281 | if (desc.has_bitangents) { 1282 | preamble << "#define HAS_BITANGENTS\n"; 1283 | } 1284 | if (desc.has_colors) { 1285 | preamble << "#define HAS_COLORS\n"; 1286 | } 1287 | if (desc.has_uv0) { 1288 | preamble << "#define HAS_UV0\n"; 1289 | } 1290 | if (desc.has_uv1) { 1291 | preamble << "#define HAS_UV1\n"; 1292 | } 1293 | if (desc.has_uvw) { 1294 | preamble << "#define HAS_UVW\n"; 1295 | } 1296 | 1297 | vs_preamble_map_[desc.int_repr] = preamble.str(); 1298 | return vs_preamble_map_[desc.int_repr].c_str(); 1299 | } 1300 | } 1301 | 1302 | const char* Renderer::GetVertexShaderPreamble(const Mesh& mesh) { 1303 | return GetVertexShaderPreamble(VertexShaderPreambleDesc{ 1304 | !mesh.vertices.empty(), !mesh.normals.empty(), !mesh.tangents.empty(), 1305 | !mesh.bitangents.empty(), !mesh.colors.empty(), mesh.uv_sets.size() > 0, 1306 | mesh.uv_sets.size() > 1, mesh.uvw_sets.size() > 0}); 1307 | } 1308 | 1309 | const char* Renderer::GetFragmentShaderPreamble( 1310 | const FragmentShaderPreambleDesc& desc) { 1311 | auto res = fs_preamble_map_.find(desc.int_repr); 1312 | 1313 | if (res != fs_preamble_map_.end()) { 1314 | return res->second.c_str(); 1315 | } else { 1316 | std::stringstream preamble; 1317 | 1318 | // Mesh 1319 | if (desc.has_positions) { 1320 | preamble << "#define HAS_POSITIONS\n"; 1321 | } 1322 | if (desc.has_normals) { 1323 | preamble << "#define HAS_NORMALS\n"; 1324 | } 1325 | if (desc.has_tangents) { 1326 | preamble << "#define HAS_TANGENTS\n"; 1327 | } 1328 | if (desc.has_bitangents) { 1329 | preamble << "#define HAS_BITANGENTS\n"; 1330 | } 1331 | if (desc.has_colors) { 1332 | preamble << "#define HAS_COLORS\n"; 1333 | } 1334 | if (desc.has_uv0) { 1335 | preamble << "#define HAS_UV0\n"; 1336 | } 1337 | if (desc.has_uv1) { 1338 | preamble << "#define HAS_UV1\n"; 1339 | } 1340 | if (desc.has_uvw) { 1341 | preamble << "#define HAS_UVW\n"; 1342 | } 1343 | 1344 | // Material 1345 | if (desc.has_diffuse_map) { 1346 | preamble << "#define HAS_DIFFUSE_MAP\n"; 1347 | } 1348 | if (desc.has_specular_map) { 1349 | preamble << "#define HAS_SPECULAR_MAP\n"; 1350 | } 1351 | if (desc.has_ambient_map) { 1352 | preamble << "#define HAS_AMBIENT_MAP\n"; 1353 | } 1354 | if (desc.has_emissive_map) { 1355 | preamble << "#define HAS_EMISSIVE_MAP\n"; 1356 | } 1357 | if (desc.has_metallic_roughness_map) { 1358 | preamble << "#define HAS_METALLIC_ROUGHNESS_MAP\n"; 1359 | } 1360 | if (desc.has_height_map) { 1361 | preamble << "#define HAS_HEIGHT_MAP\n"; 1362 | } 1363 | if (desc.has_normal_map) { 1364 | preamble << "#define HAS_NORMAL_MAP\n"; 1365 | } 1366 | if (desc.has_shininess_map) { 1367 | preamble << "#define HAS_SHININESS_MAP\n"; 1368 | } 1369 | if (desc.has_opacity_map) { 1370 | preamble << "#define HAS_OPACITY_MAP\n"; 1371 | } 1372 | if (desc.has_displacement_map) { 1373 | preamble << "#define HAS_DISPLACEMENT_MAP\n"; 1374 | } 1375 | if (desc.has_light_map) { 1376 | preamble << "#define HAS_LIGHT_MAP\n"; 1377 | } 1378 | if (desc.has_reflection_map) { 1379 | preamble << "#define HAS_REFLECTION_MAP\n"; 1380 | } 1381 | if (desc.alpha_mask) { 1382 | preamble << "#define ALPHAMODE_MASK\n"; 1383 | } 1384 | 1385 | fs_preamble_map_[desc.int_repr] = preamble.str(); 1386 | return fs_preamble_map_[desc.int_repr].c_str(); 1387 | } 1388 | } 1389 | 1390 | const char* Renderer::GetFragmentShaderPreamble(const Mesh& mesh, 1391 | const Material& material) { 1392 | auto check_type = [&](TextureType type) { 1393 | return material.texture_bindings.find(type) != 1394 | material.texture_bindings.end(); 1395 | }; 1396 | 1397 | return GetFragmentShaderPreamble(FragmentShaderPreambleDesc{ 1398 | !mesh.vertices.empty(), 1399 | !mesh.normals.empty(), 1400 | !mesh.tangents.empty(), 1401 | !mesh.bitangents.empty(), 1402 | !mesh.colors.empty(), 1403 | mesh.uv_sets.size() > 0, 1404 | mesh.uv_sets.size() > 1, 1405 | mesh.uvw_sets.size() > 0, 1406 | check_type(TextureType::Diffuse), 1407 | check_type(TextureType::Specular), 1408 | check_type(TextureType::Ambient), 1409 | check_type(TextureType::Emissive), 1410 | check_type(TextureType::MetallicRoughness), 1411 | check_type(TextureType::HeightMap), 1412 | check_type(TextureType::NormalMap), 1413 | check_type(TextureType::Shininess), 1414 | check_type(TextureType::Opacity), 1415 | check_type(TextureType::Displacement), 1416 | check_type(TextureType::LightMap), 1417 | backend_->GetTexture("goma_skybox").has_value(), 1418 | material.alpha_cutoff < 1.0f, 1419 | }); 1420 | } 1421 | 1422 | result Renderer::BindMeshBuffers(const Mesh& mesh) { 1423 | uint32_t binding = 0; 1424 | auto bind = [&](const Buffer* buf) { 1425 | if (buf && buf->valid) { 1426 | backend_->BindVertexBuffer(*buf, binding, 0); 1427 | } 1428 | binding++; 1429 | }; 1430 | 1431 | bind(mesh.buffers.vertex.get()); 1432 | bind(mesh.buffers.normal.get()); 1433 | bind(mesh.buffers.tangent.get()); 1434 | bind(mesh.buffers.bitangent.get()); 1435 | bind(mesh.buffers.color.get()); 1436 | bind(mesh.buffers.uv0.get()); 1437 | bind(mesh.buffers.uv1.get()); 1438 | bind(mesh.buffers.uvw.get()); 1439 | 1440 | if (!mesh.indices.empty()) { 1441 | backend_->BindIndexBuffer(*mesh.buffers.index); 1442 | } 1443 | 1444 | return outcome::success(); 1445 | } 1446 | 1447 | result Renderer::BindMaterialTextures(const Material& material) { 1448 | Scene* scene = engine_.scene(); 1449 | if (!scene) { 1450 | return Error::NoSceneLoaded; 1451 | } 1452 | 1453 | uint32_t binding_id = 0; 1454 | 1455 | const std::vector texture_types = { 1456 | TextureType::Diffuse, TextureType::Specular, 1457 | TextureType::Ambient, TextureType::Emissive, 1458 | TextureType::MetallicRoughness, TextureType::HeightMap, 1459 | TextureType::NormalMap, TextureType::Shininess, 1460 | TextureType::Opacity, TextureType::Displacement, 1461 | TextureType::LightMap, TextureType::Reflection}; 1462 | 1463 | for (const auto& texture_type : texture_types) { 1464 | if (texture_type == TextureType::Reflection) { 1465 | auto skybox_tex_res = backend_->GetTexture("goma_skybox"); 1466 | if (skybox_tex_res) { 1467 | backend_->BindTexture(skybox_tex_res.value()->vez, binding_id); 1468 | } 1469 | } 1470 | 1471 | auto binding = material.texture_bindings.find(texture_type); 1472 | 1473 | if (binding != material.texture_bindings.end() && 1474 | !binding->second.empty()) { 1475 | auto texture_res = 1476 | scene->GetAttachment(binding->second[0].index); 1477 | if (texture_res) { 1478 | auto& texture = texture_res.value().get(); 1479 | if (texture.image && texture.image->valid) { 1480 | backend_->BindTexture(*texture.image, binding_id); 1481 | } 1482 | } 1483 | } 1484 | binding_id++; 1485 | } 1486 | 1487 | return outcome::success(); 1488 | } 1489 | 1490 | } // namespace goma 1491 | -------------------------------------------------------------------------------- /src/scene/assimp_loader.cpp: -------------------------------------------------------------------------------- 1 | #include "scene/loaders/assimp_loader.hpp" 2 | 3 | #include "scene/attachments/texture.hpp" 4 | #include "scene/attachments/material.hpp" 5 | #include "scene/attachments/camera.hpp" 6 | #include "scene/attachments/light.hpp" 7 | #include "scene/attachments/mesh.hpp" 8 | 9 | #include "assimp/Importer.hpp" 10 | #include "assimp/postprocess.h" 11 | #include "assimp/pbrmaterial.h" 12 | 13 | #define STB_IMAGE_IMPLEMENTATION 14 | #define STBI_FAILURE_USERMSG 15 | #include 16 | 17 | #include "common/error_codes.hpp" 18 | 19 | namespace goma { 20 | 21 | result> AssimpLoader::ReadSceneFromFile( 22 | const char *file_path) { 23 | Assimp::Importer importer; 24 | auto ai_scene = 25 | importer.ReadFile(file_path, aiProcessPreset_TargetRealtime_Quality | 26 | aiProcess_TransformUVCoords); 27 | 28 | if (!ai_scene) { 29 | spdlog::error(importer.GetErrorString()); 30 | return Error::SceneImportFailed; 31 | } 32 | 33 | std::string base_path = file_path; 34 | base_path = base_path.substr(0, base_path.find_last_of('/') + 1); 35 | return std::move(ConvertScene(ai_scene, std::move(base_path))); 36 | } 37 | 38 | result> AssimpLoader::ConvertScene( 39 | const aiScene *ai_scene, const std::string &base_path) { 40 | std::unique_ptr scene = std::make_unique(); 41 | 42 | std::map node_name_map; 43 | std::map> material_map; 44 | 45 | // Convert materials 46 | if (ai_scene->HasMaterials()) { 47 | for (size_t i = 0; i < ai_scene->mNumMaterials; i++) { 48 | aiMaterial *ai_material = ai_scene->mMaterials[i]; 49 | Material material{ai_material->GetName().C_Str()}; 50 | 51 | static const std::vector> 52 | texture_types{ 53 | {aiTextureType_DIFFUSE, TextureType::Diffuse}, 54 | {aiTextureType_SPECULAR, TextureType::Specular}, 55 | {aiTextureType_AMBIENT, TextureType::Ambient}, 56 | {aiTextureType_EMISSIVE, TextureType::Emissive}, 57 | {aiTextureType_HEIGHT, TextureType::HeightMap}, 58 | {aiTextureType_NORMALS, TextureType::NormalMap}, 59 | {aiTextureType_SHININESS, TextureType::Shininess}, 60 | {aiTextureType_OPACITY, TextureType::Opacity}, 61 | {aiTextureType_DISPLACEMENT, TextureType::Displacement}, 62 | {aiTextureType_LIGHTMAP, TextureType::LightMap}, 63 | {aiTextureType_REFLECTION, TextureType::Reflection}, 64 | {aiTextureType_UNKNOWN, TextureType::MetallicRoughness}}; 65 | 66 | for (const auto &texture_type : texture_types) { 67 | for (uint32_t j = 0; 68 | j < ai_material->GetTextureCount(texture_type.first); 69 | j++) { 70 | auto tex_binding = LoadMaterialTexture( 71 | scene.get(), ai_material, base_path, texture_type, j); 72 | 73 | if (tex_binding.has_value()) { 74 | material.texture_bindings[texture_type.second] 75 | .push_back(std::move(tex_binding.value())); 76 | } 77 | } 78 | 79 | aiColor3D diffuse(0, 0, 0); 80 | aiColor3D specular(0, 0, 0); 81 | aiColor3D ambient(0, 0, 0); 82 | aiColor3D emissive(0, 0, 0); 83 | aiColor3D transparent(0, 0, 0); 84 | ai_material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse); 85 | ai_material->Get(AI_MATKEY_COLOR_SPECULAR, specular); 86 | ai_material->Get(AI_MATKEY_COLOR_AMBIENT, ambient); 87 | ai_material->Get(AI_MATKEY_COLOR_EMISSIVE, emissive); 88 | ai_material->Get(AI_MATKEY_COLOR_TRANSPARENT, transparent); 89 | material.diffuse_color = {diffuse.r, diffuse.g, diffuse.b}; 90 | material.specular_color = {specular.r, specular.g, specular.b}; 91 | material.ambient_color = {ambient.r, ambient.g, ambient.b}; 92 | material.emissive_color = {emissive.r, emissive.g, emissive.b}; 93 | material.transparent_color = {transparent.r, transparent.g, 94 | transparent.b}; 95 | 96 | int two_sided = 0; 97 | float opacity = 1.0f; 98 | float alpha_cutoff = 1.0f; 99 | float shininess_exponent = 0.0f; 100 | float specular_strength = 1.0f; 101 | float metallic_factor = 0.2f; 102 | float roughness_factor = 0.5f; 103 | 104 | ai_material->Get(AI_MATKEY_TWOSIDED, two_sided); 105 | ai_material->Get(AI_MATKEY_OPACITY, opacity); 106 | ai_material->Get(AI_MATKEY_GLTF_ALPHACUTOFF, alpha_cutoff); 107 | ai_material->Get(AI_MATKEY_SHININESS, shininess_exponent); 108 | ai_material->Get(AI_MATKEY_SHININESS_STRENGTH, 109 | specular_strength); 110 | ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, 111 | metallic_factor); 112 | ai_material->Get( 113 | AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, 114 | roughness_factor); 115 | 116 | material.two_sided = (two_sided > 0); 117 | material.opacity = opacity; 118 | material.alpha_cutoff = alpha_cutoff; 119 | material.shininess_exponent = shininess_exponent; 120 | material.specular_strength = specular_strength; 121 | material.metallic_factor = metallic_factor; 122 | material.roughness_factor = roughness_factor; 123 | } 124 | 125 | auto material_result = scene->CreateAttachment(std::move(material)); 126 | 127 | if (material_result.has_value()) { 128 | auto m = material_result.value(); 129 | scene->RegisterAttachment( 130 | m, ai_material->GetName().C_Str()); 131 | material_map[i] = m; 132 | } else { 133 | spdlog::warn("Material creation failed for material \"{}\".", 134 | ai_material->GetName().C_Str()); 135 | } 136 | } 137 | } 138 | 139 | // Convert meshes 140 | if (ai_scene->HasMeshes()) { 141 | for (size_t i = 0; i < ai_scene->mNumMeshes; i++) { 142 | aiMesh *ai_mesh = ai_scene->mMeshes[i]; 143 | Mesh mesh{ai_mesh->mName.C_Str()}; 144 | 145 | Box bounding_box; 146 | mesh.vertices.reserve(ai_mesh->mNumVertices); 147 | for (size_t j = 0; j < ai_mesh->mNumVertices; j++) { 148 | const auto &pos = ai_mesh->mVertices[j]; 149 | mesh.vertices.push_back({pos.x, pos.y, pos.z}); 150 | 151 | if (pos.x < bounding_box.min.x) { 152 | bounding_box.min.x = pos.x; 153 | } 154 | if (pos.x > bounding_box.max.x) { 155 | bounding_box.max.x = pos.x; 156 | } 157 | 158 | if (pos.y < bounding_box.min.y) { 159 | bounding_box.min.y = pos.y; 160 | } 161 | if (pos.y > bounding_box.max.y) { 162 | bounding_box.max.y = pos.y; 163 | } 164 | 165 | if (pos.z < bounding_box.min.z) { 166 | bounding_box.min.z = pos.z; 167 | } 168 | if (pos.z > bounding_box.max.z) { 169 | bounding_box.max.z = pos.z; 170 | } 171 | } 172 | mesh.bounding_box = std::make_unique(std::move(bounding_box)); 173 | 174 | mesh.normals.reserve(ai_mesh->mNumVertices); 175 | if (ai_mesh->HasNormals()) { 176 | for (size_t j = 0; j < ai_mesh->mNumVertices; j++) { 177 | const auto &nor = ai_mesh->mNormals[j]; 178 | mesh.normals.push_back({nor.x, nor.y, nor.z}); 179 | } 180 | } 181 | 182 | mesh.tangents.reserve(ai_mesh->mNumVertices); 183 | mesh.bitangents.reserve(ai_mesh->mNumVertices); 184 | if (ai_mesh->HasTangentsAndBitangents()) { 185 | for (size_t j = 0; j < ai_mesh->mNumVertices; j++) { 186 | const auto &tan = ai_mesh->mTangents[j]; 187 | const auto &bit = ai_mesh->mBitangents[j]; 188 | mesh.tangents.push_back({tan.x, tan.y, tan.z}); 189 | mesh.bitangents.push_back({bit.x, bit.y, bit.z}); 190 | } 191 | } 192 | 193 | mesh.indices.reserve(ai_mesh->mNumFaces); 194 | if (ai_mesh->HasFaces()) { 195 | for (size_t j = 0; j < ai_mesh->mNumFaces; j++) { 196 | const auto &tri = ai_mesh->mFaces[j]; 197 | for (size_t k = 0; k < tri.mNumIndices; k++) { 198 | mesh.indices.push_back(tri.mIndices[k]); 199 | } 200 | } 201 | } 202 | 203 | mesh.colors.reserve(ai_mesh->mNumFaces); 204 | if (ai_mesh->HasVertexColors(0)) { 205 | auto color_set = ai_mesh->mColors[0]; 206 | for (size_t j = 0; j < ai_mesh->mNumVertices; j++) { 207 | const auto &col = color_set[j]; 208 | mesh.colors.push_back({col.r, col.g, col.b, col.a}); 209 | } 210 | } 211 | 212 | for (size_t j = 0; j < ai_mesh->GetNumUVChannels(); j++) { 213 | // UV coords 214 | if (ai_mesh->mNumUVComponents[j] == 2) { 215 | auto ai_uv_set = ai_mesh->mTextureCoords[j]; 216 | std::vector uv_set; 217 | uv_set.reserve(ai_mesh->mNumVertices); 218 | 219 | for (size_t k = 0; k < ai_mesh->mNumVertices; k++) { 220 | const auto &uv = ai_uv_set[k]; 221 | uv_set.push_back({uv.x, uv.y}); 222 | } 223 | mesh.uv_sets.push_back(std::move(uv_set)); 224 | } 225 | 226 | // UVW coords 227 | if (ai_mesh->mNumUVComponents[j] == 3) { 228 | auto ai_uvw_set = ai_mesh->mTextureCoords[j]; 229 | std::vector uvw_set; 230 | uvw_set.reserve(ai_mesh->mNumVertices); 231 | 232 | for (size_t k = 0; k < ai_mesh->mNumVertices; k++) { 233 | const auto &uvw = ai_uvw_set[k]; 234 | uvw_set.push_back({uvw.x, uvw.y, uvw.z}); 235 | } 236 | mesh.uvw_sets.push_back(std::move(uvw_set)); 237 | } 238 | } 239 | 240 | auto material_res = material_map.find(ai_mesh->mMaterialIndex); 241 | if (material_res != material_map.end()) { 242 | mesh.material = material_res->second; 243 | } else { 244 | spdlog::warn("Could not find material for mesh \"{}\".", 245 | ai_mesh->mName.C_Str()); 246 | } 247 | 248 | auto mesh_result = scene->CreateAttachment(std::move(mesh)); 249 | 250 | if (mesh_result.has_value()) { 251 | auto m = mesh_result.value(); 252 | scene->RegisterAttachment(m, ai_mesh->mName.C_Str()); 253 | } else { 254 | spdlog::warn("Mesh creation failed for mesh \"{}\".", 255 | ai_mesh->mName.C_Str()); 256 | } 257 | } 258 | } 259 | 260 | // Convert node structure 261 | if (ai_scene->mRootNode) { 262 | std::map node_map; 263 | 264 | aiNode *ai_root_node = ai_scene->mRootNode; 265 | aiVector3D pos, rot, scale; 266 | 267 | auto scene_root_node = scene->CreateNode(scene->GetRootNode()).value(); 268 | 269 | ai_root_node->mTransformation.Decompose(scale, rot, pos); 270 | auto root_transform = scene->GetTransform(scene_root_node).value(); 271 | root_transform.position = {pos.x, pos.y, pos.z}; 272 | root_transform.rotation = glm::quat(glm::vec3(rot.x, rot.y, rot.z)); 273 | root_transform.scale = {scale.x, scale.y, scale.z}; 274 | scene->SetTransform(scene_root_node, root_transform); 275 | 276 | node_map[ai_root_node] = scene_root_node; 277 | node_name_map[ai_root_node->mName.C_Str()] = scene_root_node; 278 | 279 | for (unsigned int i = 0; i < ai_root_node->mNumMeshes; i++) { 280 | // We have a 1:1 correspondence between aiMesh and Mesh objects, 281 | // so we can use the same index 282 | uint32_t mesh_index = ai_root_node->mMeshes[i]; 283 | scene->Attach({mesh_index}, scene_root_node); 284 | } 285 | 286 | // Traverse the node graph 287 | std::queue open_nodes; 288 | std::set closed_nodes; 289 | 290 | for (unsigned int i = 0; i < ai_root_node->mNumChildren; i++) { 291 | open_nodes.push(ai_root_node->mChildren[i]); 292 | } 293 | 294 | while (open_nodes.size() > 0) { 295 | aiNode *ai_node = open_nodes.front(); 296 | open_nodes.pop(); 297 | closed_nodes.insert(ai_node); 298 | 299 | NodeIndex parent = scene_root_node; 300 | auto result = node_map.find(ai_node->mParent); 301 | if (result != node_map.end()) { 302 | parent = result->second; 303 | } 304 | 305 | ai_node->mTransformation.Decompose(scale, rot, pos); 306 | auto node_result = scene->CreateNode( 307 | parent, {{pos.x, pos.y, pos.z}, 308 | glm::quat(glm::vec3(rot.x, rot.y, rot.z)), 309 | {scale.x, scale.y, scale.z}}); 310 | 311 | if (!node_result.has_value()) { 312 | const auto &error = node_result.error(); 313 | spdlog::error( 314 | "Node conversion failed for node \"{}\". Error: {}", 315 | ai_node->mName.C_Str(), error.message()); 316 | continue; 317 | } 318 | 319 | auto node = node_result.value(); 320 | node_map[ai_node] = node; 321 | node_name_map[ai_node->mName.C_Str()] = node; 322 | 323 | for (unsigned int i = 0; i < ai_node->mNumMeshes; i++) { 324 | // We have a 1:1 correspondence between aiMesh and Mesh objects, 325 | // so we can use the same index 326 | uint32_t mesh_index = ai_node->mMeshes[i]; 327 | scene->Attach({mesh_index}, node); 328 | } 329 | 330 | // Add children to open_nodes 331 | for (unsigned int i = 0; i < ai_node->mNumChildren; i++) { 332 | auto child = ai_node->mChildren[i]; 333 | // Guard against loops in the graph 334 | if (closed_nodes.find(child) == closed_nodes.end()) { 335 | open_nodes.push(child); 336 | } 337 | } 338 | } 339 | } 340 | 341 | // Convert embedded textures (if any) 342 | if (ai_scene->HasTextures()) { 343 | for (size_t i = 0; i < ai_scene->mNumTextures; i++) { 344 | aiTexture *ai_texture = ai_scene->mTextures[i]; 345 | 346 | if (ai_texture->mFilename.length == 0) { 347 | spdlog::warn("No filename specified for texture, skipping."); 348 | continue; 349 | } 350 | 351 | if (!ai_texture->mHeight) { 352 | int width, height, n; 353 | auto image_data = stbi_load_from_memory( 354 | reinterpret_cast(ai_texture->pcData), 355 | ai_texture->mWidth, &width, &height, &n, 4); 356 | 357 | if (!image_data) { 358 | spdlog::warn("Decompressing \"{}\" failed with error: {}", 359 | ai_texture->mFilename.C_Str(), 360 | stbi_failure_reason()); 361 | 362 | // Decompression failed, store the compressed texture 363 | std::vector data; 364 | data.resize(4 * ai_texture->mWidth * ai_texture->mHeight); 365 | memcpy(data.data(), ai_texture->pcData, data.size()); 366 | 367 | std::string path = ai_texture->mFilename.C_Str(); 368 | auto texture = scene->CreateAttachment( 369 | {path, ai_texture->mWidth, 1, std::move(data), true}); 370 | 371 | if (texture.has_value()) { 372 | auto t = texture.value(); 373 | scene->RegisterAttachment(t, path); 374 | } else { 375 | const auto &error = texture.error(); 376 | spdlog::warn( 377 | "Loading texture \"{}\" failed with error: {}", 378 | path, error.message()); 379 | } 380 | } else { 381 | std::vector data; 382 | data.resize(4 * width * height); 383 | memcpy(data.data(), image_data, data.size()); 384 | 385 | std::string path = ai_texture->mFilename.C_Str(); 386 | auto texture = scene->CreateAttachment( 387 | {path, static_cast(width), 388 | static_cast(height), std::move(data), 389 | false}); 390 | 391 | if (texture.has_value()) { 392 | auto t = texture.value(); 393 | scene->RegisterAttachment(t, path); 394 | } else { 395 | const auto &error = texture.error(); 396 | spdlog::warn( 397 | "Loading texture \"{}\" failed with error: {}", 398 | path, error.message()); 399 | } 400 | } 401 | 402 | stbi_image_free(image_data); 403 | } else { 404 | // Uncompressed texture 405 | std::vector data; 406 | data.resize(4 * ai_texture->mWidth * ai_texture->mHeight); 407 | memcpy(data.data(), ai_texture->pcData, data.size()); 408 | 409 | std::string path = ai_texture->mFilename.C_Str(); 410 | auto texture = scene->CreateAttachment( 411 | {ai_texture->mFilename.C_Str(), ai_texture->mWidth, 412 | ai_texture->mHeight, std::move(data), false}); 413 | 414 | if (texture.has_value()) { 415 | auto t = texture.value(); 416 | scene->RegisterAttachment(t, path); 417 | } else { 418 | const auto &error = texture.error(); 419 | spdlog::warn("Loading texture \"{}\" failed with error: {}", 420 | path, error.message()); 421 | } 422 | } 423 | } 424 | } 425 | 426 | // Convert cameras 427 | if (ai_scene->HasCameras()) { 428 | for (size_t i = 0; i < ai_scene->mNumCameras; i++) { 429 | aiCamera *ai_camera = ai_scene->mCameras[i]; 430 | 431 | NodeIndex node = scene->GetRootNode(); 432 | auto result = node_name_map.find(ai_camera->mName.C_Str()); 433 | if (result != node_name_map.end()) { 434 | node = result->second; 435 | } 436 | 437 | auto camera_result = scene->CreateAttachment( 438 | node, {ai_camera->mName.C_Str(), 439 | glm::degrees(ai_camera->mHorizontalFOV), 440 | ai_camera->mClipPlaneNear, 441 | ai_camera->mClipPlaneFar, 442 | ai_camera->mAspect, 443 | {ai_camera->mPosition.x, ai_camera->mPosition.y, 444 | ai_camera->mPosition.z}, 445 | {ai_camera->mUp.x, ai_camera->mUp.y, ai_camera->mUp.z}, 446 | {ai_camera->mLookAt.x, ai_camera->mLookAt.y, 447 | ai_camera->mLookAt.z}}); 448 | 449 | if (camera_result.has_value()) { 450 | auto c = camera_result.value(); 451 | scene->RegisterAttachment(c, ai_camera->mName.C_Str()); 452 | } else { 453 | spdlog::warn("Camera creation failed for camera \"{}\".", 454 | ai_camera->mName.C_Str()); 455 | } 456 | } 457 | } 458 | 459 | // Convert lights 460 | if (ai_scene->HasLights()) { 461 | for (size_t i = 0; i < ai_scene->mNumLights; i++) { 462 | aiLight *ai_light = ai_scene->mLights[i]; 463 | 464 | NodeIndex node = scene->GetRootNode(); 465 | auto result = node_name_map.find(ai_light->mName.C_Str()); 466 | if (result != node_name_map.end()) { 467 | node = result->second; 468 | } 469 | 470 | static const std::map light_type_map = 471 | { 472 | {aiLightSource_UNDEFINED, LightType::Directional}, 473 | {aiLightSource_DIRECTIONAL, LightType::Directional}, 474 | {aiLightSource_POINT, LightType::Point}, 475 | {aiLightSource_SPOT, LightType::Spot}, 476 | {aiLightSource_AMBIENT, LightType::Ambient}, 477 | {aiLightSource_AREA, LightType::Area}, 478 | }; 479 | 480 | auto light_result = scene->CreateAttachment( 481 | node, 482 | {ai_light->mName.C_Str(), 483 | light_type_map.at(ai_light->mType), 484 | {ai_light->mPosition.x, ai_light->mPosition.y, 485 | ai_light->mPosition.z}, 486 | {ai_light->mDirection.x, ai_light->mDirection.y, 487 | ai_light->mDirection.z}, 488 | {ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z}, 489 | 1.0f, 490 | {ai_light->mColorDiffuse.r, ai_light->mColorDiffuse.g, 491 | ai_light->mColorDiffuse.b}, 492 | {ai_light->mColorSpecular.r, ai_light->mColorSpecular.g, 493 | ai_light->mColorSpecular.b}, 494 | {ai_light->mColorAmbient.r, ai_light->mColorAmbient.g, 495 | ai_light->mColorAmbient.b}, 496 | {ai_light->mAttenuationConstant, ai_light->mAttenuationLinear, 497 | ai_light->mAttenuationQuadratic}, 498 | glm::degrees(ai_light->mAngleInnerCone), 499 | glm::degrees(ai_light->mAngleOuterCone), 500 | {ai_light->mSize.x, ai_light->mSize.y}}); 501 | 502 | if (light_result.has_value()) { 503 | auto l = light_result.value(); 504 | scene->RegisterAttachment(l, ai_light->mName.C_Str()); 505 | } else { 506 | spdlog::warn("Light creation failed for light \"{}\".", 507 | ai_light->mName.C_Str()); 508 | } 509 | } 510 | } 511 | 512 | return std::move(scene); 513 | } 514 | 515 | result AssimpLoader::LoadMaterialTexture( 516 | Scene *scene, const aiMaterial *ai_material, const std::string &base_path, 517 | const std::pair &texture_type, 518 | uint32_t texture_index) { 519 | assert(scene && "Scene must be valid"); 520 | assert(ai_material && "Assimp material must be valid"); 521 | // Get texture information from the material 522 | aiString path; 523 | aiTextureMapping mapping = aiTextureMapping_UV; 524 | unsigned int uvindex = 0; 525 | float blend = 1.0f; 526 | aiTextureOp op = aiTextureOp_Add; 527 | aiTextureMapMode mapmode[3] = {aiTextureMapMode_Wrap, aiTextureMapMode_Wrap, 528 | aiTextureMapMode_Wrap}; 529 | 530 | ai_material->GetTexture(texture_type.first, texture_index, &path, &mapping, 531 | &uvindex, &blend, &op, mapmode); 532 | 533 | if (path.length == 0) { 534 | spdlog::warn("No path specified for texture, skipping."); 535 | return Error::NotFound; 536 | } 537 | 538 | // Gather the texture (if already loaded) or create one 539 | AttachmentIndex texture; 540 | auto result = scene->FindAttachment(path.C_Str()); 541 | if (result.has_value()) { 542 | texture = result.value().first; 543 | } else { 544 | // Create a texture using stb_image 545 | int width, height, n; 546 | auto full_path = base_path + path.C_Str(); 547 | auto image_data = stbi_load(full_path.c_str(), &width, &height, &n, 4); 548 | 549 | if (!image_data) { 550 | spdlog::warn("Decompressing \"{}\" failed with error: {}", 551 | full_path, stbi_failure_reason()); 552 | return Error::DecompressionFailed; 553 | } 554 | 555 | std::vector data; 556 | data.resize(4 * width * height); 557 | memcpy(data.data(), image_data, data.size()); 558 | 559 | auto texture_result = scene->CreateAttachment( 560 | {path.C_Str(), static_cast(width), 561 | static_cast(height), std::move(data), false}); 562 | 563 | if (texture_result.has_value()) { 564 | texture = texture_result.value(); 565 | scene->RegisterAttachment(texture, path.C_Str()); 566 | stbi_image_free(image_data); 567 | } else { 568 | const auto &error = texture_result.error(); 569 | spdlog::warn("Loading texture \"{}\" failed with error: {}", 570 | path.C_Str(), error.message()); 571 | 572 | stbi_image_free(image_data); 573 | return Error::LoadingFailed; 574 | } 575 | } 576 | 577 | static const std::map 578 | texture_wrap_modes{ 579 | {aiTextureMapMode_Wrap, TextureWrappingMode::Repeat}, 580 | {aiTextureMapMode_Clamp, TextureWrappingMode::ClampToEdge}, 581 | {aiTextureMapMode_Mirror, TextureWrappingMode::MirroredRepeat}, 582 | {aiTextureMapMode_Decal, TextureWrappingMode::Decal}}; 583 | 584 | TextureBinding tex_binding = {texture, uvindex, blend}; 585 | if (mapmode) { 586 | for (size_t k = 0; k < 3; k++) { 587 | auto m = texture_wrap_modes.find(mapmode[k]); 588 | if (m != texture_wrap_modes.end()) { 589 | tex_binding.wrapping[k] = m->second; 590 | } 591 | } 592 | } 593 | 594 | return tex_binding; 595 | } 596 | 597 | } // namespace goma 598 | -------------------------------------------------------------------------------- /src/scene/scene.cpp: -------------------------------------------------------------------------------- 1 | #include "scene/scene.hpp" 2 | 3 | #include "common/error_codes.hpp" 4 | 5 | namespace goma { 6 | 7 | std::ostream& operator<<(std::ostream& o, const goma::GenIndex& id) { 8 | o << "{id: " << id.id << ", gen: " << id.gen << "}"; 9 | return o; 10 | } 11 | 12 | std::ostream& operator<<(std::ostream& o, const goma::Transform& t) { 13 | o << "{pos: " << glm::to_string(t.position) << "," << std::endl 14 | << "rot : " << glm::to_string(t.rotation) << "," << std::endl 15 | << "scale: " << glm::to_string(t.scale) << "}"; 16 | return o; 17 | } 18 | 19 | std::ostream& operator<<(std::ostream& o, const goma::Node& n) { 20 | o << "Node(id: " << n.id << ", parent: " << n.parent << ")"; 21 | return o; 22 | } 23 | 24 | template 25 | std::ostream& operator<<(std::ostream& o, const Attachment& a) { 26 | o << "Attachment(id: " << a.id << ", nodes: ("; 27 | if (!a.nodes.empty()) { 28 | for (const auto& n : a.nodes) { 29 | o << n.id << ", "; 30 | } 31 | // Remove the last ", " 32 | o.seekp(o.tellp() - 2); 33 | } 34 | o << "))"; 35 | return o; 36 | } 37 | 38 | Scene::Scene() { nodes_.emplace_back(NodeIndex{0}, NodeIndex{0, 0}); } 39 | 40 | result Scene::CreateNode(const NodeIndex parent, 41 | const Transform& transform) { 42 | if (!ValidateNode(parent)) { 43 | return Error::InvalidParentNode; 44 | } 45 | 46 | NodeIndex ret_id; 47 | if (!recycled_nodes_.empty()) { 48 | size_t index = recycled_nodes_.front(); 49 | recycled_nodes_.pop(); 50 | 51 | // The last valid generation was stored in id.id 52 | // when the node was deleted 53 | size_t new_gen = nodes_[index].id.id + 1; 54 | 55 | nodes_[index] = {{index, new_gen}, parent, transform}; 56 | ret_id = {index, new_gen}; 57 | } else { 58 | size_t id = nodes_.size(); 59 | nodes_.emplace_back(NodeIndex{id}, parent, transform); 60 | ret_id = {id}; 61 | } 62 | 63 | nodes_[parent.id].children.insert(ret_id); 64 | return ret_id; 65 | } 66 | 67 | result Scene::GetParent(NodeIndex id) { 68 | if (!ValidateNode(id)) { 69 | return Error::InvalidNode; 70 | } 71 | if (id == GetRootNode()) { 72 | return Error::InvalidParentNode; 73 | } 74 | 75 | return nodes_[id.id].parent; 76 | } 77 | 78 | result> Scene::GetChildren(NodeIndex id) { 79 | if (!ValidateNode(id)) { 80 | return Error::InvalidNode; 81 | } 82 | return nodes_[id.id].children; 83 | } 84 | 85 | result Scene::GetTransform(NodeIndex id) { 86 | if (!ValidateNode(id)) { 87 | return Error::InvalidNode; 88 | } 89 | return nodes_[id.id].transform; 90 | } 91 | 92 | result Scene::SetTransform(NodeIndex id, const Transform& transform) { 93 | if (!ValidateNode(id)) { 94 | return Error::InvalidNode; 95 | } 96 | 97 | nodes_[id.id].transform = transform; 98 | nodes_[id.id].cached_model.reset(); 99 | return outcome::success(); 100 | } 101 | 102 | result Scene::GetTransformMatrix(NodeIndex id) { 103 | if (!ValidateNode(id)) { 104 | return Error::InvalidNode; 105 | } 106 | 107 | auto t_mat = nodes_[id.id].cached_model.get(); 108 | if (t_mat) { 109 | return *t_mat; 110 | } else { 111 | OUTCOME_TRY(ComputeTransformMatrix(id)); 112 | t_mat = nodes_[id.id].cached_model.get(); 113 | return *t_mat; 114 | } 115 | } 116 | 117 | result Scene::ComputeTransformMatrix(NodeIndex id) { 118 | std::stack node_stack; 119 | 120 | // Fill the stack with nodes for which we need 121 | // to compute model matrix. Always push the current node. 122 | auto current_node = id; 123 | do { 124 | node_stack.push(current_node); 125 | 126 | auto parent = GetParent(current_node); 127 | if (!parent) { 128 | break; 129 | } else { 130 | current_node = parent.value(); 131 | } 132 | } while (!nodes_[current_node.id].cached_model); 133 | 134 | // Compute model matrices 135 | auto current_model = glm::mat4(1.0f); 136 | if (!node_stack.empty()) { 137 | auto parent_res = GetParent(node_stack.top()); 138 | if (parent_res.has_value()) { 139 | auto& parent = parent_res.value(); 140 | if (nodes_[parent.id].cached_model) { 141 | current_model = *nodes_[parent.id].cached_model; 142 | } 143 | } 144 | } 145 | 146 | while (!node_stack.empty()) { 147 | auto cur_id = node_stack.top(); 148 | node_stack.pop(); 149 | 150 | auto transform = GetTransform(cur_id).value(); 151 | auto local_model = glm::translate(transform.position) * 152 | glm::mat4_cast(transform.rotation) * 153 | glm::scale(transform.scale); 154 | 155 | current_model = current_model * local_model; 156 | nodes_[cur_id.id].cached_model = 157 | std::make_unique(current_model); 158 | } 159 | 160 | return outcome::success(); 161 | } 162 | 163 | result Scene::DeleteNode(NodeIndex id) { 164 | // Root node cannot be deleted 165 | if (id.id == 0) { 166 | return Error::RootNodeCannotBeDeleted; 167 | } 168 | 169 | // Validate id 170 | if (!ValidateNode(id)) { 171 | return Error::InvalidNode; 172 | } 173 | 174 | // Fix parent for any child node(s) 175 | auto new_parent = nodes_[id.id].parent; 176 | nodes_[new_parent.id].children.erase(id); 177 | for (auto& child_node : nodes_) { 178 | if (child_node.parent == id) { 179 | child_node.parent = new_parent; 180 | nodes_[new_parent.id].children.insert(child_node.id); 181 | } 182 | } 183 | 184 | // We add the node's index to the recycled nodes 185 | recycled_nodes_.push(id.id); 186 | 187 | // We set generation to 0 to mark the node as 188 | // invalid and store the last valid generation into the node's id 189 | // (which is unused while the node is invalid) 190 | nodes_[id.id].id = {nodes_[id.id].id.gen, 0}; 191 | 192 | return outcome::success(); 193 | } 194 | 195 | bool Scene::ValidateNode(NodeIndex id) { 196 | return id.gen != 0 && id.id < nodes_.size() && 197 | nodes_[id.id].id.gen == id.gen; 198 | } 199 | 200 | } // namespace goma 201 | --------------------------------------------------------------------------------