diff --git a/include/Midgard/DynamicTerrain.hpp b/include/Midgard/DynamicTerrain.hpp index 153a725..6cfc74e 100644 --- a/include/Midgard/DynamicTerrain.hpp +++ b/include/Midgard/DynamicTerrain.hpp @@ -13,7 +13,8 @@ class DynamicTerrain final : public Terrain { explicit DynamicTerrain(Raz::Entity& entity); DynamicTerrain(Raz::Entity& entity, unsigned int width, unsigned int depth, float heightFactor, float flatness, float minTessLevel = 12.f); - const Raz::Texture2DPtr& getNoiseMap() const noexcept { return m_noiseMap; } + const Raz::Texture2D& getNoiseMap() const noexcept { return *m_noiseMap; } + const Raz::Texture2D& getColorMap() const noexcept { return *m_colorMap; } void setMinTessellationLevel(float minTessLevel) { setParameters(minTessLevel, m_heightFactor, m_flatness); } void setParameters(float heightFactor, float flatness) override { setParameters(m_minTessLevel, heightFactor, flatness); } @@ -32,13 +33,16 @@ class DynamicTerrain final : public Terrain { /// \param flatness Flatness of the terrain. /// \param minTessLevel Minimal tessellation level to render the terrain with. void generate(unsigned int width, unsigned int depth, float heightFactor, float flatness, float minTessLevel); - const Raz::Texture2DPtr& computeNoiseMap(float factor); + const Raz::Texture2D& computeNoiseMap(float factor); + const Raz::Texture2D& computeColorMap(); private: float m_minTessLevel {}; Raz::ComputeShaderProgram m_noiseProgram {}; + Raz::ComputeShaderProgram m_colorProgram {}; Raz::Texture2DPtr m_noiseMap {}; + Raz::Texture2DPtr m_colorMap {}; }; #endif // MIDGARD_DYNAMICTERRAIN_HPP diff --git a/main.cpp b/main.cpp index 0aaba7e..22d45d9 100644 --- a/main.cpp +++ b/main.cpp @@ -191,22 +191,24 @@ int main() { overlay.addSeparator(); #if !defined(USE_OPENGL_ES) - Raz::OverlayTexture& dynamicNoiseTexture = overlay.addTexture(*dynamicTerrain.getNoiseMap(), 150, 150); + Raz::OverlayTexture& dynamicNoiseTexture = overlay.addTexture(dynamicTerrain.getNoiseMap(), 150, 150); + Raz::OverlayTexture& dynamicColorTexture = overlay.addTexture(dynamicTerrain.getColorMap(), 150, 150); #endif Raz::Texture2D colorTexture(colorMap, false); Raz::Texture2D normalTexture(normalMap, false); Raz::Texture2D slopeTexture(slopeMap, false); - Raz::OverlayTexture& staticColorTexture = overlay.addTexture(colorTexture, 150, 150); - Raz::OverlayTexture& staticNormalTexture = overlay.addTexture(normalTexture, 150, 150); - Raz::OverlayTexture& staticSlopeTexture = overlay.addTexture(slopeTexture, 150, 150); + [[maybe_unused]] Raz::OverlayTexture& staticColorTexture = overlay.addTexture(colorTexture, 150, 150); + [[maybe_unused]] Raz::OverlayTexture& staticNormalTexture = overlay.addTexture(normalTexture, 150, 150); + [[maybe_unused]] Raz::OverlayTexture& staticSlopeTexture = overlay.addTexture(slopeTexture, 150, 150); overlay.addSeparator(); #if !defined(USE_OPENGL_ES) Raz::OverlaySlider& dynamicNoiseMapFactorSlider = overlay.addSlider("Noise map factor", [&dynamicTerrain] (float value) { dynamicTerrain.computeNoiseMap(value); + dynamicTerrain.computeColorMap(); }, 0.001f, 0.1f, 0.01f); Raz::OverlaySlider& dynamicMinTessLevelSlider = overlay.addSlider("Min tess. level", [&dynamicTerrain] (float value) { @@ -222,13 +224,13 @@ int main() { }, 1.f, 10.f, 3.f); #endif - Raz::OverlaySlider& staticHeightFactorSlider = overlay.addSlider("Height factor", [&staticTerrain, &normalTexture, &slopeTexture] (float value) { + [[maybe_unused]] Raz::OverlaySlider& staticHeightFactorSlider = overlay.addSlider("Height factor", [&staticTerrain, &normalTexture, &slopeTexture] (float value) { staticTerrain.setHeightFactor(value); normalTexture.load(staticTerrain.computeNormalMap()); slopeTexture.load(staticTerrain.computeSlopeMap()); }, 0.001f, 50.f, 30.f); - Raz::OverlaySlider& staticFlatnessSlider = overlay.addSlider("Flatness", [&staticTerrain, &normalTexture, &slopeTexture] (float value) { + [[maybe_unused]] Raz::OverlaySlider& staticFlatnessSlider = overlay.addSlider("Flatness", [&staticTerrain, &normalTexture, &slopeTexture] (float value) { staticTerrain.setFlatness(value); normalTexture.load(staticTerrain.computeNormalMap()); slopeTexture.load(staticTerrain.computeSlopeMap()); @@ -244,6 +246,7 @@ int main() { overlay.addCheckbox("Dynamic terrain", [&] () noexcept { dynamicTerrainEntity.enable(); dynamicNoiseTexture.enable(); + dynamicColorTexture.enable(); dynamicNoiseMapFactorSlider.enable(); dynamicMinTessLevelSlider.enable(); dynamicHeightFactorSlider.enable(); @@ -265,6 +268,7 @@ int main() { dynamicTerrainEntity.disable(); dynamicNoiseTexture.disable(); + dynamicColorTexture.disable(); dynamicNoiseMapFactorSlider.disable(); dynamicMinTessLevelSlider.disable(); dynamicHeightFactorSlider.disable(); diff --git a/shaders/terrain.tesc b/shaders/terrain.tesc index 87af1ba..7cd1be0 100644 --- a/shaders/terrain.tesc +++ b/shaders/terrain.tesc @@ -32,7 +32,7 @@ void main() { float tessLevelFactor = (1.0 / distance(cameraPos, patchCentroid)) * 512.0; // Outer levels get a higher factor in order to attempt filling gaps between patches - // TODO: A better way must be found, like varying the level according the edges mid points + // TODO: A better way must be found, like making the outer level vary according to the edges' mid points // See: https://www.khronos.org/opengl/wiki/Tessellation#Patch_interface_and_continuity gl_TessLevelOuter[0] = uniTessLevel * (tessLevelFactor * 4.0); gl_TessLevelOuter[1] = uniTessLevel * (tessLevelFactor * 4.0); diff --git a/shaders/terrain.tese b/shaders/terrain.tese index acd28ac..3d70350 100644 --- a/shaders/terrain.tese +++ b/shaders/terrain.tese @@ -8,7 +8,7 @@ struct MeshInfo { in MeshInfo tessMeshInfo[]; -uniform sampler2D uniNoiseMap; +uniform sampler2D uniHeightmap; uniform uvec2 uniTerrainSize; uniform float uniFlatness = 3.0; uniform float uniHeightFactor = 30.0; @@ -28,10 +28,10 @@ vec3 computeNormalDifferences(vec2 vertUV) { float uStride = 1.0 / float(uniTerrainSize.x); float vStride = 1.0 / float(uniTerrainSize.y); - float topHeight = pow(texture(uniNoiseMap, vertUV + vec2( 0.0, -vStride)).r, uniFlatness) * uniHeightFactor; - float leftHeight = pow(texture(uniNoiseMap, vertUV + vec2(-uStride, 0.0)).r, uniFlatness) * uniHeightFactor; - float rightHeight = pow(texture(uniNoiseMap, vertUV + vec2( uStride, 0.0)).r, uniFlatness) * uniHeightFactor; - float botHeight = pow(texture(uniNoiseMap, vertUV + vec2( 0.0, vStride)).r, uniFlatness) * uniHeightFactor; + float topHeight = pow(texture(uniHeightmap, vertUV + vec2( 0.0, -vStride)).r, uniFlatness) * uniHeightFactor; + float leftHeight = pow(texture(uniHeightmap, vertUV + vec2(-uStride, 0.0)).r, uniFlatness) * uniHeightFactor; + float rightHeight = pow(texture(uniHeightmap, vertUV + vec2( uStride, 0.0)).r, uniFlatness) * uniHeightFactor; + float botHeight = pow(texture(uniHeightmap, vertUV + vec2( 0.0, vStride)).r, uniFlatness) * uniHeightFactor; // Replace y by 2.0-2.5 to get the same result as the cross method return normalize(vec3(leftHeight - rightHeight, 1.0, topHeight - botHeight)); @@ -46,15 +46,15 @@ vec3 computeNormalCross(vec3 vertPos, vec2 vertUV) { vec3 bottomRightPos = vertPos + vec3( 1.0, 0.0, -1.0); vec3 topRightPos = vertPos + vec3( 1.0, 0.0, 1.0); - float bottomLeftNoise = texture(uniNoiseMap, vertUV + vec2(-uStride, -vStride)).r; - float topLeftNoise = texture(uniNoiseMap, vertUV + vec2(-uStride, vStride)).r; - float bottomRightNoise = texture(uniNoiseMap, vertUV + vec2( uStride, -vStride)).r; - float topRightNoise = texture(uniNoiseMap, vertUV + vec2( uStride, vStride)).r; + float bottomLeftHeight = texture(uniHeightmap, vertUV + vec2(-uStride, -vStride)).r; + float topLeftHeight = texture(uniHeightmap, vertUV + vec2(-uStride, vStride)).r; + float bottomRightHeight = texture(uniHeightmap, vertUV + vec2( uStride, -vStride)).r; + float topRightHeight = texture(uniHeightmap, vertUV + vec2( uStride, vStride)).r; - bottomLeftPos.y += pow(bottomLeftNoise, uniFlatness) * uniHeightFactor; - topLeftPos.y += pow(topLeftNoise, uniFlatness) * uniHeightFactor; - bottomRightPos.y += pow(bottomRightNoise, uniFlatness) * uniHeightFactor; - topRightPos.y += pow(topRightNoise, uniFlatness) * uniHeightFactor; + bottomLeftPos.y += pow(bottomLeftHeight, uniFlatness) * uniHeightFactor; + topLeftPos.y += pow(topLeftHeight, uniFlatness) * uniHeightFactor; + bottomRightPos.y += pow(bottomRightHeight, uniFlatness) * uniHeightFactor; + topRightPos.y += pow(topRightHeight, uniFlatness) * uniHeightFactor; vec3 bottomLeftDir = bottomLeftPos - vertPos; vec3 topLeftDir = topLeftPos - vertPos; @@ -86,8 +86,8 @@ void main() { vec2 vertUV1 = mix(tessMeshInfo[2].vertTexcoords, tessMeshInfo[3].vertTexcoords, gl_TessCoord.x); vec2 vertUV = mix(vertUV0, vertUV1, gl_TessCoord.y); - float midNoise = texture(uniNoiseMap, vertUV).r; - vertPos.y += pow(midNoise, uniFlatness) * uniHeightFactor; + float midHeight = texture(uniHeightmap, vertUV).r; + vertPos.y += pow(midHeight, uniFlatness) * uniHeightFactor; vec3 normal = computeNormalDifferences(vertUV); //vec3 normal = computeNormalCross(vertPos, vertUV); diff --git a/shaders/terrain_color.comp b/shaders/terrain_color.comp new file mode 100644 index 0000000..5c24e5b --- /dev/null +++ b/shaders/terrain_color.comp @@ -0,0 +1,22 @@ +layout(local_size_x = 1, local_size_y = 1, local_size_x = 1) in; + +const vec3 waterColor = vec3(0.0, 0.0, 1.0); +const vec3 grassColor = vec3(0.24, 0.49, 0.0); +const vec3 groundColor = vec3(0.62, 0.43, 0.37); +const vec3 rockColor = vec3(0.5, 0.5, 0.5); +const vec3 snowColor = vec3(1.0, 1.0, 1.0); + +layout(r16f, binding = 0) uniform readonly image2D uniHeightmap; +layout(rgba8, binding = 1) uniform writeonly image2D uniColorMap; + +void main() { + ivec2 pixelCoords = ivec2(gl_GlobalInvocationID.xy); + + float height = imageLoad(uniHeightmap, pixelCoords).r; + vec3 color = (height < 0.33 ? mix(waterColor, grassColor, height * 3.0) + : (height < 0.5 ? mix(grassColor, groundColor, (height - 0.33) * 5.75) + : (height < 0.66 ? mix(groundColor, rockColor, (height - 0.5) * 6.0) + : mix(rockColor, snowColor, (height - 0.66) * 3.0)))); + + imageStore(uniColorMap, pixelCoords, vec4(color, 1.0)); +} diff --git a/src/Midgard/DynamicTerrain.cpp b/src/Midgard/DynamicTerrain.cpp index 9bc331a..c9b8507 100644 --- a/src/Midgard/DynamicTerrain.cpp +++ b/src/Midgard/DynamicTerrain.cpp @@ -20,6 +20,10 @@ constexpr std::string_view noiseCompSource = { #include "perlin_noise_2d.comp.embed" }; +constexpr std::string_view colorCompSource = { +#include "terrain_color.comp.embed" +}; + inline void checkParameters(float& minTessLevel) { if (minTessLevel <= 0.f) { Raz::Logger::warn("[DynamicTerrain] The minimal tessellation level can't be 0 or negative; remapping to +epsilon."); @@ -35,23 +39,39 @@ DynamicTerrain::DynamicTerrain(Raz::Entity& entity) : Terrain(entity) { terrainProgram.setTessellationEvaluationShader(Raz::TessellationEvaluationShader::loadFromSource(tessEvalSource)); terrainProgram.link(); + m_noiseMap = Raz::Texture2D::create(1024, 1024, Raz::TextureColorspace::GRAY, Raz::TextureDataType::FLOAT16); + m_colorMap = Raz::Texture2D::create(1024, 1024, Raz::TextureColorspace::RGBA, Raz::TextureDataType::BYTE); + +#if !defined(USE_OPENGL_ES) + if (Raz::Renderer::checkVersion(4, 3)) { + Raz::Renderer::setLabel(Raz::RenderObjectType::TEXTURE, m_noiseMap->getIndex(), "Noise map"); + Raz::Renderer::setLabel(Raz::RenderObjectType::TEXTURE, m_colorMap->getIndex(), "Color map"); + } +#endif + m_noiseProgram.setShader(Raz::ComputeShader::loadFromSource(noiseCompSource)); + m_noiseProgram.setAttribute(8, "uniOctaveCount"); + m_noiseProgram.sendAttributes(); m_noiseProgram.use(); - m_noiseProgram.sendUniform("uniOctaveCount", 8); - - m_noiseMap = Raz::Texture2D::create(1024, 1024, Raz::TextureColorspace::GRAY, Raz::TextureDataType::FLOAT16); Raz::Renderer::bindImageTexture(0, m_noiseMap->getIndex(), 0, false, 0, Raz::ImageAccess::WRITE, Raz::ImageInternalFormat::R16F); - terrainProgram.setTexture(m_noiseMap, "uniNoiseMap"); + m_colorProgram.setShader(Raz::ComputeShader::loadFromSource(colorCompSource)); + m_colorProgram.use(); + Raz::Renderer::bindImageTexture(0, m_noiseMap->getIndex(), 0, false, 0, Raz::ImageAccess::READ, Raz::ImageInternalFormat::R16F); + Raz::Renderer::bindImageTexture(1, m_colorMap->getIndex(), 0, false, 0, Raz::ImageAccess::WRITE, Raz::ImageInternalFormat::RGBA8); + + terrainProgram.setTexture(m_noiseMap, "uniHeightmap"); + terrainProgram.setTexture(m_colorMap, Raz::MaterialTexture::BaseColor); computeNoiseMap(0.01f); + computeColorMap(); } DynamicTerrain::DynamicTerrain(Raz::Entity& entity, unsigned int width, unsigned int depth, float heightFactor, float flatness, float minTessLevel) : DynamicTerrain(entity) { - Raz::RenderShaderProgram& program = entity.getComponent().getMaterials().front().getProgram(); - program.use(); - program.sendUniform("uniTerrainSize", Raz::Vec2u(width, depth)); + Raz::RenderShaderProgram& terrainProgram = entity.getComponent().getMaterials().front().getProgram(); + terrainProgram.setAttribute(Raz::Vec2u(width, depth), "uniTerrainSize"); + terrainProgram.sendAttributes(); DynamicTerrain::generate(width, depth, heightFactor, flatness, minTessLevel); } @@ -62,11 +82,11 @@ void DynamicTerrain::setParameters(float minTessLevel, float heightFactor, float ::checkParameters(minTessLevel); m_minTessLevel = minTessLevel; - Raz::RenderShaderProgram& program = m_entity.getComponent().getMaterials().front().getProgram(); - program.use(); - program.sendUniform("uniTessLevel", m_minTessLevel); - program.sendUniform("uniHeightFactor", m_heightFactor); - program.sendUniform("uniFlatness", m_flatness); + Raz::RenderShaderProgram& terrainProgram = m_entity.getComponent().getMaterials().front().getProgram(); + terrainProgram.setAttribute(m_minTessLevel, "uniTessLevel"); + terrainProgram.setAttribute(m_heightFactor, "uniHeightFactor"); + terrainProgram.setAttribute(m_flatness, "uniFlatness"); + terrainProgram.sendAttributes(); } void DynamicTerrain::generate(unsigned int width, unsigned int depth, float heightFactor, float flatness, float minTessLevel) { @@ -126,10 +146,16 @@ void DynamicTerrain::generate(unsigned int width, unsigned int depth, float heig setParameters(minTessLevel, heightFactor, flatness); } -const Raz::Texture2DPtr& DynamicTerrain::computeNoiseMap(float factor) { - m_noiseProgram.use(); - m_noiseProgram.sendUniform("uniNoiseFactor", factor); +const Raz::Texture2D& DynamicTerrain::computeNoiseMap(float factor) { + m_noiseProgram.setAttribute(factor, "uniNoiseFactor"); + m_noiseProgram.sendAttributes(); m_noiseProgram.execute(1024, 1024); - return m_noiseMap; + return *m_noiseMap; +} + +const Raz::Texture2D& DynamicTerrain::computeColorMap() { + m_colorProgram.execute(1024, 1024); + + return *m_colorMap; }