/// <summary> /// Traces a ray and calculates the color using the methods implemented in raytracer implementations. /// </summary> /// <param name="ray">The ray that will be traced through the scene.</param> /// <param name="recursionDepth">The current recursion depth. Optional, but should never be given at the first call.</param> /// <returns>Returns the color that results when tracing the ray.</returns> protected Color Trace(Ray ray, int recursionDepth = 0) { if (recursionDepth > Settings.RecursionDepth) { return(Color.Black); } // Intersect ray with scene objects and save to closestHit if (!CurrentScene.Intersect(ray, out HitData closestHit)) { return(calculateNoHitColor(ray)); } Color color = Color.Black; color += calculateDefaultColor(ray, closestHit); // Check for each light if it is blocked by scene objects and calculate color contribution CurrentScene.LightList.ForEach((ILight light) => { //Offset start position the prevent casting of a shadow on itself. Vector3 shadowRayDirection = light.Position - closestHit.Position; shadowRayDirection.Normalize(); Vector3 offsettedStartPosition = closestHit.Position + Settings.SecondaryRayOffset * shadowRayDirection; bool isOccluded = CurrentScene.Intersect(offsettedStartPosition, light.Position, out HitData shadowRayHit, true); color += calculateColorPerLight(ray, closestHit, light, isOccluded); }); // Handle reflective materials if (closestHit.Material.Reflective(closestHit.TextureCoords) > 0.001f) { Vector3 reflectionDirection = ray.Direction.Reflect(closestHit.Normal); Ray reflectionRay = new Ray(closestHit.Position + Settings.SecondaryRayOffset * reflectionDirection, reflectionDirection); color += closestHit.Material.Reflective(closestHit.TextureCoords) * Trace(reflectionRay, recursionDepth + 1); } return(color); }