/// <summary> /// Transforms a vector by a matrix. /// </summary> public static Vector Transform(Matrix mat, Vector vec) { return vec.X * mat.U + vec.Y * mat.V + vec.Z * mat.W; }
/// <summary> /// Creates a translation matrix. /// </summary> public static Matrix Translation(Vector vec) { return new Matrix(new Vector(1, 0, 0), new Vector(0, 1, 0), new Vector(0, 0, 1), vec); }
/// <summary> /// Creates a possibly non-uniform scaling matrix. /// </summary> public static Matrix Scaling(Vector scale) { return new Matrix(new Vector(scale.X, 0, 0), new Vector(0, scale.Y, 0), new Vector(0, 0, scale.Z), Vector.Zero); }
/// <summary> /// Constructs a ray with an origin and a direction. /// </summary> public Ray(Point origin, Vector direction) { this.origin = origin; this.direction = direction.Normalize(); }
/// <summary> /// Constructs a matrix from four column vectors. /// </summary> private Matrix(Vector u, Vector v, Vector w, Vector t) { this.u = u; this.v = v; this.w = w; this.t = t; }
/// <summary> /// Renders the scene into a pixel buffer. /// </summary> public void Render(PixelBuffer pixbuf) { float dx = 1.0f / pixbuf.Width, dy = 1.0f / pixbuf.Height; camera.AspectRatio = (float)pixbuf.Width / pixbuf.Height; // Free parallelism, why not! Note a Parallel.For loop // over each row is slightly faster but less readable. Parallel.ForEach(pixbuf, (pixel) => { var color = Vector.Zero; float u = pixel.X * dx; float v = pixel.Y * dy; var rays = new[] { camera.Trace(2 * (u - 0.25f * dx) - 1, 2 * (v - 0.25f * dy) - 1), camera.Trace(2 * (u + 0.25f * dx) - 1, 2 * (v - 0.25f * dy) - 1), camera.Trace(2 * (u - 0.25f * dx) - 1, 2 * (v + 0.25f * dy) - 1), camera.Trace(2 * (u + 0.25f * dx) - 1, 2 * (v + 0.25f * dy) - 1), }; // Trace a packet of 4 coherent AA rays var packet = scene.Intersects4(rays); // Convert the packet to a set of usable ray-geometry intersections Intersection<Model>[] hits = packet.ToIntersection<Model>(scene); for (int t = 0; t < 4; ++t) { if (hits[t].HasHit) { color += new Vector(0.1f, 0.1f, 0.1f); var ray = rays[t]; var model = hits[t].Instance; // Parse the surface normal returned and then process it manually var rawNormal = new Vector(hits[t].NX, hits[t].NY, hits[t].NZ); var normal = model.CorrectNormal(rawNormal); // Important! // Calculate the new ray towards the light source var hitPoint = ray.PointAt(hits[t].Distance); var toLight = lightPosition - hitPoint; // from A to B = B - A var lightRay = new Ray(hitPoint + normal * Constants.Epsilon, toLight); // Is the light source occluded? If so, no point calculating any lighting if (!scene.Occludes(lightRay, 0, toLight.Length())) { // Compute the Lambertian cosine term (rendering equation) float cosLight = Vector.Dot(normal, toLight.Normalize()); // Calculate the total light attenuation (inverse square law + cosine law) var attenuation = lightIntensity * cosLight / Vector.Dot(toLight, toLight); color += model.Material(hits[t].Mesh).BRDF(toLight.Normalize(), ray.Direction, normal) * attenuation; } } } // Average the 4 per-pixel samples pixbuf.SetColor(pixel, color / 4); }); }
/// <summary> /// Returns the length of a vector. /// </summary> public static float Length(Vector u) { return (float)Math.Sqrt(Dot(u, u)); }
/// <summary> /// Reflects this vector about a normal. /// </summary> public Vector Reflect(Vector n) { return Reflect(this, n); }
/// <summary> /// Returns the dot product of this vector with another. /// </summary> public float Dot(Vector v) { return Dot(this, v); }
/// <summary> /// Returns the dot product of two vectors. /// </summary> public static float Dot(Vector u, Vector v) { return u.X * v.X + u.Y * v.Y + u.Z * v.Z; }
/// <summary> /// Returns the cross product of two vectors. /// </summary> public static Vector Cross(Vector u, Vector v) { return new Vector(u.Y * v.Z - u.Z * v.Y, u.Z * v.X - u.X * v.Z, u.X * v.Y - u.Y * v.X); }
/// <summary> /// Returns the azimuth of a vector. /// </summary> /// <remarks> /// Horizontal angle, zero towards the z-axis. /// </remarks> public static float Azimuth(Vector u) { var len = Length(u); if (len > 0) return (float)Math.Atan2(u.Z, u.X); else throw new InvalidOperationException("Vector has no direction"); }
/// <summary> /// Returns the inclination of a vector. /// </summary> /// <remarks> /// Vertical angle, zero on the xz-plane. /// </remarks> public static float Inclination(Vector u) { var len = Length(u); if (len > 0) return (float)(Math.Acos(u.Y / len) - Math.PI / 2); else throw new InvalidOperationException("Vector has no direction"); }
/// <summary> /// Normalizes a vector to unit length. /// </summary> public static Vector Normalize(Vector u) { var len = Length(u); if (len > 0) return u / len; else throw new InvalidOperationException("Vector has no direction"); }
/// <summary> /// Transforms a vector by this matrix. /// </summary> public Vector Transform(Vector vec) { return Transform(this, vec); }
/// <summary> /// Returns the cross product of this vector with another. /// </summary> public Vector Cross(Vector v) { return Cross(this, v); }
/// <summary> /// Returns the inverse of a matrix. /// </summary> public static Matrix Invert(Matrix mat) { // Work out determinant of the 3x3 submatrix. var det = mat.U.X * (mat.V.Y * mat.W.Z - mat.W.Y * mat.V.Z) - mat.V.X * (mat.W.Z * mat.U.Y - mat.W.Y * mat.U.Z) + mat.W.X * (mat.U.Y * mat.V.Z - mat.V.Y * mat.U.Z); if (Math.Abs(det) < 0) throw new ArgumentException("Matrix is not invertible"); // Compute inv(submatrix) = transpose(submatrix) / det. var inv_u = new Vector(mat.U.X, mat.V.X, mat.W.X) / det; var inv_v = new Vector(mat.U.Y, mat.V.Y, mat.W.Y) / det; var inv_w = new Vector(mat.U.Z, mat.V.Z, mat.W.Z) / det; // Transform the translation column by this inverse matrix. var inv_t = -(mat.T.X * inv_u + mat.T.Y * inv_v + mat.T.Z * inv_w); return new Matrix(inv_u, inv_v, inv_w, inv_t); }
/// <summary> /// Converts a vector into a point. /// </summary> /// <remarks> /// This is meaningless mathematically. /// </remarks> public static Point ToPoint(Vector u) { return Point.Zero + u; }
/// <summary> /// Corrects an Embree.NET normal, which is unnormalized /// and in object space, to a world space normal vector. /// </summary> public Vector CorrectNormal(Vector normal) { return (inverseTranspose * normal).Normalize(); }
/// <summary> /// Reflects a vector about a normal. /// </summary> public static Vector Reflect(Vector i, Vector n) { return i - 2 * n * Dot(i, n); }