예제 #1
0
        // 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);
            }
        }
예제 #2
0
파일: Plane.cs 프로젝트: voidstar69/softray
        /// <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);
            }
        }
예제 #3
0
        /// <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);
        }
예제 #4
0
        /// <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);
            }
        }
예제 #5
0
        /// <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);
            }
        }
예제 #6
0
        /// <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);
        }
예제 #7
0
        /// <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);
        }
예제 #8
0
        /// <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);
        }
예제 #9
0
        // 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);
            }
        }
예제 #10
0
        /// <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);
        }
예제 #11
0
        /// <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);
        }
예제 #12
0
파일: Plane.cs 프로젝트: voidstar69/softray
        /// <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);
            }
        }
예제 #13
0
        /// <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);
        }
예제 #14
0
        /// <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);
        }
예제 #15
0
        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);
        }
예제 #16
0
        /// <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);
        }
예제 #17
0
        /// <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);
        }
예제 #18
0
        //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);
        }