├── .gitmodules ├── .gitattributes ├── .gitignore ├── LICENSE ├── include └── Midgard │ ├── Terrain.hpp │ ├── StaticTerrain.hpp │ └── DynamicTerrain.hpp ├── shaders ├── slope.comp ├── terrain_color.comp ├── fog.frag ├── terrain.tesc ├── terrain.tese ├── perlin_noise_2d.comp └── perlin_noise_3d.comp ├── README.md ├── src └── Midgard │ ├── Terrain.cpp │ ├── DynamicTerrain.cpp │ └── StaticTerrain.cpp ├── cmake └── EmbedFiles.cmake ├── .github └── workflows │ ├── Windows.yml │ └── Linux.yml ├── main.cpp └── CMakeLists.txt /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "extern/RaZ"] 2 | path = extern/RaZ 3 | url = https://github.com/Razakhel/RaZ 4 | branch = master 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | assets/** filter=lfs diff=lfs merge=lfs -text 2 | *.obj filter=lfs diff=lfs merge=lfs -text 3 | *.png filter=lfs diff=lfs merge=lfs -text 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | #*.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Compiled Static libraries 17 | *.a 18 | *.lib 19 | 20 | # Executables 21 | *.exe 22 | *.out 23 | *.app 24 | 25 | # Build folders 26 | *build*/ 27 | 28 | # CLion/Visual Studio folders 29 | .idea/ 30 | .vs/ 31 | 32 | # MacOS specific files 33 | .DS_Store 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Romain Milbert 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 | -------------------------------------------------------------------------------- /include/Midgard/Terrain.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef MIDGARD_TERRAIN_HPP 4 | #define MIDGARD_TERRAIN_HPP 5 | 6 | namespace Raz { class Entity; } 7 | 8 | class Terrain { 9 | public: 10 | explicit Terrain(Raz::Entity& entity); 11 | Terrain(const Terrain&) = delete; 12 | Terrain(Terrain&&) noexcept = default; 13 | 14 | void setHeightFactor(float heightFactor) { setParameters(heightFactor, m_flatness); } 15 | void setFlatness(float flatness) { setParameters(m_heightFactor, flatness); } 16 | virtual void setParameters(float heightFactor, float flatness); 17 | 18 | virtual void generate(unsigned int width, unsigned int depth, float heightFactor, float flatness) = 0; 19 | 20 | Terrain& operator=(const Terrain&) = delete; 21 | Terrain& operator=(Terrain&&) noexcept = delete; 22 | 23 | virtual ~Terrain() = default; 24 | 25 | protected: 26 | static void checkParameters(float& heightFactor, float& flatness); 27 | 28 | Raz::Entity& m_entity; 29 | 30 | unsigned int m_width {}; 31 | unsigned int m_depth {}; 32 | float m_heightFactor {}; 33 | float m_flatness {}; 34 | float m_invFlatness {}; 35 | }; 36 | 37 | #endif // MIDGARD_TERRAIN_HPP 38 | -------------------------------------------------------------------------------- /shaders/slope.comp: -------------------------------------------------------------------------------- 1 | layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; 2 | 3 | layout(r16f, binding = 0) uniform readonly restrict image2D uniHeightmap; 4 | layout(rgba16f, binding = 1) uniform writeonly restrict image2D uniSlopeMap; 5 | 6 | uniform float uniFlatness = 3.0; 7 | uniform float uniHeightFactor = 30.0; 8 | 9 | vec2 computeSlope(ivec2 pixelCoords) { 10 | float leftHeight = pow(imageLoad(uniHeightmap, pixelCoords + ivec2(-1, 0)).r, uniFlatness) * uniHeightFactor; 11 | float rightHeight = pow(imageLoad(uniHeightmap, pixelCoords + ivec2( 1, 0)).r, uniFlatness) * uniHeightFactor; 12 | float topHeight = pow(imageLoad(uniHeightmap, pixelCoords + ivec2( 0, -1)).r, uniFlatness) * uniHeightFactor; 13 | float botHeight = pow(imageLoad(uniHeightmap, pixelCoords + ivec2( 0, 1)).r, uniFlatness) * uniHeightFactor; 14 | 15 | return vec2(leftHeight - rightHeight, topHeight - botHeight); 16 | } 17 | 18 | void main() { 19 | ivec2 pixelCoords = ivec2(gl_GlobalInvocationID.xy); 20 | 21 | vec2 slopeVec = computeSlope(pixelCoords); 22 | float slopeStrength = length(slopeVec) * 0.5; 23 | imageStore(uniSlopeMap, pixelCoords, vec4(normalize(slopeVec), slopeStrength, 1.0)); 24 | } 25 | -------------------------------------------------------------------------------- /include/Midgard/StaticTerrain.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef MIDGARD_STATICTERRAIN_HPP 4 | #define MIDGARD_STATICTERRAIN_HPP 5 | 6 | #include "Midgard/Terrain.hpp" 7 | 8 | #include 9 | 10 | class StaticTerrain : public Terrain { 11 | public: 12 | explicit StaticTerrain(Raz::Entity& entity) : Terrain(entity) {} 13 | StaticTerrain(Raz::Entity& entity, unsigned int width, unsigned int depth, float heightFactor, float flatness); 14 | 15 | void setParameters(float heightFactor, float flatness) override; 16 | 17 | /// Generates a terrain as a static mesh. 18 | /// \param width Width of the terrain. 19 | /// \param depth Depth of the terrain. 20 | /// \param heightFactor Height factor to apply to vertices. 21 | /// \param flatness Flatness of the terrain. 22 | void generate(unsigned int width, unsigned int depth, float heightFactor, float flatness) override; 23 | const Raz::Image& computeColorMap(); 24 | const Raz::Image& computeNormalMap(); 25 | const Raz::Image& computeSlopeMap(); 26 | 27 | private: 28 | void computeNormals(); 29 | void remapVertices(float newHeightFactor, float newFlatness); 30 | 31 | Raz::Image m_colorMap {}; 32 | Raz::Image m_normalMap {}; 33 | Raz::Image m_slopeMap {}; 34 | }; 35 | 36 | #endif // MIDGARD_STATICTERRAIN_HPP 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Midgard 2 | 3 | Terrain & landscape generator. 4 | 5 | # Gallery 6 | 7 | ![Perlin fBm noise & gradient colors](https://imgur.com/Kj7BMNc.png) 8 | 9 | ![Warping noise](https://imgur.com/QAWsvhB.png) 10 | 11 | ![Sun & fog](https://imgur.com/u37DbIK.png) 12 | 13 | More progress screenshots are available on the wiki's [milestones page](https://github.com/Razakhel/Midgard/wiki/Milestones). 14 | 15 | # Resources 16 | 17 | - Wei et al. (2007) - [Fast Hydraulic Erosion Simulation and Visualization on GPU](https://inria.hal.science/inria-00402079/document) 18 | - Paris et al. (2020) - [Modeling Rocky Scenery using Implicit Blocks](https://hal.archives-ouvertes.fr/hal-02926218/file/2020-rocks-author.pdf) ([code](https://github.com/aparis69/Rock-fracturing)) 19 | - Paris et al. (2019) - [Desertscape Simulation](https://hal.archives-ouvertes.fr/hal-02273039/document) ([code](https://github.com/aparis69/Desertscapes-Simulation)) 20 | - Peytavie et al. (2019) - [Procedural Riverscapes](https://hal.archives-ouvertes.fr/hal-02281637/document) 21 | - Weiss et al. (2020) - [Triplanar Displacement Mapping for Terrain Rendering](https://www.in.tum.de/fileadmin/w00bws/cg/Research/Publications/2020/TriplanarDisplacementMapping/short1020_CRC-Preprint.pdf) ([code](https://github.com/flojo33/Triplanar-Displacement-Mapping)) 22 | -------------------------------------------------------------------------------- /shaders/terrain_color.comp: -------------------------------------------------------------------------------- 1 | layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; 2 | 3 | const vec3 waterColor = vec3(0.0, 0.0, 1.0); 4 | const vec3 grassColor = vec3(0.24, 0.49, 0.0); 5 | const vec3 groundColor = vec3(0.62, 0.43, 0.37); 6 | const vec3 rockColor = vec3(0.5, 0.5, 0.5); 7 | const vec3 snowColor = vec3(1.0, 1.0, 1.0); 8 | 9 | layout(r16f, binding = 0) uniform readonly restrict image2D uniHeightmap; 10 | layout(rgba8, binding = 1) uniform writeonly restrict image2D uniColorMap; 11 | 12 | void main() { 13 | ivec2 pixelCoords = ivec2(gl_GlobalInvocationID.xy); 14 | 15 | float height = imageLoad(uniHeightmap, pixelCoords).r; 16 | vec3 color = (height < 0.33 ? mix(waterColor, grassColor, height * 3.0) 17 | : (height < 0.5 ? mix(grassColor, groundColor, (height - 0.33) * 5.75) 18 | : (height < 0.66 ? mix(groundColor, rockColor, (height - 0.5) * 6.0) 19 | : mix(rockColor, snowColor, (height - 0.66) * 3.0)))); 20 | 21 | // Applying gamma correction 22 | // This shouldn't be needed here, as ideally the texture should be read later as sRGB; but as it's not possible to use imageRead/Store() on an sRGB texture, 23 | // and to avoid duplicating them to use distinct ones for write & read operations, this correction is applied 24 | color = pow(color, vec3(2.2)); 25 | 26 | imageStore(uniColorMap, pixelCoords, vec4(color, 1.0)); 27 | } 28 | -------------------------------------------------------------------------------- /src/Midgard/Terrain.cpp: -------------------------------------------------------------------------------- 1 | #include "Midgard/Terrain.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | Terrain::Terrain(Raz::Entity& entity) : m_entity{ entity } { 10 | if (!m_entity.hasComponent()) 11 | m_entity.addComponent(); 12 | 13 | if (!m_entity.hasComponent()) 14 | m_entity.addComponent(); 15 | 16 | if (!m_entity.hasComponent()) 17 | m_entity.addComponent(); 18 | 19 | auto& meshRenderer = m_entity.getComponent(); 20 | 21 | if (meshRenderer.getSubmeshRenderers().empty()) 22 | meshRenderer.addSubmeshRenderer(); 23 | 24 | meshRenderer.setMaterial(Raz::Material(Raz::MaterialType::COOK_TORRANCE)).getProgram().setAttribute(0.f, Raz::MaterialAttribute::Roughness); 25 | } 26 | 27 | void Terrain::setParameters(float heightFactor, float flatness) { 28 | checkParameters(heightFactor, flatness); 29 | 30 | m_heightFactor = heightFactor; 31 | m_flatness = flatness; 32 | m_invFlatness = 1.f / flatness; 33 | } 34 | 35 | void Terrain::checkParameters(float& heightFactor, float& flatness) { 36 | if (heightFactor <= 0.f) { 37 | Raz::Logger::warn("[Terrain] The height factor can't be 0 or negative; remapping to +epsilon."); 38 | heightFactor = std::numeric_limits::epsilon(); 39 | } 40 | 41 | if (flatness <= 0.f) { 42 | Raz::Logger::warn("[Terrain] The flatness can't be 0 or negative; remapping to +epsilon."); 43 | flatness = std::numeric_limits::epsilon(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /shaders/fog.frag: -------------------------------------------------------------------------------- 1 | struct Buffers { 2 | sampler2D depth; 3 | sampler2D color; 4 | }; 5 | 6 | in vec2 fragTexcoords; 7 | 8 | layout(std140) uniform uboCameraMatrices { 9 | mat4 viewMat; 10 | mat4 invViewMat; 11 | mat4 projectionMat; 12 | mat4 invProjectionMat; 13 | mat4 viewProjectionMat; 14 | vec3 cameraPos; 15 | }; 16 | 17 | uniform Buffers uniSceneBuffers; 18 | uniform vec3 uniSunDir; 19 | uniform float uniFogDensity; 20 | 21 | layout(location = 0) out vec4 fragColor; 22 | 23 | vec3 computeViewPosFromDepth(float depth) { 24 | vec4 projPos = vec4(vec3(fragTexcoords, depth) * 2.0 - 1.0, 1.0); 25 | vec4 viewPos = invProjectionMat * projPos; 26 | 27 | return viewPos.xyz / viewPos.w; 28 | } 29 | 30 | // Basic fog implementation from https://www.iquilezles.org/www/articles/fog/fog.htm 31 | vec3 computeFog(vec3 color, float distance, vec3 viewDir) { 32 | float fogDensity = uniFogDensity / 20.0; 33 | float fogAmount = 1.0 - exp(-distance * fogDensity); 34 | 35 | float sunAmount = max(-dot(viewDir, mat3(viewMat) * uniSunDir), 0.0); 36 | vec3 fogColor = mix(vec3(0.5, 0.6, 0.7), // Sky/fog color (blue) 37 | vec3(1.0, 0.9, 0.7), // Sun color (yellow) 38 | pow(sunAmount, 8.0)); 39 | 40 | return mix(color, fogColor, fogAmount); 41 | } 42 | 43 | void main() { 44 | float depth = texture(uniSceneBuffers.depth, fragTexcoords).r; 45 | vec3 color = texture(uniSceneBuffers.color, fragTexcoords).rgb; 46 | 47 | vec3 viewPos = computeViewPosFromDepth(depth); 48 | float viewDist = length(viewPos); 49 | vec3 foggedColor = computeFog(color, viewDist, viewPos / viewDist); 50 | 51 | fragColor = vec4(foggedColor, 1.0); 52 | } 53 | -------------------------------------------------------------------------------- /cmake/EmbedFiles.cmake: -------------------------------------------------------------------------------- 1 | # Allows to create embeddable (#include-able) files directly in code 2 | 3 | function(embed_files INPUT_PATTERN OUTPUT_FOLDER MAIN_TARGET_NAME EMBED_TARGET_SUFFIX) 4 | set(EMBED_TARGET_NAME "${MAIN_TARGET_NAME}_Embed${EMBED_TARGET_SUFFIX}") 5 | set(EMBED_SCRIPT "${CMAKE_BINARY_DIR}/${EMBED_TARGET_NAME}.cmake") 6 | 7 | # Emptying the embedding script 8 | file(WRITE "${EMBED_SCRIPT}" "") 9 | 10 | file(GLOB EMBEDDABLE_FILES "${INPUT_PATTERN}") 11 | 12 | # Creating a copy of each file to enclose its content with a raw string literal R"(...)" 13 | foreach (FILE_PATH ${EMBEDDABLE_FILES}) 14 | get_filename_component(FILE_NAME "${FILE_PATH}" NAME) 15 | set(EMBED_FILE_PATH "${OUTPUT_FOLDER}/${FILE_NAME}.embed") 16 | 17 | # CMake cannot execute a script from a string and requires an actual file to be executed 18 | # The embeddable file will be written only if the original has been modified 19 | file( 20 | APPEND 21 | "${EMBED_SCRIPT}" 22 | 23 | " 24 | if (NOT EXISTS \"${EMBED_FILE_PATH}\" OR \"${FILE_PATH}\" IS_NEWER_THAN \"${EMBED_FILE_PATH}\") 25 | file(READ \"${FILE_PATH}\" FILE_CONTENT) 26 | file(WRITE \"${EMBED_FILE_PATH}\" \"R\\\"(\${FILE_CONTENT})\\\"\") 27 | endif () 28 | " 29 | ) 30 | endforeach () 31 | 32 | add_custom_target( 33 | ${EMBED_TARGET_NAME} 34 | COMMAND ${CMAKE_COMMAND} -P "${EMBED_SCRIPT}" # Executing the previously created script 35 | VERBATIM 36 | ) 37 | 38 | add_dependencies(${MAIN_TARGET_NAME} ${EMBED_TARGET_NAME}) 39 | 40 | # Adding the embedded files directory to the include dirs, so that we can include them directly in code 41 | target_include_directories(${MAIN_TARGET_NAME} PUBLIC "${OUTPUT_FOLDER}") 42 | endfunction() 43 | -------------------------------------------------------------------------------- /shaders/terrain.tesc: -------------------------------------------------------------------------------- 1 | layout(vertices = 4) out; 2 | 3 | struct MeshInfo { 4 | vec3 vertPosition; 5 | vec2 vertTexcoords; 6 | mat3 vertTBNMatrix; 7 | }; 8 | 9 | in MeshInfo vertMeshInfo[]; 10 | 11 | layout(std140) uniform uboCameraMatrices { 12 | mat4 viewMat; 13 | mat4 invViewMat; 14 | mat4 projectionMat; 15 | mat4 invProjectionMat; 16 | mat4 viewProjectionMat; 17 | vec3 cameraPos; 18 | }; 19 | 20 | uniform float uniTessLevel = 12.0; 21 | 22 | out MeshInfo tessMeshInfo[]; 23 | 24 | void main() { 25 | gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; 26 | tessMeshInfo[gl_InvocationID].vertPosition = vertMeshInfo[gl_InvocationID].vertPosition; 27 | tessMeshInfo[gl_InvocationID].vertTexcoords = vertMeshInfo[gl_InvocationID].vertTexcoords; 28 | tessMeshInfo[gl_InvocationID].vertTBNMatrix = vertMeshInfo[gl_InvocationID].vertTBNMatrix; 29 | 30 | if (gl_InvocationID == 0) { 31 | vec3 patchCentroid = (vertMeshInfo[0].vertPosition + vertMeshInfo[1].vertPosition + vertMeshInfo[2].vertPosition + vertMeshInfo[3].vertPosition) / 4.0; 32 | float tessLevelFactor = (1.0 / distance(cameraPos, patchCentroid)) * 512.0; 33 | 34 | // Outer levels get a higher factor in order to attempt filling gaps between patches 35 | // TODO: A better way must be found, like making the outer level vary according to the edges' mid points 36 | // See: https://www.khronos.org/opengl/wiki/Tessellation#Patch_interface_and_continuity 37 | gl_TessLevelOuter[0] = uniTessLevel * (tessLevelFactor * 4.0); 38 | gl_TessLevelOuter[1] = uniTessLevel * (tessLevelFactor * 4.0); 39 | gl_TessLevelOuter[2] = uniTessLevel * (tessLevelFactor * 4.0); 40 | gl_TessLevelOuter[3] = uniTessLevel * (tessLevelFactor * 4.0); 41 | 42 | gl_TessLevelInner[0] = uniTessLevel * tessLevelFactor; 43 | gl_TessLevelInner[1] = uniTessLevel * tessLevelFactor; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/Windows.yml: -------------------------------------------------------------------------------- 1 | name: Midgard - Windows 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | windows: 7 | name: Windows (${{ matrix.compiler.c }}, ${{ matrix.build_type }}) 8 | runs-on: windows-2022 9 | 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | compiler: 14 | - { c: cl, cpp: cl } 15 | build_type: 16 | - Debug 17 | - Release 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Pulling RaZ 23 | run: git submodule update --init --recursive 24 | 25 | # Installing OpenAL-soft to handle the audio part 26 | - name: Build setup 27 | run: | 28 | curl --silent --insecure https://www.openal-soft.org/openal-binaries/openal-soft-1.21.0-bin.zip -O && 29 | 7z x openal-soft-1.21.0-bin.zip && 30 | mv openal-soft-1.21.0-bin C:/OpenAL; 31 | cmake -E make_directory ${{ runner.workspace }}/build-${{ matrix.compiler.c }} 32 | 33 | - name: Configuration 34 | shell: bash 35 | working-directory: ${{ runner.workspace }}/build-${{ matrix.compiler.c }} 36 | run: | 37 | cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DMIDGARD_BUILD_RAZ=ON $GITHUB_WORKSPACE 38 | 39 | - name: Build 40 | shell: bash 41 | working-directory: ${{ runner.workspace }}/build-${{ matrix.compiler.c }} 42 | run: | 43 | cmake --build . --config ${{ matrix.build_type }} && 44 | pwd && find . 45 | 46 | # The include/ & lib/ folders are removed after installation; these are RaZ's install results 47 | # A workaround must be found to tell CMake not to install them from Midgard 48 | - name: Install 49 | working-directory: ${{ runner.workspace }}/build-${{ matrix.compiler.c }} 50 | run: | 51 | cmake --install . --prefix C:/Midgard --config ${{ matrix.build_type }} && 52 | rm C:/Midgard/include -r && 53 | rm C:/Midgard/lib -r 54 | 55 | - name: Upload build 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: Midgard-windows-${{ matrix.compiler.c }}-${{ matrix.build_type }}-${{ github.sha }} 59 | path: | 60 | C:/Midgard 61 | -------------------------------------------------------------------------------- /include/Midgard/DynamicTerrain.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef MIDGARD_DYNAMICTERRAIN_HPP 4 | #define MIDGARD_DYNAMICTERRAIN_HPP 5 | 6 | #include "Midgard/Terrain.hpp" 7 | 8 | #include 9 | #include 10 | 11 | class DynamicTerrain final : public Terrain { 12 | public: 13 | explicit DynamicTerrain(Raz::Entity& entity); 14 | DynamicTerrain(Raz::Entity& entity, unsigned int width, unsigned int depth, float heightFactor, float flatness, float minTessLevel = 12.f); 15 | 16 | const Raz::Texture2D& getNoiseMap() const noexcept { return *m_noiseMap; } 17 | const Raz::Texture2D& getColorMap() const noexcept { return *m_colorMap; } 18 | const Raz::Texture2D& getSlopeMap() const noexcept { return *m_slopeMap; } 19 | 20 | void setMinTessellationLevel(float minTessLevel) { setParameters(minTessLevel, m_heightFactor, m_flatness); } 21 | void setParameters(float heightFactor, float flatness) override { setParameters(m_minTessLevel, heightFactor, flatness); } 22 | void setParameters(float minTessLevel, float heightFactor, float flatness); 23 | 24 | /// Generates a dynamic terrain using tessellation shaders. 25 | /// \param width Width of the terrain. 26 | /// \param depth Depth of the terrain. 27 | /// \param heightFactor Height factor to apply to vertices. 28 | /// \param flatness Flatness of the terrain. 29 | void generate(unsigned int width, unsigned int depth, float heightFactor, float flatness) override { generate(width, depth, heightFactor, flatness, 12.f); } 30 | /// Generates a dynamic terrain using tessellation shaders. 31 | /// \param width Width of the terrain. 32 | /// \param depth Depth of the terrain. 33 | /// \param heightFactor Height factor to apply to vertices. 34 | /// \param flatness Flatness of the terrain. 35 | /// \param minTessLevel Minimal tessellation level to render the terrain with. 36 | void generate(unsigned int width, unsigned int depth, float heightFactor, float flatness, float minTessLevel); 37 | const Raz::Texture2D& computeNoiseMap(float factor); 38 | const Raz::Texture2D& computeColorMap(); 39 | const Raz::Texture2D& computeSlopeMap(); 40 | 41 | private: 42 | float m_minTessLevel {}; 43 | 44 | Raz::ComputeShaderProgram m_noiseProgram {}; 45 | Raz::ComputeShaderProgram m_colorProgram {}; 46 | Raz::ComputeShaderProgram m_slopeProgram {}; 47 | 48 | Raz::Texture2DPtr m_noiseMap {}; 49 | Raz::Texture2DPtr m_colorMap {}; 50 | Raz::Texture2DPtr m_slopeMap {}; 51 | }; 52 | 53 | #endif // MIDGARD_DYNAMICTERRAIN_HPP 54 | -------------------------------------------------------------------------------- /.github/workflows/Linux.yml: -------------------------------------------------------------------------------- 1 | name: Midgard - Linux 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | linux: 7 | name: Linux (${{ matrix.compiler.c }}, ${{ matrix.build_type }}) 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | compiler: 14 | - { c: gcc, cpp: g++ } 15 | - { c: clang, cpp: clang++ } 16 | build_type: 17 | - Debug 18 | - Release 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Pulling RaZ 24 | run: git submodule update --init --recursive 25 | 26 | # Updating the packages list (needed at the moment for libinput to be found), & installing the needed packages for RaZ: 27 | # - GLEW & X11 as graphical dependencies 28 | # - OpenAL-soft to handle the audio part 29 | - name: Packages installation 30 | run: | 31 | sudo apt update && 32 | sudo apt install -y --no-install-recommends \ 33 | libglew-dev libxi-dev libxcursor-dev libxrandr-dev libxinerama-dev libxxf86vm-dev \ 34 | libopenal-dev 35 | 36 | - name: Build setup 37 | run: | 38 | cmake -E make_directory ${{ runner.workspace }}/build-${{ matrix.compiler.c }} 39 | 40 | - name: Configuration 41 | shell: bash 42 | working-directory: ${{ runner.workspace }}/build-${{ matrix.compiler.c }} 43 | run: | 44 | cmake -G "Unix Makefiles" -DCMAKE_C_COMPILER=${{ matrix.compiler.c }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler.cpp }} \ 45 | -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DMIDGARD_BUILD_RAZ=ON \ 46 | $GITHUB_WORKSPACE 47 | 48 | - name: Build 49 | shell: bash 50 | working-directory: ${{ runner.workspace }}/build-${{ matrix.compiler.c }} 51 | run: | 52 | cmake --build . --config ${{ matrix.build_type }} && 53 | pwd && find . 54 | 55 | # Installing Midgard in the build directory; default is /usr/local 56 | - name: Install 57 | working-directory: ${{ runner.workspace }}/build-${{ matrix.compiler.c }} 58 | run: | 59 | sudo cmake --install . --prefix ./ --config ${{ matrix.build_type }} 60 | 61 | # Making an archive to maintain file permissions & avoiding weird paths 62 | # Additional dependencies are libc & libm (located in /usr/lib*), as well as libgcc_s & libstdc++ (which would require another package) 63 | - name: Packaging build 64 | run: | 65 | mkdir Midgard-linux-${{ matrix.compiler.c }}-${{ matrix.build_type }}-${{ github.sha }}/ && 66 | cp -t Midgard-linux-${{ matrix.compiler.c }}-${{ matrix.build_type }}-${{ github.sha }}/ \ 67 | ${{ runner.workspace }}/build-${{ matrix.compiler.c }}/Midgard \ 68 | /usr/lib/*/libGL.so \ 69 | /usr/lib/*/libopenal.so 70 | tar -cvf Midgard-linux-${{ matrix.compiler.c }}-${{ matrix.build_type }}-${{ github.sha }}.tar \ 71 | Midgard-linux-${{ matrix.compiler.c }}-${{ matrix.build_type }}-${{ github.sha }}/ 72 | 73 | - name: Upload build 74 | uses: actions/upload-artifact@v4 75 | with: 76 | name: Midgard-linux-${{ matrix.compiler.c }}-${{ matrix.build_type }}-${{ github.sha }} 77 | path: Midgard-linux-${{ matrix.compiler.c }}-${{ matrix.build_type }}-${{ github.sha }}.tar 78 | -------------------------------------------------------------------------------- /shaders/terrain.tese: -------------------------------------------------------------------------------- 1 | layout(quads, fractional_odd_spacing, ccw) in; 2 | 3 | struct MeshInfo { 4 | vec3 vertPosition; 5 | vec2 vertTexcoords; 6 | mat3 vertTBNMatrix; 7 | }; 8 | 9 | in MeshInfo tessMeshInfo[]; 10 | 11 | uniform sampler2D uniHeightmap; 12 | uniform uvec2 uniTerrainSize; 13 | uniform float uniFlatness = 3.0; 14 | uniform float uniHeightFactor = 30.0; 15 | 16 | layout(std140) uniform uboCameraMatrices { 17 | mat4 viewMat; 18 | mat4 invViewMat; 19 | mat4 projectionMat; 20 | mat4 invProjectionMat; 21 | mat4 viewProjectionMat; 22 | vec3 cameraPos; 23 | }; 24 | 25 | out MeshInfo vertMeshInfo; 26 | 27 | vec3 computeNormalDifferences(vec2 vertUV) { 28 | float uStride = 1.0 / float(uniTerrainSize.x); 29 | float vStride = 1.0 / float(uniTerrainSize.y); 30 | 31 | float topHeight = pow(texture(uniHeightmap, vertUV + vec2( 0.0, -vStride)).r, uniFlatness) * uniHeightFactor; 32 | float leftHeight = pow(texture(uniHeightmap, vertUV + vec2(-uStride, 0.0)).r, uniFlatness) * uniHeightFactor; 33 | float rightHeight = pow(texture(uniHeightmap, vertUV + vec2( uStride, 0.0)).r, uniFlatness) * uniHeightFactor; 34 | float botHeight = pow(texture(uniHeightmap, vertUV + vec2( 0.0, vStride)).r, uniFlatness) * uniHeightFactor; 35 | 36 | // Replace y by 2.0-2.5 to get the same result as the cross method 37 | return normalize(vec3(leftHeight - rightHeight, 1.0, topHeight - botHeight)); 38 | } 39 | 40 | vec3 computeNormalCross(vec3 vertPos, vec2 vertUV) { 41 | float uStride = 1.0 / float(uniTerrainSize.x); 42 | float vStride = 1.0 / float(uniTerrainSize.y); 43 | 44 | vec3 bottomLeftPos = vertPos + vec3(-1.0, 0.0, -1.0); 45 | vec3 topLeftPos = vertPos + vec3(-1.0, 0.0, 1.0); 46 | vec3 bottomRightPos = vertPos + vec3( 1.0, 0.0, -1.0); 47 | vec3 topRightPos = vertPos + vec3( 1.0, 0.0, 1.0); 48 | 49 | float bottomLeftHeight = texture(uniHeightmap, vertUV + vec2(-uStride, -vStride)).r; 50 | float topLeftHeight = texture(uniHeightmap, vertUV + vec2(-uStride, vStride)).r; 51 | float bottomRightHeight = texture(uniHeightmap, vertUV + vec2( uStride, -vStride)).r; 52 | float topRightHeight = texture(uniHeightmap, vertUV + vec2( uStride, vStride)).r; 53 | 54 | bottomLeftPos.y += pow(bottomLeftHeight, uniFlatness) * uniHeightFactor; 55 | topLeftPos.y += pow(topLeftHeight, uniFlatness) * uniHeightFactor; 56 | bottomRightPos.y += pow(bottomRightHeight, uniFlatness) * uniHeightFactor; 57 | topRightPos.y += pow(topRightHeight, uniFlatness) * uniHeightFactor; 58 | 59 | vec3 bottomLeftDir = bottomLeftPos - vertPos; 60 | vec3 topLeftDir = topLeftPos - vertPos; 61 | vec3 bottomRightDir = bottomRightPos - vertPos; 62 | vec3 topRightDir = topRightPos - vertPos; 63 | 64 | vec3 normal = cross(bottomLeftDir, topLeftDir); 65 | normal += cross(topRightDir, bottomRightDir); 66 | normal += cross(bottomRightDir, bottomLeftDir); 67 | normal += cross(topLeftDir, topRightDir); 68 | 69 | return normalize(normal); 70 | } 71 | 72 | void main() { 73 | // Top & bottom are actually inverted when looking the terrain from above: [0] & [2] are the patch's top vertices, while [1] & [3] are the bottom's 74 | vec3 bottomLeftPos = tessMeshInfo[0].vertPosition; 75 | vec3 topLeftPos = tessMeshInfo[1].vertPosition; 76 | vec3 bottomRightPos = tessMeshInfo[2].vertPosition; 77 | vec3 topRightPos = tessMeshInfo[3].vertPosition; 78 | 79 | // Interpolating bilinearly to recover the position on the patch 80 | vec3 vertPos0 = mix(tessMeshInfo[0].vertPosition, tessMeshInfo[1].vertPosition, gl_TessCoord.x); 81 | vec3 vertPos1 = mix(tessMeshInfo[2].vertPosition, tessMeshInfo[3].vertPosition, gl_TessCoord.x); 82 | vec3 vertPos = mix(vertPos0, vertPos1, gl_TessCoord.y); 83 | 84 | // Interpolating bilinearly to recover the texcoords on the patch 85 | vec2 vertUV0 = mix(tessMeshInfo[0].vertTexcoords, tessMeshInfo[1].vertTexcoords, gl_TessCoord.x); 86 | vec2 vertUV1 = mix(tessMeshInfo[2].vertTexcoords, tessMeshInfo[3].vertTexcoords, gl_TessCoord.x); 87 | vec2 vertUV = mix(vertUV0, vertUV1, gl_TessCoord.y); 88 | 89 | float midHeight = texture(uniHeightmap, vertUV).r; 90 | vertPos.y += pow(midHeight, uniFlatness) * uniHeightFactor; 91 | 92 | vec3 normal = computeNormalDifferences(vertUV); 93 | //vec3 normal = computeNormalCross(vertPos, vertUV); 94 | vec3 tangent = normal.zxy; // ? 95 | 96 | vertMeshInfo.vertPosition = vertPos; 97 | vertMeshInfo.vertTexcoords = vertUV; 98 | vertMeshInfo.vertTBNMatrix = mat3(tangent, cross(normal, tangent), normal); 99 | 100 | gl_Position = viewProjectionMat * vec4(vertPos, 1.0); 101 | } 102 | -------------------------------------------------------------------------------- /shaders/perlin_noise_2d.comp: -------------------------------------------------------------------------------- 1 | layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; 2 | 3 | layout(r16f, binding = 0) uniform writeonly restrict image2D uniNoiseMap; 4 | uniform float uniNoiseFactor = 0.01; 5 | uniform int uniOctaveCount = 1; 6 | 7 | const int permutations[512] = int[]( 8 | 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 9 | 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 10 | 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 11 | 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 12 | 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 13 | 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 14 | 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 15 | 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 16 | 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 17 | 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 18 | 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 19 | 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 20 | 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 21 | 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 22 | 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 23 | 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 24 | 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 25 | 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 26 | 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 27 | 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 28 | 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 29 | 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 30 | 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 31 | 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 32 | ); 33 | 34 | const vec2 gradients2D[8] = vec2[]( 35 | vec2( 1.0, 0.0), vec2( -1.0, 0.0), 36 | vec2( 0.0, 1.0), vec2( 0.0, -1.0), 37 | vec2(0.7071067691, 0.7071067691), vec2(-0.7071067691, 0.7071067691), 38 | vec2(0.7071067691, -0.7071067691), vec2(-0.7071067691, -0.7071067691) 39 | ); 40 | 41 | float smootherstep(float value) { 42 | return value * value * value * (value * (value * 6.0 - 15.0) + 10.0); 43 | } 44 | 45 | vec2 recoverGradient2D(int x, int y) { 46 | return gradients2D[permutations[permutations[x] + y] % gradients2D.length()]; 47 | } 48 | 49 | float computePerlin(vec2 coords) { 50 | // Recovering integer coordinates on the quad 51 | // 52 | // y0+1______x0+1/y0+1 53 | // | | 54 | // | | 55 | // x0/y0______x0+1 56 | 57 | int intX = int(coords.x); 58 | int intY = int(coords.y); 59 | 60 | int x0 = intX & 255; 61 | int y0 = intY & 255; 62 | 63 | // Recovering pseudo-random gradients at each corner of the quad 64 | vec2 leftBotGrad = recoverGradient2D(x0, y0 ); 65 | vec2 rightBotGrad = recoverGradient2D(x0 + 1, y0 ); 66 | vec2 leftTopGrad = recoverGradient2D(x0, y0 + 1); 67 | vec2 rightTopGrad = recoverGradient2D(x0 + 1, y0 + 1); 68 | 69 | // Computing the distance to the coordinates 70 | // _____________ 71 | // | | 72 | // | xWeight | 73 | // |---------X | 74 | // | | yWeight 75 | // |_________|_| 76 | 77 | float xWeight = coords.x - float(intX); 78 | float yWeight = coords.y - float(intY); 79 | 80 | float leftBotDot = dot(vec2(xWeight, yWeight ), leftBotGrad); 81 | float rightBotDot = dot(vec2(xWeight - 1.0, yWeight ), rightBotGrad); 82 | float leftTopDot = dot(vec2(xWeight, yWeight - 1.0), leftTopGrad); 83 | float rightTopDot = dot(vec2(xWeight - 1.0, yWeight - 1.0), rightTopGrad); 84 | 85 | float smoothX = smootherstep(xWeight); 86 | float smoothY = smootherstep(yWeight); 87 | 88 | float botCoeff = mix(leftBotDot, rightBotDot, smoothX); 89 | float topCoeff = mix(leftTopDot, rightTopDot, smoothX); 90 | 91 | return mix(botCoeff, topCoeff, smoothY); 92 | } 93 | 94 | float computeFbm(vec2 coords, int octaveCount) { 95 | float frequency = 1.0; 96 | float amplitude = 1.0; 97 | float total = 0.0; 98 | 99 | for (int i = 0; i < octaveCount; ++i) { 100 | total += computePerlin(coords * frequency) * amplitude; 101 | 102 | frequency *= 2.0; 103 | amplitude *= 0.5; 104 | } 105 | 106 | return (total + 1.0) / 2.0; 107 | } 108 | 109 | void main() { 110 | float noise = computeFbm(vec2(gl_GlobalInvocationID.xy) * uniNoiseFactor, uniOctaveCount); 111 | 112 | ivec2 pixelCoords = ivec2(gl_GlobalInvocationID.xy); 113 | imageStore(uniNoiseMap, pixelCoords, vec4(vec3(noise), 1.0)); 114 | } 115 | -------------------------------------------------------------------------------- /shaders/perlin_noise_3d.comp: -------------------------------------------------------------------------------- 1 | layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; 2 | 3 | layout(r16f, binding = 0) uniform writeonly restrict image3D uniNoiseMap; 4 | uniform float uniNoiseFactor = 0.01; 5 | uniform int uniOctaveCount = 1; 6 | 7 | const int permutations[512] = int[]( 8 | 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 9 | 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 10 | 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 11 | 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 12 | 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 13 | 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 14 | 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 15 | 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 16 | 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 17 | 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 18 | 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 19 | 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 20 | 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 21 | 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 22 | 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 23 | 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 24 | 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 25 | 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 26 | 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 27 | 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 28 | 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 29 | 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 30 | 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 31 | 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 32 | ); 33 | 34 | // Only 12 gradients are necessary; however, 16 are defined to avoid dividing by 12. These form a regular tetrahedron, thus no bias is introduced 35 | const vec3 gradients3D[16] = vec3[]( 36 | vec3(0.7071067691, 0.7071067691, 0.0), vec3(-0.7071067691, 0.7071067691, 0.0), 37 | vec3(0.7071067691, -0.7071067691, 0.0), vec3(-0.7071067691, -0.7071067691, 0.0), 38 | vec3(0.7071067691, 0.0, 0.7071067691), vec3(-0.7071067691, 0.0, 0.7071067691), 39 | vec3(0.7071067691, 0.0, -0.7071067691), vec3(-0.7071067691, 0.0, -0.7071067691), 40 | vec3( 0.0, 0.7071067691, 0.7071067691), vec3( 0.0, -0.7071067691, 0.7071067691), 41 | vec3( 0.0, 0.7071067691, -0.7071067691), vec3( 0.0, -0.7071067691, -0.7071067691), 42 | vec3(0.7071067691, 0.7071067691, 0.0), vec3(-0.7071067691, 0.7071067691, 0.0), 43 | vec3( 0.0, -0.7071067691, 0.7071067691), vec3( 0.0, -0.7071067691, -0.7071067691) 44 | ); 45 | 46 | float smootherstep(float value) { 47 | return value * value * value * (value * (value * 6.0 - 15.0) + 10.0); 48 | } 49 | 50 | vec3 recoverGradient3D(int x, int y, int z) { 51 | return gradients3D[permutations[permutations[permutations[x] + y] + z] % gradients3D.length()]; 52 | } 53 | 54 | float computePerlin(vec3 coords) { 55 | // Recovering integer coordinates on the cube 56 | 57 | int intX = int(coords.x); 58 | int intY = int(coords.y); 59 | int intZ = int(coords.z); 60 | 61 | int x0 = intX & 255; 62 | int y0 = intY & 255; 63 | int z0 = intZ & 255; 64 | 65 | // Recovering pseudo-random gradients at each corner of the quad 66 | vec3 leftBotBackGrad = recoverGradient3D(x0, y0, z0 ); 67 | vec3 leftBotFrontGrad = recoverGradient3D(x0, y0, z0 + 1); 68 | vec3 rightBotBackGrad = recoverGradient3D(x0 + 1, y0, z0 ); 69 | vec3 rightBotFrontGrad = recoverGradient3D(x0 + 1, y0, z0 + 1); 70 | vec3 leftTopBackGrad = recoverGradient3D(x0, y0 + 1, z0 ); 71 | vec3 leftTopFrontGrad = recoverGradient3D(x0, y0 + 1, z0 + 1); 72 | vec3 rightTopBackGrad = recoverGradient3D(x0 + 1, y0 + 1, z0 ); 73 | vec3 rightTopFrontGrad = recoverGradient3D(x0 + 1, y0 + 1, z0 + 1); 74 | 75 | // Computing the distance to the coordinates 76 | // _____________ 77 | // / /| 78 | // / / | 79 | // /___________/ X| 80 | // | |/ | 81 | // | xWeight / zWeight 82 | // |---------X | / 83 | // | | yWeight 84 | // |_________|_|/ 85 | 86 | float xWeight = coords.x - float(intX); 87 | float yWeight = coords.y - float(intY); 88 | float zWeight = coords.z - float(intZ); 89 | 90 | float leftBotBackDot = dot(vec3(xWeight, yWeight , zWeight ), leftBotBackGrad); 91 | float leftBotFrontDot = dot(vec3(xWeight, yWeight , zWeight - 1.0), leftBotFrontGrad); 92 | float rightBotBackDot = dot(vec3(xWeight - 1.0, yWeight , zWeight ), rightBotBackGrad); 93 | float rightBotFrontDot = dot(vec3(xWeight - 1.0, yWeight , zWeight - 1.0), rightBotFrontGrad); 94 | float leftTopBackDot = dot(vec3(xWeight, yWeight - 1.0, zWeight ), leftTopBackGrad); 95 | float leftTopFrontDot = dot(vec3(xWeight, yWeight - 1.0, zWeight - 1.0), leftTopFrontGrad); 96 | float rightTopBackDot = dot(vec3(xWeight - 1.0, yWeight - 1.0, zWeight ), rightTopBackGrad); 97 | float rightTopFrontDot = dot(vec3(xWeight - 1.0, yWeight - 1.0, zWeight - 1.0), rightTopFrontGrad); 98 | 99 | float smoothX = smootherstep(xWeight); 100 | float smoothY = smootherstep(yWeight); 101 | float smoothZ = smootherstep(zWeight); 102 | 103 | float botBackCoeff = mix(leftBotBackDot, rightBotBackDot, smoothX); 104 | float botFrontCoeff = mix(leftBotFrontDot, rightBotFrontDot, smoothX); 105 | float topBackCoeff = mix(leftTopBackDot, rightTopBackDot, smoothX); 106 | float topFrontCoeff = mix(leftTopFrontDot, rightTopFrontDot, smoothX); 107 | 108 | float backCoeff = mix(botBackCoeff, topBackCoeff, smoothY); 109 | float frontCoeff = mix(botFrontCoeff, topFrontCoeff, smoothY); 110 | 111 | return mix(backCoeff, frontCoeff, smoothZ); 112 | } 113 | 114 | float computeFbm(vec3 coords, int octaveCount) { 115 | float frequency = 1.0; 116 | float amplitude = 1.0; 117 | float total = 0.0; 118 | 119 | for (int i = 0; i < octaveCount; ++i) { 120 | total += computePerlin(coords * frequency) * amplitude; 121 | 122 | frequency *= 2.0; 123 | amplitude *= 0.5; 124 | } 125 | 126 | return (total + 1.0) / 2.0; 127 | } 128 | 129 | void main() { 130 | float noise = computeFbm(vec3(gl_GlobalInvocationID.xyz) * uniNoiseFactor, uniOctaveCount); 131 | 132 | ivec3 pixelCoords = ivec3(gl_GlobalInvocationID.xyz); 133 | imageStore(uniNoiseMap, pixelCoords, vec4(vec3(noise), 1.0)); 134 | } 135 | -------------------------------------------------------------------------------- /src/Midgard/DynamicTerrain.cpp: -------------------------------------------------------------------------------- 1 | #include "Midgard/DynamicTerrain.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include // Needed by TracyOpenGL.hpp 11 | #include 12 | 13 | namespace { 14 | 15 | constexpr int heightmapSize = 1024; 16 | 17 | constexpr std::string_view tessCtrlSource = { 18 | #include "terrain.tesc.embed" 19 | }; 20 | 21 | constexpr std::string_view tessEvalSource = { 22 | #include "terrain.tese.embed" 23 | }; 24 | 25 | constexpr std::string_view noiseCompSource = { 26 | #include "perlin_noise_2d.comp.embed" 27 | }; 28 | 29 | constexpr std::string_view colorCompSource = { 30 | #include "terrain_color.comp.embed" 31 | }; 32 | 33 | constexpr std::string_view slopeCompSource = { 34 | #include "slope.comp.embed" 35 | }; 36 | 37 | inline void checkParameters(float& minTessLevel) { 38 | if (minTessLevel <= 0.f) { 39 | Raz::Logger::warn("[DynamicTerrain] The minimal tessellation level can't be 0 or negative; remapping to +epsilon."); 40 | minTessLevel = std::numeric_limits::epsilon(); 41 | } 42 | } 43 | 44 | } // namespace 45 | 46 | DynamicTerrain::DynamicTerrain(Raz::Entity& entity) : Terrain(entity) { 47 | ZoneScopedN("DynamicTerrain::DynamicTerrain"); 48 | 49 | Raz::RenderShaderProgram& terrainProgram = m_entity.getComponent().getMaterials().front().getProgram(); 50 | terrainProgram.setTessellationControlShader(Raz::TessellationControlShader::loadFromSource(tessCtrlSource)); 51 | terrainProgram.setTessellationEvaluationShader(Raz::TessellationEvaluationShader::loadFromSource(tessEvalSource)); 52 | terrainProgram.link(); 53 | 54 | m_noiseMap = Raz::Texture2D::create(heightmapSize, heightmapSize, Raz::TextureColorspace::GRAY, Raz::TextureDataType::FLOAT16); 55 | m_colorMap = Raz::Texture2D::create(heightmapSize, heightmapSize, Raz::TextureColorspace::RGBA, Raz::TextureDataType::BYTE); 56 | m_slopeMap = Raz::Texture2D::create(heightmapSize, heightmapSize, Raz::TextureColorspace::RGBA, Raz::TextureDataType::FLOAT16); 57 | 58 | #if !defined(USE_OPENGL_ES) 59 | if (Raz::Renderer::checkVersion(4, 3)) { 60 | Raz::Renderer::setLabel(Raz::RenderObjectType::TEXTURE, m_noiseMap->getIndex(), "Noise map"); 61 | Raz::Renderer::setLabel(Raz::RenderObjectType::TEXTURE, m_colorMap->getIndex(), "Color map"); 62 | Raz::Renderer::setLabel(Raz::RenderObjectType::TEXTURE, m_slopeMap->getIndex(), "Slope map"); 63 | } 64 | #endif 65 | 66 | m_noiseProgram.setShader(Raz::ComputeShader::loadFromSource(noiseCompSource)); 67 | m_noiseProgram.setAttribute(8, "uniOctaveCount"); 68 | m_noiseProgram.setImageTexture(m_noiseMap, "uniNoiseMap", Raz::ImageTextureUsage::WRITE); 69 | 70 | m_colorProgram.setShader(Raz::ComputeShader::loadFromSource(colorCompSource)); 71 | m_colorProgram.setImageTexture(m_noiseMap, "uniHeightmap", Raz::ImageTextureUsage::READ); 72 | m_colorProgram.setImageTexture(m_colorMap, "uniColorMap", Raz::ImageTextureUsage::WRITE); 73 | 74 | m_slopeProgram.setShader(Raz::ComputeShader::loadFromSource(slopeCompSource)); 75 | m_slopeProgram.setImageTexture(m_noiseMap, "uniHeightmap", Raz::ImageTextureUsage::READ); 76 | m_slopeProgram.setImageTexture(m_slopeMap, "uniSlopeMap", Raz::ImageTextureUsage::WRITE); 77 | 78 | terrainProgram.setTexture(m_noiseMap, "uniHeightmap"); 79 | terrainProgram.setTexture(m_colorMap, Raz::MaterialTexture::BaseColor); 80 | 81 | computeNoiseMap(0.01f); 82 | computeSlopeMap(); 83 | computeColorMap(); 84 | } 85 | 86 | DynamicTerrain::DynamicTerrain(Raz::Entity& entity, unsigned int width, unsigned int depth, 87 | float heightFactor, float flatness, float minTessLevel) : DynamicTerrain(entity) { 88 | ZoneScopedN("DynamicTerrain::DynamicTerrain"); 89 | 90 | Raz::RenderShaderProgram& terrainProgram = entity.getComponent().getMaterials().front().getProgram(); 91 | terrainProgram.setAttribute(Raz::Vec2u(width, depth), "uniTerrainSize"); 92 | terrainProgram.sendAttributes(); 93 | 94 | DynamicTerrain::generate(width, depth, heightFactor, flatness, minTessLevel); 95 | } 96 | 97 | void DynamicTerrain::setParameters(float minTessLevel, float heightFactor, float flatness) { 98 | ZoneScopedN("DynamicTerrain::setParameters"); 99 | 100 | Terrain::setParameters(heightFactor, flatness); 101 | 102 | ::checkParameters(minTessLevel); 103 | m_minTessLevel = minTessLevel; 104 | 105 | Raz::RenderShaderProgram& terrainProgram = m_entity.getComponent().getMaterials().front().getProgram(); 106 | terrainProgram.setAttribute(m_minTessLevel, "uniTessLevel"); 107 | terrainProgram.setAttribute(m_heightFactor, "uniHeightFactor"); 108 | terrainProgram.setAttribute(m_flatness, "uniFlatness"); 109 | terrainProgram.sendAttributes(); 110 | 111 | m_slopeProgram.setAttribute(m_heightFactor, "uniHeightFactor"); 112 | m_slopeProgram.setAttribute(m_flatness, "uniFlatness"); 113 | m_slopeProgram.sendAttributes(); 114 | } 115 | 116 | void DynamicTerrain::generate(unsigned int width, unsigned int depth, float heightFactor, float flatness, float minTessLevel) { 117 | ZoneScopedN("DynamicTerrain::generate"); 118 | 119 | auto& mesh = m_entity.getComponent(); 120 | mesh.getSubmeshes().resize(1); 121 | 122 | constexpr int patchCount = 20; 123 | 124 | const float strideX = static_cast(width) / patchCount; 125 | const float strideZ = static_cast(depth) / patchCount; 126 | const float strideU = 1.f / patchCount; 127 | const float strideV = 1.f / patchCount; 128 | 129 | // Creating patches: 130 | // 131 | // 1------3 132 | // | | Z 133 | // | | ^ 134 | // | | | 135 | // 0------2 ---> X 136 | 137 | std::vector& vertices = mesh.getSubmeshes().front().getVertices(); 138 | vertices.resize(patchCount * patchCount * 4); 139 | 140 | for (std::size_t widthPatchIndex = 0; widthPatchIndex < patchCount; ++widthPatchIndex) { 141 | const float patchStartX = -static_cast(width) * 0.5f + strideX * static_cast(widthPatchIndex); 142 | const float patchStartU = strideU * static_cast(widthPatchIndex); 143 | const std::size_t finalWidthPatchIndex = widthPatchIndex * patchCount; 144 | 145 | for (std::size_t depthPatchIndex = 0; depthPatchIndex < patchCount; ++depthPatchIndex) { 146 | const float patchStartZ = -static_cast(depth) * 0.5f + strideZ * static_cast(depthPatchIndex); 147 | const float patchStartV = strideV * static_cast(depthPatchIndex); 148 | const std::size_t finalPatchIndex = (finalWidthPatchIndex + depthPatchIndex) * 4; 149 | 150 | vertices[finalPatchIndex] = Raz::Vertex{ Raz::Vec3f(patchStartX, 0.f, patchStartZ), 151 | Raz::Vec2f(patchStartU, patchStartV), 152 | Raz::Axis::Y, 153 | Raz::Axis::X }; 154 | vertices[finalPatchIndex + 1] = Raz::Vertex{ Raz::Vec3f(patchStartX, 0.f, patchStartZ + strideZ), 155 | Raz::Vec2f(patchStartU, patchStartV + strideV), 156 | Raz::Axis::Y, 157 | Raz::Axis::X }; 158 | vertices[finalPatchIndex + 2] = Raz::Vertex{ Raz::Vec3f(patchStartX + strideX, 0.f, patchStartZ), 159 | Raz::Vec2f(patchStartU + strideU, patchStartV), 160 | Raz::Axis::Y, 161 | Raz::Axis::X }; 162 | vertices[finalPatchIndex + 3] = Raz::Vertex{ Raz::Vec3f(patchStartX + strideX, 0.f, patchStartZ + strideZ), 163 | Raz::Vec2f(patchStartU + strideU, patchStartV + strideV), 164 | Raz::Axis::Y, 165 | Raz::Axis::X }; 166 | } 167 | } 168 | 169 | m_entity.getComponent().load(mesh, Raz::RenderMode::PATCH); 170 | Raz::Renderer::setPatchVertexCount(4); // Since the terrain is made of quads 171 | 172 | setParameters(minTessLevel, heightFactor, flatness); 173 | } 174 | 175 | const Raz::Texture2D& DynamicTerrain::computeNoiseMap(float factor) { 176 | ZoneScopedN("DynamicTerrain::computeNoiseMap"); 177 | TracyGpuZone("DynamicTerrain::computeNoiseMap") 178 | 179 | m_noiseProgram.setAttribute(factor, "uniNoiseFactor"); 180 | m_noiseProgram.sendAttributes(); 181 | m_noiseProgram.execute(heightmapSize, heightmapSize); 182 | 183 | return *m_noiseMap; 184 | } 185 | 186 | const Raz::Texture2D& DynamicTerrain::computeColorMap() { 187 | ZoneScopedN("DynamicTerrain::computeColorMap"); 188 | TracyGpuZone("DynamicTerrain::computeColorMap") 189 | 190 | m_colorProgram.execute(heightmapSize, heightmapSize); 191 | 192 | return *m_colorMap; 193 | } 194 | 195 | const Raz::Texture2D& DynamicTerrain::computeSlopeMap() { 196 | ZoneScopedN("DynamicTerrain::computeSlopeMap"); 197 | TracyGpuZone("DynamicTerrain::computeSlopeMap") 198 | 199 | m_slopeProgram.execute(heightmapSize, heightmapSize); 200 | 201 | return *m_slopeMap; 202 | } 203 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "Midgard/StaticTerrain.hpp" 2 | #if !defined(USE_OPENGL_ES) 3 | #include "Midgard/DynamicTerrain.hpp" 4 | #endif 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace Raz::Literals; 16 | 17 | namespace { 18 | 19 | constexpr unsigned int terrainWidth = 512; 20 | constexpr unsigned int terrainDepth = 512; 21 | 22 | constexpr std::string_view fogShaderSource = { 23 | #include "fog.frag.embed" 24 | }; 25 | 26 | } // namespace 27 | 28 | int main() { 29 | try { 30 | //////////////////// 31 | // Initialization // 32 | //////////////////// 33 | 34 | Raz::Application app; 35 | Raz::World& world = app.addWorld(3); 36 | 37 | Raz::Logger::setLoggingLevel(Raz::LoggingLevel::ALL); 38 | 39 | /////////////// 40 | // Rendering // 41 | /////////////// 42 | 43 | auto& renderSystem = world.addSystem(1280u, 720u, "Midgard", Raz::WindowSetting::DEFAULT, 2); 44 | Raz::Window& window = renderSystem.getWindow(); 45 | 46 | // Allowing to quit the application with the Escape key 47 | window.addKeyCallback(Raz::Keyboard::ESCAPE, [&app] (float /* deltaTime */) noexcept { app.quit(); }); 48 | 49 | // Allowing to quit the application when the close button is clicked 50 | window.setCloseCallback([&app] () noexcept { app.quit(); }); 51 | 52 | /////////////////// 53 | // Camera entity // 54 | /////////////////// 55 | 56 | Raz::Entity& camera = world.addEntity(); 57 | auto& cameraComp = camera.addComponent(window.getWidth(), window.getHeight(), 45_deg, 0.1f, 1000.f); 58 | auto& cameraTrans = camera.addComponent(Raz::Vec3f(0.f, 30.f, 120.f)); 59 | 60 | ///////// 61 | // Sun // 62 | ///////// 63 | 64 | Raz::Entity& light = world.addEntity(); 65 | light.addComponent(Raz::LightType::DIRECTIONAL, // Type 66 | Raz::Vec3f(0.f, -1.f, -1.f).normalize(), // Direction 67 | 3.f, // Energy 68 | Raz::ColorPreset::White); // Color (RGB) 69 | light.addComponent(); 70 | 71 | ////////////// 72 | // Fog pass // 73 | ////////////// 74 | 75 | Raz::RenderGraph& renderGraph = renderSystem.getRenderGraph(); 76 | Raz::RenderPass& geometryPass = renderGraph.getGeometryPass(); 77 | 78 | const Raz::Texture2DPtr depthBuffer = Raz::Texture2D::create(window.getWidth(), window.getHeight(), Raz::TextureColorspace::DEPTH); 79 | const Raz::Texture2DPtr colorBuffer = Raz::Texture2D::create(window.getWidth(), window.getHeight(), Raz::TextureColorspace::RGB); 80 | 81 | #if !defined(USE_OPENGL_ES) 82 | if (Raz::Renderer::checkVersion(4, 3)) { 83 | Raz::Renderer::setLabel(Raz::RenderObjectType::TEXTURE, depthBuffer->getIndex(), "Depth buffer"); 84 | Raz::Renderer::setLabel(Raz::RenderObjectType::TEXTURE, colorBuffer->getIndex(), "Color buffer"); 85 | } 86 | #endif 87 | 88 | geometryPass.setWriteDepthTexture(depthBuffer); 89 | geometryPass.addWriteColorTexture(colorBuffer, 0); 90 | 91 | Raz::RenderPass& fogPass = renderGraph.addNode(Raz::FragmentShader::loadFromSource(fogShaderSource), "Fog"); 92 | 93 | fogPass.addReadTexture(depthBuffer, "uniSceneBuffers.depth"); 94 | fogPass.addReadTexture(colorBuffer, "uniSceneBuffers.color"); 95 | fogPass.getProgram().setAttribute(light.getComponent().getDirection(), "uniSunDir"); 96 | fogPass.getProgram().setAttribute(0.1f, "uniFogDensity"); 97 | fogPass.getProgram().sendAttributes(); 98 | 99 | geometryPass.addChildren(fogPass); 100 | 101 | ///////////// 102 | // Terrain // 103 | ///////////// 104 | 105 | #if !defined(USE_OPENGL_ES) 106 | Raz::Entity& dynamicTerrainEntity = world.addEntity(); 107 | DynamicTerrain dynamicTerrain(dynamicTerrainEntity, terrainWidth, terrainDepth, 30.f, 3.f); 108 | #endif 109 | 110 | Raz::Entity& staticTerrainEntity = world.addEntity(); 111 | StaticTerrain staticTerrain(staticTerrainEntity, terrainWidth, terrainDepth, 30.f, 3.f); 112 | 113 | const Raz::Image& colorMap = staticTerrain.computeColorMap(); 114 | const Raz::Image& normalMap = staticTerrain.computeNormalMap(); 115 | const Raz::Image& slopeMap = staticTerrain.computeSlopeMap(); 116 | 117 | #if !defined(USE_OPENGL_ES) 118 | Raz::ImageFormat::save("colorMap.png", colorMap); 119 | Raz::ImageFormat::save("normalMap.png", normalMap); 120 | Raz::ImageFormat::save("slopeMap.hdr", slopeMap); 121 | #endif 122 | 123 | ///////////////////// 124 | // Camera controls // 125 | ///////////////////// 126 | 127 | float cameraSpeed = 1.f; 128 | window.addKeyCallback(Raz::Keyboard::LEFT_SHIFT, 129 | [&cameraSpeed] (float /* deltaTime */) noexcept { cameraSpeed = 2.f; }, 130 | Raz::Input::ONCE, 131 | [&cameraSpeed] () noexcept { cameraSpeed = 1.f; }); 132 | window.addKeyCallback(Raz::Keyboard::SPACE, [&cameraTrans, &cameraSpeed] (float deltaTime) { 133 | cameraTrans.move(0.f, (10.f * deltaTime) * cameraSpeed, 0.f); 134 | }); 135 | window.addKeyCallback(Raz::Keyboard::V, [&cameraTrans, &cameraSpeed] (float deltaTime) { 136 | cameraTrans.move(0.f, (-10.f * deltaTime) * cameraSpeed, 0.f); 137 | }); 138 | window.addKeyCallback(Raz::Keyboard::W, [&cameraTrans, &cameraComp, &cameraSpeed] (float deltaTime) { 139 | const float moveVal = (-10.f * deltaTime) * cameraSpeed; 140 | 141 | cameraTrans.move(0.f, 0.f, moveVal); 142 | cameraComp.setOrthographicBound(cameraComp.getOrthographicBound() + moveVal); 143 | }); 144 | window.addKeyCallback(Raz::Keyboard::S, [&cameraTrans, &cameraComp, &cameraSpeed] (float deltaTime) { 145 | const float moveVal = (10.f * deltaTime) * cameraSpeed; 146 | 147 | cameraTrans.move(0.f, 0.f, moveVal); 148 | cameraComp.setOrthographicBound(cameraComp.getOrthographicBound() + moveVal); 149 | }); 150 | window.addKeyCallback(Raz::Keyboard::A, [&cameraTrans, &cameraSpeed] (float deltaTime) { 151 | cameraTrans.move((-10.f * deltaTime) * cameraSpeed, 0.f, 0.f); 152 | }); 153 | window.addKeyCallback(Raz::Keyboard::D, [&cameraTrans, &cameraSpeed] (float deltaTime) { 154 | cameraTrans.move((10.f * deltaTime) * cameraSpeed, 0.f, 0.f); 155 | }); 156 | 157 | window.setMouseScrollCallback([&cameraComp] (double /* xOffset */, double yOffset) { 158 | const float newFov = Raz::Degreesf(cameraComp.getFieldOfView()).value + static_cast(-yOffset) * 2.f; 159 | cameraComp.setFieldOfView(Raz::Degreesf(std::clamp(newFov, 15.f, 90.f))); 160 | }); 161 | 162 | bool cameraLocked = true; // To allow moving the camera using the mouse 163 | 164 | window.addMouseButtonCallback(Raz::Mouse::RIGHT_CLICK, [&cameraLocked, &window] (float) { 165 | cameraLocked = false; 166 | window.setCursorState(Raz::Cursor::DISABLED); 167 | }, Raz::Input::ONCE, [&cameraLocked, &window] () { 168 | cameraLocked = true; 169 | window.setCursorState(Raz::Cursor::NORMAL); 170 | }); 171 | 172 | window.setMouseMoveCallback([&cameraLocked, &cameraTrans, &window] (double xMove, double yMove) { 173 | if (cameraLocked) 174 | return; 175 | 176 | // Dividing move by window size to scale between -1 and 1 177 | cameraTrans.rotate(-90_deg * yMove / window.getHeight(), 178 | -90_deg * xMove / window.getWidth()); 179 | }); 180 | 181 | ///////////// 182 | // Overlay // 183 | ///////////// 184 | 185 | Raz::OverlayWindow& overlay = window.getOverlay().addWindow("Midgard", Raz::Vec2f(-1.f)); 186 | 187 | overlay.addLabel("Press WASD to fly the camera around,"); 188 | overlay.addLabel("Space/V to go up/down,"); 189 | overlay.addLabel("& Shift to move faster."); 190 | overlay.addLabel("Hold the right mouse button to rotate the camera."); 191 | 192 | overlay.addSeparator(); 193 | 194 | #if !defined(USE_OPENGL_ES) 195 | Raz::OverlayTexture& dynamicNoiseTexture = overlay.addTexture(dynamicTerrain.getNoiseMap(), 125, 125); 196 | Raz::OverlayTexture& dynamicColorTexture = overlay.addTexture(dynamicTerrain.getColorMap(), 125, 125); 197 | Raz::OverlayTexture& dynamicSlopeTexture = overlay.addTexture(dynamicTerrain.getSlopeMap(), 125, 125); 198 | #endif 199 | 200 | Raz::Texture2D colorTexture(colorMap, false); 201 | Raz::Texture2D normalTexture(normalMap, false); 202 | Raz::Texture2D slopeTexture(slopeMap, false); 203 | 204 | [[maybe_unused]] Raz::OverlayTexture& staticColorTexture = overlay.addTexture(colorTexture, 125, 125); 205 | [[maybe_unused]] Raz::OverlayTexture& staticNormalTexture = overlay.addTexture(normalTexture, 125, 125); 206 | [[maybe_unused]] Raz::OverlayTexture& staticSlopeTexture = overlay.addTexture(slopeTexture, 125, 125); 207 | 208 | overlay.addSeparator(); 209 | 210 | #if !defined(USE_OPENGL_ES) 211 | Raz::OverlaySlider& dynamicNoiseMapFactorSlider = overlay.addSlider("Noise map factor", [&dynamicTerrain] (float value) { 212 | dynamicTerrain.computeNoiseMap(value); 213 | dynamicTerrain.computeColorMap(); 214 | dynamicTerrain.computeSlopeMap(); 215 | }, 0.001f, 0.1f, 0.01f); 216 | 217 | Raz::OverlaySlider& dynamicMinTessLevelSlider = overlay.addSlider("Min tess. level", [&dynamicTerrain] (float value) { 218 | dynamicTerrain.setMinTessellationLevel(value); 219 | }, 0.001f, 64.f, 12.f); 220 | 221 | Raz::OverlaySlider& dynamicHeightFactorSlider = overlay.addSlider("Height factor", [&dynamicTerrain] (float value) { 222 | dynamicTerrain.setHeightFactor(value); 223 | dynamicTerrain.computeSlopeMap(); 224 | }, 0.001f, 50.f, 30.f); 225 | 226 | Raz::OverlaySlider& dynamicFlatnessSlider = overlay.addSlider("Flatness", [&dynamicTerrain] (float value) { 227 | dynamicTerrain.setFlatness(value); 228 | dynamicTerrain.computeSlopeMap(); 229 | }, 1.f, 10.f, 3.f); 230 | #endif 231 | 232 | [[maybe_unused]] Raz::OverlaySlider& staticHeightFactorSlider = overlay.addSlider("Height factor", [&staticTerrain, &normalTexture, &slopeTexture] (float value) { 233 | staticTerrain.setHeightFactor(value); 234 | normalTexture.load(staticTerrain.computeNormalMap()); 235 | slopeTexture.load(staticTerrain.computeSlopeMap()); 236 | }, 0.001f, 50.f, 30.f); 237 | 238 | [[maybe_unused]] Raz::OverlaySlider& staticFlatnessSlider = overlay.addSlider("Flatness", [&staticTerrain, &normalTexture, &slopeTexture] (float value) { 239 | staticTerrain.setFlatness(value); 240 | normalTexture.load(staticTerrain.computeNormalMap()); 241 | slopeTexture.load(staticTerrain.computeSlopeMap()); 242 | }, 1.f, 10.f, 3.f); 243 | 244 | overlay.addSlider("Fog density", [&fogPass] (float value) { 245 | fogPass.getProgram().setAttribute(value, "uniFogDensity"); 246 | fogPass.getProgram().sendAttributes(); 247 | }, 0.f, 1.f, 0.1f); 248 | 249 | overlay.addSeparator(); 250 | 251 | #if !defined(USE_OPENGL_ES) 252 | overlay.addCheckbox("Dynamic terrain", [&] () noexcept { 253 | dynamicTerrainEntity.enable(); 254 | dynamicNoiseTexture.enable(); 255 | dynamicColorTexture.enable(); 256 | dynamicSlopeTexture.enable(); 257 | dynamicNoiseMapFactorSlider.enable(); 258 | dynamicMinTessLevelSlider.enable(); 259 | dynamicHeightFactorSlider.enable(); 260 | dynamicFlatnessSlider.enable(); 261 | 262 | staticTerrainEntity.disable(); 263 | staticColorTexture.disable(); 264 | staticNormalTexture.disable(); 265 | staticSlopeTexture.disable(); 266 | staticHeightFactorSlider.disable(); 267 | staticFlatnessSlider.disable(); 268 | }, [&] () noexcept { 269 | staticTerrainEntity.enable(); 270 | staticColorTexture.enable(); 271 | staticNormalTexture.enable(); 272 | staticSlopeTexture.enable(); 273 | staticHeightFactorSlider.enable(); 274 | staticFlatnessSlider.enable(); 275 | 276 | dynamicTerrainEntity.disable(); 277 | dynamicNoiseTexture.disable(); 278 | dynamicColorTexture.disable(); 279 | dynamicSlopeTexture.disable(); 280 | dynamicNoiseMapFactorSlider.disable(); 281 | dynamicMinTessLevelSlider.disable(); 282 | dynamicHeightFactorSlider.disable(); 283 | dynamicFlatnessSlider.disable(); 284 | }, true); 285 | 286 | // Disabling all static elements at first, since we want the dynamic terrain to be used by default 287 | staticTerrainEntity.disable(); 288 | staticColorTexture.disable(); 289 | staticNormalTexture.disable(); 290 | staticSlopeTexture.disable(); 291 | staticHeightFactorSlider.disable(); 292 | staticFlatnessSlider.disable(); 293 | #endif 294 | 295 | overlay.addSeparator(); 296 | 297 | overlay.addFrameTime("Frame time: %.3f ms/frame"); 298 | overlay.addFpsCounter("FPS: %.1f"); 299 | 300 | ////////////////////////// 301 | // Starting application // 302 | ////////////////////////// 303 | 304 | app.run(); 305 | } catch (const std::exception& exception) { 306 | Raz::Logger::error(exception.what()); 307 | } 308 | 309 | return EXIT_SUCCESS; 310 | } 311 | -------------------------------------------------------------------------------- /src/Midgard/StaticTerrain.cpp: -------------------------------------------------------------------------------- 1 | #include "Midgard/StaticTerrain.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace { 13 | 14 | constexpr Raz::Vec3b waterColor(0, 0, 255); 15 | constexpr Raz::Vec3b grassColor(62, 126, 0); 16 | constexpr Raz::Vec3b groundColor(157, 110, 94); 17 | constexpr Raz::Vec3b rockColor(127, 127, 127); 18 | constexpr Raz::Vec3b snowColor(255, 255, 255); 19 | 20 | } // namespace 21 | 22 | StaticTerrain::StaticTerrain(Raz::Entity& entity, unsigned int width, unsigned int depth, float heightFactor, float flatness) : StaticTerrain(entity) { 23 | ZoneScopedN("StaticTerrain::StaticTerrain"); 24 | 25 | StaticTerrain::generate(width, depth, heightFactor, flatness); 26 | } 27 | 28 | void StaticTerrain::setParameters(float heightFactor, float flatness) { 29 | ZoneScopedN("StaticTerrain::setParameters"); 30 | 31 | checkParameters(heightFactor, flatness); 32 | 33 | remapVertices(heightFactor, flatness); 34 | 35 | m_heightFactor = heightFactor; 36 | m_flatness = flatness; 37 | m_invFlatness = 1.f / flatness; 38 | } 39 | 40 | void StaticTerrain::generate(unsigned int width, unsigned int depth, float heightFactor, float flatness) { 41 | ZoneScopedN("StaticTerrain::generate"); 42 | 43 | m_width = width; 44 | m_depth = depth; 45 | Terrain::setParameters(heightFactor, flatness); 46 | 47 | auto& mesh = m_entity.getComponent(); 48 | mesh.getSubmeshes().resize(1); 49 | 50 | // Computing vertices 51 | 52 | std::vector& vertices = mesh.getSubmeshes().front().getVertices(); 53 | vertices.resize(m_width * m_depth); 54 | 55 | Raz::Threading::parallelize(0, vertices.size(), [this, &vertices] (const Raz::Threading::IndexRange& range) noexcept { 56 | ZoneScopedN("StaticTerrain::generate"); 57 | 58 | for (std::size_t i = range.beginIndex; i < range.endIndex; ++i) { 59 | const auto xCoord = static_cast(i % m_width); 60 | const auto yCoord = static_cast(i / m_width); 61 | 62 | // float noiseValue = Raz::PerlinNoise::compute2D(xCoord / 1000.f, yCoord / 1000.f, 8, true); 63 | // noiseValue = Raz::PerlinNoise::compute2D(xCoord / 1000.f + noiseValue, yCoord / 1000.f + noiseValue, 8, true); 64 | // noiseValue = Raz::PerlinNoise::compute2D(xCoord / 1000.f + noiseValue, yCoord / 1000.f + noiseValue, 8, true); 65 | 66 | float noiseValue = Raz::PerlinNoise::compute2D(xCoord / 100.f, yCoord / 100.f, 8, true); 67 | noiseValue = std::pow(noiseValue, m_flatness); 68 | 69 | const Raz::Vec2f scaledCoords = (Raz::Vec2f(xCoord, yCoord) - static_cast(m_width) * 0.5f) * 0.5f; 70 | 71 | Raz::Vertex& vertex = vertices[i]; 72 | vertex.position = Raz::Vec3f(scaledCoords.x(), noiseValue * m_heightFactor, scaledCoords.y()); 73 | vertex.texcoords = Raz::Vec2f(xCoord / static_cast(m_width), yCoord / static_cast(m_depth)); 74 | } 75 | }); 76 | 77 | // for (unsigned int j = 0; j < m_depth; ++j) { 78 | // const unsigned int depthIndex = j * m_depth; 79 | // 80 | // for (unsigned int i = 0; i < m_width; ++i) { 81 | // Raz::Vec2f coords(static_cast(i), static_cast(j)); 82 | // 83 | // // float noiseValue = Raz::PerlinNoise::compute2D(coords.x() / 1000.f, coords.y() / 1000.f, 8, true); 84 | // // noiseValue = Raz::PerlinNoise::compute2D(coords.x() / 1000.f + noiseValue, coords.y() / 1000.f + noiseValue, 8, true); 85 | // // noiseValue = Raz::PerlinNoise::compute2D(coords.x() / 1000.f + noiseValue, coords.y() / 1000.f + noiseValue, 8, true); 86 | // 87 | // float noiseValue = Raz::PerlinNoise::compute2D(coords.x() / 100.f, coords.y() / 100.f, 8, true); 88 | // noiseValue = std::pow(noiseValue, flatness); 89 | // 90 | // coords = (coords - static_cast(m_width) / 2.f) / 2.f; 91 | // 92 | // Raz::Vertex& vertex = vertices[depthIndex + i]; 93 | // vertex.position = Raz::Vec3f(coords.x(), noiseValue * heightFactor, coords.y()); 94 | // vertex.texcoords = Raz::Vec2f(static_cast(i) / static_cast(m_width), static_cast(j) / static_cast(m_depth)); 95 | // } 96 | // } 97 | 98 | computeNormals(); 99 | 100 | // Computing indices 101 | 102 | std::vector& indices = mesh.getSubmeshes().front().getTriangleIndices(); 103 | indices.resize(vertices.size() * 6); 104 | 105 | for (unsigned int j = 0; j < m_depth - 1; ++j) { 106 | const unsigned int depthIndex = j * m_depth; 107 | 108 | for (unsigned int i = 0; i < m_width - 1; ++i) { 109 | // i i + 1 110 | // v v 111 | // --------- <- j * width 112 | // | /| 113 | // | / | 114 | // | / | 115 | // |/______| <- (j + 1) * width 116 | // ^ ^ 117 | // j + 1 j + 1 + i + 1 118 | 119 | const unsigned int finalIndex = (depthIndex + i) * 6; 120 | 121 | indices[finalIndex ] = j * m_width + i; 122 | indices[finalIndex + 1] = (j + 1) * m_width + i; 123 | indices[finalIndex + 2] = j * m_width + i + 1; 124 | indices[finalIndex + 3] = j * m_width + i + 1; 125 | indices[finalIndex + 4] = (j + 1) * m_width + i; 126 | indices[finalIndex + 5] = (j + 1) * m_width + i + 1; 127 | } 128 | } 129 | 130 | m_entity.getComponent().load(mesh); 131 | } 132 | 133 | const Raz::Image& StaticTerrain::computeColorMap() { 134 | ZoneScopedN("StaticTerrain::computeColorMap"); 135 | 136 | m_colorMap = Raz::Image(m_width, m_depth, Raz::ImageColorspace::RGB); 137 | auto* imgData = static_cast(m_colorMap.getDataPtr()); 138 | 139 | const std::vector& vertices = m_entity.getComponent().getSubmeshes().front().getVertices(); 140 | 141 | Raz::Threading::parallelize(0, vertices.size(), [this, &vertices, imgData] (const Raz::Threading::IndexRange& range) noexcept { 142 | ZoneScopedN("StaticTerrain::computeColorMap"); 143 | 144 | for (std::size_t i = range.beginIndex; i < range.endIndex; ++i) { 145 | const float noiseValue = std::pow(vertices[i].position.y() / m_heightFactor, m_invFlatness); 146 | const Raz::Vec3b pixelValue = (noiseValue < 0.33f ? Raz::MathUtils::lerp(waterColor, grassColor, noiseValue * 3.f) 147 | : (noiseValue < 0.5f ? Raz::MathUtils::lerp(grassColor, groundColor, (noiseValue - 0.33f) * 5.75f) 148 | : (noiseValue < 0.66f ? Raz::MathUtils::lerp(groundColor, rockColor, (noiseValue - 0.5f) * 6.f) 149 | : Raz::MathUtils::lerp(rockColor, snowColor, (noiseValue - 0.66f) * 3.f)))); 150 | 151 | const std::size_t dataStride = i * 3; 152 | imgData[dataStride] = pixelValue.x(); 153 | imgData[dataStride + 1] = pixelValue.y(); 154 | imgData[dataStride + 2] = pixelValue.z(); 155 | } 156 | }); 157 | 158 | m_entity.getComponent().getMaterials().front().getProgram().setTexture(Raz::Texture2D::create(m_colorMap, true, true), 159 | Raz::MaterialTexture::BaseColor); 160 | 161 | return m_colorMap; 162 | } 163 | 164 | const Raz::Image& StaticTerrain::computeNormalMap() { 165 | ZoneScopedN("StaticTerrain::computeNormalMap"); 166 | 167 | m_normalMap = Raz::Image(m_width, m_depth, Raz::ImageColorspace::RGB); 168 | auto* imgData = static_cast(m_normalMap.getDataPtr()); 169 | 170 | const std::vector& vertices = m_entity.getComponent().getSubmeshes().front().getVertices(); 171 | 172 | Raz::Threading::parallelize(0, vertices.size(), [&vertices, imgData] (const Raz::Threading::IndexRange& range) noexcept { 173 | ZoneScopedN("StaticTerrain::computeNormalMap"); 174 | 175 | for (std::size_t i = range.beginIndex; i < range.endIndex; ++i) { 176 | const Raz::Vec3f& normal = vertices[i].normal; 177 | 178 | const std::size_t dataStride = i * 3; 179 | imgData[dataStride] = static_cast(std::max(0.f, normal.x()) * 255.f); 180 | imgData[dataStride + 1] = static_cast(std::max(0.f, normal.y()) * 255.f); 181 | imgData[dataStride + 2] = static_cast(std::max(0.f, normal.z()) * 255.f); 182 | } 183 | }); 184 | 185 | return m_normalMap; 186 | } 187 | 188 | const Raz::Image& StaticTerrain::computeSlopeMap() { 189 | ZoneScopedN("StaticTerrain::computeSlopeMap"); 190 | 191 | m_slopeMap = Raz::Image(m_width, m_depth, Raz::ImageColorspace::RGB, Raz::ImageDataType::FLOAT); 192 | 193 | const std::vector& vertices = m_entity.getComponent().getSubmeshes().front().getVertices(); 194 | 195 | Raz::Threading::parallelize(1, m_depth - 1, [this, &vertices] (const Raz::Threading::IndexRange& range) noexcept { 196 | ZoneScopedN("StaticTerrain::computeSlopeMap"); 197 | 198 | for (std::size_t depthIndex = range.beginIndex; depthIndex < range.endIndex; ++depthIndex) { 199 | for (std::size_t widthIndex = 1; widthIndex < m_width - 1; ++widthIndex) { 200 | const float topHeight = vertices[(depthIndex - 1) * m_width + widthIndex].position.y(); 201 | const float leftHeight = vertices[depthIndex * m_width + widthIndex - 1].position.y(); 202 | const float rightHeight = vertices[depthIndex * m_width + widthIndex + 1].position.y(); 203 | const float botHeight = vertices[(depthIndex + 1) * m_width + widthIndex].position.y(); 204 | 205 | const Raz::Vec2f slopeVec(leftHeight - rightHeight, topHeight - botHeight); 206 | const float slopeStrength = slopeVec.computeLength() * 0.5f; 207 | 208 | m_slopeMap.setPixel(widthIndex, depthIndex, Raz::Vec3f(slopeVec.normalize(), slopeStrength)); 209 | } 210 | } 211 | }); 212 | 213 | return m_slopeMap; 214 | } 215 | 216 | void StaticTerrain::computeNormals() { 217 | ZoneScopedN("StaticTerrain::computeNormals"); 218 | 219 | std::vector& vertices = m_entity.getComponent().getSubmeshes().front().getVertices(); 220 | 221 | Raz::Threading::parallelize(1, m_depth - 1, [this, &vertices] (const Raz::Threading::IndexRange& range) noexcept { 222 | ZoneScopedN("StaticTerrain::computeNormals"); 223 | 224 | for (std::size_t depthIndex = range.beginIndex; depthIndex < range.endIndex; ++depthIndex) { 225 | const std::size_t depthStride = depthIndex * m_width; 226 | 227 | for (std::size_t widthIndex = 1; widthIndex < m_width - 1; ++widthIndex) { 228 | // topHeight (depth - 1) 229 | // x 230 | // | 231 | // leftHeight (width - 1) x----X----x rightHeight (width + 1) 232 | // | 233 | // x 234 | // botHeight (depth + 1) 235 | 236 | // Using finite differences 237 | 238 | const float topHeight = vertices[(depthIndex - 1) * m_width + widthIndex].position.y(); 239 | const float leftHeight = vertices[depthStride + widthIndex - 1].position.y(); 240 | const float rightHeight = vertices[depthStride + widthIndex + 1].position.y(); 241 | const float botHeight = vertices[(depthIndex + 1) * m_width + widthIndex].position.y(); 242 | 243 | Raz::Vertex& midVertex = vertices[depthStride + widthIndex]; 244 | midVertex.normal = Raz::Vec3f(leftHeight - rightHeight, 0.1f, topHeight - botHeight).normalize(); 245 | midVertex.tangent = Raz::Vec3f(midVertex.normal.z(), midVertex.normal.x(), midVertex.normal.y()); 246 | 247 | // Using cross products 248 | 249 | // const Raz::Vec3f& topPos = vertices[(depthIndex - 1) * m_width + widthIndex].position; 250 | // const Raz::Vec3f& leftPos = vertices[depthStride + widthIndex - 1].position; 251 | // const Raz::Vec3f& rightPos = vertices[depthStride + widthIndex + 1].position; 252 | // const Raz::Vec3f& botPos = vertices[(depthIndex + 1) * m_width + widthIndex].position; 253 | // 254 | // // topDir 255 | // // ^ 256 | // // leftDir <-|-> rightDir 257 | // // v 258 | // // botDir 259 | // 260 | // Raz::Vertex& midVertex = vertices[depthStride + widthIndex]; 261 | // const Raz::Vec3f topDir = topPos - midVertex.position; 262 | // const Raz::Vec3f leftDir = leftPos - midVertex.position; 263 | // const Raz::Vec3f rightDir = rightPos - midVertex.position; 264 | // const Raz::Vec3f botDir = botPos - midVertex.position; 265 | // 266 | // midVertex.normal = rightDir.cross(topDir); 267 | // midVertex.normal += topDir.cross(leftDir); 268 | // midVertex.normal += leftDir.cross(botDir); 269 | // midVertex.normal += botDir.cross(rightDir); 270 | // midVertex.normal = midVertex.normal.normalize(); 271 | // midVertex.tangent = Raz::Vec3f(midVertex.normal.z(), midVertex.normal.x(), midVertex.normal.y()); 272 | } 273 | } 274 | }); 275 | } 276 | 277 | void StaticTerrain::remapVertices(float newHeightFactor, float newFlatness) { 278 | ZoneScopedN("StaticTerrain::remapVertices"); 279 | 280 | auto& mesh = m_entity.getComponent(); 281 | 282 | std::vector& vertices = mesh.getSubmeshes().front().getVertices(); 283 | Raz::Threading::parallelize(vertices, [this, newHeightFactor, newFlatness] (const auto& range) noexcept { 284 | ZoneScopedN("StaticTerrain::remapVertices"); 285 | 286 | for (Raz::Vertex& vertex : range) { 287 | const float baseHeight = std::pow(vertex.position.y() / m_heightFactor, m_invFlatness); 288 | vertex.position.y() = std::pow(baseHeight, newFlatness) * newHeightFactor; 289 | } 290 | }); 291 | 292 | computeNormals(); 293 | m_entity.getComponent().load(mesh); 294 | } 295 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(Midgard) 3 | 4 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 5 | 6 | # If the build type hasn't been specified, defaulting it to Release 7 | if (NOT CMAKE_BUILD_TYPE) 8 | set(CMAKE_BUILD_TYPE "Release") 9 | endif () 10 | 11 | ######################## 12 | # Midgard - Executable # 13 | ######################## 14 | 15 | add_executable(Midgard) 16 | 17 | # Using C++17 18 | target_compile_features(Midgard PRIVATE cxx_std_17) 19 | 20 | ############################## 21 | # Midgard - Useful variables # 22 | ############################## 23 | 24 | # Detect whether Emscripten is being used 25 | if (CMAKE_CXX_COMPILER MATCHES "/em\\+\\+.*$") 26 | set(MIDGARD_USE_EMSCRIPTEN ON) 27 | else () 28 | set(MIDGARD_USE_EMSCRIPTEN OFF) 29 | endif () 30 | 31 | if (MSVC AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") # Finding exclusively MSVC, not clang-cl 32 | set(MIDGARD_COMPILER_MSVC ON) 33 | target_compile_definitions(Midgard PUBLIC MIDGARD_COMPILER_MSVC) 34 | elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 35 | if (MSVC) 36 | # Using clang-cl, for which both MSVC & Clang are found 37 | set(MIDGARD_COMPILER_CLANG_CL ON) 38 | endif () 39 | 40 | set(MIDGARD_COMPILER_CLANG ON) 41 | target_compile_definitions(Midgard PUBLIC MIDGARD_COMPILER_CLANG) 42 | elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU") 43 | set(MIDGARD_COMPILER_GCC ON) 44 | target_compile_definitions(Midgard PUBLIC MIDGARD_COMPILER_GCC) 45 | endif () 46 | 47 | if (WIN32 OR CYGWIN) 48 | set(MIDGARD_PLATFORM_WINDOWS ON) 49 | target_compile_definitions(Midgard PUBLIC MIDGARD_PLATFORM_WINDOWS) 50 | 51 | if (CYGWIN) 52 | set(MIDGARD_PLATFORM_CYGWIN ON) 53 | target_compile_definitions(Midgard PUBLIC MIDGARD_PLATFORM_CYGWIN) 54 | endif () 55 | elseif (APPLE) 56 | set(MIDGARD_PLATFORM_MAC ON) 57 | target_compile_definitions(Midgard PUBLIC MIDGARD_PLATFORM_MAC) 58 | elseif (MIDGARD_USE_EMSCRIPTEN) 59 | set(MIDGARD_PLATFORM_EMSCRIPTEN ON) 60 | target_compile_definitions(Midgard PUBLIC MIDGARD_PLATFORM_EMSCRIPTEN USE_OPENGL_ES) 61 | elseif (UNIX) 62 | set(MIDGARD_PLATFORM_LINUX ON) 63 | target_compile_definitions(Midgard PUBLIC MIDGARD_PLATFORM_LINUX) 64 | endif () 65 | 66 | if (MIDGARD_COMPILER_MSVC) 67 | set(MIDGARD_CONFIG_DEBUG "$,ON,OFF>") 68 | set(MIDGARD_CONFIG_RELEASE "$,OFF,ON>") 69 | 70 | target_compile_definitions(Midgard PUBLIC $,MIDGARD_CONFIG_DEBUG,MIDGARD_CONFIG_RELEASE>) 71 | else () 72 | if (CMAKE_BUILD_TYPE STREQUAL "Debug") 73 | set(MIDGARD_CONFIG_DEBUG ON) 74 | set(MIDGARD_CONFIG_RELEASE OFF) 75 | 76 | target_compile_definitions(Midgard PUBLIC MIDGARD_CONFIG_DEBUG) 77 | else () 78 | set(MIDGARD_CONFIG_DEBUG OFF) 79 | set(MIDGARD_CONFIG_RELEASE ON) 80 | 81 | target_compile_definitions(Midgard PUBLIC MIDGARD_CONFIG_RELEASE) 82 | endif () 83 | endif () 84 | 85 | if (MIDGARD_USE_EMSCRIPTEN) 86 | target_compile_definitions(Midgard PUBLIC MIDGARD_ROOT="/") 87 | else () 88 | target_compile_definitions(Midgard PUBLIC MIDGARD_ROOT="${CMAKE_CURRENT_SOURCE_DIR}/") 89 | endif () 90 | 91 | ############################ 92 | # Midgard - Compiler flags # 93 | ############################ 94 | 95 | if (MIDGARD_COMPILER_GCC) 96 | set( 97 | MIDGARD_COMPILER_FLAGS 98 | 99 | -pedantic 100 | -pedantic-errors 101 | -Wall 102 | -Wextra 103 | 104 | -Warray-bounds 105 | -Wcast-align 106 | -Wcast-qual 107 | -Wconditionally-supported 108 | -Wconversion 109 | -Wdisabled-optimization 110 | -Wdouble-promotion 111 | -Wfloat-conversion 112 | -Wformat=2 113 | -Wformat-security 114 | -Wlogical-op 115 | -Wmissing-declarations 116 | -Wmissing-include-dirs 117 | -Wnoexcept 118 | -Wnon-virtual-dtor 119 | -Wold-style-cast 120 | -Wopenmp-simd 121 | -Woverloaded-virtual 122 | -Wpacked 123 | -Wredundant-decls 124 | -Wstrict-aliasing 125 | -Wstrict-null-sentinel 126 | #-Wsuggest-final-methods 127 | #-Wsuggest-final-types 128 | -Wtrampolines 129 | -Wundef 130 | -Wuninitialized 131 | -Wunused-macros 132 | -Wuseless-cast 133 | -Wvector-operation-performance 134 | -Wvla 135 | -Wzero-as-null-pointer-constant 136 | 137 | -Wno-comment 138 | -Wno-format-nonliteral 139 | ) 140 | 141 | # Enabling some other warnings available since GCC 5 142 | if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 5) 143 | set( 144 | MIDGARD_COMPILER_FLAGS 145 | 146 | ${MIDGARD_COMPILER_FLAGS} 147 | -fsized-deallocation 148 | -Warray-bounds=2 149 | -Wformat-signedness 150 | -Wsized-deallocation 151 | ) 152 | endif () 153 | 154 | # Enabling some other warnings available since GCC 6 155 | if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 6) 156 | set( 157 | MIDGARD_COMPILER_FLAGS 158 | 159 | ${MIDGARD_COMPILER_FLAGS} 160 | -Wduplicated-cond 161 | -Wnull-dereference 162 | ) 163 | endif () 164 | 165 | # Enabling some other warnings available since GCC 7 166 | if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 7) 167 | set( 168 | MIDGARD_COMPILER_FLAGS 169 | 170 | ${MIDGARD_COMPILER_FLAGS} 171 | -Waligned-new 172 | -Walloca 173 | -Walloc-zero 174 | -Wformat-overflow 175 | -Wshadow 176 | ) 177 | endif () 178 | 179 | # Enabling code coverage 180 | option(MIDGARD_ENABLE_COVERAGE "Enable code coverage (GCC only)" OFF) 181 | 182 | if (MIDGARD_CONFIG_DEBUG AND MIDGARD_ENABLE_COVERAGE) 183 | set( 184 | MIDGARD_COMPILER_FLAGS 185 | 186 | ${MIDGARD_COMPILER_FLAGS} 187 | -g 188 | -O0 189 | -fno-inline 190 | -fno-inline-small-functions 191 | -fno-default-inline 192 | -fprofile-arcs 193 | -ftest-coverage 194 | ) 195 | 196 | set( 197 | MIDGARD_LINKER_FLAGS 198 | 199 | gcov 200 | ) 201 | endif () 202 | elseif (MIDGARD_COMPILER_CLANG) 203 | set( 204 | MIDGARD_COMPILER_FLAGS 205 | 206 | -Weverything 207 | 208 | -Wno-c++98-compat 209 | -Wno-c++98-compat-pedantic 210 | -Wno-covered-switch-default 211 | -Wno-documentation 212 | -Wno-documentation-unknown-command 213 | -Wno-exit-time-destructors 214 | -Wno-float-equal 215 | -Wno-format-nonliteral 216 | -Wno-global-constructors 217 | -Wno-mismatched-tags 218 | -Wno-missing-braces 219 | -Wno-newline-eof 220 | -Wno-padded 221 | -Wno-reserved-id-macro 222 | -Wno-sign-conversion 223 | -Wno-switch-enum 224 | -Wno-weak-vtables 225 | ) 226 | 227 | if (MIDGARD_COMPILER_CLANG_CL) 228 | set( 229 | MIDGARD_COMPILER_FLAGS 230 | 231 | ${MIDGARD_COMPILER_FLAGS} 232 | # Disabling warnings triggered in externals 233 | -Wno-language-extension-token 234 | -Wno-nonportable-system-include-path 235 | -Wno-zero-as-null-pointer-constant 236 | ) 237 | else () 238 | set( 239 | MIDGARD_COMPILER_FLAGS 240 | 241 | ${MIDGARD_COMPILER_FLAGS} 242 | # Other warning flags not recognized by clang-cl 243 | -pedantic 244 | -pedantic-errors 245 | ) 246 | endif () 247 | 248 | # Disabling some warnings available since Clang 5 249 | if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 5) 250 | set( 251 | MIDGARD_COMPILER_FLAGS 252 | 253 | ${MIDGARD_COMPILER_FLAGS} 254 | -Wno-unused-template 255 | ) 256 | endif () 257 | elseif (MIDGARD_COMPILER_MSVC) 258 | set( 259 | MIDGARD_COMPILER_FLAGS 260 | 261 | /Wall 262 | /MP # Enabling multi-processes compilation 263 | 264 | /wd4061 # Enum value in a switch not explicitly handled by a case label 265 | /wd4571 # SEH exceptions aren't caught since Visual C++ 7.1 266 | /wd5045 # Spectre mitigation 267 | 268 | # Temporarily disabled warnings 269 | /wd4365 # Signed/unsigned mismatch (implicit conversion) 270 | /wd4625 # Copy constructor implicitly deleted 271 | /wd4626 # Copy assignment operator implicitly deleted 272 | 273 | # Warnings triggered by MSVC's standard library 274 | /wd4355 # 'this' used in base member initializing list 275 | /wd4514 # Unreferenced inline function has been removed 276 | /wd4548 # Expression before comma has no effect 277 | /wd4668 # Preprocessor macro not defined 278 | /wd4710 # Function not inlined 279 | /wd4711 # Function inlined 280 | /wd4774 # Format string is not a string literal 281 | /wd4820 # Added padding to members 282 | /wd5026 # Move constructor implicitly deleted 283 | /wd5027 # Move assignment operator implicitly deleted 284 | /wd5039 # Pointer/ref to a potentially throwing function passed to an 'extern "C"' function (with -EHc) 285 | ) 286 | 287 | # To automatically export all the classes & functions 288 | set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) 289 | 290 | # CMake automatically appends /W3 to the standard flags, which produces a warning with MSVC when adding another level; this has to be removed 291 | # TODO: if possible, this should be done per target, not globally 292 | string(REGEX REPLACE "/W[0-4]" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") 293 | string(REGEX REPLACE "/W[0-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 294 | endif () 295 | 296 | if (MIDGARD_COMPILER_MSVC OR MIDGARD_COMPILER_CLANG_CL) 297 | set( 298 | MIDGARD_COMPILER_FLAGS 299 | 300 | ${MIDGARD_COMPILER_FLAGS} 301 | /permissive- # Improving standard compliance 302 | /EHsc # Enabling exceptions 303 | /utf-8 # Forcing MSVC to actually handle files as UTF-8 304 | ) 305 | 306 | target_compile_definitions( 307 | Midgard 308 | 309 | PRIVATE 310 | 311 | NOMINMAX # Preventing definitions of min & max macros 312 | _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING # Ignoring std::codecvt deprecation warnings 313 | ) 314 | endif () 315 | 316 | if (MIDGARD_USE_EMSCRIPTEN) 317 | target_compile_options(Midgard PRIVATE "SHELL:-s USE_LIBPNG=1") 318 | 319 | target_link_options( 320 | Midgard 321 | 322 | PUBLIC 323 | 324 | "SHELL:-s USE_GLFW=3" 325 | "SHELL:-s USE_LIBPNG=1" 326 | ) 327 | 328 | target_link_libraries(Midgard PRIVATE glfw) 329 | endif () 330 | 331 | ########################## 332 | # Midgard - Source files # 333 | ########################## 334 | 335 | set( 336 | MIDGARD_SRC 337 | 338 | main.cpp 339 | src/Midgard/*.cpp 340 | 341 | include/Midgard/*.hpp 342 | include/Midgard/*.inl 343 | ) 344 | 345 | # Adding every file to be compiled 346 | file( 347 | GLOB 348 | MIDGARD_FILES 349 | 350 | ${MIDGARD_SRC} 351 | ) 352 | 353 | # The dynamic terrain uses tessellation shaders which are unavailable with OpenGL ES, used by Emscripten; removing the files to avoid using it 354 | if (MIDGARD_USE_EMSCRIPTEN) 355 | list( 356 | REMOVE_ITEM 357 | MIDGARD_FILES 358 | 359 | "${PROJECT_SOURCE_DIR}/src/Midgard/DynamicTerrain.cpp" 360 | "${PROJECT_SOURCE_DIR}/include/Midgard/DynamicTerrain.hpp" 361 | ) 362 | endif () 363 | 364 | ####################### 365 | # Midgard - RaZ usage # 366 | ####################### 367 | 368 | # Finding RaZ 369 | option(MIDGARD_BUILD_RAZ "Build RaZ alongside Midgard (requires downloading the submodule)" OFF) 370 | 371 | if (NOT MIDGARD_BUILD_RAZ) 372 | set(RAZ_ROOT "C:/RaZ" CACHE PATH "Path to the RaZ installation") 373 | 374 | set(RAZ_LIB_DIR "${RAZ_ROOT}/lib") 375 | 376 | # Visual Studio having all the configurations from within the project, CMAKE_BUILD_TYPE is unknown at generation time 377 | # Adding a link directory automatically creates another path to which is appended the $(Configuration) macro, which contains the build type 378 | if (NOT MIDGARD_COMPILER_MSVC) 379 | set(RAZ_LIB_DIR "${RAZ_LIB_DIR}/${CMAKE_BUILD_TYPE}") 380 | endif () 381 | 382 | target_link_directories(Midgard PRIVATE "${RAZ_LIB_DIR}") 383 | target_include_directories(Midgard SYSTEM PUBLIC "${RAZ_ROOT}/include") 384 | target_compile_definitions(Midgard PRIVATE RAZ_USE_WINDOW) # Raz::Window is needed 385 | 386 | # Additional linking flags 387 | if (MIDGARD_PLATFORM_WINDOWS) 388 | set( 389 | MIDGARD_LINKER_FLAGS 390 | 391 | ${MIDGARD_LINKER_FLAGS} 392 | opengl32 393 | ) 394 | elseif (MIDGARD_PLATFORM_LINUX) 395 | set( 396 | MIDGARD_LINKER_FLAGS 397 | 398 | ${MIDGARD_LINKER_FLAGS} 399 | dl 400 | pthread 401 | GL 402 | X11 403 | Xrandr 404 | Xcursor 405 | Xinerama 406 | Xxf86vm 407 | ) 408 | elseif (MIDGARD_PLATFORM_MAC) 409 | find_package(OpenGL REQUIRED) 410 | 411 | set( 412 | MIDGARD_LINKER_FLAGS 413 | 414 | ${MIDGARD_LINKER_FLAGS} 415 | OpenGL::GL 416 | "-framework OpenGL" 417 | "-framework Cocoa" 418 | "-framework IOKit" 419 | "-framework CoreVideo" 420 | ) 421 | endif () 422 | else () 423 | set(RAZ_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/extern/RaZ") 424 | 425 | if (EXISTS "${RAZ_ROOT}") 426 | # No need to keep the examples, unit tests, documentation generation & installation 427 | set(RAZ_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) 428 | set(RAZ_BUILD_TESTS OFF CACHE BOOL "" FORCE) 429 | set(RAZ_GEN_DOC OFF CACHE BOOL "" FORCE) 430 | set(RAZ_INSTALL OFF CACHE BOOL "" FORCE) 431 | 432 | add_subdirectory("${RAZ_ROOT}") 433 | 434 | # Use RaZ's compiler flags' script 435 | include("${RAZ_ROOT}/cmake/CompilerFlags.cmake") 436 | add_compiler_flags(Midgard PRIVATE) 437 | 438 | # Needed to use Tracy's GPU profiling 439 | target_link_libraries(Midgard PRIVATE GLEW) 440 | else () 441 | message(FATAL_ERROR "Failed to find RaZ; the submodule must be downloaded") 442 | endif () 443 | endif () 444 | 445 | if (MIDGARD_USE_EMSCRIPTEN) 446 | target_compile_definitions(Midgard PRIVATE RAZ_ROOT="/") 447 | else () 448 | target_compile_definitions(Midgard PRIVATE RAZ_ROOT="${RAZ_ROOT}/") 449 | endif () 450 | 451 | target_link_libraries(Midgard PUBLIC RaZ) 452 | 453 | ############################# 454 | # Midgard - Prepare shaders # 455 | ############################# 456 | 457 | include(EmbedFiles) 458 | embed_files("${PROJECT_SOURCE_DIR}/shaders/*.*" "${CMAKE_BINARY_DIR}/shaders" Midgard Shaders) 459 | 460 | ################### 461 | # Midgard - Build # 462 | ################### 463 | 464 | if (MIDGARD_USE_EMSCRIPTEN) 465 | set_target_properties(Midgard PROPERTIES SUFFIX ".html") 466 | 467 | target_link_options( 468 | Midgard 469 | 470 | PRIVATE 471 | 472 | "SHELL:--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/shaders@shaders" 473 | "SHELL:--preload-file ${RAZ_ROOT}/shaders@shaders" 474 | ) 475 | endif () 476 | 477 | target_include_directories(Midgard PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") 478 | 479 | if (NOT MIDGARD_COMPILER_MSVC) 480 | # Defining the compiler flags only for C++; this doesn't work with MSVC 481 | set(MIDGARD_COMPILER_FLAGS $<$:${MIDGARD_COMPILER_FLAGS}>) 482 | endif () 483 | 484 | target_compile_options(Midgard PRIVATE ${MIDGARD_COMPILER_FLAGS}) 485 | target_link_libraries(Midgard PRIVATE ${MIDGARD_LINKER_FLAGS}) 486 | 487 | # Cygwin's Clang needs to use GCC's standard library 488 | if (CYGWIN AND MIDGARD_COMPILER_CLANG) 489 | target_compile_options(Midgard PRIVATE -stdlib=libstdc++) 490 | target_link_libraries(Midgard PRIVATE stdc++) 491 | endif () 492 | 493 | # Compiling Midgard's sources 494 | target_sources(Midgard PRIVATE ${MIDGARD_FILES}) 495 | 496 | ########################## 497 | # Midgard - Installation # 498 | ########################## 499 | 500 | # Installing the executable 501 | if (MIDGARD_PLATFORM_WINDOWS) 502 | set(CMAKE_INSTALL_PREFIX "C:/Midgard") 503 | endif () 504 | 505 | install(TARGETS Midgard DESTINATION "${CMAKE_INSTALL_PREFIX}") 506 | --------------------------------------------------------------------------------