public void RayTriangleTest() { Vector3 origin = new Vector3(0, 0, 0); Vector3 direction = new Vector3(1, 0, 0); Ray r = new Ray(origin, direction); Triangle t = new Triangle( new Vertex() { Position = new Vector3(2, 1, 0) }, new Vertex() { Position = new Vector3(2, 0, -1) }, new Vertex() { Position = new Vector3(2, 0, 1) }); float time = -1f; bool collides = r.CollidesWith(t, ref time); Assert.True(collides); Assert.Equal(2f, time); Ray r2 = new Ray(origin, -direction); collides = r2.CollidesWith(t, ref time); Assert.False(collides); Assert.Equal(-2f, time); }
/// <summary> /// Renders the scene /// </summary> /// <returns>The constructed bitmap</returns> public Bitmap Render() { Bitmap output = new Bitmap(config.OutputWidth, config.OutputHeight); Matrix view = Matrix.LookAt(config.ViewportData.Up, config.ViewportData.Position, config.ViewportData.Target); // calcualte the camera position using the view matrix cameraPosition = new Vector3(0, 0, 0); cameraPosition.Z = (config.OutputHeight / (2.0f * (float)Math.Tan((config.ViewportData.FieldOfView / 2.0f) * (float)Math.PI / 180.0f))); cameraPosition = view * cameraPosition; Vector3 right = new Vector3(1, 0, 0); Vector3 up = new Vector3(0, 1, 0); // Get the left and top side of the viewport in screen coordinates Vector3 viewportLeftWorldSpace = (-(config.ViewportData.Width / 2.0f) * right); Vector3 viewportTopWorldSpace = ((config.ViewportData.Height / 2.0f) * up); // Get the number of world units per pixel float horizontalUnitsPerPixel = config.ViewportData.Width / config.OutputWidth; float verticalUnitsPerPixel = config.ViewportData.Height / config.OutputHeight; // Keep track of all the tasks we start (one for each ray emitted by camera Task[] tasks = new Task[config.OutputWidth * config.OutputHeight]; // Keep track of the final color of each pixel. Bitmap doesnt allow concurrent access Vector4[,] colors = new Vector4[config.OutputWidth, config.OutputHeight]; // Start casting the rays and tracing for (int h = 0; h < config.OutputHeight; h++) { for (int w = 0; w < config.OutputWidth; w++) { // Store current loop state to pass to the task TaskData d = new TaskData(); d.x = w; d.y = h; tasks[h * config.OutputWidth + w] = Task.Factory.StartNew((td) => { TaskData data = (TaskData)td; // Get the position of the ray target in the viewport Vector3 viewportPos = right * data.x * horizontalUnitsPerPixel + viewportLeftWorldSpace - up * data.y * verticalUnitsPerPixel + viewportTopWorldSpace; // Calculate the viewport position using the view matrix viewportPos = view * viewportPos; // Calculate the direction of the ray Vector3 direction = viewportPos - cameraPosition; // Create the ray Ray r = new Ray(cameraPosition, direction); // Cast the ray into the scene and store the resultant color colors[data.x, data.y] = CastRay(r); }, (object)d); } } Task.WaitAll(tasks); for (int x = 0; x < config.OutputWidth; x++) { for (int y = 0; y < config.OutputHeight; y++) { ScaleColor(ref colors[x, y]); colors[x, y] *= 255; output.SetPixel(x, y, Color.FromArgb( (int)colors[x, y].W, (int)colors[x, y].X, (int)colors[x, y].Y, (int)colors[x, y].Z)); } } // Do anti-aliasing and other post processing effects here return output; }
/// <summary> /// Determines if a point has direct line of sight to a light, or if it is shadowed by another object /// </summary> /// <param name="pos">The position being checked</param> /// <param name="light">The light source</param> /// <returns>Whether or not the point is lit by the light or in a shadow</returns> private bool InShadow(Vector3 pos, Light light) { // Get a vector from pos to light with some variation Vector3 lightVec = light.Location + new Vector3( (float)rand.NextDouble() * 2.0f * light.Radius - light.Radius, (float)rand.NextDouble() * 2.0f * light.Radius - light.Radius, (float)rand.NextDouble() * 2.0f * light.Radius - light.Radius) - pos; float lightDist = lightVec.Magnitude; Ray shadowRay = new Ray(pos, lightVec); float time = -1; // Iterate over all the triangles in the scene to see if they block the light foreach(var t in scene.Triangles) { if(shadowRay.CollidesWith(t, ref time)) { if(time < lightDist && time > ProximityTolerance) { return true; } } } return false; }
/// <summary> /// Casts a ray and gets the resultant color /// </summary> /// <param name="r">The ray to cast</param> /// <param name="depth">The current depth of the ray into the scene</param> /// <param name="sourceTriangle">The source of the ray if it was reflected/refracted</param> /// <returns>The resultant color of the ray</returns> public Vector4 CastRay(Ray r, int depth = 0, Triangle sourceTriangle = null) { float closestTime = float.MaxValue; Triangle closestTriangle = null; Vector3 closestPosition = Vector3.Zero; Vector4 returnColor = Vector4.Zero; foreach (var t in scene.Triangles) { float time = float.MaxValue; if (r.CollidesWith(t, ref time)) { if (time < closestTime && time > ProximityTolerance) { closestTime = time; closestTriangle = t; closestPosition = r.PointAtDistance(time); } } } // If we can still cast rays deeper and we collided with an object, then cast more rays if (depth < config.MaxRayDepth && closestTriangle != null) { // TODO: Cast reflection rays // TODO: Cast refraction rays // TODO: Add the color combinations from reflection and refraction returnColor = GetColor(closestTriangle, closestPosition); } else if (closestTriangle != null) { returnColor = GetColor(closestTriangle, closestPosition); } return returnColor; }