internal void Render(Scene scene) { var pixelsQuery = from y in Enumerable.Range(0, screenHeight) let recenterY = -(y - (screenHeight / 2.0)) / (2.0 * screenHeight) select from x in Enumerable.Range(0, screenWidth) let recenterX = (x - (screenWidth / 2.0)) / (2.0 * screenWidth) let point = Vector.Norm(Vector.Plus(scene.Camera.Forward, Vector.Plus(Vector.Times(recenterX, scene.Camera.Right), Vector.Times(recenterY, scene.Camera.Up)))) let ray = new Ray() { Start = scene.Camera.Pos, Dir = point } let computeTraceRay = (Func <Func <TraceRayArgs, Color>, Func <TraceRayArgs, Color> >) (f => traceRayArgs => (from isect in from thing in traceRayArgs.Scene.Things select thing.Intersect(traceRayArgs.Ray) where isect != null orderby isect.Dist let d = isect.Ray.Dir let pos = Vector.Plus(Vector.Times(isect.Dist, isect.Ray.Dir), isect.Ray.Start) let normal = isect.Thing.Normal(pos) let reflectDir = Vector.Minus(d, Vector.Times(2 * Vector.Dot(normal, d), normal)) let naturalColors = from light in traceRayArgs.Scene.Lights let ldis = Vector.Minus(light.Pos, pos) let livec = Vector.Norm(ldis) let testRay = new Ray() { Start = pos, Dir = livec } let testIsects = from inter in from thing in traceRayArgs.Scene.Things select thing.Intersect(testRay) where inter != null orderby inter.Dist select inter let testIsect = testIsects.FirstOrDefault() let neatIsect = testIsect == null ? 0 : testIsect.Dist let isInShadow = !((neatIsect > Vector.Mag(ldis)) || (neatIsect == 0)) where !isInShadow let illum = Vector.Dot(livec, normal) let lcolor = illum > 0 ? Color.Times(illum, light.Color) : Color.Make(0, 0, 0) let specular = Vector.Dot(livec, Vector.Norm(reflectDir)) let scolor = specular > 0 ? Color.Times(Math.Pow(specular, isect.Thing.Surface.Roughness), light.Color) : Color.Make(0, 0, 0) select Color.Plus(Color.Times(isect.Thing.Surface.Diffuse(pos), lcolor), Color.Times(isect.Thing.Surface.Specular(pos), scolor)) let reflectPos = Vector.Plus(pos, Vector.Times(.001, reflectDir)) let reflectColor = traceRayArgs.Depth >= MaxDepth ? Color.Make(.5, .5, .5) : Color.Times(isect.Thing.Surface.Reflect(reflectPos), f(new TraceRayArgs(new Ray() { Start = reflectPos, Dir = reflectDir }, traceRayArgs.Scene, traceRayArgs.Depth + 1))) select naturalColors.Aggregate(reflectColor, (color, natColor) => Color.Plus(color, natColor)) ).DefaultIfEmpty(Color.Background).First()) let traceRay = Y(computeTraceRay) select new { X = x, Y = y, Color = traceRay(new TraceRayArgs(ray, scene, 0)) }; foreach (var row in pixelsQuery) { foreach (var pixel in row) { setPixel(pixel.X, pixel.Y, pixel.Color); } } }
/// <summary> /// This is the main RayTrace controller algorithm, the core of the RayTracer /// recursive method setup /// this does the actual tracing of the ray and determines the color of each pixel /// supports: /// - ambient lighting /// - diffuse lighting /// - Gloss lighting /// - shadows /// - reflections /// </summary> /// <param name="info"></param> /// <param name="ray"></param> /// <param name="scene"></param> /// <param name="depth"></param> /// <returns></returns> private Color RayTrace(IntersectInfo info, Ray ray, Scene scene, int depth) { // calculate ambient light Color color = info.Color * scene.Background.Ambience; double shininess = Math.Pow(10, info.Element.Material.Gloss + 1); foreach (Light light in scene.Lights) { // calculate diffuse lighting Vector v = (light.Position - info.Position).Normalize(); if (RenderDiffuse) { double L = v.Dot(info.Normal); if (L > 0.0f) { color += info.Color * light.Color * L; } } // this is the max depth of raytracing. // increasing depth will calculate more accurate color, however it will // also take longer (exponentially) if (depth < 3) { // calculate reflection ray if (RenderReflection && info.Element.Material.Reflection > 0) { Ray reflectionray = GetReflectionRay(info.Position, info.Normal, ray.Direction); IntersectInfo refl = TestIntersection(reflectionray, scene, info.Element); if (refl.IsHit && refl.Distance > 0) { // recursive call, this makes reflections expensive refl.Color = RayTrace(refl, reflectionray, scene, depth + 1); } else // does not reflect an object, then reflect background color { refl.Color = scene.Background.Color; } color = color.Blend(refl.Color, info.Element.Material.Reflection); } //calculate refraction ray if (RenderRefraction && info.Element.Material.Transparency > 0) { Ray refractionray = GetRefractionRay(info.Position, info.Normal, ray.Direction, info.Element.Material.Refraction); IntersectInfo refr = info.Element.Intersect(refractionray); if (refr.IsHit) { //refractionray = new Ray(refr.Position, ray.Direction); refractionray = GetRefractionRay(refr.Position, refr.Normal, refractionray.Direction, refr.Element.Material.Refraction); refr = TestIntersection(refractionray, scene, info.Element); if (refr.IsHit && refr.Distance > 0) { // recursive call, this makes refractions expensive refr.Color = RayTrace(refr, refractionray, scene, depth + 1); } else { refr.Color = scene.Background.Color; } } else { refr.Color = scene.Background.Color; } color = color.Blend(refr.Color, info.Element.Material.Transparency); } } IntersectInfo shadow = new IntersectInfo(); if (RenderShadow) { // calculate shadow, create ray from intersection point to light Ray shadowray = new Ray(info.Position, v); // find any element in between intersection point and light shadow = TestIntersection(shadowray, scene, info.Element); if (shadow.IsHit && shadow.Element != info.Element) { // only cast shadow if the found interesection is another // element than the current element color *= 0.5 + 0.5 * Math.Pow(shadow.Element.Material.Transparency, 0.5); // Math.Pow(.5, shadow.HitCount); } } // only show highlights if it is not in the shadow of another object if (RenderHighlights && !shadow.IsHit && info.Element.Material.Gloss > 0) { // only show Gloss light if it is not in a shadow of another element. // calculate Gloss lighting (Phong) Vector Lv = (info.Element.Position - light.Position).Normalize(); Vector E = (scene.Camera.Position - info.Element.Position).Normalize(); Vector H = (E - Lv).Normalize(); double Glossweight = 0.0; Glossweight = Math.Pow(Math.Max(info.Normal.Dot(H), 0), shininess); color += light.Color * (Glossweight); } } // normalize the color color.Limit(); return(color); }