Permalink
Browse files

C# (Unity/Burst): update with C++ side optimizations (SoA, algebraic …

…simplifications, list of emissive objects)

- PC 155 -> 165 Mray/s
- Mac 45.1 -> 47.9 Mray/s
  • Loading branch information...
aras-p committed Apr 16, 2018
1 parent 5895cb3 commit 577d3758991561057366280d69b0989c8667a1d6
Showing with 115 additions and 57 deletions.
  1. +82 −24 Unity/Assets/Maths.cs
  2. +32 −33 Unity/Assets/Test.cs
  3. +1 −0 Unity/Assets/TestScript.cs
@@ -1,3 +1,4 @@
using Unity.Collections;
using Unity.Mathematics;
using static Unity.Mathematics.math;
@@ -99,39 +100,96 @@ public struct Sphere
{
public float3 center;
public float radius;
public float invRadius;
public Sphere(float3 center_, float radius_) { center = center_; radius = radius_; invRadius = 1.0f / radius_; }
public void UpdateDerivedData() { invRadius = 1.0f / radius; }
public Sphere(float3 center_, float radius_) { center = center_; radius = radius_; }
}
public bool HitSphere(Ray r, float tMin, float tMax, ref Hit outHit)
struct SpheresSoA
{
[ReadOnly] public NativeArray<float> centerX;
[ReadOnly] public NativeArray<float> centerY;
[ReadOnly] public NativeArray<float> centerZ;
[ReadOnly] public NativeArray<float> sqRadius;
[ReadOnly] public NativeArray<float> invRadius;
[ReadOnly] public NativeArray<int> emissives;
public int emissiveCount;
public SpheresSoA(int len)
{
float3 oc = r.orig - center;
float b = dot(oc, r.dir);
float c = dot(oc, oc) - radius * radius;
float discr = b * b - c;
if (discr > 0)
{
float discrSq = sqrt(discr);
centerX = new NativeArray<float>(len, Allocator.Persistent);
centerY = new NativeArray<float>(len, Allocator.Persistent);
centerZ = new NativeArray<float>(len, Allocator.Persistent);
sqRadius = new NativeArray<float>(len, Allocator.Persistent);
invRadius = new NativeArray<float>(len, Allocator.Persistent);
emissives = new NativeArray<int>(len, Allocator.Persistent);
emissiveCount = 0;
}
float t = (-b - discrSq);
if (t < tMax && t > tMin)
public void Dispose()
{
centerX.Dispose();
centerY.Dispose();
centerZ.Dispose();
sqRadius.Dispose();
invRadius.Dispose();
emissives.Dispose();
}
public void Update(Sphere[] src, Material[] mat)
{
emissiveCount = 0;
for (var i = 0; i < src.Length; ++i)
{
Sphere s = src[i];
centerX[i] = s.center.x;
centerY[i] = s.center.y;
centerZ[i] = s.center.z;
sqRadius[i] = s.radius * s.radius;
invRadius[i] = 1.0f / s.radius;
if (mat[i].HasEmission)
{
outHit.pos = r.PointAt(t);
outHit.normal = (outHit.pos - center) * invRadius;
outHit.t = t;
return true;
emissives[emissiveCount++] = i;
}
t = (-b + discrSq);
if (t < tMax && t > tMin)
}
}
public int HitSpheres(ref Ray r, float tMin, float tMax, ref Hit outHit)
{
float hitT = tMax;
int id = -1;
for (int i = 0; i < centerX.Length; ++i)
{
float coX = centerX[i] - r.orig.x;
float coY = centerY[i] - r.orig.y;
float coZ = centerZ[i] - r.orig.z;
float nb = coX * r.dir.x + coY * r.dir.y + coZ * r.dir.z;
float c = coX * coX + coY * coY + coZ * coZ - sqRadius[i];
float discr = nb * nb - c;
if (discr > 0)
{
outHit.pos = r.PointAt(t);
outHit.normal = (outHit.pos - center) * invRadius;
outHit.t = t;
return true;
float discrSq = sqrt(discr);
// Try earlier t
float t = nb - discrSq;
if (t <= tMin) // before min, try later t!
t = nb + discrSq;
if (t > tMin && t < hitT)
{
id = i;
hitT = t;
}
}
}
return false;
if (id != -1)
{
outHit.pos = r.PointAt(hitT);
outHit.normal = (outHit.pos - new float3(centerX[id], centerY[id], centerZ[id])) * invRadius[id];
outHit.t = hitT;
return id;
}
else
return -1;
}
}
@@ -52,30 +52,30 @@ class Test
new Material(Material.Type.Lambert, new float3(0.8f, 0.6f, 0.2f), new float3(30,25,15), 0, 0),
};
SpheresSoA s_SpheresSoA;
public Test()
{
s_SpheresSoA = new SpheresSoA(s_SpheresData.Length);
}
public void Dispose()
{
s_SpheresSoA.Dispose();
}
const float kMinT = 0.001f;
const float kMaxT = 1.0e7f;
const int kMaxDepth = 10;
static bool HitWorld(Ray r, float tMin, float tMax, ref Hit outHit, ref int outID, NativeArray<Sphere> spheres)
static bool HitWorld(Ray r, float tMin, float tMax, ref Hit outHit, ref int outID, ref SpheresSoA spheres)
{
Hit tmpHit = default(Hit);
bool anything = false;
float closest = tMax;
for (int i = 0; i < spheres.Length; ++i)
{
if (spheres[i].HitSphere(r, tMin, closest, ref tmpHit))
{
anything = true;
closest = tmpHit.t;
outHit = tmpHit;
outID = i;
}
}
return anything;
outID = spheres.HitSpheres(ref r, tMin, tMax, ref outHit);
return outID != -1;
}
static bool Scatter(Material mat, Ray r_in, Hit rec, out float3 attenuation, out Ray scattered, out float3 outLightE, ref int inoutRayCount, NativeArray<Sphere> spheres, NativeArray<Material> materials, ref uint randState)
static bool Scatter(Material mat, Ray r_in, Hit rec, out float3 attenuation, out Ray scattered, out float3 outLightE, ref int inoutRayCount, ref SpheresSoA spheres, NativeArray<Material> materials, ref uint randState)
{
outLightE = new float3(0, 0, 0);
if (mat.type == Material.Type.Lambert)
@@ -87,21 +87,22 @@ static bool Scatter(Material mat, Ray r_in, Hit rec, out float3 attenuation, out
// sample lights
#if DO_LIGHT_SAMPLING
for (int i = 0; i < spheres.Length; ++i)
for (int j = 0; j < spheres.emissiveCount; ++j)
{
if (!materials[i].HasEmission)
continue; // skip non-emissive
int i = spheres.emissives[j];
//@TODO if (&mat == &smat)
// continue; // skip self
var s = spheres[i];
//var s = spheres[i];
float3 scenter = new float3(spheres.centerX[i], spheres.centerY[i], spheres.centerZ[i]);
float sqRradius = spheres.sqRadius[i];
// create a random direction towards sphere
// coord system for sampling: sw, su, sv
float3 sw = normalize(s.center - rec.pos);
float3 sw = normalize(scenter - rec.pos);
float3 su = normalize(cross(abs(sw.x) > 0.01f ? new float3(0, 1, 0) : new float3(1, 0, 0), sw));
float3 sv = cross(sw, su);
// sample sphere by solid angle
float cosAMax = sqrt(max(0.0f, 1.0f - s.radius * s.radius / lengthSquared(rec.pos - s.center)));
float cosAMax = sqrt(max(0.0f, 1.0f - sqRradius / lengthSquared(rec.pos - scenter)));
float eps1 = RandomFloat01(ref randState), eps2 = RandomFloat01(ref randState);
float cosA = 1.0f - eps1 + eps1 * cosAMax;
float sinA = sqrt(1.0f - cosA * cosA);
@@ -113,7 +114,7 @@ static bool Scatter(Material mat, Ray r_in, Hit rec, out float3 attenuation, out
Hit lightHit = default(Hit);
int hitID = 0;
++inoutRayCount;
if (HitWorld(new Ray(rec.pos, l), kMinT, kMaxT, ref lightHit, ref hitID, spheres) && hitID == i)
if (HitWorld(new Ray(rec.pos, l), kMinT, kMaxT, ref lightHit, ref hitID, ref spheres) && hitID == i)
{
float omega = 2 * PI * (1 - cosAMax);
@@ -177,25 +178,25 @@ static bool Scatter(Material mat, Ray r_in, Hit rec, out float3 attenuation, out
return true;
}
static float3 Trace(Ray r, int depth, ref int inoutRayCount, NativeArray<Sphere> spheres, NativeArray<Material> materials, ref uint randState, bool doMaterialE = true)
static float3 Trace(Ray r, int depth, ref int inoutRayCount, ref SpheresSoA spheres, NativeArray<Material> materials, ref uint randState, bool doMaterialE = true)
{
Hit rec = default(Hit);
int id = 0;
++inoutRayCount;
if (HitWorld(r, kMinT, kMaxT, ref rec, ref id, spheres))
if (HitWorld(r, kMinT, kMaxT, ref rec, ref id, ref spheres))
{
Ray scattered;
float3 attenuation;
float3 lightE;
var mat = materials[id];
var matE = mat.emissive;
if (depth < kMaxDepth && Scatter(mat, r, rec, out attenuation, out scattered, out lightE, ref inoutRayCount, spheres, materials, ref randState))
if (depth < kMaxDepth && Scatter(mat, r, rec, out attenuation, out scattered, out lightE, ref inoutRayCount, ref spheres, materials, ref randState))
{
#if DO_LIGHT_SAMPLING
if (!doMaterialE) matE = new float3(0, 0, 0);
doMaterialE = (mat.type != Material.Type.Lambert);
#endif
return matE + lightE + attenuation * Trace(scattered, depth + 1, ref inoutRayCount, spheres, materials, ref randState, doMaterialE);
return matE + lightE + attenuation * Trace(scattered, depth + 1, ref inoutRayCount, ref spheres, materials, ref randState, doMaterialE);
}
else
{
@@ -219,7 +220,7 @@ struct TraceRowJob : IJobParallelFor
public Camera cam;
[NativeDisableParallelForRestriction] public NativeArray<int> rayCounter;
[NativeDisableParallelForRestriction] public NativeArray<Sphere> spheres;
[NativeDisableParallelForRestriction] public SpheresSoA spheres;
[NativeDisableParallelForRestriction] public NativeArray<Material> materials;
public void Execute(int y)
@@ -241,7 +242,7 @@ public void Execute(int y)
float u = (x + RandomFloat01(ref state)) * invWidth;
float v = (y + RandomFloat01(ref state)) * invHeight;
Ray r = cam.GetRay(u, v, ref state);
col += Trace(r, 0, ref rayCount, spheres, materials, ref state);
col += Trace(r, 0, ref rayCount, ref spheres, materials, ref state);
}
col *= 1.0f / (float)DO_SAMPLES_PER_PIXEL;
@@ -267,8 +268,7 @@ public void DrawTest(float time, int frameCount, int screenWidth, int screenHeig
float distToFocus = 3;
float aperture = 0.1f;
for (int i = 0; i < s_SpheresData.Length; ++i)
s_SpheresData[i].UpdateDerivedData();
s_SpheresSoA.Update(s_SpheresData, s_SphereMatsData);
Camera cam = new Camera(lookfrom, lookat, new float3(0, 1, 0), 60, (float)screenWidth / (float)screenHeight, aperture, distToFocus);
@@ -280,13 +280,12 @@ public void DrawTest(float time, int frameCount, int screenWidth, int screenHeig
job.backbuffer = backbuffer;
job.cam = cam;
job.rayCounter = new NativeArray<int>(1, Allocator.Temp);
job.spheres = new NativeArray<Sphere>(s_SpheresData, Allocator.Temp);
job.spheres = s_SpheresSoA;
job.materials = new NativeArray<Material>(s_SphereMatsData, Allocator.Temp);
var fence = job.Schedule(screenHeight, 4);
fence.Complete();
rayCount = job.rayCounter[0];
job.rayCounter.Dispose();
job.spheres.Dispose();
job.materials.Dispose();
#else
for (int y = 0; y < screenHeight; ++y)
@@ -33,6 +33,7 @@ void Start ()
private void OnDestroy()
{
m_Backbuffer.Dispose();
m_Test.Dispose();
}
void UpdateLoop()

0 comments on commit 577d375

Please sign in to comment.