fdai7303
2 years ago
24 changed files with 1247 additions and 0 deletions
-
4.vscode/settings.json
-
BINdist/manyspheres-finished.png
-
39dist/manyspheres.html
-
BINdist/phong-finished.png
-
43dist/phong.html
-
BINdist/raytracing-finished.png
-
39dist/raytracing.html
-
19src/04/bresenhamsimple.ts
-
12src/04/ddasimple.ts
-
32src/05/camera.ts
-
39src/05/intersection.ts
-
28src/05/manyspheres.ts
-
38src/05/ray.ts
-
28src/05/raytracing.ts
-
51src/05/setup-manyspheres.ts
-
38src/05/setup-raytracing.ts
-
49src/05/sphere.ts
-
256src/05/vector.ts
-
35src/06/phong.ts
-
32src/06/raytracing.ts
-
62src/06/setup-phong.ts
-
50test/ray-spec.ts
-
78test/sphere-spec.ts
-
275test/vector-spec.ts
@ -0,0 +1,4 @@ |
|||
{ |
|||
"mochaExplorer.require": "ts-node/register", |
|||
"mochaExplorer.files": "test/*.ts", |
|||
} |
After Width: 500 | Height: 500 | Size: 2.5 KiB |
@ -0,0 +1,39 @@ |
|||
<!DOCTYPE html> |
|||
|
|||
<html lang="en"> |
|||
|
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<title>Graphische Datenverarbeitung - Raytracing - Many Spheres</title> |
|||
<script type="text/javascript" src="manyspheres.bundle.js"></script> |
|||
<link rel="icon" type="image/png" href="ai-logo.png"> |
|||
</head> |
|||
|
|||
<body> |
|||
<nav class="navbar navbar-light bg-light"> |
|||
<div class="container"> |
|||
<a class="navbar-brand" href="/index.html"> |
|||
<img src="ai-logo.png" width="30" height="30" class="d-inline-block align-top" alt=""> |
|||
Graphische Datenverarbeitung |
|||
</a> |
|||
<div class="navbar-brand">Raytracing</div> |
|||
</div> |
|||
</nav> |
|||
<br> |
|||
<div class="container"> |
|||
<div class="row"> |
|||
<figure class="figure col-sm-6"> |
|||
<canvas id="result" class="figure-img mx-auto d-block" width="500" height="500"></canvas> |
|||
<figcaption class="figure-caption text-center"> |
|||
Implement a Raytracer by sending a ray into the scene for every pixel. Color the pixel with the color of the nearest sphere if the ray hits. |
|||
</figcaption> |
|||
</figure> |
|||
<figure class="col-sm-6 figure"> |
|||
<img class="figure-img mx-auto d-block" src="manyspheres-finished.png"> |
|||
<figcaption class="figure-caption text-center">Reference image</figcaption> |
|||
</figure> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
|
|||
</html> |
After Width: 500 | Height: 500 | Size: 24 KiB |
@ -0,0 +1,43 @@ |
|||
<!DOCTYPE html> |
|||
|
|||
<html lang="en"> |
|||
|
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<title>Graphische Datenverarbeitung - Raytracing - Phong</title> |
|||
<script type="text/javascript" src="phong.bundle.js"></script> |
|||
<link rel="icon" type="image/png" href="ai-logo.png"> |
|||
</head> |
|||
|
|||
<body> |
|||
<nav class="navbar navbar-light bg-light"> |
|||
<div class="container"> |
|||
<a class="navbar-brand" href="/index.html"> |
|||
<img src="ai-logo.png" width="30" height="30" class="d-inline-block align-top" alt=""> |
|||
Graphische Datenverarbeitung |
|||
</a> |
|||
<div class="navbar-brand">Phong</div> |
|||
</div> |
|||
</nav> |
|||
<br> |
|||
<div class="container"> |
|||
<div class="row"> |
|||
<figure class="figure col-sm-6"> |
|||
<canvas id="result" class="figure-img mx-auto d-block" width="500" height="500"></canvas> |
|||
<figcaption class="figure-caption text-center"> |
|||
Implement a Raytracer that uses the Phong Lighting model. |
|||
<div class="form-group text-left"> |
|||
<label for="shininess">Shininess</label> |
|||
<input class="form-control-range" id="shininess" type="range" min="1" max="50"> |
|||
</div> |
|||
</figcaption> |
|||
</figure> |
|||
<figure class="col-sm-6 figure"> |
|||
<img class="figure-img mx-auto d-block" src="phong-finished.png"> |
|||
<figcaption class="figure-caption text-center">Reference image</figcaption> |
|||
</figure> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
|
|||
</html> |
After Width: 500 | Height: 500 | Size: 2.9 KiB |
@ -0,0 +1,39 @@ |
|||
<!DOCTYPE html> |
|||
|
|||
<html lang="en"> |
|||
|
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<title>Graphische Datenverarbeitung - Raytracing - Basic Raytracing</title> |
|||
<script type="text/javascript" src="raytracing.bundle.js"></script> |
|||
<link rel="icon" type="image/png" href="ai-logo.png"> |
|||
</head> |
|||
|
|||
<body> |
|||
<nav class="navbar navbar-light bg-light"> |
|||
<div class="container"> |
|||
<a class="navbar-brand" href="/index.html"> |
|||
<img src="ai-logo.png" width="30" height="30" class="d-inline-block align-top" alt=""> |
|||
Graphische Datenverarbeitung |
|||
</a> |
|||
<div class="navbar-brand">Raytracing</div> |
|||
</div> |
|||
</nav> |
|||
<br> |
|||
<div class="container"> |
|||
<div class="row"> |
|||
<figure class="figure col-sm-6"> |
|||
<canvas id="result" class="figure-img mx-auto d-block" width="500" height="500"></canvas> |
|||
<figcaption class="figure-caption text-center"> |
|||
Implement a Raytracer by sending a ray into the scene for every pixel. Color the pixel black if the ray hits the given sphere. |
|||
</figcaption> |
|||
</figure> |
|||
<figure class="col-sm-6 figure"> |
|||
<img class="figure-img mx-auto d-block" src="raytracing-finished.png"> |
|||
<figcaption class="figure-caption text-center">Reference image</figcaption> |
|||
</figure> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
|
|||
</html> |
@ -0,0 +1,32 @@ |
|||
import Vector from '../05/vector'; |
|||
|
|||
/** |
|||
* A class representing a camera |
|||
*/ |
|||
export default class Camera { |
|||
|
|||
public width: number; |
|||
public height: number; |
|||
public alpha: number; |
|||
public origin: Vector; |
|||
|
|||
/** |
|||
* Creates a new camera with an image canvas, a field of view, and a position in world space. |
|||
* For now, the camera is always viewing along the negative z-axis. |
|||
* @param width The width of the canvas |
|||
* @param height The height of the canvas |
|||
* @param alpha The field of view in X dimension of the camera |
|||
* @param origin The origin of the camera in world coordinates |
|||
*/ |
|||
constructor( |
|||
width: number, |
|||
height: number, |
|||
alpha: number, |
|||
origin: Vector = new Vector(0, 0, 0, 1) |
|||
) { |
|||
this.width = width; |
|||
this.height = height; |
|||
this.alpha = alpha; |
|||
this.origin = origin; |
|||
} |
|||
} |
@ -0,0 +1,39 @@ |
|||
import Vector from './vector'; |
|||
|
|||
/** |
|||
* Class representing a ray-sphere intersection in 3D space |
|||
*/ |
|||
export default class Intersection { |
|||
|
|||
public t: number; |
|||
public point: Vector; |
|||
public normal: Vector; |
|||
|
|||
/** |
|||
* Create an Intersection |
|||
* @param t The distance on the ray |
|||
* @param point The intersection point |
|||
* @param normal The normal of the surface at the point of intersection |
|||
*/ |
|||
constructor(t: number = Infinity, |
|||
point: Vector = null, |
|||
normal: Vector = null) { |
|||
|
|||
this.t = t; |
|||
this.point = point; |
|||
this.normal = normal; |
|||
} |
|||
|
|||
/** |
|||
* Determines whether this intersection |
|||
* is closer than the other |
|||
* @param other The other Intersection |
|||
* @return The result |
|||
*/ |
|||
closerThan(other: Intersection): boolean { |
|||
if (this.t < other.t) |
|||
return true; |
|||
else |
|||
return false; |
|||
} |
|||
} |
@ -0,0 +1,28 @@ |
|||
import Camera from './camera'; |
|||
import Sphere from './sphere'; |
|||
import Intersection from './intersection'; |
|||
import Vector from './vector'; |
|||
import Ray from './ray'; |
|||
|
|||
/** |
|||
* Compute the color of the pixel (x, y) by raytracing |
|||
* using a given camera and multiple spheres. |
|||
* |
|||
* @param data The linearised pixel array |
|||
* @param camera The camera used for raytracing |
|||
* @param spheres The spheres to raytrace |
|||
* @param x The x coordinate of the pixel to convert |
|||
* @param y The y coordinate of the pixel to convert |
|||
* @param width The width of the canvas |
|||
* @param height The height of the canvas |
|||
*/ |
|||
export function raytrace(data: Uint8ClampedArray, |
|||
camera: Camera, |
|||
spheres: Array<Sphere>, |
|||
x: number, y: number, |
|||
width: number, height: number) { |
|||
|
|||
// TODO: Generate ray and perform intersection with every sphere.
|
|||
// TODO: On intersection set pixel color to color of the sphere
|
|||
// TODO: containing the closest intersection point.
|
|||
} |
@ -0,0 +1,38 @@ |
|||
import Vector from './vector'; |
|||
import Camera from './camera'; |
|||
|
|||
/** |
|||
* Class representing a ray |
|||
*/ |
|||
export default class Ray { |
|||
|
|||
public origin: Vector = null; |
|||
public direction: Vector = null; |
|||
|
|||
/** |
|||
* Creates a new ray with origin and direction |
|||
* @param origin The origin of the Ray |
|||
* @param direction The direction of the Ray |
|||
*/ |
|||
constructor(origin: Vector, direction: Vector) { |
|||
|
|||
this.origin = origin; |
|||
this.direction = direction; |
|||
} |
|||
|
|||
/** |
|||
* Creates a ray from the camera through the image plane. |
|||
* The image plane is positioned in direction of the negative z-axis. |
|||
* @param x The pixel's x-position in the canvas |
|||
* @param y The pixel's y-position in the canvas |
|||
* @param camera The Camera |
|||
* @return The resulting Ray |
|||
*/ |
|||
static makeRay(x: number, y: number, camera: Camera): Ray { |
|||
// TODO: Generate a ray from the camera origin through pixel (x, y)
|
|||
// TODO: on the image plane. In addition to the coordinates (x, y), you will need the
|
|||
// TODO: width and height of the camera (i.e. the width and height of the camera's
|
|||
// TODO: image plane), and the angle alpha specifying the camera's field of view.
|
|||
return null; |
|||
} |
|||
} |
@ -0,0 +1,28 @@ |
|||
import Camera from './camera'; |
|||
import Sphere from './sphere'; |
|||
import Ray from './ray'; |
|||
|
|||
/** |
|||
* Compute the color of the pixel (x, y) by raytracing |
|||
* using a given camera and a sphere. |
|||
* |
|||
* @param data The linearised pixel array |
|||
* @param camera The camera used for raytracing |
|||
* @param sphere The sphere to raytrace |
|||
* @param x The x coordinate of the pixel to convert |
|||
* @param y The y coordinate of the pixel to convert |
|||
* @param width The width of the canvas |
|||
* @param height The height of the canvas |
|||
*/ |
|||
|
|||
export function raytrace(data: Uint8ClampedArray, |
|||
camera: Camera, |
|||
sphere: Sphere, |
|||
x: number, y: number, |
|||
width: number, height: number) { |
|||
|
|||
// TODO: Create a ray from the camera's position through the pixel
|
|||
// TODO: (x, y) in the camera's image plane, and perform intersection
|
|||
// TODO: with the given sphere. Set color of pixel (x, y) in the data
|
|||
// TODO: array to black, if the ray hits the sphere.
|
|||
} |
@ -0,0 +1,51 @@ |
|||
import 'bootstrap'; |
|||
import 'bootstrap/scss/bootstrap.scss'; |
|||
import Sphere from './sphere'; |
|||
import Vector from './vector'; |
|||
import Camera from './camera'; |
|||
import { raytrace } from './manyspheres'; |
|||
|
|||
window.addEventListener('load', evt => { |
|||
|
|||
const canvas = document.getElementById("result") as HTMLCanvasElement; |
|||
|
|||
if (canvas === null) |
|||
return; |
|||
|
|||
const ctx = canvas.getContext("2d"); |
|||
var pixel = ctx.createImageData(1, 1); |
|||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
|||
const data = imageData.data; |
|||
|
|||
const spheres: Sphere[] = [ |
|||
new Sphere( |
|||
new Vector(0, 0, -10, 1), |
|||
2.0, |
|||
new Vector(1, 0, 0, 1) |
|||
), |
|||
new Sphere( |
|||
new Vector(2, 0, -12, 1), |
|||
1.5, |
|||
new Vector(0, 1, 0, 1) |
|||
), |
|||
new Sphere( |
|||
new Vector(-2, 0, -8, 1), |
|||
1.0, |
|||
new Vector(0, 0, 1, 1) |
|||
) |
|||
]; |
|||
|
|||
const camera = new Camera(canvas.width, canvas.height, Math.PI / 3); |
|||
|
|||
for (let y = 0; y < canvas.height; y++) { |
|||
for (let x = 0; x < canvas.width; x++) { |
|||
|
|||
raytrace(data, camera, spheres, x, y, canvas.width, canvas.height); |
|||
|
|||
// update pixel in HTML context2d
|
|||
for (let i = 0; i < 4; i ++) |
|||
pixel.data[i] = data[(x + y * canvas.width) * 4 + i]; |
|||
ctx.putImageData(pixel, x, y); |
|||
} |
|||
} |
|||
}); |
@ -0,0 +1,38 @@ |
|||
import 'bootstrap'; |
|||
import 'bootstrap/scss/bootstrap.scss'; |
|||
import Sphere from './sphere'; |
|||
import Vector from './vector'; |
|||
import Camera from './camera'; |
|||
import { raytrace } from './raytracing'; |
|||
|
|||
window.addEventListener('load', evt => { |
|||
|
|||
const canvas = document.getElementById("result") as HTMLCanvasElement; |
|||
if (canvas === null) |
|||
return; |
|||
|
|||
const ctx = canvas.getContext("2d"); |
|||
var pixel = ctx.createImageData(1, 1); |
|||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
|||
const data = imageData.data; |
|||
|
|||
const sphere = new Sphere( |
|||
new Vector(0, 0, -10, 1), // position
|
|||
4.0, // radius
|
|||
new Vector(0, 0, 0, 1) // color
|
|||
); |
|||
|
|||
const camera = new Camera(canvas.width, canvas.height, Math.PI / 3); |
|||
|
|||
for (let y = 0; y < canvas.height; y++) { |
|||
for (let x = 0; x < canvas.width; x++) { |
|||
|
|||
raytrace(data, camera, sphere, x, y, canvas.width, canvas.height); |
|||
|
|||
// update pixel in HTML context2d
|
|||
for (let i = 0; i < 4; i ++) |
|||
pixel.data[i] = data[(x + y * canvas.width) * 4 + i]; |
|||
ctx.putImageData(pixel, x, y); |
|||
} |
|||
} |
|||
}); |
@ -0,0 +1,49 @@ |
|||
import Vector from './vector'; |
|||
import Intersection from './intersection'; |
|||
import Ray from './ray'; |
|||
|
|||
/** |
|||
* A class representing a sphere |
|||
*/ |
|||
export default class Sphere { |
|||
|
|||
public center: Vector; |
|||
public radius: number; |
|||
public color: Vector; |
|||
|
|||
/** |
|||
* Creates a new Sphere with center and radius |
|||
* @param center The center of the Sphere |
|||
* @param radius The radius of the Sphere |
|||
* @param color The color of the Sphere |
|||
*/ |
|||
constructor( |
|||
center: Vector, |
|||
radius: number, |
|||
color: Vector |
|||
) { |
|||
this.center = center; |
|||
this.radius = radius; |
|||
this.color = color; |
|||
} |
|||
|
|||
/** |
|||
* Calculates the intersection of the sphere with the given ray |
|||
* @param ray The ray to intersect with |
|||
* @return The intersection if there is one, null if there is none |
|||
*/ |
|||
intersect(ray: Ray): Intersection | null { |
|||
|
|||
// TODO: Calculate the quadratic equation for ray-sphere
|
|||
// TODO: intersection. You will need the origin of your ray as x0,
|
|||
// TODO: the ray direction, and the radius of the sphere.
|
|||
// TODO: Don't forget to translate your ray's starting position with
|
|||
// TODO: respect to the center of the sphere.
|
|||
// TODO: Calculate the discriminant c, and distinguish between the 3
|
|||
// TODO: possible outcomes: no hit, one hit, or two hits.
|
|||
// TODO: Return an Intersection or null if there was no hit. In case
|
|||
// TODO: of two hits, return the one closer to the start point of
|
|||
// TODO: the ray.
|
|||
return null; |
|||
} |
|||
} |
@ -0,0 +1,256 @@ |
|||
/** |
|||
* Class representing a vector in 4D space |
|||
*/ |
|||
export default class Vector { |
|||
/** |
|||
* The variable to hold the vector data |
|||
*/ |
|||
data: [number, number, number, number]; |
|||
|
|||
/** |
|||
* Create a vector |
|||
* @param x The x component |
|||
* @param y The y component |
|||
* @param z The z component |
|||
* @param w The w component |
|||
*/ |
|||
constructor(x: number, y: number, z: number, w: number) { |
|||
// TODO: Set the data member components to the given values
|
|||
} |
|||
|
|||
/** |
|||
* Returns the x component of the vector |
|||
* @return The x component of the vector |
|||
*/ |
|||
get x(): number { |
|||
// TODO: Return actual value
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Sets the x component of the vector to val |
|||
* @param val - The new value |
|||
*/ |
|||
set x(val: number) { |
|||
// TODO: Set actual value
|
|||
} |
|||
|
|||
/** |
|||
* Returns the first component of the vector |
|||
* @return The first component of the vector |
|||
*/ |
|||
get r(): number { |
|||
// TODO: Return actual value
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Sets the first component of the vector to val |
|||
* @param val The new value |
|||
*/ |
|||
set r(val: number) { |
|||
// TODO: Set actual value
|
|||
} |
|||
|
|||
/** |
|||
* Returns the y component of the vector |
|||
* @return The y component of the vector |
|||
*/ |
|||
get y(): number { |
|||
// TODO: Return actual value
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Sets the y component of the vector to val |
|||
* @param val The new value |
|||
*/ |
|||
set y(val: number) { |
|||
// TODO: Set actual value
|
|||
} |
|||
|
|||
/** |
|||
* Returns the second component of the vector |
|||
* @return The second component of the vector |
|||
*/ |
|||
get g(): number { |
|||
// TODO: Return actual value
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Sets the second component of the vector to val |
|||
* @param val The new value |
|||
*/ |
|||
set g(val: number) { |
|||
// TODO: Set actual value
|
|||
} |
|||
|
|||
/** |
|||
* Returns the z component of the vector |
|||
* @return The z component of the vector |
|||
*/ |
|||
get z(): number { |
|||
// TODO: Return actual value
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Sets the z component of the vector to val |
|||
* @param val The new value |
|||
*/ |
|||
set z(val: number) { |
|||
// TODO: Set actual value
|
|||
} |
|||
|
|||
/** |
|||
* Returns the third component of the vector |
|||
* @return The third component of the vector |
|||
*/ |
|||
get b(): number { |
|||
// TODO: Return actual value
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Sets the third component of the vector to val |
|||
* @param val The new value |
|||
*/ |
|||
set b(val: number) { |
|||
// TODO: Set actual value
|
|||
} |
|||
|
|||
/** |
|||
* Returns the w component of the vector |
|||
* @return The w component of the vector |
|||
*/ |
|||
get w(): number { |
|||
// TODO: Return actual value
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Sets the w component of the vector to val |
|||
* @param val The new value |
|||
*/ |
|||
set w(val: number) { |
|||
// TODO: Set actual value
|
|||
} |
|||
|
|||
/** |
|||
* Returns the fourth component of the vector |
|||
* @return The fourth component of the vector |
|||
*/ |
|||
get a(): number { |
|||
// TODO: Return actual value
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Sets the fourth component of the vector to val |
|||
* @param val The new value |
|||
*/ |
|||
set a(val: number) { |
|||
// TODO: Set actual value
|
|||
} |
|||
|
|||
/** |
|||
* Creates a new vector with the vector added |
|||
* @param other The vector to add |
|||
* @return The new vector; |
|||
*/ |
|||
add(other: Vector): Vector { |
|||
// TODO: Return new vector with result
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Creates a new vector with the vector subtracted |
|||
* @param other The vector to subtract |
|||
* @return The new vector |
|||
*/ |
|||
sub(other: Vector): Vector { |
|||
// TODO: Return new vector with result
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Creates a new vector with the scalar multiplied |
|||
* @param other The scalar to multiply |
|||
* @return The new vector |
|||
*/ |
|||
mul(other: number): Vector { |
|||
// TODO: Return new vector with result
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Creates a new vector with the scalar divided |
|||
* @param other The scalar to divide |
|||
* @return The new vector |
|||
*/ |
|||
div(other: number): Vector { |
|||
// TODO: Return new vector with result
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Dot product |
|||
* @param other The vector to calculate the dot product with |
|||
* @return The result of the dot product |
|||
*/ |
|||
dot(other: Vector): number { |
|||
// TODO: Compute and return dot product
|
|||
return 0; |
|||
} |
|||
|
|||
/** |
|||
* Cross product |
|||
* Calculates the cross product using the first three components. |
|||
* @param other The vector to calculate the cross product with |
|||
* @return The result of the cross product as new Vector |
|||
*/ |
|||
cross(other: Vector): Vector { |
|||
// TODO: Return new vector with result
|
|||
// TODO: The fourth component should be set to 0
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Normalizes this vector in place |
|||
* @returns this vector for easier function chaining |
|||
*/ |
|||
normalize(): Vector { |
|||
// TODO: Normalize this vector and return it
|
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Compares the vector to another vector. |
|||
* @param other The vector to compare to. |
|||
* @return True if the vectors carry equal numbers. |
|||
*/ |
|||
equals(other: Vector): boolean { |
|||
// TODO: Perform comparison and return result
|
|||
// TODO: Respect inaccuracies: coordinates within 0.000001 of each other
|
|||
// TODO: should be considered equal
|
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* Calculates the length of the vector |
|||
* @return The length of the vector |
|||
*/ |
|||
get length(): number { |
|||
// TODO: Calculate and return length
|
|||
return 0; |
|||
} |
|||
|
|||
/** |
|||
* Returns an array representation of the vector |
|||
* @return An array representation. |
|||
*/ |
|||
valueOf(): [number, number, number, number] { |
|||
return this.data; |
|||
} |
|||
} |
@ -0,0 +1,35 @@ |
|||
import Vector from '../05/vector'; |
|||
import Intersection from '../05/intersection'; |
|||
|
|||
/** |
|||
* Calculate the color of an object at the intersection point according to the Phong Lighting model. |
|||
* @param color The color of the intersected object |
|||
* @param intersection The intersection information |
|||
* @param lightPositions The light positions |
|||
* @param shininess The shininess parameter of the Phong model |
|||
* @param cameraPosition The position of the camera |
|||
* @return The resulting color |
|||
*/ |
|||
export function phong( |
|||
color: Vector, |
|||
intersection: Intersection, |
|||
lightPositions: Array<Vector>, |
|||
shininess: number, |
|||
cameraPosition: Vector |
|||
): Vector { |
|||
|
|||
const lightColor = new Vector(0.8, 0.8, 0.8, 0); |
|||
const kA = 1.0; |
|||
const kD = 0.5; |
|||
const kS = 0.5; |
|||
|
|||
// TODO: Compute light intensity according to phong reflection model.
|
|||
// TODO: Compute diffuse lighting using light color, diffuse
|
|||
// TODO: reflectivity, light positions and an intersection point.
|
|||
// TODO: Compute specular reflection using light color, specular
|
|||
// TODO: reflectivity, shininess, light positions, an intersection
|
|||
// TODO: point, and eye (camera) position.
|
|||
// TODO: Return complete phong emission using object color, ambient,
|
|||
// TODO: diffuse and specular terms.
|
|||
return color; |
|||
} |
@ -0,0 +1,32 @@ |
|||
import Camera from '../05/camera'; |
|||
import Sphere from '../05/sphere'; |
|||
import Intersection from '../05/intersection'; |
|||
import Vector from '../05/vector'; |
|||
import Ray from '../05/ray'; |
|||
import { phong } from './phong'; |
|||
|
|||
/** |
|||
* Compute the color of the pixel (x, y) by raytracing |
|||
* using a given camera and multiple spheres. |
|||
* |
|||
* @param data The linearised pixel array |
|||
* @param camera The camera used for raytracing |
|||
* @param spheres The spheres to raytrace |
|||
* @param x The x coordinate of the pixel to convert |
|||
* @param y The y coordinate of the pixel to convert |
|||
* @param width The width of the canvas |
|||
* @param height The height of the canvas |
|||
*/ |
|||
export function raytracePhong(data: Uint8ClampedArray, |
|||
camera: Camera, |
|||
spheres: Array<Sphere>, |
|||
lightPositions: Array<Vector>, |
|||
shininess: number, |
|||
x: number, y: number, |
|||
width: number, height: number) { |
|||
|
|||
// TODO: Create ray from camera through image plane at position (x, y).
|
|||
// TODO: Compute closest intersection with spheres in the scene.
|
|||
// TODO: Compute emission at point of intersection using phong model.
|
|||
// TODO: Set pixel color accordingly.
|
|||
} |
@ -0,0 +1,62 @@ |
|||
import 'bootstrap'; |
|||
import 'bootstrap/scss/bootstrap.scss'; |
|||
import Vector from '../05/vector'; |
|||
import Sphere from '../05/sphere'; |
|||
import Ray from '../05/ray'; |
|||
import Camera from '../05/camera'; |
|||
import Intersection from '../05/intersection'; |
|||
import { raytracePhong } from './raytracing'; |
|||
|
|||
window.addEventListener('load', () => { |
|||
|
|||
const canvas = document.getElementById("result") as HTMLCanvasElement; |
|||
if (canvas === null) |
|||
return; |
|||
|
|||
const ctx = canvas.getContext("2d"); |
|||
var pixel = ctx.createImageData(1, 1); |
|||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
|||
const data = imageData.data; |
|||
|
|||
const spheres: Sphere[] = [ |
|||
new Sphere(new Vector(.5, -.2, -2, 1), 0.4, new Vector(.3, 0, 0, 1)), |
|||
new Sphere(new Vector(-.5, -.2, -1.7, 1), 0.2, new Vector(0, 0, .3, 1)) |
|||
]; |
|||
|
|||
const lightPositions = [ |
|||
new Vector(1, 1, -1, 1) |
|||
]; |
|||
|
|||
let shininess = 10; |
|||
|
|||
const camera = new Camera( |
|||
canvas.width, canvas.height, Math.PI / 3, new Vector(0, 0, 0, 1) |
|||
); |
|||
|
|||
function animate() { |
|||
|
|||
for (let y = 0; y < canvas.height; y++) { |
|||
for (let x = 0; x < canvas.width; x++) { |
|||
|
|||
raytracePhong(data, camera, spheres, lightPositions, shininess, x, y, canvas.width, canvas.height); |
|||
|
|||
// update pixel in HTML context2d
|
|||
for (let i = 0; i < 4; i ++) |
|||
pixel.data[i] = data[(x + y * canvas.width) * 4 + i]; |
|||
ctx.putImageData(pixel, x, y); |
|||
} |
|||
} |
|||
} |
|||
|
|||
const shininessElement = |
|||
document.getElementById("shininess") as HTMLInputElement; |
|||
|
|||
shininessElement.onchange = function () { |
|||
shininess = Number(shininessElement.value); |
|||
window.requestAnimationFrame(animate); |
|||
} |
|||
|
|||
shininess = Number(shininessElement.value); |
|||
|
|||
window.requestAnimationFrame(animate); |
|||
}); |
@ -0,0 +1,50 @@ |
|||
import Ray from "../src/05/ray"; |
|||
import Camera from "../src/05/camera"; |
|||
|
|||
import { assert, expect } from 'chai'; |
|||
|
|||
describe('Ray', () => { |
|||
|
|||
it('can be initialized with two numbers and a camera', () => { |
|||
const r: Ray = Ray.makeRay(0, 0, new Camera(128, 128, 45)); |
|||
expect(r).to.be.an('object'); |
|||
}); |
|||
|
|||
it('the origin of the ray is initialized correctly', () => { |
|||
const r: Ray = Ray.makeRay(0, 0, new Camera(128, 128, 45)); |
|||
expect(r).to.be.an('object'); |
|||
expect(r.origin.x).to.equal(0); |
|||
expect(r.origin.y).to.equal(0); |
|||
expect(r.origin.z).to.equal(0); |
|||
expect(r.origin.w).to.equal(1); |
|||
}); |
|||
|
|||
it('the direction is normalized', () => { |
|||
const r: Ray = Ray.makeRay(64, 64, new Camera(129, 129, 45)); |
|||
expect(r).to.be.an('object'); |
|||
expect(r.direction.length).to.equal(1); |
|||
}); |
|||
|
|||
it('the direction is initialized correctly', () => { |
|||
const r: Ray = Ray.makeRay(64, 64, new Camera(129, 129, 45)); |
|||
expect(r).to.be.an('object'); |
|||
expect(r.direction.x).to.be.closeTo(0, 0.01); |
|||
expect(r.direction.y).to.be.closeTo(0, 0.01); |
|||
expect(r.direction.z).to.be.closeTo(-1, 0.01); |
|||
expect(r.direction.w).to.be.closeTo(0, 0.01); |
|||
|
|||
const r2: Ray = Ray.makeRay(0, 0, new Camera(129, 129, 45)); |
|||
expect(r2).to.be.an('object'); |
|||
expect(r2.direction.x).to.be.closeTo(-0.435, 0.01); |
|||
expect(r2.direction.y).to.be.closeTo(0.435, 0.01); |
|||
expect(r2.direction.z).to.be.closeTo(-0.787, 0.01); |
|||
expect(r2.direction.w).to.be.closeTo(0, 0.01); |
|||
|
|||
const r3: Ray = Ray.makeRay(10, 7, new Camera(64, 64, 90)); |
|||
expect(r3).to.be.an('object'); |
|||
expect(r3.direction.x).to.be.closeTo(-0.564, 0.01); |
|||
expect(r3.direction.y).to.be.closeTo(0.642, 0.01); |
|||
expect(r3.direction.z).to.be.closeTo(-0.518, 0.01); |
|||
expect(r3.direction.w).to.equal(0); |
|||
}); |
|||
}); |
@ -0,0 +1,78 @@ |
|||
import Sphere from "../src/05/sphere"; |
|||
import Intersection from "../src/05/intersection"; |
|||
|
|||
import { assert, expect } from 'chai'; |
|||
import Vector from "../src/05/vector"; |
|||
import Ray from "../src/05/ray"; |
|||
|
|||
describe('Sphere', () => { |
|||
|
|||
it('can be initialized with center, radius and color', () => { |
|||
const s: Sphere = new Sphere(new Vector(0, 0, 0, 1), 1, new Vector(0, 0, 0, 0)); |
|||
expect(s).to.be.an('object'); |
|||
}); |
|||
|
|||
it('a sphere at origin and radius 1 can be intersected correctly with the x axis', () => { |
|||
|
|||
const s: Sphere = new Sphere(new Vector(0, 0, 0, 1), 1, new Vector(0, 0, 0, 0)); |
|||
const i: Intersection = s.intersect(new Ray(new Vector(-10, 0, 0, 1), new Vector(1, 0, 0, 0))); |
|||
expect(s).to.be.an('object'); |
|||
expect(i).to.be.an('object'); |
|||
|
|||
expect(i.point.x).to.equal(-1); |
|||
expect(i.point.y).to.equal(0); |
|||
expect(i.point.z).to.equal(0); |
|||
}); |
|||
|
|||
it('a sphere at origin and radius 1 can be intersected correctly with the y axis', () => { |
|||
|
|||
const s: Sphere = new Sphere(new Vector(0, 0, 0, 1), 1, new Vector(0, 0, 0, 0)); |
|||
const i: Intersection = s.intersect(new Ray(new Vector(0, -10, 0, 1), new Vector(0, 1, 0, 0))); |
|||
expect(s).to.be.an('object'); |
|||
expect(i).to.be.an('object'); |
|||
|
|||
expect(i.point.x).to.equal(0); |
|||
expect(i.point.y).to.equal(-1); |
|||
expect(i.point.z).to.equal(0); |
|||
}); |
|||
|
|||
it('a sphere at origin and radius 1 can be intersected correctly with the z axis', () => { |
|||
|
|||
const s: Sphere = new Sphere(new Vector(0, 0, 0, 1), 1, new Vector(0, 0, 0, 0)); |
|||
const i: Intersection = s.intersect(new Ray(new Vector(0, 0, -10, 1), new Vector(0, 0, 1, 0))); |
|||
expect(s).to.be.an('object'); |
|||
expect(i).to.be.an('object'); |
|||
|
|||
expect(i.point.x).to.equal(0); |
|||
expect(i.point.y).to.equal(0); |
|||
expect(i.point.z).to.equal(-1); |
|||
}); |
|||
|
|||
it('intersection is correct when radius != 1', () => { |
|||
|
|||
const s: Sphere = new Sphere(new Vector(0, 0, 0, 1), 2.5, new Vector(0, 0, 0, 0)); |
|||
const i: Intersection = s.intersect(new Ray(new Vector(-10, 0, 0, 1), new Vector(1, 0, 0, 0))); |
|||
expect(s).to.be.an('object'); |
|||
expect(i).to.be.an('object'); |
|||
|
|||
expect(i.point.x).to.equal(-2.5); |
|||
expect(i.point.y).to.equal(0); |
|||
expect(i.point.z).to.equal(0); |
|||
expect(i.t).to.equal(7.5); |
|||
}); |
|||
|
|||
it('intersection is correct when center != (0, 0, 0, 1)', () => { |
|||
|
|||
const s: Sphere = new Sphere(new Vector(1, 0, 0, 1), 2.5, new Vector(0, 0, 0, 0)); |
|||
const i: Intersection = s.intersect(new Ray(new Vector(-10, 0, 0, 1), new Vector(1, 0, 0, 0))); |
|||
expect(s).to.be.an('object'); |
|||
expect(i).to.be.an('object'); |
|||
|
|||
expect(i.point.x).to.equal(-1.5); |
|||
expect(i.point.y).to.equal(0); |
|||
expect(i.point.z).to.equal(0); |
|||
expect(i.t).to.equal(8.5); |
|||
}); |
|||
}); |
|||
|
|||
// TODO: Test normalization of normal
|
@ -0,0 +1,275 @@ |
|||
import Vector from "../src/05/vector"; |
|||
|
|||
import { expect } from 'chai'; |
|||
|
|||
describe('Vector', () => { |
|||
|
|||
it('can be initialized with 4 numbers', () => { |
|||
const v = new Vector(1, 2, 3, 4); |
|||
expect(v).to.be.an('object'); |
|||
expect(v.data[0]).to.be.a("number"); |
|||
expect(v.data[0]).to.equal(1); |
|||
expect(v.data[1]).to.be.a("number"); |
|||
expect(v.data[1]).to.equal(2); |
|||
expect(v.data[2]).to.be.a("number"); |
|||
expect(v.data[2]).to.equal(3); |
|||
expect(v.data[3]).to.be.a("number"); |
|||
expect(v.data[3]).to.equal(4); |
|||
}); |
|||
|
|||
it('x component can be set and retrieved', () => { |
|||
const v = new Vector(0, 0, 0, 0); |
|||
expect(v).has.property('x'); |
|||
v.x = 42; |
|||
expect(v.x).to.equal(42); |
|||
}); |
|||
|
|||
it('y component can be set and retrieved', () => { |
|||
const v = new Vector(0, 0, 0, 0); |
|||
expect(v).has.property('y'); |
|||
v.y = 42; |
|||
expect(v.y).to.equal(42); |
|||
}); |
|||
|
|||
it('z component can be set and retrieved', () => { |
|||
const v = new Vector(0, 0, 0, 0); |
|||
expect(v).has.property('z'); |
|||
v.z = 42; |
|||
expect(v.z).to.equal(42); |
|||
}); |
|||
|
|||
it('w component can be set and retrieved', () => { |
|||
const v = new Vector(0, 0, 0, 0); |
|||
expect(v).has.property('w'); |
|||
v.w = 42; |
|||
expect(v.w).to.equal(42); |
|||
}); |
|||
|
|||
it('r component can be set and retrieved', () => { |
|||
const v = new Vector(0, 0, 0, 0); |
|||
expect(v).has.property('r'); |
|||
v.r = 42; |
|||
expect(v.r).to.equal(42); |
|||
}); |
|||
|
|||
it('g component can be set and retrieved', () => { |
|||
const v = new Vector(0, 0, 0, 0); |
|||
expect(v).has.property('g'); |
|||
v.g = 42; |
|||
expect(v.g).to.equal(42); |
|||
}); |
|||
|
|||
it('b component can be set and retrieved', () => { |
|||
const v = new Vector(0, 0, 0, 0); |
|||
expect(v).has.property('b'); |
|||
v.b = 42; |
|||
expect(v.b).to.equal(42); |
|||
}); |
|||
|
|||
it('a component can be set and retrieved', () => { |
|||
const v = new Vector(0, 0, 0, 0); |
|||
expect(v).has.property('a'); |
|||
v.a = 42; |
|||
expect(v.a).to.equal(42); |
|||
}); |
|||
|
|||
it('method add exists', () => { |
|||
const v = new Vector(0, 0, 0, 0); |
|||
expect(v).to.respondTo('add'); |
|||
}); |
|||
|
|||
it('method sub exists', () => { |
|||
const v = new Vector(0, 0, 0, 0); |
|||
expect(v).to.respondTo('sub'); |
|||
}); |
|||
|
|||
it('method mul exists', () => { |
|||
const v = new Vector(0, 0, 0, 0); |
|||
expect(v).to.respondTo('mul'); |
|||
}); |
|||
|
|||
it('method div exists', () => { |
|||
const v = new Vector(0, 0, 0, 0); |
|||
expect(v).to.respondTo('div'); |
|||
}); |
|||
|
|||
it('method normalize exists', () => { |
|||
const v = new Vector(0, 0, 0, 0); |
|||
expect(v).to.respondTo('normalize'); |
|||
}); |
|||
|
|||
it('length returns the correct result', () => { |
|||
const v = new Vector(0, 4, 3, 0); |
|||
expect(v).has.property('length'); |
|||
expect(v.length).to.equal(5); |
|||
}); |
|||
|
|||
it('addition adds the other vector', () => { |
|||
const a = new Vector(1, 2, 3, 1); |
|||
const b = new Vector(1, -2, 0, 0); |
|||
expect(a).to.respondTo('add'); |
|||
|
|||
const c: Vector = a.add(b); |
|||
expect(c).to.not.be.null; |
|||
expect(c).to.be.an('object'); |
|||
|
|||
expect(c.x).to.equal(2); |
|||
expect(c.y).to.equal(0); |
|||
expect(c.z).to.equal(3); |
|||
expect(c.w).to.equal(1); |
|||
}); |
|||
|
|||
it('addition does not change the value of "this"', () => { |
|||
|
|||
const a = new Vector(1, 2, 3, 1); |
|||
const b = new Vector(1, -2, 0, 0); |
|||
expect(a).to.respondTo('add'); |
|||
|
|||
const c: Vector = a.add(b); |
|||
expect(a).to.not.be.null; |
|||
expect(a).to.be.an('object'); |
|||
|
|||
expect(a.x).to.equal(1); |
|||
expect(a.y).to.equal(2); |
|||
expect(a.z).to.equal(3); |
|||
expect(a.w).to.equal(1); |
|||
}); |
|||
|
|||
it('subtraction subtracts the other vector', () => { |
|||
const a = new Vector(1, 3, 3, 0); |
|||
const b = new Vector(2, -2, 0, 0); |
|||
expect(a).to.respondTo('sub'); |
|||
|
|||
const c: Vector = a.sub(b); |
|||
expect(c).to.not.be.null; |
|||
expect(c).to.be.an('object'); |
|||
|
|||
expect(c.x).to.equal(-1); |
|||
expect(c.y).to.equal(5); |
|||
expect(c.z).to.equal(3); |
|||
expect(c.w).to.equal(0); |
|||
}); |
|||
|
|||
it('subtraction does not change the value of "this"', () => { |
|||
|
|||
const a = new Vector(1, 2, 3, 1); |
|||
const b = new Vector(1, -2, 0, 0); |
|||
expect(a).to.respondTo('sub'); |
|||
|
|||
const c: Vector = a.sub(b); |
|||
expect(a).to.not.be.null; |
|||
expect(a).to.be.an('object'); |
|||
|
|||
expect(a.x).to.equal(1); |
|||
expect(a.y).to.equal(2); |
|||
expect(a.z).to.equal(3); |
|||
expect(a.w).to.equal(1); |
|||
}); |
|||
|
|||
it('multiplication with a scalar multiplies correctly', () => { |
|||
const a = new Vector(1, 3, 3, 0); |
|||
expect(a).to.respondTo('mul'); |
|||
|
|||
const c: Vector = a.mul(4); |
|||
expect(c.x).to.equal(4); |
|||
expect(c.y).to.equal(12); |
|||
expect(c.z).to.equal(12); |
|||
expect(c.w).to.equal(0); |
|||
}); |
|||
|
|||
it('multiplication does not change the value of "this"', () => { |
|||
const a = new Vector(1, 3, 3, 0); |
|||
expect(a).to.respondTo('mul'); |
|||
|
|||
const c: Vector = a.mul(4); |
|||
expect(a.x).to.equal(1); |
|||
expect(a.y).to.equal(3); |
|||
expect(a.z).to.equal(3); |
|||
expect(a.w).to.equal(0); |
|||
}); |
|||
|
|||
|
|||
it('division by a scalar divides correctly', () => { |
|||
const a = new Vector(3, 12, 6, 0); |
|||
expect(a).to.respondTo('div'); |
|||
|
|||
const c: Vector = a.div(3); |
|||
expect(c.x).to.equal(1); |
|||
expect(c.y).to.equal(4); |
|||
expect(c.z).to.equal(2); |
|||
expect(c.w).to.equal(0); |
|||
}); |
|||
|
|||
it('division does not change the value of "this"', () => { |
|||
const a = new Vector(1, 3, 3, 0); |
|||
expect(a).to.respondTo('div'); |
|||
|
|||
const c: Vector = a.div(4); |
|||
expect(a.x).to.equal(1); |
|||
expect(a.y).to.equal(3); |
|||
expect(a.z).to.equal(3); |
|||
expect(a.w).to.equal(0); |
|||
}); |
|||
|
|||
it('cross product returns the correct result', () => { |
|||
|
|||
const a = new Vector(1, 3, 3, 0); |
|||
const b = new Vector(2, -2, 0, 0); |
|||
expect(a).to.respondTo('cross'); |
|||
const c = a.cross(b); |
|||
expect(c.x).to.equal(6); |
|||
expect(c.y).to.equal(6); |
|||
expect(c.z).to.equal(-8); |
|||
expect(c.w).to.equal(0); |
|||
}); |
|||
|
|||
it('cross product does not change the value of "this"', () => { |
|||
|
|||
const a = new Vector(1, 3, 3, 0); |
|||
const b = new Vector(2, -2, 0, 0); |
|||
expect(a).to.respondTo('cross'); |
|||
const c = a.cross(b); |
|||
expect(a.x).to.equal(1); |
|||
expect(a.y).to.equal(3); |
|||
expect(a.z).to.equal(3); |
|||
expect(a.w).to.equal(0); |
|||
}); |
|||
|
|||
it('dot product is correct', () => { |
|||
|
|||
const a = new Vector(1, 3, 3, 0); |
|||
const b = new Vector(2, 2, 1, 0); |
|||
const c = new Vector(2, -2, 0, 0); |
|||
expect(a).to.respondTo('dot'); |
|||
const d = a.dot(b); |
|||
const e = b.dot(c); |
|||
expect(d).to.equal(11); |
|||
expect(e).to.equal(0); |
|||
}); |
|||
|
|||
it('equality of different vectors returns false', () => { |
|||
|
|||
const a = new Vector(1, 3, 3, 1); |
|||
const b = new Vector(2, -2, 0, 1); |
|||
expect(a).to.respondTo('equals'); |
|||
|
|||
expect(a.equals(b)).to.equal(false); |
|||
}); |
|||
|
|||
it('equality of equal vectors returns true', () => { |
|||
|
|||
const a = new Vector(1, 3, 3, 1); |
|||
expect(a).to.respondTo('equals'); |
|||
|
|||
expect(a.equals(a)).to.equal(true); |
|||
}); |
|||
|
|||
it('vectors very close to each other are equal', () => { |
|||
|
|||
const a = new Vector(1, 3, 3, 1); |
|||
const b = new Vector(1.0000000000001, 3.000000000001, 2.9999999999999, 1); |
|||
expect(a).to.respondTo('equals'); |
|||
|
|||
expect(a.equals(b)).to.equal(true); |
|||
}); |
|||
}); |
Write
Preview
Loading…
Cancel
Save
Reference in new issue