├── .gitignore ├── assembly ├── Intersection.ts ├── tsconfig.json ├── Pixel.ts ├── Sphere.ts ├── Vector3.ts ├── index.ts └── RayTracer.ts ├── package.json └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | npm-debug.log 4 | .env 5 | .DS_Store -------------------------------------------------------------------------------- /assembly/Intersection.ts: -------------------------------------------------------------------------------- 1 | export class Intersection { 2 | t0: f64; 3 | t1: f64; 4 | } 5 | -------------------------------------------------------------------------------- /assembly/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../node_modules/assemblyscript/std/assembly.json", 3 | "include": [ 4 | "./**/*.ts" 5 | ] 6 | } -------------------------------------------------------------------------------- /assembly/Pixel.ts: -------------------------------------------------------------------------------- 1 | import { RayTracer } from './RayTracer'; 2 | 3 | declare namespace console { 4 | function debug(val: number): void; 5 | } 6 | 7 | @unmanaged 8 | export class Pixel { 9 | r: u8; 10 | g: u8; 11 | b: u8; 12 | a: u8; 13 | } 14 | 15 | export function getPixel(x: i32, y: i32): Pixel { 16 | return changetype(getPixelOffset(x, y)); 17 | } 18 | 19 | export function getPixelOffset(x: i32, y: i32): i32 { 20 | return ( 21 | (RayTracer.renderBufferWidth * y + x) * sizeof() + 22 | RayTracer.renderBufferStartOffset 23 | ); 24 | } 25 | 26 | export function setPixel( 27 | x: i32, 28 | y: i32, 29 | r: u8, 30 | g: u8, 31 | b: u8, 32 | a: u8 = 255, 33 | ): void { 34 | // store(getPixelOffset(x, y), (255 << 24) | (b << 16) | (g << 8) | r); 35 | let pixel = getPixel(x, y); 36 | pixel.r = r; 37 | pixel.g = g; 38 | pixel.b = b; 39 | pixel.a = 255; 40 | } 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "assembly-script-ray-tracer", 3 | "version": "1.0.0", 4 | "description": "A RayTracer built using AssemblyScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --sourceMap --validate --importMemory --memoryBase 2000000 --use Math=JSMath", 8 | "asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --sourceMap --validate -O3 --runPasses --noDebug --importMemory --memoryBase 2000000 --use Math=JSMath", 9 | "asbuild": "npm run asbuild:untouched && npm run asbuild:optimized", 10 | "server": "http-server . -o -c-1" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "assemblyscript": "github:AssemblyScript/assemblyscript" 16 | }, 17 | "dependencies": { 18 | "glob": "^7.1.2", 19 | "http-server": "^0.11.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /assembly/Sphere.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from './Vector3'; 2 | import { Intersection } from './Intersection'; 3 | declare namespace console { 4 | function debug(val: number): void; 5 | } 6 | 7 | export class Sphere { 8 | public radius2: f64; 9 | 10 | constructor( 11 | public center: Vector3, 12 | public radius: f64, 13 | public surfaceColor: Vector3, 14 | public reflection: f64, 15 | public transparency: f64, 16 | public emissionColor: Vector3, 17 | ) { 18 | this.radius2 = radius * radius; 19 | } 20 | 21 | normalize(point: Vector3): Vector3 { 22 | return point.clone().subtract(this.center).normalize(); 23 | } 24 | 25 | intersect(rayOrigin: Vector3, rayDirection: Vector3): Intersection | null { 26 | let l: Vector3 = this.center.clone().subtract(rayOrigin); 27 | let tca: f64 = l.dotProduct(rayDirection); 28 | 29 | if (tca < 0) { 30 | free_memory(changetype(l)); 31 | return null; 32 | } 33 | 34 | let d2: f64 = l.dotProduct(l) - tca * tca; 35 | 36 | if (d2 > this.radius2) { 37 | free_memory(changetype(l)); 38 | return null; 39 | } 40 | 41 | let thc = sqrt(this.radius2 - d2); 42 | free_memory(changetype(l)); 43 | return { 44 | t0: tca - thc, 45 | t1: tca + thc, 46 | }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /assembly/Vector3.ts: -------------------------------------------------------------------------------- 1 | declare namespace console { 2 | function debug(val: number): void; 3 | } 4 | export class Vector3 { 5 | constructor(public x: f64 = 0, public y: f64 = 0, public z: f64 = 0) { 6 | } 7 | 8 | product(v2: Vector3): Vector3 { 9 | this.x *= v2.x; 10 | this.y *= v2.y; 11 | this.z *= v2.z; 12 | return this; 13 | } 14 | 15 | multiply(scalar: f64): Vector3 { 16 | this.x *= scalar; 17 | this.y *= scalar; 18 | this.z *= scalar; 19 | return this; 20 | } 21 | 22 | clone(): Vector3 { 23 | return new Vector3(this.x, this.y, this.z); 24 | } 25 | 26 | length2(): f64 { 27 | return this.x * this.x + this.y * this.y + this.z * this.z; 28 | } 29 | 30 | length(): f64 { 31 | return sqrt(this.length2()); 32 | } 33 | 34 | normalize(): Vector3 { 35 | let len2 = this.length2(); 36 | let len2Sqrt = sqrt(len2); 37 | 38 | if (len2 > 0) { 39 | this.x /= len2Sqrt; 40 | this.y /= len2Sqrt; 41 | this.z /= len2Sqrt; 42 | } 43 | return this; 44 | } 45 | 46 | dotProduct(v2: Vector3): f64 { 47 | return this.x * v2.x + this.y * v2.y + this.z * v2.z; 48 | } 49 | 50 | add(v2: Vector3): Vector3 { 51 | this.x += v2.x; 52 | this.y += v2.y; 53 | this.z += v2.z; 54 | return this; 55 | } 56 | 57 | subtract(v2: Vector3): Vector3 { 58 | this.x -= v2.x; 59 | this.y -= v2.y; 60 | this.z -= v2.z; 61 | return this; 62 | } 63 | 64 | reverse(): Vector3 { 65 | this.x = -this.x; 66 | this.y = -this.y; 67 | this.z = -this.z; 68 | return this; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

RayTracer.js - Example 1 5 |
Simple Scene [640x480]

6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 |
15 |
16 | 17 |
18 | 19 | 20 | 21 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /assembly/index.ts: -------------------------------------------------------------------------------- 1 | import 'allocator/tlsf'; 2 | import { RayTracer } from './RayTracer'; 3 | import { Sphere } from './Sphere'; 4 | import { Vector3 } from './Vector3'; 5 | declare namespace console { 6 | function debug(val: number): void; 7 | } 8 | 9 | let elements: Sphere[] = []; 10 | 11 | export function init(width: i32 = 640, height: i32 = 480): void { 12 | RayTracer.renderBufferWidth = width; 13 | RayTracer.renderBufferHeight = height; 14 | RayTracer.renderBufferLength = width * height * sizeof(); 15 | 16 | elements.push( 17 | new Sphere( 18 | new Vector3(0.0, -10004, -20), 19 | 10000, 20 | new Vector3(0.2, 0.2, 0.2), 21 | 0, 22 | 0, 23 | new Vector3(), 24 | ), 25 | ); 26 | 27 | elements.push( 28 | new Sphere( 29 | new Vector3(0, 0, -20), 30 | 4, 31 | new Vector3(1.0, 0.32, 0.36), 32 | 1, 33 | 0.5, 34 | new Vector3(), 35 | ), 36 | ); 37 | elements.push( 38 | new Sphere( 39 | new Vector3(5, -1, -15), 40 | 2, 41 | new Vector3(0.9, 0.76, 0.46), 42 | 1, 43 | 0, 44 | new Vector3(), 45 | ), 46 | ); 47 | elements.push( 48 | new Sphere( 49 | new Vector3(5, 0, -25), 50 | 3, 51 | new Vector3(0.65, 0.77, 0.97), 52 | 1, 53 | 0, 54 | new Vector3(), 55 | ), 56 | ); 57 | elements.push( 58 | new Sphere( 59 | new Vector3(-5.5, 0, -15), 60 | 3, 61 | new Vector3(0.9, 0.9, 0.9), 62 | 1, 63 | 0, 64 | new Vector3(), 65 | ), 66 | ); 67 | 68 | elements.push( 69 | new Sphere( 70 | new Vector3(0, 20, -30), 71 | 3, 72 | new Vector3(), 73 | 0, 74 | 0, 75 | new Vector3(1.2, 1.2, 1.2), 76 | ), 77 | ); 78 | elements.push( 79 | new Sphere( 80 | new Vector3(0, 10, 10), 81 | 3, 82 | new Vector3(), 83 | 0, 84 | 0, 85 | new Vector3(1, 1, 1), 86 | ), 87 | ); 88 | let backgroundColor: Vector3 = new Vector3(2.0, 2.0, 2.0); 89 | 90 | RayTracer.render(640, 480, elements, backgroundColor); 91 | } 92 | 93 | // export function addSphere(): void { 94 | // let toto: Pixel = new Pixel(); 95 | // toto.b = 50; 96 | // toto.r = 50; 97 | // toto.g = 50; 98 | // console.debug(changetype(toto)); 99 | // } 100 | 101 | // export function test(): u8 { 102 | // setPixel(0, 0); 103 | // let toto: Pixel = getPixel(0, 0); 104 | // // toto.b = 255; 105 | // // toto.r = 255; 106 | // // toto.g = 255; 107 | // console.debug(changetype(toto)); 108 | // return toto.r; 109 | // } 110 | -------------------------------------------------------------------------------- /assembly/RayTracer.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from './Vector3'; 2 | import { Sphere } from './Sphere'; 3 | import { Intersection } from './Intersection'; 4 | import { setPixel } from './Pixel'; 5 | 6 | declare namespace console { 7 | function debug(val: number): void; 8 | } 9 | 10 | export class RayTracer { 11 | static renderBufferStartOffset: i32 = 0; 12 | static renderBufferWidth: i32 = 640; 13 | static renderBufferHeight: i32 = 480; 14 | static renderBufferLength: i32 = 15 | RayTracer.renderBufferHeight * RayTracer.renderBufferWidth * sizeof(); 16 | 17 | static trace( 18 | rayOrigin: Vector3, 19 | rayDirection: Vector3, 20 | depth: f64, 21 | elements: Sphere[], 22 | backgroundColor: Vector3, 23 | ): Vector3 { 24 | let tnear: f64 = Infinity; 25 | let sphere: Sphere | null = null; 26 | 27 | for (let i = 0; i < elements.length; i++) { 28 | let hitInfo: Intersection | null = elements[i].intersect( 29 | rayOrigin, 30 | rayDirection, 31 | ); 32 | 33 | if (hitInfo) { 34 | if (hitInfo.t0 < 0) { 35 | hitInfo.t0 = hitInfo.t1; 36 | } 37 | if (hitInfo.t0 < tnear) { 38 | tnear = hitInfo.t0; 39 | sphere = elements[i]; 40 | } 41 | } 42 | free_memory(changetype(hitInfo)); 43 | } 44 | 45 | if (!sphere) { 46 | return backgroundColor.clone(); 47 | } 48 | 49 | let surfaceColor = new Vector3(); 50 | let tmp = rayDirection.clone(); 51 | let intersectionPoint = rayOrigin.clone().add(tmp.multiply(tnear)); 52 | free_memory(changetype(tmp)); 53 | let intersectionNormal = sphere.normalize(intersectionPoint); 54 | 55 | let bias = 1e-4; 56 | 57 | let inside = false; 58 | 59 | if (rayDirection.dotProduct(intersectionNormal) > 0) { 60 | intersectionNormal.reverse(); 61 | inside = true; 62 | } 63 | 64 | for (let i = 0; i < elements.length; i++) { 65 | if ( 66 | elements[i].emissionColor.x > 0 || 67 | elements[i].emissionColor.y > 0 || 68 | elements[i].emissionColor.z > 0 69 | ) { 70 | let transmission: Vector3 = new Vector3(1, 1, 1); 71 | 72 | let tmp3 = elements[i].center.clone(); 73 | 74 | let lightDirection = elements[i].center 75 | .clone() 76 | .subtract(intersectionPoint) 77 | .normalize(); 78 | 79 | for (let j = 0; j < elements.length; j++) { 80 | if (i !== j) { 81 | let tmp = intersectionPoint.clone(); 82 | let tmp2 = intersectionNormal.clone(); 83 | 84 | let hitInfo: Intersection | null = elements[j].intersect( 85 | tmp.add(tmp2.multiply(bias)), 86 | lightDirection, 87 | ); 88 | if (hitInfo) { 89 | transmission.x = 0; 90 | transmission.y = 0; 91 | transmission.z = 0; 92 | free_memory(changetype(hitInfo)); 93 | break; 94 | } 95 | free_memory(changetype(tmp)); 96 | free_memory(changetype(tmp2)); 97 | } 98 | } 99 | 100 | let lightRatio = max(0, intersectionNormal.dotProduct(lightDirection)); 101 | let tmp = sphere.surfaceColor.clone(); 102 | let tmp2 = elements[i].emissionColor.clone(); 103 | 104 | surfaceColor.add( 105 | tmp.product(transmission).product(tmp2.multiply(lightRatio)), 106 | ); 107 | 108 | free_memory(changetype(lightDirection)); 109 | free_memory(changetype(transmission)); 110 | free_memory(changetype(tmp)); 111 | free_memory(changetype(tmp2)); 112 | } 113 | } 114 | 115 | surfaceColor.add(sphere.emissionColor); 116 | free_memory(changetype(intersectionNormal)); 117 | return surfaceColor; 118 | } 119 | 120 | static render( 121 | width: i32, 122 | height: i32, 123 | elements: Sphere[], 124 | backgroundColor: Vector3, 125 | ): void { 126 | let invWidth: f64 = 1 / width; 127 | let invHeight: f64 = 1 / height; 128 | 129 | let fov: i32 = 30; 130 | let aspectRatio: f64 = width / height; 131 | console.debug(aspectRatio); 132 | let angle: f64 = Math.tan((Math.PI * 0.5 * fov) / 180); 133 | let rayOrigin: Vector3 = new Vector3(); 134 | 135 | for (let y: i32 = 0; y < height; y++) { 136 | for (let x: i32 = 0; x < width; x++) { 137 | let xx: f64 = (2 * ((x + 0.5) * invWidth) - 1) * angle * aspectRatio; 138 | let yy: f64 = (1 - 2 * ((y + 0.5) * invHeight)) * angle; 139 | 140 | let rayDir: Vector3 = new Vector3(xx, yy, -1.0); 141 | 142 | rayDir.normalize(); 143 | 144 | let pixelColor = RayTracer.trace( 145 | rayOrigin, 146 | rayDir, 147 | 0, 148 | elements, 149 | backgroundColor, 150 | ); 151 | // convert pixel to bytes 152 | let r = nearest(min(1, pixelColor.x) * 255); 153 | let g = nearest(min(1, pixelColor.y) * 255); 154 | let b = nearest(min(1, pixelColor.z) * 255); 155 | setPixel(x, y, r, g, b); 156 | free_memory(changetype(rayDir)); 157 | free_memory(changetype(pixelColor)); 158 | } 159 | } 160 | free_memory(changetype(rayOrigin)); 161 | } 162 | } 163 | --------------------------------------------------------------------------------