// 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); } }
// 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> /// Convert a 4D coordinate within the light field, to a ray travelling from first spherical point to second spherical point. /// </summary> /// <param name="coord4D">4D coordinate within the light field</param> /// <param name="start">The first point on the sphere</param> /// <param name="dir">The vector from the first to second points on the sphere</param> public void Coord4DToRay(Coord4D coord4D, out Vector start, out Vector dir) { Contract.Requires(coord4D != null); Contract.Requires(coord4D.Item1 < cacheRes * 2); Contract.Requires(coord4D.Item2 < cacheRes); Contract.Requires(coord4D.Item3 < cacheRes * 2); Contract.Requires(coord4D.Item4 < cacheRes); Contract.Ensures(!Contract.ValueAtReturn(out dir).IsZeroVector); // generate the canonical ray associated with this lightfield entry double maxValueUandS = cacheRes * 2 - 1; double maxValueVandT = cacheRes - 1; var u = (((double)coord4D.Item1 + 0.5) / maxValueUandS - 0.5) * Math.PI * 2; // [-π, π] var v = (((double)coord4D.Item2 + 0.5) / maxValueVandT - 0.5) * Math.PI; // [-π/2, π/2] var s = (((double)coord4D.Item3 + 0.5) / maxValueUandS - 0.5) * Math.PI * 2; // [-π, π] var t = (((double)coord4D.Item4 + 0.5) / maxValueVandT - 0.5) * Math.PI; // [-π/2, π/2] Sphere.LatLong spherePt1 = new Sphere.LatLong(u, v); Sphere.LatLong spherePt2 = new Sphere.LatLong(s, t); boundingSphere.ConvertLine(spherePt1, spherePt2, out start, out dir); dir = dir - start; }
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); }
// TODO: is this method multi-thread safe? // cache ray colors in a 4D light field private uint CalcColorForRay(Vector rayStart, Vector rayDir, RenderContext context) { // TODO: do we need locking to ensure another thread does not overwrite lightfield cache entry(s)? if (!Interpolate) { // no interpolation Coord4D lfCoord = null; // 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(backgroundColor); } return(CalcColorForCoord(lfCoord, context)); } else { // quad-linear interpolation // Convert ray to 4D spherical coordinates Float4D lfFloat4D = lightFieldCache.RayToFloat4D(ref rayStart, ref rayDir); if (lfFloat4D == null) { return(backgroundColor); } // this linearly interpolates lightfield colours along all four axes Coord4D coord = new Coord4D((byte)lfFloat4D.Item1, (byte)lfFloat4D.Item2, (byte)lfFloat4D.Item3, (byte)lfFloat4D.Item4); double uFrac = lfFloat4D.Item1 - coord.Item1; double vFrac = lfFloat4D.Item2 - coord.Item2; double sFrac = lfFloat4D.Item3 - coord.Item3; double tFrac = lfFloat4D.Item4 - coord.Item4; Color finalColor = new Color(); for (byte u = 0; u <= 1; u++) { var uFactor = (u == 1 ? uFrac : 1 - uFrac); for (byte v = 0; v <= 1; v++) { var vFactor = (v == 1 ? vFrac : 1 - vFrac); for (byte s = 0; s <= 1; s++) { var sFactor = (s == 1 ? sFrac : 1 - sFrac); for (byte t = 0; t <= 1; t++) { Coord4D newCoord = new Coord4D( (byte)((coord.Item1 + u) % uRes), (byte)((coord.Item2 + v) % vRes), (byte)((coord.Item3 + s) % sRes), (byte)((coord.Item4 + t) % tRes)); var color = new Color(CalcColorForCoord(newCoord, context)); finalColor += color * uFactor * vFactor * sFactor * (t == 1 ? tFrac : 1 - tFrac); } } } } return(finalColor.ToARGB()); } }