voxrender-cpu 0.1.0

The CPU reference renderer for voxrender's render contract.
docs.rs failed to build voxrender-cpu-0.1.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

voxrender-cpu

The CPU reference renderer for voxrender's render contract. It follows the contract literally and makes the images every other renderer is tested against. A consumer reaches it through voxrender's Render trait.

The walk

RenderRayWalk walks one grid ray per placement with an integer DDA and yields a RenderHit at each surface it crosses, nearest first. RenderRayWalk::from_ray quantizes a world ray into every placement's grid. The surface is the boundary between materials. In each placement the ray remembers the material of the cell it is inside. Entering a live cell of another material is an entry. Leaving a material for an empty cell, the outside of the grid, or a material that is not opaque is an exit. An exit comes before the entry at the same face. A slab of one material shows its near and far faces. A ray that starts inside a cell leaves that cell's material without an exit. cast_ray returns the walk's first hit within a distance. A hit carries:

  1. The placement
  2. The cell
  3. The face the ray crossed, as a unit SurfaceSpan
  4. Where on the face the ray landed, as fractions and as a fixed point
  5. The distance
  6. Whether the ray left the cell through the face

A shadow ray is the same walk from a fixed point on the face toward a light. shadow_rays builds one per placement, and RenderGridRay::toward ends it at the light.

let ray = RenderViewRays::new(&view, 1024, 1024).ray(512, 512);

for hit in RenderRayWalk::from_ray(&scene, &ray)? {
    let object_id = scene.placement(hit.placement_id)?.object_id;
    let occlusion = corner_occlusion(scene.object(object_id)?, &hit.face);
}

Rendering

render draws a view of the scene into a linear image under every light of the scene. Each hit shades with glTF's metallic-roughness model: Lambert diffuse, GGX specular with Smith visibility and Schlick Fresnel, and the emissive term. The light kinds reach a hit differently:

  1. A directional light shines down its -Z
  2. A point light falls off by the inverse square, with glTF's smooth cutoff at its range
  3. A spot light falls off as a point light does, times glTF's cone falloff between its inner and outer angles
  4. A hemisphere light mixes sky and ground by the +Y of the normal, or of the view's mirror direction for the transmitted share

Under RenderOcclusion::Corner, the corner occlusion darkens the hemisphere light. It counts only the cells whose material passes no light, so glass darkens nothing it encloses. An exit shades with its normal turned back into the material it leaves. inner_corner_occlusion reads the exit's occlusion on that side. A shadow is one grid ray toward the light. It starts with the pass of the material it starts inside and transmits by the pass of each surface it meets. A red pane throws a red shadow. RenderShadow casts it per pixel, per face, or per corner, and blends the corner results across the face per channel.

render takes its Dependencies first, which map the image's rows to their shades. DependenciesImpl, behind the default impl feature, spreads the rows across threads.

A RenderBloom adds a halo over the emissive term before the tonemap. The part of each hit's emission whose brightest channel passes the threshold blurs out to the radius, scales by the strength, and lands on every pixel. The default strength of 0 skips the pass.

A pixel walks its ray front to back. Each hit adds its shade at its base color's alpha and passes the rest through by the material's pass: the share of the light behind the surface that transmission lets through. The pass loses Schlick's Fresnel on the ray's cosine to the face. Glass passes less and reflects more of the hemisphere toward grazing. Glass shows what lies behind it and tints at each wall. Touching voxels of one glass read as one slab.

let image = render(
    &DependenciesImpl,
    &scene,
    view_id,
    RenderOcclusion::Corner,
    RenderBloom::default(),
    1024,
    1024,
)?;