Skip to content

Commit

Permalink
Merge pull request #14 from quephird/explore_anti_aliasing
Browse files Browse the repository at this point in the history
Introduce antialiasing and make it optional
  • Loading branch information
quephird committed Oct 16, 2022
2 parents fcceec4 + a0c3cf9 commit 4f80aee
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 17 deletions.
59 changes: 52 additions & 7 deletions README.md
@@ -1,6 +1,6 @@
# Purpose

This is a library that is intended to be used to generate ray traced scenes. I had originally implemented this in Clojure but I wanted to learn more about Swift by reimplementing it in that language. It's a library instead of an application like POV-Ray, but there is a component in it that allows you to easily create an object scene by expressing it in a Swift DSL, and then render it to a file. Moreover, since you can use Xcode to type in a scene, you can take advantage of its own features like type-checking and fixits, which were not possible using Clojure. As with the Clojure implementation, this one is based on the tests provided by the amazing book, The Ray Tracer Challenge by Jamis Buck.
This is a library that is intended to be used to generate ray traced scenes. I had originally implemented this in Clojure but I wanted to learn more about Swift by reimplementing it in that language. It's a library instead of an application like POV-Ray, but there is a component in it that allows you to easily create an object scene by expressing it in a Swift DSL, and then render it to a file. Moreover, since you can use Xcode to type in a scene, you can take advantage of its own features like type-checking and fixits, which were not possible using Clojure. We're effectively using Xcode as a GUI for running a set of sketches. As with the Clojure implementation, this one is based on the tests provided by the amazing book, The Ray Tracer Challenge by Jamis Buck.

# Quick start

Expand All @@ -19,7 +19,7 @@ import ScintillaLib

@main
struct QuickStart: ScintillaApp {
var body: World {
var body = World {
PointLight(point(-10, 10, -10))
Camera(400, 400, PI/3, .view(
point(0, 2, -2),
Expand Down Expand Up @@ -93,7 +93,7 @@ import ScintillaLib
@main
struct MyWorld: ScintillaApp {
var body: World {
var body = World {
PointLight(point(-10, 10, -10))
Camera(800, 600, PI/3, .view(
point(0, 0, -5),
Expand Down Expand Up @@ -258,8 +258,8 @@ The following diagram might make it clearer to understand what the parameters re
| | | * | | * |
| | * | | * | |
| |------|------|------|------|
fullUVec | | | * | *|
| | * | * | | |
| | | * | *|
fullUVec | * | * | | |
| |------|------|------|------|
| | | * | * | |
| | * | | |* |
Expand Down Expand Up @@ -331,10 +331,10 @@ import ScintillaLib

@main
struct MyWorld: ScintillaApp {
var body: World {
var body = World {
PointLight(point(-10, 10, -10))
Camera(800, 600, PI/3, .view(
point(0, 10, -15),
point(0, 1, -2),
point(0, 0, 0),
vector(0, 1, 0)))
Sphere(.solidColor(Color(0, 0, 1)))
Expand Down Expand Up @@ -366,6 +366,51 @@ If you've done all that, you now have a bona fide application and should be able

![](./images/MyWorld.png)

You can also optionally render a scene with antialiasing. In the image above, you can see that the various edges of the object are pretty jagged and take away from the verisimilitude of the image. By adding a property modifier to the `World` object, `.antialiasing(true)`, we can improve its quality:

```
import ScintillaLib

@main
struct CSGExample: ScintillaApp {
var body = World {
PointLight(point(-10, 10, -10))
Camera(400, 400, PI/3, .view(
point(0, 1.5, -2),
point(0, 0, 0),
vector(0, 1, 0)))
Sphere(.solidColor(Color(0, 0, 1)))
.intersection {
Cube(.solidColor(Color(1, 0, 0)))
.scale(0.8, 0.8, 0.8)
}
.difference {
for (thetaX, thetaZ) in [(0, 0), (0, PI/2), (PI/2, 0)] {
Cylinder(.solidColor(Color(0, 1, 0)))
.scale(0.5, 0.5, 0.5)
.rotateX(thetaX)
.rotateZ(thetaZ)
}
}
.rotateY(PI/6)
}
.antialiasing(true)
}
```

... and below is the resultant image:

![](./images/Antialiasing.png)

Because rendering times are much slower with antialiasing turned out, you should make sure that the run configuration is set to Release in order to run Swift in the fastest fashion. To get there, go to Product -> Scheme -> Edit Scheme...

![](./images/SchemeSettings.png)

## Adding new scenes

You can have multiple scenes in a single project by adding new targets via File -> New -> Target... Just make sure that ScintillaLib is included as a library in the target; go to the project navigator, click on the project name, then the target name in the editor pane, then the General tab.

![](./images/Libraries.png)

## Relevant links

Expand Down
53 changes: 43 additions & 10 deletions Sources/ScintillaLib/World.swift
Expand Up @@ -9,13 +9,17 @@ import Foundation

let MAX_RECURSIVE_CALLS = 5

public struct World {
public class World {
var light: Light
var camera: Camera
var objects: [Shape]
var antialiasing: Bool = false

public init(@WorldBuilder builder: () -> World) {
self = builder()
let world = builder()
self.light = world.light
self.camera = world.camera
self.objects = world.objects
}

public init(_ light: Light, _ camera: Camera, @ShapeBuilder builder: () -> [Shape]) {
Expand All @@ -30,6 +34,11 @@ public struct World {
self.objects = objects
}

public func antialiasing(_ antialiasing: Bool) -> Self {
self.antialiasing = antialiasing
return self
}

func intersect(_ ray: Ray) -> [Intersection] {
var intersections = objects.flatMap({object in object.intersect(ray)})
intersections
Expand Down Expand Up @@ -187,10 +196,10 @@ public struct World {
}
}

func rayForPixel(_ pixelX: Int, _ pixelY: Int) -> Ray {
func rayForPixel(_ pixelX: Int, _ pixelY: Int, _ dx: Double = 0.5, _ dy: Double = 0.5) -> Ray {
// The offset from the edge of the canvas to the pixel's center
let offsetX = (Double(pixelX) + 0.5) * self.camera.pixelSize
let offsetY = (Double(pixelY) + 0.5) * self.camera.pixelSize
let offsetX = (Double(pixelX) + dx) * self.camera.pixelSize
let offsetY = (Double(pixelY) + dy) * self.camera.pixelSize

// The untransformed coordinates of the pixel in world space.
// (Remember that the camera looks toward -z, so +x is to the *left*.)
Expand All @@ -209,14 +218,38 @@ public struct World {

public func render() -> Canvas {
var canvas = Canvas(self.camera.horizontalSize, self.camera.verticalSize)
for y in 0...self.camera.verticalSize-1 {
for x in 0...self.camera.horizontalSize-1 {
let ray = self.rayForPixel(x, y)
let color = self.colorAt(ray, MAX_RECURSIVE_CALLS)
for y in 0..<self.camera.verticalSize {
for x in 0..<self.camera.horizontalSize {
let color: Color

if self.antialiasing {
let subpixelSamplesX = 4
let subpixelSamplesY = 4

var colorSamples: Color = .black
for i in 0..<subpixelSamplesX {
for j in 0..<subpixelSamplesY {
let subpixelWidth = 1.0/Double(subpixelSamplesX)
let subpixelHeight = 1.0/Double(subpixelSamplesY)
let jitterX = Double.random(in: 0.0...subpixelWidth)
let jitterY = Double.random(in: 0.0...subpixelHeight)
let dx = Double(i)*subpixelWidth + jitterX
let dy = Double(j)*subpixelHeight + jitterY
let ray = self.rayForPixel(x, y, dx, dy)
let colorSample = self.colorAt(ray, MAX_RECURSIVE_CALLS)
colorSamples = colorSamples.add(colorSample)
}
}

let totalSamples = subpixelSamplesX*subpixelSamplesX
color = colorSamples.divideScalar(Double(totalSamples))
} else {
let ray = self.rayForPixel(x, y)
color = self.colorAt(ray, MAX_RECURSIVE_CALLS)
}
canvas.setPixel(x, y, color)
}
}
return canvas
}
}

Binary file added images/Antialiasing.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Libraries.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/SchemeSettings.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 4f80aee

Please sign in to comment.