' '

Difficulty

2

Reading material

samplers/basics

samplers/random

samplers/testing

preview
var camera = Cameras.perspective( [ "eye": pos(5, 0.5, 5),
                                    "look_at": pos(0, 0.5, 0),
                                    "up": vec(0, 1, 0),
                                    "distance": 1,
                                    "aspect_ratio": 1 ] )

var white_material = Materials.uniform( [ "ambient": Colors.white(),
                                          "diffuse": Colors.black(),
                                          "specular": Colors.black(),
                                          "specular_exponent": 0 ] )

var black_material = Materials.uniform( [ "ambient": Colors.black(),
                                          "diffuse": Colors.black(),
                                          "specular": Colors.black(),
                                          "specular_exponent": 0 ] )

var checkered_material = Materials.from_pattern(Patterns.checkered(1, 1), white_material, black_material)

var root        = decorate(checkered_material, xz_plane())

var lights      = [ Lights.omnidirectional( pos(0, 10, 0), Colors.white() ) ]

var scene = create_scene(camera, root, lights)


var raytracer   = Raytracers.v6()
var renderer    = Renderers.standard( [ "width": 500,
                                        "height": 500,
                                        "sampler": Samplers.random(8),
                                        "ray_tracer": raytracer ] )

pipeline( scene,
          [ Pipeline.renderer(renderer),
            Pipeline.studio() ] )

1. Goal

The purpose of samplers is explained on the page discussing ray tracing concepts. In short, we have a pixel for which we need to determine its color. We have to cast a ray through it which we’ll trace around the scene. But since the pixel is not a single point but is rectangular, there are infinitely many points through which we could cast the ray. A sampler’s job is to pick points within the pixel. The number of points and which points these are is up to the sampler. The only guarantee the sampler has to give is that when given a 2D rectangle, it will produce points that are within this rectangle. Go take a look at samplers/sampler.h. The (first) sample method reflects this purpose: it takes a rectangle and returns a list of points. You can ignore the second sample overload for now.

The random sampler can be parameterized with an integer n. When asked to sample a rectangle, it will return n random points in this rectangle.

Create new files samplers/random-sampler.h and samplers/random-sampler.cpp. Define a new sampler class. Let the existing single-sampler guide you.

class RandomSampler(SamplerImplementation):
  # Constructor initializes fields
  def __init__(self, sample_count):
    self.__sample_count = sample_count
    # Create RNG once in constructor
    self.__random = Random()

  def sample(rectangle, callback):
    for _ in range(self.__sample_count):
      x = self.__random() # double between 0 and 1
      y = self.__random() # double between 0 and 1
      sample = rectangle.from_relative(x, y)

      callback(sample)


# Factory function takes a single parameter
def random_sampler(sample_count):
  return RandomSampler(sample_count)
Tip

Use Rectangle2D::from_relative to easily find points inside the rectangle.

Tip

The sample method is const. However, making use of the RNG changes its state. In other words, your const method sample tries to change the state of one of the object’s members, which is forbidden. Luckily it’s possible to circumvent the constness safely. Do not dare use const_cast though, as this would lead to undefined behavior.

confession

sample might actually be better off being a nonconst method, but changing this at the time of writing would break students' code.

Expose the sampler to the scripting language by updating scripting/samplers-module.cpp.

2. Evaluation

Render the following scene:

def scene_at(now)
{
    var camera = Cameras.perspective( [ "eye": pos(0,0,5),
                                        "look_at": pos(0,0,0) ] )

    var root = sphere()

    var lights = [ ]

    create_scene(camera, root, lights)
}

var renderer = Renderers.standard( [ "width": 100,
                                     "height": 100,
                                     "sampler": Samplers.random(1),
                                     "ray_tracer": Raytracers.v0() ] )

pipeline( scene_animation(scene_at, seconds(1)),
          [ Pipeline.animation(30),
            Pipeline.renderer(renderer),
            Pipeline.wif(),
            Pipeline.base64(),
            Pipeline.stdout() ] )

Explain why there is such noise on the circumference of the circle.

Render the following scene:

def scene_at(now)
{
    var camera = Cameras.perspective( [ "eye": pos(0,0,5),
                                        "look_at": pos(0,0,0) ] )

    var root = sphere()

    var lights = [ ]

    create_scene(camera, root, lights)
}

var renderer = Renderers.standard( [ "width": 100,
                                     "height": 100,
                                     "sampler": Samplers.random(10),
                                     "ray_tracer": Raytracers.v0() ] )

pipeline( scene_animation(scene_at, seconds(1)),
          [ Pipeline.animation(30),
            Pipeline.renderer(renderer),
            Pipeline.wif(),
            Pipeline.base64(),
            Pipeline.stdout() ] )

Explain the change in noise. What would you do to get rid of the noise altogether?