This is another simple diff compared to the last experiment. All I’m adding is the ability to create mirrored surfaces.
Mirrored surfaces require the ability to recursively render the scene from different viewpoints.
To capture this ability, I add a new type that represents a function that can render a scene, given
Ray, and produce a
type RenderRay = Ray -> Light
Material type also changes to take this function as a parameter:
type Material = [PointLightSource] -> Ray -> Point -> UnitVector -> RenderRay -> Light
The various functions that already exist within the
Material module change accordingly to pass
this parameter around, or to ignore it.
Since many of the reflective surfaces I want to represent won’t be fully reflective, I need a way
of scaling different materials. For this, I added the
scaleMaterial :: Material -> Double -> [PointLightSource] -> Ray -> Point -> UnitVector -> RenderRay -> Light scaleMaterial material factor lights ray ixp surfaceNormal renderRay = material lights ray ixp surfaceNormal renderRay `scaled` factor
(Although thinking about it, I haven’t been very consistent here. All the core material functions take a scaling factor as a parameter, but the
addMaterialsfunction doesn’t. I should probably have removed the scaling factor from all of them, and left the scaling to the above function.)
With that refactoring in place, I can now write the actual
reflectiveMaterial function. It
calculates the reflected ray from the surface, and then uses the recursive function supplied to
calculate the light being reflected:
reflectiveMaterial :: Double -> [PointLightSource] -> Ray -> Point -> UnitVector -> RenderRay -> Light reflectiveMaterial !factor _ (Ray _ !rd) intersectionPosition surfaceNormal renderRay = reflectLight `scaled` factor where c1 = - (surfaceNormal |.| rd) r1 = rd |+| (surfaceNormal |*| (2 * c1)) reflectRay = Ray intersectionPosition (normalize r1) reflectLight = renderRay reflectRay
Of course, with any recursive algorithm, I have to make sure that the recursion terminates after
a set number of iterations. To support this, I modify the
renderRay :: Ray -> Scene -> Color renderRay ray scene = toColor $ renderRayRecursive scene 4 ray
4 is the maximum number of times light is allowed to bounce within the scene. In
normal situations, this is perfectly acceptable – it’s unlikely to show artifacts unless the
scene contains mirrors that face each other.
renderRayRecursive is defined as follows:
renderRayRecursive :: Scene -> Int -> Ray -> Light renderRayRecursive scene level ray | level <= 0 = black | otherwise = fromMaybe black maybeColor where maybeColor = do (Intersection rt (Surface _ nrm mat) _ wp) <- sceneIntersection scene ray return $ mat (pointLightSources scene) rt wp (nrm wp) $ renderRayRecursive scene (level - 1)
If the limit is exceeded, simply return no light, otherwise calculate the intersection and render as usual. Notice that the recursive render function is pased to the material, but with a reflection counter one less than before.
The end result is an image that looks as follows:
Code is in Github, if you want to take a look.
Published: Saturday, November 28, 2015
Hackification.io is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to amazon.com. I may earn a small commission for my endorsement, recommendation, testimonial, and/or link to any products or services from this website.