Changed the pixel rendering function to include anti-aliasing using Sobol sampling in Renderer.c. Changed the path tracing function to adjust light shading context and Russian roulette logic in PathTracing.c. Changed the normal calculation logic to use a ternary operator in RayIntersection.c. Changed the Sobol sample calculation for consistency in Debug.c. Removed some items from the project roadmap in README.md. Modified the sample count from 64 to 16 in main.c, affecting rendering quality and performance.
372 lines
13 KiB
C
372 lines
13 KiB
C
#include "Algorithm/RayIntersection.h"
|
||
#include "Common.h"
|
||
#include "Geometry/Triangle.h"
|
||
#include "cglm/struct/vec3.h"
|
||
|
||
ray_t ray_create(vec3s origin, vec3s direction)
|
||
{
|
||
return (ray_t)
|
||
{
|
||
.origin = origin,
|
||
.direction = direction,
|
||
.inverse_direction = glms_vec3_div(glms_vec3_one(), direction),
|
||
.sign =
|
||
((direction.x < 0.0f) ? 1 : 0) |
|
||
((direction.y < 0.0f) ? 2 : 0) |
|
||
((direction.z < 0.0f) ? 4 : 0)
|
||
};
|
||
}
|
||
|
||
static inline float next_float_up(float value)
|
||
{
|
||
if (isnan(value) || (isfinite(value) && value > 0))
|
||
{
|
||
return value;
|
||
}
|
||
|
||
return nextafterf(value, INFINITY);
|
||
}
|
||
|
||
static inline float next_float_down(float value)
|
||
{
|
||
if (isnan(value) || (isfinite(value) && value < 0))
|
||
{
|
||
return value;
|
||
}
|
||
|
||
return nextafterf(value, -INFINITY);
|
||
}
|
||
|
||
vec3s offset_ray_origin(vec3s point, vec3s normal, vec3s wo)
|
||
{
|
||
vec3s abs_normal = glms_vec3_abs(normal);
|
||
float c = glms_vec3_max(point) * gamma(3);
|
||
float d = glms_vec3_dot(abs_normal, (vec3s){c, c, c});
|
||
|
||
vec3s offset = glms_vec3_scale(abs_normal, d);
|
||
if (glms_vec3_dot(wo, normal) < 0.0f)
|
||
{
|
||
offset = glms_vec3_negate(offset);
|
||
}
|
||
|
||
vec3s position = glms_vec3_add(point, offset);
|
||
for (int i = 0; i < 3; i++)
|
||
{
|
||
if (offset.raw[i] > 0.0f)
|
||
{
|
||
position.raw[i] = next_float_up(position.raw[i]);
|
||
}
|
||
else if (offset.raw[i] < 0.0f)
|
||
{
|
||
position.raw[i] = next_float_down(position.raw[i]);
|
||
}
|
||
}
|
||
|
||
return position;
|
||
//return point;
|
||
}
|
||
|
||
#if 0
|
||
// TODO: We still have small amount of block dots in current implementation. It's because of floating point precision. May need to fall back to double or handle it in a different way.
|
||
hit_result_t ray_intersect_triangle(const ray_t* ray, const triangle_t* triangle)
|
||
{
|
||
hit_result_t result = {0};
|
||
|
||
vec3s edge1 = glms_vec3_sub(triangle->vertices[1].position, triangle->vertices[0].position);
|
||
vec3s edge2 = glms_vec3_sub(triangle->vertices[2].position, triangle->vertices[1].position);
|
||
vec3s edge3 = glms_vec3_sub(triangle->vertices[0].position, triangle->vertices[2].position);
|
||
|
||
vec3s normal = triangle->face_normal;
|
||
float n_dot_r = glms_vec3_dot(normal, ray->direction);
|
||
if (n_dot_r > 0.0f)
|
||
{
|
||
normal = glms_vec3_negate(normal);
|
||
}
|
||
|
||
// triangle is parallel to the ray
|
||
if (fabsf(n_dot_r) < FLT_EPSILON)
|
||
{
|
||
return result;
|
||
}
|
||
|
||
// Get distance from ray origin to triangle plane
|
||
float distance = (glms_vec3_dot(normal, triangle->vertices[0].position) - glms_vec3_dot(normal, ray->origin)) / glms_vec3_dot(normal, ray->direction);
|
||
|
||
float eps = gamma(3) * glms_vec3_max(glms_vec3_abs(ray->origin));
|
||
if (distance <= eps)
|
||
{
|
||
return result;
|
||
}
|
||
|
||
vec3s intersection_point = glms_vec3_add(ray->origin, glms_vec3_scale(ray->direction, distance));
|
||
|
||
// Check if the intersection point is inside the triangle using barycentric coordinates
|
||
vec3s vp = glms_vec3_sub(intersection_point, triangle->vertices[0].position);
|
||
vec3s vp2 = glms_vec3_sub(intersection_point, triangle->vertices[1].position);
|
||
vec3s vp3 = glms_vec3_sub(intersection_point, triangle->vertices[2].position);
|
||
|
||
vec3s c1 = glms_vec3_cross(edge1, vp);
|
||
vec3s c2 = glms_vec3_cross(edge2, vp2);
|
||
vec3s c3 = glms_vec3_cross(edge3, vp3);
|
||
|
||
float n_dot_c1 = glms_vec3_dot(normal, c1);
|
||
float n_dot_c2 = glms_vec3_dot(normal, c2);
|
||
float n_dot_c3 = glms_vec3_dot(normal, c3);
|
||
|
||
bool r1 = n_dot_c1 > 0.0f && n_dot_c2 > 0.0f && n_dot_c3 > 0.0f;
|
||
bool r2 = n_dot_c1 < 0.0f && n_dot_c2 < 0.0f && n_dot_c3 < 0.0f;
|
||
|
||
if (!r1 && !r2)
|
||
{
|
||
return result;
|
||
}
|
||
|
||
// TODO: Normal interpolation
|
||
vec3s v0v1 = glms_vec3_sub(triangle->vertices[1].position, triangle->vertices[0].position);
|
||
vec3s v0v2 = glms_vec3_sub(triangle->vertices[2].position, triangle->vertices[0].position);
|
||
vec3s v0p = glms_vec3_sub(intersection_point, triangle->vertices[0].position);
|
||
float d00 = glms_vec3_dot(v0v1, v0v1); // dot(v0v1, v0v1)
|
||
float d01 = glms_vec3_dot(v0v1, v0v2); // dot(v0v1, v0v2)
|
||
float d11 = glms_vec3_dot(v0v2, v0v2); // dot(v0v2, v0v2)
|
||
float d20 = glms_vec3_dot(v0p, v0v1); // dot(v0p, v0v1)
|
||
float d21 = glms_vec3_dot(v0p, v0v2); // dot(v0p, v0v2)
|
||
float denom = d00 * d11 - d01 * d01;
|
||
//if (denom < FLT_EPSILON)
|
||
//{
|
||
// return result;
|
||
//}
|
||
|
||
float invDenom = 1.0f / denom;
|
||
float u = (d11 * d20 - d01 * d21) * invDenom; // This is b1 (weight for V1)
|
||
float v = (d00 * d21 - d01 * d20) * invDenom; // This is b2 (weight for V2)
|
||
|
||
float w = 1.0f - u - v;
|
||
|
||
//vec3s smooth_normal = glms_vec3_add(glms_vec3_scale(triangle->vertices[0].normal, u), glms_vec3_add(glms_vec3_scale(triangle->vertices[1].normal, v), glms_vec3_scale(triangle->vertices[2].normal, w)));
|
||
|
||
result.hit = true;
|
||
result.point = intersection_point;
|
||
result.normal = normal;
|
||
result.uv = (vec2s)
|
||
{
|
||
.x = w * triangle->vertices[0].uv.x + u * triangle->vertices[1].uv.x + v * triangle->vertices[2].uv.x,
|
||
.y = w * triangle->vertices[0].uv.y + u * triangle->vertices[1].uv.y + v * triangle->vertices[2].uv.y,
|
||
};
|
||
result.distance = distance;
|
||
|
||
return result;
|
||
}
|
||
#else
|
||
hit_result_t ray_intersect_triangle(const ray_t* ray, const triangle_t* triangle)
|
||
{
|
||
hit_result_t result = {0};
|
||
|
||
vec3s origin = ray->origin;
|
||
vec3s direction = ray->direction;
|
||
vec3s v0 = triangle->vertices[0].position;
|
||
vec3s v1 = triangle->vertices[1].position;
|
||
vec3s v2 = triangle->vertices[2].position;
|
||
|
||
vec3s e1 = glms_vec3_sub(v1, v0);
|
||
vec3s e2 = glms_vec3_sub(v2, v0);
|
||
|
||
// Begin Möller–Trumbore
|
||
vec3s P = glms_vec3_cross(direction, e2);
|
||
float det = glms_vec3_dot(e1, P);
|
||
|
||
if (fabsf(det) < FLT_EPSILON)
|
||
{
|
||
return result;
|
||
}
|
||
|
||
float invDet = 1.0f / det;
|
||
|
||
// Calculate barycentric u
|
||
vec3s T = glms_vec3_sub(origin, v0);
|
||
float u = glms_vec3_dot(T, P) * invDet;
|
||
if (u < 0.0f || u > 1.0f)
|
||
{
|
||
return result;
|
||
}
|
||
|
||
// Calculate barycentric v
|
||
vec3s Q = glms_vec3_cross(T, e1);
|
||
float v = glms_vec3_dot(direction, Q) * invDet;
|
||
if (v < 0.0f || u + v > 1.0f)
|
||
{
|
||
return result;
|
||
}
|
||
|
||
// Distance along the ray
|
||
float t = glms_vec3_dot(e2, Q) * invDet;
|
||
if (t < RAY_EPSILON)
|
||
{
|
||
return result;
|
||
}
|
||
|
||
float w = 1.0f - u - v;
|
||
|
||
result.hit = true;
|
||
result.distance = t;
|
||
result.point = glms_vec3_add(origin, glms_vec3_scale(direction, t));
|
||
|
||
vec3s normal = glms_vec3_scale(triangle->vertices[0].normal, w);
|
||
normal = glms_vec3_add(normal, glms_vec3_scale(triangle->vertices[1].normal, u));
|
||
normal = glms_vec3_add(normal, glms_vec3_scale(triangle->vertices[2].normal, v));
|
||
normal = glms_vec3_dot(normal, direction) < 0.0f ? normal : glms_vec3_negate(normal);
|
||
result.normal = glms_vec3_normalize(normal);
|
||
|
||
vec3s tangent = glms_vec3_scale(triangle->vertices[0].tangent, w);
|
||
tangent = glms_vec3_add(tangent, glms_vec3_scale(triangle->vertices[1].tangent, u));
|
||
tangent = glms_vec3_add(tangent, glms_vec3_scale(triangle->vertices[2].tangent, v));
|
||
result.tangent = glms_vec3_normalize(tangent);
|
||
|
||
result.uv.x = w * triangle->vertices[0].uv.x
|
||
+ u * triangle->vertices[1].uv.x
|
||
+ v * triangle->vertices[2].uv.x;
|
||
result.uv.y = w * triangle->vertices[0].uv.y
|
||
+ u * triangle->vertices[1].uv.y
|
||
+ v * triangle->vertices[2].uv.y;
|
||
|
||
return result;
|
||
}
|
||
#endif
|
||
|
||
bool ray_intersect_aabb(const ray_t* ray, aabb_t aabb, float* enter_out, float* exit_out)
|
||
{
|
||
// select slab min/max per axis based on sign:
|
||
float tx_min = ((SIGN_BIT(ray->sign, 0) ? aabb.max.x : aabb.min.x) - ray->origin.x ) * ray->inverse_direction.x;
|
||
float tx_max = ((SIGN_BIT(ray->sign, 0) ? aabb.min.x : aabb.max.x) - ray->origin.x ) * ray->inverse_direction.x;
|
||
|
||
float ty_min = ((SIGN_BIT(ray->sign, 1) ? aabb.max.y : aabb.min.y) - ray->origin.y ) * ray->inverse_direction.y;
|
||
float ty_max = ((SIGN_BIT(ray->sign, 1) ? aabb.min.y : aabb.max.y) - ray->origin.y ) * ray->inverse_direction.y;
|
||
|
||
// early exit if slabs miss
|
||
if ((tx_min > ty_max) || (ty_min > tx_max))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// merge X and Y
|
||
float t0 = tx_min > ty_min ? tx_min : ty_min;
|
||
float t1 = tx_max < ty_max ? tx_max : ty_max;
|
||
|
||
float tz_min = ( (SIGN_BIT(ray->sign, 2) ? aabb.max.z : aabb.min.z) - ray->origin.z ) * ray->inverse_direction.z;
|
||
float tz_max = ( (SIGN_BIT(ray->sign, 2) ? aabb.min.z : aabb.max.z) - ray->origin.z ) * ray->inverse_direction.z;
|
||
|
||
// final overlap test
|
||
if ((t0 > tz_max) || (tz_min > t1))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// update entry/exit
|
||
if (enter_out != NULL)
|
||
{
|
||
*enter_out = t0 > tz_min ? t0 : tz_min;
|
||
}
|
||
if (exit_out != NULL)
|
||
{
|
||
*exit_out = t1 < tz_max ? t1 : tz_max;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static inline float distance_to_aabb(vec3s point, aabb_t aabb)
|
||
{
|
||
float dx = fmaxf(aabb.min.x - point.x, 0.0f) + fmaxf(point.x - aabb.max.x, 0.0f);
|
||
float dy = fmaxf(aabb.min.y - point.y, 0.0f) + fmaxf(point.y - aabb.max.y, 0.0f);
|
||
float dz = fmaxf(aabb.min.z - point.z, 0.0f) + fmaxf(point.z - aabb.max.z, 0.0f);
|
||
return sqrtf(dx * dx + dy * dy + dz * dz);
|
||
}
|
||
|
||
// TODO: Use a stack to avoid recursion.
|
||
void ray_intersect_bvh(const ray_t* ray, const bvh_node_t* bvh_nodes, const uint64_t* primitive_indices, const triangle_collection_t* all_triangles, uint64_t node_index, float* closest_out,
|
||
hit_result_t* best_hit_out)
|
||
{
|
||
const bvh_node_t* node = &bvh_nodes[node_index];
|
||
|
||
float enter, exit;
|
||
if (!ray_intersect_aabb(ray, node->bounds, &enter, &exit))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (enter > *closest_out)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// If primitive_count > 0 implies leaf:
|
||
if (node->primitive_count > 0)
|
||
{
|
||
for (uint32_t i = 0; i < node->primitive_count; i++)
|
||
{
|
||
uint64_t triangle_index = primitive_indices[node->start_index + i];
|
||
hit_result_t hit_result = ray_intersect_triangle(ray, &all_triangles->buffer[triangle_index]);
|
||
if (hit_result.hit && hit_result.distance < *closest_out)
|
||
{
|
||
hit_result.triangle_id = triangle_index;
|
||
*best_hit_out = hit_result;
|
||
*closest_out = hit_result.distance;
|
||
}
|
||
}
|
||
}
|
||
else // Internal node (primitive_count == 0 implies internal)
|
||
{
|
||
uint64_t left_child_index = node->left_child_offset;
|
||
uint64_t right_child_index = node->right_child_offset;
|
||
|
||
const bvh_node_t* left_child = &bvh_nodes[left_child_index];
|
||
const bvh_node_t* right_child = &bvh_nodes[right_child_index];
|
||
|
||
float left_enter, left_exit, right_enter, right_exit;
|
||
bool hit_left_aabb = ray_intersect_aabb(ray, left_child->bounds, &left_enter, &left_exit);
|
||
bool hit_right_aabb = ray_intersect_aabb(ray, right_child->bounds, &right_enter, &right_exit);
|
||
|
||
// Traverse children based on closest AABB and current best hit distance (*closest_t)
|
||
if (hit_left_aabb && hit_right_aabb)
|
||
{
|
||
if (left_enter < right_enter)
|
||
{
|
||
ray_intersect_bvh(ray, bvh_nodes, primitive_indices, all_triangles, node->left_child_offset, closest_out, best_hit_out);
|
||
if (right_enter < *closest_out)
|
||
{
|
||
ray_intersect_bvh(ray, bvh_nodes, primitive_indices, all_triangles, node->right_child_offset, closest_out, best_hit_out);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ray_intersect_bvh(ray, bvh_nodes, primitive_indices, all_triangles, node->right_child_offset, closest_out, best_hit_out);
|
||
if (left_enter < *closest_out)
|
||
{
|
||
ray_intersect_bvh(ray, bvh_nodes, primitive_indices, all_triangles, node->left_child_offset, closest_out, best_hit_out);
|
||
}
|
||
}
|
||
}
|
||
else if (hit_left_aabb)
|
||
{
|
||
ray_intersect_bvh(ray, bvh_nodes, primitive_indices, all_triangles, node->left_child_offset, closest_out, best_hit_out);
|
||
}
|
||
else if (hit_right_aabb)
|
||
{
|
||
ray_intersect_bvh(ray, bvh_nodes, primitive_indices, all_triangles, node->right_child_offset, closest_out, best_hit_out);
|
||
}
|
||
}
|
||
}
|
||
|
||
hit_result_t ray_intersect_scene(const ray_t* ray, const scene_t* scene)
|
||
{
|
||
hit_result_t result = {0};
|
||
float closest = FLT_MAX;
|
||
|
||
if (scene == NULL || scene->bvh_tree.nodes == NULL || scene->triangles.count == 0 || scene->bvh_tree.node_count == 0 || scene->bvh_tree.primitive_count == 0)
|
||
{
|
||
return result;
|
||
}
|
||
ray_intersect_bvh(ray, scene->bvh_tree.nodes, scene->bvh_tree.primitive_indices, &scene->triangles, 0, &closest, &result);
|
||
|
||
return result;
|
||
}
|