/// <summary> /// Calculates ambient occlusion at a point on a surface. /// </summary> /// <param name="surface">Information about surface point</param> /// <param name="geometry">The geometry to be raytraced (both occlusion caster and occlusion receiver)</param> /// <param name="random">Random number generator to use</param> /// <returns>Fraction of non-occlusion at surface point (between 0 and 1): 0 = surface point fully occluded by neighbour surfaces; 1 = surface point not occluded at all</returns> private double CalcAmbientOcclusion(IntersectionInfo surface, Raytrace.IRayIntersectable geometry, RenderContext context) { Contract.Ensures(0 <= Contract.Result <double>() && Contract.Result <double>() <= 1); var random = context.RNG; var rayStart = surface.pos + surface.normal * ambientOcclusionProbeOffset; Vector avgEscapedRayDir = new Vector(); int rayEscapeCount = 0; for (int i = 0; i < ambientOcclusionQuality; i++) { // Pick a random direction within the hemisphere around the surface normal // TODO: works for external surfaces, but some self-intersection on interior surfaces var rayDir = new Vector(random.NextDouble() * 2 - 1, random.NextDouble() * 2 - 1, random.NextDouble() * 2 - 1); //rayDir.Normalise(); if (rayDir.DotProduct(surface.normal) < 0) { rayDir = -rayDir; } // Pick random directions until we find one roughly in the same direction as the surface normal //Vector rayDir; //double cosOfAngle; //do //{ // rayDir = new Vector(random.NextDouble() * 2 - 1, random.NextDouble() * 2 - 1, random.NextDouble() * 2 - 1); // rayDir.Normalise(); // cosOfAngle = rayDir.DotProduct(surfaceInfo.normal); // // force ray to be in the hemisphere around the surface normal //} while (cosOfAngle < 0.0); // Fire off ray to check for nearby surface in chosen direction // TODO: might be more efficient if ray tracing stopped after a short distance from ray origin Raytrace.IntersectionInfo shadowInfo = geometry.IntersectRay(rayStart, rayDir, context); if (shadowInfo == null || shadowInfo.rayFrac > ambientOcclusionProbeDist) { // This ray did not hit a nearby surface rayEscapeCount++; avgEscapedRayDir += rayDir; } } // visualise direction of unobstructed space around surface point //avgEscapedRayDir.Normalise(); //var v = new Vector(avgEscapedRayDir.x + 1, avgEscapedRayDir.y + 1, avgEscapedRayDir.z + 1); //v *= 128; //return Surface.PackRgb((byte)v.x, (byte)v.y, (byte)v.z); //avgEscapedRayDir.Normalise(); //avgEscapedRayDir *= 255; //return Surface.PackRgb((byte)avgEscapedRayDir.x, (byte)avgEscapedRayDir.y, (byte)avgEscapedRayDir.z); return((double)rayEscapeCount / (double)ambientOcclusionQuality); }
/// <summary> /// Intersect a ray against this object. /// </summary> /// <param name="start">The start position of the ray, in object space.</param> /// <param name="dir">The direction of the ray, in object space (not a unit vector).</param> /// <returns>Information about the nearest intersection, or null if no intersection.</returns> public IntersectionInfo IntersectRay(Vector start, Vector dir, RenderContext context) { Contract.Ensures(Contract.Result <IntersectionInfo>() == null || (Contract.Result <IntersectionInfo>().color & 0xff000000) == 0xff000000); // trace ray through underlying geometry IntersectionInfo info = geometry.IntersectRay(start, dir, context); // if we are disabled, pass-through the ray intersection if (!Enabled) { return(info); } // did ray not hit any geometry? if (info == null) { return(null); } // shade the surface point Vector surfaceNormal = info.normal; Vector newRayStart = info.pos + surfaceNormal * raySurfaceOffset; Vector newRayDir = RandomRayInHemisphere(surfaceNormal, context.RNG); newRayDir.Normalise(); // only needed for calling BRDF function // Fire off ray to check for another surface in the chosen direction Raytrace.IntersectionInfo newRayInfo = geometry.IntersectRay(newRayStart, newRayDir, context); // Did this ray did hit another surface? Color incomingLight = Color.Black; // TODO: use background color from Renderer if (newRayInfo != null) { incomingLight = new Color(newRayInfo.color); } // TODO: color conversions are probably very slow Color surfaceEmission = new Color(info.color); // TODO: info.color might be shaded surface color! We want the raw material color here! double reflectedLightFrac = BRDF(newRayDir, surfaceNormal /*, dir */); Color outgoingLight = incomingLight * reflectedLightFrac + surfaceEmission; // only normalise colour if R, G or B are greater than 1.0 if (outgoingLight.r > 1.0 || outgoingLight.g > 1.0 || outgoingLight.b > 1.0) { outgoingLight.Normalise(); } Contract.Assert(0.0 <= outgoingLight.r && outgoingLight.r <= 1.0); Contract.Assert(0.0 <= outgoingLight.g && outgoingLight.g <= 1.0); Contract.Assert(0.0 <= outgoingLight.b && outgoingLight.b <= 1.0); info.color = outgoingLight.ToARGB(); return(info); }