/// <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()); } }