lighting.py

At 8d263df

8d263dfHistory

Preview

Code · 1.6 KiB
"""Compact analytical colored-light fields calibrated offline.

The optional JSON holds Gaussian lobe amplitudes, not an image or pixel texture.
Every lobe has a fixed geometric position, scale, and contour-distance profile.
"""
from pathlib import Path
import json
import numpy as np

PARAMETERS = Path(__file__).with_name('lighting.json')
SPECS = {'outer': (54., 1141., 399.), 'middle': (54., 953., 338.), 'front': (54., 697., 258.)}
_CACHE = None

def fields(name, x, y, distance):
    top,bottom,halfwidth = SPECS[name]
    u=(x-490.)/halfwidth
    v=(y-top)/(bottom-top)
    for cy in np.linspace(0,1,10):
        gy=np.exp(-.5*((v-cy)/.135)**2)
        for cx in np.linspace(-1,1,5):
            yield gy*np.exp(-.5*((u-cx)/.37)**2)
    for center,sigma in [(1.5,1.5),(6.,4.),(17.,9.),(38.,18.)]:
        gd=np.exp(-.5*((distance-center)/sigma)**2)
        for cy,sy in [(cy,.075) for cy in np.linspace(0,1,12)]+[(.015,.016),(.04,.023),(.075,.035)]:
            gy=np.exp(-.5*((v-cy)/sy)**2)
            for side in [-1,1]:
                yield gd*gy*(1/(1+np.exp(-np.clip(side*u*12,-60,60))))

def apply(name, colors, x, y, distance):
    global _CACHE
    if _CACHE is None:
        _CACHE=json.loads(PARAMETERS.read_text()) if PARAMETERS.exists() else {}
    if name not in _CACHE:
        return colors
    correction=np.zeros_like(colors,dtype=np.float32)
    for field,coefficient in zip(fields(name,x,y,distance), _CACHE[name]):
        correction+=field[...,None]*np.asarray(coefficient,dtype=np.float32)
    # Fit is deliberately smooth; clamp only when the completed render is exported.
    return colors+correction

Files