Enhance graphics library functionality and structure

Added new function signatures in `assimp-vc143-mt.lib` for improved logging, parsing, and vector operations.
Added new metadata and configuration information in `assimp-vc143-mt.dll` for versioning and licensing compliance.
Added Sobol sequence generation in `Sobol.c` for quasi-random sampling.
Added window message handling in `Window.c` for rendering graphics.
Added ray-triangle intersection tests in `RayIntersection.c` for collision detection.
Added functions for loading mesh data in `Mesh.c` to support 3D model import.
Added functions for managing triangle collections in `Triangle.c` to enhance geometric data handling.
Added light evaluation functions in `LightEvaluation.c` and `SkyLight.c` for realistic rendering.
Added sampling and evaluation functions for simple lit materials in `SimpleLit.c`.
Changed various header files to include copyright and licensing information.
Changed existing functions in multiple files to improve performance and clarity.
Removed unused code in several files to streamline the library.
This commit is contained in:
2025-04-18 01:54:26 +09:00
parent b915d56f73
commit bfc94f0008
138 changed files with 28915 additions and 534 deletions

View File

@@ -1,147 +0,0 @@
#include "Algorithm/BSDF.h"
#include "Common.h"
#include "cglm/struct/vec3.h"
#include <float.h>
#include <math.h>
static const float DIELECTRIC_REFLECTIVE_F0 = 0.04f; // Standard dielectric reflectivity coef at incident angle (= 4%)
static const vec3s DIELECTRIC_REFLECTIVE = {0.04f, 0.04f, 0.04f}; // Standard dielectric reflectivity coef at incident angle (= 4%)
static inline float roughness_to_blinn_phong_specular_exponent(float roughness)
{
return glm_clamp(2 * 1.0f / (max(roughness * roughness, FLT_EPSILON)) - 2, FLT_EPSILON, 1.0f / FLT_EPSILON);
}
static inline vec3s fresnel_schlick_vec3(vec3s f0, float cos_theta)
{
float factor = powf(1.0f - cos_theta, 5.0f);
vec3s one = {{1.0f, 1.0f, 1.0f}};
vec3s reflected = glms_vec3_scale(glms_vec3_sub(one, f0), factor);
return glms_vec3_add(f0, reflected);
}
vec3s sample_bsdf_simple_lit(const void* data, const vec3s normal, const vec3s wo, float* pdf_out)
{
const simple_lit_data_t shading_data = *(const simple_lit_data_t*)data;
//TODO: having a bsdf data struct to avoid recomputing the same thing in both sample and evaluate
vec3s f0 = glms_vec3_lerp(DIELECTRIC_REFLECTIVE, shading_data.albedo, shading_data.metallic);
float cos_theta_0 = fmaxf(glms_vec3_dot(normal, wo), 0.0f);
float F = glms_vec3_max(fresnel_schlick_vec3(f0, cos_theta_0)); // We use the max component of the Fresnel term for simplicity
float prob_specular = glm_lerp(F, 1.0f, shading_data.metallic);
float prob_diffuse = (1.0f - shading_data.metallic) * (1.0f - F); // Diffuse only for non-metals, reduced by reflection
float total_prob = prob_diffuse + prob_specular;
if (total_prob < FLT_EPSILON)
{
*pdf_out = 0.0f;
return glms_vec3_zero();
}
// Normalize probabilities
prob_diffuse /= total_prob;
prob_specular /= total_prob;
vec3s wi = glms_vec3_zero();
float pdf_lobe = 0.0f;
if (random_float() < prob_diffuse) // Diffuse Lobe
{
wi = random_cosine_direction(normal);
pdf_lobe = fmaxf(glms_vec3_dot(wi, normal), 0.0f) / (float)M_PI; // PDF for cosine sampling
if (pdf_lobe < FLT_EPSILON)
{
*pdf_out = 0.0f;
return glms_vec3_zero();
}
// Calculate combined PDF (using probabilities of both methods generating THIS wi) - Simplified: use chosen lobe's PDF
// float pdf_spec = pdf_ggx_specular_lobe(normal, wo, roughness, wi);
// *pdf_out = prob_diffuse * pdf_lobe + prob_specular * pdf_spec; // Power Heuristic / MIS would be better
*pdf_out = pdf_lobe;
}
else // Specular Lobe
{
// For simplification we use blinn-phong lobe distribution, we will implement GGX for standard lit later
// When talking about simplification, wen even can use a simple interpolation bwtween roughness and wi, but it's too biased.
// A common simplification involves sampling spherical coordinates(theta and phi angles) related to normal such that cose(theta) is distributed according to the Blinn-Phong distribution
// We can use a inversion sampling where cos(theta) = powf(random_float(), 1.0f / (specular_exponent + 1.0f)) and phi = 2 * PI * random_float()
float specular_exponent = roughness_to_blinn_phong_specular_exponent(shading_data.roughness);
float theta = acosf(powf(random_float(), 1.0f / (specular_exponent + 1.0f)));
float phi = 2.0f * (float)M_PI * random_float();
vec3s h_ts = (vec3s){
sinf(theta) * cosf(phi),
sinf(theta) * sinf(phi),
cosf(theta)
};
vec3s tangent_u; // World-space tangent (U)
vec3s bitangent_v; // World-space bitangent (V)
create_orthonormal_basis(normal, &tangent_u, &bitangent_v);
vec3s scaled_u = glms_vec3_scale(tangent_u, h_ts.x);
vec3s scaled_v = glms_vec3_scale(bitangent_v, h_ts.y);
vec3s scaled_n = glms_vec3_scale(normal, h_ts.z);
// Transform h from tangent space to world space
vec3s h_ws;
h_ws = glms_vec3_add(scaled_u, scaled_v);
h_ws = glms_vec3_add(h_ws, scaled_n);
// wi is simple now, just reflect wo around h
wi = glms_vec3_reflect(glms_vec3_negate(wo), h_ws);
// The pdf of sampling wi via this blinn-phong is related to the pdf of sampling h and Jacobian of the transformation from h to wi
// Since the pdf(h) is (exp + 1) / (2 * pi) * pow(dot(n, h), exp). The jacobian is 1 / (4 * dot(wo, h)).
// The pdf(wi) will be pdf(h) * jacobian
// We use inverse CDF here to get the cos from sampling.
float cos_theta_h = powf(random_float(), 1.0f / (specular_exponent + 1.0f));
float pdf_h = (specular_exponent + 1.0f) / (2.0f * (float)M_PI) * powf(cos_theta_h, specular_exponent);
float w_dot_h = fmaxf(FLT_EPSILON, glms_vec3_dot(wo, h_ws));
float jacobian = 1.0f / (4.0f * w_dot_h);
pdf_lobe = pdf_h * jacobian;
if (pdf_lobe < FLT_EPSILON)
{
*pdf_out = 0.0f;
return glms_vec3_zero();
}
*pdf_out = pdf_lobe;
}
// Final check to ensure wi is in the correct hemisphere
if (glms_vec3_dot(wi, normal) < 0.0f)
{
*pdf_out = 0.0f;
return glms_vec3_zero();
}
return wi;
}
vec3s evaluate_bsdf_simple_lit(const shading_context_t* context, const void* data)
{
const simple_lit_data_t shading_data = *(const simple_lit_data_t*)data;
const shading_context_t shading_context = *context;
vec3s h = glms_vec3_normalize(glms_vec3_add(shading_context.wi, shading_context.wo));
float n_dot_h = glms_vec3_dot(shading_context.normal, h);
float v_dot_h = glms_vec3_dot(shading_context.wo, h);
vec3s f0 = glms_vec3_lerp(DIELECTRIC_REFLECTIVE, shading_data.albedo, shading_data.metallic);
vec3s diffuse_color = glms_vec3_scale(shading_data.albedo, 1.0f - shading_data.metallic);
float specular_exponent = roughness_to_blinn_phong_specular_exponent(shading_data.roughness);
// Normalization factor D (Blinn-Phong distribution)
float D_norm = (specular_exponent + 2.0f) / (2.0f * (float)M_PI); // Common normalization
float D = D_norm * powf(n_dot_h, specular_exponent);
vec3s F = fresnel_schlick_vec3(f0, v_dot_h);
vec3s diffuse_term = glms_vec3_scale(diffuse_color, 1.0f / (float)M_PI);
vec3s specular_term = glms_vec3_scale(F, D);
// return diffuse_term;
return glms_vec3_add(diffuse_term, specular_term);
}

View File

@@ -1,104 +1,73 @@
#include "Algorithm/PathTracing.h"
#include "Common.h"
#include "Material.h"
#include "Algorithm/RayIntersection.h"
#include "Algorithm/BSDF.h"
#include "Algorithm/Sobol.h"
#include "Lighting/LightEvaluation.h"
static hit_result_t ray_intersect(const triangle_t triangle, const ray_t ray)
// TODO: Implement a faster methods like BVH, KD-Tree or uniform grid acceleration
// TODO: Split the diffuse and specular into different Monte Carlo, so we can decide the sample count for each one
vec3s path_trace(const scene_t* scene, const ray_t ray, const uint32_t sample_index, const int max_depth)
{
hit_result_t result = {0};
const triangle_collection_t* triangles = &scene->triangles;
const material_collection_t* materials = &scene->materials;
const light_collection_t* lights = &scene->lights;
vec3s normal = triangle.normal;
float n_dot_r = glms_vec3_dot(normal, ray.direction);
if (n_dot_r > 0.0f)
{
normal = glms_vec3_scale(normal, -1.0f);
}
// triangle is parallel to the ray
if (fabsf(n_dot_r) < FLT_EPSILON)
{
result.hit = false;
return result;
}
// Get distance from ray origin to triangle plane
float distance = (glms_vec3_dot(normal, triangle.point1) - glms_vec3_dot(normal, ray.origin)) / glms_vec3_dot(normal, ray.direction);
if (distance < FLT_EPSILON)
{
result.hit = false;
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 edge1 = glms_vec3_sub(triangle.point2, triangle.point1);
vec3s edge2 = glms_vec3_sub(triangle.point3, triangle.point2);
vec3s edge3 = glms_vec3_sub(triangle.point1, triangle.point3);
vec3s vp = glms_vec3_sub(intersection_point, triangle.point1);
vec3s vp2 = glms_vec3_sub(intersection_point, triangle.point2);
vec3s vp3 = glms_vec3_sub(intersection_point, triangle.point3);
vec3s c1 = glms_vec3_cross(edge1, vp);
vec3s c2 = glms_vec3_cross(edge2, vp2);
vec3s c3 = glms_vec3_cross(edge3, vp3);
if (glms_vec3_dot(triangle.normal, c1) < 0.0f
|| glms_vec3_dot(triangle.normal, c2) < 0.0f
|| glms_vec3_dot(triangle.normal, c3) < 0.0f)
{
result.hit = false;
return result;
}
result.hit = true;
result.point = intersection_point;
result.normal = normal;
result.distance = distance;
return result;
}
// TODO: Implement faster methods like BVH, KD-Tree or uniform grid acceleration
vec3s path_trace(const triangle_collection_t* triangles, const material_collection_t* materials, ray_t ray, int max_depth)
{
vec3s accumulated_color = glms_vec3_zero();
vec3s throughput = glms_vec3_one();
ray_t active_ray = ray;
vec3s prev_normal = glms_vec3_zero();
float pdf_bsdf = 1.0f; // Even though pdf_bsdf should be avaliable after the first bounce. For seafty, we set it to 1.0f for the first iteration.
sobol_state_t sobol_state = {sample_index, 0};
int depth = 0;
while (depth < max_depth)
{
uint8_t material_id = 255;
hit_result_t closest_hit = {0};
closest_hit.distance = 1145141919.810f;
for (uint64_t i = 0; i < triangles->count; i++)
{
hit_result_t hit_result = ray_intersect(triangles->buffer[i], ray);
if (hit_result.hit && hit_result.distance < closest_hit.distance)
{
closest_hit = hit_result;
material_id = triangles->buffer[i].material_id;
}
}
hit_result_t closest_hit = ray_intersect_closest(triangles, active_ray);
if (!closest_hit.hit)
{
// accumulated_color = glms_vec3_add(accumulated_color, throughput); // TODO: Skybox
vec3s sky_light = evaluate_bsdf_sky(scene, NULL, &sobol_state);
if (depth > 0)
{
// Have to multiply the weight since we evaluate the sky at each bounce
float pdf_nee = pdf_cosine_weighted_hemisphere(prev_normal, active_ray.direction);
float weight = power_heuristic(pdf_bsdf, pdf_nee);
sky_light = glms_vec3_scale(sky_light, weight);
}
accumulated_color = glms_vec3_add(accumulated_color, sky_light); // TODO: Skybox
break;
}
material_t* hit_material = &materials->buffer[material_id];
// Add the emission of the hit material to the accumulated color
material_t* hit_material = &materials->buffer[triangles->buffer[closest_hit.triangle_id].material_id];
vec3s emission = hit_material->emission;
accumulated_color = glms_vec3_add(accumulated_color, glms_vec3_mul(throughput, emission));
float pdf;
vec3s wo = glms_vec3_negate(ray.direction); // We need to negate the direction of the incoming ray
vec3s wi = sample_material_bsdf(hit_material, closest_hit.normal, wo, &pdf);
//vec3s wi = random_cosine_direction(closest_hit.normal);
//float cos_theta_s = fmaxf(0.0f, glms_vec3_dot(wi, closest_hit.normal));
//float pdf = cos_theta_s / (float)M_PI;
if (pdf < 0.0f)
light_shading_context_t light_context = {
.normal = closest_hit.normal,
.hit_point = closest_hit.point,
.wo = active_ray.direction,
.throughput = throughput,
.triangles = triangles,
.material = hit_material
};
// Running the light loop.
// TODO: Implementing other light types.
for (uint32_t i = 0; i < lights->directional_light_count; i++)
{
vec3s l = evaluate_bsdf_directional(lights->directional_lights[i], &light_context, &sobol_state);
accumulated_color = glms_vec3_add(accumulated_color, l);
}
vec3s sky_light = evaluate_bsdf_sky(scene, &light_context, &sobol_state);
accumulated_color = glms_vec3_add(accumulated_color, sky_light);
// Bounce and prepare for the next iteration
vec3s wo = glms_vec3_negate(active_ray.direction); // We need to negate the direction of the incoming ray
vec3s wi = sample_material_bsdf(hit_material, closest_hit.normal, wo, &sobol_state, &pdf_bsdf);
if (pdf_bsdf <= 0.0f)
{
break;
}
@@ -110,20 +79,24 @@ vec3s path_trace(const triangle_collection_t* triangles, const material_collecti
};
vec3s bsdf = evaluate_material_bsdf(hit_material, &shading_context);
float cos_theta = fmaxf(0.0f, glms_vec3_dot(wi, closest_hit.normal));
throughput = glms_vec3_mul(throughput, glms_vec3_scale(bsdf, cos_theta / pdf));
// We do Russian roulette to decide whether to continue tracing or terminate the path
throughput = glms_vec3_mul(throughput, glms_vec3_scale(bsdf, cos_theta / pdf_bsdf));
// We do Russian roulette to decide whether to continue tracing or terminate the path
if (depth > 2)
{
float q = fminf(glms_vec3_max(throughput), 0.95f);
if (random_float() > q)
{
break; // Terminate the path
break; // Terminate the path
}
// Keep the energy of the path by scaling the throughput
throughput = glms_vec3_scale(throughput, 1.0f / q);
}
ray.origin = glms_vec3_add(closest_hit.point, glms_vec3_scale(closest_hit.normal, FLT_EPSILON));
ray.direction = wi;
active_ray.origin = glms_vec3_add(closest_hit.point, glms_vec3_scale(closest_hit.normal, FLT_EPSILON));
active_ray.direction = wi;
prev_normal = closest_hit.normal;
depth++;
}

View File

@@ -0,0 +1,114 @@
#include "algorithm/RayIntersection.h"
hit_result_t ray_intersect(const triangle_t triangle, const ray_t ray)
{
hit_result_t result = {0};
vec3s normal_face = triangle.normal_face;
float n_dot_r = glms_vec3_dot(normal_face, ray.direction);
if (n_dot_r > 0.0f)
{
normal_face = glms_vec3_negate(normal_face);
}
// triangle is parallel to the ray
if (fabsf(n_dot_r) < FLT_EPSILON)
{
result.hit = false;
return result;
}
// Get distance from ray origin to triangle plane
float distance = (glms_vec3_dot(normal_face, triangle.point_1) - glms_vec3_dot(normal_face, ray.origin)) / glms_vec3_dot(normal_face, ray.direction);
if (distance < FLT_EPSILON)
{
result.hit = false;
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 edge1 = glms_vec3_sub(triangle.point_2, triangle.point_1);
vec3s edge2 = glms_vec3_sub(triangle.point_3, triangle.point_2);
vec3s edge3 = glms_vec3_sub(triangle.point_1, triangle.point_3);
vec3s vp = glms_vec3_sub(intersection_point, triangle.point_1);
vec3s vp2 = glms_vec3_sub(intersection_point, triangle.point_2);
vec3s vp3 = glms_vec3_sub(intersection_point, triangle.point_3);
vec3s c1 = glms_vec3_cross(edge1, vp);
vec3s c2 = glms_vec3_cross(edge2, vp2);
vec3s c3 = glms_vec3_cross(edge3, vp3);
if (glms_vec3_dot(triangle.normal_face, c1) < 0.0f
|| glms_vec3_dot(triangle.normal_face, c2) < 0.0f
|| glms_vec3_dot(triangle.normal_face, c3) < 0.0f)
{
result.hit = false;
return result;
}
// Normal interpolation
float d00 = glms_vec3_dot(edge1, edge1);
float d01 = glms_vec3_dot(edge1, edge2);
float d11 = glms_vec3_dot(edge2, edge2);
float d20 = glms_vec3_dot(vp, edge1);
float d21 = glms_vec3_dot(vp, edge2);
float denom = d00 * d11 - d01 * d01;
float v = (d11 * d20 - d01 * d21) / denom;
float w = (d00 * d21 - d01 * d20) / denom;
float u = 1.0f - v - w;
vec3s normal_interp = glms_vec3_addadd(
glms_vec3_scale(triangle.normal_1, u),
glms_vec3_scale(triangle.normal_2, v),
glms_vec3_scale(triangle.normal_3, w));
normal_interp = glms_vec3_normalize(normal_interp);
if (n_dot_r > 0.0f)
{
normal_interp = glms_vec3_negate(normal_interp);
}
result.hit = true;
result.point = intersection_point;
result.normal = normal_interp;
result.distance = distance;
return result;
}
hit_result_t ray_intersect_closest(const triangle_collection_t* triangles, const ray_t ray)
{
hit_result_t closest_hit = {0};
closest_hit.distance = FLT_MAX;
for (uint64_t i = 0; i < triangles->count; i++)
{
hit_result_t hit_result = ray_intersect(triangles->buffer[i], ray);
if (hit_result.hit && hit_result.distance < closest_hit.distance)
{
closest_hit = hit_result;
closest_hit.triangle_id = i;
}
}
return closest_hit;
}
hit_result_t ray_intersect_any(const triangle_collection_t* triangles, const ray_t ray)
{
for (uint64_t i = 0; i < triangles->count; i++)
{
hit_result_t hit_result = ray_intersect(triangles->buffer[i], ray);
if (hit_result.hit)
{
hit_result.triangle_id = i;
return hit_result;
}
}
return (hit_result_t){0};
}

89
source/Algorithm/Sobol.c Normal file
View File

@@ -0,0 +1,89 @@
#include "Algorithm/Sobol.h"
//NOTE: Maybe precompute the vector table?
void sobol_init()
{
// First, set dimension 0 manually (Van der Corput)
for (int i = 0; i < SOBOL_BITS; i++)
{
sobol_direction_vectors[0][i] = 1U << (31 - i);
}
// Dimensions 2 to 6
for (int d = 1; d < SOBOL_DIMENSIONS; d++)
{
int s = s_vals[d - 1];
int a = a_vals[d - 1];
const int* m = m_vals[d - 1];
// Set initial direction numbers
for (int i = 0; i < s; i++)
{
sobol_direction_vectors[d][i] = (uint32_t)m[i] << (31 - i);
}
// Compute remaining direction numbers
for (int i = s; i < SOBOL_BITS; i++)
{
uint32_t v = sobol_direction_vectors[d][i - s];
v ^= v >> s;
for (int k = 1; k < s; k++)
{
if ((a >> (s - 1 - k)) & 1)
v ^= sobol_direction_vectors[d][i - k];
}
sobol_direction_vectors[d][i] = v;
}
}
}
static inline uint32_t sobol_get_bit(uint32_t index, uint32_t bit)
{
return (index >> bit) & 1;
}
static inline float sobol_uint_to_float(uint32_t x)
{
return x * 2.3283064365386963e-10f; // 1/2^32
}
float sobol_sample(uint32_t index, uint32_t dimension)
{
if (dimension >= SOBOL_DIMENSIONS)
{
return 0.0f; // Invalid dimension
}
uint32_t result = 0;
for (int i = 0; i < SOBOL_BITS; i++)
{
if (sobol_get_bit(index, i))
{
result ^= sobol_direction_vectors[dimension][i];
}
}
return sobol_uint_to_float(result);
}
float sobol_next(sobol_state_t* state)
{
uint32_t index = state->index;
uint32_t dimension = state->dimension;
uint32_t result = 0;
for (int i = 0; i < SOBOL_BITS; i++)
{
if (sobol_get_bit(index, i))
{
result ^= sobol_direction_vectors[dimension][i];
}
}
// Increment the index for the next call
state->dimension = (state->dimension + 1) % SOBOL_DIMENSIONS;
return sobol_uint_to_float(result);
}