public void SimpleQuad_ShouldBeIntersected() { var vertices = new Vector3[] { new Vector3(-1, 0, -1), new Vector3(1, 0, -1), new Vector3(1, 0, 1), new Vector3(-1, 0, 1) }; var indices = new int[] { 0, 1, 2, 0, 2, 3 }; TriangleMesh mesh = new(vertices, indices); using var rt = new Raytracer(); rt.AddMesh(mesh); rt.CommitScene(); Hit hit = rt.Trace(new Ray { Origin = new Vector3(-0.5f, -10, 0), Direction = new Vector3(0, 1, 0), MinDistance = 1.0f }); Assert.Equal(10.0f, hit.Distance, 0); Assert.Equal(1u, hit.PrimId); Assert.Equal(mesh, hit.Mesh); }
public void SimpleQuad_ShouldBeMissed() { var vertices = new Vector3[] { new Vector3(-1, 0, -1), new Vector3(1, 0, -1), new Vector3(1, 0, 1), new Vector3(-1, 0, 1) }; var indices = new int[] { 0, 1, 2, 0, 2, 3 }; TriangleMesh mesh = new TriangleMesh(vertices, indices); var rt = new Raytracer(); rt.AddMesh(mesh); rt.CommitScene(); Hit hit = rt.Trace(new Ray { Origin = new Vector3(-0.5f, -10, 0), Direction = new Vector3(0, -1, 0), MinDistance = 1.0f }); Assert.False(hit); }
static void MeasurePinvokeOverhead(int numTrials) { var vertices = new Vector3[] { new Vector3(-1, 0, -1), new Vector3(1, 0, -1), new Vector3(1, 0, 1), new Vector3(-1, 0, 1) }; var indices = new int[] { 0, 1, 2, 0, 2, 3 }; TriangleMesh mesh = new TriangleMesh(vertices, indices); var rt = new Raytracer(); rt.AddMesh(mesh); rt.CommitScene(); Stopwatch stop = Stopwatch.StartNew(); for (int k = 0; k < numTrials; ++k) { for (int i = 0; i < 1000000; ++i) { rt.Trace(new Ray { Origin = new Vector3(-0.5f, -10, 0), Direction = new Vector3(0, 1, 0), MinDistance = 1.0f }); } } stop.Stop(); Console.WriteLine($"One million rays intersected in {stop.ElapsedMilliseconds / numTrials}ms"); }
public override Color GetColor(Raytracer raytracer, Ray ray, RaycastHit hit, TraceData traceData) { Vector2 texCoord; Vector3 surfaceNormal; Vector3 tangent, binormal; bool texCoordAndTangentRequired = NormalMap != null || DiffuseTexture != null || SpecularMap != null || ReflectionMap != null; texCoordAndTangentRequired = true; // DEBUG if (texCoordAndTangentRequired) { Vector3 localNormal; Vector3 localTangent; ColliderUtils.GetTexCoordAndTangent( hit, out texCoord, out localNormal, out localTangent ); tangent = hit.collider.transform.TransformDirection(localTangent); surfaceNormal = hit.collider.transform.TransformDirection(localNormal); binormal = Vector3.Cross(tangent, surfaceNormal); #region Debug Visualisation #if DEBUG_SHOW_TEX_COORDS return(new Color(texCoord.x, texCoord.y, 0, 1)); #endif #if DEBUG_SHOW_BARYCENTRIC_COORDS Vector3 dbgBc = hit.barycentricCoordinate; return(new Color(dbgBc.x, dbgBc.y, dbgBc.z, 1)); #endif #if DEBUG_SHOW_NORMALS Vector3 dbgLocalNormal = localNormal; //dbgLocalNormal = new Vector3 ( // Mathf.Abs ( dbgLocalNormal.x ), // Mathf.Abs ( dbgLocalNormal.y ), // Mathf.Abs ( dbgLocalNormal.z ) //); dbgLocalNormal = (dbgLocalNormal + Vector3.one) * 0.5f; return(new Color(dbgLocalNormal.x, dbgLocalNormal.y, dbgLocalNormal.z, 1)); #endif #if DEBUG_SHOW_TANGENTS Vector3 dbgLocalTangent = localTangent; if (false) { dbgLocalTangent = new Vector3( Mathf.Abs(dbgLocalTangent.x), Mathf.Abs(dbgLocalTangent.y), Mathf.Abs(dbgLocalTangent.z) ); } else if (false) { dbgLocalTangent = new Vector3( Mathf.Abs(dbgLocalTangent.x > 0 ? dbgLocalTangent.x : 0), Mathf.Abs(dbgLocalTangent.y > 0 ? dbgLocalTangent.y : 0), Mathf.Abs(dbgLocalTangent.z > 0 ? dbgLocalTangent.z : 0) ); } else { dbgLocalTangent = (dbgLocalTangent + Vector3.one) * 0.5f; } return(new Color(dbgLocalTangent.x, dbgLocalTangent.y, dbgLocalTangent.z, 1)); #endif #if DEBUG_SHOW_BINORMALS Vector3 localBinormal = Vector3.Cross(localTangent, localNormal); Vector3 dbgBinormal = localBinormal; dbgBinormal = new Vector3( Mathf.Abs(dbgBinormal.x), Mathf.Abs(dbgBinormal.y), Mathf.Abs(dbgBinormal.z) ); return(new Color(dbgBinormal.x, dbgBinormal.y, dbgBinormal.z, 1)); #endif #endregion Debug Visualisation } else { texCoord = Vector2.zero; surfaceNormal = hit.normal; tangent = Vector3.zero; binormal = Vector3.zero; } bool entering = Vector3.Dot(hit.normal, ray.direction) <= 0; surfaceNormal = entering ? surfaceNormal : -surfaceNormal; /* TODO: revise where "entering" calculated upon hit.normal should be replaced * with "entering" calculated upon surfaceNormal transformed with TBN. */ if (NormalMap != null && NormalMapInfluence > 0) { var normalMapRt = TextureCache.FromUnityTexture(NormalMap); Color normalMapColor = normalMapRt.GetFilteredPixel(texCoord.x, texCoord.y); Vector3 texNormal = new Vector3(normalMapColor.r, normalMapColor.g, normalMapColor.b); texNormal = 2 * texNormal - Vector3.one; texNormal.Normalize(); Vector3 texNormalWorld = Raytracer.TransformTbn(texNormal, tangent, binormal, surfaceNormal); float normalMapInfluence = Mathf.Clamp01(NormalMapInfluence); surfaceNormal = Vector3.Lerp(surfaceNormal, texNormalWorld, normalMapInfluence).normalized; #if DEBUG_SHOW_SURFACE_NORMALS Vector3 dbgSurfaceNormal = surfaceNormal; //dbgSurfaceNormal = new Vector3 ( // Mathf.Abs ( dbgSurfaceNormal.x ), // Mathf.Abs ( dbgSurfaceNormal.y ), // Mathf.Abs ( dbgSurfaceNormal.z ) //); dbgSurfaceNormal = (dbgSurfaceNormal + Vector3.one) * 0.5f; return(new Color(dbgSurfaceNormal.x, dbgSurfaceNormal.y, dbgSurfaceNormal.z, 1)); #endif } float specularIntensity = SpecularComponent; if (SpecularMap != null && SpecularMapInfluence > 0 && specularIntensity > 0) { var specularMapRt = TextureCache.FromUnityTexture(SpecularMap); Color specularMapColor = specularMapRt.GetFilteredPixel(texCoord.x, texCoord.y); specularIntensity *= specularMapColor.grayscale * specularMapColor.a; float specularMapInfluence = Mathf.Clamp01(SpecularMapInfluence); specularIntensity = Mathf.Lerp(SpecularComponent, specularIntensity, specularMapInfluence); } Color totalColor = Color.black; Color diffuseLightSumColor = raytracer.OverrideAmbientLight ? raytracer.AmbientLight : RenderSettings.ambientLight; diffuseLightSumColor *= DiffuseComponent; Color specularLightSumColor = Color.black; var lights = Light.GetLights(LightType.Point, 0); foreach (var light in lights) { Vector3 vToLight = light.transform.position - hit.point; float lightVolumeRadius = light.range * 0.00625f; // Empirical coefficient. float distance = vToLight.magnitude - lightVolumeRadius; if (distance < 0) { distance = 0; } else if (distance >= light.range) { continue; } Vector3 dirToLight = vToLight.normalized; float attenuation; attenuation = 1 - distance / light.range; attenuation = attenuation * attenuation; float lightIntensity = light.intensity * LightIntensityFactor; if (DiffuseComponent > 0) { float diffuseIntensity = Vector3.Dot(dirToLight, surfaceNormal); if (diffuseIntensity > 0) { diffuseIntensity = Mathf.Pow(diffuseIntensity, DiffuseExponent); Color diffuseLightColor = light.color * attenuation * diffuseIntensity * lightIntensity; diffuseLightSumColor += diffuseLightColor; } } if (specularIntensity > 0) { Vector3 reflectionDir = Vector3.Reflect(-dirToLight, surfaceNormal); Vector3 vToView = raytracer.Camera.transform.position - hit.point; Vector3 dirToView = vToView.normalized; float specularity = Vector3.Dot(reflectionDir, dirToView); if (specularity > 0) { specularity = Mathf.Pow(specularity, SpecularPower); Color specularLightColor = light.color * attenuation * specularity * lightIntensity; specularLightSumColor += specularLightColor; } } } Color diffuseColor; if (DiffuseTexture != null) { var diffuseTextureRt = TextureCache.FromUnityTexture(DiffuseTexture); // TODO: calculate miplevel, get the color according to its value. Color texColor = diffuseTextureRt.GetFilteredPixel(texCoord.x, texCoord.y); if (texColor.a < 1 && DiffuseColorIsBackground) { diffuseColor = Color.Lerp(this.DiffuseColor, texColor, texColor.a); } else { diffuseColor = Color.Lerp(Color.black, texColor, texColor.a); } diffuseColor.a = texColor.a; } else { diffuseColor = this.DiffuseColor; } totalColor = diffuseLightSumColor * diffuseColor * DiffuseComponent + specularLightSumColor * specularIntensity; if (raytracer.MustInterrupt(totalColor, traceData)) { return(totalColor); } bool willReflect; float reflectionIntensity; if (entering) { willReflect = ReflectionComponent > 0 && traceData.NumReflections < raytracer.MaxReflections; reflectionIntensity = ReflectionComponent; } else { willReflect = InnerReflectionComponent > 0 && traceData.NumInnerReflections < raytracer.MaxInnerReflections; reflectionIntensity = InnerReflectionComponent; } if (willReflect && ReflectionMap != null && ReflectionMapInfluence > 0) { var reflectionMapRt = TextureCache.FromUnityTexture(ReflectionMap); Color reflectionMapColor = reflectionMapRt.GetFilteredPixel(texCoord.x, texCoord.y); float reflectionMapIntensity = reflectionMapColor.grayscale * reflectionMapColor.a; float reflectionMapInfluence = Mathf.Clamp01(ReflectionMapInfluence); reflectionIntensity = Mathf.Lerp(reflectionIntensity, reflectionMapIntensity * reflectionIntensity, reflectionMapInfluence); willReflect = reflectionIntensity > 0; } float refractionIntensity = RefractionComponent; if (RefractWhereTranslucent) { refractionIntensity *= 1 - diffuseColor.a * DiffuseComponent; } bool willRefract = refractionIntensity > 0 && traceData.NumRefractions < raytracer.MaxRefractions; bool forkingRequired = willReflect && willRefract; TraceData tdForRefraction; if (forkingRequired) { tdForRefraction = traceData.Fork(); } else { tdForRefraction = traceData; } if (willReflect) { if (entering) { traceData.NumReflections++; traceData.Counters.Reflections++; } else { traceData.NumInnerReflections++; traceData.Counters.InnerReflections++; } Vector3 reflectionDir = Vector3.Reflect(ray.direction, surfaceNormal); Vector3 pushedOutPoint = hit.point + reflectionDir * Raytracer.PushOutMagnitude; Color reflectionColor = raytracer.Trace(new Ray(pushedOutPoint, reflectionDir), traceData); totalColor += reflectionColor * reflectionIntensity; if (raytracer.MustInterrupt(totalColor, traceData)) { return(totalColor); } } if (willRefract) { tdForRefraction.NumRefractions++; tdForRefraction.Counters.Refractions++; CoefficientOut = RefractionIndex; CoefficientIn = 1 / RefractionIndex; if (CoefficientOut > 1) { CriticalOutAngleCos = Mathf.Sqrt(1 - CoefficientIn * CoefficientIn); CriticalInAngleCos = 0; } else { CriticalOutAngleCos = 0; CriticalInAngleCos = Mathf.Sqrt(1 - CoefficientOut * CoefficientOut); } float criticalAngleCos = entering ? CriticalInAngleCos : CriticalOutAngleCos; Vector3 refractionDir; float nDotRay = Vector3.Dot(surfaceNormal, ray.direction); if (Mathf.Abs(nDotRay) >= criticalAngleCos) { if (entering) { tdForRefraction.PenetrationStack.Push(hit); } else { tdForRefraction.PenetrationStack.Pop(); } float k = entering ? CoefficientIn : CoefficientOut; refractionDir = Raytracer.Refract(ray.direction, surfaceNormal, nDotRay, k); refractionDir.Normalize(); } else // Total internal reflection. { refractionDir = Vector3.Reflect(ray.direction, surfaceNormal); } Vector3 pushedOutPoint = hit.point + refractionDir * Raytracer.PushOutMagnitude; Color refractionColor = raytracer.Trace(new Ray(pushedOutPoint, refractionDir), tdForRefraction); if (ColorAberration != 0 && entering) { //float rDotRay = Vector3.Dot ( refractionDir, ray.direction ); refractionColor = HsvColor.ChangeHue(refractionColor, (1 + nDotRay) * ColorAberration); } totalColor += refractionColor * refractionIntensity; if (raytracer.MustInterrupt(totalColor, tdForRefraction)) { return(totalColor); } } return(totalColor); }
public static void MeasurePinvokeOverhead(int numTrials) { var vertices = new Vector3[] { new Vector3(-1, 0, -1), new Vector3(1, 0, -1), new Vector3(1, 0, 1), new Vector3(-1, 0, 1) }; var indices = new int[] { 0, 1, 2, 0, 2, 3 }; TriangleMesh mesh = new TriangleMesh(vertices, indices); var rt = new Raytracer(); rt.AddMesh(mesh); rt.CommitScene(); Random rng = new(1337); Stopwatch stop = Stopwatch.StartNew(); for (int k = 0; k < numTrials; ++k) { for (int i = 0; i < 1000000; ++i) { rt.Trace(new Ray { Origin = new Vector3( (float)rng.NextDouble(), (float)rng.NextDouble(), (float)rng.NextDouble()), Direction = new Vector3( (float)rng.NextDouble(), (float)rng.NextDouble(), (float)rng.NextDouble()), MinDistance = 0.0f }); } } stop.Stop(); Console.WriteLine($"One million rays intersected in {stop.ElapsedMilliseconds / numTrials}ms"); long traceCost = stop.ElapsedMilliseconds / numTrials; stop = Stopwatch.StartNew(); for (int k = 0; k < numTrials; ++k) { for (int i = 0; i < 1000000; ++i) { new Vector3( (float)rng.NextDouble(), (float)rng.NextDouble(), (float)rng.NextDouble() ); new Vector3( (float)rng.NextDouble(), (float)rng.NextDouble(), (float)rng.NextDouble() ); } } stop.Stop(); Console.WriteLine($"RNG overhead: {stop.ElapsedMilliseconds / numTrials}ms"); long rngCost = stop.ElapsedMilliseconds / numTrials; Console.WriteLine($"Pure cost for tracing + overhead: {traceCost-rngCost}ms"); }
public static void ComplexScene(int numTrials) { // the scene is compressed to avoid git issues if (!File.Exists("../Data/breakfast_room.obj")) { ZipFile.ExtractToDirectory("../Data/breakfast_room.zip", "../Data"); } Stopwatch stop = Stopwatch.StartNew(); List <TriangleMesh> meshes = new(); Vector3 min = Vector3.One * float.MaxValue; Vector3 max = -Vector3.One * float.MaxValue; Assimp.AssimpContext context = new(); var scene = context.ImportFile("../Data/breakfast_room.obj", Assimp.PostProcessSteps.GenerateNormals | Assimp.PostProcessSteps.JoinIdenticalVertices | Assimp.PostProcessSteps.PreTransformVertices | Assimp.PostProcessSteps.Triangulate); foreach (var m in scene.Meshes) { var material = scene.Materials[m.MaterialIndex]; string materialName = material.Name; Vector3[] vertices = new Vector3[m.VertexCount]; for (int i = 0; i < m.VertexCount; ++i) { vertices[i] = new(m.Vertices[i].X, m.Vertices[i].Y, m.Vertices[i].Z); } meshes.Add(new(vertices, m.GetIndices())); min.X = MathF.Min(min.X, m.BoundingBox.Min.X); min.Y = MathF.Min(min.X, m.BoundingBox.Min.Y); min.Z = MathF.Min(min.X, m.BoundingBox.Min.Z); max.X = MathF.Max(max.X, m.BoundingBox.Max.X); max.Y = MathF.Max(max.X, m.BoundingBox.Max.Y); max.Z = MathF.Max(max.X, m.BoundingBox.Max.Z); } var diagonal = max - min; Console.WriteLine($"Scene loaded in {stop.ElapsedMilliseconds}ms"); stop.Restart(); var rt = new Raytracer(); foreach (var m in meshes) { rt.AddMesh(m); } rt.CommitScene(); Console.WriteLine($"Acceleration structures built in {stop.ElapsedMilliseconds}ms"); Random rng = new(1337); Vector3 NextVector() => new Vector3( (float)rng.NextDouble(), (float)rng.NextDouble(), (float)rng.NextDouble()); stop.Restart(); float averageDistance = 0; for (int k = 0; k < numTrials; ++k) { for (int i = 0; i < 1000000; ++i) { var hit = rt.Trace(new Ray { Origin = NextVector() * diagonal + min, Direction = NextVector(), MinDistance = 0.0f }); if (hit) { averageDistance += hit.Distance / 1000000 / numTrials; } } } stop.Stop(); Console.WriteLine($"One million closest hits found in {stop.ElapsedMilliseconds / numTrials}ms"); Console.WriteLine($"Average distance: {averageDistance}"); stop.Restart(); float averageVisibility = 0; for (int k = 0; k < numTrials; ++k) { for (int i = 0; i < 1000000; ++i) { bool occluded = rt.IsOccluded(new ShadowRay(new Ray { Origin = NextVector() * diagonal + min, Direction = NextVector(), MinDistance = 0.0f }, maxDistance: (float)rng.NextDouble() * averageDistance * 5)); if (!occluded) { averageVisibility += 1.0f / 1000000.0f / numTrials; } } } stop.Stop(); Console.WriteLine($"One million any hits found in {stop.ElapsedMilliseconds / numTrials}ms"); Console.WriteLine($"Average visibility: {averageVisibility}"); }
private static void RaycastingMesh(Texture2D texture, float3 cameraPosition, float3 lightPosition, float3 target) { // View and projection matrices var viewMatrix = Transforms.LookAtLH(cameraPosition, target, float3.up); var projectionMatrix = Transforms.PerspectiveFovLH(pi_over_4, texture.Height / (float)texture.Width, 0.01f, 20); var scene = new Scene <PositionNormal>(); CreateMeshScene(scene); // Raycaster to trace rays and check for shadow rays. var shadower = new Raytracer <ShadowRayPayload, PositionNormal>(); shadower.OnAnyHit += (IRaycastContext context, PositionNormal attribute, ref ShadowRayPayload payload) => { // If any object is found in ray-path to the light, the ray is shadowed. payload.Shadowed = true; // No necessary to continue checking other objects return(HitResult.Stop); }; // Raycaster to trace rays and lit closest surfaces var raycaster = new Raytracer <RayPayload, PositionNormal>(); raycaster.OnClosestHit += (IRaycastContext context, PositionNormal attribute, ref RayPayload payload) => { // Move geometry attribute to world space attribute = attribute.Transform(context.FromGeometryToWorld); var V = normalize(cameraPosition - attribute.Position); var L = normalize(lightPosition - attribute.Position); var lambertFactor = max(0, dot(attribute.Normal, L)); // Check ray to light... var shadow = new ShadowRayPayload(); shadower.Trace(scene, RayDescription.FromTo( attribute.Position + attribute.Normal * 0.001f, // Move an epsilon away from the surface to avoid self-shadowing lightPosition), ref shadow); payload.Color = shadow.Shadowed ? float3(0, 0, 0) : float3(1, 1, 1) * lambertFactor; }; raycaster.OnMiss += (IRaycastContext context, ref RayPayload payload) => { payload.Color = float3(0, 0, 1); // Blue, as the sky. }; // Render all points of the screen for (var px = 0; px < texture.Width; px++) { for (var py = 0; py < texture.Height; py++) { var progress = px * texture.Height + py; if (progress % 100 == 0) { Console.Write("\r" + progress * 100 / (float)(texture.Width * texture.Height) + "% "); } var ray = RayDescription.FromScreen(px + 0.5f, py + 0.5f, texture.Width, texture.Height, inverse(viewMatrix), inverse(projectionMatrix), 0, 1000); var coloring = new RayPayload(); raycaster.Trace(scene, ray, ref coloring); texture.Write(px, py, float4(coloring.Color, 1)); } } Console.Write("\r" + 100 + "% "); }