singe/util/makeSky.py

38 lines
1.7 KiB
Python

# Writes a procedural equirectangular sky (blue gradient, haze at the horizon, a sun, a plain
# ground) as a PNG for the test scenes; a real HDRI drops in the same way.
# Usage: python3 util/makeSky.py testScripts/sky.png [width]
import sys, math
import numpy as np
from PIL import Image
out = sys.argv[1]
W = int(sys.argv[2]) if len(sys.argv) > 2 else 1024
H = W // 2
v = (np.arange(H) + 0.5) / H
u = (np.arange(W) + 0.5) / W
theta = v * math.pi # 0 at the zenith
phi = (u - 0.5) * 2 * math.pi # 0 faces -Z
T, P = np.meshgrid(theta, phi, indexing='ij')
y = np.cos(T)
x = np.sin(T) * np.sin(P)
z = -np.sin(T) * np.cos(P)
img = np.zeros((H, W, 3))
# Sky: deep blue overhead to pale at the horizon.
t = np.clip(y, 0, 1)
sky = (1 - t)[..., None] * np.array([0.72, 0.80, 0.92]) + t[..., None] * np.array([0.20, 0.42, 0.85])
# Sun toward +X, high up, with a soft glow.
sun = np.array([0.55, 0.65, -0.52]); sun /= np.linalg.norm(sun)
cosang = x * sun[0] + y * sun[1] + z * sun[2]
glow = np.exp((cosang - 1) * 60) * 1.5 + np.exp((cosang - 1) * 700) * 6.0
sky += glow[..., None] * np.array([1.0, 0.95, 0.85])
# Haze just above the horizon.
haze = np.exp(-np.abs(y) * 12) * 0.35
sky += haze[..., None] * np.array([0.9, 0.85, 0.8])
# Ground: a muted olive that darkens straight down.
g = np.clip(-y, 0, 1)
ground = (1 - g)[..., None] * np.array([0.45, 0.42, 0.34]) + g[..., None] * np.array([0.22, 0.20, 0.16])
img = np.where((y >= 0)[..., None], sky, ground)
# To sRGB bytes; the engine decodes PNG skies back to linear.
img = np.clip(img, 0, 1)
srgb = np.where(img <= 0.0031308, img * 12.92, 1.055 * np.power(img, 1 / 2.4) - 0.055)
Image.fromarray((srgb * 255).astype(np.uint8)).save(out)
print(out, W, H)