├── .gitignore
├── README.md
├── dummy.dom.js
├── package.json
├── raytrace.css
├── raytracer.html
├── raytracer.js
├── raytracer.js.map
├── raytracer.ts
├── rt.vanilla.html
├── rt.vanilla.js
├── trace.ts.js
├── trace.vanilla.js
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | package-lock.json
2 | node_modules/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # raytrace
2 |
3 | A comparison between [original raytrace.ts](https://github.com/Microsoft/TypeScriptSamples/tree/master/raytracer) performance VS a hand-written [JS port](./rt.vanilla.js).
4 |
5 | **Live Test** - both pages show render time in console
6 |
7 | * original [**TypeScript**](https://webreflection.github.io/raytrace/raytracer.html) targeting *ES2022* code
8 | * hand-written [**JavaScript**](https://webreflection.github.io/raytrace/rt.vanilla.html)
9 |
10 | ## Licence ##
11 |
12 | The [original TypeScript version](https://github.com/Microsoft/TypeScriptSamples/issues/143) and this JS translation are licenced under the Apache-2.0 licence.
13 |
14 | ### How to test locally
15 |
16 | * fork or clone this repo
17 | * enter `raytrace` directory (`cd raytrace`)
18 | * `npm i`
19 | * `npm run build`
20 | * run any local server to test `raytracer.html` or `rt.vanilla.html` on your localhost
21 |
22 | ### Could the vanilla version have TS too?
23 |
24 | Check the [JSDoc TS](https://github.com/WebReflection/raytrace/blob/jsdoc-ts/rt.vanilla.js) annotated file which is fully compliant with TS and it still performs as good as the not-annotated file.
25 |
--------------------------------------------------------------------------------
/dummy.dom.js:
--------------------------------------------------------------------------------
1 | // minimum dependencies to make the code run on node
2 | globalThis.requestAnimationFrame = queueMicrotask;
3 | globalThis.document = {
4 | body: {appendChild(){}},
5 | documentElement: {
6 | classList: {add(){}}
7 | },
8 | createElement: () => ({
9 | width: 0,
10 | height: 0,
11 | getContext: () => ({
12 | fillStyle: '',
13 | fillRect() {}
14 | })
15 | })
16 | };
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "tsc -p .",
5 | "dexnode:vanilla": "npx dexnode --out v8.js.log --redirect-code-traces-to=./v8.vanilla.log trace.vanilla.js",
6 | "dexnode:ts": "npx dexnode --out v8.ts.log --redirect-code-traces-to=./v8.ts.log trace.ts.js"
7 | },
8 | "license": "ISC",
9 | "devDependencies": {
10 | "typescript": "^5.0.4"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/raytrace.css:
--------------------------------------------------------------------------------
1 | html {
2 | background-color: black;
3 | }
4 |
5 | html.done canvas {
6 | animation: blur 1s;
7 | }
8 |
9 | @keyframes blur {
10 | from {
11 | filter: blur(5px);
12 | }
13 | to {
14 | filter: blur(0);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/raytracer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Raytracer TS
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/raytracer.js:
--------------------------------------------------------------------------------
1 | const { floor, min, sqrt } = Math;
2 | class Vector {
3 | constructor(x, y, z) {
4 | this.x = x;
5 | this.y = y;
6 | this.z = z;
7 | }
8 | static times(k, v) { return new Vector(k * v.x, k * v.y, k * v.z); }
9 | static minus(v1, v2) { return new Vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); }
10 | static plus(v1, v2) { return new Vector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); }
11 | static dot(v1, v2) { return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; }
12 | static mag(v) { return sqrt(v.x * v.x + v.y * v.y + v.z * v.z); }
13 | static norm(v) {
14 | var mag = Vector.mag(v);
15 | var div = (mag === 0) ? Infinity : 1.0 / mag;
16 | return Vector.times(div, v);
17 | }
18 | static cross(v1, v2) {
19 | return new Vector(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x);
20 | }
21 | }
22 | class Color {
23 | constructor(r, g, b) {
24 | this.r = r;
25 | this.g = g;
26 | this.b = b;
27 | }
28 | static scale(k, v) { return new Color(k * v.r, k * v.g, k * v.b); }
29 | static plus(v1, v2) { return new Color(v1.r + v2.r, v1.g + v2.g, v1.b + v2.b); }
30 | static times(v1, v2) { return new Color(v1.r * v2.r, v1.g * v2.g, v1.b * v2.b); }
31 | static { this.white = new Color(1.0, 1.0, 1.0); }
32 | static { this.grey = new Color(0.5, 0.5, 0.5); }
33 | static { this.black = new Color(0.0, 0.0, 0.0); }
34 | static { this.background = Color.black; }
35 | static { this.defaultColor = Color.black; }
36 | static toDrawingColor(c) {
37 | return {
38 | r: floor(min(c.r, 1) * 255),
39 | g: floor(min(c.g, 1) * 255),
40 | b: floor(min(c.b, 1) * 255)
41 | };
42 | }
43 | }
44 | class Camera {
45 | constructor(pos, lookAt) {
46 | this.pos = pos;
47 | var down = new Vector(0.0, -1.0, 0.0);
48 | this.forward = Vector.norm(Vector.minus(lookAt, this.pos));
49 | this.right = Vector.times(1.5, Vector.norm(Vector.cross(this.forward, down)));
50 | this.up = Vector.times(1.5, Vector.norm(Vector.cross(this.forward, this.right)));
51 | }
52 | }
53 | class Sphere {
54 | constructor(center, radius, surface) {
55 | this.center = center;
56 | this.surface = surface;
57 | this.radius2 = radius * radius;
58 | }
59 | normal(pos) { return Vector.norm(Vector.minus(pos, this.center)); }
60 | intersect(ray) {
61 | var eo = Vector.minus(this.center, ray.start);
62 | var v = Vector.dot(eo, ray.dir);
63 | var dist = 0;
64 | if (v >= 0) {
65 | var disc = this.radius2 - (Vector.dot(eo, eo) - v * v);
66 | if (disc >= 0) {
67 | dist = v - Math.sqrt(disc);
68 | }
69 | }
70 | if (dist === 0) {
71 | return null;
72 | }
73 | else {
74 | return { thing: this, ray: ray, dist: dist };
75 | }
76 | }
77 | }
78 | class Plane {
79 | constructor(norm, offset, surface) {
80 | this.norm = norm;
81 | this.offset = offset;
82 | this.surface = surface;
83 | }
84 | normal(_pos) { return this.norm; }
85 | intersect(ray) {
86 | var denom = Vector.dot(this.norm, ray.dir);
87 | if (denom > 0) {
88 | return null;
89 | }
90 | else {
91 | var dist = (Vector.dot(this.norm, ray.start) + this.offset) / (-denom);
92 | return { thing: this, ray: ray, dist: dist };
93 | }
94 | }
95 | }
96 | class Surfaces {
97 | static { this.shiny = {
98 | diffuse: function (pos) { return Color.white; },
99 | specular: function (pos) { return Color.grey; },
100 | reflect: function (pos) { return 0.7; },
101 | roughness: 250
102 | }; }
103 | static { this.checkerboard = {
104 | diffuse: function (pos) {
105 | if ((Math.floor(pos.z) + Math.floor(pos.x)) % 2 !== 0) {
106 | return Color.white;
107 | }
108 | else {
109 | return Color.black;
110 | }
111 | },
112 | specular: function (pos) { return Color.white; },
113 | reflect: function (pos) {
114 | if ((Math.floor(pos.z) + Math.floor(pos.x)) % 2 !== 0) {
115 | return 0.1;
116 | }
117 | else {
118 | return 0.7;
119 | }
120 | },
121 | roughness: 150
122 | }; }
123 | }
124 | class RayTracer {
125 | constructor() {
126 | this.maxDepth = 5;
127 | }
128 | intersections(ray, scene) {
129 | var closest = +Infinity;
130 | var closestInter = undefined;
131 | for (const thing of scene.things) {
132 | var inter = thing.intersect(ray);
133 | if (inter != null && inter.dist < closest) {
134 | closestInter = inter;
135 | closest = inter.dist;
136 | }
137 | }
138 | return closestInter;
139 | }
140 | testRay(ray, scene) {
141 | var isect = this.intersections(ray, scene);
142 | if (isect != null) {
143 | return isect.dist;
144 | }
145 | else {
146 | return undefined;
147 | }
148 | }
149 | traceRay(ray, scene, depth) {
150 | var isect = this.intersections(ray, scene);
151 | if (isect == null) {
152 | return Color.background;
153 | }
154 | else {
155 | return this.shade(isect, scene, depth);
156 | }
157 | }
158 | shade(isect, scene, depth) {
159 | var d = isect.ray.dir;
160 | var pos = Vector.plus(Vector.times(isect.dist, d), isect.ray.start);
161 | var normal = isect.thing.normal(pos);
162 | var reflectDir = Vector.minus(d, Vector.times(2, Vector.times(Vector.dot(normal, d), normal)));
163 | var naturalColor = Color.plus(Color.background, this.getNaturalColor(isect.thing, pos, normal, reflectDir, scene));
164 | var reflectedColor = (depth >= this.maxDepth) ? Color.grey : this.getReflectionColor(isect.thing, pos, normal, reflectDir, scene, depth);
165 | return Color.plus(naturalColor, reflectedColor);
166 | }
167 | getReflectionColor(thing, pos, normal, rd, scene, depth) {
168 | return Color.scale(thing.surface.reflect(pos), this.traceRay({ start: pos, dir: rd }, scene, depth + 1));
169 | }
170 | getNaturalColor(thing, pos, norm, rd, scene) {
171 | var addLight = (col, light) => {
172 | var ldis = Vector.minus(light.pos, pos);
173 | var livec = Vector.norm(ldis);
174 | var neatIsect = this.testRay({ start: pos, dir: livec }, scene);
175 | var isInShadow = (neatIsect === undefined) ? false : (neatIsect <= Vector.mag(ldis));
176 | if (isInShadow) {
177 | return col;
178 | }
179 | else {
180 | var illum = Vector.dot(livec, norm);
181 | var lcolor = (illum > 0) ? Color.scale(illum, light.color)
182 | : Color.defaultColor;
183 | var specular = Vector.dot(livec, Vector.norm(rd));
184 | var scolor = (specular > 0) ? Color.scale(Math.pow(specular, thing.surface.roughness), light.color)
185 | : Color.defaultColor;
186 | return Color.plus(col, Color.plus(Color.times(thing.surface.diffuse(pos), lcolor), Color.times(thing.surface.specular(pos), scolor)));
187 | }
188 | };
189 | return scene.lights.reduce(addLight, Color.defaultColor);
190 | }
191 | render(scene, ctx, screenWidth, screenHeight) {
192 | var getPoint = (x, y, camera) => {
193 | var recenterX = x => (x - (screenWidth / 2.0)) / 2.0 / screenWidth;
194 | var recenterY = y => -(y - (screenHeight / 2.0)) / 2.0 / screenHeight;
195 | return Vector.norm(Vector.plus(camera.forward, Vector.plus(Vector.times(recenterX(x), camera.right), Vector.times(recenterY(y), camera.up))));
196 | };
197 | const { camera } = scene;
198 | for (var y = 0; y < screenHeight; y++) {
199 | for (var x = 0; x < screenWidth; x++) {
200 | var color = this.traceRay({ start: camera.pos, dir: getPoint(x, y, camera) }, scene, 0);
201 | const { r, g, b } = Color.toDrawingColor(color);
202 | ctx.fillStyle = `rgb(${r},${g},${b})`;
203 | ctx.fillRect(x, y, 1, 1);
204 | }
205 | }
206 | }
207 | }
208 | function defaultScene() {
209 | return {
210 | things: [new Plane(new Vector(0.0, 1.0, 0.0), 0.0, Surfaces.checkerboard),
211 | new Sphere(new Vector(0.0, 1.0, -0.25), 1.0, Surfaces.shiny),
212 | new Sphere(new Vector(-1.0, 0.5, 1.5), 0.5, Surfaces.shiny)],
213 | lights: [{ pos: new Vector(-2.0, 2.5, 0.0), color: new Color(0.49, 0.07, 0.07) },
214 | { pos: new Vector(1.5, 2.5, 1.5), color: new Color(0.07, 0.07, 0.49) },
215 | { pos: new Vector(1.5, 2.5, -1.5), color: new Color(0.07, 0.49, 0.071) },
216 | { pos: new Vector(0.0, 3.5, 0.0), color: new Color(0.21, 0.21, 0.35) }],
217 | camera: new Camera(new Vector(3.0, 2.0, 4.0), new Vector(-1.0, 0.5, 0.0))
218 | };
219 | }
220 | const exec = (width, height) => {
221 | const canv = document.createElement("canvas");
222 | canv.width = width;
223 | canv.height = height;
224 | (new RayTracer).render(defaultScene(), canv.getContext("2d"), width, height);
225 | document.body.appendChild(canv);
226 | };
227 | const size = 256;
228 | const render = () => new Promise(resolve => {
229 | requestAnimationFrame(() => {
230 | console.time('raytrace TS');
231 | exec(size, size);
232 | console.timeEnd('raytrace TS');
233 | resolve();
234 | });
235 | });
236 | render()
237 | .then(render)
238 | .then(render)
239 | .then(render)
240 | .then(render)
241 | .then(render)
242 | .then(() => {
243 | document.documentElement.classList.add('done');
244 | });
245 | //# sourceMappingURL=raytracer.js.map
--------------------------------------------------------------------------------
/raytracer.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"raytracer.js","sourceRoot":"","sources":["raytracer.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/raytracer.ts:
--------------------------------------------------------------------------------
1 | const { floor, min, sqrt } = Math;
2 |
3 | class Vector {
4 | constructor(public x: number,
5 | public y: number,
6 | public z: number) {
7 | }
8 | static times(k: number, v: Vector) { return new Vector(k * v.x, k * v.y, k * v.z); }
9 | static minus(v1: Vector, v2: Vector) { return new Vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); }
10 | static plus(v1: Vector, v2: Vector) { return new Vector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); }
11 | static dot(v1: Vector, v2: Vector) { return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; }
12 | static mag(v: Vector) { return sqrt(v.x * v.x + v.y * v.y + v.z * v.z); }
13 | static norm(v: Vector) {
14 | var mag = Vector.mag(v);
15 | var div = (mag === 0) ? Infinity : 1.0 / mag;
16 | return Vector.times(div, v);
17 | }
18 | static cross(v1: Vector, v2: Vector) {
19 | return new Vector(v1.y * v2.z - v1.z * v2.y,
20 | v1.z * v2.x - v1.x * v2.z,
21 | v1.x * v2.y - v1.y * v2.x);
22 | }
23 | }
24 |
25 | class Color {
26 | constructor(public r: number,
27 | public g: number,
28 | public b: number) {
29 | }
30 | static scale(k: number, v: Color) { return new Color(k * v.r, k * v.g, k * v.b); }
31 | static plus(v1: Color, v2: Color) { return new Color(v1.r + v2.r, v1.g + v2.g, v1.b + v2.b); }
32 | static times(v1: Color, v2: Color) { return new Color(v1.r * v2.r, v1.g * v2.g, v1.b * v2.b); }
33 | static white = new Color(1.0, 1.0, 1.0);
34 | static grey = new Color(0.5, 0.5, 0.5);
35 | static black = new Color(0.0, 0.0, 0.0);
36 | static background = Color.black;
37 | static defaultColor = Color.black;
38 | static toDrawingColor(c: Color) {
39 | return {
40 | r: floor(min(c.r, 1) * 255),
41 | g: floor(min(c.g, 1) * 255),
42 | b: floor(min(c.b, 1) * 255)
43 | }
44 | }
45 | }
46 |
47 | class Camera {
48 | public forward: Vector;
49 | public right: Vector;
50 | public up: Vector;
51 |
52 | constructor(public pos: Vector, lookAt: Vector) {
53 | var down = new Vector(0.0, -1.0, 0.0);
54 | this.forward = Vector.norm(Vector.minus(lookAt, this.pos));
55 | this.right = Vector.times(1.5, Vector.norm(Vector.cross(this.forward, down)));
56 | this.up = Vector.times(1.5, Vector.norm(Vector.cross(this.forward, this.right)));
57 | }
58 | }
59 |
60 | interface Ray {
61 | start: Vector;
62 | dir: Vector;
63 | }
64 |
65 | interface Intersection {
66 | thing: Thing;
67 | ray: Ray;
68 | dist: number;
69 | }
70 |
71 | interface Surface {
72 | diffuse: (pos: Vector) => Color;
73 | specular: (pos: Vector) => Color;
74 | reflect: (pos: Vector) => number;
75 | roughness: number;
76 | }
77 |
78 | interface Thing {
79 | intersect: (ray: Ray) => Intersection;
80 | normal: (pos: Vector) => Vector;
81 | surface: Surface;
82 | }
83 |
84 | interface Light {
85 | pos: Vector;
86 | color: Color;
87 | }
88 |
89 | interface Scene {
90 | things: Thing[];
91 | lights: Light[];
92 | camera: Camera;
93 | }
94 |
95 | class Sphere implements Thing {
96 | public radius2: number;
97 |
98 | constructor(public center: Vector, radius: number, public surface: Surface) {
99 | this.radius2 = radius * radius;
100 | }
101 | normal(pos: Vector): Vector { return Vector.norm(Vector.minus(pos, this.center)); }
102 | intersect(ray: Ray) {
103 | var eo = Vector.minus(this.center, ray.start);
104 | var v = Vector.dot(eo, ray.dir);
105 | var dist = 0;
106 | if (v >= 0) {
107 | var disc = this.radius2 - (Vector.dot(eo, eo) - v * v);
108 | if (disc >= 0) {
109 | dist = v - Math.sqrt(disc);
110 | }
111 | }
112 | if (dist === 0) {
113 | return null;
114 | } else {
115 | return { thing: this, ray: ray, dist: dist };
116 | }
117 | }
118 | }
119 |
120 | class Plane implements Thing {
121 | constructor(public norm: Vector, public offset: number, public surface: Surface) {}
122 | normal(_pos: Vector) { return this.norm; }
123 | intersect(ray: Ray): Intersection {
124 | var denom = Vector.dot(this.norm, ray.dir);
125 | if (denom > 0) {
126 | return null;
127 | } else {
128 | var dist = (Vector.dot(this.norm, ray.start) + this.offset) / (-denom);
129 | return { thing: this, ray: ray, dist: dist };
130 | }
131 | }
132 | }
133 |
134 | class Surfaces {
135 | static shiny: Surface = {
136 | diffuse: function(pos) { return Color.white; },
137 | specular: function(pos) { return Color.grey; },
138 | reflect: function(pos) { return 0.7; },
139 | roughness: 250
140 | }
141 | static checkerboard: Surface = {
142 | diffuse: function(pos) {
143 | if ((Math.floor(pos.z) + Math.floor(pos.x)) % 2 !== 0) {
144 | return Color.white;
145 | } else {
146 | return Color.black;
147 | }
148 | },
149 | specular: function(pos) { return Color.white; },
150 | reflect: function(pos) {
151 | if ((Math.floor(pos.z) + Math.floor(pos.x)) % 2 !== 0) {
152 | return 0.1;
153 | } else {
154 | return 0.7;
155 | }
156 | },
157 | roughness: 150
158 | }
159 | }
160 |
161 |
162 | class RayTracer {
163 | private maxDepth = 5;
164 |
165 | private intersections(ray: Ray, scene: Scene) {
166 | var closest = +Infinity;
167 | var closestInter: Intersection = undefined;
168 | for (const thing of scene.things) {
169 | var inter = thing.intersect(ray);
170 | if (inter != null && inter.dist < closest) {
171 | closestInter = inter;
172 | closest = inter.dist;
173 | }
174 | }
175 | return closestInter;
176 | }
177 |
178 | private testRay(ray: Ray, scene: Scene) {
179 | var isect = this.intersections(ray, scene);
180 | if (isect != null) {
181 | return isect.dist;
182 | } else {
183 | return undefined;
184 | }
185 | }
186 |
187 | private traceRay(ray: Ray, scene: Scene, depth: number): Color {
188 | var isect = this.intersections(ray, scene);
189 | if (isect == null) {
190 | return Color.background;
191 | } else {
192 | return this.shade(isect, scene, depth);
193 | }
194 | }
195 |
196 | private shade(isect: Intersection, scene: Scene, depth: number) {
197 | var d = isect.ray.dir;
198 | var pos = Vector.plus(Vector.times(isect.dist, d), isect.ray.start);
199 | var normal = isect.thing.normal(pos);
200 | var reflectDir = Vector.minus(d, Vector.times(2, Vector.times(Vector.dot(normal, d), normal)));
201 | var naturalColor = Color.plus(Color.background,
202 | this.getNaturalColor(isect.thing, pos, normal, reflectDir, scene));
203 | var reflectedColor = (depth >= this.maxDepth) ? Color.grey : this.getReflectionColor(isect.thing, pos, normal, reflectDir, scene, depth);
204 | return Color.plus(naturalColor, reflectedColor);
205 | }
206 |
207 | private getReflectionColor(thing: Thing, pos: Vector, normal: Vector, rd: Vector, scene: Scene, depth: number) {
208 | return Color.scale(thing.surface.reflect(pos), this.traceRay({ start: pos, dir: rd }, scene, depth + 1));
209 | }
210 |
211 | private getNaturalColor(thing: Thing, pos: Vector, norm: Vector, rd: Vector, scene: Scene) {
212 | var addLight = (col, light) => {
213 | var ldis = Vector.minus(light.pos, pos);
214 | var livec = Vector.norm(ldis);
215 | var neatIsect = this.testRay({ start: pos, dir: livec }, scene);
216 | var isInShadow = (neatIsect === undefined) ? false : (neatIsect <= Vector.mag(ldis));
217 | if (isInShadow) {
218 | return col;
219 | } else {
220 | var illum = Vector.dot(livec, norm);
221 | var lcolor = (illum > 0) ? Color.scale(illum, light.color)
222 | : Color.defaultColor;
223 | var specular = Vector.dot(livec, Vector.norm(rd));
224 | var scolor = (specular > 0) ? Color.scale(Math.pow(specular, thing.surface.roughness), light.color)
225 | : Color.defaultColor;
226 | return Color.plus(col, Color.plus(Color.times(thing.surface.diffuse(pos), lcolor),
227 | Color.times(thing.surface.specular(pos), scolor)));
228 | }
229 | }
230 | return scene.lights.reduce(addLight, Color.defaultColor);
231 | }
232 |
233 | render(scene, ctx, screenWidth, screenHeight) {
234 | var getPoint = (x, y, camera) => {
235 | var recenterX = x =>(x - (screenWidth / 2.0)) / 2.0 / screenWidth;
236 | var recenterY = y => - (y - (screenHeight / 2.0)) / 2.0 / screenHeight;
237 | return Vector.norm(Vector.plus(camera.forward, Vector.plus(Vector.times(recenterX(x), camera.right), Vector.times(recenterY(y), camera.up))));
238 | }
239 | const { camera } = scene;
240 | for (var y = 0; y < screenHeight; y++) {
241 | for (var x = 0; x < screenWidth; x++) {
242 | var color = this.traceRay({ start: camera.pos, dir: getPoint(x, y, camera) }, scene, 0);
243 | const { r, g, b } = Color.toDrawingColor(color);
244 | ctx.fillStyle = `rgb(${r},${g},${b})`;
245 | ctx.fillRect(x, y, 1, 1);
246 | }
247 | }
248 | }
249 | }
250 |
251 |
252 | function defaultScene(): Scene {
253 | return {
254 | things: [new Plane(new Vector(0.0, 1.0, 0.0), 0.0, Surfaces.checkerboard),
255 | new Sphere(new Vector(0.0, 1.0, -0.25), 1.0, Surfaces.shiny),
256 | new Sphere(new Vector(-1.0, 0.5, 1.5), 0.5, Surfaces.shiny)],
257 | lights: [{ pos: new Vector(-2.0, 2.5, 0.0), color: new Color(0.49, 0.07, 0.07) },
258 | { pos: new Vector(1.5, 2.5, 1.5), color: new Color(0.07, 0.07, 0.49) },
259 | { pos: new Vector(1.5, 2.5, -1.5), color: new Color(0.07, 0.49, 0.071) },
260 | { pos: new Vector(0.0, 3.5, 0.0), color: new Color(0.21, 0.21, 0.35) }],
261 | camera: new Camera(new Vector(3.0, 2.0, 4.0), new Vector(-1.0, 0.5, 0.0))
262 | };
263 | }
264 |
265 |
266 | const exec = (width: number, height: number) => {
267 | const canv = document.createElement("canvas");
268 | canv.width = width;
269 | canv.height = height;
270 | (new RayTracer).render(defaultScene(), canv.getContext("2d"), width, height);
271 | document.body.appendChild(canv);
272 | };
273 |
274 |
275 | const size = 256;
276 | const render = () => new Promise(resolve => {
277 | requestAnimationFrame(() => {
278 | console.time('raytrace TS');
279 | exec(size, size);
280 | console.timeEnd('raytrace TS');
281 | resolve();
282 | });
283 | });
284 |
285 | render()
286 | .then(render)
287 | .then(render)
288 | .then(render)
289 | .then(render)
290 | .then(render)
291 | .then(() => {
292 | document.documentElement.classList.add('done');
293 | });
294 |
--------------------------------------------------------------------------------
/rt.vanilla.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Raytracer JS
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/rt.vanilla.js:
--------------------------------------------------------------------------------
1 | const { floor, min, sqrt } = Math;
2 |
3 | const vector = (x, y, z) => ({ x, y, z });
4 | const Vector = {
5 | times: (k, { x, y, z }) => vector(k * x, k * y, k * z),
6 | minus: (v1, v2) => vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z),
7 | plus: (v1, v2) => vector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z),
8 | dot: (v1, v2) => v1.x * v2.x + v1.y * v2.y + v1.z * v2.z,
9 | mag: ({ x, y, z }) => sqrt(x * x + y * y + z * z),
10 | norm: v => {
11 | const mag = Vector.mag(v);
12 | const div = (mag === 0) ? Infinity : 1.0 / mag;
13 | return Vector.times(div, v);
14 | },
15 | cross: (v1, v2) => vector(
16 | v1.y * v2.z - v1.z * v2.y,
17 | v1.z * v2.x - v1.x * v2.z,
18 | v1.x * v2.y - v1.y * v2.x
19 | )
20 | };
21 |
22 | const color = (r, g, b) => ({ r, g, b });
23 | const black = color(0.0, 0.0, 0.0);
24 | const Color = {
25 | black,
26 | background: black,
27 | defaultColor: black,
28 | white: color(1.0, 1.0, 1.0),
29 | grey: color(0.5, 0.5, 0.5),
30 | scale: (k, { r, g, b }) => color(k * r, k * g, k * b),
31 | plus: (v1, v2) => color(v1.r + v2.r, v1.g + v2.g, v1.b + v2.b),
32 | times: (v1, v2) => color(v1.r * v2.r, v1.g * v2.g, v1.b * v2.b),
33 | toDrawingColor: ({ r, g, b }) => color(
34 | floor(min(r, 1) * 255),
35 | floor(min(g, 1) * 255),
36 | floor(min(b, 1) * 255)
37 | )
38 | };
39 |
40 | const camera = (pos, lookAt) => {
41 | const forward = Vector.norm(Vector.minus(lookAt, pos));
42 | const right = Vector.times(1.5, Vector.norm(Vector.cross(forward, vector(0.0, -1.0, 0.0))));
43 | return { pos, forward, right, up: Vector.times(1.5, Vector.norm(Vector.cross(forward, right))) };
44 | };
45 |
46 | const geo = (thing, ray, dist) => ({ thing, ray, dist });
47 | class Sphere {
48 | constructor(center, radius, surface) {
49 | this.center = center;
50 | this.surface = surface;
51 | this.radius2 = radius * radius;
52 | }
53 | normal(pos) {
54 | return Vector.norm(Vector.minus(pos, this.center));
55 | }
56 | intersect(r) {
57 | const eo = Vector.minus(this.center, r.start);
58 | const v = Vector.dot(eo, r.dir);
59 | let dist = 0;
60 | if (v >= 0) {
61 | const disc = this.radius2 - (Vector.dot(eo, eo) - v * v);
62 | if (disc >= 0)
63 | dist = v - sqrt(disc);
64 | }
65 | return dist === 0 ? null : geo(this, r, dist);
66 | }
67 | }
68 |
69 | class Plane {
70 | constructor(norm, offset, surface) {
71 | this.norm = norm;
72 | this.offset = offset;
73 | this.surface = surface;
74 | }
75 | normal(_) {
76 | return this.norm;
77 | }
78 | intersect(r) {
79 | const { norm, offset } = this;
80 | const denom = Vector.dot(norm, r.dir);
81 | return denom > 0 ? null : geo(this, r, (Vector.dot(norm, r.start) + offset) / (-denom));
82 | }
83 | }
84 |
85 | const Surfaces = {
86 | shiny: {
87 | diffuse: (_) => Color.white,
88 | specular: (_) => Color.grey,
89 | reflect: (_) => 0.7,
90 | roughness: 250
91 | },
92 | checkerboard: {
93 | diffuse: ({ x, z }) => (floor(z) + floor(x)) % 2 !== 0 ? Color.white : Color.black,
94 | specular: (_) => Color.white,
95 | reflect: ({ x, z }) => (floor(z) + floor(x)) % 2 !== 0 ? 0.1 : 0.7,
96 | roughness: 150
97 | }
98 | };
99 |
100 | const ray = (start, dir) => ({ start, dir });
101 | class RayTracer {
102 | constructor() {
103 | this.maxDepth = 5;
104 | }
105 | intersections(r, scene) {
106 | let closest = +Infinity;
107 | let closestInter = null;
108 | for (const thing of scene.things) {
109 | const inter = thing.intersect(r);
110 | if (inter != null && inter.dist < closest) {
111 | closestInter = inter;
112 | closest = inter.dist;
113 | }
114 | }
115 | return closestInter;
116 | }
117 | testRay(r, scene) {
118 | return this.intersections(r, scene)?.dist;
119 | }
120 | traceRay(r, scene, depth) {
121 | const isect = this.intersections(r, scene);
122 | return isect == null ? Color.background : this.shade(isect, scene, depth);
123 | }
124 | shade({ thing, dist, ray: { start, dir } }, scene, depth) {
125 | const pos = Vector.plus(Vector.times(dist, dir), start);
126 | const normal = thing.normal(pos);
127 | const reflectDir = Vector.minus(dir, Vector.times(2, Vector.times(Vector.dot(normal, dir), normal)));
128 | return Color.plus(
129 | Color.plus(Color.background, this.getNaturalColor(thing, pos, normal, reflectDir, scene)),
130 | (depth >= this.maxDepth) ? Color.grey : this.getReflectionColor(thing, pos, normal, reflectDir, scene, depth)
131 | );
132 | }
133 | getReflectionColor({ surface }, pos, _, rd, scene, depth) {
134 | return Color.scale(surface.reflect(pos), this.traceRay(ray(pos, rd), scene, depth + 1));
135 | }
136 | getNaturalColor({ surface }, pos, norm, rd, scene) {
137 | let col = Color.defaultColor;
138 | for (const light of scene.lights) {
139 | const ldis = Vector.minus(light.pos, pos);
140 | const livec = Vector.norm(ldis);
141 | const neatIsect = this.testRay(ray(pos, livec), scene);
142 | if (neatIsect != null && neatIsect <= Vector.mag(ldis))
143 | continue;
144 | const illum = Vector.dot(livec, norm);
145 | const lcolor = illum > 0 ? Color.scale(illum, light.color) : Color.defaultColor;
146 | const specular = Vector.dot(livec, Vector.norm(rd));
147 | const scolor = specular > 0 ?
148 | Color.scale(Math.pow(specular, surface.roughness), light.color) :
149 | Color.defaultColor;
150 | col = Color.plus(col, Color.plus(Color.times(surface.diffuse(pos), lcolor), Color.times(surface.specular(pos), scolor)));
151 | }
152 | return col;
153 | }
154 | render(scene, ctx, screenWidth, screenHeight) {
155 | const recenterX = x => (x - (screenWidth / 2.0)) / 2.0 / screenWidth;
156 | const recenterY = y => -(y - (screenHeight / 2.0)) / 2.0 / screenHeight;
157 | const getPoint = (x, y, camera) => Vector.norm(
158 | Vector.plus(camera.forward, Vector.plus(
159 | Vector.times(recenterX(x), camera.right),
160 | Vector.times(recenterY(y), camera.up)
161 | ))
162 | );
163 | const { camera } = scene;
164 | for (let y = 0; y < screenHeight; y++) {
165 | for (let x = 0; x < screenWidth; x++) {
166 | const { r, g, b } = Color.toDrawingColor(
167 | this.traceRay(ray(camera.pos, getPoint(x, y, camera)), scene, 0)
168 | );
169 | ctx.fillStyle = `rgb(${r},${g},${b})`;
170 | ctx.fillRect(x, y, 1, 1);
171 | }
172 | }
173 | }
174 | }
175 |
176 | const light = (pos, color) => ({ pos, color });
177 | const defaultScene = () => ({
178 | things: [
179 | new Plane(vector(0.0, 1.0, 0.0), 0.0, Surfaces.checkerboard),
180 | new Sphere(vector(0.0, 1.0, -0.25), 1.0, Surfaces.shiny),
181 | new Sphere(vector(-1.0, 0.5, 1.5), 0.5, Surfaces.shiny)
182 | ],
183 | lights: [
184 | light(vector(-2.0, 2.5, 0.0), color(0.49, 0.07, 0.07)),
185 | light(vector(1.5, 2.5, 1.5), color(0.07, 0.07, 0.49)),
186 | light(vector(1.5, 2.5, -1.5), color(0.07, 0.49, 0.071)),
187 | light(vector(0.0, 3.5, 0.0), color(0.21, 0.21, 0.35))
188 | ],
189 | camera: camera(vector(3.0, 2.0, 4.0), vector(-1.0, 0.5, 0.0))
190 | });
191 |
192 | const exec = (width, height) => {
193 | const canv = document.createElement("canvas");
194 | canv.width = width;
195 | canv.height = height;
196 | (new RayTracer).render(defaultScene(), canv.getContext("2d"), width, height);
197 | document.body.appendChild(canv);
198 | };
199 |
200 | const size = 256;
201 | const render = () => new Promise(resolve => {
202 | requestAnimationFrame(() => {
203 | console.time('raytrace JS');
204 | exec(size, size);
205 | console.timeEnd('raytrace JS');
206 | resolve();
207 | });
208 | });
209 |
210 | render()
211 | .then(render)
212 | .then(render)
213 | .then(render)
214 | .then(render)
215 | .then(render)
216 | .then(() => {
217 | document.documentElement.classList.add('done');
218 | });
219 |
--------------------------------------------------------------------------------
/trace.ts.js:
--------------------------------------------------------------------------------
1 | import('./dummy.dom.js').then(() => import('./raytracer.js'));
2 |
--------------------------------------------------------------------------------
/trace.vanilla.js:
--------------------------------------------------------------------------------
1 | import('./dummy.dom.js').then(() => import('./rt.vanilla.js'));
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["DOM", "ES2022"],
4 | "module": "ES2022",
5 | "target": "ES2022",
6 | "useDefineForClassFields": false,
7 | "sourceMap": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------