IV. Les ombres▲
Pour commencer, nous déplaçons les fonctions de calcul d'intersection pour qu'elles puissent être importées dans le module Lights.
module
Intersections (
computeIntersection,
computeIntersections,
) where
import
Types
import
Tools
import
Objects
--Calcule les intersections du rayon avec un objet.
computeIntersection ::
Ray ->
Object ->
Scene ->
[Intersection]
computeIntersection ray object scene =
fmap localIntersectionToGlobal intersections
where
intersections =
intersect ray >>=
mapM (distanceToIntersection object ray) $
scene'
Object objectLocation _
intersect _
=
object
Scene camera objects lights =
scene
Camera cameraLocation distance planSize exposition =
camera
camera'
=
Camera (cameraLocation -!
objectLocation) distance planSize exposition
scene'
=
Scene camera'
objects lights
--Applique le calcul d'intersections sur chacun des objets
computeIntersections ::
Ray ->
Scene ->
[Intersection]
computeIntersections ray scene =
concat (contextualIntersections scene)
where
Scene _
objects _
=
scene
contextualIntersections =
sequence $
map (computeIntersection ray) objects
Commençons avec la lumière correctionnelle. Tout objet sur la trajectoire obtenue en remontant le rayon de lumière crée une ombre. Il faut donc détecter si un objet se trouve dans cette direction. Si oui, on ne doit produire aucune lumière. On va donc lancer un rayon depuis l'intersection.
On commence par exporter une constante, signifiant que toute distance inférieure à celle-ci est considérée comme égale à 0. Cela va nous permettre d'éviter de ré-intersecter la position depuis laquelle on lance notre rayon.
epsilon ::
RealRep
epsilon =
1e-
12
Maintenant, on veut pouvoir relancer un rayon depuis une intersection. On ajoute donc une fonction spécifique dans le module Intersections. On constate ici que cela consiste simplement à modifier la position de la caméra, et de filtrer les intersections obtenues.
--Recherche les intersections depuis la position location vers la direction ray
lookFrom ::
Location ->
Ray ->
Scene ->
[Intersection]
lookFrom location ray scene =
filter (\(Intersection _
_
distance _
_
) ->
distance >
epsilon ) $
computeIntersections ray scene'
where
Scene camera objects light =
scene
Camera cameraLocation cameraDistance cameraPlanSize cameraExposition =
camera
camera'
=
Camera location cameraDistance cameraPlanSize cameraExposition
scene'
=
Scene camera'
objects light
On modifie maintenant le calcul de lumière en rajoutant une instruction conditionnelle. Si la liste obtenue par lookFrom en regardant dans la direction de la source lumineuse est vide, on peut calculer la lumière, sinon on renvoie (0, 0, 0).
buildDirectionalLight lightDirection lightColor =
Light lightFunction
where
unaryLightVector =
normalise $
(-
1
) *!
lightDirection
lightFunction intersection scene =
if
hidden then
(0
, 0
, 0
) else
computedLight
where
hidden =
not . null $
lookFrom intersectionLocation unaryLightVector scene
computedLight =
(diffuseLightFactor +
specularLightFactor) *!
color
-- ...
Au tour maintenant de l'ombre du spot. Pour celle-ci, on ne doit pas compter les objets derrière le spot. On filtre donc pour exclure les intersections trop éloignées. Puisqu'on fera exactement la même chose pour la lumière omnidirectionnelle, on ajoute une nouvelle fonction.
isHidden ::
Distance ->
Location ->
UnaryLightVector ->
Scene ->
Bool
isHidden lightDistance location unaryLightVector scene =
not . null $
filteredIntersections
where
intersections =
lookFrom location unaryLightVector scene
filteredIntersections =
filter (\ (Intersection _
_
distance _
_
) ->
distance <
lightDistance) intersections
buildSpotLight ::
LightDirection ->
RadianAngle ->
Attenuation ->
Location ->
Color ->
Light
buildSpotLight lightDirection coneAngle attenuation lightLocation lightColor =
Light lightFunction
where
lightDistance =
norm lightDirection
unaryLightDirection =
normalise $
(-
1
) *!
lightDirection
lightFunction intersection scene =
if
hidden then
(0
, 0
, 0
) else
computedLight
where
hidden =
isHidden lightDistance intersectionLocation unaryLightVector scene
computedLight =
factor *!
color
Pour ce qui est de la source omnidirectionnelle, c'est identique au précédent. Il nous faut juste obtenir le vecteur pointant vers la source et la distance de la source à l'intersection.
buildOmnidirectionalLight ::
Attenuation ->
Location ->
Color ->
Light
buildOmnidirectionalLight attenuation lightLocation lightColor =
Light lightFunction
where
lightFunction intersection scene =
if
hidden then
(0
, 0
, 0
) else
computedLight
where
lightDistance =
norm lightDirection
hidden =
isHidden lightDistance intersectionLocation unaryLightVector scene
computedLight =
factor *!
color
lightDirection =
lightLocation -!
intersectionLocation
unaryLightVector =
normalise lightDirection
-- ...
Pour tester les ombres, j'ai rajouté quelques objets à notre scène précédente :
objects =
[buildSphere 1
.0
(0
, 1
.2
, -
7
) (Material (1
.0
, 1
.0
, 1
.0
) 1
2
6
),
buildSphere 1
.0
(0
, -
1
.2
, -
7
) (Material (1
.0
, 0
.0
, 1
.0
) 1
2
50
),
buildSphere 1
.0
(1
.2
, 0
, -
7
) (Material (0
.0
, 1
.0
, 1
.0
) 1
2
12
),
buildSphere 1
.0
(-
1
.5
, 0
, -
7
) (Material (0
.0
, 0
.0
, 1
.0
) 1
1
12
),
buildSphere 0
.5
(0
, 2
, -
6
.6
) (Material (1
.0
, 1
.0
, 0
.0
) 1
1
12
),
buildSphere 1
.0
(-
2
, 2
, -
4
) (Material (1
.0
, 0
.0
, 0
.0
) 1
1
12
),
buildSphere 0
.1
(-
0
.5
, 0
.7
, -
4
.5
) (Material (1
.0
, 0
.0
, 0
.0
) 1
1
12
)]