{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-} {-# OPTIONS_GHC -Wall #-} ---------------------------------------------------------------------- -- | -- Module : Shady.Lighting -- Copyright : (c) Conal Elliott 2009 -- License : AGPLv3 -- -- Maintainer : conal@conal.net -- Stability : experimental -- -- Lighting/shading. Adapted from Vertigo. ---------------------------------------------------------------------- module Shady.Lighting ( LightInfo(..), Light, View(..), SurfInfo(..) , Lighter, LLighter , ambient, eye, lights , surfP, surfN, intrinsic , colorL, dirL, illuminance , diffuse, ambDiff, eyeDir, eyeLight, reflection , specularG, specularRV, specularNH , BasicSh, basic, basicRV, basicNH , Liftable(..) , Dir3E, dirLight, pointLight , stdViewPos, stdView, view1 , ma,md,ms,msh , basicStd ) where import Data.VectorSpace (AdditiveGroup(..),(*^), sumV,(<.>),normalized) import Data.Boolean import Shady.Language.Exp import Shady.Color {-------------------------------------------------------------------- Basic types --------------------------------------------------------------------} -- | Info about how one light affects a given point. The surface lighter -- decides what to do with the light info. Attenuation and relation of -- light position (if finitely distant) to surface position are already -- accounted for. 'liDir' is the direction /to/ the light (normalized). data LightInfo = LI { liColor :: Color, liDir :: Dir3E } -- | A light is something that provides light info to every point in space -- (though to some points it provides blackness), independent of -- obstructions. Should probably also take an atmosphere argument. type Light = R3E -> LightInfo -- | Viewing environment: ambient, eye, lights data View = View { viewAmbient :: Color , viewEye :: R3E , viewLights :: [Light] } -- | Info about a surface at a point: position, normal, color data SurfInfo = SurfInfo { surfPos :: R3E, surfNormal :: Dir3E, surfColor :: Color } -- Lighters are functions from contextual info to values. -- | View-dependent lighter type Lighter a = View -> SurfInfo -> a -- | Light- and view-dependent lighter type LLighter a = LightInfo -> Lighter a {-------------------------------------------------------------------- Extractors --------------------------------------------------------------------} -- | Ambient color ambient :: Lighter Color ambient = lift . viewAmbient -- ambient v _ = viewAmbient v -- ambient v = const (viewAmbient v) -- | Eye point eye :: Lighter R3E eye = lift . viewEye -- | Lights lights :: Lighter [Light] lights = lift . viewLights -- | Surface point surfP :: Lighter R3E surfP = lift surfPos -- | Surface normal surfN :: Lighter Dir3E surfN = lift surfNormal -- | Surface Color intrinsic :: Lighter Color intrinsic = lift surfColor -- | Light color colorL :: LLighter Color colorL = lift . liColor -- | Direction /to/ light dirL :: LLighter R3E dirL = lift . liDir -- | Combine contributions from multiple lights. Patterned after -- Renderman's @illuminance@ construct. illuminance :: -- (Num a, VectorSpace IfB (VecE OneT Bool) (Scalar a), Num (Scalar a)) => (AdditiveGroup a, IfB BoolE a) => LLighter a -> Lighter a illuminance llighter v@(View _ _ ls) s@(SurfInfo p _ _) = -- sumV [ (ifB (lift surfN <.> dirL >* 0) llighter zeroV) (light p) v s | light <- ls ] sumV [ llighter (light p) v s | light <- ls ] -- bsign b = boolean 1 0 b -- | Does this bsign multiplier really help? The vertex engine clamps to -- [0,1] anyway. Oh, with multiple light sources, negative contributions -- would subtract from positive ones. {-------------------------------------------------------------------- Composite lighters --------------------------------------------------------------------} -- Two-sided lighting? twoSided :: Bool twoSided = True -- One-sided or two-sided lighting sided :: (Ord a, Num a) => a -> a sided | twoSided = abs | otherwise = max 0 -- | Pure diffuse diffuse :: Lighter Color diffuse = illuminance (sided (lift surfN <.> dirL) *^ colorL) -- | Weighted combination of ambient and diffuse ambDiff :: (Color, Color) -> Lighter Color ambDiff (ka,kd) = intrinsic * (lift ka * ambient + lift kd * diffuse) -- | The Stanford rtsl version, with ambient and weights: -- -- surface float4 -- lightmodel_diffuse (float4 ka, float4 kd) -- { -- perlight float diffuse = dot(N,L); -- perlight float4 fr = kd * select(diffuse > 0, diffuse, 0); -- return ka * Ca + integrate(fr * Cl); -- } -- | Direction from surface point to eye eyeDir :: Lighter Dir3E eyeDir = normalized (eye - surfP) -- | Eye/light vector average (CGPP p 731) eyeLight :: LLighter Dir3E eyeLight = normalized (dirL + lift eyeDir) -- | Reflection vector (CGPP p 730) reflection :: LLighter Dir3E reflection = (2 * (n' <.> dirL)) *^ n' - dirL where n' = lift surfN -- | Pure specular. Ignores intrinsic surface color. There are different -- ways to compute the power base. specularG :: LLighter FloatE -> FloatE -> Lighter Color specularG base sh = illuminance ((max 0 base ** lift sh) *^ colorL) -- Phong's specular (R.V) model specularRV :: FloatE -> Lighter Color specularRV = specularG (reflection <.> lift eyeDir) -- | Or the N.H model: specularNH :: FloatE -> Lighter Color specularNH = specularG (lift surfN <.> eyeLight) -- | surface floatv -- lightmodel_specular (floatv s, floatv e, float sh) -- { -- perlight float diffuse = dot(N,DIRL); -- perlight float specular = pow(max(dot(N,H),0),sh); -- perlight floatv fr = select(diffuse > 0, s * specular, Zero); -- return integrate(fr * Cl) + e; -- } type BasicSh = (Color,Color,Color,FloatE) -> Lighter Color -- | Combine intrinsic, ambient, diffuse and specular, with weightings basic :: LLighter FloatE -> BasicSh basic base (ka,kd,ks,sh) = ambDiff (ka,kd) + lift ks * specularG base sh basicRV, basicNH :: BasicSh basicRV = basic (reflection <.> lift eyeDir) -- The R.V model basicNH = basic (lift surfN <.> eyeLight) -- The N.H model -- | surface float4 -- lightmodel (float4 a, float4 d, float4 s, float4 e, float sh) -- { -- perlight float diffuse = dot(N,DIRL); -- perlight float specular = pow(max(dot(N,H),0),sh); -- perlight float4 fr = d * max(diffuse, 0) + -- s * select(diffuse > 0, specular, 0); -- return a * Ca + integrate(fr * Cl) + e; -- } {- -- Phong model, taking normal as parameter -- WORKING HERE. Coefficients not yet right. phongN :: LLighter DirE -> Lighter Color phongN n = cs * lift ka * ambient + cs * lift kd ^* illuminance ldotN + lift ks ^* illuminance (vdotR ** lift sh) -} {-------------------------------------------------------------------- Lifting. Revisit --------------------------------------------------------------------} class Liftable k f where lift :: k -> f instance Liftable a a where lift = id instance Liftable b (a->b) where lift = const instance Liftable c (a->b->c) where lift = const . const instance Liftable d (a->b->c->d) where lift = const . const . const {-------------------------------------------------------------------- Some lights --------------------------------------------------------------------} -- | Direction. Assumed normalized. type Dir3E = R3E -- | Directional light, given the direction /from/ the light (opposite 'dirL') dirLight :: Color -> Dir3E -> Light dirLight col dir = const (LI col (- dir)) -- | Point light pointLight :: Color -> R3E -> Light pointLight col lpos p = LI col (normalized (lpos - p)) -- To do: add distance-based fall-off and spot lights stdViewPos :: R3E stdViewPos = vec3 0.5 1 (-2) :: R3E -- | View with white ambient light and given eye position and lights stdView :: R3E -> [Light] -> View stdView = View white -- | View with white ambient light, one directional light, and given eye position. -- For now, light position is like eye position but more so. view1 :: R3E -> View view1 eyePos = stdView eyePos [dirLight lightColor lightDir] where lightColor = white -- lightColor = rgb 1 0.9 0.5 -- light gold lightDir = normalized (-eyePos + vec3 2 1 0) -- view2 :: Anim R3E -> Anim View -- view2 pos t = stdView [pointLight red (pos t)] -- Standard material properties (from rtsl-shaders.in) ma,md,ms :: Color msh :: FloatE ma = gray 0.2 -- ambient md = gray 0.4 -- diffuse ms = gray 0.5 -- specular msh = 15 -- specular exponent {- -- Simple shaders with standard material properties ambDiffStd = ambDiff ma md specularStd = specularNH msh -} basicStd :: Lighter Color basicStd = basicRV (ma,md,ms,msh)