Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
# raytrace
Generates raytraced PNG images. Supports spheres and triangles because they were the easiest shapes to implement.

## Compiling
To build:
```shell
stack build
```
This will produce an executable appropriate for your system somewhere in `stack-work/dist`.

## Running
```shell
raytrace-exe <config path> <output path>
```
For example:
```shell
raytrace-exe examples/infinity-mirror.yaml ~/Desktop/infinity-mirror.png
```
On my machine, `inifinity-mirror.yaml` takes about 20 seconds to finish running. Reducing the image dimensions is the biggest influence on runtime.
2 changes: 1 addition & 1 deletion app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ main = do
) <- Y.decodeThrow configStr :: IO C.Config
let shapes = map C.toTuple objects
let cameraDirectionNorm = V.normalize cameraDirection
viewportUp = V.normalize $ viewportVertical -*- ((viewportVertical .*. cameraDirection) *** cameraDirectionNorm)
viewportUp = V.normalize $ V.reject viewportVertical cameraDirectionNorm
viewportRight = V.normalize $ cross cameraDirection viewportUp
viewportUpperLeft = cameraPosition +*+ (viewportDistance *** cameraDirectionNorm) +*+ ((viewportHeight / 2) *** viewportUp) -*- ((viewportWidth / 2) *** viewportRight)
let fn imgX imgY =
Expand Down
15 changes: 8 additions & 7 deletions examples/infiinity-mirror.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ world:
material:
tag: phong
ambient: [0.05, 0.05, 0.05]
diffuse: [0.1, 0.1, 0.1]
diffuse: [0.05, 0.05, 0.05]
specular: [0.5, 0.5, 0.5]
shininess: 50
- shape:
Expand All @@ -19,7 +19,7 @@ world:
material:
tag: phong
ambient: [0.05, 0.05, 0.05]
diffuse: [0.1, 0.1, 0.1]
diffuse: [0.05, 0.05, 0.05]
specular: [0.5, 0.5, 0.5]
shininess: 50
- shape:
Expand All @@ -33,17 +33,18 @@ world:
specular: [0.6, 0.6, 0.9]
shininess: 50
lights:
- intensity: [1, 1, 1]
- intensity: [0.3, 0.3, 0.3]
location: [10, 0, 1]
- intensity: [1, 1, 1]
- intensity: [0.3, 0.3, 0.3]
location: [10, 0, -4]
- intensity: [1, 1, 1]
- intensity: [0.3, 0.3, 0.3]
location: [-10, -10, 4]
ambientLight: [0.05, 0.05, 0.05]
raytracer:
reflectionStrategy:
tag: mirror
maxBounces: 50
tag: sphericalburst
n: 1
maxBounces: 2
image:
width: 2500
height: 2500
Expand Down
12 changes: 11 additions & 1 deletion src/Material.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
module Material where

import Linear.V3 (V3 (..))
import qualified Linear.V3 as V3
import qualified Vector as V
import Vector ((.*.), (***), (+*+), (-*-))

import qualified Control.Applicative as A

-- 0 represents minimum brightness, 1 represents maximum.
-- Callers should use clampColor to sanitize values outside those bounds.
data Color = Color {
Expand Down Expand Up @@ -34,13 +37,20 @@ render (Color r g b) = (renderChannel r, renderChannel g, renderChannel b)
type ColorMultiplier = Color

data ReflectionStrategy = Mirror
| Circular {
| SphericalBurst {
n :: Int
} deriving (Eq, Show)

-- accepts an incident ray and normal vector, and returns a list of (reflected ray, multiplier)
reflect :: ReflectionStrategy -> V3 Double -> V3 Double -> [V3 Double]
reflect Mirror incidentRay normalVector = [V.reflect incidentRay normalVector]
reflect (SphericalBurst n) incidentRay normalVector = burstRays
where reflectedRay = V.normalize $ V.reflect incidentRay normalVector
axis = V.reject normalVector reflectedRay
angles = map getAngle [0,1..n]
where getAngle x = 2 * fromIntegral x * pi / (fromIntegral n + 1)
burstRays = filter (\ray -> ray .*. normalVector > 0) $ map getRay $ A.liftA2 (,) angles angles
where getRay (a1, a2) = V.rotate (V.rotate reflectedRay axis a1) reflectedRay a2

data Material = PhongMaterial {
ambient :: ColorMultiplier,
Expand Down
17 changes: 16 additions & 1 deletion src/Vector.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
module Vector where

import Linear.V3 (V3 (..))
import qualified Linear.V3 as V

dot :: V3 Double -> V3 Double -> Double
dot (V3 x1 y1 z1) (V3 x2 y2 z2) = x1 * x2 + y1 * y2 + z1 * z2
Expand Down Expand Up @@ -30,8 +31,9 @@ normalize :: V3 Double -> V3 Double
normalize v = scale (1 / norm v) v

-- incidentRay is pointed _towards_ us, and the returned vector is pointed _away_ from us
-- normalVector must be normalized
reflect :: V3 Double -> V3 Double -> V3 Double
reflect incidentRay normalVector = incidentRay -*- ((2 * (incidentRay .*. normalVector)) *** normalVector)
reflect incidentRay normalVector = incidentRay -*- (2 *** project incidentRay normalVector)

negReflect :: V3 Double -> V3 Double -> V3 Double
negReflect incidentRay = reflect ((-1) *** incidentRay)
Expand All @@ -40,3 +42,16 @@ data Ray = Ray {
origin :: V3 Double,
direction :: V3 Double
}

rotate :: V3 Double -> V3 Double -> Double -> V3 Double
-- Assumes that vec and axis are perpendicular and normalized
-- angle is in radians
rotate vec axis angle = (cos angle *** vec) +*+ (sin angle *** V.cross axis vec)

project :: V3 Double -> V3 Double -> V3 Double
-- Assumes that target is normalized
project vec target = (vec .*. target) *** target

reject :: V3 Double -> V3 Double -> V3 Double
-- Assumes that target is normalized
reject vec target = vec -*- project vec target