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);
24float voronoiDist(
const NoiseField &
n,
float x,
float y) {
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;
40 return std::sqrt(best);
43image::ImageData *makeFromHeightFn(
const Params ¶ms, std::string &
error,
44 const TextureRecipeDef &def) {
46 if (ctx.width > 4096 || ctx.height > 4096) {
47 error =
"texture size too large (max 4096)";
50 auto *img =
new image::ImageData(ctx.width, ctx.height,
"RGBA8");
58TextureRecipeDef makeDef(std::string
id, ColorRamp ramp,
59 std::function<
float(
float,
float,
const NoiseField &)>
height,
62 def.id = std::move(
id);
63 def.albedo = std::move(ramp);
64 def.height = std::move(
height);
69std::vector<TextureRecipeDef> buildDefs() {
70 std::vector<TextureRecipeDef> defs;
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);
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);
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);
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;
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);
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);
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);
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;
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);
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;
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);
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);
196 ring *= smoothstep(0.f, 0.3f,
d);
197 return ring * 0.7f +
n.fbm(
u * 2.f,
v * 2.f, 3) * 0.3f;
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;
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);
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;
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);
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);
262 float thread = 1.f -
c * 2.f;
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;
266 const float noise =
n.valueNoise(
u * 2.f,
v * 2.f);
267 return thread * 0.55f + noise * 0.35f + 0.1f;
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);
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;
297 ramp.add(0.00f, 220, 200, 168);
298 ramp.add(0.35f, 196, 172, 138);
299 ramp.add(0.50f, 90, 62, 40);
300 ramp.add(0.75f, 60, 42, 30);
301 ramp.add(1.00f, 150, 120, 92);
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;
319 ramp.add(0.00f, 245, 245, 244);
320 ramp.add(0.50f, 30, 30, 32);
321 ramp.add(1.00f, 245, 245, 244);
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;
339 ramp.add(0.00f, 78, 46, 38);
340 ramp.add(0.30f, 130, 74, 54);
341 ramp.add(0.60f, 164, 96, 66);
342 ramp.add(1.00f, 196, 130, 92);
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;
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);
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;
387 ramp.add(0.00f, 40, 26, 18);
388 ramp.add(0.30f, 84, 56, 34);
389 ramp.add(0.60f, 116, 78, 46);
390 ramp.add(1.00f, 148, 104, 62);
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);
400 return plate * 0.8f +
n.fbm(
u * 2.5f,
v * 2.5f, 3) * 0.2f;
409CloudField::Params cloudFieldFromParams(
const Params ¶ms) {
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);
423image::ImageData *genCloud(
const Params ¶ms, std::string &
error) {
425 if (ctx.width > 4096 || ctx.height > 4096) {
426 error =
"texture size too large (max 4096)";
429 auto *img =
new image::ImageData(ctx.width, ctx.height,
"RGBA8");
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);
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);
446image::ImageData *genCloudShadow(
const Params ¶ms, std::string &
error) {
448 if (ctx.width > 4096 || ctx.height > 4096) {
449 error =
"texture size too large (max 4096)";
452 auto *img =
new image::ImageData(ctx.width, ctx.height,
"RGBA8");
454 ramp.add(0.00f, 18, 20, 26);
455 ramp.add(0.30f, 60, 62, 70);
456 ramp.add(0.60f, 120, 122, 130);
457 ramp.add(1.00f, 200, 202, 208);
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);
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);
480 static const std::vector<TextureRecipeDef> defs = buildDefs();
485 if (builtinsRegistered_)
return;
490 return makeFromHeightFn(params, error, def);
493 builtinsRegistered_ =
true;
Generation parameters. Algorithm-specific keys live in values as strings (no overloads; typed setters...
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.
static TextureGenContext fromParams(const Params ¶ms)
A procedural texture is fully described by an albedo ramp + a displacement field + PBR knobs....