Ray Tracing C# Triangle Intersection - c#

So basicly I want to reflect a ray over a triangle. Here's my ray class
public sealed class Ray
{
public readonly Point3D Source;
public readonly Point3D Direction;
public readonly Color Light;
public Ray(Point3D source, Point3D direction, Color light)
{
if (source == direction)
{
throw new ArgumentException("Source and Direction cannot be equal");
}
this.Source = source;
this.Direction = direction;
this.Light = light;
}
}
Heres my Point3D class
public struct Point3D : IEquatable<Point3D>
{
public static readonly Point3D Zero = new Point3D();
public float X;
public float Y;
public float Z;
public Point3D(float x, float y, float z)
{
this.X = x;
this.Y = y;
this.Z = z;
}
public override bool Equals(object obj)
{
if (!(obj is Point3D))
{
return false;
}
return this.Equals((Point3D)obj);
}
public static bool operator ==(Point3D one, Point3D two)
{
return one.Equals(two);
}
public static bool operator !=(Point3D one, Point3D two)
{
return !one.Equals(two);
}
public static Point3D operator *(float n, Point3D v)
{
return new Point3D(v.X * n, v.Y * n, v.Z * n);
}
public static Point3D operator +(Point3D v1, Point3D v2)
{
return new Point3D(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}
public static Point3D operator -(Point3D v1, Point3D v2)
{
return new Point3D(v1.X - v2.X, v1.Y - v2.Y, v1.Z - v2.Z);
}
public static float operator *(Point3D v1, Point3D v2)
{
return (v1.X * v2.X) + (v1.Y * v2.Y) + (v1.Z * v2.Z);
}
public static float Magnitude(Point3D v)
{
return (float)Math.Sqrt(v * v);
}
public static Point3D Normalize(Point3D v)
{
float mag = Magnitude(v);
float div = (mag == 0) ? float.PositiveInfinity : (1 / mag);
return div * v;
}
public static Point3D Cross(Point3D v1, Point3D v2)
{
return new Point3D(((v1.Y * v2.Z) - (v1.Z * v2.Y)),
((v1.Z * v2.X) - (v1.X * v2.Z)),
((v1.X * v2.Y) - (v1.Y * v2.X)));
}
/// <summary>
/// doesnt take square root
/// </summary>
public static float FastDistance(Point3D v1, Point3D v2)
{
float x = v1.X - v2.X;
x *= x;
float y = v1.Y - v2.Y;
y *= y;
float z = v1.Z - v2.Z;
z *= z;
return x + y + z;
}
/// <summary>
/// Takes square root:
/// </summary>
public static float Distance(Point3D v1, Point3D v2)
{
return (float)Math.Sqrt(Point3D.FastDistance(v1, v2));
}
public override int GetHashCode()
{
return this.X.GetHashCode()
^ this.Y.GetHashCode()
^ this.Y.GetHashCode();
}
public override string ToString()
{
return this.X + ", " + this.Y + ", " + this.Z;
}
public bool Equals(Point3D other)
{
return this.X == other.X
&& this.Y == other.Y
&& this.Z == other.Z;
}
}
and finally here is where I need my method to be realized.
public interface ITriangleAccess
{
Triangle3D Find(Ray ray, out Point3D crossPoint);
}
public sealed class TriangleAccess : ITriangleAccess
{
private readonly List<KeyValuePair<float, Triangle3D>> trianglesByX;
private readonly List<Triangle3D> allTriangles;
public TriangleAccess(Body[] bodies)
{
if (null == bodies)
{
throw new ArgumentNullException("bodies");
}
this.allTriangles = bodies.SelectMany((x) => x.Parts).ToList();
this.trianglesByX = bodies.SelectMany((x) => x.Parts).SelectMany((y) => new KeyValuePair<float, Triangle3D>[]
{
new KeyValuePair<float,Triangle3D>(y.Point1.X,y),
new KeyValuePair<float,Triangle3D>(y.Point2.X,y),
new KeyValuePair<float,Triangle3D>(y.Point3.X,y)
}).ToList();
}
public Triangle3D Find(Ray ray, out Point3D crossPoint)
{
crossPoint = Point3D.Zero;
List<Triangle3D> relevant = this.GetRelevantTriangles(ray);
Triangle3D absoluteTriangle = null;
float min = float.MaxValue;
foreach (Triangle3D item in relevant)
{
Point3D currentCrossPoint;
if (this.RayIntersectTriangle(ray, item, out currentCrossPoint))
{
float distance = Point3D.Distance(ray.Source, currentCrossPoint);
if (distance < min)
{
absoluteTriangle = item;
crossPoint = currentCrossPoint;
min = distance;
}
}
}
return absoluteTriangle;
}
public Ray Reflect(Ray ray, Point3D crossPoint, Triangle3D intersect)
{
//need this to be realized
//please help
}
/// <summary>
/// TODO: Finish this Up:
/// </summary>
/// <param name="ray"></param>
/// <returns></returns>
private List<Triangle3D> GetRelevantTriangles(Ray ray)
{
return this.allTriangles;
}
private bool RayIntersectTriangle(Ray ray, Triangle3D triangle, out Point3D crossPoint)
{
// Find vectors for two edges sharing vert0
Point3D edge1 = triangle.Point2 - triangle.Point1;
Point3D edge2 = triangle.Point3 - triangle.Point1;
// Begin calculating determinant - also used to calculate barycentricU parameter
Point3D pvec = Point3D.Cross(ray.Direction, edge2);
// If determinant is near zero, ray lies in plane of triangle
float det = edge1 * pvec;
if (det < 0.0001f)
{
crossPoint = Point3D.Zero;
return false;
}
// Calculate distance from vert0 to ray origin
Point3D tvec = ray.Source - triangle.Point1;
// Calculate barycentricU parameter and test bounds
float barycentricU = tvec * pvec;
if (barycentricU < 0.0f || barycentricU > det)
{
crossPoint = Point3D.Zero;
return false;
}
// Prepare to test barycentricV parameter
Point3D qvec = Point3D.Cross(tvec, edge1);
// Calculate barycentricV parameter and test bounds
float barycentricV = ray.Direction * qvec;
if (barycentricV < 0.0f || barycentricU + barycentricV > det)
{
crossPoint = Point3D.Zero;
return false;
}
// Calculate pickDistance, scale parameters, ray intersects triangle
float pickDistance = edge2 * qvec;
float fInvDet = 1.0f / det;
pickDistance *= fInvDet;
barycentricU *= fInvDet;
barycentricV *= fInvDet;
crossPoint = MathHelper.BaryCentric(triangle, barycentricU, barycentricV);
return true;
}
}
Thank you.
P.S have patience with me... I'm just 15 :)

I'm assuming that if you have a ray that intersects with a triangle, your Reflect function should return a ray that originates at that intersection and heads off in a new direction (like light on a mirror).
That said, we already have the ray source (crossPoint).
The formula for the reflected vector (direction of the ray) is R=V−2N(V⋅N) where V is the inverted direction of the incoming ray (as if it originated from the object) and N is the normal vector of the triangle.
With your code, you should just need to something like:
public Ray Reflect(Ray ray, Point3D crossPoint, Triangle3D intersect)
{
Point3D V = -ray.Direction.Normalize();
return new Ray(crossPoint,
V - 2 * intersect.Normal * (V * intersect.Normal),
Color.white);
}
Also, if your triangle structure doesn't have the normal, you calculate it using the cross product of the vectors that form each side.

public Ray Reflect(Ray ray, Point3D crossPoint, Triangle3D intersect)
{
// find normal of intersect triangle
Point3D normal = Point3D.Cross( intersect.Point2 - intersect.Point1,
intersect.Point3 - intersect.Point1 );
normal = Point3D.Normalize( normal );
// find ray part before intersection
Point3D inbound = crossPoint - ray.Source;
// find projection of inbound ray to normal
Point3D projection = (normal * inbound) * normal;
// find lateral component of inbound ray
Point3D lateral = inbound - projection;
// find outbound direction
Point3D direction = (ray.Source + 2 * lateral) - crossPoint;
direction = Point3D.Normalize( direction );
// I assume your direction is unit vector (magnitude = 1)
// if not multiply it with magnitude you want to be
// direction = ... * direction;
return new Ray( crossPoint, direction, ray.Color );
}

Related

Rotate a vector around an axis in 3D space

Tried to solve through the rotation matrix, but it does not work
Input:
Ten reals:
The 3D coordinates of the vector.
The rotation angle in radians.
The 3D coordinates of a point of the axis.
The 3D coordinates of the direction vector of the axis.
The length of the direction vector is greater than 1e-8.
Output:
Three reals, the 3D coordinates of the vector after its rotation.
Example:
Input:
3.0 2.0 4.0 3.141592653 0.0 0.0 0.0 0.0 1.0 0.0
Output:
-3.0 2.0 -4.0
My realization of Vector3:
class Vector3D
{
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
public double Length
{
get
{
return Math.Sqrt(X * X + Y * Y + Z * Z);
}
}
public Vector3D(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
}
public Vector3D()
{
X = 0;
Y = 0;
Z = 0;
}
public static Vector3D operator +(Vector3D w, Vector3D v) {
return new Vector3D(w.X + v.X, w.Y + v.Y, w.Z + v.Z);
}
public static Vector3D operator -(Vector3D w, Vector3D v)
{
return new Vector3D(w.X - v.X, w.Y - v.Y, w.Z - v.Z);
}
public static Vector3D operator *(double a, Vector3D v)
{
return new Vector3D(a * v.X, a * v.Y, a * v.Z);
}
public Vector3D RotateVector(Vector3D axis, double angle)
{
Vector3D vxp = axis.Cross(this);
Vector3D vxvxp = axis.Cross(vxp);
return this + Math.Sin(angle) * vxp + (1 - Math.Cos(angle)) * vxvxp;
}
public Vector3D RotateAboutPoint(Vector3D pivot, Vector3D axis, double angle)
{
return pivot + (this - pivot).RotateVector(axis, angle);
}
public void Normalize()
{
var div = 1 / Length;
X *= div;
Y *= div;
Z *= div;
}
public Vector3D Cross(Vector3D v)
{
return new Vector3D(Y * v.Z - Z * v.Y, - X * v.Z + Z * v.X, X * v.Y - Y * v.X);
}
}
Subtract the point of rotation from the vector
Rotate using a rotation matrix or whatever way you want.
Add the point of rotation to the result
In [C#] world the above is as follows:
/// <summary>
/// Rotates a vector using the Rodriguez rotation formula
/// about an arbitrary axis.
/// </summary>
/// <param name="vector">The vector to be rotated.</param>
/// <param name="axis">The rotation axis.</param>
/// <param name="angle">The rotation angle.</param>
/// <returns>The rotated vector</returns>
public static Vector3 RotateVector(Vector3 vector, Vector3 axis, float angle)
{
Vector3 vxp = Vector3.Cross(axis, vector);
Vector3 vxvxp = Vector3.Cross(axis, vxp);
return vector + Sin(angle) * vxp + (1 - Cos(angle)) * vxvxp;
}
/// <summary>
/// Rotates a vector about a point in space.
/// </summary>
/// <param name="vector">The vector to be rotated.</param>
/// <param name="pivot">The pivot point.</param>
/// <param name="axis">The rotation axis.</param>
/// <param name="angle">The rotation angle.</param>
/// <returns>The rotated vector</returns>
public static Vector3 RotateVectorAboutPoint(Vector3 vector, Vector3 pivot, Vector3 axis, float angle)
{
return pivot+ RotateVector(vector - pivot, axis, angle);
}
with the driver code
static void Main(string[] args)
{
Vector3 vector = new Vector3(3.0f, 2.0f, 4.0f);
float angle = 3.141592653f;
Vector3 pivot = new Vector3(0.0f, 0.0f, 0.0f);
Vector3 axis = new Vector3(0.0f, 1.0f, 0.0f);
Vector3 result = RotateAboutPoint(vector, pivot, axis, angle);
Console.WriteLine(result);
// <-3, 2, -4>
}

Sometimes entering an infinite loop in EPA algorithm when calculating contact normal and penetration depth

Currently I'm in the middle of developing a 2D physics engine by scratch. And Everything worked well by far. It's object oriented, obviously, with Vectors, matrices and shape classes written from scratch by myself. Collision are not yet that organized, but before that I need to make sure it works.
So for educational purposes I decided to implement GJK algorithm for collision detection and EPA for calculating contact normal. The GJK part works fine. And from the simplex (Triangle since it's 2D) I calculate the closest edge and from there I followed the algorithm. But for some reason which I can't seem to find, in some instances only, the program enters an infinite loop, adding new Vertices to the vertices list, but when I print them they're all the same, And the difference that should be near 0 in EPA gives big results. such as 70.55. So I hope you'll be able to help. Thank you in advance.
public class Collision
{
private Shape shape1;
private Shape shape2;
public List<Vector2> vertices;
private Vector2 direction;
private double TOLERENCE = 2;
public Vector2 contactNormal;
public double penetrationDepth;
private enum State
{
NO_INTERSECTION, FOUND_INTERSECTION, STILL_EVOLVING
}
private State result = State.STILL_EVOLVING;
public Collision(Shape shape1, Shape shape2)
{
this.shape1 = shape1;
this.shape2 = shape2;
vertices = new List<Vector2>();
}
public bool isColliding()
{
while (result == State.STILL_EVOLVING)
{
result = evolveSimplex();
}
if (result == State.FOUND_INTERSECTION)
findContactDetails();
return result == State.FOUND_INTERSECTION;
}
private State evolveSimplex()
{
if (vertices.Count == 0)
{
direction = shape1.position - shape2.position;
}
else if (vertices.Count == 1)
{
direction = -direction;
}
else if (vertices.Count == 2)
{
//ab is the vector from the 1st vertex to the second one
Vector2 ab = vertices[1] - vertices[0];
//a0 is the vector from the 1st vertex to the origin
Vector2 a0 = -vertices[0];
//Get the direction that is perpendicular to ab in the direction of the origin.
direction = ab.getPerpendicularUnitVectorTowards(a0);
}
else if (vertices.Count == 3)
{
//a,b and c are the three vertices
Vector2 a = vertices[0];
Vector2 b = vertices[1];
Vector2 c = vertices[2];
//lets find cb, ca and c0
Vector2 cb = b - c;
Vector2 ca = a - c;
Vector2 c0 = -c;
// let's find the perpendicular directions to cb and ca that are towards outside the simplex.
Vector2 cbPerp = cb.getPerpendicularUnitVectorTowards(c0);
Vector2 caPerp = ca.getPerpendicularUnitVectorTowards(c0);
if (cbPerp * c0 < 0)
{
vertices.Remove(a);
direction = cbPerp;
}
else if (caPerp * c0 < 0)
{
vertices.Remove(b);
direction = caPerp;
}
else
{
return State.FOUND_INTERSECTION;
}
}
return addSupport(direction) ? State.STILL_EVOLVING : State.NO_INTERSECTION;
}
private bool addSupport(Vector2 direction)
{
Vector2 support = shape1.getSupportPoint(direction) - shape2.getSupportPoint(-direction);
vertices.Add(support);
return direction * support > 0;
}
private void findContactDetails()
{
while (true)
{
Edge closestEdge = findClosestEdge(vertices);
Vector2 support = shape1.getSupportPoint(closestEdge.normal) - shape2.getSupportPoint(-closestEdge.normal);
double distance = support * closestEdge.normal;
Console.WriteLine("difference between the two = " + (Math.Abs(distance - closestEdge.distance)) + " and it's " + (Math.Abs(distance - closestEdge.distance) < TOLERENCE));
if (Math.Abs(distance - closestEdge.distance) < TOLERENCE)
{
Console.WriteLine("Contact details found");
contactNormal = closestEdge.normal;
penetrationDepth = distance;
break;
}
Console.WriteLine("That was not the closest edge. adding the support to the vertices at index " + closestEdge.index);
vertices.Insert(closestEdge.index, support);
Console.WriteLine("The number of vertices now = " + vertices.Count);
for (int i = 0; i < vertices.Count; i++)
{
Form1.dots.Add(vertices[i]);
}
}
}
private Edge findClosestEdge(List<Vector2> simplex)
{
Console.WriteLine("Finding closest edge");
Edge closest = new Edge();
closest.distance = Double.PositiveInfinity;
for (int i = 0; i < simplex.Count; i++)
{
Console.WriteLine("i = " + i);
int j = i + 1 == simplex.Count ? 0 : i + 1;
Vector2 a = simplex[i];
Vector2 b = simplex[j];
Vector2 edge = b - a;
Vector2 oa = a;
Vector2 normal = edge.getPerpendicularUnitVectorTowards(oa);
double d = Math.Abs(normal * oa);
Console.WriteLine("distance from origin to edge = " + d);
if (d < closest.distance)
{
closest.distance = d;
closest.normal = normal;
closest.index = j;
}
}
Console.WriteLine("returing closest edge " + closest.index + " distance = " + closest.distance);
return closest;
}
}
And Heres the vector2 class.
public class Vector2
{
public double x;
public double y;
public static Vector2 zeroVector2 = new Vector2(0,0);
public Vector2(double x, double y)
{
this.x = x;
this.y = y;
}
public Vector2 add(Vector2 vector)
{
return new Vector2(x + vector.x, y + vector.y);
}
public Vector2 subtract(Vector2 vector)
{
return new Vector2(x - vector.x, y - vector.y);
}
public double dot(Vector2 vector)
{
return x * vector.x + y * vector.y;
}
public Vector2 multiply(double scalar)
{
return new Vector2(x*scalar, y*scalar);
}
public double getMagnitude()
{
return Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2));
}
public void negate()
{
x = -x;
y = -y;
}
public Vector2 negated()
{
return new Vector2(-x, -y);
}
public bool isZeroVector()
{
if (x == 0 && y == 0)
return true;
else return false;
}
public Vector2 normalized()
{
return this * (1 / getMagnitude());
}
public void normalize()
{
x *= (1 / getMagnitude());
y *= (1 / getMagnitude());
}
public static Vector2 operator -(Vector2 vector)
{
return vector.negated();
}
public static Vector2 operator +(Vector2 v1, Vector2 v2)
{
return v1.add(v2);
}
public static Vector2 operator -(Vector2 v1, Vector2 v2)
{
return v1.subtract(v2);
}
public static double operator *(Vector2 v1, Vector2 v2)
{
return v1.dot(v2);
}
public static Vector2 operator *(Vector2 v, double scalar)
{
return v.multiply(scalar);
}
public static Vector2 operator *(double scalar, Vector2 v)
{
return v.multiply(scalar);
}
public static bool operator ==(Vector2 v1, Vector2 v2)
{
if (v1.x == v2.x && v1.y == v2.y)
return true;
return false;
}
public static bool operator !=(Vector2 v1, Vector2 v2)
{
if (v1.x == v2.x && v1.y == v2.y)
return false;
return true;
}
public override string ToString()
{
return "x = " + x + " , y = " + y;
}
public Vector3 to3DVector()
{
return new Vector3(x, y, 0);
}
public Matrix toMatrix()
{
return new Matrix(new double[2,1]{{x}, {y}});
}
public Vector2 rotate(double degrees)
{
return (Matrix.getRotationMatrix(degrees) * this.toMatrix()).toVector2();
}
public static Vector2 toVector2(Point point)
{
return new Vector2(point.X, point.Y);
}
public Point toPoint()
{
return new Point((int)x,(int)y);
}
public Vector2 getPerpendicularUnitVectorTowards(Vector2 direction)
{
Vector2 perp = new Vector2(-y, x) * (1 / getMagnitude());
Console.WriteLine(perp);
if (perp * direction < 0)
perp = -perp;
return perp;
}
}

Unity - performant options for loading large numbers of vertices to ComputeShader for raytracing?

I've been doing some self-guided training in ComputeShaders using Daerst's awesome Unity Raytracing tutorials (from http://blog.three-eyed-games.com/2018/05/03/gpu-ray-tracing-in-unity-part-1/).
I'm currently in the process of extending the raytracer to accept arbitrary mesh objects so I can visualize other objects, rather than just spheres and planes. I wrote my own implementation of Moeller-Trombore using the Wikipedia implementation of a guide, and that worked as expected for low numbers of triangles (order 700).
However, I'm finding that writing a mesh with 80,000 vertices to the buffer is taking an obscene amount of time on the order of minutes. I know this vertex count is pretty low by rendering standards, so I figure there must be something in the way I'm handling this that's causing the performance issues.
To clarify, my performance issue isn't in terms of FPS once the mesh is loaded- for my purposes, anything greater than .1FPS is great! It's loading the mesh that feels like it's going way too slowly.
Here's my ComputeShader code:
#pragma kernel CSMain
RWTexture2D<float4> Result;
float4x4 _CameraToWorld;
float4x4 _CameraInverseProjection;
float4 _DirectionalLight;
float2 _PixelOffset;
Texture2D<float4> _SkyboxTexture;
SamplerState sampler_SkyboxTexture;
static const float PI = 3.14159265f;
//-------------------------------------
//- UTILITY
float sdot(float3 x, float3 y, float f = 1.0f)
{
return saturate(dot(x, y) * f);
}
float energy(float3 color)
{
return dot(color, 1.0f / 3.0f);
}
//-------------------------------------
//- RANDOMNESS
float2 _Pixel;
float _Seed;
float rand()
{
float result = frac(sin(_Seed / 100.0f * dot(_Pixel, float2(12.9898f, 78.233f))) * 43758.5453f);
_Seed += 1.0f;
return result;
}
struct Geometry
{
uint type; //4;
float smoothness; //8;
float3 albedo; //20;
float3 specular; //32;
float3 emission; //44;
int3 verts; //56
};
StructuredBuffer<Geometry> _Geometries;
StructuredBuffer<float3> _Vertices;
//-------------------------------------
//- RAY
struct Ray
{
float3 origin;
float3 direction;
float3 energy;
};
Ray CreateRay(float3 origin, float3 direction)
{
Ray ray;
ray.origin = origin;
ray.direction = direction;
ray.energy = float3(1.0f, 1.0f, 1.0f);
return ray;
}
Ray CreateCameraRay(float2 uv)
{
// Transform the camera origin to world space
float3 origin = mul(_CameraToWorld, float4(0.0f, 0.0f, 0.0f, 1.0f)).xyz;
// Invert the perspective projection of the view-space position
float3 direction = mul(_CameraInverseProjection, float4(uv, 0.0f, 1.0f)).xyz;
// Transform the direction from camera to world space and normalize
direction = mul(_CameraToWorld, float4(direction, 0.0f)).xyz;
direction = normalize(direction);
return CreateRay(origin, direction);
}
//-------------------------------------
//- RAYHIT
struct RayHit
{
float3 position;
float distance;
float3 normal;
float3 albedo;
float3 specular;
float smoothness;
float3 emission;
};
RayHit CreateRayHit()
{
RayHit hit;
hit.position = float3(0.0f, 0.0f, 0.0f);
hit.distance = 1.#INF;
hit.normal = float3(0.0f, 0.0f, 0.0f);
hit.albedo = float3(0.0f, 0.0f, 0.0f);
hit.specular = float3(0.0f, 0.0f, 0.0f);
hit.smoothness = 0.0f;
hit.emission = float3(0.0f, 0.0f, 0.0f);
return hit;
}
//-------------------------------------
//- POLYGONS
struct Triangle
{
float3 vertexA; //12
float3 vertexB; //24
float3 vertexC; //36
float3 albedo; //48
float3 specular; //60
float smoothness; //64
float3 emission; //76
};
float3 GetTriangleNormal(float3 vA, float3 vB, float3 vC)
{
return cross(vB-vA, vC-vA);
}
Triangle TriangleFromGeometry(Geometry geometry)
{
Triangle tri;
tri.albedo = geometry.albedo;
tri.specular = geometry.specular;
tri.smoothness = geometry.smoothness;
tri.emission = geometry.emission;
tri.vertexA = _Vertices[geometry.verts[0]];
tri.vertexB = _Vertices[geometry.verts[1]];
tri.vertexC = _Vertices[geometry.verts[2]];
return tri;
}
void IntersectTriangle(Ray ray, inout RayHit bestHit, Triangle tri)
{
float epsilon = 0.0000001;
float3 pA = tri.vertexA;
float3 pB = tri.vertexB;
float3 pC = tri.vertexC;
float3 edge1 = pB - pA;
float3 edge2 = pC - pA;
float3 rayVector = ray.direction;// - ray.origin;
float3 h = cross(rayVector, edge2);
float a = dot(edge1, h);
if (a > -epsilon && a < epsilon)
{
return;
}
float f = 1/a;
float3 s = ray.origin - pA;
float u = f * dot(s, h);
if (u < 0.0f || u> 1.0f)
{
return;
}
float3 q = cross(s, edge1);
float v = f * dot(rayVector, q);
if (v < 0.0 || u + v > 1.0)
{
return;
}
float t = f * dot(edge2, q);
if (t > epsilon && t < bestHit.distance)
{
bestHit.distance = t;
bestHit.position = ray.origin + rayVector * t;
bestHit.normal = GetTriangleNormal(pA, pB, pC);
bestHit.albedo = tri.albedo;
bestHit.specular = tri.specular;
bestHit.smoothness = tri.smoothness;
bestHit.emission = tri.emission;
}
}
void IntersectGeometry(Ray ray, inout RayHit bestHit, Geometry geometry) {
if (geometry.type == 1) {
Triangle tri = TriangleFromGeometry(geometry);
IntersectTriangle(ray, bestHit,tri);
}
}
//-------------------------------------
//- TRACE
RayHit Trace(Ray ray)
{
RayHit bestHit = CreateRayHit();
uint numGeometries, geometryStride;
_Geometries.GetDimensions(numGeometries, geometryStride);
for (uint i = 0; i < numGeometries; i++)
{
IntersectGeometry(ray, bestHit, _Geometries[i]);
}
return bestHit;
}
//-------------------------------------
//- SAMPLING
float3x3 GetTangentSpace(float3 normal)
{
// Choose a helper vector for the cross product
float3 helper = float3(1, 0, 0);
if (abs(normal.x) > 0.99f)
helper = float3(0, 0, 1);
// Generate vectors
float3 tangent = normalize(cross(normal, helper));
float3 binormal = normalize(cross(normal, tangent));
return float3x3(tangent, binormal, normal);
}
float3 SampleHemisphere(float3 normal, float alpha)
{
// Sample the hemisphere, where alpha determines the kind of the sampling
float cosTheta = pow(rand(), 1.0f / (alpha + 1.0f));
float sinTheta = sqrt(1.0f - cosTheta * cosTheta);
float phi = 2 * PI * rand();
float3 tangentSpaceDir = float3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
// Transform direction to world space
return mul(tangentSpaceDir, GetTangentSpace(normal));
}
//-------------------------------------
//- SHADE
float SmoothnessToPhongAlpha(float s)
{
return pow(1000.0f, s * s);
}
float3 Shade(inout Ray ray, RayHit hit)
{
if (hit.distance < 1.#INF)
{
// Calculate chances of diffuse and specular reflection
hit.albedo = min(1.0f - hit.specular, hit.albedo);
float specChance = energy(hit.specular);
float diffChance = energy(hit.albedo);
// Roulette-select the ray's path
float roulette = rand();
if (roulette < specChance)
{
// Specular reflection
ray.origin = hit.position + hit.normal * 0.001f;
float alpha = SmoothnessToPhongAlpha(hit.smoothness);
ray.direction = SampleHemisphere(reflect(ray.direction, hit.normal), alpha);
float f = (alpha + 2) / (alpha + 1);
ray.energy *= (1.0f / specChance) * hit.specular * sdot(hit.normal, ray.direction, f);
}
else if (diffChance > 0 && roulette < specChance + diffChance)
{
// Diffuse reflection
ray.origin = hit.position + hit.normal * 0.001f;
ray.direction = SampleHemisphere(hit.normal, 1.0f);
ray.energy *= (1.0f / diffChance) * hit.albedo;
}
else
{
// Terminate ray
ray.energy = 0.0f;
}
return hit.emission;
}
else
{
// Erase the ray's energy - the sky doesn't reflect anything
ray.energy = 0.0f;
// Sample the skybox and write it
float theta = acos(ray.direction.y) / -PI;
float phi = atan2(ray.direction.x, -ray.direction.z) / -PI * 0.5f;
return _SkyboxTexture.SampleLevel(sampler_SkyboxTexture, float2(phi, theta), 0).xyz;
}
}
//-------------------------------------
//- KERNEL
[numthreads(16,16,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
_Pixel = id.xy;
// Get the dimensions of the RenderTexture
uint width, height;
Result.GetDimensions(width, height);
// Transform pixel to [-1,1] range
float2 uv = float2((id.xy + _PixelOffset) / float2(width, height) * 2.0f - 1.0f);
// Get a ray for the UVs
Ray ray = CreateCameraRay(uv);
// Trace and shade the ray
float3 result = float3(0, 0, 0);
for (int i = 0; i < 4; i++)
{
RayHit hit = Trace(ray);
result += ray.energy * Shade(ray, hit);
if (!any(ray.energy))
break;
}
Result[id.xy] = float4(result, 1);
}
and here's the C# class which writes information to the shader:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;
public class RayTracingMaster : MonoBehaviour
{
public ComputeShader RayTracingShader;
private RenderTexture _target;
public Texture SkyboxTexture;
private uint _currentSample = 0;
private Material _addMaterial;
private ComputeBuffer _geometryBuffer;
private ComputeBuffer _vertexBuffer;
private Camera _camera;
public Light DirectionalLight;
private RenderTexture _converged;
public int SphereSeed = 2018;
public List<Geometry> geometries = new List<Geometry>();
private List<Vector3> _vertices = new List<Vector3>();
private void Awake()
{
_camera = GetComponent<Camera>();
}
private void Update()
{
if (transform.hasChanged)
{
_currentSample = 0;
transform.hasChanged = false;
}
if (DirectionalLight.transform.hasChanged)
{
_currentSample = 0;
DirectionalLight.transform.hasChanged = false;
}
}
private void OnEnable()
{
_currentSample = 0;
SetUpScene();
}
private void OnDisable()
{
if (_geometryBuffer != null)
_geometryBuffer.Release();
}
private void SetUpScene()
{
Random.InitState(SphereSeed);
Profiler.BeginSample("Geometry Buffer Creation");
_geometryBuffer = new ComputeBuffer(geometries.Count, 56);
_geometryBuffer.SetData(geometries);
_vertexBuffer = new ComputeBuffer(_vertices.Count, 12);
_vertexBuffer.SetData(_vertices);
Profiler.EndSample();
}
private void SetShaderParameters()
{
RayTracingShader.SetMatrix("_CameraToWorld", _camera.cameraToWorldMatrix);
RayTracingShader.SetMatrix("_CameraInverseProjection", _camera.projectionMatrix.inverse);
RayTracingShader.SetVector("_PixelOffset", new Vector2(Random.value, Random.value));
Vector3 l = DirectionalLight.transform.forward;
RayTracingShader.SetVector("_DirectionalLight", new Vector4(l.x, l.y, l.z, DirectionalLight.intensity));
RayTracingShader.SetInt("numGeometry",_geometryBuffer.count);
Debug.Log("Geometries: " + _geometryBuffer.count);
RayTracingShader.SetBuffer(0, "_Geometries", _geometryBuffer);
RayTracingShader.SetBuffer(0, "_Vertices", _vertexBuffer);
RayTracingShader.SetFloat("_Seed", Random.value);
RayTracingShader.SetTexture(0, "_SkyboxTexture", SkyboxTexture);
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
SetShaderParameters();
Render(destination);
}
private void Render(RenderTexture destination)
{
// Make sure we have a current render target
InitRenderTexture();
// Set the target and dispatch the compute shader
RayTracingShader.SetTexture(0, "Result", _target);
int threadGroupsX = Mathf.CeilToInt(Screen.width / 8.0f);
int threadGroupsY = Mathf.CeilToInt(Screen.height / 8.0f);
RayTracingShader.Dispatch(0, threadGroupsX, threadGroupsY, 1);
// Blit the result texture to the screen
if (_addMaterial == null)
_addMaterial = new Material(Shader.Find("Hidden/AddShader"));
_addMaterial.SetFloat("_Sample", _currentSample);
Graphics.Blit(_target, destination, _addMaterial);
_currentSample++;
}
private void InitRenderTexture()
{
if ((_target == null || _target.width != Screen.width || _target.height != Screen.height) || (_converged == null || _converged.width != Screen.width || _converged.height != Screen.height))
{
// Release render texture if we already have one
if (_target != null)
_target.Release();
// Get a render target for Ray Tracing
_target = new RenderTexture(Screen.width, Screen.height, 0,
RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear);
_target.enableRandomWrite = true;
_target.Create();
if (_converged != null)
{
_converged.Release();
}
_converged = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.ARGBFloat,RenderTextureReadWrite.Linear);
_converged.enableRandomWrite = true;
_converged.Create();
_currentSample = 0;
}
}
public Geometry[] ShaderGeometryFromMesh(Mesh mesh, Vector3 albedo, Vector3 specular, float smoothness, Vector3 emission, Vector3 worldScale, Quaternion worldRotation) {
Profiler.BeginSample("Shader Geometry Creation");
Geometry[] geometry = new Geometry[mesh.triangles.Length/3];
for (int i = 0; i < geometry.Length; i++)
{
int[] vertIndices = new int[3];
for (int j = 0; j < 3; j++)
{
Vector3 vert = mesh.vertices[mesh.triangles[i * 3 + j]];
if (!_vertices.Contains(vert))
{
_vertices.Add(vert);
}
vertIndices[j] = _vertices.IndexOf(vert);
}
geometry[i] = new Geometry(albedo, specular, smoothness, emission,vertIndices);
}
Profiler.EndSample();
return geometry;
}
public struct Geometry
{
public uint type;
public Vector3 albedo;
public Vector3 specular;
public float smoothness;
public Vector3 emission;
public Vector3Int verts;
public Geometry( Vector3 albedo, Vector3 specular, float smoothness, Vector3 emission, int[] verts, uint type = 1) {
this.albedo = albedo;
this.specular = specular;
this.smoothness = smoothness;
this.emission = emission;
this.verts = new Vector3Int(verts[0],verts[1],verts[2]);
this.type = type;
}
}
}
Turns out I didn't have the problem I thought I had at all! my bottleneck was the ShaderGeometryFromMesh() method attempted to avoid duplicate vertices by checking if each and every vertex being added was unique. Obviously, this took a ton of time as the list expanded to thousands or tens of thousands of items. Uploading to the buffer is relatively trivial.

How to find the intersection point of a ray and a triangle?

I find the trilinear coordinates of the coordinate of the point of intersection through the barycentric coordinates. Barycentric coordinates are correct (seemingly).
private const double Epsilon = 0.000001d;
public static Vector3? GetPointIntersectionRayAndTriangle(Vector3 rayOrigin, Vector3 rayDirection, Vector3 vert0, Vector3 vert1, Vector3 vert2)
{
Vector3 edge1 = new Vector3();
Vector3 edge2 = new Vector3();
Vector3 tvec = new Vector3();
Vector3 pvec = new Vector3();
Vector3 qvec = new Vector3();
double det, invDet;
edge1 = vert1 - vert0;
edge2 = vert2 - vert0;
pvec = Cross(rayDirection, edge2);
det = Dot(edge1, pvec);
if (det > -Epsilon && det < Epsilon)
{
return null;
}
invDet = 1d / det;
tvec = rayOrigin - vert0;
double t, u, v;
u = Dot(tvec, pvec) * invDet;
if (u < 0 || u > 1)
{
return null;
}
qvec = Cross(tvec, edge1);
v = Dot(rayDirection, qvec) * invDet;
if (v < 0 || u + v > 1)
{
return null;
}
t = Dot(edge2, qvec) * invDet;
return GetTrilinearCoordinates(t, u, v, vert0, vert1, vert2);
}
private static double Dot(Vector3 v1, Vector3 v2)
{
return v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z;
}
private static Vector3 Cross(Vector3 v1, Vector3 v2)
{
Vector3 dest;
dest.X = v1.Y * v2.Z - v1.Z * v2.Y;
dest.Y = v1.Z * v2.X - v1.X * v2.Z;
dest.Z = v1.X * v2.Y - v1.Y * v2.X;
return dest;
}
private static Vector3 GetTrilinearCoordinates(double t, double u, double v, Vector3 vert0, Vector3 vert1, Vector3 vert2)
{
float a = (vert0 - vert1).Length();
float b = (vert1 - vert2).Length();
float c = (vert2 - vert0).Length();
return new Vector3((float)t / a, (float)u / b, (float)v / c);
}
rayOrigin - beginning of the ray.
vert0, vert1, vert2 - coordinates
of the triangle.
I use this unit test to check:
[TestMethod]
public void GetPointIntersectionRayAndTriangleCheckOnResult()
{
Vector3? vector1 = ComputationsInThreeDimensionalSpace.GetPointIntersectionRayAndTriangle(
new Vector3(1, 1, 2),
new Vector3(0, 0, -4),
new Vector3(0, 0, 0),
new Vector3(4, -1, 0),
new Vector3(0, 5, 0));
if (!vector1.HasValue)
{
Assert.Fail();
}
Assert.AreEqual(new Vector3(1, 1, 0), vector1.Value);
}
Are there other ways to find the point of intersection of a ray with a triangle? It is desirable without barycentric coordinates.
t is not a barycentric coordinate, but the distance from the origin to the intersection, so should not be passed to GetTrilinearCoordinates. Instead you should pass 1 - u - v, because Moller-Trumbore returns normalized Barycentric coordinates.
This is the working code for finding the point where the ray hits the triangle. GetTimeAndUvCoord returns null if the beam does not hit the triangle
The function GetTimeAndUvCoord finds T and UV. The GetTrilinearCoordinateOfTheHit function returns XYZ.
private const double Epsilon = 0.000001d;
public static Vector3? GetTimeAndUvCoord(Vector3 rayOrigin, Vector3 rayDirection, Vector3 vert0, Vector3 vert1, Vector3 vert2)
{
var edge1 = vert1 - vert0;
var edge2 = vert2 - vert0;
var pvec = Cross(rayDirection, edge2);
var det = Dot(edge1, pvec);
if (det > -Epsilon && det < Epsilon)
{
return null;
}
var invDet = 1d / det;
var tvec = rayOrigin - vert0;
var u = Dot(tvec, pvec) * invDet;
if (u < 0 || u > 1)
{
return null;
}
var qvec = Cross(tvec, edge1);
var v = Dot(rayDirection, qvec) * invDet;
if (v < 0 || u + v > 1)
{
return null;
}
var t = Dot(edge2, qvec) * invDet;
return new Vector3((float)t, (float)u, (float)v);
}
private static double Dot(Vector3 v1, Vector3 v2)
{
return v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z;
}
private static Vector3 Cross(Vector3 v1, Vector3 v2)
{
Vector3 dest;
dest.X = v1.Y * v2.Z - v1.Z * v2.Y;
dest.Y = v1.Z * v2.X - v1.X * v2.Z;
dest.Z = v1.X * v2.Y - v1.Y * v2.X;
return dest;
}
public static Vector3 GetTrilinearCoordinateOfTheHit(float t, Vector3 rayOrigin, Vector3 rayDirection)
{
return rayDirection * t + rayOrigin;
}

Finding Circle-Line collision (as points, not bools) C#

So unlike everyone here, I'm trying to find the intersections from circle-line collision.
The points are taken from the user input.
float cx; //circle center x
float cy; //circle center y
float px; //point x
float py; //point y
float vx; //vector x
float vy; //vector y
float vySq = vy * vy;
float vxSq = vx * vx;
float cxSq = cx * cx;
float rSq = r * r;
float thatPart = ( ( (vy * px) / vx) + py - cy); //so I don't have to re-type it 3 different times
float a = 1 + (vySq/vxSq);
float b = (2 * cx) + ( (2 * vy) * thatPart / vx);
float c = cxSq + ( thatPart * thatPart ) - rSq;
float x1 = QUADRATIC(a, b, c, true);
float x2 = QUADRATIC(a, b, c, false);
float y1 = ((vy * x1) - (vy * px) + (vx * py)) / vx;
float y2 = ((vy * x2) - (vy * px) + (vx * py)) / vx;
My QUADRATIC function 100% works, so I am not worried about that.
It's a, b, and c. I receive the wrong values for them. And all this math is based on a pdf given in math class. (here)
So... what am I doing wrong?
Thanks.
The equations in both the PDF and the source code seem unnecessarily complicated, so I will re-establish them in a clenear way (or at least one that pleases me).
The vector from the center of the circle to any point S on the line can be written as
CS = CP + t V
where t is an arbitrary parameter.
S belongs to the circle when
CS² = R² = (CP + t V)²
giving the quadratic equation in t:
V² t² + 2 CP.V t + CP² - R² = 0
When you have the values of t, plug in the first equation.
Math Treatment
You have a line with coordinates (x,y) = (px + vx*t, py + vy*t) where t is an arbitrary parameter.
The line intersects the circle when its coordinates solve x^2 + y^2 = r^2.
This leads to the following quadratic equation, to be solved for t.
t^2 + 2*t*b + a = 0
a = (px^2+py^2-r^2)/(vx^2+vy^2)
b = (px*vx+py*vy)/(vx^2+vy^2)
The two solutions are:
t = -b ± sqrt(b^2-a)
Use the two t values into (x,y) to get the coordinates.
Sample Code
static class Program
{
static void Main(string[] args)
{
var ray = new Ray() {
Point = new Vector2(-3, 1),
Direction = new Vector2(2, 0.5f)
};
var circle = new Circle
{
Center = new Vector2(1, 1),
Radius = 4
};
var points = Geometry.Intersect(circle, ray);
if(points.Length>0)
{
foreach(var point in points)
{
Console.WriteLine(point.ToString());
}
}
else
{
Console.WriteLine("Circle and Ray do not intersect");
}
}
}
Object Oriented Models
public class Circle
{
public Vector2 Center { get; set; }
public float Radius { get; set; }
}
public class Ray
{
public Vector2 Point { get; set; }
public Vector2 Direction { get; set; }
public Vector2 Along(float t)
{
return Point + t*Direction;
}
}
public static class Geometry
{
public static Vector2[] Intersect(Circle circle, Ray ray)
{
float a = (ray.Point.LengthSquared()-circle.Radius*circle.Radius)/ray.Direction.LengthSquared();
float b = Vector2.Dot(ray.Point, ray.Direction)/ray.Direction.LengthSquared();
if(b*b-a>0)
{
// two intersection points
float t1 = -b-(float)Math.Sqrt(b*b-a);
float t2 = -b+(float)Math.Sqrt(b*b-a);
return new Vector2[] {
ray.Along(t1),
ray.Along(t2),
};
}
else if(b*b-a==0)
{
// one intersection point
float t = -b;
return new Vector2[] { ray.Along(t) };
}
else
{
// no intersection, return empty array
return new Vector2[0];
}
}
}
NOTE: Code uses System.Numerics library

Categories