Source code for arlunio.raytrace.render

from __future__ import annotations

import logging

import numpy as np

import arlunio as ar
import arlunio.image as image
import arlunio.math as math
from .camera import SimpleCamera
from .data import Rays
from .data import ScatterPoint
from .material import Gradient
from .material import LambertianDiffuse


[docs]@ar.definition def ZDepthRenderer( width: int, height: int, *, camera=None, objects=None ) -> image.Image: """A renderer that returns a z-depth pass.""" camera = SimpleCamera() if camera is None else camera objects = [] if objects is None else objects rays = camera(width=width, height=height) scatter = ScatterPoint.new(rays) for obj in objects: test = obj(rays=rays, t_min=0, t_max=scatter.t) scatter.merge(test) depth = np.abs(scatter.p[:, 2] - camera.origin[2]) depth[scatter.t == ScatterPoint.MAX_FLOAT] = np.max(depth) vs = np.array((depth / np.max(depth)) * 255, dtype=np.uint8) vs = vs.reshape(height, width) return image.fromarray(vs, "L")
[docs]@ar.definition def ClayRenderer( width: int, height: int, *, background=None, color="lightgrey", camera=None, objects=None, bounces=8, ) -> image.Image: """A simple clay renderer.""" background = Gradient() if background is None else background camera = SimpleCamera() if camera is None else camera objects = [] if objects is None else objects rays = camera(width=width, height=height) scatter = ScatterPoint.new(rays) mat = LambertianDiffuse(color=color) color = background(scatter=scatter) depth = 1 mask = np.full((scatter.hit.shape), True) while depth < bounces: for obj in objects: test = obj(rays=rays, t_min=0.001, t_max=scatter.t) scatter.merge(test) # If there were no hits, then there's nothing to do if not np.any(scatter.hit): break # Update the mask to reflect where we have intersections. mask[mask] = scatter.hit matcol, rays = mat(scatter=scatter[scatter.hit]) color[mask] *= matcol # Increasee the bounce counter and run again depth += 1 scatter = ScatterPoint.new(rays) return color
[docs]@ar.definition def MaterialRenderer( width: int, height: int, *, objects=None, bounces=8, camera=None, background=None ): """A renderer capable of rendering materials.""" logger = logging.getLogger(__name__) background = Gradient() if background is None else background camera = SimpleCamera() if camera is None else camera objects = [] if objects is None else objects rays = camera(width=width, height=height) scatter = ScatterPoint.new(rays) color = background(scatter=scatter) depth = 1 mask = np.full((scatter.hit.shape), True) materials = [mat for _, mat in objects] logger.info("Starting render.") while depth < bounces: matmap = np.full((scatter.hit.shape), -1) logger.debug(" Depth: %i ".center(80, "-"), depth) logger.debug("Mask: %s", mask.shape) logger.debug("Material Map: %s", matmap.shape) logger.debug("%s", scatter) for idx, (obj, _) in enumerate(objects): logger.debug(" Obj: %i ".center(80, "-"), idx) test = obj(rays=rays, t_min=0.001, t_max=scatter.t) # Record which material should be used for these intersections. matmap[test.hit] = idx scatter.merge(test) if not np.any(scatter.hit): logger.debug("Nothing hit, stopping") break matids = np.unique(matmap[matmap != -1]) logger.debug("-" * 80) logger.debug("Mat Ids: %s", matids) logger.debug("#hits: %s", scatter.hit[scatter.hit == True].shape) mask[mask] = scatter.hit origin = scatter.p[scatter.hit] directions = scatter.normal[scatter.hit] for idx in matids: logger.debug(" Mat: %i ".center(80, "-"), idx) mat = materials[idx] # Only select the intersections that correspond with this material mat_mask = matmap[scatter.hit] == idx scatter_mask = np.array(scatter.hit) scatter_mask[scatter.hit] = mat_mask color_mask = np.array(mask) color_mask[color_mask] = scatter_mask[scatter.hit] logger.debug("Mat Mask: %s", mat_mask.shape) logger.debug("Scatter Mask: %s", scatter_mask.shape) logger.debug("Color Mask: %s", color_mask.shape) mat_scatter = scatter[scatter_mask] logger.debug("Mat Scatter: %s", mat_scatter) matcol, matrays = mat(scatter=mat_scatter) color[color_mask] *= matcol origin[mat_mask] = matrays.origin directions[mat_mask] = matrays.direction depth += 1 rays = Rays(origin, directions) scatter = ScatterPoint.new(rays) return color
[docs]@ar.definition def SampledRenderer(width: int, height: int, *, kernel=None, samples=10) -> image.Image: """A renderer is responsible for orchestrating the entire process.""" if kernel is None: raise ValueError("Missing renderer kernel") s = samples n = width * height scale = 1.0 / s cols = np.zeros((n, 3)) for _ in range(s): cols += kernel(width=width, height=height) * scale # Gamma correction... cols = np.sqrt(cols) r = math.clamp(cols[:, 0], 0, 0.99999) * 256 g = math.clamp(cols[:, 1], 0, 0.99999) * 256 b = math.clamp(cols[:, 2], 0, 0.99999) * 256 color = np.array(np.dstack([r, g, b])[0], dtype=np.uint8) color = color.reshape(height, width, 3) return image.fromarray(color, "RGB")