9  Naive Light

In this chapter we’re going to remove the background light from the code, and instead, we’re going to add a emmisive part to materials, for implementing naive light source. This chapter is simply a trick, so no theory is needed.

9.1 Design

Our design to this emissive material is trivial - we just add a emissive method to the definition of Material. What we’re doing is split the light behaviour of out material into emissive part and scatter part, and the contribution is the sum of them. However, we’ll make all materials we’ve presented before non-emmisive, i.e., emit no light, and add a new Emmisive material that only emits light, but don’t scatter light. This is for simplicity, though you can still use the same material for both purposes.

9.2 Implementation

First we just delete the environment light in the render_ray function:

pub fn render_ray(
    ctx: &RenderingContext,
    ray: Ray,
    depth: usize,
    max_ray_length: f32,
) -> Option<Color> {
    // ...

    // contribution of the entry ray from the environment
    log::trace!("The ray hit no object.");
    None
}

Note this locaiton is where we later add a real environment light like environment mapping.

Then simply add a new function emit to the Surface trait, with almost same signature to scatter:

#[enum_delegate::register]
pub trait Surface {
    fn scatter(&self, ray_in: Ray, rec: &SurfaceHit) -> Option<SurfaceInteraction>;
    fn emit(&self, ray_in: Ray, rec: &SurfaceHit) -> Color;
}

For those non-emissive material, simply returna black light, i.e., they all implement emit like:

impl Surface for Diffuse {
    // ...

    fn emit(&self, _ray_in: Ray, _rec: &SurfaceHit) -> Color {
        Color::new(0.0, 0.0, 0.0)
    }
}

And for the Emissive material, the scatter method always returns None, and the emit method simply reads the color from texture and multiply it by a factor, called light_strength:

impl Surface for Emissive {
    fn scatter(&self, _ray_in: Ray, _rec: &SurfaceHit) -> Option<SurfaceInteraction> {
        None
    }

    fn emit(&self, _ray_in: Ray, rec: &SurfaceHit) -> Color {
        self.albedo.value(rec.position) * self.light_strength
    }
}

To apply the emit method, we should modify the render_ray function, and instead of just return None if there’s no valid scattered light, we should always calculate the emit component, and add the scatter component to it if presented.

pub fn render_ray(
    ctx: &RenderingContext,
    ray: Ray,
    depth: usize,
    max_ray_length: f32,
) -> Option<Color> {
    // if the ray hits an object
    if let Some(hit) = ctx.aggregate.hit(ray, max_ray_length) {
        // light-surface interaction
        let mut light = hit.material.emit(ray, &hit);
        if let Some(interaction) = hit.material.scatter(ray, &hit) {
            // render the scattered ray
            let max_scatter_length = max_ray_length - hit.ray_length;
            if max_ray_length > 0.0 {
                if let Some(scattered) =
                    render_ray(ctx, interaction.scattered, depth + 1, max_scatter_length)
                {
                    // contribution of the entry ray from the scattered ray
                    log::trace!("Scattered ray rendered.");
                    light += interaction.attenuation * scattered;
                }
            }
        }

        return Some(light);
    }

    // ..
}

Now replace some textures in the scene of last chapter, and you should be able to see the effect:

Naive Light Source

Though fairly noisy, it’s still a simple light source, right? And one major task of later chapters is trying to decrease that noise and make it more physically plausible.