载入中...
搜索中...
未找到
BuiltinTextures.cpp
浏览该文件的文档.
6
7#include "image/ImageData.h"
8
9#include <algorithm>
10#include <cmath>
11#include <functional>
12#include <vector>
13
14namespace eve::procgen {
15namespace {
16
17float smoothstep(float edge0, float edge1, float x) {
18 if (edge0 == edge1) return x < edge0 ? 0.f : 1.f;
19 const float t = std::clamp((x - edge0) / (edge1 - edge0), 0.f, 1.f);
20 return t * t * (3.f - 2.f * t);
21}
22
24float voronoiDist(const NoiseField &n, float x, float y) {
25 float best = 1e18f;
26 const int cx0 = int(std::floor(x));
27 const int cy0 = int(std::floor(y));
28 for (int oy = -1; oy <= 1; ++oy) {
29 for (int ox = -1; ox <= 1; ++ox) {
30 const int cx = cx0 + ox;
31 const int cy = cy0 + oy;
32 const float px = float(cx) + n.hash01(cx, cy);
33 const float py = float(cy) + n.hash01(cx * 7 + 3, cy * 13 + 5);
34 const float dx = x - px;
35 const float dy = y - py;
36 const float d = dx * dx + dy * dy;
37 if (d < best) best = d;
38 }
39 }
40 return std::sqrt(best);
41}
42
43image::ImageData *makeFromHeightFn(const Params &params, std::string &error,
44 const TextureRecipeDef &def) {
45 const auto ctx = TextureGenContext::fromParams(params);
46 if (ctx.width > 4096 || ctx.height > 4096) {
47 error = "texture size too large (max 4096)";
48 return nullptr;
49 }
50 auto *img = new image::ImageData(ctx.width, ctx.height, "RGBA8");
51 std::vector<float> height;
52 fillHeightField(ctx, def.height, height);
53 paintHeightToImage(*img, height, ctx.width, ctx.height, def.albedo, ctx.colors, ctx.pixelSize);
54 return img;
55}
56
58TextureRecipeDef makeDef(std::string id, ColorRamp ramp,
59 std::function<float(float, float, const NoiseField &)> height,
60 PbrParams pbr = {}) {
61 TextureRecipeDef def;
62 def.id = std::move(id);
63 def.albedo = std::move(ramp);
64 def.height = std::move(height);
65 def.pbr = pbr;
66 return def;
67}
68
69std::vector<TextureRecipeDef> buildDefs() {
70 std::vector<TextureRecipeDef> defs;
71
72 // --- tex.soil: loose dark soil ---
73 {
74 ColorRamp ramp;
75 ramp.add(0.00f, 58, 38, 22);
76 ramp.add(0.35f, 92, 62, 34);
77 ramp.add(0.65f, 120, 84, 48);
78 ramp.add(1.00f, 148, 110, 68);
79 PbrParams pbr;
80 pbr.roughnessLow = 0.75f;
81 pbr.roughnessHigh = 1.f;
82 defs.push_back(makeDef(
83 "tex.soil", std::move(ramp),
84 [](float u, float v, const NoiseField &n) {
85 float h = n.fbm(u, v, 4);
86 h = h * 0.75f + 0.25f * n.valueNoise(u * 3.1f + 2.f, v * 3.1f);
87 return h;
88 },
89 pbr));
90 }
91
92 // --- tex.stone: cracked stone ---
93 {
94 ColorRamp ramp;
95 ramp.add(0.00f, 52, 54, 58);
96 ramp.add(0.40f, 88, 90, 96);
97 ramp.add(0.70f, 130, 132, 138);
98 ramp.add(1.00f, 176, 178, 184);
99 PbrParams pbr;
100 pbr.roughnessLow = 0.6f;
101 pbr.roughnessHigh = 0.9f;
102 pbr.aoStrength = 1.4f;
103 defs.push_back(makeDef(
104 "tex.stone", std::move(ramp),
105 [](float u, float v, const NoiseField &n) {
106 float h = n.ridged(u, v, 4);
107 const float crack = std::fabs(n.valueNoise(u * 2.7f, v * 2.7f) - 0.5f) * 2.f;
108 h = h * 0.7f + (1.f - crack) * 0.3f;
109 return h;
110 },
111 pbr));
112 }
113
114 // --- tex.rock: warm boulder, larger-scale ridged ---
115 {
116 ColorRamp ramp;
117 ramp.add(0.00f, 60, 54, 46);
118 ramp.add(0.35f, 96, 88, 76);
119 ramp.add(0.65f, 132, 122, 104);
120 ramp.add(1.00f, 168, 158, 140);
121 PbrParams pbr;
122 pbr.roughnessLow = 0.6f;
123 pbr.roughnessHigh = 0.95f;
124 pbr.aoStrength = 1.3f;
125 defs.push_back(makeDef(
126 "tex.rock", std::move(ramp),
127 [](float u, float v, const NoiseField &n) {
128 float h = n.ridged(u * 0.8f, v * 0.8f, 4);
129 h = h * 0.75f + 0.25f * n.fbm(u * 2.5f, v * 2.5f, 2);
130 return h;
131 },
132 pbr));
133 }
134
135 // --- tex.marble: polished veined marble ---
136 {
137 ColorRamp ramp;
138 ramp.add(0.00f, 230, 230, 235);
139 ramp.add(0.45f, 200, 200, 210);
140 ramp.add(0.55f, 120, 120, 135);
141 ramp.add(0.70f, 190, 190, 200);
142 ramp.add(1.00f, 245, 245, 248);
143 PbrParams pbr;
144 pbr.roughnessLow = 0.08f;
145 pbr.roughnessHigh = 0.35f;
146 defs.push_back(makeDef(
147 "tex.marble", std::move(ramp),
148 [](float u, float v, const NoiseField &n) {
149 const float w = n.warp(u, v, 2.5f, std::max(2, 3));
150 float vein = std::sin((u + w * 4.f) * 3.14159265f * 2.f) * 0.5f + 0.5f;
151 vein = std::pow(vein, 1.6f);
152 return vein * 0.65f + w * 0.35f;
153 },
154 pbr));
155 }
156
157 // --- tex.water: rippling water surface ---
158 {
159 ColorRamp ramp;
160 ramp.add(0.00f, 18, 60, 110);
161 ramp.add(0.40f, 28, 96, 150);
162 ramp.add(0.70f, 48, 140, 180);
163 ramp.add(1.00f, 120, 200, 210);
164 PbrParams pbr;
165 pbr.roughnessLow = 0.05f;
166 pbr.roughnessHigh = 0.3f;
167 pbr.metallic = 0.05f;
168 defs.push_back(makeDef(
169 "tex.water", std::move(ramp),
170 [](float u, float v, const NoiseField &n) {
171 float wave = 0.5f + 0.5f * std::sin((u * 2.2f + v * 0.4f) * 6.28318f +
172 n.valueNoise(u * 1.3f, v * 1.3f) * 2.f);
173 float detail = n.fbm(u * 1.5f, v * 1.5f, 4);
174 return wave * 0.55f + detail * 0.45f;
175 },
176 pbr));
177 }
178
179 // --- tex.ripple: concentric ripples from scatter points ---
180 {
181 ColorRamp ramp;
182 ramp.add(0.00f, 20, 60, 120);
183 ramp.add(0.40f, 40, 120, 190);
184 ramp.add(0.70f, 90, 170, 220);
185 ramp.add(1.00f, 190, 230, 245);
186 PbrParams pbr;
187 pbr.roughnessLow = 0.04f;
188 pbr.roughnessHigh = 0.5f;
189 defs.push_back(makeDef(
190 "tex.ripple", std::move(ramp),
191 [](float u, float v, const NoiseField &n) {
192 const float rings = 6.f;
193 float d = voronoiDist(n, u, v);
194 float ring = 0.5f + 0.5f * std::sin(d * 6.28318f * rings);
195 // fade rings near each source centre.
196 ring *= smoothstep(0.f, 0.3f, d);
197 return ring * 0.7f + n.fbm(u * 2.f, v * 2.f, 3) * 0.3f;
198 },
199 pbr));
200 }
201
202 // --- tex.sky_cloud: sky with clouds ---
203 {
204 ColorRamp ramp;
205 ramp.add(0.00f, 70, 130, 200);
206 ramp.add(0.45f, 110, 170, 220);
207 ramp.add(0.70f, 180, 210, 235);
208 ramp.add(1.00f, 245, 248, 252);
209 defs.push_back(makeDef(
210 "tex.sky_cloud", std::move(ramp),
211 [](float u, float v, const NoiseField &n) {
212 const float sky = std::clamp(1.f - v / std::max(0.01f, 4.f), 0.f, 1.f);
213 float clouds = n.fbm(u * 0.9f + 3.f, v * 0.9f, 4);
214 clouds = smoothstep(0.35f, 0.75f, clouds);
215 return sky * 0.45f + clouds * 0.55f;
216 }));
217 }
218
219 // --- tex.wood: wavy grain running along V ---
220 {
221 ColorRamp ramp;
222 ramp.add(0.00f, 150, 106, 58);
223 ramp.add(0.30f, 178, 128, 72);
224 ramp.add(0.55f, 130, 88, 48);
225 ramp.add(0.75f, 96, 62, 36);
226 ramp.add(1.00f, 70, 44, 28);
227 PbrParams pbr;
228 pbr.roughnessLow = 0.35f;
229 pbr.roughnessHigh = 0.7f;
230 defs.push_back(makeDef(
231 "tex.wood", std::move(ramp),
232 [](float u, float v, const NoiseField &n) {
233 const float w = n.warp(u * 0.8f, v * 0.8f, 2.5f, 3);
234 float grain = 0.5f + 0.5f * std::sin((u * 4.f + w * 6.f) * 6.28318f);
235 grain = std::pow(grain, 2.4f);
236 const float detail = n.fbm(u * 3.f, v * 3.f, 3);
237 return grain * 0.6f + detail * 0.4f;
238 },
239 pbr));
240 }
241
242 // --- tex.cloth: woven fabric ---
243 {
244 ColorRamp ramp;
245 ramp.add(0.00f, 30, 44, 34);
246 ramp.add(0.40f, 52, 76, 52);
247 ramp.add(0.70f, 74, 104, 68);
248 ramp.add(1.00f, 108, 140, 92);
249 PbrParams pbr;
250 pbr.roughnessLow = 0.8f;
251 pbr.roughnessHigh = 1.f;
252 pbr.normalStrength = 2.f;
253 defs.push_back(makeDef(
254 "tex.cloth", std::move(ramp),
255 [](float u, float v, const NoiseField &n) {
256 const float threads = 9.f;
257 const float fx = u * threads;
258 const float fy = v * threads;
259 const float dx = std::fabs(std::fmod(fx, 1.f) - 0.5f);
260 const float dy = std::fabs(std::fmod(fy, 1.f) - 0.5f);
261 const float c = std::min(dx, dy); // 0 at thread, .5 between
262 float thread = 1.f - c * 2.f; // thread cross-section
263 const int ix = int(std::floor(fx));
264 const int iy = int(std::floor(fy));
265 if ((ix + iy) % 2 == 0) thread = 1.f - thread; // interlace over/under
266 const float noise = n.valueNoise(u * 2.f, v * 2.f);
267 return thread * 0.55f + noise * 0.35f + 0.1f;
268 },
269 pbr));
270 }
271
272 // --- tex.ornament: irregular decorative swirl ---
273 {
274 ColorRamp ramp;
275 ramp.add(0.00f, 88, 30, 60);
276 ramp.add(0.30f, 160, 74, 96);
277 ramp.add(0.50f, 224, 176, 120);
278 ramp.add(0.70f, 150, 96, 70);
279 ramp.add(1.00f, 74, 46, 60);
280 PbrParams pbr;
281 pbr.roughnessLow = 0.3f;
282 pbr.roughnessHigh = 0.6f;
283 defs.push_back(makeDef(
284 "tex.ornament", std::move(ramp),
285 [](float u, float v, const NoiseField &n) {
286 const float w = n.warp(u, v, 3.5f, 4);
287 float swirl = 0.5f + 0.5f * (std::sin((u + w * 4.f) * 6.28318f) *
288 std::cos((v - w * 3.f) * 6.28318f));
289 return swirl * 0.6f + n.fbm(u * 2.f, v * 2.f, 4) * 0.4f;
290 },
291 pbr));
292 }
293
294 // --- tex.spot: blobby spots (leopard / dalmatian) ---
295 {
296 ColorRamp ramp;
297 ramp.add(0.00f, 220, 200, 168); // pale base
298 ramp.add(0.35f, 196, 172, 138);
299 ramp.add(0.50f, 90, 62, 40); // dark spot
300 ramp.add(0.75f, 60, 42, 30);
301 ramp.add(1.00f, 150, 120, 92);
302 PbrParams pbr;
303 pbr.roughnessLow = 0.5f;
304 pbr.roughnessHigh = 0.9f;
305 defs.push_back(makeDef(
306 "tex.spot", std::move(ramp),
307 [](float u, float v, const NoiseField &n) {
308 const float blobs = n.valueNoise(u * 2.2f, v * 2.2f);
309 float spot = smoothstep(0.5f, 0.6f, blobs);
310 spot = spot * 0.8f + n.fbm(u * 3.f, v * 3.f, 3) * 0.2f;
311 return spot;
312 },
313 pbr));
314 }
315
316 // --- tex.zebra: wavy black/white stripes ---
317 {
318 ColorRamp ramp;
319 ramp.add(0.00f, 245, 245, 244);
320 ramp.add(0.50f, 30, 30, 32);
321 ramp.add(1.00f, 245, 245, 244);
322 PbrParams pbr;
323 pbr.roughnessLow = 0.4f;
324 pbr.roughnessHigh = 0.8f;
325 defs.push_back(makeDef(
326 "tex.zebra", std::move(ramp),
327 [](float u, float v, const NoiseField &n) {
328 const float w = n.warp(u, v, 1.5f, 3);
329 float stripe = 0.5f + 0.5f * std::sin((u * 5.f + w * 4.f) * 6.28318f);
330 stripe = smoothstep(0.3f, 0.55f, stripe);
331 return stripe * 0.9f + n.valueNoise(u * 3.f, v * 3.f) * 0.1f;
332 },
333 pbr));
334 }
335
336 // --- tex.wall: brick wall with recessed mortar ---
337 {
338 ColorRamp ramp;
339 ramp.add(0.00f, 78, 46, 38); // mortar
340 ramp.add(0.30f, 130, 74, 54);
341 ramp.add(0.60f, 164, 96, 66);
342 ramp.add(1.00f, 196, 130, 92);
343 PbrParams pbr;
344 pbr.roughnessLow = 0.65f;
345 pbr.roughnessHigh = 0.95f;
346 pbr.aoStrength = 1.6f;
347 defs.push_back(makeDef(
348 "tex.wall", std::move(ramp),
349 [](float u, float v, const NoiseField &n) {
350 const float rows = 5.f;
351 const float cols = 2.f * rows;
352 const int row = int(std::floor(v * rows));
353 const float off = (row % 2 == 0) ? 0.f : 0.5f;
354 const float bx = u * cols + off;
355 const float mh = std::fabs(std::fmod(v * rows, 1.f) - 0.5f);
356 const float mv = std::fabs(std::fmod(bx, 1.f) - 0.5f);
357 float brick = 1.f - smoothstep(0.30f, 0.42f, std::min(mh, mv));
358 const float noise = n.valueNoise(u * 2.f, v * 2.f);
359 return brick * 0.75f + noise * 0.25f * brick + (1.f - brick) * 0.15f;
360 },
361 pbr));
362 }
363
364 // --- tex.cement: mottled grey with hairline cracks ---
365 {
366 ColorRamp ramp;
367 ramp.add(0.00f, 60, 62, 66);
368 ramp.add(0.35f, 96, 100, 106);
369 ramp.add(0.70f, 138, 142, 148);
370 ramp.add(1.00f, 182, 186, 192);
371 PbrParams pbr;
372 pbr.roughnessLow = 0.6f;
373 pbr.roughnessHigh = 0.85f;
374 defs.push_back(makeDef(
375 "tex.cement", std::move(ramp),
376 [](float u, float v, const NoiseField &n) {
377 float mottle = n.fbmPerlin(u, v, 4);
378 const float crack = 1.f - smoothstep(0.55f, 0.72f, n.ridgedPerlin(u * 3.f, v * 3.f, 3));
379 return mottle * 0.6f + crack * 0.4f;
380 },
381 pbr));
382 }
383
384 // --- tex.mud: dried cracked mud plates ---
385 {
386 ColorRamp ramp;
387 ramp.add(0.00f, 40, 26, 18); // crack
388 ramp.add(0.30f, 84, 56, 34);
389 ramp.add(0.60f, 116, 78, 46);
390 ramp.add(1.00f, 148, 104, 62);
391 PbrParams pbr;
392 pbr.roughnessLow = 0.7f;
393 pbr.roughnessHigh = 1.f;
394 pbr.aoStrength = 1.8f;
395 defs.push_back(makeDef(
396 "tex.mud", std::move(ramp),
397 [](float u, float v, const NoiseField &n) {
398 float d = voronoiDist(n, u, v);
399 float plate = smoothstep(0.04f, 0.14f, d); // 0 on cracks, 1 inside plates
400 return plate * 0.8f + n.fbm(u * 2.5f, v * 2.5f, 3) * 0.2f;
401 },
402 pbr));
403 }
404
405 return defs;
406}
407
409CloudField::Params cloudFieldFromParams(const Params &params) {
410 CloudField::Params p;
411 p.seed = params.getSeed();
412 p.worldScale = params.getFloat("worldScale", 96.f);
413 p.coverage = params.getFloat("cloudCoverage", 0.55f);
414 p.softness = params.getFloat("cloudSoftness", 0.12f);
415 p.detail = params.getFloat("cloudDetail", 0.5f);
416 p.windSpeed = params.getFloat("windSpeed", 4.f);
417 p.windAngle = params.getFloat("windAngle", 0.f);
418 p.octaves = params.getInt("octaves", 4);
419 return p;
420}
421
423image::ImageData *genCloud(const Params &params, std::string &error) {
424 const auto ctx = TextureGenContext::fromParams(params);
425 if (ctx.width > 4096 || ctx.height > 4096) {
426 error = "texture size too large (max 4096)";
427 return nullptr;
428 }
429 auto *img = new image::ImageData(ctx.width, ctx.height, "RGBA8");
430 ColorRamp ramp;
431 ramp.add(0.00f, 90, 140, 205);
432 ramp.add(0.30f, 168, 205, 238);
433 ramp.add(0.55f, 238, 248, 252);
434 ramp.add(1.00f, 255, 255, 255);
435
436 CloudField field(cloudFieldFromParams(params));
437 const float time = params.getFloat("time", 0.f);
438 const float extent = params.getFloat("extent", field.params().worldScale);
439 std::vector<float> height(size_t(ctx.width * ctx.height));
440 field.sample(height.data(), ctx.width, ctx.height, time, 0.f, 0.f, extent);
441 paintHeightToImage(*img, height, ctx.width, ctx.height, ramp, ctx.colors, ctx.pixelSize);
442 return img;
443}
444
446image::ImageData *genCloudShadow(const Params &params, std::string &error) {
447 const auto ctx = TextureGenContext::fromParams(params);
448 if (ctx.width > 4096 || ctx.height > 4096) {
449 error = "texture size too large (max 4096)";
450 return nullptr;
451 }
452 auto *img = new image::ImageData(ctx.width, ctx.height, "RGBA8");
453 ColorRamp ramp;
454 ramp.add(0.00f, 18, 20, 26); // clear (lit)
455 ramp.add(0.30f, 60, 62, 70);
456 ramp.add(0.60f, 120, 122, 130);
457 ramp.add(1.00f, 200, 202, 208); // dense cloud (strong shadow)
458
459 CloudField::Params fp = cloudFieldFromParams(params);
460 CloudShadow::Params sp;
461 sp.field = CloudField(fp);
462 sp.sunDirX = params.getFloat("sunDirX", 0.f);
463 sp.sunDirY = params.getFloat("sunDirY", 1.f);
464 sp.sunDirZ = params.getFloat("sunDirZ", 0.f);
465 sp.cloudAltitude = params.getFloat("cloudAltitude", 60.f);
466 sp.strength = params.getFloat("cloudShadowStrength", 0.8f);
467 CloudShadow shadow(sp);
468
469 const float time = params.getFloat("time", 0.f);
470 const float extent = params.getFloat("extent", fp.worldScale);
471 std::vector<float> height(size_t(ctx.width * ctx.height));
472 shadow.sampleCoverage(height.data(), ctx.width, ctx.height, time, 0.f, 0.f, extent);
473 paintHeightToImage(*img, height, ctx.width, ctx.height, ramp, ctx.colors, ctx.pixelSize);
474 return img;
475}
476
477} // namespace
478
479const std::vector<TextureRecipeDef> &builtinTextureDefs() {
480 static const std::vector<TextureRecipeDef> defs = buildDefs();
481 return defs;
482}
483
485 if (builtinsRegistered_) return;
486 registerRecipe("tex.cloud", genCloud);
487 registerRecipe("tex.cloud_shadow", genCloudShadow);
488 for (const TextureRecipeDef &def : builtinTextureDefs()) {
489 registerRecipe(def.id, [def](const Params &params, std::string &error) {
490 return makeFromHeightFn(params, error, def);
491 });
492 }
493 builtinsRegistered_ = true;
494}
495
496} // namespace eve::procgen
float cx
Definition CardTypes.cpp:31
float cy
Definition CardTypes.cpp:32
int y
Definition Grass.cpp:135
float height
Definition Grass.cpp:235
float u
Definition Grass.cpp:234
int x
Definition Grass.cpp:135
glm::vec3 n
Definition Grass.cpp:64
int h
int w
std::vector< Colorf > px
std::string error
uint32_t c
glm::vec4 p[6]
int d
int v
Generation parameters. Algorithm-specific keys live in values as strings (no overloads; typed setters...
Definition Params.h:13
void registerRecipe(const std::string &id, TextureRecipeFn fn)
const std::vector< TextureRecipeDef > & builtinTextureDefs()
All built-in texture definitions (albedo + PBR). Populated lazily.
void paintHeightToImage(image::ImageData &img, const std::vector< float > &height, int w, int h, const ColorRamp &ramp, int bands, int pixelSize)
void fillHeightField(const TextureGenContext &ctx, const std::function< float(float, float, const NoiseField &)> &fn, std::vector< float > &height)
Sample a height function over the full [w×h] grid into height (clamped to [0,1]).
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
Definition Widget.cpp:431
static TextureGenContext fromParams(const Params &params)
A procedural texture is fully described by an albedo ramp + a displacement field + PBR knobs....