├── .clang-format ├── .editorconfig ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── .vscode └── launch.json ├── 3rdparty └── CMakeLists.txt ├── CMakeLists.txt ├── CMakePresets.json ├── LICENSE ├── README.md ├── images └── cover.png ├── resources ├── fonts │ ├── LICENSE.txt │ └── Roboto-Regular.ttf └── resources.conf └── src ├── Animables ├── AxisRotationAnimable.h └── AxisTranslationAnimable.h ├── CMakeLists.txt ├── DefaultMaterial.h ├── Drawables ├── InstanceDrawable.h ├── TexturedDrawable.h ├── VelocityDrawable.h └── VelocityInstanceDrawable.h ├── Feature.h ├── ImGuiApplication.cpp ├── ImGuiApplication.h ├── Mosaiikki.cpp ├── Mosaiikki.h ├── Options.h ├── Scene.cpp ├── Scene.h ├── Shaders ├── DepthBlitShader.cpp ├── DepthBlitShader.frag ├── DepthBlitShader.h ├── DepthBlitShader.vert ├── ReconstructionOptions.h ├── ReconstructionShader.cpp ├── ReconstructionShader.frag ├── ReconstructionShader.h ├── ReconstructionShader.vert ├── VelocityShader.cpp ├── VelocityShader.frag ├── VelocityShader.h ├── VelocityShader.vert └── resources.conf ├── main.cpp └── windows-dpi-awareness.manifest /.clang-format: -------------------------------------------------------------------------------- 1 | # https://zed0.co.uk/clang-format-configurator/ 2 | # 7.0.0 (VS 2019) 3 | BasedOnStyle: WebKit 4 | AccessModifierOffset: '-4' 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: 'false' 7 | AlignConsecutiveDeclarations: 'false' 8 | AlignEscapedNewlines: Left 9 | AlignOperands: 'true' 10 | AlignTrailingComments: 'true' 11 | AllowAllParametersOfDeclarationOnNextLine: 'false' 12 | AllowShortBlocksOnASingleLine: 'false' 13 | AllowShortCaseLabelsOnASingleLine: 'false' 14 | AllowShortFunctionsOnASingleLine: Empty 15 | AllowShortIfStatementsOnASingleLine: 'false' 16 | AllowShortLoopsOnASingleLine: 'false' 17 | AlwaysBreakAfterReturnType: None 18 | AlwaysBreakBeforeMultilineStrings: 'false' 19 | AlwaysBreakTemplateDeclarations: 'Yes' 20 | BinPackArguments: 'false' 21 | BinPackParameters: 'false' 22 | BreakBeforeBinaryOperators: None 23 | BreakBeforeBraces: Allman 24 | BreakBeforeTernaryOperators: 'true' 25 | BreakConstructorInitializers: AfterColon 26 | BreakInheritanceList: AfterColon 27 | BreakStringLiterals: 'false' 28 | ColumnLimit: '120' 29 | CompactNamespaces: 'false' 30 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' 31 | ConstructorInitializerIndentWidth: '4' 32 | ContinuationIndentWidth: '4' 33 | Cpp11BracedListStyle: 'false' 34 | DerivePointerAlignment: 'false' 35 | DisableFormat: 'false' 36 | ExperimentalAutoDetectBinPacking: 'false' 37 | FixNamespaceComments: 'true' 38 | IncludeBlocks: Merge 39 | IndentCaseLabels: 'true' 40 | IndentPPDirectives: None 41 | IndentWidth: '4' 42 | IndentWrappedFunctionNames: 'true' 43 | KeepEmptyLinesAtTheStartOfBlocks: 'false' 44 | Language: Cpp 45 | MaxEmptyLinesToKeep: '1' 46 | NamespaceIndentation: None 47 | PointerAlignment: Left 48 | ReflowComments: 'false' 49 | SortIncludes: 'false' 50 | SortUsingDeclarations: 'false' 51 | SpaceAfterCStyleCast: 'false' 52 | SpaceAfterTemplateKeyword: 'false' 53 | SpaceBeforeAssignmentOperators: 'true' 54 | SpaceBeforeCpp11BracedList: 'true' 55 | SpaceBeforeCtorInitializerColon: 'true' 56 | SpaceBeforeInheritanceColon: 'true' 57 | SpaceBeforeParens: Never 58 | SpaceBeforeRangeBasedForLoopColon: 'true' 59 | SpaceInEmptyParentheses: 'false' 60 | SpacesBeforeTrailingComments: '1' 61 | SpacesInAngles: 'false' 62 | SpacesInCStyleCastParentheses: 'false' 63 | SpacesInContainerLiterals: 'true' 64 | SpacesInParentheses: 'false' 65 | SpacesInSquareBrackets: 'false' 66 | Standard: Cpp11 67 | TabWidth: '4' 68 | UseTab: Never 69 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | # end_of_line = lf 8 | insert_final_newline = true 9 | 10 | # 4 spaces indentation 11 | [*.{c,cpp,h,hpp,glsl,vert,frag}] 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [CMakeLists.txt] 16 | indent_style = space 17 | indent_size = 4 18 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | runs-on: ${{ matrix.os }} 7 | strategy: 8 | matrix: 9 | os: [windows-latest, ubuntu-latest] 10 | config: [Debug, Release] 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | submodules: recursive 15 | - name: Dependencies 16 | if: startsWith(matrix.os,'ubuntu') 17 | run: | 18 | sudo apt-get update 19 | sudo apt-get install xorg-dev libglu1-mesa-dev 20 | - name: Generate 21 | run: | 22 | mkdir build 23 | cd build 24 | cmake -DCMAKE_BUILD_TYPE=${{ matrix.config }} -DSHADER_VALIDATION=ON .. 25 | cd .. 26 | - name: Build 27 | run: | 28 | cmake --build build/ --parallel --config ${{ matrix.config }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build*/ 2 | /out/ 3 | /images/temp/ 4 | 5 | # Large files 6 | /resources/models/ 7 | 8 | # Graphic debuggers 9 | /*.cap 10 | /Nsight/ 11 | 12 | /.vs/ 13 | /.vscode/ 14 | !/.vscode/launch.json 15 | 16 | CMakeUserPresets.json 17 | 18 | # Prerequisites 19 | *.d 20 | 21 | # Compiled Object files 22 | *.slo 23 | *.lo 24 | *.o 25 | *.obj 26 | 27 | # Precompiled Headers 28 | *.gch 29 | *.pch 30 | 31 | # Compiled Dynamic libraries 32 | *.so 33 | *.dylib 34 | *.dll 35 | 36 | # Fortran module files 37 | *.mod 38 | *.smod 39 | 40 | # Compiled Static libraries 41 | *.lai 42 | *.la 43 | *.a 44 | *.lib 45 | 46 | # Executables 47 | *.exe 48 | *.out 49 | *.app 50 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/corrade"] 2 | path = 3rdparty/corrade 3 | url = https://github.com/mosra/corrade.git 4 | [submodule "3rdparty/magnum"] 5 | path = 3rdparty/magnum 6 | url = https://github.com/mosra/magnum.git 7 | [submodule "3rdparty/magnum-plugins"] 8 | path = 3rdparty/magnum-plugins 9 | url = https://github.com/mosra/magnum-plugins.git 10 | [submodule "3rdparty/magnum-integration"] 11 | path = 3rdparty/magnum-integration 12 | url = https://github.com/mosra/magnum-integration.git 13 | [submodule "3rdparty/imgui"] 14 | path = 3rdparty/imgui 15 | url = https://github.com/ocornut/imgui.git 16 | [submodule "3rdparty/glfw"] 17 | path = 3rdparty/glfw 18 | url = https://github.com/glfw/glfw.git 19 | branch = master 20 | [submodule "3rdparty/glslang"] 21 | path = 3rdparty/glslang 22 | url = https://github.com/KhronosGroup/glslang.git 23 | branch = master 24 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "(Windows) Launch", 9 | "type": "cppvsdbg", 10 | "request": "launch", 11 | "program": "${command:cmake.launchTargetPath}", 12 | "args": [], 13 | "stopAtEntry": false, 14 | "cwd": "${command:cmake.buildDirectory}", 15 | "environment": [] 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /3rdparty/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # glfw 2 | 3 | set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) 4 | set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) 5 | set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) 6 | set(GLFW_INSTALL OFF CACHE BOOL "" FORCE) 7 | add_subdirectory(glfw EXCLUDE_FROM_ALL) 8 | 9 | # glslang 10 | 11 | if(SHADER_VALIDATION) 12 | # if we don't do this, CMake will complain that Glslang::Glslang depends on 13 | # a non-existent path /build/include in INTERFACE_INCLUDE_DIRECTORIES 14 | # glslang only creates this directory later at build time 15 | file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/include") 16 | 17 | set(BUILD_TESTING OFF CACHE BOOL "" FORCE) 18 | set(BUILD_EXTERNAL OFF CACHE BOOL "" FORCE) 19 | set(ENABLE_CTEST OFF CACHE BOOL "" FORCE) 20 | set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "" FORCE) 21 | set(ENABLE_OPT OFF CACHE BOOL "" FORCE) 22 | set(ENABLE_HLSL OFF CACHE BOOL "" FORCE) 23 | set(ENABLE_SPVREMAPPER OFF CACHE BOOL "" FORCE) 24 | add_subdirectory(glslang EXCLUDE_FROM_ALL) 25 | endif() 26 | 27 | # Corrade and Magnum settings 28 | 29 | set(BUILD_DEPRECATED OFF CACHE BOOL "" FORCE) 30 | 31 | if(NOT BUILD_SHARED_LIBS) 32 | set(BUILD_STATIC ON CACHE BOOL "" FORCE) 33 | set(BUILD_PLUGINS_STATIC ON CACHE BOOL "" FORCE) 34 | endif() 35 | 36 | # Corrade 37 | 38 | set(WITH_INTERCONNECT OFF CACHE BOOL "" FORCE) 39 | set(WITH_TESTSUITE OFF CACHE BOOL "" FORCE) 40 | set(WITH_MAIN ON CACHE BOOL "" FORCE) 41 | set(WITH_UTILITY ON CACHE BOOL "" FORCE) 42 | set(WITH_RC ON CACHE BOOL "" FORCE) 43 | add_subdirectory(corrade EXCLUDE_FROM_ALL) 44 | 45 | # Magnum 46 | 47 | set(WITH_GLFWAPPLICATION ON CACHE BOOL "" FORCE) 48 | set(WITH_ANYSCENEIMPORTER ON CACHE BOOL "" FORCE) 49 | set(WITH_ANYIMAGEIMPORTER ON CACHE BOOL "" FORCE) 50 | set(WITH_DEBUGTOOLS ON CACHE BOOL "" FORCE) 51 | set(WITH_MESHTOOLS ON CACHE BOOL "" FORCE) 52 | set(WITH_SCENEGRAPH ON CACHE BOOL "" FORCE) 53 | set(WITH_SHADERS ON CACHE BOOL "" FORCE) 54 | set(WITH_TRADE ON CACHE BOOL "" FORCE) 55 | set(WITH_GL ON CACHE BOOL "" FORCE) 56 | if(SHADER_VALIDATION) 57 | set(WITH_ANYSHADERCONVERTER ON CACHE BOOL "" FORCE) 58 | set(WITH_SHADERCONVERTER ON CACHE BOOL "" FORCE) 59 | set(WITH_SHADERTOOLS ON CACHE BOOL "" FORCE) 60 | endif() 61 | add_subdirectory(magnum EXCLUDE_FROM_ALL) 62 | 63 | # Magnum plugins 64 | 65 | set(WITH_GLTFIMPORTER ON CACHE BOOL "" FORCE) 66 | set(WITH_STBIMAGEIMPORTER ON CACHE BOOL "" FORCE) 67 | if(SHADER_VALIDATION) 68 | set(WITH_GLSLANGSHADERCONVERTER ON CACHE BOOL "" FORCE) 69 | endif() 70 | add_subdirectory(magnum-plugins EXCLUDE_FROM_ALL) 71 | 72 | # Magnum integration 73 | 74 | set(IMGUI_DIR "${CMAKE_CURRENT_SOURCE_DIR}/imgui") 75 | set(WITH_IMGUI ON CACHE BOOL "" FORCE) 76 | add_subdirectory(magnum-integration EXCLUDE_FROM_ALL) 77 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 3.4 would be enough if we didn't need CMP0079 for shaderconverter 2 | cmake_minimum_required(VERSION 3.13) 3 | project(mosaiikki CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 14) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | if(NOT CMAKE_BUILD_TYPE) 9 | set(CMAKE_BUILD_TYPE Release) 10 | endif() 11 | 12 | set(BUILD_SHARED_LIBS OFF) 13 | 14 | # we don't install Corrade/Magnum/Plugins (and their CMake config files) so we need to point 15 | # CMake at the FindXXX.cmake files to find them via find_package 16 | # MagnumPlugins has all of them, except MagnumIntegration (which is missing MagnumPlugins) 17 | list(APPEND CMAKE_MODULE_PATH 18 | "${PROJECT_SOURCE_DIR}/3rdparty/magnum-plugins/modules" 19 | "${PROJECT_SOURCE_DIR}/3rdparty/magnum-integration/modules" 20 | ) 21 | 22 | option(SHADER_VALIDATION "Validate shaders at build time" OFF) 23 | 24 | add_subdirectory(3rdparty) 25 | add_subdirectory(src) 26 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 20, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "base", 11 | "hidden": true, 12 | "generator": "Ninja", 13 | "binaryDir": "${sourceDir}/build/${presetName}", 14 | "cacheVariables": { 15 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}" 16 | } 17 | }, 18 | { 19 | "name": "debug", 20 | "inherits": "base", 21 | "displayName": "Debug", 22 | "description": "Debug build", 23 | "cacheVariables": { 24 | "CMAKE_BUILD_TYPE": "Debug", 25 | "SHADER_VALIDATION": "ON" 26 | } 27 | }, 28 | { 29 | "name": "release", 30 | "inherits": "base", 31 | "displayName": "Release", 32 | "description": "Optimized build", 33 | "cacheVariables": { 34 | "CMAKE_BUILD_TYPE": "Release" 35 | } 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 pezcode 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 | # Mosaiikki 2 | 3 | An implementation of **Checkerboard Rendering** using [Magnum](https://magnum.graphics/) and OpenGL. 4 | 5 | ![cover](images/cover.png) 6 | 7 | ## Technique 8 | 9 | Checkerboard rendering is a temporal upsampling technique that reduces shading work with minimal impact on visual quality. Expensive fragment shader operations are run at half-resolution and then combined with the previous frame's output in a resolve shader. 10 | 11 | By always shading 2 MSAA samples and jittering the viewport by one pixel in every odd frame, we can combine the current frame's samples A and B with last frame's samples C and D to fill the full-res framebuffer: 12 | 13 | ``` 14 | even frame: odd frame: combined frame: 15 | (current) (previous) (full-res) 16 | 17 | +---+---+ +---+---+ +---+---+ 18 | | | A | | | C | | C | A | 19 | +---+---+ + +---+---+ = +---+---+---+ 20 | | B | | | D | | | D | B | 21 | +---+---+ +---+---+ +---+---+ 22 | ``` 23 | 24 | While this is trivial for static scenes, complications arise with moving objects/cameras and (dis)occlusion. The resolve step needs to consider per-pixel velocity between frames and detect and handle fragments being occluded. 25 | 26 | For a more thorough introduction, check out [Intel's white paper](https://software.intel.com/content/www/us/en/develop/articles/checkerboard-rendering-for-real-time-upscaling-on-intel-integrated-graphics.html). 27 | 28 | ## Overview 29 | 30 | This implementation is loosely based on the Intel D3D11 forward shading example, while also introducing improvements from several other papers (see [References](#references)). Here's a rough overview of all the steps: 31 | 32 | 1. **Velocity pass** 33 | - Render full-res per-pixel screenspace velocity buffer 34 | - Only dynamic objects; for static objects camera reprojection is used in the reconstruction pass 35 | 2. **Jitter** camera viewport 36 | - Translate a full-res pixel to the right 37 | - Happens every second (= odd) frame 38 | 3. **Checkerboard pass**: Render scene at **quarter-res** 39 | - MSAA 2X framebuffer with fixed sample positions 40 | - Fragment shader is run on both samples 41 | - Reuses downsampled velocity pass depth to reduce overdraw 42 | 4. **Reconstruction pass**: Combine previous and current quarter-res 43 | - Fullscreen pass, one thread for each full-res fragment 44 | - See [Reconstruction shader](#Reconstruction-shader) for more details 45 | 46 | ## Implementation 47 | 48 | ### Reconstruction shader 49 | 50 | The reconstruction shader is run for each full-res fragment. It performs the following steps: 51 | 52 | 1. Determine if the current fragment was shaded by this frame 53 | - This is true if this fragment falls on the current sample positions. Sample positions depend on the current camera jitter 54 | - If so, return the color and exit 55 | 2. Resolve the current color from last frame's output 56 | - Sample velocity buffer to calculate the current fragment's position in the previous frame 57 | - For static objects the velocity is instead calculated by reprojecting the current world space position through the previous camera matrices 58 | - Check if the previous fragment position **doesn't** fall on the previous frame's sample positions 59 | - This is the case when any movement cancelled the camera jitter 60 | - If so, return the neighborhood average and exit 61 | - Check for occlusion 62 | - Done by comparing the reprojected depth against its neighbors with a configurable tolerance value 63 | - If occluded, return the neighborhood average and exit 64 | - Fetch the reprojected fragment color 65 | 3. Clamp result 66 | - Clamp the reprojected color to the min/max values of the neighborhood [El Mansouri] 67 | - To allow for small-scale details, blend back to the actual color using a confidence value 68 | - Confidence is based on the neighborhood depth difference 69 | 70 | #### Notes on average calculation 71 | 72 | - To prevent bright samples from dominating the result, a simple [tonemapping operator](https://gpuopen.com/learn/optimized-reversible-tonemapper-for-resolve/) is used before averaging 73 | - Using a *differential blend operator* [Wihlidal, Jimenez] here greatly reduces checkerboard artifacts at color discontinuities 74 | 75 | There are a few more edge cases and some extra debug output not mentioned here. The full GLSL code can be found in [ReconstructionShader.frag](src/Shaders/ReconstructionShader.frag). 76 | 77 | ### Sample positions and shading 78 | 79 | OpenGL doesn't have a fixed set of MSAA sample positions, but several extensions exist that allow you to manually specify them: 80 | 81 | - [ARB_sample_locations](https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_sample_locations.txt) 82 | - [NV_sample_locations](https://www.khronos.org/registry/OpenGL/extensions/NV/NV_sample_locations.txt) 83 | - [AMD_sample_positions](https://www.khronos.org/registry/OpenGL/extensions/AMD/AMD_sample_positions.txt) 84 | 85 | The ARB extension is based on the NV extension and is [in practice only supported by Nvidia hardware](https://opengl.gpuinfo.org/listreports.php?extension=GL_NV_sample_locations). 86 | 87 | To enable per-sample fragment shader invocations, [ARB_sample_shading](https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_sample_shading.txt) (or OpenGL 4.0) is required. Setting a `MIN_SAMPLE_SHADING_VALUE` of 1.0 causes per-sample fragment evaluation, as well as exact interpolation of input values to the sample position. 88 | 89 | ### LOD bias 90 | 91 | Since screen-space derivatives in the fragment shader are calculated at half-res, they have twice the magnitude compared to full-res rendering. This is especially detrimental for texturing since larger UV derivatives cause higher MIP levels and therefore blurriness. To fix this, use `textureGrad` with corrected gradients or add a LOD bias of -0.5 to all texture samplers. 92 | 93 | ## Possible enhancements 94 | 95 | - Transparent objects cause artifacts since the velocity used for reprojection accounts for the transparent object, not anything behind it. Look into ways to improve this. 96 | - Extensive benchmarks. Provide some hard numbers on frame times using a more complex scene and realistic shading work. 97 | - Implement a deferred rendering variant. Intel's implementation uses separate render targets for storing opaque lighting and the transparent forward pass which doesn't seem entirely necessary. 98 | - Use a different color space than RGB. YCoCg might be a better candidate for color clamping/clipping. 99 | - Variable Rate Shading (through e.g. [NV_shading_rate_image](https://www.khronos.org/registry/OpenGL/extensions/NV/NV_shading_rate_image.txt)) could be used in place of per-sample shading. A 2x1 shading rate would allow interlacing columns similar to SMAA TU2x. 100 | 101 | ## References 102 | 103 | - [Checkerboard Rendering for Real-Time Upscaling on Intel Integrated Graphics](https://software.intel.com/en-us/articles/checkerboard-rendering-for-real-time-upscaling-on-intel-integrated-graphics) (Trapper Mcferron, Adam Lake, 2018) 104 | - [Rendering Rainbow Six Siege](https://twvideo01.ubm-us.net/o1/vault/gdc2016/Presentations/El_Mansouri_Jalal_Rendering_Rainbow_Six.pdf) (Jalal El Mansouri, 2016) 105 | - [4K Checkerboard in Battlefield 1 and Mass Effect](http://frostbite-wp-prd.s3.amazonaws.com/wp-content/uploads/2017/03/04173623/GDC-Checkerboard.compressed.pdf) (Graham Wihlidal, 2017) 106 | - [Dynamic Temporal Antialiasing and Upsampling in Call of Duty](https://www.activision.com/cdn/research/Dynamic_Temporal_Antialiasing_and_Upsampling_in_Call_of_Duty_v4.pdf) (Jorge Jimenez, 2020) 107 | 108 | ## Compilation 109 | 110 | [CMake](https://cmake.org/) (>= 3.4) is required for building. 111 | 112 | 1. Clone repository: 113 | 114 | ```bash 115 | git clone --recursive https://github.com/pezcode/mosaiikki.git 116 | cd mosaiikki 117 | ``` 118 | 119 | 2. Generate project files: 120 | 121 | ```bash 122 | mkdir build 123 | cd build 124 | # e.g. VS 2019, compile for x64 platform 125 | cmake -G "Visual Studio 16 2019" -A x64 .. 126 | cd .. 127 | ``` 128 | 129 | 3. Build. Open the project files with your IDE/build tool, or use CMake: 130 | 131 | ```bash 132 | cmake --build build/ --parallel --config Release 133 | ``` 134 | 135 | ## Libraries 136 | 137 | - [Magnum](https://magnum.graphics/) for rendering and asset import 138 | - [Dear ImGui](https://github.com/ocornut/imgui) for the UI 139 | 140 | ## License 141 | 142 | [MIT](https://opensource.org/licenses/MIT) 143 | -------------------------------------------------------------------------------- /images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pezcode/mosaiikki/cbf7f71ff58363b27392d4776df6bc0997bbac35/images/cover.png -------------------------------------------------------------------------------- /resources/fonts/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /resources/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pezcode/mosaiikki/cbf7f71ff58363b27392d4776df6bc0997bbac35/resources/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /resources/resources.conf: -------------------------------------------------------------------------------- 1 | group=resources 2 | 3 | [file] 4 | filename=fonts/Roboto-Regular.ttf 5 | -------------------------------------------------------------------------------- /src/Animables/AxisRotationAnimable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // continually rotate an object around an axis with a given angular velocity 11 | // rotates until a given angle (range) has been covered, then rotates in the opposite direction 12 | template 13 | class AxisRotationAnimable : public Magnum::SceneGraph::Animable3D 14 | { 15 | public: 16 | typedef Magnum::SceneGraph::AbstractObject Object; 17 | 18 | explicit AxisRotationAnimable(Object& object, 19 | const Magnum::Vector3& axis, 20 | Magnum::Rad velocity, /* radians per second */ 21 | Magnum::Rad range = Magnum::Rad(Magnum::Constants::inf()) /* radians */) : 22 | Magnum::SceneGraph::Animable3D(object), 23 | transformation(static_cast&>(object)), 24 | axis(axis.normalized()), 25 | velocity(velocity), 26 | range(Magnum::Math::abs(range)) 27 | { 28 | setRepeated(true); 29 | } 30 | 31 | private: 32 | virtual void animationStopped() override 33 | { 34 | transformation.rotateLocal(-distance, axis); 35 | distance = Magnum::Rad(0.0f); 36 | direction = 1.0f; 37 | } 38 | 39 | virtual void animationStep(Magnum::Float /*absolute*/, Magnum::Float delta) override 40 | { 41 | Magnum::Rad deltaDistance = direction * velocity * delta; 42 | Magnum::Rad diff = Magnum::Math::abs(distance + deltaDistance) - range; 43 | while(diff > Magnum::Rad(0.0f)) 44 | { 45 | direction = -direction; 46 | deltaDistance += 2.0f * diff * direction; 47 | diff -= 2.0f * range; 48 | } 49 | 50 | distance += deltaDistance; 51 | transformation.rotateLocal(deltaDistance, axis); 52 | } 53 | 54 | Magnum::SceneGraph::AbstractTranslationRotation3D& transformation; 55 | 56 | const Magnum::Vector3 axis; 57 | const Magnum::Rad velocity; 58 | const Magnum::Rad range; 59 | 60 | Magnum::Rad distance = Magnum::Rad(0.0f); 61 | Magnum::Float direction = 1.0f; 62 | }; 63 | -------------------------------------------------------------------------------- /src/Animables/AxisTranslationAnimable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // continually moves an object back and forth along an axis 10 | // oscillates around the original position with the given velocity, range units in each direction 11 | // animation happens relative to the current position so you can overlay translations 12 | template 13 | class AxisTranslationAnimable : public Magnum::SceneGraph::Animable3D 14 | { 15 | public: 16 | typedef Magnum::SceneGraph::AbstractObject Object; 17 | 18 | explicit AxisTranslationAnimable(Object& object, 19 | const Magnum::Vector3& axis, 20 | Magnum::Float velocity, /* units per second */ 21 | Magnum::Float range = Magnum::Constants::inf() /* units */) : 22 | Magnum::SceneGraph::Animable3D(object), 23 | transformation(static_cast&>(object)), 24 | axis(axis.normalized()), 25 | velocity(velocity), 26 | range(Magnum::Math::abs(range)) 27 | { 28 | setRepeated(true); 29 | } 30 | 31 | private: 32 | virtual void animationStopped() override 33 | { 34 | transformation.translate(axis * -distance); 35 | distance = 0.0f; 36 | direction = 1.0f; 37 | } 38 | 39 | virtual void animationStep(Magnum::Float /*absolute*/, Magnum::Float delta) override 40 | { 41 | Magnum::Float deltaDistance = direction * velocity * delta; 42 | Magnum::Float diff = Magnum::Math::abs(distance + deltaDistance) - range; 43 | while(diff > 0.0f) 44 | { 45 | // handle reflected distance, important for huge deltas 46 | direction = -direction; 47 | deltaDistance += 2.0f * diff * direction; 48 | diff -= 2.0f * range; 49 | } 50 | distance += deltaDistance; 51 | transformation.translate(axis * deltaDistance); 52 | } 53 | 54 | Magnum::SceneGraph::AbstractTranslation3D& transformation; 55 | 56 | const Magnum::Vector3 axis; 57 | const Magnum::Float velocity; 58 | const Magnum::Float range; 59 | 60 | Magnum::Float distance = 0.0f; 61 | Magnum::Float direction = 1.0f; 62 | }; 63 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SOURCES 2 | main.cpp 3 | Mosaiikki.h 4 | Mosaiikki.cpp 5 | ImGuiApplication.h 6 | ImGuiApplication.cpp 7 | Scene.h 8 | Scene.cpp 9 | Drawables/TexturedDrawable.h 10 | Drawables/InstanceDrawable.h 11 | Drawables/VelocityDrawable.h 12 | Drawables/VelocityInstanceDrawable.h 13 | Animables/AxisTranslationAnimable.h 14 | Animables/AxisRotationAnimable.h 15 | DefaultMaterial.h 16 | Feature.h 17 | Options.h 18 | Shaders/VelocityShader.h 19 | Shaders/VelocityShader.cpp 20 | Shaders/DepthBlitShader.h 21 | Shaders/DepthBlitShader.cpp 22 | Shaders/ReconstructionShader.h 23 | Shaders/ReconstructionShader.cpp 24 | Shaders/ReconstructionOptions.h 25 | ) 26 | 27 | set(SHADERS 28 | Shaders/ReconstructionShader.vert 29 | Shaders/ReconstructionShader.frag 30 | Shaders/VelocityShader.vert 31 | Shaders/VelocityShader.frag 32 | Shaders/DepthBlitShader.vert 33 | Shaders/DepthBlitShader.frag 34 | ) 35 | 36 | source_group("Shader Files" FILES ${SHADERS}) 37 | 38 | if(CORRADE_TARGET_MSVC) 39 | # this is required to turn off automatic scaling and 40 | # get the DPI scaling factor from Magnum/SDL 41 | list(APPEND SOURCES windows-dpi-awareness.manifest) 42 | endif() 43 | 44 | # enable compiler warnings 45 | set_directory_properties(PROPERTIES CORRADE_USE_PEDANTIC_FLAGS ON) 46 | 47 | find_package(Corrade REQUIRED 48 | Main 49 | Utility 50 | ) 51 | find_package(Magnum REQUIRED 52 | GlfwApplication 53 | GL 54 | SceneGraph 55 | Shaders 56 | Trade 57 | MeshTools 58 | DebugTools 59 | AnySceneImporter 60 | AnyImageImporter 61 | ) 62 | find_package(MagnumPlugins REQUIRED 63 | GltfImporter 64 | StbImageImporter 65 | ) 66 | find_package(MagnumIntegration REQUIRED 67 | ImGui 68 | ) 69 | 70 | # embed resources 71 | corrade_add_resource(RESOURCES "${PROJECT_SOURCE_DIR}/resources/resources.conf") 72 | corrade_add_resource(SHADER_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/Shaders/resources.conf") 73 | 74 | add_executable(${PROJECT_NAME} WIN32 ${SOURCES} ${SHADERS} ${RESOURCES} ${SHADER_RESOURCES}) 75 | target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") 76 | target_compile_definitions(${PROJECT_NAME} PRIVATE IMGUI_DISABLE_OBSOLETE_FUNCTIONS) 77 | target_link_libraries(${PROJECT_NAME} PRIVATE 78 | Corrade::Main 79 | Corrade::Utility 80 | Magnum::Magnum 81 | Magnum::Application # alias to application class being used 82 | Magnum::GL 83 | Magnum::SceneGraph 84 | Magnum::Shaders 85 | Magnum::Trade 86 | Magnum::MeshTools 87 | Magnum::AnySceneImporter 88 | Magnum::AnyImageImporter 89 | Magnum::DebugTools 90 | MagnumPlugins::GltfImporter 91 | MagnumPlugins::StbImageImporter 92 | MagnumIntegration::ImGui 93 | ) 94 | 95 | # warnings 96 | target_include_directories(${PROJECT_NAME} SYSTEM INTERFACE "${PROJECT_SOURCE_DIR}/3rdparty") 97 | if(CORRADE_TARGET_MSVC) 98 | target_compile_options(${PROJECT_NAME} PRIVATE 99 | /wd26812 # The enum type X is unscoped. Prefer 'enum class' over 'enum' 100 | ) 101 | endif() 102 | 103 | # TODO GLSL -> GLSL conversion once implemented in Magnum 104 | if(SHADER_VALIDATION) 105 | find_package(Magnum REQUIRED 106 | AnyShaderConverter 107 | shaderconverter 108 | ) 109 | find_package(MagnumPlugins REQUIRED 110 | GlslangShaderConverter 111 | ) 112 | # need to tell shaderconverter to link against static plugins 113 | # this requires CMake 3.13 since the target was defined in another directory 114 | target_link_libraries(magnum-shaderconverter PRIVATE MagnumPlugins::GlslangShaderConverter Magnum::AnyShaderConverter) 115 | 116 | foreach(SHADER ${SHADERS}) 117 | add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD 118 | # glslang has no validation support for GL_ARB_texture_multisample 119 | # so we need to use GLSL 3.2 (150) which supports sampler2DMSArray 120 | COMMAND Magnum::shaderconverter --validate --input-version "150" --output-version opengl -D VALIDATION "${CMAKE_CURRENT_SOURCE_DIR}/${SHADER}" 121 | ) 122 | endforeach() 123 | endif() 124 | 125 | # link/copy resources folder to binary dir 126 | if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.14.0") 127 | file(CREATE_LINK "${PROJECT_SOURCE_DIR}/resources" "${PROJECT_BINARY_DIR}/resources" COPY_ON_ERROR SYMBOLIC) 128 | else() 129 | file(COPY "${PROJECT_SOURCE_DIR}/resources" DESTINATION "${PROJECT_BINARY_DIR}") 130 | endif() 131 | 132 | # install binary + resources 133 | install(TARGETS ${PROJECT_NAME} DESTINATION "${MAGNUM_DEPLOY_PREFIX}") 134 | install(DIRECTORY 135 | "${PROJECT_SOURCE_DIR}/resources" 136 | DESTINATION "${MAGNUM_DEPLOY_PREFIX}" 137 | ) 138 | 139 | # Make the executable a default target to build & run in Visual Studio 140 | set_property(DIRECTORY "${PROJECT_SOURCE_DIR}" PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME}) 141 | # set debug working directory (default is CMAKE_CURRENT_BINARY_DIR) 142 | set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_BINARY_DIR}") 143 | -------------------------------------------------------------------------------- /src/DefaultMaterial.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class DefaultMaterial : public Magnum::Trade::MaterialData 13 | { 14 | public: 15 | enum : Magnum::UnsignedInt 16 | { 17 | AmbientTextureId = 0u, 18 | DiffuseTextureId, 19 | SpecularTextureId, 20 | NormalTextureId, 21 | TextureCount 22 | }; 23 | 24 | explicit DefaultMaterial() : 25 | MaterialData( 26 | Magnum::Trade::MaterialType::Phong, 27 | { { Magnum::Trade::MaterialAttribute::AmbientTexture, Magnum::UnsignedInt(AmbientTextureId) }, 28 | { Magnum::Trade::MaterialAttribute::DiffuseTexture, Magnum::UnsignedInt(DiffuseTextureId) }, 29 | { Magnum::Trade::MaterialAttribute::SpecularTexture, Magnum::UnsignedInt(SpecularTextureId) }, 30 | { Magnum::Trade::MaterialAttribute::SpecularGlossinessTexture, Magnum::UnsignedInt(SpecularTextureId) }, 31 | { Magnum::Trade::MaterialAttribute::NormalTexture, Magnum::UnsignedInt(NormalTextureId) }, 32 | { Magnum::Trade::MaterialAttribute::Shininess, 80.0f } }) 33 | { 34 | } 35 | 36 | Corrade::Containers::Array createTextures(Magnum::Vector2i size = { 1, 1 }) 37 | { 38 | Corrade::Containers::Array textures(Corrade::NoInit, TextureCount); 39 | // NoInit requires placement new 40 | new(&textures[AmbientTextureId]) Magnum::GL::Texture2D(createTexture(Magnum::Color4(1.0f), size)); 41 | new(&textures[DiffuseTextureId]) Magnum::GL::Texture2D(createTexture(Magnum::Color4(1.0f), size)); 42 | new(&textures[SpecularTextureId]) Magnum::GL::Texture2D(createTexture(Magnum::Color4(1.0f), size)); 43 | new(&textures[NormalTextureId]) Magnum::GL::Texture2D(createTexture(Magnum::Color4(0.5f, 0.5f, 1.0f), size)); 44 | 45 | return textures; 46 | }; 47 | 48 | private: 49 | Magnum::GL::Texture2D createTexture(const Magnum::Color4& color, Magnum::Vector2i size) 50 | { 51 | Corrade::Containers::Array data( 52 | Corrade::DirectInit, size.product(), Magnum::Math::pack(color)); 53 | Magnum::ImageView2D image { Magnum::PixelFormat::RGBA8Unorm, size, data }; 54 | 55 | Magnum::GL::Texture2D texture; 56 | 57 | texture.setStorage(1, Magnum::GL::TextureFormat::RGBA8, size); 58 | texture.setSubImage(0, { 0, 0 }, image); 59 | 60 | return texture; 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /src/Drawables/InstanceDrawable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | template 15 | class InstanceDrawable : public Magnum::SceneGraph::Drawable3D 16 | { 17 | public: 18 | typedef Magnum::SceneGraph::AbstractObject Object; 19 | 20 | struct InstanceData 21 | { 22 | Magnum::Matrix4 transformationMatrix; 23 | Magnum::Matrix3 normalMatrix; 24 | Magnum::Color4 color; 25 | }; 26 | 27 | typedef Corrade::Containers::Array InstanceArray; 28 | 29 | explicit InstanceDrawable(Object& object, InstanceArray& instanceData) : 30 | Magnum::SceneGraph::Drawable3D(object), instanceData(instanceData) 31 | { 32 | } 33 | 34 | void setColor(const Magnum::Color4& newColor) 35 | { 36 | color = newColor; 37 | } 38 | 39 | static Magnum::GL::Buffer addInstancedBuffer(Magnum::GL::Mesh& mesh) 40 | { 41 | Magnum::GL::Buffer instanceBuffer(Magnum::GL::Buffer::TargetHint::Array); 42 | mesh.addVertexBufferInstanced(instanceBuffer, 43 | 1, // divisor 44 | 0, // offset 45 | Magnum::Shaders::GenericGL3D::TransformationMatrix(), 46 | Magnum::Shaders::GenericGL3D::NormalMatrix(), 47 | Magnum::Shaders::GenericGL3D::Color4()); 48 | return Magnum::GL::Buffer(std::move(instanceBuffer)); 49 | } 50 | 51 | protected: 52 | virtual void draw(const Magnum::Matrix4& transformationMatrix, Magnum::SceneGraph::Camera3D& /* camera */) override 53 | { 54 | Corrade::Containers::arrayAppend(instanceData, 55 | { transformationMatrix, transformationMatrix.normalMatrix(), color }); 56 | } 57 | 58 | Magnum::Color4 color = { 1.0f, 1.0f, 1.0f, 1.0f }; 59 | 60 | InstanceArray& instanceData; 61 | }; 62 | -------------------------------------------------------------------------------- /src/Drawables/TexturedDrawable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Drawables/InstanceDrawable.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | //#include 18 | 19 | template 20 | class TexturedDrawable : public Magnum::SceneGraph::Drawable3D 21 | { 22 | public: 23 | typedef Magnum::SceneGraph::AbstractObject Object; 24 | 25 | explicit TexturedDrawable( 26 | Object& object, 27 | Magnum::Shaders::PhongGL& shader, 28 | Magnum::UnsignedInt meshId, 29 | Magnum::GL::Mesh& mesh, 30 | Magnum::GL::Buffer& instanceBuffer, 31 | Corrade::Containers::ArrayView> textures, 32 | const Magnum::Trade::PhongMaterialData& material, 33 | Magnum::Float shininess) : 34 | Magnum::SceneGraph::Drawable3D(object), 35 | shader(shader), 36 | _meshId(meshId), 37 | _mesh(mesh), 38 | instanceBuffer(instanceBuffer), 39 | material(material), 40 | shininess(shininess) 41 | { 42 | ambientTexture = diffuseTexture = specularTexture = normalTexture = nullptr; 43 | 44 | CORRADE_ASSERT(isCompatibleMaterial(material, shader), "Material missing required textures", ); 45 | 46 | if(material.hasAttribute(Magnum::Trade::MaterialAttribute::AmbientTexture)) 47 | ambientTexture = textures[material.ambientTexture()].get(); 48 | if(material.hasAttribute(Magnum::Trade::MaterialAttribute::DiffuseTexture)) 49 | diffuseTexture = textures[material.diffuseTexture()].get(); 50 | if(material.hasAttribute(Magnum::Trade::MaterialAttribute::SpecularTexture) || 51 | material.hasAttribute(Magnum::Trade::MaterialAttribute::SpecularGlossinessTexture)) 52 | specularTexture = textures[material.specularTexture()].get(); 53 | if(material.hasAttribute(Magnum::Trade::MaterialAttribute::NormalTexture)) 54 | normalTexture = textures[material.normalTexture()].get(); 55 | } 56 | 57 | Magnum::UnsignedInt meshId() const 58 | { 59 | return _meshId; 60 | } 61 | 62 | InstanceDrawable& addInstance(Object& object) 63 | { 64 | // template is required so the compiler knows we don't mean less-than 65 | InstanceDrawable& instance = object.template addFeature>(instanceData); 66 | instanceDrawables.add(instance); 67 | return instance; 68 | } 69 | 70 | Magnum::SceneGraph::DrawableGroup3D& instances() 71 | { 72 | return instanceDrawables; 73 | } 74 | 75 | static bool isCompatibleMaterial(const Magnum::Trade::PhongMaterialData& material, 76 | const Magnum::Shaders::PhongGL& shader) 77 | { 78 | const std::pair combinations[] = { 79 | { Magnum::Shaders::PhongGL::Flag::AmbientTexture, Magnum::Trade::MaterialAttribute::AmbientTexture }, 80 | { Magnum::Shaders::PhongGL::Flag::DiffuseTexture, Magnum::Trade::MaterialAttribute::DiffuseTexture }, 81 | // TODO make this more generic, this only works for the GLTF test meshes 82 | { Magnum::Shaders::PhongGL::Flag::SpecularTexture, 83 | Magnum::Trade::MaterialAttribute::SpecularGlossinessTexture }, 84 | { Magnum::Shaders::PhongGL::Flag::NormalTexture, Magnum::Trade::MaterialAttribute::NormalTexture } 85 | }; 86 | 87 | for(const auto& combination : combinations) 88 | { 89 | if((shader.flags() & combination.first) && !material.hasAttribute(combination.second)) 90 | return false; 91 | } 92 | 93 | return true; 94 | } 95 | 96 | private: 97 | virtual void draw(const Magnum::Matrix4& /*transformationMatrix*/, Magnum::SceneGraph::Camera3D& camera) override 98 | { 99 | if(instanceDrawables.isEmpty()) 100 | return; 101 | 102 | /* 103 | // sort objects back to front for correct alpha blending 104 | // not needed currently since we add instances in our scene in the correct order and the camera position is static 105 | std::vector, Matrix4>> drawableTransformations = 106 | camera.drawableTransformations(instanceDrawables); 107 | 108 | std::sort(drawableTransformations.begin(), 109 | drawableTransformations.end(), 110 | [](const std::pair, Matrix4>& a, 111 | const std::pair, Matrix4>& b) { 112 | return a.second.translation().z() < b.second.translation().z(); 113 | }); 114 | */ 115 | 116 | Corrade::Containers::arrayResize(instanceData, 0); 117 | camera.draw(instanceDrawables /*drawableTransformations*/); 118 | 119 | instanceBuffer.setData(instanceData, Magnum::GL::BufferUsage::DynamicDraw); 120 | _mesh.setInstanceCount(instanceData.size()); 121 | 122 | if(ambientTexture || diffuseTexture || specularTexture || normalTexture) 123 | shader.bindTextures(ambientTexture, diffuseTexture, specularTexture, normalTexture); 124 | 125 | shader 126 | // we override the material shininess because for imported GLTF it's always 80 127 | .setShininess(shininess) 128 | // make sure ambient alpha is 1.0 since it gets multiplied with instanced vertex color 129 | .setAmbientColor({ material.ambientColor().rgb(), 1.0f }) 130 | // diffuse and specular colors have no alpha so when the light influences are added up, alpha is unaffected 131 | // this isn't ideal since specular highlights should increase opacity, but with the built-in Phong shader 132 | // the alpha added from a single strong light makes the entire object completely opaque 133 | // TODO maybe this is fixable with some light value tweaks 134 | .setDiffuseColor({ material.diffuseColor().rgb(), 0.0f }) 135 | .setSpecularColor({ material.specularColor().rgb(), 0.0f }) 136 | .setProjectionMatrix(camera.projectionMatrix()); 137 | 138 | shader.draw(_mesh); 139 | } 140 | 141 | Magnum::Shaders::PhongGL& shader; 142 | Magnum::UnsignedInt _meshId; 143 | Magnum::GL::Mesh& _mesh; 144 | Magnum::GL::Buffer& instanceBuffer; 145 | const Magnum::Trade::PhongMaterialData& material; 146 | const Magnum::Float shininess; 147 | 148 | Magnum::GL::Texture2D *ambientTexture, *diffuseTexture, *specularTexture, *normalTexture; 149 | 150 | Magnum::SceneGraph::DrawableGroup3D instanceDrawables; 151 | typename InstanceDrawable::InstanceArray instanceData; 152 | }; 153 | -------------------------------------------------------------------------------- /src/Drawables/VelocityDrawable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Drawables/VelocityInstanceDrawable.h" 4 | #include "Shaders/VelocityShader.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | template 11 | class VelocityDrawable : public Magnum::SceneGraph::Drawable3D 12 | { 13 | public: 14 | typedef Magnum::SceneGraph::AbstractObject Object; 15 | 16 | explicit VelocityDrawable(Object& object, 17 | VelocityShader& shader, 18 | Magnum::UnsignedInt meshId, 19 | Magnum::GL::Mesh& mesh, 20 | Magnum::GL::Buffer& instanceBuffer) : 21 | Magnum::SceneGraph::Drawable3D(object), 22 | shader(shader), 23 | _meshId(meshId), 24 | _mesh(mesh), 25 | instanceBuffer(instanceBuffer) 26 | { 27 | } 28 | 29 | Magnum::UnsignedInt meshId() const 30 | { 31 | return _meshId; 32 | } 33 | 34 | VelocityInstanceDrawable& addInstance(Object& object) 35 | { 36 | VelocityInstanceDrawable& instance = 37 | object.template addFeature>(instanceData); 38 | instanceDrawables.add(instance); 39 | return instance; 40 | } 41 | 42 | Magnum::SceneGraph::DrawableGroup3D& instances() 43 | { 44 | return instanceDrawables; 45 | } 46 | 47 | private: 48 | virtual void draw(const Magnum::Matrix4& /* transformationMatrix */, Magnum::SceneGraph::Camera3D& camera) override 49 | { 50 | if(instanceDrawables.isEmpty()) 51 | return; 52 | 53 | Corrade::Containers::arrayResize(instanceData, 0); 54 | camera.draw(instanceDrawables); 55 | 56 | instanceBuffer.setData(instanceData, Magnum::GL::BufferUsage::DynamicDraw); 57 | _mesh.setInstanceCount(instanceData.size()); 58 | 59 | shader.draw(_mesh); 60 | } 61 | 62 | VelocityShader& shader; 63 | Magnum::UnsignedInt _meshId; 64 | Magnum::GL::Mesh& _mesh; 65 | Magnum::GL::Buffer& instanceBuffer; 66 | 67 | Magnum::SceneGraph::DrawableGroup3D instanceDrawables; 68 | typename VelocityInstanceDrawable::InstanceArray instanceData; 69 | }; 70 | -------------------------------------------------------------------------------- /src/Drawables/VelocityInstanceDrawable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Shaders/VelocityShader.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | template 13 | class VelocityInstanceDrawable : public Magnum::SceneGraph::Drawable3D 14 | { 15 | public: 16 | typedef Magnum::SceneGraph::AbstractObject Object; 17 | 18 | struct InstanceData 19 | { 20 | Magnum::Matrix4 transformationMatrix; 21 | Magnum::Matrix4 oldTransformationMatrix; 22 | }; 23 | 24 | typedef Corrade::Containers::Array InstanceArray; 25 | 26 | explicit VelocityInstanceDrawable(Object& object, InstanceArray& instanceData) : 27 | Magnum::SceneGraph::Drawable3D(object), 28 | oldTransformation(Magnum::Math::IdentityInit), 29 | instanceData(instanceData) 30 | { 31 | } 32 | 33 | static Magnum::GL::Buffer addInstancedBuffer(Magnum::GL::Mesh& mesh) 34 | { 35 | Magnum::GL::Buffer instanceBuffer(Magnum::GL::Buffer::TargetHint::Array); 36 | mesh.addVertexBufferInstanced(instanceBuffer, 37 | 1, // divisor 38 | 0, // offset 39 | VelocityShader::TransformationMatrix(), 40 | VelocityShader::OldTransformationMatrix()); 41 | return Magnum::GL::Buffer(std::move(instanceBuffer)); 42 | } 43 | 44 | protected: 45 | virtual void draw(const Magnum::Matrix4& transformationMatrix, Magnum::SceneGraph::Camera3D& /* camera */) override 46 | { 47 | Corrade::Containers::arrayAppend(instanceData, { transformationMatrix, oldTransformation }); 48 | oldTransformation = transformationMatrix; 49 | } 50 | 51 | Magnum::Matrix4 oldTransformation; 52 | 53 | InstanceArray& instanceData; 54 | }; 55 | -------------------------------------------------------------------------------- /src/Feature.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Feature 8 | { 9 | template using FeatureList = Corrade::Containers::Array; 10 | 11 | // find first feature of the specified type 12 | template 13 | F* feature(Magnum::SceneGraph::Object& object) 14 | { 15 | for(auto& candidate : object.features()) 16 | { 17 | F* feature = dynamic_cast(&candidate); 18 | if(feature) 19 | { 20 | return feature; 21 | } 22 | } 23 | return nullptr; 24 | } 25 | 26 | // find all features of the specified type 27 | template 28 | FeatureList features(Magnum::SceneGraph::Object& object) 29 | { 30 | FeatureList result; 31 | for(auto& candidate : object.features()) 32 | { 33 | F* feature = dynamic_cast(&candidate); 34 | if(feature) 35 | { 36 | Corrade::Containers::arrayAppend(result, feature); 37 | } 38 | } 39 | return result; 40 | } 41 | 42 | // find all features of the specified type in any direct or indirect children 43 | template 44 | FeatureList featuresInChildren(Magnum::SceneGraph::Object& object, FeatureList* result = nullptr) 45 | { 46 | FeatureList storage; 47 | if(result == nullptr) 48 | { 49 | result = &storage; 50 | } 51 | else 52 | { 53 | for(auto& candidate : object.features()) 54 | { 55 | F* feature = dynamic_cast(&candidate); 56 | if(feature) 57 | { 58 | Corrade::Containers::arrayAppend(*result, feature); 59 | } 60 | } 61 | } 62 | 63 | for(auto& child : object.children()) 64 | { 65 | featuresInChildren(child, result); 66 | } 67 | 68 | return storage; 69 | } 70 | 71 | // find all features of the specified type in any direct or indirect parent 72 | template 73 | FeatureList featuresInParents(Magnum::SceneGraph::Object& object, FeatureList* result = nullptr) 74 | { 75 | FeatureList storage; 76 | if(result == nullptr) 77 | { 78 | result = &storage; 79 | } 80 | else 81 | { 82 | for(auto& candidate : object.features()) 83 | { 84 | F* feature = dynamic_cast(&candidate); 85 | if(feature) 86 | { 87 | Corrade::Containers::arrayAppend(*result, feature); 88 | } 89 | } 90 | } 91 | Magnum::SceneGraph::Object* parent = object.parent(); 92 | if(parent != nullptr) 93 | { 94 | featuresInParents(object.parent(), result); 95 | } 96 | return storage; 97 | } 98 | } // namespace Feature 99 | -------------------------------------------------------------------------------- /src/ImGuiApplication.cpp: -------------------------------------------------------------------------------- 1 | #include "ImGuiApplication.h" 2 | 3 | #include 4 | #include 5 | 6 | using namespace Magnum; 7 | 8 | ImGuiApplication::ImGuiApplication(const Arguments& arguments, 9 | const Configuration& configuration, 10 | const GLConfiguration& glConfiguration) : 11 | Magnum::Platform::Application(arguments, configuration, glConfiguration), imgui(Magnum::NoCreate) 12 | { 13 | init(); 14 | } 15 | 16 | ImGuiApplication::ImGuiApplication(const Arguments& arguments, NoCreateT) : 17 | Magnum::Platform::Application(arguments, NoCreate), imgui(Magnum::NoCreate) 18 | { 19 | } 20 | 21 | ImGuiApplication::~ImGuiApplication() 22 | { 23 | imgui.release(); 24 | ImGui::DestroyContext(); 25 | } 26 | 27 | void ImGuiApplication::create(const Configuration& configuration, const GLConfiguration& glConfiguration) 28 | { 29 | Platform::Application::create(configuration, glConfiguration); 30 | init(); 31 | } 32 | 33 | bool ImGuiApplication::tryCreate(const Configuration& configuration, const GLConfiguration& glConfiguration) 34 | { 35 | if(Platform::Application::tryCreate(configuration, glConfiguration)) 36 | { 37 | init(); 38 | return true; 39 | } 40 | return false; 41 | } 42 | 43 | void ImGuiApplication::init() 44 | { 45 | ImGui::CreateContext(); 46 | // we can't call an overridden method from the base class constructor. 47 | // any configuration in the derived class must happen after the base 48 | // class constructor. that means the (default) font atlas is created 49 | // and uploaded, and then later created again after setFont. 50 | // not a massive deal, but one workaround would be creating the 51 | // implementation instance in the first drawEvent. 52 | imgui = Magnum::ImGuiIntegration::Context(*ImGui::GetCurrentContext(), uiSize(), windowSize(), framebufferSize()); 53 | } 54 | 55 | void ImGuiApplication::drawEvent() 56 | { 57 | imgui.newFrame(); 58 | 59 | // enable text input, if needed (shows screen keyboard on some platforms) 60 | if(ImGui::GetIO().WantTextInput && !isTextInputActive()) 61 | startTextInput(); 62 | else if(!ImGui::GetIO().WantTextInput && isTextInputActive()) 63 | stopTextInput(); 64 | 65 | buildUI(); 66 | 67 | imgui.updateApplicationCursor(*this); 68 | 69 | // state required for imgui rendering 70 | // alpha blending, scissor, no culling, no depth test 71 | 72 | GL::Renderer::setBlendEquation(GL::Renderer::BlendEquation::Add, GL::Renderer::BlendEquation::Add); 73 | GL::Renderer::setBlendFunction(GL::Renderer::BlendFunction::SourceAlpha, 74 | GL::Renderer::BlendFunction::OneMinusSourceAlpha); 75 | 76 | GL::Renderer::enable(GL::Renderer::Feature::Blending); 77 | GL::Renderer::enable(GL::Renderer::Feature::ScissorTest); 78 | GL::Renderer::disable(GL::Renderer::Feature::FaceCulling); 79 | GL::Renderer::disable(GL::Renderer::Feature::DepthTest); 80 | 81 | imgui.drawFrame(); 82 | 83 | GL::Renderer::enable(GL::Renderer::Feature::DepthTest); 84 | GL::Renderer::enable(GL::Renderer::Feature::FaceCulling); 85 | GL::Renderer::disable(GL::Renderer::Feature::ScissorTest); 86 | GL::Renderer::disable(GL::Renderer::Feature::Blending); 87 | } 88 | 89 | // this is not necessarily called for DPI changes (at least not on Windows). 90 | // app might have to be restarted to reflect new settings. 91 | void ImGuiApplication::viewportEvent(Magnum::Platform::Application::ViewportEvent& event) 92 | { 93 | GL::defaultFramebuffer.setViewport({ { 0, 0 }, event.framebufferSize() }); 94 | 95 | // TODO 96 | // if the pixel density changed, we should recreate the fonts 97 | // need to store font data internally for this to work 98 | // or get the info from a virtual function callback 99 | 100 | imgui.relayout(uiSize(), event.windowSize(), event.framebufferSize()); 101 | } 102 | 103 | void ImGuiApplication::keyPressEvent(KeyEvent& event) 104 | { 105 | if(imgui.handleKeyPressEvent(event)) 106 | event.setAccepted(); 107 | } 108 | 109 | void ImGuiApplication::keyReleaseEvent(KeyEvent& event) 110 | { 111 | if(imgui.handleKeyReleaseEvent(event)) 112 | event.setAccepted(); 113 | } 114 | 115 | void ImGuiApplication::mousePressEvent(MouseEvent& event) 116 | { 117 | if(imgui.handleMousePressEvent(event)) 118 | event.setAccepted(); 119 | } 120 | 121 | void ImGuiApplication::mouseReleaseEvent(MouseEvent& event) 122 | { 123 | if(imgui.handleMouseReleaseEvent(event)) 124 | event.setAccepted(); 125 | } 126 | 127 | void ImGuiApplication::mouseMoveEvent(MouseMoveEvent& event) 128 | { 129 | if(imgui.handleMouseMoveEvent(event)) 130 | event.setAccepted(); 131 | } 132 | 133 | void ImGuiApplication::mouseScrollEvent(MouseScrollEvent& event) 134 | { 135 | if(imgui.handleMouseScrollEvent(event)) 136 | event.setAccepted(); 137 | } 138 | 139 | void ImGuiApplication::textInputEvent(TextInputEvent& event) 140 | { 141 | if(imgui.handleTextInputEvent(event)) 142 | event.setAccepted(); 143 | } 144 | 145 | Magnum::Vector2 ImGuiApplication::uiSize() const 146 | { 147 | return Magnum::Vector2(windowSize()) / dpiScaling(); 148 | } 149 | 150 | void ImGuiApplication::setFont(const char* fontFile, float pixels) 151 | { 152 | ImGuiIO& io = ImGui::GetIO(); 153 | io.Fonts->Clear(); 154 | ImFontConfig fontConfig; 155 | fontConfig.GlyphRanges = io.Fonts->GetGlyphRangesDefault(); 156 | io.Fonts->AddFontFromFileTTF(fontFile, pixels * framebufferSize().x() / uiSize().x(), &fontConfig); 157 | // update font atlas 158 | imgui.relayout(uiSize(), windowSize(), framebufferSize()); 159 | } 160 | 161 | void ImGuiApplication::setFont(const void* fontData, size_t dataSize, float pixels) 162 | { 163 | ImGuiIO& io = ImGui::GetIO(); 164 | io.Fonts->Clear(); 165 | ImFontConfig fontConfig; 166 | fontConfig.GlyphRanges = io.Fonts->GetGlyphRangesDefault(); 167 | fontConfig.FontDataOwnedByAtlas = false; 168 | io.Fonts->AddFontFromMemoryTTF( 169 | const_cast(fontData), int(dataSize), pixels * framebufferSize().x() / uiSize().x(), &fontConfig); 170 | // update font atlas 171 | imgui.relayout(uiSize(), windowSize(), framebufferSize()); 172 | } 173 | -------------------------------------------------------------------------------- /src/ImGuiApplication.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class ImGuiApplication : public Magnum::Platform::Application 7 | { 8 | public: 9 | explicit ImGuiApplication(const Arguments& arguments, 10 | const Configuration& configuration = Configuration(), 11 | const GLConfiguration& glConfiguration = GLConfiguration()); 12 | explicit ImGuiApplication(const Arguments& arguments, Magnum::NoCreateT); 13 | ~ImGuiApplication(); 14 | 15 | void create(const Configuration& configuration = Configuration(), 16 | const GLConfiguration& glConfiguration = GLConfiguration()); 17 | bool tryCreate(const Configuration& configuration, const GLConfiguration& glConfiguration = GLConfiguration()); 18 | 19 | protected: 20 | // user interface size (used for widget positioning) 21 | Magnum::Vector2 uiSize() const; 22 | // convenience function that sets the GUI font 23 | // removes all other fonts and builds the font atlas 24 | void setFont(const char* fontFile, float pixels); 25 | void setFont(const void* fontData, size_t dataSize, float pixels); 26 | 27 | // override and build your UI with ImGui:: calls 28 | // called each frame 29 | virtual void buildUI() = 0; 30 | 31 | // call this at the end of your derived application's drawEvent() 32 | virtual void drawEvent() override; 33 | 34 | virtual void viewportEvent(ViewportEvent& event) override; 35 | 36 | // if you override these, make sure to call the base class 37 | // implementation first and check if the event was already 38 | // handled using event.isAccepted() 39 | virtual void keyPressEvent(KeyEvent& event) override; 40 | virtual void keyReleaseEvent(KeyEvent& event) override; 41 | virtual void mousePressEvent(MouseEvent& event) override; 42 | virtual void mouseReleaseEvent(MouseEvent& event) override; 43 | virtual void mouseMoveEvent(MouseMoveEvent& event) override; 44 | virtual void mouseScrollEvent(MouseScrollEvent& event) override; 45 | virtual void textInputEvent(TextInputEvent& event) override; 46 | 47 | private: 48 | void init(); 49 | 50 | Magnum::ImGuiIntegration::Context imgui; 51 | }; 52 | -------------------------------------------------------------------------------- /src/Mosaiikki.cpp: -------------------------------------------------------------------------------- 1 | #include "Mosaiikki.h" 2 | 3 | #include "Scene.h" 4 | #include "Feature.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | using namespace Magnum; 20 | using namespace Corrade; 21 | using namespace Magnum::Math::Literals; 22 | using namespace Feature; 23 | 24 | const char* Mosaiikki::NAME = "mosaiikki"; 25 | 26 | Mosaiikki::Mosaiikki(const Arguments& arguments) : 27 | ImGuiApplication(arguments, NoCreate), 28 | logFile(std::string(NAME) + ".log", std::fstream::out | std::fstream::trunc), 29 | fullscreenTriangle(NoCreate), 30 | velocityFramebuffer(NoCreate), 31 | velocityAttachment(NoCreate), 32 | velocityDepthAttachment(NoCreate), 33 | framebuffers { GL::Framebuffer(NoCreate), GL::Framebuffer(NoCreate) }, 34 | colorAttachments(NoCreate), 35 | depthAttachments(NoCreate), 36 | depthBlitShader(NoCreate), 37 | outputFramebuffer(NoCreate), 38 | outputColorAttachment(NoCreate), 39 | reconstructionShader(NoCreate) 40 | { 41 | // Redirect log to file 42 | 43 | if(logFile.good()) 44 | { 45 | _debug = Corrade::Containers::pointer(&logFile, Utility::Debug::Flag::NoSpace); 46 | _warning = Corrade::Containers::pointer(&logFile, Utility::Debug::Flag::NoSpace); 47 | _error = Corrade::Containers::pointer(&logFile, Utility::Debug::Flag::NoSpace); 48 | } 49 | 50 | // Configuration and GL context 51 | 52 | Configuration conf; 53 | conf.setSize({ 800, 600 }); 54 | conf.setTitle(NAME); 55 | conf.setWindowFlags(Configuration::WindowFlag::Resizable); 56 | 57 | constexpr GL::Version GLVersion = GL::Version::GL320; 58 | 59 | GLConfiguration glConf; 60 | // for anything >= 3.20 Magnum creates a core context 61 | // if we don't set a version on Nvidia drivers, we get the highest version possible, but a compatibility context :( 62 | // forward-compatible removes anything deprecated 63 | glConf.setVersion(GLVersion); 64 | glConf.addFlags(GLConfiguration::Flag::ForwardCompatible); 65 | #ifdef CORRADE_IS_DEBUG_BUILD 66 | glConf.addFlags(GLConfiguration::Flag::Debug); 67 | #endif 68 | create(conf, glConf); 69 | 70 | #ifndef CORRADE_IS_DEBUG_BUILD 71 | setSwapInterval(0); // disable v-sync 72 | #endif 73 | 74 | MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::explicit_attrib_location); // core in 3.3 75 | MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::sample_shading); // core in 4.0 76 | MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::texture_multisample); // core in 3.2 77 | MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::uniform_buffer_object); // core in 3.1 78 | 79 | GL::Renderer::enable(GL::Renderer::Feature::Multisampling); 80 | 81 | // Debug output 82 | 83 | profiler.setup(DebugTools::FrameProfilerGL::Value::FrameTime | DebugTools::FrameProfilerGL::Value::GpuDuration, 60); 84 | 85 | #ifdef CORRADE_IS_DEBUG_BUILD 86 | GL::Renderer::enable(GL::Renderer::Feature::DebugOutput); 87 | // output debug messages on the same thread as the GL commands 88 | // this fixes issues with Corrade::Debug having per-thread output streams 89 | GL::Renderer::enable(GL::Renderer::Feature::DebugOutputSynchronous); 90 | // redirect debug messages to Corrade::Debug 91 | GL::DebugOutput::setDefaultCallback(); 92 | // disable unimportant output 93 | // markers and groups are only used for RenderDoc 94 | GL::DebugOutput::setEnabled(GL::DebugOutput::Source::Application, GL::DebugOutput::Type::Marker, false); 95 | GL::DebugOutput::setEnabled(GL::DebugOutput::Source::Application, GL::DebugOutput::Type::PushGroup, false); 96 | GL::DebugOutput::setEnabled(GL::DebugOutput::Source::Application, GL::DebugOutput::Type::PopGroup, false); 97 | // Nvidia drivers: "Buffer detailed info" 98 | GL::DebugOutput::setEnabled(GL::DebugOutput::Source::Api, GL::DebugOutput::Type::Other, { 131185 }, false); 99 | #endif 100 | 101 | // UI 102 | 103 | ImGui::StyleColorsDark(); 104 | 105 | Utility::Resource rs("resources"); 106 | Containers::ArrayView font = rs.getRaw("fonts/Roboto-Regular.ttf"); 107 | setFont(font.data(), font.size(), 15.0f); 108 | 109 | // Framebuffers 110 | 111 | resizeFramebuffers(framebufferSize()); 112 | 113 | setSamplePositions(); 114 | 115 | // Shaders 116 | 117 | depthBlitShader = DepthBlitShader(); 118 | depthBlitShader.setLabel("Depth blit shader"); 119 | 120 | ReconstructionShader::Flags reconstructionFlags = ReconstructionShader::Flags() 121 | #ifdef CORRADE_IS_DEBUG_BUILD 122 | | ReconstructionShader::Flag::Debug 123 | #endif 124 | ; 125 | reconstructionShader = ReconstructionShader(reconstructionFlags); 126 | reconstructionShader.setLabel("Checkerboard resolve shader"); 127 | 128 | // Scene 129 | 130 | scene.emplace(); 131 | 132 | for(Containers::Pointer& texture : scene->textures) 133 | { 134 | if(texture) 135 | { 136 | // LOD calculation is something roughly equivalent to: log2(max(len(dFdx(uv)), len(dFdy(uv))) 137 | // halving the rendering width/height doubles the derivate length 138 | // after upsampling, textures would become blurry compared to full-resolution rendering 139 | // so offset LOD to lower mip level to full resolution equivalent (log2(sqrt(2)) = 0.5) 140 | texture->setLodBias(-0.5f); 141 | } 142 | } 143 | 144 | scene->camera->setViewport(framebufferSize()); 145 | updateProjectionMatrix(*scene->camera); 146 | 147 | CORRADE_INTERNAL_CONSTEXPR_ASSERT(GLVersion >= GL::Version::GL300); 148 | fullscreenTriangle = MeshTools::fullScreenTriangle(GLVersion); 149 | 150 | timeline.start(); 151 | } 152 | 153 | void Mosaiikki::updateProjectionMatrix(Magnum::SceneGraph::Camera3D& cam) 154 | { 155 | //constexpr Rad hFOV_4by3 = 90.0_degf; 156 | //Rad vFOV = Math::atan(Math::tan(hFOV_4by3 * 0.5f) / (4.0f / 3.0f)) * 2.0f; 157 | constexpr Rad vFOV = 73.74_degf; 158 | 159 | float aspectRatio = Vector2(cam.viewport()).aspectRatio(); 160 | Rad hFOV = Math::atan(Math::tan(vFOV * 0.5f) * aspectRatio) * 2.0f; 161 | cam.setProjectionMatrix(Matrix4::perspectiveProjection(hFOV, aspectRatio, scene->cameraNear, scene->cameraFar)); 162 | } 163 | 164 | void Mosaiikki::resizeFramebuffers(Vector2i size) 165 | { 166 | // make texture dimensions multiple of two 167 | size += size % 2; 168 | 169 | // xy = velocity, z = mask for dynamic objects 170 | velocityAttachment = GL::Texture2D(); 171 | velocityAttachment.setStorage(1, GL::TextureFormat::RGBA16F, size); 172 | velocityAttachment.setLabel("Velocity texture"); 173 | velocityDepthAttachment = GL::Texture2D(); 174 | velocityDepthAttachment.setStorage(1, GL::TextureFormat::DepthComponent24, size); 175 | velocityDepthAttachment.setLabel("Velocity depth texture"); 176 | 177 | velocityFramebuffer = GL::Framebuffer({ { 0, 0 }, size }); 178 | velocityFramebuffer.attachTexture(GL::Framebuffer::ColorAttachment(0), velocityAttachment, 0 /* level */); 179 | velocityFramebuffer.attachTexture(GL::Framebuffer::BufferAttachment::Depth, velocityDepthAttachment, 0 /* level */); 180 | velocityFramebuffer.mapForDraw({ { VelocityShader::VelocityOutput, GL::Framebuffer::ColorAttachment(0) } }); 181 | velocityFramebuffer.setLabel("Velocity framebuffer"); 182 | 183 | CORRADE_INTERNAL_ASSERT(velocityFramebuffer.checkStatus(GL::FramebufferTarget::Read) == 184 | GL::Framebuffer::Status::Complete); 185 | CORRADE_INTERNAL_ASSERT(velocityFramebuffer.checkStatus(GL::FramebufferTarget::Draw) == 186 | GL::Framebuffer::Status::Complete); 187 | 188 | const Vector2i quarterSize = size / 2; 189 | const Vector3i arraySize = { quarterSize, FRAMES }; 190 | 191 | colorAttachments = GL::MultisampleTexture2DArray(); 192 | colorAttachments.setStorage(2, GL::TextureFormat::RGBA8, arraySize, GL::MultisampleTextureSampleLocations::Fixed); 193 | colorAttachments.setLabel("Color texture array (quarter-res 2x MSAA)"); 194 | depthAttachments = GL::MultisampleTexture2DArray(); 195 | depthAttachments.setStorage( 196 | 2, GL::TextureFormat::DepthComponent24, arraySize, GL::MultisampleTextureSampleLocations::Fixed); 197 | depthAttachments.setLabel("Depth texture array (quarter-res 2x MSAA)"); 198 | 199 | for(size_t i = 0; i < FRAMES; i++) 200 | { 201 | framebuffers[i] = GL::Framebuffer({ { 0, 0 }, quarterSize }); 202 | framebuffers[i].attachTextureLayer(GL::Framebuffer::ColorAttachment(0), colorAttachments, i /* layer */); 203 | framebuffers[i].attachTextureLayer(GL::Framebuffer::BufferAttachment::Depth, depthAttachments, i /* layer */); 204 | framebuffers[i].mapForDraw({ { Shaders::GenericGL3D::ColorOutput, GL::Framebuffer::ColorAttachment(0) } }); 205 | framebuffers[i].setLabel(Utility::format("Framebuffer {} (quarter-res)", i + 1)); 206 | 207 | CORRADE_INTERNAL_ASSERT(framebuffers[i].checkStatus(GL::FramebufferTarget::Read) == 208 | GL::Framebuffer::Status::Complete); 209 | CORRADE_INTERNAL_ASSERT(framebuffers[i].checkStatus(GL::FramebufferTarget::Draw) == 210 | GL::Framebuffer::Status::Complete); 211 | } 212 | 213 | outputColorAttachment = GL::Texture2D(); 214 | outputColorAttachment.setStorage(1, GL::TextureFormat::RGBA8, size); 215 | // filter and wrapping for zoomed GUI debug output 216 | outputColorAttachment.setMagnificationFilter(SamplerFilter::Nearest); 217 | outputColorAttachment.setWrapping({ GL::SamplerWrapping::ClampToBorder, GL::SamplerWrapping::ClampToBorder }); 218 | outputColorAttachment.setBorderColor(0x000000_rgbf); 219 | outputColorAttachment.setLabel("Output color texture"); 220 | 221 | outputFramebuffer = GL::Framebuffer({ { 0, 0 }, size }); 222 | outputFramebuffer.attachTexture(GL::Framebuffer::ColorAttachment(0), outputColorAttachment, 0 /* level */); 223 | // no depth buffer needed 224 | outputFramebuffer.mapForDraw({ { ReconstructionShader::ColorOutput, GL::Framebuffer::ColorAttachment(0) } }); 225 | outputFramebuffer.setLabel("Output framebuffer"); 226 | 227 | CORRADE_INTERNAL_ASSERT(outputFramebuffer.checkStatus(GL::FramebufferTarget::Read) == 228 | GL::Framebuffer::Status::Complete); 229 | CORRADE_INTERNAL_ASSERT(outputFramebuffer.checkStatus(GL::FramebufferTarget::Draw) == 230 | GL::Framebuffer::Status::Complete); 231 | } 232 | 233 | void Mosaiikki::setSamplePositions() 234 | { 235 | const GLsizei SAMPLE_COUNT = 2; 236 | const Vector2 samplePositions[SAMPLE_COUNT] = { { 0.75f, 0.75f }, { 0.25f, 0.25f } }; 237 | 238 | // set explicit MSAA sample locations 239 | // OpenGL does not specify them, so we have to do it manually using one of three extensions 240 | 241 | // ARB extension is really only supported by Nvidia (Maxwell and later) and requires GL 4.5 242 | bool ext_arb = GL::Context::current().isExtensionSupported(); 243 | bool ext_nv = GL::Context::current().isExtensionSupported(); 244 | bool ext_amd = GL::Context::current().isExtensionSupported(); 245 | // Haven't found an Intel extension although D3D12 support for it exists 246 | 247 | if(!(ext_arb || ext_nv || ext_amd)) 248 | { 249 | // none of the extensions are supported (also happens in RenderDoc which force-disables it) 250 | // warn here instead of aborting because you might have the correct sample positions anyway (which we check below) 251 | // the sample positions we request seem to be the default on Nvidia GPUs 252 | Warning() << "No extension for setting sample positions found!"; 253 | } 254 | 255 | for(size_t frame = 0; frame < FRAMES; frame++) 256 | { 257 | framebuffers[frame].bind(); 258 | 259 | if(ext_arb) 260 | { 261 | int supportedSampleCount = 0; 262 | glGetIntegerv(GL_PROGRAMMABLE_SAMPLE_LOCATION_TABLE_SIZE_ARB, &supportedSampleCount); 263 | CORRADE_INTERNAL_ASSERT(SAMPLE_COUNT <= supportedSampleCount); 264 | 265 | glFramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_PROGRAMMABLE_SAMPLE_LOCATIONS_ARB, GL_TRUE); 266 | glFramebufferSampleLocationsfvARB(GL_FRAMEBUFFER, 0, SAMPLE_COUNT, samplePositions[0].data()); 267 | } 268 | else if(ext_nv) 269 | { 270 | int supportedSampleCount = 0; 271 | glGetIntegerv(GL_PROGRAMMABLE_SAMPLE_LOCATION_TABLE_SIZE_NV, &supportedSampleCount); 272 | CORRADE_INTERNAL_ASSERT(SAMPLE_COUNT <= supportedSampleCount); 273 | 274 | glFramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_PROGRAMMABLE_SAMPLE_LOCATIONS_NV, GL_TRUE); 275 | glFramebufferSampleLocationsfvNV(GL_FRAMEBUFFER, 0, SAMPLE_COUNT, samplePositions[0].data()); 276 | } 277 | else if(ext_amd) 278 | { 279 | for(GLuint i = 0; i < SAMPLE_COUNT; i++) 280 | { 281 | glSetMultisamplefvAMD(GL_SAMPLE_POSITION, i, samplePositions[i].data()); 282 | } 283 | } 284 | } 285 | 286 | // read back, report and check actual sample locations 287 | 288 | bool mismatch = false; 289 | Debug(Debug::Flag::NoSpace) << "MSAA " << SAMPLE_COUNT << "x sample positions:"; 290 | for(GLuint i = 0; i < SAMPLE_COUNT; i++) 291 | { 292 | Vector2 position; 293 | // GL_SAMPLE_POSITION reads the default sample location with ARB/NV_sample_locations 294 | GLenum name = ext_arb ? GL_PROGRAMMABLE_SAMPLE_LOCATION_ARB 295 | : (ext_nv ? GL_PROGRAMMABLE_SAMPLE_LOCATION_NV : GL_SAMPLE_POSITION); 296 | glGetMultisamplefv(name, i, position.data()); 297 | Debug(Debug::Flag::NoSpace) << i << ": " << position; 298 | 299 | // positions can be quantized, so only do a rough comparison 300 | // we can query the quantization amount (SUBSAMPLE_DISTANCE_AMD and SAMPLE_LOCATION_SUBPIXEL_BITS_NV) 301 | // but we're only interested in an acceptable absolute error anyway 302 | constexpr float allowedError = 1.0f / 8.0f; 303 | if((Math::abs(position - samplePositions[i]) > Vector2(allowedError)).any()) 304 | { 305 | mismatch = true; 306 | } 307 | } 308 | 309 | if(mismatch) 310 | Error() << "Wrong sample positions, output will likely be incorrect!"; 311 | 312 | GL::defaultFramebuffer.bind(); 313 | } 314 | 315 | void Mosaiikki::drawEvent() 316 | { 317 | profiler.beginFrame(); 318 | 319 | if(!paused || advanceOneFrame) 320 | { 321 | advanceOneFrame = false; 322 | 323 | scene->meshAnimables.step(timeline.previousFrameTime(), timeline.previousFrameDuration()); 324 | scene->cameraAnimables.step(timeline.previousFrameTime(), timeline.previousFrameDuration()); 325 | 326 | constexpr GL::Renderer::DepthFunction depthFunction = GL::Renderer::DepthFunction::LessOrEqual; // default: Less 327 | 328 | GL::Renderer::enable(GL::Renderer::Feature::FaceCulling); 329 | GL::Renderer::enable(GL::Renderer::Feature::DepthTest); 330 | GL::Renderer::setDepthFunction(depthFunction); 331 | 332 | GL::Renderer::disable(GL::Renderer::Feature::Blending); 333 | GL::Renderer::setBlendEquation(GL::Renderer::BlendEquation::Add, GL::Renderer::BlendEquation::Add); 334 | GL::Renderer::setBlendFunction(GL::Renderer::BlendFunction::SourceAlpha, 335 | GL::Renderer::BlendFunction::OneMinusSourceAlpha); 336 | 337 | static Containers::StaticArray oldMatrices = { Matrix4(Math::IdentityInit), 338 | Matrix4(Math::IdentityInit) }; 339 | Containers::StaticArray matrices; 340 | 341 | // jitter viewport half a pixel to the right = one pixel in the full-res framebuffer 342 | // = width of NDC divided by full-res pixel count 343 | const Matrix4 unjitteredProjection = scene->camera->projectionMatrix(); 344 | const float offset = 2.0f / scene->camera->viewport().x(); 345 | matrices[JITTERED_FRAME] = Matrix4::translation(Vector3::xAxis(offset)) * unjitteredProjection; 346 | matrices[1 - JITTERED_FRAME] = unjitteredProjection; 347 | 348 | // fill velocity buffer 349 | 350 | if(options.reconstruction.createVelocityBuffer) 351 | { 352 | GL::DebugGroup group(GL::DebugGroup::Source::Application, 0, "Velocity buffer"); 353 | 354 | velocityFramebuffer.bind(); 355 | velocityFramebuffer.clearColor(0, 0_rgbf); 356 | velocityFramebuffer.clearDepth(1.0f); 357 | 358 | // dynamic objects only 359 | // camera velocity for static objects is calculated with reprojection in the checkerboard resolve pass 360 | if(scene->meshAnimables.runningCount() > 0) 361 | { 362 | // offset depth for the depth blit, otherwise the depth test might fail in the quarter-res pass 363 | // not entirely sure what causes this, could be floating point inaccuracy? 364 | // slope bias allows an offset based on triangle depth gradient, 365 | // without it we'd need to use a larger constant bias and pray it works 366 | GL::Renderer::enable(GL::Renderer::Feature::PolygonOffsetFill); 367 | GL::Renderer::setPolygonOffset(1 /* slope bias */, 1 /* constant bias */); 368 | 369 | // use current frame's jitter 370 | // this only matters because we blit the velocity depth buffer to reuse it for the quarter resolution pass 371 | // without it, you can use either jittered or unjittered, as long as they match 372 | scene->velocityShader.setProjectionMatrix(matrices[currentFrame]) 373 | .setOldProjectionMatrix(oldMatrices[currentFrame]); 374 | 375 | scene->camera->draw(scene->velocityDrawables); 376 | 377 | // transparent objects shouldn't write to the depth buffer if we blit and reuse it in the quarter-res scene pass 378 | // TODO without depth writes they now have to be properly sorted back to front 379 | // we kinda do this during scene creation, which works because the camera position is static 380 | // for the opaque velocity drawables, we could sort front to back to reduce overdraw 381 | // should we do the same for the normal renderables? it'll be a bit annoying to duplicate the 382 | // Renderables added in loadScene :< 383 | GL::Renderer::setDepthMask(GL_FALSE); 384 | scene->camera->draw(scene->transparentVelocityDrawables); 385 | GL::Renderer::setDepthMask(GL_TRUE); 386 | 387 | GL::Renderer::disable(GL::Renderer::Feature::PolygonOffsetFill); 388 | } 389 | } 390 | 391 | // render scene at quarter resolution 392 | 393 | { 394 | GL::DebugGroup group(GL::DebugGroup::Source::Application, 0, "Scene rendering (quarter-res)"); 395 | 396 | GL::Framebuffer& framebuffer = framebuffers[currentFrame]; 397 | framebuffer.bind(); 398 | 399 | // run fragment shader for each sample 400 | GL::Renderer::enable(GL::Renderer::Feature::SampleShading); 401 | GL::Renderer::setMinSampleShading(1.0f); 402 | 403 | // copy and reuse velocity depth buffer 404 | if(options.reconstruction.createVelocityBuffer && options.reuseVelocityDepth) 405 | { 406 | GL::DebugGroup group2(GL::DebugGroup::Source::Application, 0, "Velocity depth blit"); 407 | 408 | GL::Renderer::setDepthFunction( 409 | GL::Renderer::DepthFunction::Always); // fullscreen pass, always pass depth test 410 | GL::Renderer::setColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); // disable color writing 411 | 412 | // blit to quarter res with max filter 413 | depthBlitShader.bindDepth(velocityDepthAttachment); 414 | depthBlitShader.draw(fullscreenTriangle); 415 | 416 | GL::Renderer::setColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 417 | GL::Renderer::setDepthFunction(depthFunction); 418 | 419 | // implementations can choose to optimize storage by not writing actual depth 420 | // values and reconstructing them during sampling at the default sample positions 421 | // these commands force correct per-sample depth to be written to the depth buffer 422 | if(glEvaluateDepthValuesARB) 423 | glEvaluateDepthValuesARB(); 424 | else if(glResolveDepthValuesNV) 425 | glResolveDepthValuesNV(); 426 | } 427 | else 428 | { 429 | framebuffer.clearDepth(1.0f); 430 | } 431 | 432 | const Color4 clearColor = Color4::fromSrgb(0x772953_rgbf); // Ubuntu Canonical aubergine 433 | framebuffer.clearColor(0, clearColor); 434 | 435 | // use jittered camera if necessary 436 | scene->camera->setProjectionMatrix(matrices[currentFrame]); 437 | 438 | GL::Renderer::enable(GL::Renderer::Feature::Blending); 439 | 440 | scene->camera->draw(scene->drawables); 441 | 442 | GL::Renderer::disable(GL::Renderer::Feature::Blending); 443 | 444 | GL::Renderer::disable(GL::Renderer::Feature::SampleShading); 445 | } 446 | 447 | // undo any jitter 448 | scene->camera->setProjectionMatrix(unjitteredProjection); 449 | 450 | // combine framebuffers 451 | 452 | { 453 | GL::DebugGroup group(GL::DebugGroup::Source::Application, 1, "Checkerboard resolve"); 454 | 455 | outputFramebuffer.bind(); 456 | 457 | GL::Renderer::disable(GL::Renderer::Feature::DepthTest); 458 | 459 | reconstructionShader.bindColor(colorAttachments) 460 | .bindDepth(depthAttachments) 461 | .bindVelocity(velocityAttachment) 462 | .setCurrentFrame(currentFrame) 463 | .setCameraInfo(*scene->camera, scene->cameraNear, scene->cameraFar) 464 | .setOptions(options.reconstruction) 465 | .setBuffer(); 466 | reconstructionShader.draw(fullscreenTriangle); 467 | } 468 | 469 | // housekeeping 470 | 471 | currentFrame = (currentFrame + 1) % FRAMES; 472 | oldMatrices = matrices; 473 | } 474 | 475 | GL::Framebuffer::blit( 476 | outputFramebuffer, GL::defaultFramebuffer, GL::defaultFramebuffer.viewport(), GL::FramebufferBlit::Color); 477 | 478 | // render UI 479 | 480 | GL::defaultFramebuffer.bind(); 481 | 482 | { 483 | GL::DebugGroup group(GL::DebugGroup::Source::Application, 2, "imgui"); 484 | 485 | ImGuiApplication::drawEvent(); 486 | } 487 | 488 | timeline.nextFrame(); 489 | profiler.endFrame(); 490 | 491 | swapBuffers(); 492 | redraw(); 493 | } 494 | 495 | void Mosaiikki::viewportEvent(ViewportEvent& event) 496 | { 497 | ImGuiApplication::viewportEvent(event); 498 | 499 | resizeFramebuffers(event.framebufferSize()); 500 | scene->camera->setViewport(event.framebufferSize()); 501 | updateProjectionMatrix(*scene->camera); 502 | } 503 | 504 | void Mosaiikki::keyReleaseEvent(KeyEvent& event) 505 | { 506 | ImGuiApplication::keyReleaseEvent(event); 507 | if(event.isAccepted()) 508 | return; 509 | 510 | if(event.modifiers()) 511 | return; 512 | 513 | switch(event.key()) 514 | { 515 | case KeyEvent::Key::Space: 516 | paused = !paused; 517 | break; 518 | case KeyEvent::Key::Right: 519 | advanceOneFrame = true; 520 | break; 521 | // can't use Tab because it's eaten by imgui's keyboard navigation 522 | //case KeyEvent::Key::Tab: 523 | case KeyEvent::Key::H: 524 | hideUI = !hideUI; 525 | break; 526 | default: 527 | return; 528 | } 529 | 530 | event.setAccepted(); 531 | } 532 | 533 | void Mosaiikki::buildUI() 534 | { 535 | if(hideUI) 536 | return; 537 | 538 | //ImGui::ShowDemoWindow(); 539 | 540 | const ImVec2 margin = { 5.0f, 5.0f }; 541 | 542 | ImGui::Begin( 543 | "Options", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove); 544 | { 545 | ImGui::Checkbox("Animated objects", &options.scene.animatedObjects); 546 | ImGui::Checkbox("Animated camera", &options.scene.animatedCamera); 547 | 548 | ImGui::Separator(); 549 | 550 | ImGui::Checkbox("Create velocity buffer", &options.reconstruction.createVelocityBuffer); 551 | if(ImGui::IsItemHovered()) 552 | ImGui::SetTooltip( 553 | "Calculate per-pixel velocity vectors instead of reprojecting the pixel position using the depth buffer"); 554 | 555 | ImGui::BeginDisabled(!options.reconstruction.createVelocityBuffer); 556 | ImGui::Checkbox("Re-use velocity depth", &options.reuseVelocityDepth); 557 | if(ImGui::IsItemHovered()) 558 | ImGui::SetTooltip("Downsample and re-use the velocity pass depth buffer for the quarter-res pass"); 559 | ImGui::EndDisabled(); 560 | 561 | ImGui::Checkbox("Always assume occlusion", &options.reconstruction.assumeOcclusion); 562 | if(ImGui::IsItemHovered()) 563 | ImGui::SetTooltip( 564 | "Always assume an old pixel is occluded in the current frame if the pixel moved by more than a quarter-res pixel.\n" 565 | "If this is disabled, a more expensive check against the average surrounding depth value is used."); 566 | 567 | ImGui::BeginDisabled(options.reconstruction.assumeOcclusion); 568 | ImGui::SetNextItemWidth(ImGui::GetWindowWidth() / 2.0f); // slider size (without label) 569 | ImGui::SliderFloat("Depth tolerance", &options.reconstruction.depthTolerance, 0.0f, 0.5f, "%.3f"); 570 | if(ImGui::IsItemHovered()) 571 | ImGui::SetTooltip("Maximum allowed view space depth difference before assuming occlusion"); 572 | ImGui::EndDisabled(); 573 | 574 | ImGui::Checkbox("Use differential blending", &options.reconstruction.differentialBlending); 575 | if(ImGui::IsItemHovered()) 576 | ImGui::SetTooltip( 577 | "When blending pixel neighbor horizontal/vertical axes, weight their contribution by how small the color difference is.\n" 578 | "This greatly reduces checkerboard artifacts at sharp edges."); 579 | 580 | #ifdef CORRADE_IS_DEBUG_BUILD 581 | 582 | ImGui::Separator(); 583 | 584 | static const char* const debugSamplesOptions[] = { "Combined", "Even", "Odd (jittered)" }; 585 | ImGui::SetNextItemWidth(ImGui::GetWindowWidth() * 0.5f); 586 | ImGui::Combo("Show samples", 587 | reinterpret_cast(&options.reconstruction.debug.showSamples), 588 | debugSamplesOptions, 589 | Containers::arraySize(debugSamplesOptions)); 590 | 591 | ImGui::BeginDisabled(!options.reconstruction.createVelocityBuffer); 592 | ImGui::Checkbox("Show velocity buffer", &options.reconstruction.debug.showVelocity); 593 | ImGui::EndDisabled(); 594 | 595 | ImGui::Checkbox("Show debug colors", &options.reconstruction.debug.showColors); 596 | if(ImGui::IsItemHovered()) 597 | { 598 | ImGui::BeginTooltip(); 599 | { 600 | ImGui::ColorButton("magenta", ImVec4(Color4::magenta())); 601 | ImGui::SameLine(); 602 | ImGui::Text("Old pixel was occluded"); 603 | } 604 | { 605 | ImGui::ColorButton("yellow", ImVec4(Color4::yellow())); 606 | ImGui::SameLine(); 607 | ImGui::Text("Old pixel position is outside the screen"); 608 | } 609 | { 610 | ImGui::ColorButton("cyan", ImVec4(Color4::cyan())); 611 | ImGui::SameLine(); 612 | ImGui::Text("Old pixel information is missing (jitter was cancelled out)"); 613 | } 614 | ImGui::EndTooltip(); 615 | } 616 | 617 | #endif 618 | 619 | ImGui::Separator(); 620 | 621 | if(ImGui::CollapsingHeader("Controls")) 622 | { 623 | ImGui::Text("Space"); 624 | ImGui::SameLine(ImGui::GetWindowWidth() * 0.45f); 625 | ImGui::Text("Pause/Continue"); 626 | 627 | ImGui::Text("Right Arrow"); 628 | ImGui::SameLine(ImGui::GetWindowWidth() * 0.45f); 629 | ImGui::Text("Next frame"); 630 | 631 | ImGui::Text("Right Mouse"); 632 | ImGui::SameLine(ImGui::GetWindowWidth() * 0.45f); 633 | ImGui::Text("Zoom"); 634 | 635 | ImGui::Text("H"); 636 | ImGui::SameLine(ImGui::GetWindowWidth() * 0.45f); 637 | ImGui::Text("Hide UI"); 638 | } 639 | 640 | const ImVec2 pos = { ImGui::GetIO().DisplaySize.x - ImGui::GetWindowSize().x - margin.x, margin.y }; 641 | ImGui::SetWindowPos(pos); 642 | } 643 | ImGui::End(); 644 | 645 | ImGui::Begin( 646 | "Stats", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove); 647 | { 648 | ImGui::Text("%s", profiler.statistics().c_str()); 649 | if(paused) 650 | ImGui::TextColored(ImVec4(Color4::yellow()), "PAUSED"); 651 | 652 | const ImVec2 pos = { margin.x, margin.y }; 653 | ImGui::SetWindowPos(pos); 654 | } 655 | ImGui::End(); 656 | 657 | if(!ImGui::GetIO().WantCaptureMouse && ImGui::IsMouseDown(ImGuiMouseButton_Right)) 658 | { 659 | ImGui::Begin("Zoom", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize); 660 | { 661 | static const float zoom = 8.0f; 662 | static const Vector2 imageSize = { 256.0f, 256.0f }; 663 | 664 | const Vector2 screenSize = Vector2(ImGui::GetIO().DisplaySize); 665 | Vector2 uv = (Vector2(ImGui::GetMousePos()) + Vector2(0.5f)) / screenSize; 666 | uv.y() = 1.0f - uv.y(); 667 | const Range2D range = Range2D::fromCenter(uv, imageSize / screenSize / zoom * 0.5f); 668 | ImGuiIntegration::image(outputColorAttachment, imageSize, range); 669 | 670 | ImGui::SetMouseCursor(ImGuiMouseCursor_None); 671 | 672 | Vector2 pos = Vector2(ImGui::GetMousePos()) - (Vector2(ImGui::GetWindowSize()) * 0.5f); 673 | ImGui::SetWindowPos(ImVec2(pos)); 674 | } 675 | ImGui::End(); 676 | } 677 | 678 | for(size_t i = 0; i < scene->meshAnimables.size(); i++) 679 | { 680 | scene->meshAnimables[i].setState(options.scene.animatedObjects ? SceneGraph::AnimationState::Running 681 | : SceneGraph::AnimationState::Paused); 682 | } 683 | 684 | for(size_t i = 0; i < scene->cameraAnimables.size(); i++) 685 | { 686 | scene->cameraAnimables[0].setState(options.scene.animatedCamera ? SceneGraph::AnimationState::Running 687 | : SceneGraph::AnimationState::Paused); 688 | } 689 | } 690 | -------------------------------------------------------------------------------- /src/Mosaiikki.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ImGuiApplication.h" 4 | #include "Options.h" 5 | #include "Scene.h" 6 | #include "Shaders/ReconstructionShader.h" 7 | #include "Shaders/DepthBlitShader.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | class Mosaiikki : public ImGuiApplication 21 | { 22 | public: 23 | explicit Mosaiikki(const Arguments& arguments); 24 | 25 | static const char* NAME; 26 | 27 | private: 28 | virtual void drawEvent() override; 29 | virtual void viewportEvent(ViewportEvent& event) override; 30 | virtual void keyReleaseEvent(KeyEvent& event) override; 31 | virtual void buildUI() override; 32 | 33 | void updateProjectionMatrix(Magnum::SceneGraph::Camera3D& camera); 34 | void resizeFramebuffers(Magnum::Vector2i frameBufferSize); 35 | void setSamplePositions(); 36 | 37 | // debug output 38 | 39 | std::fstream logFile; 40 | // global instances to override the ostream for all instances created later 41 | // we need to override each of them separately 42 | // don't use these, they're nullptr if the file can't be created 43 | Corrade::Containers::Pointer _debug; 44 | Corrade::Containers::Pointer _warning; 45 | Corrade::Containers::Pointer _error; 46 | 47 | Magnum::DebugTools::FrameProfilerGL profiler; 48 | 49 | // scene 50 | 51 | Corrade::Containers::Pointer scene; 52 | 53 | Magnum::GL::Mesh fullscreenTriangle; 54 | 55 | Magnum::Timeline timeline; 56 | 57 | bool paused = false; 58 | bool advanceOneFrame = false; 59 | 60 | bool hideUI = false; 61 | 62 | // checkerboard rendering 63 | 64 | Magnum::GL::Framebuffer velocityFramebuffer; 65 | Magnum::GL::Texture2D velocityAttachment; 66 | Magnum::GL::Texture2D velocityDepthAttachment; 67 | 68 | static constexpr size_t FRAMES = 2; 69 | static constexpr size_t JITTERED_FRAME = 1; 70 | size_t currentFrame = 0; 71 | 72 | // quarter-size framebuffers (half width, half height) 73 | Magnum::GL::Framebuffer framebuffers[FRAMES]; 74 | Magnum::GL::MultisampleTexture2DArray colorAttachments; 75 | Magnum::GL::MultisampleTexture2DArray depthAttachments; 76 | 77 | DepthBlitShader depthBlitShader; 78 | 79 | Magnum::GL::Framebuffer outputFramebuffer; 80 | Magnum::GL::Texture2D outputColorAttachment; 81 | 82 | ReconstructionShader reconstructionShader; 83 | 84 | Options options; 85 | }; 86 | -------------------------------------------------------------------------------- /src/Options.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Options 4 | { 5 | bool reuseVelocityDepth = true; // depends on createVelocityBuffer 6 | 7 | struct Scene 8 | { 9 | bool animatedObjects = false; 10 | bool animatedCamera = false; 11 | } scene; 12 | 13 | struct Reconstruction 14 | { 15 | bool createVelocityBuffer = true; 16 | bool assumeOcclusion = false; 17 | float depthTolerance = 0.01f; 18 | bool differentialBlending = true; 19 | 20 | struct Debug 21 | { 22 | enum Samples : int 23 | { 24 | Combined = 0, 25 | Even, 26 | Odd 27 | }; 28 | 29 | Samples showSamples = Combined; 30 | bool showVelocity = false; 31 | bool showColors = false; 32 | } debug; 33 | } reconstruction; 34 | }; 35 | -------------------------------------------------------------------------------- /src/Scene.cpp: -------------------------------------------------------------------------------- 1 | #include "Scene.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace Magnum; 21 | using namespace Magnum::Math::Literals; 22 | using namespace Corrade; 23 | using namespace Feature; 24 | 25 | // for some reason GCC expects a definition for a static constexpr float 26 | constexpr Magnum::Float Scene::shininess; 27 | 28 | Scene::Scene(NoCreateT) : materialShader(NoCreate), velocityShader(NoCreate) { } 29 | 30 | Scene::Scene() : materialShader(NoCreate), velocityShader(NoCreate) 31 | { 32 | // Default material 33 | 34 | Containers::Array defaultTextures = defaultMaterial.createTextures({ 4, 4 }); 35 | for(GL::Texture2D& texture : defaultTextures) 36 | Containers::arrayAppend(textures, Containers::pointer(std::move(texture))); 37 | 38 | // Scene 39 | 40 | // w in light positions decides about light type (1 = point, 0 = directional) 41 | lightPositions = Containers::array({ { -3.0f, 10.0f, 10.0f, 0.0f } }); 42 | lightColors = Containers::array({ 0xffffff_rgbf }); 43 | CORRADE_INTERNAL_ASSERT(lightPositions.size() == lightColors.size()); 44 | 45 | cameraObject.setParent(&scene); 46 | cameraObject.translate(Vector3::zAxis(-5.0f)); 47 | camera.reset(new SceneGraph::Camera3D(cameraObject)); 48 | 49 | RotationAnimable3D& camAnimable = 50 | cameraObject.addFeature(Vector3::yAxis(), 15.0_degf, 45.0_degf); 51 | cameraAnimables.add(camAnimable); 52 | camAnimable.setState(SceneGraph::AnimationState::Running); 53 | 54 | root.setParent(&scene); 55 | 56 | // Shaders 57 | 58 | // vertex color is coming from the instance buffer attribute 59 | materialShader = 60 | Shaders::PhongGL(Shaders::PhongGL::Flag::InstancedTransformation | Shaders::PhongGL::Flag::VertexColor | 61 | Shaders::PhongGL::Flag::DiffuseTexture | Shaders::PhongGL::Flag::SpecularTexture | 62 | Shaders::PhongGL::Flag::NormalTexture, 63 | lightPositions.size()); 64 | materialShader.setLightPositions(lightPositions); 65 | materialShader.setLightColors(lightColors); 66 | materialShader.setLabel("Material shader (instanced, textured Phong)"); 67 | 68 | velocityShader = VelocityShader(VelocityShader::Flag::InstancedTransformation); 69 | velocityShader.setLabel("Velocity shader (instanced)"); 70 | 71 | // Objects 72 | 73 | const char* mesh = "resources/models/Avocado/Avocado.gltf"; 74 | 75 | Object3D& object = root.addChild(); 76 | object.translate({ 0.0f, 0.0f, -5.0f }); 77 | 78 | Range3D bounds; 79 | bool loaded = loadScene(mesh, object, &bounds); 80 | CORRADE_ASSERT(loaded, "Failed to load scene", ); 81 | 82 | float scale = 2.0f / bounds.size().max(); 83 | 84 | object.scaleLocal(Vector3(scale)); 85 | 86 | Vector3 center(float(objectGridSize - 1) / 2.0f); 87 | 88 | // animated objects + associated velocity drawables 89 | 90 | for(TexturedDrawable3D* drawable : featuresInChildren(object)) 91 | { 92 | Object3D& drawableObject = static_cast(drawable->object()); 93 | 94 | Magnum::UnsignedInt id = drawable->meshId(); 95 | 96 | VelocityDrawable3D& velocityDrawable = drawableObject.addFeature( 97 | velocityShader, id, *velocityMeshes[id], *velocityInstanceBuffers[id]); 98 | velocityDrawables.add(velocityDrawable); 99 | 100 | VelocityDrawable3D& transparentVelocityDrawable = drawableObject.addFeature( 101 | velocityShader, id, *velocityMeshes[id], *velocityInstanceBuffers[id]); 102 | transparentVelocityDrawables.add(transparentVelocityDrawable); 103 | 104 | for(size_t z = 0; z < objectGridSize; z++) 105 | { 106 | for(size_t y = 0; y < objectGridSize; y++) 107 | { 108 | for(size_t x = 0; x < objectGridSize; x++) 109 | { 110 | Object3D& instance = drawableObject.addChild(); 111 | 112 | Matrix3 toLocal = Matrix3(instance.absoluteTransformationMatrix()).inverted(); 113 | 114 | // add instances back to front for alpha blending 115 | Vector3 translation = (Vector3(x, y, -float(objectGridSize - z - 1)) - center) * 4.0f; 116 | instance.translate(toLocal * translation); 117 | 118 | InstanceDrawable3D& instanceDrawable = drawable->addInstance(instance); 119 | 120 | bool transparent = z == (objectGridSize - 1); 121 | Color3 color = 122 | (Color3(x, y, z) + Color3(1.0f)) / objectGridSize; // +1 to avoid completely black objects 123 | float alpha = transparent ? 0.75f : 1.0f; 124 | instanceDrawable.setColor(Color4(color, alpha)); 125 | 126 | Vector3 localX = toLocal * Vector3::xAxis(); 127 | Vector3 localY = toLocal * Vector3::yAxis(); 128 | 129 | if(transparent) 130 | transparentVelocityDrawable.addInstance(instance); 131 | else 132 | velocityDrawable.addInstance(instance); 133 | 134 | TranslationAnimable3D& translationAnimable = instance.addFeature( 135 | localX, 5.5f * localX.length(), 3.0f * localX.length()); 136 | meshAnimables.add(translationAnimable); 137 | translationAnimable.setState(SceneGraph::AnimationState::Running); 138 | 139 | RotationAnimable3D& rotationAnimable = 140 | instance.addFeature(localY, 90.0_degf, Rad(Constants::inf())); 141 | meshAnimables.add(rotationAnimable); 142 | rotationAnimable.setState(SceneGraph::AnimationState::Running); 143 | } 144 | } 145 | } 146 | } 147 | } 148 | 149 | bool Scene::loadScene(const char* file, Object3D& root, Range3D* bounds) 150 | { 151 | // load importer 152 | 153 | PluginManager::Manager manager; 154 | Containers::Pointer importer = manager.loadAndInstantiate("AnySceneImporter"); 155 | if(!importer) 156 | return false; 157 | 158 | // load scene 159 | 160 | if(!importer->openFile(file)) 161 | return false; 162 | 163 | if(importer->sceneCount() == 0) 164 | return false; 165 | 166 | Int sceneId = importer->defaultScene(); 167 | if(sceneId == -1) 168 | sceneId = 0; 169 | Containers::Optional sceneData = importer->scene(sceneId); 170 | if(!sceneData) 171 | return false; 172 | 173 | if(!sceneData->is3D() || 174 | !sceneData->hasField(Trade::SceneField::Parent) || // TODO does this break files with just one object? 175 | !sceneData->hasField(Trade::SceneField::Mesh)) 176 | return false; 177 | 178 | // load and compile meshes 179 | 180 | Magnum::UnsignedInt meshOffset = meshes.size(); 181 | Containers::arrayResize(meshes, meshes.size() + importer->meshCount()); 182 | Containers::arrayResize(instanceBuffers, instanceBuffers.size() + importer->meshCount()); 183 | Containers::arrayResize(velocityMeshes, velocityMeshes.size() + importer->meshCount()); 184 | Containers::arrayResize(velocityInstanceBuffers, velocityInstanceBuffers.size() + importer->meshCount()); 185 | 186 | Range3D sceneBounds; 187 | 188 | for(UnsignedInt i = 0; i < importer->meshCount(); i++) 189 | { 190 | Containers::Optional data = importer->mesh(i); 191 | if(data && data->hasAttribute(Trade::MeshAttribute::Position) && 192 | data->hasAttribute(Trade::MeshAttribute::Normal) && data->hasAttribute(Trade::MeshAttribute::Tangent) && 193 | data->hasAttribute(Trade::MeshAttribute::TextureCoordinates) && 194 | GL::meshPrimitive(data->primitive()) == GL::MeshPrimitive::Triangles) 195 | { 196 | if(bounds) 197 | sceneBounds = Math::join(sceneBounds, Range3D(Math::minmax(data->positions3DAsArray()))); 198 | 199 | GL::Buffer indices, vertices; 200 | indices.setData(data->indexData()); 201 | vertices.setData(data->vertexData()); 202 | 203 | meshes[meshOffset + i].emplace(MeshTools::compile(*data, indices, vertices)); 204 | instanceBuffers[meshOffset + i].emplace(InstanceDrawable3D::addInstancedBuffer(*meshes[i])); 205 | 206 | velocityMeshes[meshOffset + i].emplace(MeshTools::compile(*data, indices, vertices)); 207 | velocityInstanceBuffers[meshOffset + i].emplace( 208 | VelocityInstanceDrawable3D::addInstancedBuffer(*velocityMeshes[i])); 209 | } 210 | else 211 | Warning(Warning::Flag::NoSpace) 212 | << "Skipping mesh " << i << " (must be a triangle mesh with normals, tangents and UV coordinates)"; 213 | } 214 | 215 | if(bounds) 216 | *bounds = sceneBounds; 217 | 218 | // load materials 219 | 220 | Magnum::UnsignedInt materialOffset = materials.size(); 221 | Containers::arrayResize(materials, materials.size() + importer->materialCount()); 222 | 223 | for(UnsignedInt i = 0; i < importer->materialCount(); i++) 224 | { 225 | Containers::Optional data = importer->material(i); 226 | if(data && data->types() & Trade::MaterialType::Phong) 227 | { 228 | materials[materialOffset + i].emplace(std::move(*data)); 229 | } 230 | else 231 | Warning(Warning::Flag::NoSpace) << "Skipping material " << i << " (not Phong-compatible)"; 232 | } 233 | 234 | // load textures 235 | 236 | Magnum::UnsignedInt textureOffset = textures.size(); 237 | Containers::arrayResize(textures, textures.size() + importer->textureCount()); 238 | 239 | for(UnsignedInt i = 0; i < importer->textureCount(); i++) 240 | { 241 | Containers::Optional textureData = importer->texture(i); 242 | if(textureData && textureData->type() == Trade::TextureType::Texture2D) 243 | { 244 | Containers::Optional imageData = importer->image2D(textureData->image(), 0 /* level */); 245 | if(imageData) 246 | { 247 | GL::TextureFormat format; 248 | switch(imageData->format()) 249 | { 250 | case PixelFormat::RGB8Unorm: 251 | format = GL::TextureFormat::RGB8; 252 | break; 253 | case PixelFormat::RGBA8Unorm: 254 | format = GL::TextureFormat::RGBA8; 255 | break; 256 | default: 257 | Warning(Warning::Flag::NoSpace) 258 | << "Skipping texture " << i << " (unsupported format " << imageData->format() << ")"; 259 | continue; 260 | } 261 | GL::Texture2D texture; 262 | texture.setMagnificationFilter(textureData->magnificationFilter()) 263 | .setMinificationFilter(textureData->minificationFilter(), textureData->mipmapFilter()) 264 | .setWrapping(textureData->wrapping().xy()) 265 | .setStorage(Math::log2(imageData->size().max()) + 1, format, imageData->size()) 266 | .setSubImage(0, {}, *imageData) 267 | .generateMipmap(); 268 | textures[textureOffset + i].emplace(std::move(texture)); 269 | } 270 | } 271 | } 272 | 273 | // load objects 274 | 275 | Containers::Array objects { size_t(sceneData->mappingBound()) }; 276 | const auto parents = sceneData->parentsAsArray(); 277 | // create objects in the scene graph 278 | for(const Containers::Pair& parent : parents) 279 | { 280 | objects[parent.first()] = &root.addChild(); 281 | } 282 | 283 | // set parents, separate pass because children can occur 284 | // before parents 285 | for(const Containers::Pair& parent : parents) 286 | { 287 | Object3D* parentObject = parent.second() == -1 ? &root : objects[parent.second()]; 288 | objects[parent.first()]->setParent(parentObject); 289 | } 290 | 291 | for(const Containers::Pair& transformation : sceneData->transformations3DAsArray()) 292 | { 293 | if(Object3D* object = objects[transformation.first()]) 294 | object->setTransformation(transformation.second()); 295 | } 296 | 297 | for(const Containers::Pair>& meshMaterial : 298 | sceneData->meshesMaterialsAsArray()) 299 | { 300 | if(Object3D* object = objects[meshMaterial.first()]) 301 | { 302 | bool useDefaultMaterial = true; 303 | const UnsignedInt meshId = meshOffset + meshMaterial.second().first(); 304 | const Int materialId = meshMaterial.second().second(); 305 | if(materialId != -1 && materials[materialOffset + materialId]) 306 | { 307 | const Trade::PhongMaterialData& material = 308 | materials[materialOffset + materialId]->as(); 309 | if(TexturedDrawable3D::isCompatibleMaterial(material, materialShader)) 310 | { 311 | TexturedDrawable3D& drawable = 312 | object->addFeature(materialShader, 313 | meshId, 314 | *meshes[meshId], 315 | *instanceBuffers[meshId], 316 | textures.exceptPrefix(textureOffset), 317 | material, 318 | shininess); 319 | drawables.add(drawable); 320 | useDefaultMaterial = false; 321 | } 322 | } 323 | 324 | if(useDefaultMaterial) 325 | { 326 | const Trade::PhongMaterialData& material = defaultMaterial.as(); 327 | TexturedDrawable3D& drawable = 328 | object->addFeature(materialShader, 329 | meshId, 330 | *meshes[meshId], 331 | *instanceBuffers[meshId], 332 | textures, // first textures are the default textures 333 | material, 334 | material.shininess()); 335 | drawables.add(drawable); 336 | } 337 | 338 | // by default, there are no instances 339 | // add an InstanceDrawable3D to drawable.instanceDrawables() for each instance 340 | } 341 | } 342 | 343 | return true; 344 | } 345 | -------------------------------------------------------------------------------- /src/Scene.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Feature.h" 4 | 5 | #include "Drawables/TexturedDrawable.h" 6 | #include "Drawables/VelocityDrawable.h" 7 | #include "Shaders/VelocityShader.h" 8 | #include "Animables/AxisTranslationAnimable.h" 9 | #include "Animables/AxisRotationAnimable.h" 10 | #include "DefaultMaterial.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | class Scene 26 | { 27 | public: 28 | typedef Magnum::SceneGraph::MatrixTransformation3D Transform3D; 29 | typedef Magnum::SceneGraph::Object Object3D; 30 | typedef Magnum::SceneGraph::Scene Scene3D; 31 | 32 | typedef TexturedDrawable TexturedDrawable3D; 33 | typedef InstanceDrawable InstanceDrawable3D; 34 | 35 | typedef VelocityDrawable VelocityDrawable3D; 36 | typedef VelocityInstanceDrawable VelocityInstanceDrawable3D; 37 | 38 | typedef AxisTranslationAnimable TranslationAnimable3D; 39 | typedef AxisRotationAnimable RotationAnimable3D; 40 | 41 | explicit Scene(Magnum::NoCreateT); 42 | explicit Scene(); 43 | 44 | // Copying is not allowed 45 | Scene(const Scene&) = delete; 46 | Scene& operator=(const Scene&) = delete; 47 | 48 | // Move constructor/assignment 49 | // TODO default functions are deleted because some(?) member is not movable 50 | //Scene(Scene&&) noexcept = default; 51 | //Scene& operator=(Scene&&) noexcept = default; 52 | 53 | // hardcoded because Magnum always sets this to 80 for GLTF 54 | static constexpr Magnum::Float shininess = 20.0f; 55 | 56 | bool loadScene(const char* file, Object3D& root, Magnum::Range3D* bounds = nullptr); 57 | 58 | // normal meshes with default instance data (transformation, normal matrix, color) 59 | Corrade::Containers::Array> meshes; 60 | Corrade::Containers::Array> instanceBuffers; 61 | 62 | // meshes with instance data for the velocity shader (transformation, old transformation) 63 | Corrade::Containers::Array> velocityMeshes; 64 | Corrade::Containers::Array> velocityInstanceBuffers; 65 | 66 | Corrade::Containers::Array> materials; 67 | Corrade::Containers::Array> textures; 68 | 69 | DefaultMaterial defaultMaterial; 70 | 71 | Scene3D scene; 72 | Object3D root; 73 | 74 | Object3D cameraObject; 75 | Corrade::Containers::Pointer camera; 76 | float cameraNear = 1.0f; 77 | float cameraFar = 50.0f; 78 | 79 | Magnum::SceneGraph::AnimableGroup3D meshAnimables; 80 | Magnum::SceneGraph::AnimableGroup3D cameraAnimables; 81 | 82 | Magnum::SceneGraph::DrawableGroup3D drawables; 83 | // moving objects that contribute to the velocity buffer 84 | Magnum::SceneGraph::DrawableGroup3D velocityDrawables; 85 | // same as above, but for transparent meshes that don't write to the depth buffer 86 | // only necessary if we reuse the velocity depth buffer in the quarter-res scene pass 87 | Magnum::SceneGraph::DrawableGroup3D transparentVelocityDrawables; 88 | 89 | static constexpr size_t objectGridSize = 6; 90 | Corrade::Containers::Array lightPositions; 91 | Corrade::Containers::Array lightColors; 92 | 93 | Magnum::Shaders::PhongGL materialShader; 94 | VelocityShader velocityShader; 95 | }; 96 | -------------------------------------------------------------------------------- /src/Shaders/DepthBlitShader.cpp: -------------------------------------------------------------------------------- 1 | #include "DepthBlitShader.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace Magnum; 11 | 12 | DepthBlitShader::DepthBlitShader(NoCreateT) : GL::AbstractShaderProgram(NoCreate) { } 13 | 14 | DepthBlitShader::DepthBlitShader() 15 | { 16 | GL::Shader vert(GLVersion, GL::Shader::Type::Vertex); 17 | GL::Shader frag(GLVersion, GL::Shader::Type::Fragment); 18 | 19 | Utility::Resource rs("shaders"); 20 | vert.addSource(rs.getString("DepthBlitShader.vert")); 21 | frag.addSource(rs.getString("DepthBlitShader.frag")); 22 | 23 | CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({ vert, frag })); 24 | attachShaders({ vert, frag }); 25 | CORRADE_INTERNAL_ASSERT_OUTPUT(link()); 26 | 27 | setUniform(uniformLocation("depth"), DepthTextureUnit); 28 | } 29 | 30 | DepthBlitShader& DepthBlitShader::bindDepth(GL::Texture2D& attachment) 31 | { 32 | attachment.bind(DepthTextureUnit); 33 | return *this; 34 | } 35 | -------------------------------------------------------------------------------- /src/Shaders/DepthBlitShader.frag: -------------------------------------------------------------------------------- 1 | // gl_SampleID 2 | // core in 4.0 3 | #extension GL_ARB_sample_shading : require 4 | 5 | uniform sampler2D depth; // full-resolution velocity pass depth 6 | 7 | // downsample depth buffer to quarter-size 2X multisampled depth 8 | 9 | void main() 10 | { 11 | ivec2 halfCoords = ivec2(floor(gl_FragCoord.xy)); 12 | ivec2 coords = halfCoords << 1; 13 | 14 | // let each sample copy the depth value from the full screen pass 15 | // this works because we know exactly which pixels the samples fall on 16 | // requires per-sample shading, which is forced on by using gl_SampleID 17 | gl_FragDepth = texelFetch(depth, coords + ivec2(1 - gl_SampleID), 0).x; 18 | } 19 | -------------------------------------------------------------------------------- /src/Shaders/DepthBlitShader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class DepthBlitShader : public Magnum::GL::AbstractShaderProgram 7 | { 8 | public: 9 | explicit DepthBlitShader(Magnum::NoCreateT); 10 | explicit DepthBlitShader(); 11 | 12 | DepthBlitShader& bindDepth(Magnum::GL::Texture2D& attachment); 13 | 14 | private: 15 | using Magnum::GL::AbstractShaderProgram::drawTransformFeedback; 16 | using Magnum::GL::AbstractShaderProgram::dispatchCompute; 17 | 18 | static constexpr Magnum::GL::Version GLVersion = Magnum::GL::Version::GL300; 19 | 20 | enum : Magnum::Int 21 | { 22 | DepthTextureUnit = 0 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /src/Shaders/DepthBlitShader.vert: -------------------------------------------------------------------------------- 1 | void main() 2 | { 3 | // generate triangle vertices from the IDs 4 | // this saves us from sending the position attribute 5 | gl_Position = vec4((gl_VertexID == 2) ? 3.0 : -1.0, 6 | (gl_VertexID == 1) ? -3.0 : 1.0, 7 | 0.0, 8 | 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /src/Shaders/ReconstructionOptions.h: -------------------------------------------------------------------------------- 1 | #ifndef ReconstructionOptions_h 2 | #define ReconstructionOptions_h 3 | 4 | #ifdef __cplusplus 5 | #pragma once 6 | #endif 7 | 8 | #define OPTION_USE_VELOCITY_BUFFER (1 << 0) 9 | #define OPTION_ASSUME_OCCLUSION (1 << 1) 10 | #define OPTION_DIFFERENTIAL_BLENDING (1 << 2) 11 | 12 | #define OPTION_DEBUG_SHOW_SAMPLES (1 << 3) 13 | #define OPTION_DEBUG_SHOW_EVEN_SAMPLES (1 << 4) 14 | #define OPTION_DEBUG_SHOW_VELOCITY (1 << 5) 15 | #define OPTION_DEBUG_SHOW_COLORS (1 << 6) 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/Shaders/ReconstructionShader.cpp: -------------------------------------------------------------------------------- 1 | #include "ReconstructionShader.h" 2 | 3 | #include "ReconstructionOptions.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace Magnum; 14 | 15 | ReconstructionShader::ReconstructionShader(NoCreateT) : GL::AbstractShaderProgram(NoCreate), optionsBuffer(NoCreate) { } 16 | 17 | ReconstructionShader::ReconstructionShader(const Flags flags) : _flags(flags) 18 | { 19 | GL::Shader vert(GLVersion, GL::Shader::Type::Vertex); 20 | GL::Shader frag(GLVersion, GL::Shader::Type::Fragment); 21 | 22 | Utility::Resource rs("shaders"); 23 | 24 | vert.addSource(rs.getString("ReconstructionShader.vert")); 25 | 26 | frag.addSource(flags & Flag::Debug ? "#define DEBUG\n" : ""); 27 | frag.addSource(Utility::formatString("#define COLOR_OUTPUT_ATTRIBUTE_LOCATION {}\n", ColorOutput)); 28 | frag.addSource(rs.getString("ReconstructionOptions.h")); 29 | frag.addSource(rs.getString("ReconstructionShader.frag")); 30 | 31 | // possibly parallel compilation 32 | CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({ vert, frag })); 33 | attachShaders({ vert, frag }); 34 | CORRADE_INTERNAL_ASSERT_OUTPUT(link()); 35 | 36 | setUniform(uniformLocation("color"), ColorTextureUnit); 37 | setUniform(uniformLocation("depth"), DepthTextureUnit); 38 | setUniform(uniformLocation("velocity"), VelocityTextureUnit); 39 | 40 | optionsBlock = uniformBlockIndex("OptionsBlock"); 41 | 42 | optionsBuffer = GL::Buffer(GL::Buffer::TargetHint::Uniform, { optionsData }, GL::BufferUsage::DynamicDraw); 43 | optionsBuffer.setLabel("Checkerboard resolve uniform buffer"); 44 | } 45 | 46 | ReconstructionShader& ReconstructionShader::bindColor(GL::MultisampleTexture2DArray& attachment) 47 | { 48 | attachment.bind(ColorTextureUnit); 49 | return *this; 50 | } 51 | 52 | ReconstructionShader& ReconstructionShader::bindDepth(GL::MultisampleTexture2DArray& attachment) 53 | { 54 | attachment.bind(DepthTextureUnit); 55 | return *this; 56 | } 57 | 58 | ReconstructionShader& ReconstructionShader::bindVelocity(GL::Texture2D& attachment) 59 | { 60 | attachment.bind(VelocityTextureUnit); 61 | return *this; 62 | } 63 | 64 | ReconstructionShader& ReconstructionShader::setCurrentFrame(Int currentFrame) 65 | { 66 | optionsData.currentFrame = currentFrame; 67 | return *this; 68 | } 69 | 70 | ReconstructionShader& ReconstructionShader::setCameraInfo(SceneGraph::Camera3D& camera, float nearPlane, float farPlane) 71 | { 72 | bool projectionChanged = (projection - camera.projectionMatrix()).toVector() != Math::Vector<4 * 4, Float>(0.0f); 73 | optionsData.cameraParametersChanged = viewport != camera.viewport() || projectionChanged; 74 | projection = camera.projectionMatrix(); 75 | 76 | viewport = camera.viewport(); 77 | optionsData.viewport = viewport; 78 | optionsData.near = nearPlane; 79 | optionsData.far = farPlane; 80 | 81 | const Matrix4 viewProjection = projection * camera.cameraMatrix(); 82 | optionsData.prevViewProjection = prevViewProjection; 83 | optionsData.invViewProjection = viewProjection.inverted(); 84 | prevViewProjection = viewProjection; 85 | 86 | return *this; 87 | } 88 | 89 | ReconstructionShader& ReconstructionShader::setOptions(const Options::Reconstruction& options) 90 | { 91 | GLint flags_bitset = 0; 92 | if(options.createVelocityBuffer) 93 | flags_bitset |= OPTION_USE_VELOCITY_BUFFER; 94 | if(options.assumeOcclusion) 95 | flags_bitset |= OPTION_ASSUME_OCCLUSION; 96 | if(options.differentialBlending) 97 | flags_bitset |= OPTION_DIFFERENTIAL_BLENDING; 98 | if(options.debug.showSamples != Options::Reconstruction::Debug::Samples::Combined) 99 | flags_bitset |= OPTION_DEBUG_SHOW_SAMPLES; 100 | if(options.debug.showSamples == Options::Reconstruction::Debug::Samples::Even) 101 | flags_bitset |= OPTION_DEBUG_SHOW_EVEN_SAMPLES; 102 | if(options.debug.showVelocity) 103 | flags_bitset |= OPTION_DEBUG_SHOW_VELOCITY; 104 | if(options.debug.showColors) 105 | flags_bitset |= OPTION_DEBUG_SHOW_COLORS; 106 | 107 | optionsData.flags = flags_bitset; 108 | optionsData.depthTolerance = options.depthTolerance; 109 | return *this; 110 | } 111 | 112 | ReconstructionShader& ReconstructionShader::setBuffer() 113 | { 114 | // orphan the old buffer to prevent stalls 115 | optionsBuffer.setData({ optionsData }, GL::BufferUsage::DynamicDraw); 116 | //optionsBuffer.setSubData(0, { optionsData }); 117 | optionsBuffer.bind(GL::Buffer::Target::Uniform, optionsBlock); 118 | return *this; 119 | } 120 | -------------------------------------------------------------------------------- /src/Shaders/ReconstructionShader.frag: -------------------------------------------------------------------------------- 1 | // uniform buffer 2 | // core in 3.1 3 | #extension GL_ARB_uniform_buffer_object : require 4 | // sampler2DMS 5 | // core in 3.2 6 | #extension GL_ARB_texture_multisample : require 7 | // layout(location = ...) 8 | // core in 3.3 9 | #extension GL_ARB_explicit_attrib_location : require 10 | 11 | #ifdef VALIDATION 12 | #extension GL_GOOGLE_include_directive : require 13 | #include "ReconstructionOptions.h" 14 | 15 | #define COLOR_OUTPUT_ATTRIBUTE_LOCATION 0 16 | #endif 17 | 18 | // References: 19 | 20 | // Checkerboard Rendering for Real-Time Upscaling on Intel Integrated Graphics 21 | // https://software.intel.com/en-us/articles/checkerboard-rendering-for-real-time-upscaling-on-intel-integrated-graphics 22 | // - checkerboard pattern, viewport jitter 23 | // - velocity pass, depth reprojection 24 | // - initial reconstruction shader with depth-based occlusion test 25 | 26 | // Rendering Rainbow Six Siege, Jalal El Mansouri 27 | // https://twvideo01.ubm-us.net/o1/vault/gdc2016/Presentations/El_Mansouri_Jalal_Rendering_Rainbow_Six.pdf 28 | // - color clamping and confidence blend 29 | // - dilated velocity, preserves object silhouette (TODO) 30 | // - use velocity of pixel in 3x3 neighboorhood that's closest to the camera 31 | // - we need full-res depth here which requires the velocity pass to render the entire scene 32 | 33 | // 4K Checkerboard in Battlefield 1 and Mass Effect, Graham Wihlidal 34 | // http://frostbite-wp-prd.s3.amazonaws.com/wp-content/uploads/2017/03/04173623/GDC-Checkerboard.compressed.pdf 35 | // - differential blend operator, removes artifacts around object edges 36 | // - sharpen filter (TODO) 37 | 38 | // Dynamic Temporal Antialiasing and Upsampling in Call of Duty, Jorge Jimenez 39 | // https://www.activision.com/cdn/research/Dynamic_Temporal_Antialiasing_and_Upsampling_in_Call_of_Duty_v4.pdf 40 | // - composite object velocity with camera velocity (TODO) 41 | // - downsample to half-res closest velocity in same pass 42 | // - reduces texture reads required, 2 gathers for velocity 43 | 44 | // quarter-res 2X multisampled textures 45 | // two layers: even / odd (jittered) 46 | uniform sampler2DMSArray color; 47 | uniform sampler2DMSArray depth; 48 | 49 | // full-res screen-space velocity buffer 50 | // .z is a mask for moving objects 51 | uniform sampler2D velocity; 52 | 53 | layout(std140) uniform OptionsBlock 54 | { 55 | mat4 prevViewProjection; 56 | mat4 invViewProjection; 57 | ivec2 viewport; 58 | float near; 59 | float far; 60 | int currentFrame; // is the current frame even or odd? (-> index into color and depth array layers) 61 | bool cameraParametersChanged; 62 | int flags; 63 | float depthTolerance; 64 | }; 65 | 66 | #define OPTION_SET(OPT) ((flags & (OPTION_ ## OPT)) != 0) 67 | #ifdef DEBUG 68 | #define DEBUG_OPTION_SET(OPT) OPTION_SET(DEBUG_ ## OPT) 69 | #else 70 | #define DEBUG_OPTION_SET(OPT) (false) 71 | #endif 72 | 73 | layout(location = COLOR_OUTPUT_ATTRIBUTE_LOCATION) out vec4 fragColor; 74 | 75 | /* 76 | each quarter-res pixel corresponds to 4 pixels (quadrants) in the full-res output 77 | each quarter-res pixel has two MSAA samples at fixed positions 78 | 79 | quadrants: 80 | +---+---+ 81 | | 2 | 3 | 82 | +---+---+ 83 | | 0 | 1 | 84 | +---+---+ 85 | 86 | sample positions: 87 | +---+---+ 88 | | | 0 | 89 | +---+---+ 90 | | 1 | | 91 | +---+---+ 92 | 93 | the odd frames' viewport is jittered half a pixel (= one full-res pixel) to the right 94 | so the sample positions overlap for full coverage across two frames 95 | 96 | even: 97 | +---+---+ 98 | | | 0 | 99 | +---+---+ 100 | | 1 | | 101 | +---+---+ 102 | 103 | odd: 104 | +---+---+ 105 | | | A | 106 | +---+---+ 107 | | B | | 108 | +---+---+ 109 | 110 | combined: 111 | +---+---+ 112 | | A | 0 | 113 | +---+---+---+ 114 | | B | 1 | | 115 | +---+---+---+ 116 | */ 117 | 118 | int calculateQuadrant(ivec2 pixelCoords) 119 | { 120 | return (pixelCoords.x & 1) + (pixelCoords.y & 1) * 2; 121 | } 122 | 123 | vec4 fetchQuadrant(sampler2DMSArray tex, ivec2 coords, int quadrant) 124 | { 125 | switch(quadrant) 126 | { 127 | default: 128 | case 0: // (x, y, even/odd), sample 129 | return texelFetch(tex, ivec3(coords, 0), 1); 130 | case 1: 131 | return texelFetch(tex, ivec3(coords + ivec2(1, 0), 1), 1); 132 | case 2: 133 | return texelFetch(tex, ivec3(coords, 1), 0); 134 | case 3: 135 | return texelFetch(tex, ivec3(coords, 0), 0); 136 | } 137 | } 138 | 139 | /* 140 | quadrants to evaluate when averaging values around a quadrant: 141 | 142 | 0: 143 | +---+---+ 144 | | 0 | | 145 | ---+---+---+ 146 | 1 | X | 1 | 147 | ---+---+---+ 148 | | 0 | 149 | 150 | 1: 151 | +---+---+ 152 | | | 0 | 153 | +---+---+--- 154 | | 1 | X | 1 155 | +---+---+--- 156 | | 0 | 157 | 158 | 2: 159 | | 1 | 160 | ---+---+---+ 161 | 0 | X | 0 | 162 | ---+---+---+ 163 | | 1 | | 164 | +---+---+ 165 | 166 | 3: 167 | | 1 | 168 | +---+---+--- 169 | | 0 | X | 0 170 | +---+---+--- 171 | | | 1 | 172 | +---+---+ 173 | */ 174 | 175 | #define UP 0 176 | #define DOWN 1 177 | #define LEFT 2 178 | #define RIGHT 3 179 | 180 | const ivec2 directionOffsets[4*4] = ivec2[4*4]( 181 | // quadrant 0 182 | ivec2( 0, 0), // up 183 | ivec2( 0, -1), // down 184 | ivec2(-1, 0), // left 185 | ivec2( 0, 0), // right 186 | // quadrant 1 187 | ivec2( 0, 0), 188 | ivec2( 0, -1), 189 | ivec2( 0, 0), 190 | ivec2(+1, 0), 191 | // quadrant 2 192 | ivec2( 0, +1), 193 | ivec2( 0, 0), 194 | ivec2(-1, 0), 195 | ivec2( 0, 0), 196 | // quadrant 3 197 | ivec2( 0, +1), 198 | ivec2( 0, 0), 199 | ivec2( 0, 0), 200 | ivec2(+1, 0) 201 | ); 202 | 203 | const ivec4 directionQuadrants[4] = ivec4[4]( 204 | // quadrant 0 205 | ivec4(ivec2(2), ivec2(1)), // up/down, left/right 206 | // quadrant 1 207 | ivec4(ivec2(3), ivec2(0)), 208 | // quadrant 2 209 | ivec4(ivec2(0), ivec2(3)), 210 | // quadrant 3 211 | ivec4(ivec2(1), ivec2(2)) 212 | ); 213 | 214 | // tonemapping operator for combining HDR colors to prevent bright samples from dominating the result 215 | // https://gpuopen.com/learn/optimized-reversible-tonemapper-for-resolve/ 216 | 217 | vec4 tonemap(vec4 color) 218 | { 219 | return color / (max(color.r, max(color.g, color.b)) + 1.0); 220 | } 221 | 222 | vec4 undoTonemap(vec4 color) 223 | { 224 | return color / (1.0 - max(color.r, max(color.g, color.b))); 225 | } 226 | 227 | struct ColorNeighborhood 228 | { 229 | // values are tonemapped! 230 | // if you want to output any of these (or a linear combination of them) use undoTonemap 231 | vec4 up; 232 | vec4 down; 233 | vec4 left; 234 | vec4 right; 235 | }; 236 | 237 | // differential blend operator 238 | // look at vertical and horizontal color blend 239 | // higher weight on whichever has the lowest color difference 240 | // this greatly reduces checkerboard artifacts at color discontinuities and edges 241 | 242 | float colorBlendWeight(vec4 a, vec4 b) 243 | { 244 | return 1.0 / max(length(a.rgb - b.rgb), 0.001); 245 | } 246 | 247 | vec4 differentialBlend(ColorNeighborhood neighbors) 248 | { 249 | float verticalWeight = colorBlendWeight(neighbors.up, neighbors.down); 250 | float horizontalWeight = colorBlendWeight(neighbors.left, neighbors.right); 251 | vec4 result = (neighbors.up + neighbors.down) * verticalWeight + 252 | (neighbors.left + neighbors.right) * horizontalWeight; 253 | return result * 0.5 * 1.0/(verticalWeight + horizontalWeight); 254 | } 255 | 256 | void fetchColorNeighborhood(ivec2 coords, int quadrant, out ColorNeighborhood neighbors) 257 | { 258 | int k = quadrant * 4; 259 | neighbors.up = tonemap(fetchQuadrant(color, coords + directionOffsets[k + UP ], directionQuadrants[quadrant][UP ])); 260 | neighbors.down = tonemap(fetchQuadrant(color, coords + directionOffsets[k + DOWN ], directionQuadrants[quadrant][DOWN ])); 261 | neighbors.left = tonemap(fetchQuadrant(color, coords + directionOffsets[k + LEFT ], directionQuadrants[quadrant][LEFT ])); 262 | neighbors.right = tonemap(fetchQuadrant(color, coords + directionOffsets[k + RIGHT], directionQuadrants[quadrant][RIGHT])); 263 | } 264 | 265 | vec4 colorAverage(ColorNeighborhood neighbors) 266 | { 267 | vec4 result; 268 | if(OPTION_SET(DIFFERENTIAL_BLENDING)) 269 | result = differentialBlend(neighbors); 270 | else 271 | result = (neighbors.up + neighbors.down + neighbors.left + neighbors.right) * 0.25; 272 | return undoTonemap(result); 273 | } 274 | 275 | vec4 colorClamp(ColorNeighborhood neighbors, vec4 color) 276 | { 277 | vec4 minColor = min(min(neighbors.up, neighbors.down), min(neighbors.left, neighbors.right)); 278 | vec4 maxColor = max(max(neighbors.up, neighbors.down), max(neighbors.left, neighbors.right)); 279 | return undoTonemap(clamp(tonemap(color), minColor, maxColor)); 280 | } 281 | 282 | // undo depth projection 283 | // assumes a projection transformation produced by Matrix4::perspectiveProjection with finite far plane 284 | // solve for view space z: (z(n+f) + 2nf)/(-z(n-f)) = 2w-1 285 | // w = window space depth [0;1] 286 | // 2w-1 = NDC space depth [-1;1] 287 | float screenToViewDepth(float depth) 288 | { 289 | return (far * near) / ((far * depth) - far - (near * depth)); 290 | } 291 | 292 | // returns averaged depth in view space 293 | // depth buffer values are non-linear, averaging those produces incorrect results 294 | float fetchDepthAverage(ivec2 coords, int quadrant) 295 | { 296 | int k = quadrant * 4; 297 | float result = 298 | screenToViewDepth(fetchQuadrant(depth, coords + directionOffsets[k + UP ], directionQuadrants[quadrant][UP ]).x) + 299 | screenToViewDepth(fetchQuadrant(depth, coords + directionOffsets[k + DOWN ], directionQuadrants[quadrant][DOWN ]).x) + 300 | screenToViewDepth(fetchQuadrant(depth, coords + directionOffsets[k + LEFT ], directionQuadrants[quadrant][LEFT ]).x) + 301 | screenToViewDepth(fetchQuadrant(depth, coords + directionOffsets[k + RIGHT], directionQuadrants[quadrant][RIGHT]).x); 302 | return result * 0.25; 303 | } 304 | 305 | // get screen space velocity vector from fullscreen coordinates 306 | // the z component is a mask for dynamic objects, if it's 0 no velocity was calculated at that coordinate 307 | // and camera reprojection is necessary 308 | vec3 fetchVelocity(ivec2 coords) 309 | { 310 | return texelFetch(velocity, coords, 0).xyz * vec3(viewport, 1.0); 311 | } 312 | 313 | // get old frame's pixel position based on camera movement 314 | // unprojects world position from screen space depth, then projects into previous frame's screen space 315 | ivec2 reprojectPixel(ivec2 coords, float depth) 316 | { 317 | vec2 screen = vec2(coords) + 0.5; // gl_FragCoord x/y are located at half-pixel centers, undo the flooring 318 | vec3 ndc = vec3(screen / viewport, depth) * 2.0 - 1.0; // z: [0;1] -> [-1;1] 319 | vec4 clip = vec4(ndc, 1.0); 320 | vec4 world = invViewProjection * clip; 321 | world /= world.w; 322 | clip = prevViewProjection * world; 323 | ndc = clip.xyz / clip.w; 324 | screen = (ndc.xy * 0.5 + 0.5) * viewport; 325 | coords = ivec2(floor(screen)); 326 | return coords; 327 | } 328 | 329 | void main() 330 | { 331 | ivec2 coords = ivec2(floor(gl_FragCoord.xy)); 332 | ivec2 halfCoords = coords >> 1; 333 | int quadrant = calculateQuadrant(coords); 334 | 335 | const ivec2 FRAME_QUADRANTS[2] = ivec2[]( 336 | ivec2(3, 0), // even 337 | ivec2(2, 1) // odd 338 | ); 339 | 340 | // debug output: velocity buffer 341 | if(DEBUG_OPTION_SET(SHOW_VELOCITY)) 342 | { 343 | vec2 vel = texelFetch(velocity, coords, 0).xy; 344 | fragColor = vec4(abs(vel * 255.0), 0.0, 1.0); 345 | return; 346 | } 347 | 348 | // debug output: checkered frame 349 | if(DEBUG_OPTION_SET(SHOW_SAMPLES)) 350 | { 351 | int sampleFrame = OPTION_SET(DEBUG_SHOW_EVEN_SAMPLES) ? 0 : 1; 352 | ivec2 boardQuadrants = FRAME_QUADRANTS[sampleFrame]; 353 | if(any(equal(vec2(quadrant), boardQuadrants))) 354 | fragColor = fetchQuadrant(color, halfCoords, quadrant); 355 | else 356 | fragColor = vec4(0.0, 0.0, 0.0, 0.0); 357 | return; 358 | } 359 | 360 | ivec2 currentQuadrants = FRAME_QUADRANTS[currentFrame]; 361 | 362 | // was this pixel rendered with the most recent frame? 363 | // -> just use it 364 | if(any(equal(ivec2(quadrant), currentQuadrants))) 365 | { 366 | fragColor = fetchQuadrant(color, halfCoords, quadrant); 367 | return; 368 | } 369 | 370 | ColorNeighborhood neighbors; 371 | fetchColorNeighborhood(halfCoords, quadrant, neighbors); 372 | 373 | // we have no old data, use average 374 | if(cameraParametersChanged) 375 | { 376 | fragColor = colorAverage(neighbors); 377 | return; 378 | } 379 | 380 | bool possiblyOccluded = false; 381 | 382 | // find pixel position in previous frame 383 | 384 | ivec2 oldCoords = coords; 385 | bool velocityFromDepth = true; 386 | 387 | // for fully general results, sample from a velocity buffer 388 | if(OPTION_SET(USE_VELOCITY_BUFFER)) 389 | { 390 | vec3 velocity = fetchVelocity(coords); 391 | // z is a mask for dynamic objects 392 | if(velocity.z > 0.0) 393 | { 394 | oldCoords = ivec2(floor(gl_FragCoord.xy - velocity.xy)); 395 | velocityFromDepth = false; 396 | } 397 | else 398 | { 399 | // force occlusion check to prevent ghosting around previously 400 | // occluded pixels 401 | // if we only check for quarter-pixel movement, we'd see (0,0) movement in 402 | // that case and directly use the old frame's, but we need to average 403 | possiblyOccluded = true; 404 | } 405 | } 406 | 407 | // if we're not using a velocity buffer or the object is static, reproject using the camera transformation 408 | if(velocityFromDepth) 409 | { 410 | float z = fetchQuadrant(depth, halfCoords, quadrant).x; 411 | oldCoords = reprojectPixel(coords, z); 412 | } 413 | 414 | ivec2 oldHalfCoords = oldCoords >> 1; 415 | int oldQuadrant = calculateQuadrant(oldCoords); 416 | 417 | // TODO 418 | // this eliminates jitter, but breaks with smearing everywhere 419 | // occlusion? 420 | //fragColor = fetchQuadrant(color, oldHalfCoords, oldQuadrant); 421 | //return; 422 | 423 | // is the previous position outside the screen? 424 | if(any(lessThan(oldCoords, ivec2(0, 0))) || any(greaterThanEqual(oldCoords, viewport))) 425 | { 426 | if(DEBUG_OPTION_SET(SHOW_COLORS)) 427 | fragColor = vec4(1.0, 1.0, 0.0, 1.0); 428 | else 429 | fragColor = colorAverage(neighbors); 430 | return; 431 | } 432 | 433 | // is the previous position not in an old frame quadrant? 434 | // this happens when any movement cancelled the jitter 435 | // -> there's no shading information 436 | ivec2 oldQuadrants = FRAME_QUADRANTS[1 - currentFrame]; 437 | if(!any(equal(ivec2(oldQuadrant), oldQuadrants))) 438 | { 439 | if(DEBUG_OPTION_SET(SHOW_COLORS)) 440 | fragColor = vec4(0.0, 1.0, 1.0, 1.0); 441 | else 442 | fragColor = colorAverage(neighbors); 443 | return; 444 | } 445 | 446 | // check for occlusion if the old position was in a different quarter-res pixel 447 | if(any(greaterThan(abs(oldHalfCoords - halfCoords), ivec2(0)))) 448 | { 449 | possiblyOccluded = true; 450 | } 451 | 452 | // check for occlusion 453 | bool occluded = false; 454 | if(possiblyOccluded) 455 | { 456 | // simple variant: always assume occlusion 457 | if(OPTION_SET(ASSUME_OCCLUSION)) 458 | { 459 | occluded = true; 460 | } 461 | else // more correct heuristic: check depth against current depth average 462 | { 463 | float currentDepthAverage = fetchDepthAverage(halfCoords, quadrant); 464 | float oldDepth = fetchQuadrant(depth, oldHalfCoords, oldQuadrant).x; 465 | // fetchDepthAverage returns average of linear view space depth 466 | oldDepth = screenToViewDepth(oldDepth); 467 | occluded = abs(currentDepthAverage - oldDepth) >= depthTolerance; 468 | } 469 | } 470 | 471 | if(occluded) 472 | { 473 | if(DEBUG_OPTION_SET(SHOW_COLORS)) 474 | fragColor = vec4(1.0, 0.0, 1.0, 1.0); 475 | else 476 | fragColor = colorAverage(neighbors); 477 | return; 478 | } 479 | 480 | // clamp color based on neighbors 481 | vec4 reprojectedColor = fetchQuadrant(color, oldHalfCoords, oldQuadrant); 482 | vec4 clampedColor = colorClamp(neighbors, reprojectedColor); 483 | 484 | // blend back towards reprojected result using confidence based on old depth 485 | float currentDepthAverage = fetchDepthAverage(halfCoords, quadrant); 486 | float oldDepth = screenToViewDepth(fetchQuadrant(depth, oldHalfCoords, oldQuadrant).x); 487 | float diff = abs(currentDepthAverage - oldDepth); 488 | // reuse depthTolerance to indicate 0 confidence cutoff 489 | // square falloff 490 | float deviation = diff - depthTolerance; 491 | float confidence = clamp(deviation * deviation, 0.0, 1.0); 492 | fragColor = mix(clampedColor, reprojectedColor, confidence); 493 | } 494 | -------------------------------------------------------------------------------- /src/Shaders/ReconstructionShader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "Options.h" 10 | 11 | class ReconstructionShader : public Magnum::GL::AbstractShaderProgram 12 | { 13 | public: 14 | enum : Magnum::UnsignedInt 15 | { 16 | ColorOutput = Magnum::Shaders::GenericGL3D::ColorOutput 17 | }; 18 | 19 | enum class Flag : Magnum::UnsignedShort 20 | { 21 | // Debug output (configured through setOptions) 22 | Debug = 1 << 0 23 | }; 24 | 25 | typedef Corrade::Containers::EnumSet Flags; 26 | 27 | explicit ReconstructionShader(Magnum::NoCreateT); 28 | explicit ReconstructionShader(const Flags flags); 29 | 30 | Flags flags() const 31 | { 32 | return _flags; 33 | }; 34 | 35 | ReconstructionShader& bindColor(Magnum::GL::MultisampleTexture2DArray& attachment); 36 | ReconstructionShader& bindDepth(Magnum::GL::MultisampleTexture2DArray& attachment); 37 | ReconstructionShader& bindVelocity(Magnum::GL::Texture2D& attachment); 38 | ReconstructionShader& setCurrentFrame(Magnum::Int currentFrame); 39 | ReconstructionShader& setCameraInfo(Magnum::SceneGraph::Camera3D& camera, float nearPlane, float farPlane); 40 | ReconstructionShader& setOptions(const Options::Reconstruction& options); 41 | // call this once before draw, after setting all the data, to transfer the uniform buffer 42 | // the alternative would be to implement all 6 versions of AbstractShaderProgram::draw() 43 | ReconstructionShader& setBuffer(); 44 | 45 | private: 46 | using Magnum::GL::AbstractShaderProgram::drawTransformFeedback; 47 | using Magnum::GL::AbstractShaderProgram::dispatchCompute; 48 | 49 | static constexpr Magnum::GL::Version GLVersion = Magnum::GL::Version::GL300; 50 | 51 | Flags _flags; 52 | 53 | enum : Magnum::Int 54 | { 55 | ColorTextureUnit = 0, 56 | DepthTextureUnit = 1, 57 | VelocityTextureUnit = 2 58 | }; 59 | 60 | Magnum::Int optionsBlock = -1; 61 | Magnum::GL::Buffer optionsBuffer; 62 | 63 | struct OptionsBufferData 64 | { 65 | Magnum::Matrix4 prevViewProjection = Magnum::Matrix4(Magnum::Math::IdentityInit); 66 | Magnum::Matrix4 invViewProjection = Magnum::Matrix4(Magnum::Math::IdentityInit); 67 | Magnum::Vector2i viewport = { 0, 0 }; 68 | float near = 0.01f; 69 | float far = 50.0f; 70 | GLint currentFrame = 0; 71 | GLuint cameraParametersChanged = false; 72 | GLint flags = 0; 73 | GLfloat depthTolerance = 0.01f; 74 | } optionsData; 75 | 76 | Magnum::Vector2i viewport = { 0, 0 }; 77 | Magnum::Matrix4 projection = Magnum::Matrix4(Magnum::Math::IdentityInit); 78 | Magnum::Matrix4 prevViewProjection = Magnum::Matrix4(Magnum::Math::IdentityInit); 79 | }; 80 | 81 | CORRADE_ENUMSET_OPERATORS(ReconstructionShader::Flags) 82 | -------------------------------------------------------------------------------- /src/Shaders/ReconstructionShader.vert: -------------------------------------------------------------------------------- 1 | void main() 2 | { 3 | // generate triangle vertices from the IDs 4 | // this saves us from sending the position attribute 5 | // requires OpenGL 3.0 or above 6 | gl_Position = vec4((gl_VertexID == 2) ? 3.0 : -1.0, 7 | (gl_VertexID == 1) ? -3.0 : 1.0, 8 | 0.0, 9 | 1.0); 10 | } 11 | -------------------------------------------------------------------------------- /src/Shaders/VelocityShader.cpp: -------------------------------------------------------------------------------- 1 | #include "VelocityShader.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace Magnum; 11 | 12 | VelocityShader::VelocityShader(NoCreateT) : GL::AbstractShaderProgram(NoCreate) { } 13 | 14 | VelocityShader::VelocityShader(const Flags flags) : _flags(flags) 15 | { 16 | GL::Shader vert(GLVersion, GL::Shader::Type::Vertex); 17 | GL::Shader frag(GLVersion, GL::Shader::Type::Fragment); 18 | 19 | Utility::Resource rs("shaders"); 20 | 21 | vert.addSource(flags & Flag::InstancedTransformation ? "#define INSTANCED_TRANSFORMATION\n" : ""); 22 | vert.addSource(Utility::formatString("#define POSITION_ATTRIBUTE_LOCATION {}\n" 23 | "#define TRANSFORMATION_ATTRIBUTE_LOCATION {}\n" 24 | "#define OLD_TRANSFORMATION_ATTRIBUTE_LOCATION {}\n", 25 | Shaders::GenericGL3D::Position::Location, 26 | TransformationMatrix::Location, 27 | OldTransformationMatrix::Location)); 28 | vert.addSource(rs.getString("VelocityShader.vert")); 29 | 30 | frag.addSource(Utility::formatString("#define VELOCITY_OUTPUT_ATTRIBUTE_LOCATION {}\n", VelocityOutput)); 31 | frag.addSource(rs.getString("VelocityShader.frag")); 32 | 33 | CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({ vert, frag })); 34 | attachShaders({ vert, frag }); 35 | CORRADE_INTERNAL_ASSERT_OUTPUT(link()); 36 | 37 | transformationMatrixUniform = uniformLocation("transformationMatrix"); 38 | oldTransformationMatrixUniform = uniformLocation("oldTransformationMatrix"); 39 | projectionMatrixUniform = uniformLocation("projectionMatrix"); 40 | oldProjectionMatrixUniform = uniformLocation("oldProjectionMatrix"); 41 | } 42 | 43 | VelocityShader& VelocityShader::setTransformationMatrix(const Magnum::Matrix4& transformationMatrix) 44 | { 45 | setUniform(transformationMatrixUniform, transformationMatrix); 46 | return *this; 47 | } 48 | 49 | VelocityShader& VelocityShader::setOldTransformationMatrix(const Magnum::Matrix4& oldTransformationMatrix) 50 | { 51 | setUniform(oldTransformationMatrixUniform, oldTransformationMatrix); 52 | return *this; 53 | } 54 | 55 | VelocityShader& VelocityShader::setProjectionMatrix(const Magnum::Matrix4& projectionMatrix) 56 | { 57 | setUniform(projectionMatrixUniform, projectionMatrix); 58 | return *this; 59 | } 60 | 61 | VelocityShader& VelocityShader::setOldProjectionMatrix(const Magnum::Matrix4& oldProjectionMatrix) 62 | { 63 | setUniform(oldProjectionMatrixUniform, oldProjectionMatrix); 64 | return *this; 65 | } 66 | -------------------------------------------------------------------------------- /src/Shaders/VelocityShader.frag: -------------------------------------------------------------------------------- 1 | // layout(location = ...) 2 | // core in 3.3 3 | #extension GL_ARB_explicit_attrib_location : require 4 | 5 | #ifdef VALIDATION 6 | #define VELOCITY_OUTPUT_ATTRIBUTE_LOCATION 0 7 | #endif 8 | 9 | in vec4 clipPos; 10 | in vec4 oldClipPos; 11 | 12 | layout(location = VELOCITY_OUTPUT_ATTRIBUTE_LOCATION) out vec4 velocity; 13 | 14 | void main() 15 | { 16 | vec2 distance = (clipPos.xy / clipPos.w) - (oldClipPos.xy / oldClipPos.w); 17 | // scale to [-1;1] so we can simply multiply by the viewport size to get screenspace velocity 18 | // requires 16-bit float framebuffer attachment 19 | distance *= 0.5; 20 | velocity = vec4(distance, 1.0, 1.0); 21 | } 22 | -------------------------------------------------------------------------------- /src/Shaders/VelocityShader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class VelocityShader : public Magnum::GL::AbstractShaderProgram 10 | { 11 | public: 12 | typedef Magnum::Shaders::GenericGL3D::TransformationMatrix TransformationMatrix; 13 | typedef Magnum::GL::Attribute 14 | OldTransformationMatrix; 15 | 16 | enum : Magnum::UnsignedInt 17 | { 18 | VelocityOutput = Magnum::Shaders::GenericGL3D::ColorOutput 19 | }; 20 | 21 | enum class Flag : Magnum::UnsignedShort 22 | { 23 | /** 24 | * Instanced transformation. Retrieves a per-instance 25 | * transformation and old transformation matrix from the 26 | * TransformationMatrix / OldTransformationMatrix attributes and 27 | * uses them together with matrices coming from 28 | * setTransformationMatrix() and setOldTransformationMatrix() (first 29 | * the per-instance, then the uniform matrix). 30 | */ 31 | InstancedTransformation = 1 << 0 32 | }; 33 | 34 | typedef Corrade::Containers::EnumSet Flags; 35 | 36 | explicit VelocityShader(Magnum::NoCreateT); 37 | explicit VelocityShader(const Flags flags = {}); 38 | 39 | Flags flags() const 40 | { 41 | return _flags; 42 | }; 43 | 44 | VelocityShader& setTransformationMatrix(const Magnum::Matrix4& transformationMatrix); 45 | VelocityShader& setOldTransformationMatrix(const Magnum::Matrix4& oldTransformationMatrix); 46 | 47 | VelocityShader& setProjectionMatrix(const Magnum::Matrix4& projectionMatrix); 48 | VelocityShader& setOldProjectionMatrix(const Magnum::Matrix4& oldProjectionMatrix); 49 | 50 | private: 51 | using Magnum::GL::AbstractShaderProgram::drawTransformFeedback; 52 | using Magnum::GL::AbstractShaderProgram::dispatchCompute; 53 | 54 | static constexpr Magnum::GL::Version GLVersion = Magnum::GL::Version::GL300; 55 | 56 | Flags _flags; 57 | 58 | Magnum::Int transformationMatrixUniform = -1; 59 | Magnum::Int oldTransformationMatrixUniform = -1; 60 | Magnum::Int projectionMatrixUniform = -1; 61 | Magnum::Int oldProjectionMatrixUniform = -1; 62 | }; 63 | 64 | CORRADE_ENUMSET_OPERATORS(VelocityShader::Flags) 65 | -------------------------------------------------------------------------------- /src/Shaders/VelocityShader.vert: -------------------------------------------------------------------------------- 1 | // layout(location = ...) 2 | // core in 3.3 3 | #extension GL_ARB_explicit_attrib_location : require 4 | 5 | #ifdef VALIDATION 6 | #define POSITION_ATTRIBUTE_LOCATION 0 7 | #define TRANSFORMATION_ATTRIBUTE_LOCATION 1 8 | #define OLD_TRANSFORMATION_ATTRIBUTE_LOCATION 2 9 | #endif 10 | 11 | uniform mat4 transformationMatrix = mat4(1.0); 12 | uniform mat4 oldTransformationMatrix = mat4(1.0); 13 | 14 | uniform mat4 projectionMatrix = mat4(1.0); 15 | uniform mat4 oldProjectionMatrix = mat4(1.0); 16 | 17 | layout(location = POSITION_ATTRIBUTE_LOCATION) in vec4 position; 18 | 19 | #ifdef INSTANCED_TRANSFORMATION 20 | layout(location = TRANSFORMATION_ATTRIBUTE_LOCATION) in mat4 instancedTransformationMatrix; 21 | layout(location = OLD_TRANSFORMATION_ATTRIBUTE_LOCATION) in mat4 instancedOldTransformationMatrix; 22 | #endif 23 | 24 | out vec4 clipPos; 25 | out vec4 oldClipPos; 26 | 27 | void main() 28 | { 29 | clipPos = projectionMatrix * transformationMatrix * 30 | #ifdef INSTANCED_TRANSFORMATION 31 | instancedTransformationMatrix * 32 | #endif 33 | position; 34 | 35 | oldClipPos = oldProjectionMatrix * oldTransformationMatrix * 36 | #ifdef INSTANCED_TRANSFORMATION 37 | instancedOldTransformationMatrix * 38 | #endif 39 | position; 40 | 41 | gl_Position = clipPos; 42 | } 43 | -------------------------------------------------------------------------------- /src/Shaders/resources.conf: -------------------------------------------------------------------------------- 1 | group=shaders 2 | 3 | [file] 4 | filename=VelocityShader.vert 5 | 6 | [file] 7 | filename=VelocityShader.frag 8 | 9 | [file] 10 | filename=DepthBlitShader.vert 11 | 12 | [file] 13 | filename=DepthBlitShader.frag 14 | 15 | [file] 16 | filename=ReconstructionShader.vert 17 | 18 | [file] 19 | filename=ReconstructionShader.frag 20 | 21 | [file] 22 | filename=ReconstructionOptions.h 23 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "Mosaiikki.h" 2 | 3 | MAGNUM_APPLICATION_MAIN(Mosaiikki) 4 | -------------------------------------------------------------------------------- /src/windows-dpi-awareness.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | 7 | 8 | 9 | --------------------------------------------------------------------------------