/* * Returns an array of colours representing the output image */ public Colour[,] GetImage(WorldSpace world, int width, int height, int reflectiveBounces = 4) { CalculateCorners(); for (int i = 0; i < world.objects.Count; i++) { world.objects[i].CalculateTriangles(); } Colour[,] image = new Colour[width, height]; Vector3 deltaWidth = Vector3.Divide(Vector3.Subtract(transformedCorners[1], transformedCorners[0]), width); Vector3 deltaHeight = Vector3.Divide(Vector3.Subtract(transformedCorners[2], transformedCorners[0]), height); for (int i = 0; i < height; i++) { Vector3 heightPoint = Vector3.Add(transformedCorners[0], Vector3.Multiply(deltaHeight, i)); for (int j = 0; j < width; j++) { Vector3 point = Vector3.Add(heightPoint, Vector3.Multiply(deltaWidth, j)); Vector3 rayDir; if (perspective) { rayDir = Vector3.Unit(Vector3.Subtract(point, origin)); } else { rayDir = direction; } RayHit camHit = GetRayHitClipping(world, point, rayDir, near, far); if (camHit.hit) { float smallestDistance = camHit.distances[0]; Vector3 closestIntersect = camHit.intersects[0]; Triangle triangle = camHit.triangles[0]; for (int k = 1; k < camHit.intersects.Count; k++) { if (camHit.distances[k] < smallestDistance) { smallestDistance = camHit.distances[k]; closestIntersect = camHit.intersects[k]; triangle = camHit.triangles[k]; } } image[j, i] = RayTrace(world, rayDir, closestIntersect, triangle, reflectiveBounces); } else { image[j, i] = background; } } } return(image); }
/* * Checks for intersections only if they are within a certain range of the camera */ public RayHit GetRayHitClipping(WorldSpace world, Vector3 start, Vector3 ray, float near, float far) { RayHit h = GetRayHit(world, start, ray, null); RayHit newH = new RayHit(); for (int i = 0; i < h.intersects.Count; i++) { float perpDistance = h.distances[i]; if (perspective) { perpDistance *= Vector3.DotProduct(ray, direction); } if (perpDistance >= near - screenDistance && perpDistance <= far - screenDistance) { newH.hit = true; newH.intersects.Add(h.intersects[i]); newH.triangles.Add(h.triangles[i]); newH.distances.Add(h.distances[i]); } } return(newH); }
/* * Returns the colour that would be seen for a specific ray trace */ public Colour RayTrace(WorldSpace world, Vector3 viewVector, Vector3 intersect, Triangle triangle, int reflectiveBounces) { RayHit reflectHit; Colour diffuse; Vector3 lightRay; float lightCos; if (world.light.GetType() == typeof(DirectionalLight)) { //Directional lighting DirectionalLight worldLight = (DirectionalLight)world.light; lightRay = Vector3.Unit(Vector3.Multiply(worldLight.lightVec, -1)); lightCos = Math.Abs(Vector3.DotProduct(lightRay, triangle.normal)); reflectHit = GetRayHit(world, intersect, lightRay, triangle); diffuse = Colour.Combine(triangle.material.diffuse, world.light.colour, world.light.brightness * lightCos * triangle.material.diffuseVal); } else if (world.light.GetType() == typeof(SpotLight)) { //Spot lighting SpotLight worldLight = (SpotLight)world.light; Vector3 spotVector = Vector3.Unit(Vector3.Multiply(worldLight.lightVec, -1)); lightRay = Vector3.Subtract(world.light.origin, intersect); float lightDistance = lightRay.Magnitude(); lightRay = Vector3.Unit(lightRay); lightCos = Math.Abs(Vector3.DotProduct(lightRay, triangle.normal)); if (Vector3.DotProduct(spotVector, lightRay) >= Math.Cos(worldLight.spotAngle)) { reflectHit = GetRayHit(world, intersect, lightRay, triangle); diffuse = Colour.Combine(triangle.material.diffuse, world.light.colour, world.light.brightness * lightCos * triangle.material.diffuseVal / (lightDistance * lightDistance)); } else { reflectHit = new RayHit(); reflectHit.hit = true; diffuse = new Colour(ColourList.BLACK); } } else { //Point lighting lightRay = Vector3.Subtract(world.light.origin, intersect); float lightDistance = lightRay.Magnitude(); lightRay = Vector3.Unit(lightRay); lightCos = Math.Abs(Vector3.DotProduct(lightRay, triangle.normal)); reflectHit = GetRayHit(world, intersect, lightRay, triangle); diffuse = Colour.Combine(triangle.material.diffuse, world.light.colour, world.light.brightness * lightCos * triangle.material.diffuseVal / (lightDistance * lightDistance)); } Colour ambient = Colour.Combine(triangle.material.diffuse, background, world.ambientBrightness); Colour reflection = new Colour(ColourList.BLACK); Colour specular = new Colour(ColourList.BLACK); if (reflectiveBounces > 0 && triangle.material.reflectivity > 0) { //Checks for recursive reflectivity Vector3 reverseVector = Vector3.Unit(Vector3.Multiply(viewVector, -1)); Vector3 reflectance = Vector3.Subtract(Vector3.Multiply(triangle.normal, 2 * Vector3.DotProduct(triangle.normal, reverseVector)), reverseVector); Vector3 lightReflectance = Vector3.Subtract(Vector3.Multiply(triangle.normal, 2 * Vector3.DotProduct(triangle.normal, lightRay)), lightRay); float specularDot = Vector3.DotProduct(lightReflectance, reverseVector); if (specularDot < 0) { specularDot = 0; } specular = Colour.Combine(triangle.material.specular, world.light.colour, triangle.material.reflectivity * (float)Math.Pow(specularDot, triangle.material.shininess)); RayHit reflectionHit = GetRayHit(world, intersect, reflectance, triangle); if (reflectionHit.hit) { float smallestDistance = reflectionHit.distances[0]; Vector3 reflectionIntersect = reflectionHit.intersects[0]; Triangle reflectionTriangle = reflectionHit.triangles[0]; for (int k = 1; k < reflectionHit.intersects.Count; k++) { if (reflectionHit.distances[k] < smallestDistance) { smallestDistance = reflectionHit.distances[k]; reflectionIntersect = reflectionHit.intersects[k]; reflectionTriangle = reflectionHit.triangles[k]; } } reflection = Colour.Add(reflection, Colour.Multiply(RayTrace(world, reflectance, reflectionIntersect, reflectionTriangle, reflectiveBounces - 1), triangle.material.reflectivity)); } } if (reflectHit.hit) { return(Colour.Add(ambient, reflection)); } else { return(Colour.Add(ambient, diffuse, reflection, specular)); } }
/* * Checks to see if the current ray is intersecting a triangle * This is where the most optimization is required */ public RayHit GetRayHit(WorldSpace world, Vector3 start, Vector3 ray, Triangle exclusionTriangle) { RayHit h = new RayHit(); for (int i = 0; i < world.objects.Count; i++) { //Checks to see if the ray is within an object's AABB Vector3 minAABB = world.objects[i].minAABB; Vector3 maxAABB = world.objects[i].maxAABB; float tx1 = (minAABB.x - start.x) / ray.x; float tx2 = (maxAABB.x - start.x) / ray.x; float ty1 = (minAABB.y - start.y) / ray.y; float ty2 = (maxAABB.y - start.y) / ray.y; float tz1 = (minAABB.z - start.z) / ray.z; float tz2 = (maxAABB.z - start.z) / ray.z; float tmin, tmax; if (tx1 < tx2) { tmin = tx1; tmax = tx2; } else { tmin = tx2; tmax = tx1; } float tmpmin, tmpmax; if (ty1 < ty2) { tmpmin = ty1; tmpmax = ty2; } else { tmpmin = ty2; tmpmax = ty1; } if (tmax < tmpmin) { tmpmin = tmax; } if (tmin > tmpmax) { tmpmax = tmin; } if (tmin < tmpmin) { tmin = tmpmin; } if (tmax > tmpmax) { tmax = tmpmax; } if (tz1 < tz2) { tmpmin = tz1; tmpmax = tz2; } else { tmpmin = tz2; tmpmax = tz1; } if (tmax < tmpmin) { tmpmin = tmax; } if (tmin > tmpmax) { tmpmax = tmin; } if (tmin < tmpmin) { tmin = tmpmin; } if (tmax > tmpmax) { tmax = tmpmax; } if (tmax == tmin) { continue; } //Checks each triangle for intersection with vectors for (int j = 0; j < world.objects[i].triangles.Length; j++) { Triangle currentTriangle = world.objects[i].triangles[j]; if (currentTriangle == exclusionTriangle) { continue; } if (Vector3.DotProduct(currentTriangle.normal, ray) >= 0) { continue; } float npb = Vector3.DotProduct(currentTriangle.normal, Vector3.Subtract(start, currentTriangle.vertices[1])); float nr = Vector3.DotProduct(currentTriangle.normal, ray); float t = 0; // distance if (nr != 0) { t = -npb / nr; } if (t <= 0) { continue; } Vector3 ab = Vector3.Subtract(currentTriangle.vertices[0], currentTriangle.vertices[1]); Vector3 cb = Vector3.Subtract(currentTriangle.vertices[2], currentTriangle.vertices[1]); Vector3 ac = Vector3.Subtract(currentTriangle.vertices[0], currentTriangle.vertices[2]); Vector3 intersect = Vector3.Add(start, Vector3.Multiply(ray, t)); Vector3 da = Vector3.Subtract(intersect, currentTriangle.vertices[0]); Vector3 db = Vector3.Subtract(intersect, currentTriangle.vertices[1]); Vector3 dc = Vector3.Subtract(intersect, currentTriangle.vertices[2]); //Checks to see if there is a point of intersection if (Vector3.DotProduct(currentTriangle.normal, Vector3.CrossProduct(da, ab)) >= 0 && Vector3.DotProduct(currentTriangle.normal, Vector3.CrossProduct(cb, db)) >= 0 && Vector3.DotProduct(currentTriangle.normal, Vector3.CrossProduct(ac, dc)) >= 0) { h.hit = true; h.triangles.Add(currentTriangle); h.intersects.Add(intersect); h.distances.Add(t); } } } return(h); }
static void Main(string[] args) { int width = 1280; int height = 720; string filename = "image"; Console.WriteLine("Importing models...\t 0% complete"); Importer importer = new Importer(); Console.WriteLine("Generating scene...\t25% complete"); Camera camera = new Camera(new Vector3(0, 3, 0), new Vector3(0, 0, -15), 80.0f, 1.0f, 40.0f, (float)height / width, true, new Colour(155, 230, 255)); WorldSpace world = new WorldSpace(0.15f); Material orangeFlat = new Material(new Colour(ColourList.ORANGE), new Colour(ColourList.WHITE), 1.0f, 0.0f, 0); Material greenShiny = new Material(new Colour(ColourList.YELLOW), new Colour(ColourList.WHITE), 1.0f, 0.5f, 5); Material floorGrey = new Material(new Colour(ColourList.GREY), new Colour(ColourList.WHITE), 1.0f, 0.5f, 20); Material glossPurple = new Material(new Colour(ColourList.PURPLE), new Colour(ColourList.WHITE), 1.0f, 0.9f, 3); WorldObject pyramid = new WorldObject( new Vector3(5, 1.5f, -1.5f), new Vector3[] { new Vector3(0, -1, 0), new Vector3(1, 1, -1), new Vector3(1, 1, 1), new Vector3(-1, 1, 0) }, new int[, ] { { 0, 1, 2 }, { 0, 3, 1 }, { 0, 2, 3 }, { 3, 2, 1 } }, new Material[] { greenShiny, orangeFlat, greenShiny, orangeFlat } ); WorldObject sphere = importer.ImportSTL( new Vector3(6, 1.5f, 0), glossPurple, "sphere.stl" ); WorldObject floor = new WorldObject( new Vector3(0, 0, 0), new Vector3[] { new Vector3(0, 0, -20), new Vector3(0, 0, 20), new Vector3(40, 0, -20), new Vector3(40, 0, 20), new Vector3(0, -1, 0) }, new int[, ] { { 0, 1, 2 }, { 1, 3, 2 } }, new Material[] { floorGrey, floorGrey } ); pyramid.RotateZ(45); LightSource light = new PointLight(new Vector3(3.0f, 5.0f, 1.0f), 30.0f, new Colour(255, 247, 219)); //LightSource light = new SpotLight(new Vector3(3.0f, 5.0f, 1.0f), new Vector3(1, -1, 0), 60.0f, new Colour(36, 36, 36), 30.0f); //LightSource light = new DirectionalLight(new Vector3(1, -1, -1), 0.7f, new Colour(255, 247, 219)); world.AddObject(pyramid); world.AddObject(sphere); world.AddObject(floor); world.SetLight(light); Exporter exporter = new Exporter(width, height); Colour[,] image = new Colour[width, height]; Console.WriteLine("Rendering image...\t50% complete"); exporter.SetImage(camera.GetImage(world, width, height, 8)); Console.WriteLine("Exporting...\t\t75% complete"); exporter.CreateFile(filename); Console.WriteLine("All jobs finished\t100% complete\n"); Console.WriteLine("Exported file " + filename + ".ppm"); Console.WriteLine("Press any key to continue"); Console.ReadLine(); }