25float smoothstep(
float edge0,
float edge1,
float x) {
26 if (edge0 == edge1)
return x < edge0 ? 0.f : 1.f;
27 const float t = std::clamp((
x - edge0) / (edge1 - edge0), 0.f, 1.f);
28 return t *
t * (3.f - 2.f *
t);
32float voronoiDist(
const NoiseField &
n,
float x,
float y) {
34 const int cx0 = int(std::floor(
x));
35 const int cy0 = int(std::floor(
y));
36 for (
int oy = -1;
oy <= 1; ++
oy) {
37 for (
int ox = -1;
ox <= 1; ++
ox) {
38 const int cx = cx0 +
ox;
39 const int cy = cy0 +
oy;
40 const float px = float(
cx) +
n.hash01(
cx,
cy);
41 const float py = float(
cy) +
n.hash01(
cx * 7 + 3,
cy * 13 + 5);
42 const float dx =
x -
px;
43 const float dy =
y -
py;
48 return std::sqrt(
best);
51std::unique_ptr<image::ImageData> makeFromHeightFn(
const Params &
params, std::string &
error,
52 const TextureRecipeDef &def) {
54 if (ctx.width > 4096 || ctx.height > 4096) {
55 error =
"texture size too large (max 4096)";
58 auto img = std::make_unique<image::ImageData>(ctx.width, ctx.height,
"RGBA8");
66TextureRecipeDef makeDef(std::string
id, ColorRamp ramp,
67 std::function<
float(
float,
float,
const NoiseField &)>
height,
70 def.id = std::move(
id);
71 def.albedo = std::move(ramp);
72 def.height = std::move(
height);
77std::vector<TextureRecipeDef> buildDefs() {
78 std::vector<TextureRecipeDef> defs;
83 ramp.add(0.00f, 58, 38, 22);
84 ramp.add(0.35f, 92, 62, 34);
85 ramp.add(0.65f, 120, 84, 48);
86 ramp.add(1.00f, 148, 110, 68);
88 pbr.roughnessLow = 0.75f;
89 pbr.roughnessHigh = 1.f;
90 defs.push_back(makeDef(
91 "tex.soil", std::move(ramp),
92 [](
float u,
float v,
const NoiseField &
n) {
93 float h =
n.fbm(
u,
v, 4);
94 h =
h * 0.75f + 0.25f *
n.valueNoise(
u * 3.1f + 2.f,
v * 3.1f);
103 ramp.add(0.00f, 52, 54, 58);
104 ramp.add(0.40f, 88, 90, 96);
105 ramp.add(0.70f, 130, 132, 138);
106 ramp.add(1.00f, 176, 178, 184);
108 pbr.roughnessLow = 0.6f;
109 pbr.roughnessHigh = 0.9f;
110 pbr.aoStrength = 1.4f;
111 defs.push_back(makeDef(
112 "tex.stone", std::move(ramp),
113 [](
float u,
float v,
const NoiseField &
n) {
114 float h =
n.ridged(
u,
v, 4);
115 const float crack = std::fabs(
n.valueNoise(
u * 2.7f,
v * 2.7f) - 0.5f) * 2.f;
116 h =
h * 0.7f + (1.f - crack) * 0.3f;
125 ramp.add(0.00f, 28, 24, 20);
126 ramp.add(0.28f, 58, 52, 44);
127 ramp.add(0.55f, 92, 84, 70);
128 ramp.add(0.80f, 122, 112, 96);
129 ramp.add(1.00f, 148, 138, 120);
131 pbr.roughnessLow = 0.62f;
132 pbr.roughnessHigh = 0.98f;
133 pbr.aoStrength = 1.7f;
134 pbr.normalStrength = 3.0f;
135 defs.push_back(makeDef(
136 "tex.rock", std::move(ramp),
137 [](
float u,
float v,
const NoiseField &
n) {
138 float h =
n.ridged(
u * 0.9f,
v * 0.9f, 5);
140 std::pow(std::fabs(
n.valueNoise(
u * 2.2f,
v * 2.2f) - 0.5f) * 2.f, 1.6f);
141 h =
h * 0.62f + 0.22f *
n.fbm(
u * 2.8f,
v * 2.8f, 3) + (1.f - crack) * 0.16f;
150 ramp.add(0.00f, 230, 230, 235);
151 ramp.add(0.45f, 200, 200, 210);
152 ramp.add(0.55f, 120, 120, 135);
153 ramp.add(0.70f, 190, 190, 200);
154 ramp.add(1.00f, 245, 245, 248);
156 pbr.roughnessLow = 0.08f;
157 pbr.roughnessHigh = 0.35f;
158 defs.push_back(makeDef(
159 "tex.marble", std::move(ramp),
160 [](
float u,
float v,
const NoiseField &
n) {
161 const float w =
n.warp(
u,
v, 2.5f, std::max(2, 3));
162 float vein = std::sin((
u +
w * 4.f) * 3.14159265f * 2.f) * 0.5f + 0.5f;
163 vein = std::pow(vein, 1.6f);
164 return vein * 0.65f +
w * 0.35f;
172 ramp.add(0.00f, 18, 60, 110);
173 ramp.add(0.40f, 28, 96, 150);
174 ramp.add(0.70f, 48, 140, 180);
175 ramp.add(1.00f, 120, 200, 210);
177 pbr.roughnessLow = 0.05f;
178 pbr.roughnessHigh = 0.3f;
179 pbr.metallic = 0.05f;
180 defs.push_back(makeDef(
181 "tex.water", std::move(ramp),
182 [](
float u,
float v,
const NoiseField &
n) {
183 float wave = 0.5f + 0.5f * std::sin((
u * 2.2f +
v * 0.4f) * 6.28318f +
184 n.valueNoise(
u * 1.3f,
v * 1.3f) * 2.f);
185 float detail =
n.fbm(
u * 1.5f,
v * 1.5f, 4);
186 return wave * 0.55f +
detail * 0.45f;
194 ramp.add(0.00f, 20, 60, 120);
195 ramp.add(0.40f, 40, 120, 190);
196 ramp.add(0.70f, 90, 170, 220);
197 ramp.add(1.00f, 190, 230, 245);
199 pbr.roughnessLow = 0.04f;
200 pbr.roughnessHigh = 0.5f;
201 defs.push_back(makeDef(
202 "tex.ripple", std::move(ramp),
203 [](
float u,
float v,
const NoiseField &
n) {
204 const float rings = 6.f;
205 float d = voronoiDist(
n,
u,
v);
206 float ring = 0.5f + 0.5f * std::sin(
d * 6.28318f * rings);
208 ring *= smoothstep(0.f, 0.3f,
d);
209 return ring * 0.7f +
n.fbm(
u * 2.f,
v * 2.f, 3) * 0.3f;
217 ramp.add(0.00f, 70, 130, 200);
218 ramp.add(0.45f, 110, 170, 220);
219 ramp.add(0.70f, 180, 210, 235);
220 ramp.add(1.00f, 245, 248, 252);
221 defs.push_back(makeDef(
222 "tex.sky_cloud", std::move(ramp),
223 [](
float u,
float v,
const NoiseField &
n) {
224 const float sky = std::clamp(1.f -
v / std::max(0.01f, 4.f), 0.f, 1.f);
225 float clouds =
n.fbm(
u * 0.9f + 3.f,
v * 0.9f, 4);
227 return sky * 0.45f +
clouds * 0.55f;
234 ramp.add(0.00f, 150, 106, 58);
235 ramp.add(0.30f, 178, 128, 72);
236 ramp.add(0.55f, 130, 88, 48);
237 ramp.add(0.75f, 96, 62, 36);
238 ramp.add(1.00f, 70, 44, 28);
240 pbr.roughnessLow = 0.35f;
241 pbr.roughnessHigh = 0.7f;
242 defs.push_back(makeDef(
243 "tex.wood", std::move(ramp),
244 [](
float u,
float v,
const NoiseField &
n) {
245 const float w =
n.warp(
u * 0.8f,
v * 0.8f, 2.5f, 3);
246 float grain = 0.5f + 0.5f * std::sin((
u * 4.f +
w * 6.f) * 6.28318f);
248 const float detail =
n.fbm(
u * 3.f,
v * 3.f, 3);
257 ramp.add(0.00f, 30, 44, 34);
258 ramp.add(0.40f, 52, 76, 52);
259 ramp.add(0.70f, 74, 104, 68);
260 ramp.add(1.00f, 108, 140, 92);
262 pbr.roughnessLow = 0.8f;
263 pbr.roughnessHigh = 1.f;
264 pbr.normalStrength = 2.f;
265 defs.push_back(makeDef(
266 "tex.cloth", std::move(ramp),
267 [](
float u,
float v,
const NoiseField &
n) {
268 const float threads = 9.f;
269 const float fx =
u * threads;
270 const float fy =
v * threads;
271 const float dx = std::fabs(std::fmod(fx, 1.f) - 0.5f);
272 const float dy = std::fabs(std::fmod(fy, 1.f) - 0.5f);
273 const float c = std::min(
dx,
dy);
274 float thread = 1.f -
c * 2.f;
275 const int ix = int(std::floor(fx));
276 const int iy = int(std::floor(fy));
277 if ((ix + iy) % 2 == 0) thread = 1.f - thread;
278 const float noise =
n.valueNoise(
u * 2.f,
v * 2.f);
279 return thread * 0.55f + noise * 0.35f + 0.1f;
287 ramp.add(0.00f, 88, 30, 60);
288 ramp.add(0.30f, 160, 74, 96);
289 ramp.add(0.50f, 224, 176, 120);
290 ramp.add(0.70f, 150, 96, 70);
291 ramp.add(1.00f, 74, 46, 60);
293 pbr.roughnessLow = 0.3f;
294 pbr.roughnessHigh = 0.6f;
295 defs.push_back(makeDef(
296 "tex.ornament", std::move(ramp),
297 [](
float u,
float v,
const NoiseField &
n) {
298 const float w =
n.warp(
u,
v, 3.5f, 4);
299 float swirl = 0.5f + 0.5f * (std::sin((
u +
w * 4.f) * 6.28318f) *
300 std::cos((
v -
w * 3.f) * 6.28318f));
301 return swirl * 0.6f +
n.fbm(
u * 2.f,
v * 2.f, 4) * 0.4f;
309 ramp.add(0.00f, 220, 200, 168);
310 ramp.add(0.35f, 196, 172, 138);
311 ramp.add(0.50f, 90, 62, 40);
312 ramp.add(0.75f, 60, 42, 30);
313 ramp.add(1.00f, 150, 120, 92);
315 pbr.roughnessLow = 0.5f;
316 pbr.roughnessHigh = 0.9f;
317 defs.push_back(makeDef(
318 "tex.spot", std::move(ramp),
319 [](
float u,
float v,
const NoiseField &
n) {
320 const float blobs =
n.valueNoise(
u * 2.2f,
v * 2.2f);
321 float spot = smoothstep(0.5f, 0.6f, blobs);
322 spot = spot * 0.8f +
n.fbm(
u * 3.f,
v * 3.f, 3) * 0.2f;
331 ramp.add(0.00f, 245, 245, 244);
332 ramp.add(0.50f, 30, 30, 32);
333 ramp.add(1.00f, 245, 245, 244);
335 pbr.roughnessLow = 0.4f;
336 pbr.roughnessHigh = 0.8f;
337 defs.push_back(makeDef(
338 "tex.zebra", std::move(ramp),
339 [](
float u,
float v,
const NoiseField &
n) {
340 const float w =
n.warp(
u,
v, 1.5f, 3);
341 float stripe = 0.5f + 0.5f * std::sin((
u * 5.f +
w * 4.f) * 6.28318f);
342 stripe = smoothstep(0.3f, 0.55f, stripe);
343 return stripe * 0.9f +
n.valueNoise(
u * 3.f,
v * 3.f) * 0.1f;
351 ramp.add(0.00f, 78, 46, 38);
352 ramp.add(0.30f, 130, 74, 54);
353 ramp.add(0.60f, 164, 96, 66);
354 ramp.add(1.00f, 196, 130, 92);
356 pbr.roughnessLow = 0.65f;
357 pbr.roughnessHigh = 0.95f;
358 pbr.aoStrength = 1.6f;
359 defs.push_back(makeDef(
360 "tex.wall", std::move(ramp),
361 [](
float u,
float v,
const NoiseField &
n) {
362 const float rows = 5.f;
364 const int row = int(std::floor(
v *
rows));
365 const float off = (
row % 2 == 0) ? 0.f : 0.5f;
366 const float bx =
u *
cols + off;
367 const float mh = std::fabs(std::fmod(
v *
rows, 1.f) - 0.5f);
368 const float mv = std::fabs(std::fmod(
bx, 1.f) - 0.5f);
369 float brick = 1.f - smoothstep(0.30f, 0.42f, std::min(mh, mv));
370 const float noise =
n.valueNoise(
u * 2.f,
v * 2.f);
371 return brick * 0.75f + noise * 0.25f * brick + (1.f - brick) * 0.15f;
379 ramp.add(0.00f, 60, 62, 66);
380 ramp.add(0.35f, 96, 100, 106);
381 ramp.add(0.70f, 138, 142, 148);
382 ramp.add(1.00f, 182, 186, 192);
384 pbr.roughnessLow = 0.6f;
385 pbr.roughnessHigh = 0.85f;
386 defs.push_back(makeDef(
387 "tex.cement", std::move(ramp),
388 [](
float u,
float v,
const NoiseField &
n) {
389 float mottle =
n.fbmPerlin(
u,
v, 4);
390 const float crack = 1.f - smoothstep(0.55f, 0.72f,
n.ridgedPerlin(
u * 3.f,
v * 3.f, 3));
391 return mottle * 0.6f + crack * 0.4f;
402 ramp.add(0.00f, 132, 134, 138);
403 ramp.add(0.30f, 172, 174, 178);
404 ramp.add(0.68f, 214, 216, 220);
405 ramp.add(1.00f, 244, 245, 247);
407 pbr.roughnessLow = 0.78f;
408 pbr.roughnessHigh = 0.96f;
409 pbr.normalStrength = 2.2f;
410 pbr.aoStrength = 0.65f;
411 pbr.heightStrength = 0.45f;
412 defs.push_back(makeDef(
413 "tex.asphalt", std::move(ramp),
414 [](
float u,
float v,
const NoiseField &
n) {
415 const float fine =
n.fbmPerlin(
u * 5.f,
v * 5.f, 4);
416 const float aggregate =
n.valueNoise(
u * 18.f + 3.7f,
v * 18.f + 9.1f);
417 const float wear = smoothstep(0.68f, 0.82f,
n.ridgedPerlin(
u * 1.6f,
v * 1.6f, 3));
418 return std::clamp(fine * 0.55f + aggregate * 0.35f -
wear * 0.18f + 0.16f, 0.f, 1.f);
426 ramp.add(0.00f, 40, 26, 18);
427 ramp.add(0.30f, 84, 56, 34);
428 ramp.add(0.60f, 116, 78, 46);
429 ramp.add(1.00f, 148, 104, 62);
431 pbr.roughnessLow = 0.7f;
432 pbr.roughnessHigh = 1.f;
433 pbr.aoStrength = 1.8f;
434 defs.push_back(makeDef(
435 "tex.mud", std::move(ramp),
436 [](
float u,
float v,
const NoiseField &
n) {
437 float d = voronoiDist(
n,
u,
v);
438 float plate = smoothstep(0.04f, 0.14f,
d);
439 return plate * 0.8f +
n.fbm(
u * 2.5f,
v * 2.5f, 3) * 0.2f;
447 ramp.add(0.00f, 28, 18, 12);
448 ramp.add(0.22f, 58, 38, 24);
449 ramp.add(0.48f, 98, 68, 42);
450 ramp.add(0.72f, 138, 102, 64);
451 ramp.add(1.00f, 72, 48, 30);
453 pbr.roughnessLow = 0.58f;
454 pbr.roughnessHigh = 0.98f;
455 pbr.aoStrength = 1.85f;
456 pbr.normalStrength = 3.6f;
457 defs.push_back(makeDef(
458 "tex.bark", std::move(ramp),
459 [](
float u,
float v,
const NoiseField &
n) {
460 const float warp =
n.warp(
u,
v, 2.1f, 3);
462 0.5f + 0.5f * std::sin((
u * 9.f +
warp * 6.f) * 6.28318f);
463 const float ridge =
n.ridged(
u * 1.6f,
v * 0.28f, 5);
465 std::pow(std::fabs(
n.valueNoise(
u * 1.4f,
v * 4.2f) - 0.5f) * 2.f, 1.55f);
466 const float flake =
n.fbm(
u * 3.2f + 1.7f,
v * 0.9f, 3);
467 return grain * 0.28f + ridge * 0.38f + (1.f - crack) * 0.22f + flake * 0.12f;
475 ramp.add(0.00f, 36, 40, 32);
476 ramp.add(0.30f, 62, 72, 48);
477 ramp.add(0.52f, 78, 108, 52);
478 ramp.add(0.72f, 96, 132, 58);
479 ramp.add(1.00f, 148, 156, 138);
481 pbr.roughnessLow = 0.72f;
482 pbr.roughnessHigh = 1.f;
483 pbr.aoStrength = 1.7f;
484 pbr.normalStrength = 3.1f;
485 defs.push_back(makeDef(
486 "tex.moss", std::move(ramp),
487 [](
float u,
float v,
const NoiseField &
n) {
488 float rock =
n.ridged(
u * 0.95f,
v * 0.95f, 5);
489 float moss = smoothstep(0.32f, 0.78f,
n.fbm(
u * 1.8f + 2.f,
v * 1.8f, 4));
491 std::pow(std::fabs(
n.valueNoise(
u * 2.4f,
v * 2.4f) - 0.5f) * 2.f, 1.5f);
492 return rock * (1.f - moss * 0.55f) + moss * 0.62f + (1.f - crack) * 0.08f;
501CloudField::Params cloudFieldFromParams(
const Params &
params) {
502 CloudField::Params
p;
504 p.worldScale =
params.getFloat(
"worldScale", 96.f);
505 p.coverage =
params.getFloat(
"cloudCoverage", 0.55f);
506 p.softness =
params.getFloat(
"cloudSoftness", 0.12f);
507 p.detail =
params.getFloat(
"cloudDetail", 0.5f);
508 p.windSpeed =
params.getFloat(
"windSpeed", 4.f);
509 p.windAngle =
params.getFloat(
"windAngle", 0.f);
510 p.octaves =
params.getInt(
"octaves", 4);
515std::unique_ptr<image::ImageData> genCloud(
const Params &
params, std::string &
error) {
517 if (ctx.width > 4096 || ctx.height > 4096) {
518 error =
"texture size too large (max 4096)";
521 auto img = std::make_unique<image::ImageData>(ctx.width, ctx.height,
"RGBA8");
523 ramp.add(0.00f, 90, 140, 205);
524 ramp.add(0.30f, 168, 205, 238);
525 ramp.add(0.55f, 238, 248, 252);
526 ramp.add(1.00f, 255, 255, 255);
529 const float time =
params.getFloat(
"time", 0.f);
530 const float extent =
params.getFloat(
"extent",
field.params().worldScale);
531 std::vector<float>
height(
size_t(ctx.width * ctx.height));
532 field.sample(
height.data(), ctx.width, ctx.height,
time, 0.f, 0.f, extent);
538std::unique_ptr<image::ImageData> genCloudShadow(
const Params &
params, std::string &
error) {
540 if (ctx.width > 4096 || ctx.height > 4096) {
541 error =
"texture size too large (max 4096)";
544 auto img = std::make_unique<image::ImageData>(ctx.width, ctx.height,
"RGBA8");
546 ramp.add(0.00f, 18, 20, 26);
547 ramp.add(0.30f, 60, 62, 70);
548 ramp.add(0.60f, 120, 122, 130);
549 ramp.add(1.00f, 200, 202, 208);
551 CloudField::Params fp = cloudFieldFromParams(
params);
552 CloudShadow::Params sp;
553 sp.field = CloudField(fp);
554 sp.sunDirX =
params.getFloat(
"sunDirX", 0.f);
555 sp.sunDirY =
params.getFloat(
"sunDirY", 1.f);
556 sp.sunDirZ =
params.getFloat(
"sunDirZ", 0.f);
557 sp.cloudAltitude =
params.getFloat(
"cloudAltitude", 60.f);
558 sp.strength =
params.getFloat(
"cloudShadowStrength", 0.8f);
559 CloudShadow shadow(sp);
561 const float time =
params.getFloat(
"time", 0.f);
562 const float extent =
params.getFloat(
"extent", fp.worldScale);
563 std::vector<float>
height(
size_t(ctx.width * ctx.height));
564 shadow.sampleCoverage(
height.data(), ctx.width, ctx.height,
time, 0.f, 0.f, extent);
581constexpr int kLeafCardPanels = 6;
582constexpr int kLeafCardCols = 2;
583constexpr int kLeafCardRows = 3;
588void buildBlueNoiseCardStamps(std::uint32_t
seed,
int count, std::vector<LeafStamp> &stamps) {
592 const float minDist01 = std::sqrt(0.42f /
float(
count));
593 constexpr int kSide = 64;
594 const float radius = std::max(1.5f, minDist01 *
float(kSide));
596 std::mt19937 rng(
seed ^ 0x9e3779b9u);
597 std::uniform_real_distribution<float>
unit(0.f, 1.f);
598 constexpr float kMargin = 0.07f;
600 for (
const ProcgenPoint &
p : pts.points()) {
603 s.cx = kMargin + (1.f - 2.f * kMargin) * (
p.x /
float(kSide));
604 s.cy = kMargin + (1.f - 2.f * kMargin) * (
p.z /
float(kSide));
605 s.ang =
unit(rng) * 6.28318530718f;
606 s.halfW = 0.050f + 0.028f *
unit(rng);
607 s.halfH = 0.070f + 0.036f *
unit(rng);
615 s.cx = kMargin + (1.f - 2.f * kMargin) *
unit(rng);
616 s.cy = kMargin + (1.f - 2.f * kMargin) *
unit(rng);
618 for (
const LeafStamp &o : stamps) {
619 const float dx =
s.cx - o.cx,
dy =
s.cy - o.cy;
620 if (
dx *
dx +
dy *
dy < minDist01 * minDist01) {
626 s.ang =
unit(rng) * 6.28318530718f;
627 s.halfW = 0.050f + 0.028f *
unit(rng);
628 s.halfH = 0.070f + 0.036f *
unit(rng);
634void sampleOvateLeafCard(
const NoiseField &noise,
float u,
float v,
const std::vector<LeafStamp> &stamps,
635 float &cover,
float &shade) {
638 for (
const LeafStamp &
s : stamps) {
641 const float ca = std::cos(
s.ang), sa = std::sin(
s.ang);
642 const float rx =
dx * ca -
dy * sa;
643 const float ry =
dx * sa +
dy * ca;
645 const float halfW =
s.halfW * (1.f - 0.42f * std::max(0.f, ry / std::max(1e-4f,
s.halfH)));
646 const float d = (rx * rx) / std::max(1e-5f,
halfW *
halfW) +
647 (ry * ry) / std::max(1e-5f,
s.halfH *
s.halfH);
648 const float mask = 1.f - smoothstep(0.78f, 1.02f,
d);
649 if (
mask <= cover)
continue;
652 shade = 0.48f + 0.12f * noise.hash01(
int(
s.cx * 97.f),
int(
s.cy * 53.f));
659void buildSixLeafCardPanels(std::uint32_t
seed,
const NoiseField &noise,
660 std::vector<std::vector<LeafStamp>> &panels) {
662 panels.resize(
size_t(kLeafCardPanels));
663 for (
int i = 0; i < kLeafCardPanels; ++i) {
664 const std::uint32_t panelSeed =
seed ^ (std::uint32_t(i + 1) * 2654435761u);
665 const int count = 8 + int(noise.hash01(i * 3 + 1, i * 7 + 2) * 8.999f);
666 buildBlueNoiseCardStamps(panelSeed,
count, panels[
size_t(i)]);
673void sampleLeafCardAtlas(
const NoiseField &noise,
float fu,
float v,
674 const std::vector<std::vector<LeafStamp>> &panels,
float &cover,
float &shade) {
675 const int col = std::clamp(
int(fu *
float(kLeafCardCols)), 0, kLeafCardCols - 1);
676 const int row = std::clamp(
int(
v *
float(kLeafCardRows)), 0, kLeafCardRows - 1);
677 const float localU = fu * float(kLeafCardCols) - float(col);
678 const float localV =
v * float(kLeafCardRows) - float(row);
679 const int panel =
row * kLeafCardCols + col;
680 sampleOvateLeafCard(noise,
localU,
localV, panels[
size_t(panel)], cover, shade);
686void sampleFoliageFill(
const NoiseField &noise,
float u,
float v,
float scale,
float &cover,
float &shade) {
689 const float su =
u *
scale;
691 for (
int oy = -1;
oy <= 1; ++
oy) {
692 for (
int ox = -1;
ox <= 1; ++
ox) {
693 const int cx = int(std::floor(su * 2.2f)) +
ox;
694 const int cy = int(std::floor(sv * 2.2f)) +
oy;
695 const float px = float(
cx) + noise.hash01(
cx,
cy);
696 const float py = float(
cy) + noise.hash01(
cx * 7 + 3,
cy * 13 + 5);
697 float dx = su * 2.2f -
px;
698 float dy =
sv * 2.2f -
py;
699 const float ang = (noise.hash01(
cx * 3 + 1,
cy * 5 + 2) - 0.5f) * 1.4f;
700 const float ca = std::cos(
ang), sa = std::sin(
ang);
701 const float rx =
dx * ca -
dy * sa;
702 const float ry =
dx * sa +
dy * ca;
703 const float halfW = 0.48f * (1.f - 0.4f * std::max(0.f, ry));
704 const float d = (rx * rx) / std::max(1e-4f,
halfW *
halfW) + (ry * ry) / 0.58f;
705 const float mask = 1.f - smoothstep(0.55f, 1.05f,
d);
706 if (
mask <= cover)
continue;
708 const float vein = std::pow(
709 std::fabs(std::sin(ry * 8.f + noise.valueNoise(
px * 0.35f,
py * 0.35f) * 2.5f)), 3.f);
710 const float mott = noise.fbm(
px * 0.28f +
u * 1.6f,
py * 0.28f +
v * 1.6f, 3);
711 shade = std::clamp(0.30f + mott * 0.42f + (1.f - vein) * 0.28f, 0.f, 1.f);
715 cover = std::clamp(cover * 0.55f + 0.45f, 0.f, 1.f);
721Rgba8 sampleBarkColor(
const NoiseField &noise,
float bu,
float v,
float scale,
int colors) {
723 barkRamp.add(0.00f, 28, 18, 12);
724 barkRamp.add(0.22f, 58, 38, 24);
725 barkRamp.add(0.48f, 102, 72, 44);
726 barkRamp.add(0.72f, 148, 110, 70);
727 barkRamp.add(1.00f, 68, 46, 28);
729 const float grain = 0.5f + 0.5f * std::sin((bu * 9.f +
warp * 6.f) * 6.28318f);
730 const float ridge = noise.ridged(bu * 1.6f *
scale,
v * 0.28f *
scale, 5);
732 std::pow(std::fabs(noise.valueNoise(bu * 1.4f *
scale,
v * 4.2f *
scale) - 0.5f) * 2.f, 1.55f);
733 const float flake = noise.fbm(bu * 3.2f *
scale + 1.7f,
v * 0.9f *
scale, 3);
735 std::clamp(
grain * 0.28f + ridge * 0.38f + (1.f - crack) * 0.22f + flake * 0.12f, 0.f, 1.f);
736 return barkRamp.sampleBanded(
h,
colors);
743std::unique_ptr<image::ImageData> genTreeAtlas(
const Params &
params, std::string &
error) {
745 if (ctx.width > 4096 || ctx.height > 4096) {
746 error =
"texture size too large (max 4096)";
749 auto img = std::make_unique<image::ImageData>(ctx.width, ctx.height,
"RGBA8");
751 noise.seed = ctx.seed;
753 noise.periodX = std::max(1,
int(ctx.scale));
754 noise.periodY = noise.periodX;
758 leafRamp.add(0.00f, 14, 36, 10);
759 leafRamp.add(0.28f, 32, 78, 22);
760 leafRamp.add(0.52f, 52, 118, 34);
761 leafRamp.add(0.78f, 88, 148, 46);
762 leafRamp.add(1.00f, 24, 54, 16);
764 std::vector<std::vector<LeafStamp>> leafPanels;
765 buildSixLeafCardPanels(std::uint32_t(ctx.seed), noise, leafPanels);
767 const float invW = 1.f / float(std::max(1, ctx.width - 1));
768 const float invH = 1.f / float(std::max(1, ctx.height - 1));
769 for (
int y = 0;
y < ctx.height; ++
y) {
770 for (
int x = 0;
x < ctx.width; ++
x) {
771 const float u = float(
x) * invW;
772 const float v = float(
y) * invH;
775 color = sampleBarkColor(noise,
u / 0.48f,
v, ctx.scale, ctx.colors);
776 }
else if (
u > 0.52f) {
777 const float fu = (
u - 0.52f) / 0.48f;
778 float cover = 0.f, shade = 0.f;
779 sampleLeafCardAtlas(noise, fu,
v, leafPanels, cover, shade);
781 color = {0, 0, 0, 0};
783 color = leafRamp.sample(shade);
787 color = {72, 86, 48, 255};
803std::unique_ptr<image::ImageData> genFoliage(
const Params &
params, std::string &
error) {
805 if (ctx.width > 4096 || ctx.height > 4096) {
806 error =
"texture size too large (max 4096)";
809 auto img = std::make_unique<image::ImageData>(ctx.width, ctx.height,
"RGBA8");
811 noise.seed = ctx.seed;
813 noise.periodX = std::max(1,
int(ctx.scale));
814 noise.periodY = noise.periodX;
819 leafRamp.add(0.00f, 12, 32, 16);
820 leafRamp.add(0.28f, 28, 68, 34);
821 leafRamp.add(0.52f, 42, 96, 48);
822 leafRamp.add(0.78f, 62, 122, 58);
823 leafRamp.add(1.00f, 22, 48, 28);
825 std::vector<std::vector<LeafStamp>> leafPanels;
826 buildSixLeafCardPanels(std::uint32_t(ctx.seed), noise, leafPanels);
828 const float invW = 1.f / float(std::max(1, ctx.width - 1));
829 const float invH = 1.f / float(std::max(1, ctx.height - 1));
830 for (
int y = 0;
y < ctx.height; ++
y) {
831 for (
int x = 0;
x < ctx.width; ++
x) {
832 const float u = float(
x) * invW;
833 const float v = float(
y) * invH;
834 float cover = 0.f, shade = 0.f;
837 color = sampleBarkColor(noise,
u / 0.22f,
v, ctx.scale, ctx.colors);
839 }
else if (
u < 0.24f) {
840 color = {72, 64, 42, 255};
841 }
else if (
u <= 0.50f) {
842 const float fu = (
u - 0.24f) / 0.26f;
843 sampleFoliageFill(noise, fu,
v, ctx.scale, cover, shade);
844 color = leafRamp.sampleBanded(shade, ctx.colors);
846 }
else if (
u < 0.52f) {
847 color = {40, 78, 36, 255};
849 const float fu = (
u - 0.52f) / 0.48f;
850 sampleLeafCardAtlas(noise, fu,
v, leafPanels, cover, shade);
852 color = {0, 0, 0, 0};
854 color = leafRamp.sample(shade);
870std::unique_ptr<image::ImageData> genFlower(
const Params &
params, std::string &
error) {
872 if (ctx.width > 4096 || ctx.height > 4096) {
873 error =
"texture size too large (max 4096)";
876 auto img = std::make_unique<image::ImageData>(ctx.width, ctx.height,
"RGBA8");
878 noise.seed = ctx.seed;
880 noise.periodX = std::max(1,
int(ctx.scale));
881 noise.periodY = noise.periodX;
885 stemRamp.add(0.00f, 28, 72, 28);
886 stemRamp.add(0.55f, 48, 110, 42);
887 stemRamp.add(1.00f, 34, 86, 32);
890 petalRamp.add(0.00f, 210, 200, 205);
891 petalRamp.add(0.40f, 245, 236, 240);
892 petalRamp.add(0.70f, 255, 250, 252);
893 petalRamp.add(1.00f, 255, 236, 180);
895 const float invW = 1.f / float(std::max(1, ctx.width - 1));
896 const float invH = 1.f / float(std::max(1, ctx.height - 1));
897 for (
int y = 0;
y < ctx.height; ++
y) {
898 for (
int x = 0;
x < ctx.width; ++
x) {
899 const float u = float(
x) * invW;
900 const float v = float(
y) * invH;
903 const float su =
u / 0.32f;
905 std::clamp(0.45f + 0.35f * noise.fbm(su * 2.f,
v * 4.f, 3) +
906 0.2f * std::sin(
v * 18.f),
908 color = stemRamp.sampleBanded(
h, ctx.colors);
912 const float pu = (
u - 0.36f) / 0.62f;
914 const float dx = (pu - 0.5f) * 2.f;
915 const float dy = (pv - 0.5f) * 2.f;
916 const float halfW = 0.72f * (1.f - 0.35f * std::max(0.f,
dy));
919 const float cover = 1.f - smoothstep(0.72f, 1.05f,
d);
920 const float vein = std::pow(
921 std::fabs(std::sin(
dy * 6.f + noise.valueNoise(pu * 2.f, pv * 2.f) * 2.f)),
923 const float centre = 1.f - smoothstep(0.05f, 0.42f, std::sqrt(
dx *
dx +
dy *
dy));
924 const float h = std::clamp(0.35f + (1.f - vein) * 0.25f + centre * 0.45f +
925 noise.fbm(pu * 3.f, pv * 3.f, 2) * 0.1f,
927 color = petalRamp.sampleBanded(
h, ctx.colors);
928 color.a =
static_cast<uint8_t
>(std::clamp(cover, 0.f, 1.f) * 255.f);
941 static const std::vector<TextureRecipeDef> defs = buildDefs();
946 if (builtinsRegistered_)
return;
948 auto cloudDescriptor = [](std::string
id, std::string
name,
bool shadow) {
966 registerRecipe(cloudDescriptor(
"tex.cloud",
"Cloud",
false), genCloud);
967 registerRecipe(cloudDescriptor(
"tex.cloud_shadow",
"Cloud Shadow",
true), genCloudShadow);
997 builtinsRegistered_ =
true;
std::array< float, 3 > scale
std::vector< float > colors
std::map< Cell, int > best
medialoader::Colorf Colorf
Owning, typed generation parameters.
void registerRecipe(const std::string &id, TextureRecipeFn fn)
Register a recipe without metadata.
void registerBuiltins()
Register engine-provided recipes once.
std::vector< ParamSpec > params
WGPUStringView sv(const char *s)
Build a WGPUStringView from a C string (null-safe, length auto-computed).
void registerRoadTextureRecipes(TextureRecipeRegistry ®istry)
Registers road texture recipes.
const EditorValue * field(const EditorValue &value, const char *name)
void registerCableTextureRecipes(TextureRecipeRegistry ®istry)
Register tex.cable.steel / tex.chain.iron / tex.rope.hemp.
void registerPrototypeTextureRecipes(TextureRecipeRegistry ®istry)
Register all patterns as tex.prototype.* texture recipes.
PointSet poissonDiskPoints(int width, int depth, float radius, uint32_t seed, int maxPoints)
Bridson blue-noise (Poisson disk) samples in a width x depth area (XZ, y=0).
RecipeDescriptor makeTextureRecipeDescriptor(const TextureRecipeDef &definition)
Build editable metadata for a texture definition.
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)
Paint height to image.
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]).
void registerFloorTextureRecipes(TextureRecipeRegistry ®istry)
Register tex.floor.wood and tex.floor.tile with schema metadata.
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
static ParamDescriptor boolean(std::string key, std::string label, bool defaultValue)
Construct a Boolean descriptor stored as zero or one in Params.
static ParamDescriptor floating(std::string key, std::string label, float defaultValue, float minimum, float maximum, float step)
Construct a bounded floating-point descriptor.
static ParamDescriptor integer(std::string key, std::string label, int defaultValue, int minimum, int maximum, int step=1)
Construct a bounded integer descriptor.
Complete metadata and parameter schema shared by every procgen recipe family.
static RecipeDescriptor grid(std::string id, std::string displayName, std::string category, int minimumWidth, int minimumHeight, int maximumWidth=4096, int maximumHeight=4096)
Create a schema with common seed, width and height parameters.
std::vector< ParamDescriptor > params
static TextureGenContext fromParams(const Params ¶ms)
From params.
A procedural texture is fully described by an albedo ramp + a displacement field + PBR knobs....