| """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 |
|
|
| 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) |
|
|
|
|
| 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) |
|
|