// 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); }
// 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)); }
public override number Intersect(Ray ray) { number normalDotRayDir = normal.Dot(ray.direction); if (normalDotRayDir == 0) // Ray is parallel to plane (this early-out won't help very often!) return -1; // Any none-parallel ray will hit the plane at some point - the question now is just // if it in the positive or negative ray direction. number hitDistance = -(normal.Dot(ray.origin) - distance) / normalDotRayDir; if (hitDistance < 0) // Ray dir is negative, ie we're behind the ray's origin return -1; else return hitDistance; }
// Given a ray with origin and direction set, fill in the intersection info static void CheckIntersection(ref Ray ray) { foreach (RTObject obj in objects) { // loop through objects, test for intersection number hitDistance = obj.Intersect(ray); // check for intersection with this object and find distance if (hitDistance < ray.closestHitDistance && hitDistance > 0) { ray.closestHitObject = obj; // object hit and closest yet found - store it ray.closestHitDistance = hitDistance; } } ray.hitPoint = ray.origin + (ray.direction * ray.closestHitDistance); // also store the point of intersection }
public override number Intersect(Ray ray) { Vector3f lightFromOrigin = position - ray.origin; // dir from origin to us number v = lightFromOrigin.Dot(ray.direction); // cos of angle between dirs from origin to us and from origin to where the ray's pointing number hitDistance = radius * radius + v * v - lightFromOrigin.x * lightFromOrigin.x - lightFromOrigin.y * lightFromOrigin.y - lightFromOrigin.z * lightFromOrigin.z; if (hitDistance < 0) // no hit (do this check now before bothering to do the sqrt below) return -1; hitDistance = v - (number)Math.Sqrt(hitDistance); // get actual hit distance if (hitDistance < 0) return -1; else return (number)hitDistance; }
public abstract number Intersect(Ray ray);
public abstract float Intersect(Ray ray);
// 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)); }
// 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)); }