This is a really tiny diff compared to the last experiment - I’m just going to add a new material type of ‘specular’.
I factored out a bit of common code (lightDistanceAndRay
), and added specularMaterial
:
diffuseMaterial :: Color -> Double -> [PointLightSource] -> Ray -> Point -> UnitVector -> Light
diffuseMaterial !col !factor !lights _ intersectionPosition surfaceNormal =
sumLights $ map diffuseLight lights
where
diffuseLight (PointLightSource !lightPosition !lightColor)
| diffuseFactor > 0 = lightColor `colored` col `scaled` diffuseFactor
| otherwise = black
where
(lightDistance, lightRay) = lightDistanceAndRay intersectionPosition lightPosition
lightAttenuation = 1.0 / lightDistance
diffuseFactor = factor * (surfaceNormal |.| lightRay) * lightAttenuation
specularMaterial :: Double -> Double -> [PointLightSource] -> Ray -> Point -> UnitVector -> Light
specularMaterial !factor !shininess !lights (Ray _ !rd) intersectionPosition surfaceNormal =
sumLights $ map specularLight lights
where
specularLight (PointLightSource !lightPosition !lightColor)
| cosFactor > 0 = lightColor `scaled` factor `scaled` (cosFactor ** shininess) `scaled` lightAttenuation
| otherwise = black
where
(lightDistance, lightRay) = lightDistanceAndRay intersectionPosition lightPosition
lightReflect = surfaceNormal |*| ((surfaceNormal |*| 2) |.| lightRay) |-| lightRay
cosFactor = lightReflect |.| neg rd
lightAttenuation = 1.0 / lightDistance
lightDistanceAndRay :: Point -> Point -> (Double, UnitVector)
lightDistanceAndRay intersectionPosition lightPosition
= (lightDistance, lightRay)
where
lightVector = intersectionPosition `to` lightPosition
lightDistance = magnitude lightVector
lightRay = normalize lightVector
Notice that in diffuseMaterial
, we ignore the Ray
parameter (the ray from camera to surface),
but in specularMaterial
, it’s part of the lighting calculation. Hopefully this makes sense -
a matt material looks roughly the same no matter where you view it from, but a shiny surface will
have different brightness depending on the viewing angle. (If you happen to catch a reflection of a
light, it will appear bright at that point, but if not, the surface at that point will be dim).
Rather than have material variants for only diffuse, only specular, and part-diffuse-part-specular surfaces, I’ve added a method that lets me combine different surface material kinds:
addMaterials :: [Material] -> [PointLightSource] -> Ray -> Point -> UnitVector -> Light
addMaterials materials lights ray ixp surfaceNormal =
sumLights $ map (\m -> m lights ray ixp surfaceNormal) materials
So with all that in place, the final image looks as follows:
Code is in Github, if you want to take a look.
Published: Monday, July 20, 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.