Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html>
- <head>
- <title>Camera position demo - Computer Graphics from scratch</title>
- </head>
- <body>
- <div class="centered">
- <canvas id="canvas" width=600 height=600 style="border: 1px grey solid"></canvas>
- </div>
- <script>
- // ======================================================================
- // Low-level canvas access.
- // ======================================================================
- var canvas = document.getElementById("canvas");
- var canvas_context = canvas.getContext("2d");
- var canvas_buffer = canvas_context.getImageData(0, 0, canvas.width, canvas.height);
- var canvas_pitch = canvas_buffer.width * 4;
- // The PutPixel() function.
- var PutPixel = function(x, y, color) {
- x = canvas.width / 2 + x;
- y = canvas.height / 2 - y - 1;
- if (x < 0 || x >= canvas.width || y < 0 || y >= canvas.height) {
- return;
- }
- var offset = 4 * x + canvas_pitch * y;
- canvas_buffer.data[offset++] = color[0];
- canvas_buffer.data[offset++] = color[1];
- canvas_buffer.data[offset++] = color[2];
- canvas_buffer.data[offset++] = 255; // Alpha = 255 (full opacity)
- }
- // Displays the contents of the offscreen buffer into the canvas.
- var UpdateCanvas = function() {
- canvas_context.putImageData(canvas_buffer, 0, 0);
- }
- // ======================================================================
- // Linear algebra and helpers.
- // ======================================================================
- // Conceptually, an "infinitesimally small" real number.
- var EPSILON = 0.001;
- // Dot product of two 3D vectors.
- var DotProduct = function (v1, v2) {
- return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
- }
- // Length of a 3D vector.
- var Length = function (vec) {
- return Math.sqrt(DotProduct(vec, vec));
- }
- // Multiplies a scalar and a vector.
- var MultiplySV = function (k, vec) {
- return [k * vec[0], k * vec[1], k * vec[2]];
- }
- // Multiplies a matrix and a vector.
- var MultiplyMV = function (mat, vec) {
- var result = [0, 0, 0];
- for (var i = 0; i < 3; i++) {
- for (var j = 0; j < 3; j++) {
- result[i] += vec[j] * mat[i][j];
- }
- }
- return result;
- }
- // Computes v1 + v2.
- var Add = function (v1, v2) {
- return [v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2]];
- }
- // Computes v1 - v2.
- var Subtract = function (v1, v2) {
- return [v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]];
- }
- // Clamps a color to the canonical color range.
- var Clamp = function (vec) {
- return [
- Math.min(255, Math.max(0, vec[0])),
- Math.min(255, Math.max(0, vec[1])),
- Math.min(255, Math.max(0, vec[2]))
- ];
- }
- // Computes the reflection of v1 respect to v2.
- var ReflectRay = function (v1, v2) {
- return Subtract(MultiplySV(2 * DotProduct(v1, v2), v2), v1);
- }
- // ======================================================================
- // A raytracer with diffuse and specular illumination, shadows and reflections,
- // arbitrary camera position and orientation.
- // ======================================================================
- // A Sphere.
- var Sphere = function (center, radius, color, specular, reflective, refractive, refractive_index) {
- this.center = center;
- this.radius = radius;
- this.color = color;
- this.specular = specular;
- this.reflective = reflective;
- this.refractive = refractive;
- this.refractive_index = refractive_index; // add this
- }
- // A Light.
- var Light = function (ltype, intensity, position) {
- this.ltype = ltype;
- this.intensity = intensity;
- this.position = position;
- }
- Light.AMBIENT = 0;
- Light.POINT = 1;
- Light.DIRECTIONAL = 2;
- // Scene setup.
- var viewport_size = 1;
- var projection_plane_z = 1;
- var camera_position = [3, 0, 1];
- var camera_rotation = [
- [0.7071, 0, -0.7071],
- [0, 1, 0],
- [0.7071, 0, 0.7071]
- ];
- var background_color = [0, 0, 0];
- var spheres = [
- new Sphere([0, -1, 3], 1, [255, 0, 0], 500, 0.2, true, 1.9),
- new Sphere([2, 0, 4], 1, [0, 0, 255], 500, 0.3, false, 1.0),
- new Sphere([-2, 0, 4], 1, [0, 255, 0], 10, 0.4, false, 1.0),
- new Sphere([0, -5001, 0], 5000, [255, 255, 0], 1000, 0.5, false, 1.0)
- ];
- var lights = [
- new Light(Light.AMBIENT, 0.2),
- new Light(Light.POINT, 0.6, [2, 1, 0]),
- new Light(Light.DIRECTIONAL, 0.2, [1, 4, 4])
- ];
- var recursion_depth = 3;
- // Converts 2D canvas coordinates to 3D viewport coordinates.
- var CanvasToViewport = function (p2d) {
- return [
- p2d[0] * viewport_size / canvas.width,
- p2d[1] * viewport_size / canvas.height,
- projection_plane_z
- ];
- }
- // Computes the intersection of a ray and a sphere. Returns the values
- // of t for the intersections.
- var IntersectRaySphere = function (origin, direction, sphere) {
- var oc = Subtract(origin, sphere.center);
- var k1 = DotProduct(direction, direction);
- var k2 = 2 * DotProduct(oc, direction);
- var k3 = DotProduct(oc, oc) - sphere.radius * sphere.radius;
- var discriminant = k2 * k2 - 4 * k1 * k3;
- if (discriminant < 0) {
- return [Infinity, Infinity];
- }
- var t1 = (-k2 + Math.sqrt(discriminant)) / (2 * k1);
- var t2 = (-k2 - Math.sqrt(discriminant)) / (2 * k1);
- return [t1, t2];
- }
- var ComputeLighting = function (point, normal, view, specular) {
- var intensity = 0;
- var length_n = Length(normal); // Should be 1.0, but just in case...
- var length_v = Length(view);
- for (var i = 0; i < lights.length; i++) {
- var light = lights[i];
- if (light.ltype == Light.AMBIENT) {
- intensity += light.intensity;
- } else {
- var vec_l, t_max;
- if (light.ltype == Light.POINT) {
- vec_l = Subtract(light.position, point);
- t_max = 1.0;
- } else { // Light.DIRECTIONAL
- vec_l = light.position;
- t_max = Infinity;
- }
- // Shadow check.
- var blocker = ClosestIntersection(point, vec_l, EPSILON, t_max);
- if (blocker) {
- continue;
- }
- // Diffuse reflection.
- var n_dot_l = DotProduct(normal, vec_l);
- if (n_dot_l > 0) {
- intensity += light.intensity * n_dot_l / (length_n * Length(vec_l));
- }
- // Specular reflection.
- if (specular != -1) {
- var vec_r = ReflectRay(vec_l, normal);
- var r_dot_v = DotProduct(vec_r, view);
- if (r_dot_v > 0) {
- intensity += light.intensity * Math.pow(r_dot_v / (Length(vec_r) * length_v), specular);
- }
- }
- }
- }
- return intensity;
- }
- // Find the closest intersection between a ray and the spheres in the scene.
- var ClosestIntersection = function (origin, direction, min_t, max_t) {
- var closest_t = Infinity;
- var closest_sphere = null;
- for (var i = 0; i < spheres.length; i++) {
- var ts = IntersectRaySphere(origin, direction, spheres[i]);
- if (ts[0] < closest_t && min_t < ts[0] && ts[0] < max_t) {
- closest_t = ts[0];
- closest_sphere = spheres[i];
- }
- if (ts[1] < closest_t && min_t < ts[1] && ts[1] < max_t) {
- closest_t = ts[1];
- closest_sphere = spheres[i];
- }
- }
- if (closest_sphere) {
- return [closest_sphere, closest_t];
- }
- return null;
- }
- // Computes the refracted ray direction.
- var RefractRay = function (ray, normal, refractive_index) {
- var cosi = -Math.max(-1, Math.min(1, DotProduct(ray, normal)));
- var etai = 1,
- etat = refractive_index;
- var n = normal;
- if (cosi < 0) {
- cosi = -cosi;
- [etai, etat] = [etat, etai];
- n = MultiplySV(-1, normal);
- }
- var eta = etai / etat;
- var k = 1 - eta * eta * (1 - cosi * cosi);
- return k < 0 ?
- [0, 0, 0] :
- Add(MultiplySV(eta, ray), MultiplySV((eta * cosi - Math.sqrt(k)), n));
- }
- // Traces a ray against the set of spheres in the scene.
- var TraceRay = function (origin, direction, min_t, max_t, depth) {
- var intersection = ClosestIntersection(origin, direction, min_t, max_t);
- if (!intersection) {
- return background_color;
- }
- var closest_sphere = intersection[0];
- var closest_t = intersection[1];
- var point = Add(origin, MultiplySV(closest_t, direction));
- var normal = Subtract(point, closest_sphere.center);
- normal = MultiplySV(1.0 / Length(normal), normal);
- var view = MultiplySV(-1, direction);
- var lighting = ComputeLighting(point, normal, view, closest_sphere.specular);
- var local_color = MultiplySV(lighting, closest_sphere.color);
- if (depth <= 0) {
- return local_color;
- }
- var reflected_color = [0, 0, 0];
- if (closest_sphere.reflective > 0) {
- var reflected_ray = ReflectRay(view, normal);
- reflected_color = TraceRay(point, reflected_ray, EPSILON, Infinity, depth - 1);
- }
- var refracted_color = [0, 0, 0];
- if (closest_sphere.refractive) {
- var refracted_ray = RefractRay(view, normal, closest_sphere.refractive_index);
- refracted_color = TraceRay(point, refracted_ray, EPSILON, Infinity, depth - 1);
- }
- return Add(MultiplySV(1 - closest_sphere.reflective - closest_sphere.refractive, local_color),
- Add(MultiplySV(closest_sphere.reflective, reflected_color),
- MultiplySV(closest_sphere.refractive, refracted_color)));
- }
- //
- // Main loop.
- //
- for (var x = -canvas.width/2; x < canvas.width/2; x++) {
- for (var y = -canvas.height/2; y < canvas.height/2; y++) {
- var direction = CanvasToViewport([x, y])
- direction = MultiplyMV(camera_rotation, direction);
- var color = TraceRay(camera_position, direction, 1, Infinity, recursion_depth);
- PutPixel(x, y, Clamp(color));
- }
- }
- UpdateCanvas();
- </script>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement