I'm making a ray tracer in unity and I'm trying to model refraction, however when I use refract()
in my shader it doesn't change the ray direction at all.
here is a picture of the sphere I'm trying to refract, it has a transparency of (.9, .9, .9) so that it can be seen - ignore the sphere in the top right:
my ray tracing compute shader
#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;
static const float EPSILON = 1e-8;
float isAir = 1;
float nAir = 1.0f;
float nGlass = 1.5f;
//-------------------------------------
//- 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;
}
//-------------------------------------
//- SPHERES
struct Sphere
{
float3 position;
float radius;
float3 albedo;
float3 specular;
float smoothness;
float3 emission;
};
StructuredBuffer<Sphere> _Spheres;
//-------------------------------------
//- MESHES
struct MeshObject
{
float4x4 localToWorldMatrix;
int indices_offset;
int indices_count;
float3 albedo;
float3 specular;
float smoothness;
float3 emission;
float3 transparency;
};
StructuredBuffer<MeshObject> _MeshObjects;
StructuredBuffer<float3> _Vertices;
StructuredBuffer<int> _Indices;
//-------------------------------------
//- 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;
float3 transparency;
};
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;
}
//-------------------------------------
//- INTERSECTION
void IntersectGroundPlane(Ray ray, inout RayHit bestHit)
{
// Calculate distance along the ray where the ground plane is intersected
float t = -ray.origin.y / ray.direction.y;
if (t > 0 && t < bestHit.distance)
{
bestHit.distance = t;
bestHit.position = ray.origin + t * ray.direction;
bestHit.normal = float3(0.0f, 1.0f, 0.0f);
bestHit.albedo = 0;
bestHit.specular = 0.9f;
bestHit.smoothness = 1;
bestHit.emission = 0.2f;
bestHit.transparency = float3(0, 0, 0);
}
}
void IntersectSphere(Ray ray, inout RayHit bestHit, Sphere sphere)
{
// Calculate distance along the ray where the sphere is intersected
float3 d = ray.origin - sphere.position;
float p1 = -dot(ray.direction, d);
float p2sqr = p1 * p1 - dot(d, d) + sphere.radius * sphere.radius;
if (p2sqr < 0)
return;
float p2 = sqrt(p2sqr);
float t = p1 - p2 > 0 ? p1 - p2 : p1 + p2;
if (t > 0 && t < bestHit.distance)
{
bestHit.distance = t;
bestHit.position = ray.origin + t * ray.direction;
bestHit.normal = normalize(bestHit.position - sphere.position);
bestHit.albedo = float3(0, 0, 0);
bestHit.specular = float3(0, 0, 0);
bestHit.smoothness = 2;
bestHit.emission = float3(0, 0, 0);
bestHit.transparency = float3(0.9f, 0.9f, 0.9f);
}
}
bool IntersectTriangle_MT97(Ray ray, float3 vert0, float3 vert1, float3 vert2,
inout float t, inout float u, inout float v)
{
// find vectors for two edges sharing vert0
float3 edge1 = vert1 - vert0;
float3 edge2 = vert2 - vert0;
// begin calculating determinant - also used to calculate U parameter
float3 pvec = cross(ray.direction, edge2);
// if determinant is near zero, ray lies in plane of triangle
float det = dot(edge1, pvec);
// use backface culling
if (det < EPSILON)
return false;
float inv_det = 1.0f / det;
// calculate distance from vert0 to ray origin
float3 tvec = ray.origin - vert0;
// calculate U parameter and test bounds
u = dot(tvec, pvec) * inv_det;
if (u < 0.0 || u > 1.0f)
return false;
// prepare to test V parameter
float3 qvec = cross(tvec, edge1);
// calculate V parameter and test bounds
v = dot(ray.direction, qvec) * inv_det;
if (v < 0.0 || u + v > 1.0f)
return false;
// calculate t, ray intersects triangle
t = dot(edge2, qvec) * inv_det;
return true;
}
void IntersectMeshObject(Ray ray, inout RayHit bestHit, MeshObject meshObject)
{
uint offset = meshObject.indices_offset;
uint count = offset + meshObject.indices_count;
for (uint i = offset; i < count; i += 3)
{
float3 v0 = (mul(meshObject.localToWorldMatrix, float4(_Vertices[_Indices[i]], 1))).xyz;
float3 v1 = (mul(meshObject.localToWorldMatrix, float4(_Vertices[_Indices[i + 1]], 1))).xyz;
float3 v2 = (mul(meshObject.localToWorldMatrix, float4(_Vertices[_Indices[i + 2]], 1))).xyz;
float t, u, v;
if (IntersectTriangle_MT97(ray, v0, v1, v2, t, u, v))
{
if (t > 0 && t < bestHit.distance)
{
bestHit.distance = t;
bestHit.position = ray.origin + t * ray.direction;
bestHit.normal = ray.direction;
bestHit.albedo = meshObject.albedo;
bestHit.specular = meshObject.specular;
bestHit.smoothness = meshObject.smoothness;
bestHit.emission = meshObject.emission;
bestHit.transparency = meshObject.transparency;
}
}
}
}
//-------------------------------------
//- TRACE
RayHit Trace(Ray ray)
{
RayHit bestHit = CreateRayHit();
uint count, stride, i;
// Trace spheres
_Spheres.GetDimensions(count, stride);
for (i = 0; i < count; i++)
{
IntersectSphere(ray, bestHit, _Spheres[i]); // irelevant as i dont have spheres
}
// Trace mesh objects
_MeshObjects.GetDimensions(count, stride);
for (i = 0; i < count; i++)
{
IntersectMeshObject(ray, bestHit, _MeshObjects[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);
float transChance = energy(hit.transparency);
// 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 if (roulette < transChance)
{
ray.origin = hit.position + hit.normal * 0.001f;
if (isAir == 1)
ray.direction = refract(ray.direction, hit.normal, 0.1f);
else
ray.direction = refract(ray.direction, hit.normal, 0.1f);
ray.energy *= hit.transparency;
}
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(8,8,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 < 8; i++)
{
RayHit hit = Trace(ray);
result += ray.energy * Shade(ray, hit);
if (!any(ray.energy))
break;
}
Result[id.xy] = float4(result, 1);
}