Skip to content

Commit 4ec9172

Browse files
committed
Improving Terrain performance.
Refactoring Terrain to reduce the number of buffer resizes per frame by reserving vert_buffer size in the update function before multiple add_verts calls. Moved noise generation functions to Noise.hpp and added WIP facilty for calculating normals analytically. Added graphics debug options to view terrain normals. Added an ImGui helpmarker + info for noise param settings in UI.
1 parent adbd2fb commit 4ec9172

File tree

9 files changed

+409
-154
lines changed

9 files changed

+409
-154
lines changed

source/Component/Terrain.cpp

Lines changed: 171 additions & 133 deletions
Large diffs are not rendered by default.

source/Component/Terrain.hpp

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
#include "Geometry/QuadKey.hpp"
77

8-
#include "Utility/PerlinNoise.hpp"
8+
#include "Utility/Noise.hpp"
99

1010
#include <unordered_map>
1111

@@ -28,10 +28,13 @@ namespace Component
2828
size_t chunk_vert_buff_stride() const;
2929
size_t chunk_index_buff_stride() const;
3030
void regenerate_mesh();
31+
32+
33+
using MeshIter = std::unordered_map<Geometry::QuadKey, size_t>::iterator;
34+
3135
// Remove the verts for the given quadkey and return the iterator to the next element.
32-
std::unordered_map<Geometry::QuadKey, size_t>::iterator remove_verts(const Geometry::QuadKey& key);
33-
void add_verts(const Geometry::QuadKey& info);
34-
float compute_height(float p_x, float p_z, const siv::PerlinNoise& perlin);
36+
void remove_verts(MeshIter& to_remove);
37+
void add_verts(const Geometry::QuadKey& quad, std::optional<size_t> buffer_index_overwrite = std::nullopt);
3538

3639
// Given a player_pos to center around, return the quadkeys which represent the leaf nodes of the quad tree centered around that pos.
3740
std::vector<Geometry::QuadKey> get_tree_leaf_nodes();
@@ -52,16 +55,12 @@ namespace Component
5255
std::optional<RootInfo> root_bounds;
5356
glm::vec2 player_pos; // Player position used to center the quad tree.
5457
uint8_t max_depth; // max depth of the quad tree.
55-
uint8_t chunk_detail; // Number of vertices per chunk side.
58+
uint16_t chunk_detail; // Number of vertices per chunk side.
5659
float decay_rate;
5760

58-
// Noise params
5961
constexpr static size_t Persistent_ID = 6;
60-
float m_scale_factor;
61-
float m_amplitude;
62-
float m_lacunarity;
63-
float m_persistence;
64-
int m_octaves;
62+
63+
Utility::Perlin::Params noise_params;
6564

6665
TextureRef m_grass_tex;
6766
TextureRef m_gravel_tex;
@@ -71,8 +70,9 @@ namespace Component
7170
TextureRef m_snow_tex;
7271

7372
unsigned int m_seed; // Seed used to generate m_mesh.
73+
bool gen_normals_analytically = false;
7474

75-
Terrain(float amplitude) noexcept;
75+
Terrain(float height) noexcept;
7676
Terrain& operator=(Terrain&& p_other) noexcept = default;
7777
Terrain(Terrain&& p_other) noexcept = default;
7878
Terrain(const Terrain& p_other); // = delete;

source/External/ImGuiUser/imgui_user.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ namespace ImGui
139139
{
140140
return SliderFloat(label, &p_float, v_min, v_max, format, flags);
141141
}
142+
inline bool Slider(const char* label, double& p_double, double v_min, double v_max, const char* format = "%.3f", ImGuiSliderFlags flags = 0)
143+
{
144+
return SliderScalar(label, ImGuiDataType_Double, &p_double, &v_min, &v_max, format, flags);
145+
}
142146
inline bool Slider(const char* label, int& p_v, int v_min, int v_max, const char* format = "%d", ImGuiSliderFlags flags = 0)
143147
{
144148
return SliderInt(label, &p_v, v_min, v_max, format, flags);
@@ -216,4 +220,17 @@ namespace ImGui
216220
}
217221
return result;
218222
}
223+
224+
static void HelpMarker(const char* desc)
225+
{
226+
ImGui::SameLine();
227+
ImGui::TextDisabled("(?)");
228+
if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort) && ImGui::BeginTooltip())
229+
{
230+
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
231+
ImGui::TextUnformatted(desc);
232+
ImGui::PopTextWrapPos();
233+
ImGui::EndTooltip();
234+
}
235+
}
219236
}

source/OpenGL/GLSL/phong_terrain.frag

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ uniform sampler2D rock;
99
uniform sampler2D snow;
1010
// uniform sampler2D sand;
1111

12+
uniform bool debug_normals;
13+
1214
struct DirectionalLight
1315
{
1416
vec3 direction;
@@ -99,6 +101,12 @@ out vec4 Colour;
99101

100102
void main()
101103
{
104+
if (debug_normals)
105+
{
106+
Colour = vec4(normalize(fs_in.normal) * 0.5 + 0.5, 1.0); // * 0.5 + 0.5 to convert from [-1, 1] to [0, 1] range
107+
return;
108+
}
109+
102110
Colour = vec4(0.0, 0.0, 0.0, 1.0);
103111

104112
vec3 view_direction = normalize(vec3(fs_in.camera_position) - fs_in.position);

source/OpenGL/OpenGLRenderer.cpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ namespace OpenGL
5454
, m_draw_grid{false}
5555
, m_draw_terrain_nodes{false}
5656
, m_draw_terrain_wireframe{false}
57+
, m_visualise_terrain_normals{false}
5758
{
5859
#ifdef Z_DEBUG // Ensure the uniform block layout matches the Component::ViewInformation struct layout for direct memory copy.
5960
{
@@ -227,8 +228,10 @@ namespace OpenGL
227228
dc.set_uniform("model", glm::identity<glm::mat4>());
228229
dc.set_uniform("shininess", 1000000.f); // Force terrain to not be shiny.
229230
// TODO: calc instead of using amplitude... use the actual height of the terrain.
230-
dc.set_uniform("min_height", -p_terrain.m_amplitude);
231-
dc.set_uniform("max_height", p_terrain.m_amplitude);
231+
dc.set_uniform("min_height", -p_terrain.noise_params.height);
232+
dc.set_uniform("max_height", p_terrain.noise_params.height);
233+
234+
dc.set_uniform("debug_normals", m_visualise_terrain_normals);
232235

233236
dc.set_texture("grass", p_terrain.m_grass_tex->m_GL_texture);
234237
dc.set_texture("rock", p_terrain.m_rock_tex->m_GL_texture);
@@ -248,6 +251,7 @@ namespace OpenGL
248251
ImGui::Checkbox("Draw grid", &m_draw_grid);
249252
ImGui::Checkbox("Draw terrain nodes", &m_draw_terrain_nodes);
250253
ImGui::Checkbox("Draw terrain wireframe", &m_draw_terrain_wireframe);
254+
ImGui::Checkbox("Debug terrain Normals", &m_visualise_terrain_normals);
251255

252256
if (ImGui::Button("Reload Shaders"))
253257
reload_shaders();
@@ -271,11 +275,12 @@ namespace OpenGL
271275

272276
void OpenGLRenderer::reset_debug_options()
273277
{
274-
m_draw_shadows = true;
275-
m_draw_grid = true;
276-
m_draw_terrain_nodes = false;
277-
m_draw_terrain_wireframe = false;
278-
m_post_processing_options = {};
278+
m_draw_shadows = true;
279+
m_draw_grid = true;
280+
m_draw_terrain_nodes = false;
281+
m_draw_terrain_wireframe = false;
282+
m_visualise_terrain_normals = false;
283+
m_post_processing_options = {};
279284
}
280285
void OpenGLRenderer::reload_shaders()
281286
{

source/OpenGL/OpenGLRenderer.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ namespace OpenGL
6565
bool m_draw_grid;
6666
bool m_draw_terrain_nodes;
6767
bool m_draw_terrain_wireframe;
68+
bool m_visualise_terrain_normals;
6869

6970
// OpenGLRenderer reads and renders the current state of pStorage when draw() is called.
7071
OpenGLRenderer(Platform::Window& p_window, System::AssetManager& p_asset_manager, System::SceneSystem& p_scene_system) noexcept;

source/System/SceneSystem.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ namespace System
3131
add_default_camera(scene);
3232
construct_2_sphere_scene(scene);
3333

34-
Component::Terrain terrain(20);
34+
Component::Terrain terrain(2048.f);
3535
terrain.m_grass_tex = m_asset_manager.get_texture(Config::Texture_PBR_Directory / "Grass" / "Color.jpg");
3636
terrain.m_sand_tex = m_asset_manager.get_texture(Config::Texture_PBR_Directory / "Sand" / "Color.jpg");
3737
terrain.m_snow_tex = m_asset_manager.get_texture(Config::Texture_PBR_Directory / "Snow" / "Color.jpg");

source/Utility/Noise.hpp

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#pragma once
2+
3+
4+
#include "Utility/PerlinNoise.hpp"
5+
#include "Utility/Performance.hpp"
6+
7+
#include <cmath>
8+
9+
namespace Utility
10+
{
11+
struct Perlin
12+
{
13+
struct Params
14+
{
15+
uint8_t octaves = 7u; // number of noise layers
16+
float scale = 375.f; // world-to-noise scale
17+
float persistence = 1.3f; // amplitude falloff per octave
18+
float lacunarity = 2.3f; // frequency multiplier per octave
19+
float exponentiation = 8.5f; // curve‑shaping exponent
20+
float height = 2048.f; // final amplitude multiplier
21+
};
22+
23+
static float Get(float x, float y, const Params& params = Params{})
24+
{
25+
PERF(GeneratePerlinNoise);
26+
27+
float xs = x / params.scale;
28+
float ys = y / params.scale;
29+
float G = std::pow(2.f, -params.persistence);
30+
float amp = 1.f;
31+
float freq = 1.f;
32+
float norm = 0.f;
33+
float total = 0.f;
34+
auto perlin = siv::BasicPerlinNoise<float>{};
35+
36+
for (uint8_t o = 0; o < params.octaves; ++o)
37+
{
38+
float nv = perlin.noise2D(xs * freq, ys * freq) * 0.5f + 0.5f;
39+
total += nv * amp;
40+
norm += amp;
41+
amp *= G;
42+
freq *= params.lacunarity;
43+
}
44+
45+
total /= norm;
46+
return std::pow(total, params.exponentiation) * params.height;
47+
}
48+
49+
50+
struct NoiseResult
51+
{
52+
float value; // Noise value
53+
float dx; // Partial derivative w.r.t x
54+
float dy; // Partial derivative w.r.t y
55+
};
56+
static NoiseResult noise2DWithDerivative(const siv::BasicPerlinNoise<float>& noise, float x, float y) noexcept
57+
{
58+
auto FadeDerivative = [](float t) noexcept { return 30 * t * t * (t * (t - 2) + 1); };
59+
60+
constexpr float Z = static_cast<float>(SIVPERLIN_DEFAULT_Z);
61+
const float X0 = std::floor(x);
62+
const float Y0 = std::floor(y);
63+
const float Z0 = std::floor(Z);
64+
65+
const int32_t ix = static_cast<int32_t>(X0) & 255;
66+
const int32_t iy = static_cast<int32_t>(Y0) & 255;
67+
const int32_t iz = static_cast<int32_t>(Z0) & 255;
68+
69+
const float fx = x - X0;
70+
const float fy = y - Y0;
71+
const float fz = Z - Z0;
72+
73+
const float u = siv::perlin_detail::Fade(fx);
74+
const float v = siv::perlin_detail::Fade(fy);
75+
const float w = siv::perlin_detail::Fade(fz);
76+
const float du = FadeDerivative(fx);
77+
const float dv = FadeDerivative(fy);
78+
// w derivative is zero because z is constant
79+
80+
auto& permutation = noise.serialize();
81+
82+
const uint8_t A = (permutation[ix] + iy) & 255;
83+
const uint8_t B = (permutation[(ix + 1) & 255] + iy) & 255;
84+
85+
const uint8_t AA = (permutation[A] + iz) & 255;
86+
const uint8_t AB = (permutation[(A + 1) & 255] + iz) & 255;
87+
const uint8_t BA = (permutation[B] + iz) & 255;
88+
const uint8_t BB = (permutation[(B + 1) & 255] + iz) & 255;
89+
90+
// Gradients at each corner
91+
const float g000 = siv::perlin_detail::Grad(permutation[AA], fx, fy, fz);
92+
const float g100 = siv::perlin_detail::Grad(permutation[BA], fx - 1, fy, fz);
93+
const float g010 = siv::perlin_detail::Grad(permutation[AB], fx, fy - 1, fz);
94+
const float g110 = siv::perlin_detail::Grad(permutation[BB], fx - 1, fy - 1, fz);
95+
const float g001 = siv::perlin_detail::Grad(permutation[(AA + 1) & 255], fx, fy, fz - 1);
96+
const float g101 = siv::perlin_detail::Grad(permutation[(BA + 1) & 255], fx - 1, fy, fz - 1);
97+
const float g011 = siv::perlin_detail::Grad(permutation[(AB + 1) & 255], fx, fy - 1, fz - 1);
98+
const float g111 = siv::perlin_detail::Grad(permutation[(BB + 1) & 255], fx - 1, fy - 1, fz - 1);
99+
100+
const float x00 = siv::perlin_detail::Lerp(g000, g100, u);
101+
const float x10 = siv::perlin_detail::Lerp(g010, g110, u);
102+
const float x01 = siv::perlin_detail::Lerp(g001, g101, u);
103+
const float x11 = siv::perlin_detail::Lerp(g011, g111, u);
104+
105+
const float y0 = siv::perlin_detail::Lerp(x00, x10, v);
106+
const float y1 = siv::perlin_detail::Lerp(x01, x11, v);
107+
108+
const float value = siv::perlin_detail::Lerp(y0, y1, w);
109+
110+
// Partial derivative w.r.t x
111+
const float dx00 = (g100 - g000);
112+
const float dx10 = (g110 - g010);
113+
const float dx01 = (g101 - g001);
114+
const float dx11 = (g111 - g011);
115+
116+
const float dy0 = siv::perlin_detail::Lerp(dx00, dx10, v);
117+
const float dy1 = siv::perlin_detail::Lerp(dx01, dx11, v);
118+
const float dx = du * siv::perlin_detail::Lerp(dy0, dy1, w);
119+
120+
// Partial derivative w.r.t y
121+
const float dy00 = (x10 - x00);
122+
const float dy01 = (x11 - x01);
123+
const float dy = dv * siv::perlin_detail::Lerp(dy00, dy01, w);
124+
125+
return { value, dx, dy };
126+
}
127+
128+
struct Result
129+
{
130+
float height;
131+
glm::vec3 normal;
132+
};
133+
static Result GetWithNormal(float x, float y, const Params& params = Params{})
134+
{
135+
PERF(GeneratePerlinNoiseWithNormal);
136+
137+
float xs = x / params.scale;
138+
float ys = y / params.scale;
139+
float G = std::pow(2.0f, -params.persistence);
140+
float amp = 1.f;
141+
float freq = 1.f;
142+
float norm = 0.f;
143+
float total = 0.f;
144+
float dx_total = 0.f;
145+
float dy_total = 0.f;
146+
auto perlin = siv::BasicPerlinNoise<float>{};
147+
148+
for (uint8_t o = 0; o < params.octaves; ++o)
149+
{
150+
auto result = noise2DWithDerivative(perlin, xs * freq, ys * freq);
151+
152+
float nv = result.value * 0.5f + 0.5f;
153+
total += nv * amp;
154+
155+
// derivative scaled by amp
156+
dx_total += result.dx * freq * amp;
157+
dy_total += result.dy * freq * amp;
158+
159+
norm += amp;
160+
amp *= G;
161+
freq *= params.lacunarity;
162+
}
163+
164+
total /= norm;
165+
dx_total /= norm;
166+
dy_total /= norm;
167+
168+
// Apply exponentiation curve to height
169+
float finalHeight = std::pow(total, params.exponentiation) * params.height;
170+
171+
// Apply exponentiation chain rule for derivatives
172+
float exponentFactor = params.exponentiation * std::pow(total, params.exponentiation - 1) * params.height;
173+
174+
175+
176+
glm::vec3 normal = glm::normalize(glm::vec3(
177+
-dx_total * exponentFactor / params.scale,
178+
1.0f,
179+
-dy_total * exponentFactor / params.scale));
180+
181+
// normal = glm::normalize(glm::vec3(dx_total, 1.0f, dy_total));
182+
183+
return {finalHeight, normal};
184+
}
185+
};
186+
} // namespace Utility

source/Utility/Performance.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ namespace Utility
200200
}// namespace Utility
201201

202202
#ifdef Z_DEBUG
203-
#define PERF(p_name) ZoneScopedN(#p_name); Utility::ScopedPerformanceBench perf_##p_name{#p_name};
203+
#define PERF(p_name) ZoneScopedN(#p_name);
204204
#define PERF_FRAME_END FrameMark;
205205
#else
206206
#define PERF(p_name) ZoneScopedN(#p_name);

0 commit comments

Comments
 (0)