Source code for wolfhece.generate_pbr_presets

from __future__ import annotations

from pathlib import Path

import numpy as np
from PIL import Image

from .pyvertexvectors.polygon_pbr_material import get_pbr_presets_root


[docs] def _save_rgb(path: Path, arr: np.ndarray) -> None: path.parent.mkdir(parents=True, exist_ok=True) arr_u8 = np.clip(arr, 0, 255).astype(np.uint8) Image.fromarray(arr_u8, mode='RGB').save(path)
[docs] def _save_gray(path: Path, arr: np.ndarray) -> None: path.parent.mkdir(parents=True, exist_ok=True) arr_u8 = np.clip(arr, 0, 255).astype(np.uint8) Image.fromarray(arr_u8, mode='L').save(path)
[docs] def _normal_from_height(height: np.ndarray, strength: float = 2.0) -> np.ndarray: h = height.astype(np.float32) / 255.0 gy, gx = np.gradient(h) nx = -gx * strength ny = -gy * strength nz = np.ones_like(nx) norm = np.sqrt(nx * nx + ny * ny + nz * nz) + 1e-8 nx /= norm ny /= norm nz /= norm return np.stack([ (nx * 0.5 + 0.5) * 255.0, (ny * 0.5 + 0.5) * 255.0, (nz * 0.5 + 0.5) * 255.0, ], axis=-1)
[docs] def _make_concrete(base_dir: Path, size: int = 256) -> None: rng = np.random.default_rng(42) noise = rng.normal(0.0, 18.0, (size, size)) speckle = rng.normal(0.0, 25.0, (size, size)) mask = rng.random((size, size)) < 0.07 noise += speckle * mask gray = 140.0 + noise albedo = np.stack([gray * 1.02, gray, gray * 0.97], axis=-1) normal = _normal_from_height(gray, strength=3.0) ao = np.full((size, size), 200.0) rough = np.full((size, size), 220.0) # metallic: 0.2 baseline (50/255) for non-metallic surfaces metallic = np.full((size, size), 50.0) orm = np.stack([ao, rough, metallic], axis=-1) folder = base_dir / 'concrete' _save_rgb(folder / 'albedo.png', albedo) _save_rgb(folder / 'normal.png', normal) _save_rgb(folder / 'orm.png', orm)
[docs] def _make_rusted_metal(base_dir: Path, size: int = 256) -> None: rng = np.random.default_rng(123) yy, xx = np.mgrid[0:size, 0:size] bands = 0.5 + 0.5 * np.sin((xx + yy * 0.3) / 12.0) rust_mask = bands > 0.58 metal = np.stack([ np.full((size, size), 108.0), np.full((size, size), 112.0), np.full((size, size), 118.0), ], axis=-1) rust = np.stack([ np.full((size, size), 142.0), np.full((size, size), 70.0), np.full((size, size), 42.0), ], axis=-1) grain = rng.normal(0.0, 12.0, (size, size, 1)) albedo = np.where(rust_mask[..., None], rust, metal) + grain height = np.where(rust_mask, 190.0, 140.0) + rng.normal(0.0, 8.0, (size, size)) normal = _normal_from_height(height, strength=4.0) ao = np.where(rust_mask, 210.0, 170.0) rough = np.where(rust_mask, 235.0, 115.0) # metallic: rust zones are non-metallic (60/255=0.24), metal zones are highly metallic (245/255=0.96) metallic = np.where(rust_mask, 60.0, 245.0) orm = np.stack([ao, rough, metallic], axis=-1) folder = base_dir / 'rusted_metal' _save_rgb(folder / 'albedo.png', albedo) _save_rgb(folder / 'normal.png', normal) _save_rgb(folder / 'orm.png', orm)
[docs] def _make_asphalt(base_dir: Path, size: int = 256) -> None: rng = np.random.default_rng(999) base = 52.0 + rng.normal(0.0, 10.0, (size, size)) crack = np.zeros((size, size), dtype=np.float32) for _ in range(8): x0 = rng.integers(0, size) y0 = rng.integers(0, size) x1 = (x0 + rng.integers(-120, 120)) % size y1 = (y0 + rng.integers(-120, 120)) % size n = 250 xs = np.linspace(x0, x1, n).astype(int) % size ys = np.linspace(y0, y1, n).astype(int) % size crack[ys, xs] = 1.0 crack_blur = crack.copy() for _ in range(3): crack_blur = ( crack_blur + np.roll(crack_blur, 1, axis=0) + np.roll(crack_blur, -1, axis=0) + np.roll(crack_blur, 1, axis=1) + np.roll(crack_blur, -1, axis=1) ) / 5.0 height = base - crack_blur * 25.0 albedo_gray = base - crack_blur * 15.0 albedo = np.stack([albedo_gray, albedo_gray, albedo_gray], axis=-1) normal = _normal_from_height(height, strength=5.0) ao = 180.0 + crack_blur * 40.0 rough = 235.0 - crack_blur * 30.0 # metallic: 0.2 baseline (50/255) for non-metallic asphalt surface metallic = np.full((size, size), 50.0) orm = np.stack([ao, rough, metallic], axis=-1) emissive = np.zeros((size, size), dtype=np.float32) stripe = (np.abs((np.arange(size) - size // 2)) < 2).astype(np.float32) emissive += stripe[None, :] * 90.0 folder = base_dir / 'asphalt' _save_rgb(folder / 'albedo.png', albedo) _save_rgb(folder / 'normal.png', normal) _save_rgb(folder / 'orm.png', orm) _save_gray(folder / 'emissive.png', emissive)
[docs] def generate_builtin_polygon_pbr_presets(base_dir: Path | None = None) -> Path: """Generate packaged polygon PBR preset textures and return the target directory.""" target = get_pbr_presets_root(base_dir) _make_concrete(target) _make_rusted_metal(target) _make_asphalt(target) return target
[docs] def main() -> None: target = generate_builtin_polygon_pbr_presets() print(f'PBR presets generated in: {target}')
if __name__ == '__main__': main()