Unravel Engine C++ Reference
Loading...
Searching...
No Matches
cloud_noise.cpp
Go to the documentation of this file.
1#include "cloud_noise.h"
2#include <graphics/graphics.h>
3#include <logging/logging.h>
4
5#include <cmath>
6#include <cstring>
7#include <vector>
8#include <algorithm>
9
10namespace unravel
11{
12
13namespace
14{
15
16// ---- Integer hashing (Wang hash) — excellent distribution for small integers ----
17
18auto wang_hash(uint32_t seed) -> uint32_t
19{
20 seed = (seed ^ 61u) ^ (seed >> 16u);
21 seed *= 9u;
22 seed = seed ^ (seed >> 4u);
23 seed *= 0x27d4eb2du;
24 seed = seed ^ (seed >> 15u);
25 return seed;
26}
27
28auto hash_int3(int x, int y, int z) -> float
29{
30 uint32_t h = wang_hash(
31 static_cast<uint32_t>(x) * 73856093u ^
32 static_cast<uint32_t>(y) * 19349663u ^
33 static_cast<uint32_t>(z) * 83492791u);
34 return static_cast<float>(h) / static_cast<float>(0xFFFFFFFFu);
35}
36
37struct f3
38{
39 float x, y, z;
40};
41
42auto hash_int3_vec3(int x, int y, int z) -> f3
43{
44 float hx = hash_int3(x, y, z);
45 float hy = hash_int3(x + 47, y + 17, z + 31);
46 float hz = hash_int3(x + 89, y + 53, z + 71);
47 return {hx, hy, hz};
48}
49
50// ---- Tiling helpers ----
51
53
54auto wrap(int v) -> int
55{
56 return ((v % period) + period) % period;
57}
58
59// ---- Math helpers ----
60
61auto lerp(float a, float b, float t) -> float { return a + (b - a) * t; }
62
63auto smoothstep_f(float t) -> float
64{
65 t = std::clamp(t, 0.0f, 1.0f);
66 return t * t * (3.0f - 2.0f * t);
67}
68
69// ---- Tileable value noise (Perlin-like) ----
70
71auto value_noise_3d(float px, float py, float pz) -> float
72{
73 int ix = static_cast<int>(std::floor(px));
74 int iy = static_cast<int>(std::floor(py));
75 int iz = static_cast<int>(std::floor(pz));
76
77 float fx = px - std::floor(px);
78 float fy = py - std::floor(py);
79 float fz = pz - std::floor(pz);
80
81 float ux = fx * fx * (3.0f - 2.0f * fx);
82 float uy = fy * fy * (3.0f - 2.0f * fy);
83 float uz = fz * fz * (3.0f - 2.0f * fz);
84
85 int i0x = wrap(ix), i0y = wrap(iy), i0z = wrap(iz);
86 int i1x = wrap(ix + 1), i1y = wrap(iy + 1), i1z = wrap(iz + 1);
87
88 float n000 = hash_int3(i0x, i0y, i0z);
89 float n100 = hash_int3(i1x, i0y, i0z);
90 float n010 = hash_int3(i0x, i1y, i0z);
91 float n110 = hash_int3(i1x, i1y, i0z);
92 float n001 = hash_int3(i0x, i0y, i1z);
93 float n101 = hash_int3(i1x, i0y, i1z);
94 float n011 = hash_int3(i0x, i1y, i1z);
95 float n111 = hash_int3(i1x, i1y, i1z);
96
97 float nx00 = lerp(n000, n100, ux);
98 float nx10 = lerp(n010, n110, ux);
99 float nx01 = lerp(n001, n101, ux);
100 float nx11 = lerp(n011, n111, ux);
101
102 float nxy0 = lerp(nx00, nx10, uy);
103 float nxy1 = lerp(nx01, nx11, uy);
104
105 return lerp(nxy0, nxy1, uz);
106}
107
108// ---- Tileable Worley noise ----
109
110auto worley_noise_3d(float px, float py, float pz) -> float
111{
112 int ix = static_cast<int>(std::floor(px));
113 int iy = static_cast<int>(std::floor(py));
114 int iz = static_cast<int>(std::floor(pz));
115
116 float fx = px - std::floor(px);
117 float fy = py - std::floor(py);
118 float fz = pz - std::floor(pz);
119
120 float min_dist = 1.0f;
121
122 for(int dx = -1; dx <= 1; dx++)
123 {
124 for(int dy = -1; dy <= 1; dy++)
125 {
126 for(int dz = -1; dz <= 1; dz++)
127 {
128 int cx = wrap(ix + dx);
129 int cy = wrap(iy + dy);
130 int cz = wrap(iz + dz);
131
132 f3 pt = hash_int3_vec3(cx, cy, cz);
133
134 float diff_x = float(dx) + pt.x - fx;
135 float diff_y = float(dy) + pt.y - fy;
136 float diff_z = float(dz) + pt.z - fz;
137
138 float dist = diff_x * diff_x + diff_y * diff_y + diff_z * diff_z;
139 min_dist = std::min(min_dist, dist);
140 }
141 }
142 }
143 return std::sqrt(min_dist);
144}
145
146// ---- FBM wrappers ----
147
148auto fbm_value(float px, float py, float pz, int octaves) -> float
149{
150 float value = 0.0f;
151 float amplitude = 0.5f;
152 float freq = 1.0f;
153 for(int i = 0; i < octaves; i++)
154 {
155 value += amplitude * value_noise_3d(px * freq, py * freq, pz * freq);
156 freq *= 2.0f;
157 amplitude *= 0.5f;
158 }
159 return value;
160}
161
162auto worley_at(float px, float py, float pz, float freq) -> float
163{
164 return worley_noise_3d(px * freq, py * freq, pz * freq);
165}
166
167// ---- Remap utility (Schneider / HZD) ----
168
169auto remap(float value, float lo, float hi, float new_lo, float new_hi) -> float
170{
171 return new_lo + (value - lo) / (hi - lo) * (new_hi - new_lo);
172}
173
174// ---- Float-to-half conversion ----
175
176auto float_to_half(float value) -> uint16_t
177{
178 uint32_t f = 0;
179 std::memcpy(&f, &value, 4);
180
181 uint32_t sign = (f >> 16u) & 0x8000u;
182 auto exponent = static_cast<int32_t>(((f >> 23u) & 0xFFu)) - 127 + 15;
183 uint32_t mantissa = f & 0x007FFFFFu;
184
185 if(exponent <= 0)
186 {
187 return static_cast<uint16_t>(sign);
188 }
189 if(exponent >= 31)
190 {
191 return static_cast<uint16_t>(sign | 0x7C00u);
192 }
193
194 return static_cast<uint16_t>(sign | (static_cast<uint32_t>(exponent) << 10u) | (mantissa >> 13u));
195}
196
197} // namespace
198
200{
201 generate_3d();
202 generate_flat();
203}
204
206{
207 base_noise.reset();
208 flat_noise.reset();
209}
210
211void cloud_noise_textures::generate_3d()
212{
213 constexpr uint16_t res = resolution;
214 constexpr size_t total = size_t(res) * res * res;
215 constexpr size_t num_channels = 4;
216 constexpr size_t num_halfs = total * num_channels;
217 constexpr size_t bytes = num_halfs * sizeof(uint16_t);
218
219 APPLOG_TRACE("[CloudNoise] Generating {}x{}x{} 3D noise texture (RGBA16F, period={})...",
220 res, res, res, tile_period);
221
222 std::vector<uint16_t> data(num_halfs);
223
224 const float inv_res = float(tile_period) / float(res);
225
226 for(uint16_t z = 0; z < res; z++)
227 {
228 for(uint16_t y = 0; y < res; y++)
229 {
230 for(uint16_t x = 0; x < res; x++)
231 {
232 float px = float(x) * inv_res;
233 float py = float(y) * inv_res;
234 float pz = float(z) * inv_res;
235
236 // Perlin FBM (4 octaves) — soft large-scale shape
237 float perlin4 = fbm_value(px, py, pz, 4);
238
239 // Inverted Worley at 1x — puffy cell centers
240 float inv_worley = 1.0f - worley_at(px, py, pz, 1.0f);
241
242 // R: Perlin-Worley blend — slightly higher Worley weight for chunkier, less uniform masses.
243 float perlin_worley = perlin4 * 0.55f + inv_worley * 0.45f;
244 perlin_worley = std::clamp(perlin_worley, 0.0f, 1.0f);
245
246 // G/B/A: Worley at increasing frequencies for multi-octave erosion
247 float worley_1x = worley_at(px, py, pz, 1.0f);
248 float worley_2x = worley_at(px, py, pz, 2.0f);
249 float worley_4x = worley_at(px, py, pz, 4.0f);
250
251 size_t idx = (size_t(z) * res * res + size_t(y) * res + size_t(x)) * num_channels;
252 data[idx + 0] = float_to_half(perlin_worley);
253 data[idx + 1] = float_to_half(worley_1x);
254 data[idx + 2] = float_to_half(worley_2x);
255 data[idx + 3] = float_to_half(worley_4x);
256 }
257 }
258 }
259
260 APPLOG_TRACE("[CloudNoise] Noise computed, uploading to GPU...");
261
262 auto* mem = gfx::copy(data.data(), static_cast<uint32_t>(bytes));
263 base_noise = std::make_unique<gfx::texture>(
264 res, res, res,
265 false,
266 gfx::texture_format::RGBA16F,
267 BGFX_TEXTURE_NONE | BGFX_SAMPLER_NONE,
268 mem);
269
270 APPLOG_TRACE("[CloudNoise] 3D noise texture ready ({} MB).", bytes / size_t(1024 * 1024));
271}
272
273void cloud_noise_textures::generate_flat()
274{
275 constexpr uint16_t res = flat_resolution;
276 constexpr size_t total = size_t(res) * res;
277 constexpr size_t num_channels = 4;
278 constexpr size_t num_halfs = total * num_channels;
279 constexpr size_t bytes = num_halfs * sizeof(uint16_t);
280
281 APPLOG_TRACE("[CloudNoise] Generating {}x{} 2D flat noise texture (RGBA16F, period={})...",
282 res, res, tile_period);
283
284 std::vector<uint16_t> data(num_halfs);
285
286 const float inv_res = float(tile_period) / float(res);
287 const float fixed_z = float(tile_period) * 0.5f;
288
289 for(uint16_t y = 0; y < res; y++)
290 {
291 for(uint16_t x = 0; x < res; x++)
292 {
293 float px = float(x) * inv_res;
294 float py = float(y) * inv_res;
295
296 float perlin4 = fbm_value(px, fixed_z, py, 4);
297 float inv_worley = 1.0f - worley_at(px, fixed_z, py, 1.0f);
298
299 float perlin_worley = perlin4 * 0.65f + inv_worley * 0.35f;
300 perlin_worley = std::clamp(perlin_worley, 0.0f, 1.0f);
301
302 float worley_1x = worley_at(px, fixed_z, py, 1.0f);
303 float worley_2x = worley_at(px, fixed_z, py, 2.0f);
304 float worley_4x = worley_at(px, fixed_z, py, 4.0f);
305
306 size_t idx = (size_t(y) * res + size_t(x)) * num_channels;
307 data[idx + 0] = float_to_half(perlin_worley);
308 data[idx + 1] = float_to_half(worley_1x);
309 data[idx + 2] = float_to_half(worley_2x);
310 data[idx + 3] = float_to_half(worley_4x);
311 }
312 }
313
314 APPLOG_TRACE("[CloudNoise] 2D flat noise computed, uploading to GPU...");
315
316 auto* mem = gfx::copy(data.data(), static_cast<uint32_t>(bytes));
317 flat_noise = std::make_unique<gfx::texture>(
318 res, res,
319 false,
320 1,
321 gfx::texture_format::RGBA16F,
322 BGFX_TEXTURE_NONE | BGFX_SAMPLER_NONE,
323 mem);
324
325 APPLOG_TRACE("[CloudNoise] 2D flat noise texture ready ({} KB).", bytes / size_t(1024));
326}
327
328} // namespace unravel
entt::handle b
entt::handle a
float y
float x
float z
std::uint64_t bytes
Definition eviction.cpp:823
#define APPLOG_TRACE(...)
Definition logging.h:17
const memory_view * copy(const void *_data, uint32_t _size)
Definition graphics.cpp:460
auto lerp(const T &start, const T &end, float progress, const ease_t &ease_func=ease::linear) -> T
Linearly interpolates between two values based on progress.
Definition seq_math.hpp:8
static constexpr uint16_t resolution
Definition cloud_noise.h:12
static constexpr uint16_t flat_resolution
Definition cloud_noise.h:13
std::unique_ptr< gfx::texture > flat_noise
256x256 RGBA16F 2D tileable noise for flat clouds.
Definition cloud_noise.h:23
static constexpr int tile_period
Definition cloud_noise.h:14
std::unique_ptr< gfx::texture > base_noise
64^3 RGBA16F 3D tileable noise for volumetric clouds.
Definition cloud_noise.h:20