// Returns index of triangle intersected by central axis of lightfield cell's beam // Returns -1 if no triangle is intersected private int BeamTriangleSimpleIntersect(Coord4D lfCoord, RenderContext context) { // switch to tracing the canonical ray associated with this lightfield entry, instead of the original ray (to avoid biasing values in lightfield) Vector cellRayStart; Vector cellRayDir; lightFieldCache.Coord4DToRay(lfCoord, out cellRayStart, out cellRayDir); // TODO: once the light field cache 'fills up', do we cease tracing rays? Prove/measure this! // trace a primary ray against all triangles in scene // TODO: trace multiple rays to find the triangle with largest cross-section in the beam corresponding to this lightfield cell? // This may also avoid incorrectly storing 'missing triangle' value when primary ray misses because there are no triangles along central axis of cell's beam. IntersectionInfo intersection = geometry_subdivided.IntersectRay(cellRayStart, cellRayDir, context); if (intersection == null) { return(-1); } else { Contract.Assert(intersection.triIndex >= 0); return(intersection.triIndex); } }
/// <summary> /// Intersect a line segment this object. /// </summary> /// <param name="start">The start position of the line segment, in object space.</param> /// <param name="end">The end position of the line segment, in object space.</param> /// <returns>Information about the first intersection, or null if no intersection.</returns> public IntersectionInfo IntersectLineSegment(Vector start, Vector end) { // Project the start and end vectors onto the plane normal. // Note that all distances are in multiples of the normal, NOT in space units! double startDist = start.DotProduct(_normal); double endDist = end.DotProduct(_normal); double lineFrac = (_originDist - startDist) / (endDist - startDist); if (0.0 <= lineFrac && lineFrac <= 1.0) { IntersectionInfo info = new IntersectionInfo(); info.rayFrac = lineFrac; info.pos = start + (end - start) * lineFrac; // TODO: not strictly neccessary info.normal = _normal; info.color = 0x00ffffff; // TODO: better parameterisation of the surface // info.u = info.pos.x; // info.v = info.pos.z; return(info); } else { return(null); } }
/// <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> /// <remarks>Thread safe</remarks> public IntersectionInfo IntersectRay(Vector start, Vector dir, RenderContext context) { // trace ray through underlying geometry IntersectionInfo info = geometry.IntersectRay(start, dir, context); // if shadowing is disabled, pass-through the ray intersection if (!Enabled) { return(info); } // did ray not hit any geometry? if (info == null) { return(null); } byte lightIntensityByte = 255; if (softShadowCache != null) { // Static soft shadows, cached in a 3D texture currSurfaceNormal = info.normal; // a hack to pass this value to TraceRaysForSoftShadows lightIntensityByte = softShadowCache.Sample(info.pos); } else { // Dynamic soft shadows, not cached lightIntensityByte = (byte)(TraceRaysForSoftShadows(info.pos, info.normal, geometry, context) * 255); } info.color = Color.ModulatePackedColor(info.color, lightIntensityByte); return(info); }
/// <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) { numRayTests = 0; IntersectionInfo closest = new IntersectionInfo(); closest.rayFrac = double.MaxValue; foreach (IRayIntersectable geometry in _geomList) { IntersectionInfo curr = geometry.IntersectRay(start, dir, context); if (curr != null && curr.rayFrac < closest.rayFrac) { closest = curr; } numRayTests += geometry.NumRayTests; RayGeometryTestCount++; } if (closest.rayFrac == double.MaxValue) { return(null); } else { return(closest); } }
/// <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) { numRayTests = 0; IntersectionInfo closest = new IntersectionInfo(); closest.rayFrac = double.MaxValue; foreach (Plane plane in planes) { // Does ray intersect this plane? IntersectionInfo curr = plane.IntersectRay(start, dir, context); numRayTests += plane.NumRayTests; if (curr != null && curr.rayFrac < closest.rayFrac) { // Does the intersection point lie on the surface of this box? if (ContainsPoint(curr.pos)) { // Yes, so the ray intersects this box. closest = curr; } } } if (closest.rayFrac == double.MaxValue) { return(null); } else { return(closest); } }
/// <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) { IntersectionInfo info = plane.IntersectRay(start, dir, context); if (info == null) { return(null); } Assert.IsTrue(info.rayFrac >= 0.0, "Ray fraction is negative"); Vector v1ToIntersection = info.pos - vertex1.Position; double s = v1ToIntersection.DotProduct(edge2Perp) / edge1.DotProduct(edge2Perp); if (s < 0.0 || s > 1.0) { return(null); } double t = v1ToIntersection.DotProduct(edge1Perp) / edge2.DotProduct(edge1Perp); if (s >= 0.0 && t >= 0.0 && s + t <= 1.0) { info.color = color; info.triIndex = TriangleIndex; return(info); } return(null); }
/// <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); }
// Returns index of triangle most frequently intersected by random rays along lightfield cell's beam // Returns -1 if no triangle is intersected private int BeamTriangleComplexIntersect(Coord4D lfCoord, RenderContext context) { var triCount = new Dictionary <int, short>(); for (int i = 0; i < 100; i++) { const double bias = +0.5; Float4D randCoord = new Float4D( lfCoord.Item1 + random.NextDouble() + bias, lfCoord.Item2 + random.NextDouble() + bias, lfCoord.Item3 + random.NextDouble() + bias, lfCoord.Item4 + random.NextDouble() + bias); // switch to tracing the canonical ray associated with this lightfield entry, instead of the original ray (to avoid biasing values in lightfield) Vector cellRayStart; Vector cellRayDir; lightFieldCache.Float4DToRay(randCoord, out cellRayStart, out cellRayDir); // TODO: once the light field cache 'fills up', do we cease tracing rays? Prove/measure this! // trace a primary ray against all triangles in scene // TODO: trace multiple rays to find the triangle with largest cross-section in the beam corresponding to this lightfield cell? // This may also avoid incorrectly storing 'missing triangle' value when primary ray misses because there are no triangles along central axis of cell's beam. IntersectionInfo intersection = geometry_subdivided.IntersectRay(cellRayStart, cellRayDir, context); if (null != intersection) { var triIndex = intersection.triIndex; Contract.Assert(triIndex >= 0); if (triCount.ContainsKey(triIndex)) { triCount[triIndex]++; } else { triCount[triIndex] = 1; } } } if (triCount.Count == 0) { return(-1); } else { // find tri index that occurs most often var maxCount = triCount.Max(x => x.Value); return(triCount.First(x => x.Value == maxCount).Key); } }
/// <summary> /// Clip a line segment against this axis-aligned box. /// Any portion of the line segment within this box is returned. /// Otherwise false is returned. /// </summary> /// <param name="start">The start position of the line segment, in object space.</param> /// <param name="end">The end position of the line segment, in object space.</param> /// <returns>False if line segment entirely outside of box; /// True if some portion of line segment is inside box</returns> public bool ClipLineSegment(ref Vector start, ref Vector end) { bool startInside = ContainsPoint(start); bool endInside = ContainsPoint(end); if (startInside && endInside) { // Line segment is entirely inside this box. return(true); } IntersectionInfo intersection = IntersectLineSegment(start, end); if (intersection == null) { // Line segment does not intersect box, so entirely outside of box. Contract.Assert(!startInside && !endInside, "Start and end must be outside box"); return(false); } // Line segment intersects box. if (startInside) { // Only end is outside of box, so clipped line is now entirely within the box. end = intersection.pos; return(true); } // Start is outside box, so clip to surface of box. Vector originalStart = start; start = intersection.pos; // Is end outside of box? if (!endInside) { // End is outside of box, and original start was outside of box, // so original line segment must intersect box at two points. intersection = IntersectLineSegment(end, originalStart); Contract.Assume(intersection != null, "Intersection must exist"); end = intersection.pos; } return(true); }
/// <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> /// <remarks>Thread safe</remarks> public IntersectionInfo IntersectRay(Vector start, Vector dir, RenderContext context) { // trace ray through underlying geometry IntersectionInfo info = geometry.IntersectRay(start, dir, context); // if AO is disabled, pass-through the ray intersection if (!Enabled) { return(info); } // did ray not hit any geometry? if (info == null) { return(null); } if (ambientOcclusionCache == null) { // Create ambient occlusion cache, and optionally load cache data from disk // Extract the name of the model from the 3DS file name. // TODO: this is missing most/all of the time! //var modelFileName = model.GetFileName(); //Contract.Assert(modelFileName != null && modelFileName.Length > 0); //var modelName = RemoveFileExtension(modelFileName); // TODO: power-of-two size/resolution might make 3D array indexing quicker ambientOcclusionCache = new AmbientOcclusion(resolution, instanceKey, cachePath); } // Should the AO cache be enabled, or pass-through? ambientOcclusionCache.EnableCache = EnableAoCache; // shade surface point based on nearby geometry blocking ambient light from reaching surface var lightIntensityByte = ambientOcclusionCache.CacheAmbientOcclusion(info, geometry, context); info.color = Color.ModulatePackedColor(info.color, lightIntensityByte); return(info); }
/// <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) { // Project the start and direction vectors onto the plane normal. // Note that all distances are in multiples of the normal, NOT in space units! double startDist = start.DotProduct(_normal); double dirDist = dir.DotProduct(_normal); // Make the plane one-sided. if (dirDist >= 0.0) { return(null); } double rayFrac = _originDist - startDist; if (rayFrac <= 0.0 /* epsilon */) { rayFrac /= dirDist; IntersectionInfo info = new IntersectionInfo(); info.pos = start + dir * rayFrac; info.normal = _normal; info.rayFrac = rayFrac; info.color = color; // TODO: better parameterisation of the surface // info.u = info.pos.x; // info.v = info.pos.z; Assert.IsTrue(info.rayFrac >= 0.0, "Ray fraction is negative"); return(info); } else { return(null); } }
/// <summary> /// Calculates soft shadows at a point on a surface. /// Multithread safe? /// </summary> /// <param name="surfacePos">Position of surface point to be shadowed</param> /// <param name="surfaceNormal">Normal to surface at surface point</param> /// <param name="geometry">The geometry to be raytraced (both shadow caster and shadow receiver)</param> /// <returns>Fraction of light reaching the surface point (between 0 and 1): 0 = surface point fully shadowed; 1 = surface point not shadowed at all</returns> private double TraceRaysForSoftShadows(Vector surfacePos, Vector surfaceNormal, IRayIntersectable geometry, RenderContext context) { int rayEscapeCount = 0; for (int i = 0; i < softShadowQuality; i++) { // Check for shadow. Calculate direction and distance from light to surface point. Vector dirLightToSurface; Vector shadowRayStart; Vector shadowRayEnd = surfacePos + surfaceNormal * shadowProbeOffset; if (scene.pointLighting) { // generate points on the surface of a unit sphere (representing the light source) var lightSource = scene.positionalLightPos_Model + areaLightOffsets[i]; dirLightToSurface = shadowRayEnd - lightSource; shadowRayStart = lightSource; } else { // Directional lighting. // TODO: soft shadows might make no sense for a directional light! dirLightToSurface = scene.directionalLightDir_Model; shadowRayStart = shadowRayEnd + dirLightToSurface * 1000.0 + areaLightOffsets[i]; } // Fire off shadow ray through underlying geometry IntersectionInfo shadowInfo = geometry.IntersectRay(shadowRayStart, dirLightToSurface, context); // Did the shadow ray hit an occluder before reaching the light? if (shadowInfo == null || shadowInfo.rayFrac > 1.0) { // No, so this light ray reaches the surface point rayEscapeCount++; } } return((double)rayEscapeCount / (double)softShadowQuality); }
/// <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) { // trace ray through underlying geometry IntersectionInfo info = geometry.IntersectRay(start, dir, context); // if shading is 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 based on angle of lighting //double intensity = CalcLightingIntensity(info.pos, info.normal); // in object space // TODO: All lighting calcs are broken for raytracing. Vector pos_View = instance.TransformPosToView(info.pos); Vector normal_View = instance.TransformDirection(info.normal); double intensity = CalcLightingIntensity(pos_View, normal_View); byte lightIntensityByte = 255; // TODO: change this to a double in range [0, 1] so that conversion to byte occurs only once? lightIntensityByte = (byte)(lightIntensityByte * intensity); //else //{ // return Surface.PackRgb(lightIntensityByte, lightIntensityByte, lightIntensityByte); //} //color = Surface.ColorFromArgb(0, intensityByte, intensityByte, intensityByte); info.color = Color.ModulatePackedColor(info.color, lightIntensityByte); return(info); }
private uint CalcColorForCoord(Coord4D lfCoord, RenderContext context) { // Index into light field cache using 4D spherical coordinates uint lfCacheEntry = lightFieldCache.ReadCache(lfCoord.Item1, lfCoord.Item2, lfCoord.Item3, lfCoord.Item4); if (lfCacheEntry != lightFieldCache.EmptyCacheEntry) { // lightfield stores colors, and we found a color entry to return return(lfCacheEntry); } // switch to tracing the ray associated with this lightfield entry, instead of the original ray (to avoid biasing values in lightfield) Vector rayStart; Vector rayDir; lightFieldCache.Coord4DToRay(lfCoord, out rayStart, out rayDir); // TODO: once the light field cache 'fills up', do we cease tracing rays? Prove/measure this! // we did not find a color in the light field corresponding to this ray, so trace this ray against the geometry // TODO: this line is the major performance bottleneck! IntersectionInfo info = geometry.IntersectRay(rayStart, rayDir, context); if (info == null) { // ray did not hit the geometry, so cache background color into the 4D light field lightFieldCache.WriteCache(lfCoord.Item1, lfCoord.Item2, lfCoord.Item3, lfCoord.Item4, backgroundColor); return(backgroundColor); } // TODO: this is where lots of other AO, shadow code etc used to execute // cache ray colors in a 4D light field? lightFieldCache.WriteCache(lfCoord.Item1, lfCoord.Item2, lfCoord.Item3, lfCoord.Item4, info.color); return(info.color); }
/// <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) { // if lightfield is disabled, pass-through the ray intersection if (!Enabled) { return(geometry.IntersectRay(start, dir, context)); } // lazily create the lightfield cache if (lightFieldCache == null) { lightFieldCache = new LightField4D <ushort>(resolution, instanceKey, default(ushort), default(ushort) + 1, cachePath); } // cache triangle indices in a 4D light field Coord4D lfCoord = null; Vector rayStart = start; Vector rayDir = dir; // Convert ray to 4D spherical coordinates // TODO: do we need locking to ensure another thread does not overwrite this lightfield cache entry? lfCoord = lightFieldCache.RayToCoord4D(ref rayStart, ref rayDir); if (lfCoord == null) { return(null); } // TODO: debugging. Very slow! Also broken as of Aug 2016. //Vector tmpStart; //Vector tmpDir; //lightFieldCache.Coord4DToRay(lfCoord, out tmpStart, out tmpDir); //var tmpCoord = lightFieldCache.RayToCoord4D(ref tmpStart, ref tmpDir); //Contract.Assert(tmpCoord.Equals(lfCoord)); // Index into light field cache using 4D spherical coordinates ushort lfCacheEntry = lightFieldCache.ReadCache(lfCoord.Item1, lfCoord.Item2, lfCoord.Item3, lfCoord.Item4); if (lfCacheEntry == lightFieldCache.EmptyCacheEntryReplacement) { // cache entry indicates 'missing triangle', so nothing to intersect against return(null); } // if lightfield cache entry is empty, populate it with a triangle from the cell's beam int triIndex; if (lightFieldCache.EmptyCacheEntry == lfCacheEntry) { // lightfield does not yet contain a triangle entry to intersect against // TODO: once the light field cache 'fills up', do we cease tracing rays? Prove/measure this! // Intersect random rays along beam of lightfield cell, against all triangles // TODO: this *should* improve quality, but seems to decrease it, and adds a little randomness to the regression tests //triIndex = BeamTriangleComplexIntersect(lfCoord, context); // Intersect central axis of lightfield cell against all triangles triIndex = BeamTriangleSimpleIntersect(lfCoord, context); if (triIndex == -1) { // Cache 'missing triangle' value into the light field cache lightFieldCache.WriteCache(lfCoord.Item1, lfCoord.Item2, lfCoord.Item3, lfCoord.Item4, lightFieldCache.EmptyCacheEntryReplacement); return(null); } else { // Take triangle that we intersected, and store its index into the light field cache // TODO: triIndex could overflow a ushort and wrap around to an incorrect triangle index! lfCacheEntry = (ushort)(triIndex + 2); // account for empty cache value and replacement cache value (i.e. avoid storing 0 or 1 into lightfield) lightFieldCache.WriteCache(lfCoord.Item1, lfCoord.Item2, lfCoord.Item3, lfCoord.Item4, lfCacheEntry); } } Contract.Assert(lightFieldCache.EmptyCacheEntry != lfCacheEntry); // lightfield contains a triangle entry to intersect against // store triangle indices in light field, and raytrace the triangle that we get from the lightfield // TODO: Store index of bucket of triangles into lightfield? Tradeoff between small buckets and many buckets. Optimise for 8-bit or 16-bit bucket indices? // Intersect primary ray against closest/likely/best triangle (from lightfield cache) triIndex = (int)lfCacheEntry - 2; // discount empty and replacement cache values // TODO: Contracts analyser says assert unproven Contract.Assert(triIndex >= 0); var tri = geometry_simple[triIndex] as Raytrace.Triangle; IntersectionInfo intersection = tri.IntersectRay(rayStart, rayDir, context); // intersect ray against triangle // intersect ray against plane of triangle, to avoid holes by covering full extent of lightfield cell. Creates spatial tearing around triangle silhouettes. //info = tri.Plane.IntersectRay(rayStart, rayDir); //info.color = tri.Color; // all planes are white, so use color of triangle if (intersection == null) { // Intersect ray against triangles 'near' to the triangle from the lightfield (i.e. in the same subdivision node). // This is slower than intersecting the single triangle from the lightfield, but much faster than intersecting the entire subdivision structure. // TODO: need to walk the subdivision tree to find triangles in nearby tree nodes intersection = geometry_subdivided.IntersectRayWithLeafNode(rayStart, rayDir, tri, context); #if VISUALISATION if (intersection != null) { return new IntersectionInfo { color = 0x000000FF, normal = new Vector(0, 1, 0) } } ; // visualise hitting nearby triangle (blue) #endif } if (intersection == null) { // Intersect ray against all triangles in the geometry. // TODO: this is much slower than intersecting the single triangle from the lightfield, or the triangles in the same node. // Performance will be reasonable if not many pixels reach this code path. intersection = geometry_subdivided.IntersectRay(rayStart, rayDir, context); #if VISUALISATION if (intersection != null) { return new IntersectionInfo { color = 0x00FF0000, normal = new Vector(0, 1, 0) } } ; // visualise hitting triangle in distant node (red) #endif } //if (info == null) //{ // // intersect ray against plane of triangle, to avoid holes by covering full extent of lightfield cell // // TODO: this creates spatial tearing around triangle silhouettes, as all background pixels in this cell are covered by this plane. // info = tri.Plane.IntersectRay(rayStart, rayDir); // if (info != null) // info.color = tri.Color; // all planes are white, so use color of triangle //} #if VISUALISATION if (intersection == null) { return new IntersectionInfo { color = 0x0000FF00, normal = new Vector(0, 1, 0) } } ; // visualise missing nearby triangles (green) #endif return(intersection); }
/// <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) { // TODO: optimise ray-sphere intersection code // Make dir a unit vector. // TODO: not strictly neccessary, but it makes the quadratic equation a bit simpler. // TODO: make this function faster by removing the normalise, and accounting for sphereToStartProjDir // being scaled up by the length of dir. dir.Normalise(); // Solve quadratic equation to determine number of intersection points between (infinite) line and sphere: 0, 1 or 2 intersections Vector sphereToStart = start - center; // o - c double sphereToStartProjDir = sphereToStart.DotProduct(dir); // l.(o - c) // Is the entire sphere behind the start of the ray? if (sphereToStartProjDir > radius) { return(null); } double sphereToStartDistSqr = sphereToStart.LengthSqr; // |o - c|^2 // TODO: ignore rays starting inside the sphere? // Is any part of the sphere behind the start of the ray? // if(sphereToStartProjDir > -radius) // return null; // Is the sphere surrounding the ray start? TODO: is this ever true after the previous check? // if(sphereToStartDistSqr < radiusSqr) // return null; double termUnderSqrRoot = sphereToStartProjDir * sphereToStartProjDir - sphereToStartDistSqr + radiusSqr; // (l.(o - c))^2 - |o - c|^2 + r^2 if (termUnderSqrRoot < epsilon) // use epsilon to avoid unstable pixels flickering between one root and zero roots // (infinite) line does not intersect sphere { return(null); } double positiveSqrRoot = Math.Sqrt(termUnderSqrRoot); double intersectRayFrac1 = -sphereToStartProjDir - positiveSqrRoot; // distance along line from ray-start to first intersection double intersectRayFrac2 = -sphereToStartProjDir + positiveSqrRoot; // distance along line from ray-start to second intersection (might be same as first intersection) double rayFrac = (intersectRayFrac1 >= 0 ? intersectRayFrac1 : intersectRayFrac2); if (rayFrac < 0) { // (infinite) line intersects sphere, but ray does not return(null); } // Determine details of nearest intersection point // TODO: rearchitect to avoid calculating intersection position and normal unless this is the first object hit IntersectionInfo info = new IntersectionInfo(); info.rayFrac = rayFrac; info.pos = start + dir * rayFrac; info.normal = info.pos - center; info.normal.Normalise(); info.color = color; // TODO: this is for debugging spherical angles! /* * // work out spherical angles of intersection point * var sphereToHitPt = info.pos - center; * var horizAngle = Math.Atan2(sphereToHitPt.x, sphereToHitPt.z); // longitude of point on sphere, -π ≤ θ ≤ π * var vertAngle = Math.Asin(sphereToHitPt.y / radius); // latitude of point on sphere, -π/2 ≤ θ ≤ π/2 * * //var visualNorm = horizAngle / Math.PI / 2.0 + 0.5; // [0, 1] * var visualNorm = vertAngle / Math.PI + 0.5; // [0, 1] * //byte visualNormByte = (byte)(visualNorm * 255); // [0, 255] * var visualNormInt = (int)(visualNorm * 255); // [0, 255] * info.color = (uint)(visualNormInt << 8); * //info.color = (uint)((visualNormByte << 16) + (visualNormByte << 8) + visualNormByte); */ return(info); }
//public void CalcAllAmbientOcclusion() //{ // // TODO: write brute force algorithm: for every cell in 3D cube, find intersecting triangles and calc AO for these? Or just find nearest triangle to each cell center? //} /// <summary> /// Calculates and caches ambient occlusion at a point on a surface. /// Multithread safe. /// </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. Set to <value>NULL</value> to use the default random number generator</param> /// <returns>Fraction of non-occlusion at surface point (between 1 and 255): 1 = surface point fully occluded by neighbour surfaces; 255 = surface point not occluded at all</returns> public T CacheAmbientOcclusion(IntersectionInfo surface, Raytrace.IRayIntersectable geometry, RenderContext context) { Contract.Requires(surface != null); // TODO: Sometimes a surface point coordinate is very slightly outside the unit cube (i.e. Z coordinate of 0.50000000001) const double maxExtent = 0.5; // TODO: extent seems too large on Couch model... const double scale = 0.5 / maxExtent; // So we clamp coordinates to the unit cube surface.pos.x = Math.Min(Math.Max(-0.5, surface.pos.x), 0.5); surface.pos.y = Math.Min(Math.Max(-0.5, surface.pos.y), 0.5); surface.pos.z = Math.Min(Math.Max(-0.5, surface.pos.z), 0.5); /* * Assert.IsTrue(-maxExtent <= surface.pos.x && surface.pos.x <= maxExtent, "Surface point is outside unit cube"); * Assert.IsTrue(-maxExtent <= surface.pos.y && surface.pos.y <= maxExtent, "Surface point is outside unit cube"); * Assert.IsTrue(-maxExtent <= surface.pos.z && surface.pos.z <= maxExtent, "Surface point is outside unit cube"); */ var cacheIndex = (int)((surface.pos.x * scale + 0.5) * (cacheSize - 1)) * cacheSize * cacheSize + (int)((surface.pos.y * scale + 0.5) * (cacheSize - 1)) * cacheSize + (int)((surface.pos.z * scale + 0.5) * (cacheSize - 1)); // fetch cache entry, but if missing then calc AO factor // TODO: probably not multithread safe! // TODO: tri-linearly interpolate AO factor from eight neighbouring cache entries for smoother results! // Cheap thread lock - prevent other threads recalculating this value, while it is calculated by this thread // TODO: cannot be used with byte values, but could be used with ints //if(0 == Interlocked.CompareExchange<byte>(ref cacheData[cacheIndex], (byte)1, (byte)0)) if (!EnableCache) { // calc shading intensity based on percentage AO shadowing double unoccludedFactor = CalcAmbientOcclusion(surface, geometry, context); // scale to range [1, 255]. We avoid zero as this is a sentinel value indicating empty cache entry. return((T)(unoccludedFactor * 254 + 1)); } // Double-check locking pattern // TODO: rare IndexOutOfRangeException here T lightByte = cacheData[cacheIndex]; if (EmptyCacheEntry == lightByte) { lock (calcLock) { // another thread may have got the lock first, and already calculated this value if (EmptyCacheEntry == cacheData[cacheIndex]) { // calc shading intensity based on percentage AO shadowing double unoccludedFactor = CalcAmbientOcclusion(surface, geometry, context); // scale to range [1, 255]. We avoid zero as this is a sentinel value indicating empty cache entry. lightByte = (T)(unoccludedFactor * 254 + 1); cacheData[cacheIndex] = lightByte; // Time to persist cache data to file in isolated storage? numCalcsUntilNextPersist--; if (numCalcsUntilNextPersist <= 0) { // TODO: lock may be held for long time! SaveCacheToDisk(cacheFilePath, cacheData); numCalcsUntilNextPersist = numCalcsBetweenPersists; } } } } return(lightByte); }