AUREOLE-R-v3 / aureole /renderer.py
PureOne's picture
AUREOLE-R 3.0.0-hf.1: standalone public research release
9d6c005 verified
Raw
History Blame Contribute Delete
5.77 kB
"""Small physical shadow-ray oracle with an exact finite-light reference.
Lambertian point receivers on z=0, three opaque sphere occluders, and a 6x6
array of point lights at z=2.2. This is direct illumination only. The scene
does not contain indirect transport, specular receivers, or camera antialiasing.
"""
from dataclasses import dataclass
import numpy as np
@dataclass
class Scene:
seed: int
spheres: np.ndarray # [3,4] = x,y,z,radius, renderer-owned geometry
def __post_init__(self):
self.spheres=np.asarray(self.spheres,dtype=float)
if self.spheres.shape!=(3,4) or not np.isfinite(self.spheres).all() or (self.spheres[:,3]<=0).any():
raise ValueError("Reference scene requires three finite spheres with positive radii")
@classmethod
def create(cls, seed):
rng = np.random.default_rng(seed)
xyz = rng.uniform([-0.72,-0.72,0.45], [0.72,0.72,0.95], size=(3,3))
radius = rng.uniform(0.17,0.36, size=(3,1))
spheres = np.concatenate([xyz, radius], axis=1)
return cls(int(seed), spheres[np.argsort(spheres[:, 0])])
def changed(self):
moved = self.spheres.copy()
moved[0, 0] += 0.65
moved[1, 1] -= 0.6
moved[2, 3] *= 1.25
return Scene(self.seed, moved)
def visibility(self, receivers, lights):
"""Exact segment/sphere visibility for matching broadcastable arrays."""
p, l = np.broadcast_arrays(np.asarray(receivers, float), np.asarray(lights, float))
if p.shape[-1]!=3 or not np.isfinite(p).all() or not np.isfinite(l).all():
raise ValueError("Physical segment endpoints must be finite 3D points")
d = l-p
a = (d*d).sum(-1)
if (a <= 0).any():
raise ValueError("A physical shadow segment must have positive length")
visible = np.ones(a.shape, bool)
for sphere in self.spheres:
oc = p-sphere[:3]
b = (oc*d).sum(-1)
c = (oc*oc).sum(-1)-sphere[3]**2
discriminant = b*b-a*c
root = np.sqrt(np.maximum(discriminant,0))
near, far = (-b-root)/a, (-b+root)/a
intersects = (discriminant >= 0) & (far > 1e-6) & (near < 1-1e-6)
visible &= ~intersects
return visible.astype(np.float64)
def features(self, receivers, lights):
p, l = np.broadcast_arrays(np.asarray(receivers, float), np.asarray(lights, float))
geom = np.broadcast_to(self.spheres.ravel(), p.shape[:-1]+(12,))
return np.concatenate([p[..., :2], l[..., :2], geom], axis=-1).astype(np.float32)
def receiver_grid(height=32, width=64):
x = np.linspace(-1,1,width)
y = np.linspace(-1,1,height)
xx,yy = np.meshgrid(x,y)
return np.stack([xx,yy,np.zeros_like(xx)],axis=-1).reshape(-1,3)
def light_grid(side=6):
x = np.linspace(-0.85,0.85,side)
xx,yy = np.meshgrid(x,x)
return np.stack([xx,yy,np.full_like(xx,2.2)],axis=-1).reshape(-1,3)
def albedo(points, phase=0):
x,y = points[:,0],points[:,1]
checker = ((np.floor((x+1)*10)+np.floor((y+1)*10))%2)
r = 0.22+0.42*checker
g = 0.25+0.25*(0.5+0.5*np.sin(11*x+phase))
b = 0.2+0.38*(0.5+0.5*np.sin(13*y-0.3+phase))
return np.stack([r,g,b],axis=-1)
def lighting(lights, changed=False):
x,y=lights[:,0],lights[:,1]
if changed:
rgb=np.stack([0.3+1.7*(x>0),0.4+0.3*np.cos(3*y)**2,0.5+1.2*(x<0)],axis=-1)
else:
rgb=np.stack([0.8+0.4*np.cos(2*x)**2,0.9+0.2*np.sin(y)**2,0.7+0.2*np.cos(3*y)**2],axis=-1)
return 11.0*rgb/len(lights) # each emitter's radiant intensity
def unoccluded(points, lights, changed_lighting=False, material_phase=0):
d=lights[None,:,:]-points[:,None,:]
distance=np.linalg.norm(d,axis=-1)
cosine=np.maximum(d[...,2]/distance,0)
geometry=cosine/(np.pi*distance**2)
return geometry[...,None]*albedo(points,material_phase)[:,None,:]*lighting(lights,changed_lighting)[None,:,:]
def physical_table(scene, points, lights, bound):
"""Privileged offline reference; do not pass this table to online policy."""
vis=scene.visibility(points[:,None,:],lights[None,:,:])
return bound*vis[...,None]
class VisibilityPrior:
"""Portable NumPy evaluation of the trained 16-48-48-1 MLP."""
def __init__(self,path):
with np.load(path,allow_pickle=False) as data:
self.arrays={k:np.array(data[k]) for k in data.files}
required={"mean","scale","w0","b0","w1","b1","w2","b2"}
if set(self.arrays)!=required or not all(np.isfinite(v).all() for v in self.arrays.values()):
raise ValueError("Malformed prior weights")
shapes={"mean":(16,),"scale":(16,),"w0":(48,16),"b0":(48,),"w1":(48,48),"b1":(48,),"w2":(1,48),"b2":(1,)}
if any(self.arrays[k].shape!=s for k,s in shapes.items()) or (self.arrays["scale"]<=0).any():
raise ValueError("Invalid prior dimensions/scaling")
def __call__(self,features):
features=np.asarray(features,np.float32)
if features.ndim<1 or features.shape[-1]!=16 or not np.isfinite(features).all():
raise ValueError("Visibility prior requires finite 16-dimensional features")
original_shape=features.shape[:-1]
x=np.asarray(features,np.float32).reshape(-1,16)
a=self.arrays
outputs=[]
for start in range(0,len(x),8192):
h=(x[start:start+8192]-a["mean"])/a["scale"]
h=np.maximum(h@a["w0"].T+a["b0"],0)
h=np.maximum(h@a["w1"].T+a["b1"],0)
h=h@a["w2"].T+a["b2"]
outputs.append(1/(1+np.exp(-np.clip(h,-40,40))))
if not outputs:
return np.empty(original_shape,dtype=np.float32)
return np.concatenate(outputs).reshape(original_shape)