public Ray(Vector3f o, Vector3f d) { origin = o; direction = d; closestHitDistance = WORLD_MAX; closestHitObject = null; }
public Sphere(Vector3f p, number r, Color c) { position = p; radius = r; color = c; }
public abstract Vector3f GetSurfaceNormalAtPoint(Vector3f p);
public Vector3f ReflectIn(Vector3f normal) { Vector3f negVector = -this; Vector3f reflectedDir = normal * (2.0f * negVector.Dot(normal)) - negVector; return reflectedDir; }
public override Vector3f GetSurfaceNormalAtPoint(Vector3f p) { return(normal); // This is of course the same across the entire plane }
public override Vector3f GetSurfaceNormalAtPoint(Vector3f p) { return normal; // This is of course the same across the entire plane }
// given a ray, trace it into the scene and return the colour of the surface it hits // (handles reflections recursively) static Color Trace(Ray ray, int traceDepth) { // See if the ray intersected an object CheckIntersection(ref ray); if (ray.closestHitDistance >= Ray.WORLD_MAX || ray.closestHitObject == null) // No intersection return BG_COLOR; // Got a hit - set initial colour to ambient light number r = 0.15f * ray.closestHitObject.color.R; number g = 0.15f * ray.closestHitObject.color.G; number b = 0.15f * ray.closestHitObject.color.B; // Set up stuff we'll need for shading calcs Vector3f surfaceNormal = ray.closestHitObject.GetSurfaceNormalAtPoint(ray.hitPoint); Vector3f viewerDir = -ray.direction; // Direction back to the viewer (simply negative of ray dir) // Loop through the lights, adding contribution of each foreach (Light light in lights) { Vector3f lightDir = new Vector3f(); number lightDistance; // Find light direction and distance lightDir = light.position - ray.hitPoint; // Get direction to light lightDistance = lightDir.Magnitude(); //lightDir = lightDir / lightDistance; // Light exponential falloff lightDir.Normalise(); // Shadow check: check if this light's visible from the point // NB: Step out slightly from the hitpoint first Ray shadowRay = new Ray(ray.hitPoint + (lightDir * TINY), lightDir); shadowRay.closestHitDistance = lightDistance; // IMPORTANT: We only want it to trace as far as the light! CheckIntersection(ref shadowRay); if (shadowRay.closestHitObject != null) // We hit something -- ignore this light entirely continue; number cosLightAngleWithNormal = surfaceNormal.Dot(lightDir); if (MATERIAL_DIFFUSE_COEFFICIENT > TINY) { // Calculate light's diffuse component - note that this is view independant // Dot product of surface normal and light direction gives cos of angle between them so will be in // range -1 to 1. We use that as a scaling factor; common technique, called "cosine shading". if (cosLightAngleWithNormal <= 0) continue; // Add this light's diffuse contribution to our running totals r += MATERIAL_DIFFUSE_COEFFICIENT * cosLightAngleWithNormal * ray.closestHitObject.color.R; g += MATERIAL_DIFFUSE_COEFFICIENT * cosLightAngleWithNormal * ray.closestHitObject.color.G; b += MATERIAL_DIFFUSE_COEFFICIENT * cosLightAngleWithNormal * ray.closestHitObject.color.B; } if (MATERIAL_SPECULAR_COEFFICIENT > TINY) { // Specular component - dot product of light's reflection vector and viewer direction // Direction to the viewer is simply negative of the ray direction Vector3f lightReflectionDir = surfaceNormal * (cosLightAngleWithNormal * 2) - lightDir; number specularFactor = viewerDir.Dot(lightReflectionDir); if (specularFactor > 0) { // To get smaller, sharper highlights we raise it to a power and multiply it specularFactor = MATERIAL_SPECULAR_COEFFICIENT * (number)Math.Pow(specularFactor, MATERIAL_SPECULAR_POWER); // Add the specular contribution to our running totals r += specularFactor * ray.closestHitObject.color.R; g += specularFactor * ray.closestHitObject.color.G; b += specularFactor * ray.closestHitObject.color.B; } } } // Now do reflection, unless we're too deep if (traceDepth < MAX_DEPTH && MATERIAL_REFLECTION_COEFFICIENT > TINY) { // Set up the reflected ray - notice we move the origin out a tiny bit again Vector3f reflectedDir = ray.direction.ReflectIn(surfaceNormal); Ray reflectionRay = new Ray(ray.hitPoint + reflectedDir * TINY, reflectedDir); // And trace! Color reflectionCol = Trace(reflectionRay, traceDepth + 1); // Add reflection results to running totals, scaling by reflect coeff. r += MATERIAL_REFLECTION_COEFFICIENT * reflectionCol.R; g += MATERIAL_REFLECTION_COEFFICIENT * reflectionCol.G; b += MATERIAL_REFLECTION_COEFFICIENT * reflectionCol.B; } // Clamp RGBs if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; return (Color.FromArgb(255, (int)r, (int)g, (int)b)); }
// render a pixel (ie, set pixel color to result of a trace of a ray starting from eye position and // passing through the world coords of the pixel) static Color RenderPixel(int x, int y) { // First, calculate direction of the current pixel from eye position float sx = screenTopLeftPos.x + (x * pixelWidth); float sy = screenTopLeftPos.y - (y * pixelHeight); Vector3f eyeToPixelDir = new Vector3f(sx, sy, 0) - eyePos; eyeToPixelDir.Normalise(); // Set up primary (eye) ray Ray ray = new Ray(eyePos, eyeToPixelDir); // And send a bunch of reverse photons that way! // Since each photon we send into Trace with a depth of 0 will // bounce around randomly, we need to send many photons into // every pixel to get good convergence float r = 0, g = 0, b = 0; for (int i = 0; i < RAYS_PER_PIXEL; i++) { Color c = Trace(ray, 1); r += c.R; g += c.G; b += c.B; } r /= RAYS_PER_PIXEL; g /= RAYS_PER_PIXEL; b /= RAYS_PER_PIXEL; return (Color.FromArgb(255, (int)r, (int)g, (int)b)); }
// given a ray, trace it into the scene and return the colour of the surface it hits // (handles bounces recursively) static Color Trace(Ray ray, int traceDepth) { // See if the ray intersected an object (only if it hasn't already got one - we don't need to // recalculate the first intersection for each sample on the same pixel!) if (ray.closestHitObject == null) CheckIntersection(ref ray); if (ray.closestHitDistance >= Ray.WORLD_MAX || ray.closestHitObject == null) // No intersection return Color.Black; // Got a hit - was it an emitter? If so just return the emitter's colour if (ray.closestHitObject.isEmitter) return ray.closestHitObject.color; if (traceDepth >= MAX_DEPTH) return Color.Black; // Get surface normal at intersection Vector3f surfaceNormal = ray.closestHitObject.GetSurfaceNormalAtPoint(ray.hitPoint); // Pick a point on a hemisphere placed on the intersection point (of which // the surface normal is the north pole) if (surfaceNormal.Dot(ray.direction) >= 0) surfaceNormal = surfaceNormal * -1.0f; float r1 = (float)(random.NextDouble() * Math.PI * 2.0f); float r2 = (float)random.NextDouble(); float r2s = (float)Math.Sqrt(r2); Vector3f u = new Vector3f(1.0f, 0, 0); if (Math.Abs(surfaceNormal.x) > 0.1f) { u.x = 0; u.y = 1.0f; } u = Vector3f.CrossProduct(u, surfaceNormal); u.Normalise(); Vector3f v = Vector3f.CrossProduct(u, surfaceNormal); // Now set up a direction from the hitpoint to that chosen point Vector3f reflectionDirection = (u * (float)Math.Cos(r1) * r2s + v * (float)Math.Sin(r1) * r2s + surfaceNormal * (float)Math.Sqrt(1 - r2)); reflectionDirection.Normalise(); // And follow that path (note that we're not spawning a new ray -- just following the one we were // originally passed for MAX_DEPTH jumps) Ray reflectionRay = new Ray(ray.hitPoint, reflectionDirection); Color reflectionCol = Trace(reflectionRay, traceDepth + 1); // Now factor the colour we got from the reflection // into this object's own colour; ie, illuminate // the current object with the results of that reflection float r = ray.closestHitObject.color.R * reflectionCol.R; float g = ray.closestHitObject.color.G * reflectionCol.G; float b = ray.closestHitObject.color.B * reflectionCol.B; r /= 255.0f; g /= 255.0f; b /= 255.0f; return (Color.FromArgb(255, (int)r, (int)g, (int)b)); }
public static Vector3f CrossProduct(Vector3f v1, Vector3f v2) { Vector3f v = new Vector3f(); v.x = v1.y * v2.z - v1.z * v2.y; v.y = v1.z * v2.x - v1.x * v2.z; v.z = v1.x * v2.y - v1.y * v2.x; return v; }
public Plane(Vector3f n, float d, Color c) { normal = n; distance = d; color = c; isEmitter = false; }
public static Vector3f CrossProduct(Vector3f v1, Vector3f v2) { return new Vector3f( v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x ); }
// given a ray, trace it into the scene and return the colour of the surface it hits // (handles bounces recursively) static Color Trace(Ray ray, int traceDepth) { // See if the ray intersected an object (only if it hasn't already got one - we don't need to // recalculate the first intersection for each sample on the same pixel!) if (ray.closestHitObject == null) { CheckIntersection(ref ray); } if (ray.closestHitDistance >= Ray.WORLD_MAX || ray.closestHitObject == null) // No intersection { return(Color.Black); } // Got a hit - was it an emitter? If so just return the emitter's colour if (ray.closestHitObject.isEmitter) { return(ray.closestHitObject.color); } if (traceDepth >= MAX_DEPTH) { return(Color.Black); } // Get surface normal at intersection Vector3f surfaceNormal = ray.closestHitObject.GetSurfaceNormalAtPoint(ray.hitPoint); // Pick a point on a hemisphere placed on the intersection point (of which // the surface normal is the north pole) if (surfaceNormal.Dot(ray.direction) >= 0) { surfaceNormal = surfaceNormal * -1.0f; } float r1 = (float)(random.NextDouble() * Math.PI * 2.0f); float r2 = (float)random.NextDouble(); float r2s = (float)Math.Sqrt(r2); Vector3f u = new Vector3f(1.0f, 0, 0); if (Math.Abs(surfaceNormal.x) > 0.1f) { u.x = 0; u.y = 1.0f; } u = Vector3f.CrossProduct(u, surfaceNormal); u.Normalise(); Vector3f v = Vector3f.CrossProduct(u, surfaceNormal); // Now set up a direction from the hitpoint to that chosen point Vector3f reflectionDirection = (u * (float)Math.Cos(r1) * r2s + v * (float)Math.Sin(r1) * r2s + surfaceNormal * (float)Math.Sqrt(1 - r2)); reflectionDirection.Normalise(); // And follow that path (note that we're not spawning a new ray -- just following the one we were // originally passed for MAX_DEPTH jumps) Ray reflectionRay = new Ray(ray.hitPoint, reflectionDirection); Color reflectionCol = Trace(reflectionRay, traceDepth + 1); // Now factor the colour we got from the reflection // into this object's own colour; ie, illuminate // the current object with the results of that reflection float r = ray.closestHitObject.color.R * reflectionCol.R; float g = ray.closestHitObject.color.G * reflectionCol.G; float b = ray.closestHitObject.color.B * reflectionCol.B; r /= 255.0f; g /= 255.0f; b /= 255.0f; return(Color.FromArgb(255, (int)r, (int)g, (int)b)); }
// dot product -- returns the cosine of the angle between two vectors public float Dot(Vector3f b) { return(x * b.x + y * b.y + z * b.z); }
public override Vector3f GetSurfaceNormalAtPoint(Vector3f p) { Vector3f normal = p - position; normal.Normalise(); return normal; }
public Sphere(Vector3f p, float r, Color c) { position = p; radius = r; color = c; isEmitter = false; }
public Plane(Vector3f n, number d, Color c) { normal = n; distance = d; color = c; }
public float Dot(Vector3f b) { return (x * b.x + y * b.y + z * b.z); }
// raytrace a pixel (ie, set pixel color to result of a trace of a ray starting from eye position and // passing through the world coords of the pixel) static Color RenderPixel(int x, int y) { // First, calculate direction of the current pixel from eye position number sx = screenTopLeftPos.x + (x * pixelWidth); number sy = screenTopLeftPos.y - (y * pixelHeight); Vector3f eyeToPixelDir = new Vector3f(sx, sy, 0) - eyePos; eyeToPixelDir.Normalise(); // Set up primary (eye) ray Ray ray = new Ray(eyePos, eyeToPixelDir); // And trace it! return Trace(ray, 0); }
public Plane(Vector3f n, float d, Color c) { normal = n; distance = d; color = c; }
public number Dot(Vector3f b) { return (x * b.x + y * b.y + z * b.z); }
public Sphere(Vector3f p, float r, Color c) { position = p; radius = r; color = c; }
public Light(Vector3f p) { position = p; }
// return the surface normal (perpendicular vector to the surface) for a given point on the surface on the object public abstract Vector3f GetSurfaceNormalAtPoint(Vector3f p);