Change project structure;

Added new c# binding;
This commit is contained in:
2025-12-30 20:54:05 +09:00
parent 5f5404268c
commit f1d3dddb9a
392 changed files with 2694 additions and 360462 deletions

View File

@@ -0,0 +1,106 @@
#include "Material/Material.h"
#include <string.h>
bool material_collection_init(uint8_t size, material_collection_t* materials)
{
if (size > 254)
{
size = 254;
}
material_collection_t temp = {0};
temp.buffer = (material_t*)malloc(size * sizeof(material_t));
if (temp.buffer == NULL)
{
return false;
}
temp.size = (uint8_t)size;
temp.count = 0;
*materials = temp;
return true;
}
void material_collection_resize(material_collection_t* materials, size_t size)
{
if (size == INVALID_MATERIAL_ID)
{
size = INVALID_MATERIAL_ID - 1;
}
if (size == materials->size)
{
return;
}
material_t* temp = (material_t*)realloc(materials->buffer, size * sizeof(material_t));
if (temp != NULL)
{
materials->buffer = temp;
materials->size = (uint8_t)size;
}
}
void material_collection_free(material_collection_t* materials)
{
if (materials->buffer != NULL)
{
for (uint8_t i = 0; i < materials->count; i++)
{
free(materials->buffer[i].properties);
materials->buffer[i].properties = NULL;
}
free(materials->buffer);
materials->buffer = NULL;
}
}
material_handle_t material_create(const void* properties, size_t properties_size, material_render_loop_f render_loop, material_render_aov_f render_aov, material_collection_t* collection)
{
material_t material = {0};
if (collection->count >= collection->size)
{
material_collection_resize(collection, collection->size * 2);
}
void* temp = malloc(properties_size);
if (temp == NULL)
{
return invalid_material_entity();
}
memcpy(temp, properties, properties_size);
material.properties = temp;
material.properties_size = properties_size;
material.render_loop = render_loop;
material.render_aov = render_aov;
material_handle_t entity = {.id = collection->count};
collection->buffer[collection->count] = material;
collection->count++;
return entity;
}
// void material_free(material_entity_t entity, material_collection_t* collection)
// {
// if (entity.id >= collection->count || !is_material_entity_valid(entity))
// {
// return;
// }
//
// free(collection->buffer[entity.id].properties);
// collection->buffer[entity.id].properties = NULL;
//
// for (uint8_t i = entity.id; i < collection->count - 1; i++)
// {
// collection->buffer[i] = collection->buffer[i + 1];
// }
//
// collection->count--;
// }

View File

@@ -0,0 +1,388 @@
#include "Material/StandardLit.h"
#include "Algorithm/BSDF.h"
#include "Algorithm/MicrofacetGGX.h"
#include "Algorithm/GGXMultiScatter.h"
#include "Lighting/LightEvaluation.h"
static float oren_nayar_eval(vec3s l, vec3s v, vec3s n, float roughness, float n_dot_l, float n_dot_v)
{
// Full Qualitative Oren-Nayar
float sigma2 = roughness * roughness;
float A = 1.0f - (sigma2 / (2.0f * (sigma2 + 0.33f)));
float B = 0.45f * sigma2 / (sigma2 + 0.09f);
// Cosine of azimuth difference (phi)
// Project V and L onto the tangent plane
vec3s v_plane = glms_vec3_normalize(glms_vec3_sub(v, glms_vec3_scale(n, n_dot_v)));
vec3s l_plane = glms_vec3_normalize(glms_vec3_sub(l, glms_vec3_scale(n, n_dot_l)));
float cos_phi_diff = fmaxf(0.0f, glms_vec3_dot(v_plane, l_plane));
// Sine and Tangent terms
// alpha = max(theta_l, theta_v), beta = min(theta_l, theta_v)
// We use sin/tan relationships: sin(x) = sqrt(1-cos^2(x))
float sin_theta_l = sqrtf(fmaxf(0.0f, 1.0f - n_dot_l * n_dot_l));
float sin_theta_v = sqrtf(fmaxf(0.0f, 1.0f - n_dot_v * n_dot_v));
float sin_alpha, tan_beta;
if (n_dot_v > n_dot_l)
{
sin_alpha = sin_theta_l;
tan_beta = sin_theta_v / fmaxf(n_dot_v, 0.0001f);
}
else
{
sin_alpha = sin_theta_v;
tan_beta = sin_theta_l / fmaxf(n_dot_l, 0.0001f);
}
return (A + B * cos_phi_diff * sin_alpha * tan_beta) * INV_PI;
}
static void get_surface_data(const shading_context_t* context, const standard_lit_properties_t* properties, standard_lit_surface_data_t* data_out)
{
// Use the ray cone width (footprint) instead of simple distance for mip selection
float distance = glms_vec3_distance(context->camera_position, context->position);
vec3s view = context->camera_direction;
// Fetch geometry data for LOD calculation
const triangle_t* triangle = &context->triangles->buffer[context->triangle_id];
texture_sample_context_t sample_context =
{
.view_direction = view,
.normal = context->normal,
.edge1 = glms_vec3_sub(triangle->vertices[1].position, triangle->vertices[0].position),
.edge2 = glms_vec3_sub(triangle->vertices[2].position, triangle->vertices[0].position),
.uv1 = glms_vec2_sub(triangle->vertices[1].uv, triangle->vertices[0].uv),
.uv2 = glms_vec2_sub(triangle->vertices[2].uv, triangle->vertices[0].uv),
.ray_width = context->cone_width,
.distance = distance
};
data_out->albedo = properties->albedo;
const texture_t* albedo_texture = get_texture(context->textures, properties->albedo_texture);
if (albedo_texture != NULL && albedo_texture->data != NULL)
{
data_out->albedo = glms_vec3_mul(data_out->albedo, glms_vec3(texture_sample(albedo_texture, &sample_context, context->uv)));
}
data_out->normal = context->normal;
const texture_t* normal_texture = get_texture(context->textures, properties->normal_texture);
if (normal_texture != NULL && normal_texture->data != NULL)
{
vec3s normal_sample = glms_vec3(texture_sample(normal_texture, &sample_context, context->uv));
normal_sample = normal_unpack(normal_sample);
data_out->normal = normal_ts_to_ws(normal_sample, context->normal, context->tangent);
}
data_out->diffuse_roughness = properties->diffuse_roughness;
data_out->roughness = properties->roughness;
const texture_t* roughness_texture = get_texture(context->textures, properties->roughness_texture);
if (roughness_texture != NULL && roughness_texture->data != NULL)
{
data_out->roughness = data_out->roughness * texture_sample(roughness_texture, &sample_context, context->uv).x;
}
data_out->roughness = fmaxf(data_out->roughness, 0.001f);
data_out->metallic = properties->metallic;
const texture_t* metallic_texture = get_texture(context->textures, properties->metallic_texture);
if (metallic_texture != NULL && metallic_texture->data != NULL)
{
data_out->metallic = data_out->metallic * texture_sample(metallic_texture, &sample_context, context->uv).x;
}
}
static vec3s evaluate_bsdf_standard_lit(const shading_context_t* context, standard_lit_surface_data_t* surface_data, vec3s wi)
{
vec3s n = surface_data->normal;
vec3s v = glms_vec3_negate(context->wo);
vec3s l = wi;
float n_dot_l = fmaxf(glms_vec3_dot(n, l), 0.0f);
float n_dot_v = fmaxf(glms_vec3_dot(n, v), 0.0f);
if (n_dot_l <= 0.0f || n_dot_v <= 0.0f || glms_vec3_dot(context->normal, l) <= 0.0f)
{
return glms_vec3_zero();
}
vec3s h = glms_vec3_normalize(glms_vec3_add(v, l));
float n_dot_h = fmaxf(glms_vec3_dot(n, h), 0.0f);
float v_dot_h = fmaxf(glms_vec3_dot(v, h), 0.0f);
// Fresnel
vec3s f0 = glms_vec3_lerp(DIELECTRIC_REFLECTIVE, surface_data->albedo, surface_data->metallic);
vec3s F = fresnel_schlick_vec3(f0, v_dot_h);
// Specular (GGX)
float D = ggx_distribution(n_dot_h, surface_data->roughness);
float G = ggx_g_smith(n_dot_v, n_dot_l, surface_data->roughness);
vec3s spec = glms_vec3_scale(glms_vec3_mul(F, (vec3s){D * G, D * G, D * G}), 1.0f / fmaxf(4.0f * n_dot_v * n_dot_l, 0.0001f));
// Multi-scatter GGX (broad lobe)
vec3s ms = ggx_multi_scatter_lambert(f0, n_dot_v, n_dot_l, surface_data->roughness);
// Diffuse (Oren-Nayar)
// Using (1 - F) here can make rough dielectrics look too dark because our specular is single-scatter GGX
// (missing multi-scattering energy compensation). A stable approximation is (1 - F0_diel).
float kd_scale = (1.0f - surface_data->metallic) * (1.0f - DIELECTRIC_REFLECTIVE_F0);
vec3s kD = glms_vec3_scale(glms_vec3_one(), kd_scale);
float on_val = oren_nayar_eval(l, v, n, surface_data->diffuse_roughness, n_dot_l, n_dot_v);
vec3s diff = glms_vec3_scale(glms_vec3_mul(surface_data->albedo, kD), on_val);
return glms_vec3_add(glms_vec3_add(diff, spec), ms);
}
static float sample_bsdf_pdf(const standard_lit_surface_data_t* surface_data, vec3s V, vec3s L)
{
float n_dot_l = fmaxf(glms_vec3_dot(surface_data->normal, L), 0.0f);
if (n_dot_l <= 0.0f)
{
return 0.0f;
}
// Lobe probabilities (single-scatter spec vs cosine)
// We allocate some cosine probability for multi-scatter spec, especially for rough metals.
vec3s f0 = glms_vec3_lerp(DIELECTRIC_F0, surface_data->albedo, surface_data->metallic);
float n_dot_v = fmaxf(glms_vec3_dot(surface_data->normal, V), 0.0001f);
vec3s F_est = fresnel_schlick_vec3(f0, n_dot_v);
float spec_strength = luminance(F_est);
float diff_strength = (1.0f - surface_data->metallic) * (1.0f - spec_strength);
float Eo = ggx_ms_E(n_dot_v, surface_data->roughness);
float w_ss = spec_strength * Eo;
float w_cos = spec_strength * (1.0f - Eo) + diff_strength;
float sum_w = w_ss + w_cos;
if (sum_w < FLT_EPSILON)
{
return 0.0f;
}
w_ss /= sum_w;
w_cos /= sum_w;
// Specular PDF (GGX VNDF reflection)
// GGX VNDF reflection pdf:
// p_h(h) = D(h) * G1(v) * (N·H)/(N·V)
// p_w(wi) = p_h(h) / (4 * (V·H))
vec3s H = glms_vec3_normalize(glms_vec3_add(L, V));
float v_dot_h = glms_vec3_dot(V, H);
if (v_dot_h <= 1e-6f)
{
return 0.0f;
}
float n_dot_h = fmaxf(glms_vec3_dot(surface_data->normal, H), 0.0f);
float D = ggx_distribution(n_dot_h, surface_data->roughness);
float G1v = ggx_g1(n_dot_v, surface_data->roughness);
float pdf_h = (D * G1v * n_dot_h) / fmaxf(n_dot_v, 1e-6f);
float pdf_spec = pdf_h / (4.0f * fmaxf(v_dot_h, 1e-6f));
// Cosine PDF (used for diffuse + multi-scatter)
float pdf_cos = n_dot_l * INV_PI;
return w_ss * pdf_spec + w_cos * pdf_cos;
}
path_output standard_lit_render_loop(const standard_lit_properties_t* properties, const shading_context_t* context)
{
standard_lit_surface_data_t surface_data; // Assuming you reuse your struct
get_surface_data(context, properties, &surface_data);
// Keep shading normal in the same hemisphere as the geometric normal to avoid invalid transport.
if (glms_vec3_dot(surface_data.normal, context->normal) < 0.0f)
{
surface_data.normal = glms_vec3_negate(surface_data.normal);
}
path_output output = {0};
vec3s V = glms_vec3_negate(context->wo);
// Keep shading normal in the same hemisphere as the geometric normal.
if (glms_vec3_dot(surface_data.normal, context->normal) < 0.0f)
{
surface_data.normal = glms_vec3_negate(surface_data.normal);
}
// If shading normal faces away from the view, fall back to geometric normal (prevents VNDF/pdf blow-ups).
if (glms_vec3_dot(surface_data.normal, V) <= 0.0f)
{
surface_data.normal = context->normal;
}
float n_dot_v = fmaxf(glms_vec3_dot(surface_data.normal, V), 0.0001f);
// Ensure LUT is ready (thread-safe, one-time).
ggx_ms_init_lut_once();
light_shading_context_t light_context = {
.position = context->position,
.normal = surface_data.normal,
.geometric_normal = context->normal,
.tangent = context->tangent,
.uv = context->uv,
.wo = context->wo,
.bounce_depth = context->bounce_depth,
.scene = context->scene,
.bvh_tree = context->bvh_tree,
.textures = context->textures,
};
uint32_t scramble = hash_position(context->position);
// Running the light loop.
// TODO: Implementing other light types.
for (uint32_t i = 0; i < context->lights->directional_light_count; i++)
{
path_output light_output = evaluate_bsdf_directional(context->lights->directional_lights[i], &light_context, context->throughput, context->sample_index);
if (light_output.state == PS_SUCCESS)
{
vec3s bsdf_dir_light = evaluate_bsdf_standard_lit(context, &surface_data, light_output.wi);
float pdf_bsdf = sample_bsdf_pdf(&surface_data, V, light_output.wi);
vec3s light_contribute = weight_nee_light(bsdf_dir_light, light_output.direct_lighting, pdf_bsdf, light_output.pdf);
output.direct_lighting = glms_vec3_add(output.direct_lighting, light_contribute);
}
}
// Sky
path_output sky_output = evaluate_bsdf_sky(context->lights, &light_context, context->throughput, context->sample_index);
if (sky_output.state == PS_SUCCESS)
{
vec3s bsdf_sky_light = evaluate_bsdf_standard_lit(context, &surface_data, sky_output.wi);
float pdf_bsdf = sample_bsdf_pdf(&surface_data, V, sky_output.wi);
vec3s sky_light = weight_nee_light(bsdf_sky_light, sky_output.direct_lighting, pdf_bsdf, sky_output.pdf);
output.direct_lighting = glms_vec3_add(output.direct_lighting, sky_light);
}
// ----------------------------------------------------
// Indirect Lighting (Sampling Next Ray)
// ----------------------------------------------------
// 1. Choose lobe: VNDF single-scatter spec vs cosine (diffuse + multi-scatter spec)
vec3s f0 = glms_vec3_lerp(DIELECTRIC_F0, surface_data.albedo, surface_data.metallic);
vec3s F_est = fresnel_schlick_vec3(f0, n_dot_v);
float spec_strength = luminance(F_est);
float diff_strength = (1.0f - surface_data.metallic) * (1.0f - spec_strength);
float Eo = ggx_ms_E(n_dot_v, surface_data.roughness);
float w_ss = spec_strength * Eo;
float w_cos = spec_strength * (1.0f - Eo) + diff_strength;
float sum_w = w_ss + w_cos;
if (sum_w < FLT_EPSILON)
{
output.state = PS_TERMINATE;
return output;
}
w_ss /= sum_w;
w_cos /= sum_w;
vec3s f_eval = glms_vec3_zero();
float n_dot_l = 0.0f;
float r_lobe = sobol_sample_scrambled(context->sample_index, sobol_get_dimension(context->bounce_depth, PRNG_BSDF), scramble);
bool is_specular = (r_lobe < w_ss);
if (is_specular)
{
// Sample GGX
uint32_t d1 = sobol_get_dimension(context->bounce_depth, PRNG_BSDF_U);
uint32_t d2 = sobol_get_dimension(context->bounce_depth, PRNG_BSDF_V);
float u1 = sobol_sample_scrambled(context->sample_index, d1, scramble);
float u2 = sobol_sample_scrambled(context->sample_index, d2, scramble);
vec3s H = ggx_sample_vndf(surface_data.normal, V, surface_data.roughness, u1, u2);
output.wi = glms_vec3_reflect(context->wo, H); // reflect(-V, H) -> V is wo inverted
if (glms_vec3_dot(output.wi, surface_data.normal) <= 0.0f || glms_vec3_dot(output.wi, context->normal) <= 0.0f)
{
output.state = PS_TERMINATE;
return output;
}
// Recalculate dots
n_dot_l = fmaxf(glms_vec3_dot(surface_data.normal, output.wi), 0.0001f);
vec3s H_new = glms_vec3_normalize(glms_vec3_add(output.wi, V));
float n_dot_h = fmaxf(glms_vec3_dot(surface_data.normal, H_new), 0.0001f);
float v_dot_h = fmaxf(glms_vec3_dot(V, H_new), 0.0001f);
// Evaluate BSDF
float D = ggx_distribution(n_dot_h, surface_data.roughness);
float G1v = ggx_g1(n_dot_v, surface_data.roughness);
float G = ggx_g_smith(n_dot_v, n_dot_l, surface_data.roughness);
vec3s f0 = glms_vec3_lerp(DIELECTRIC_F0, surface_data.albedo, surface_data.metallic);
vec3s F = fresnel_schlick_vec3(f0, v_dot_h);
vec3s spec_f = glms_vec3_scale(glms_vec3_mul(F, (vec3s){D * G, D * G, D * G}),
1.0f / fmaxf(4.0f * n_dot_v * n_dot_l, 1e-6f));
f_eval = spec_f;
// Propagate spread angle for ray cones
// Heuristic: spread increases with roughness
output.spread_angle = context->spread_angle + surface_data.roughness * QUARTER_PI;
}
else
{
// Sample Cosine hemisphere (Diffuse + Multi-scatter spec)
// Note: We use cosine sampling for Oren-Nayar and the broad MS term.
uint32_t d1 = sobol_get_dimension(context->bounce_depth, PRNG_BSDF_U);
uint32_t d2 = sobol_get_dimension(context->bounce_depth, PRNG_BSDF_V);
output.wi = random_cosine_direction(surface_data.normal, context->sample_index, d1, d2, scramble);
n_dot_l = fmaxf(glms_vec3_dot(surface_data.normal, output.wi), 0.0001f);
if (glms_vec3_dot(output.wi, context->normal) <= 0.0f)
{
output.state = PS_TERMINATE;
return output;
}
float kd_scale = (1.0f - surface_data.metallic) * (1.0f - DIELECTRIC_REFLECTIVE_F0);
vec3s kD = glms_vec3_scale(glms_vec3_one(), kd_scale);
float on = oren_nayar_eval(output.wi, V, surface_data.normal, surface_data.diffuse_roughness, n_dot_l, n_dot_v);
// Diffuse bounce significantly increases spread (effectively resets or becomes very wide)
output.spread_angle = context->spread_angle + 0.5f;
vec3s diff_f = glms_vec3_scale(glms_vec3_mul(surface_data.albedo, kD), on);
// Multi-scatter GGX term (broad): sampled here with cosine.
vec3s ms_f = ggx_multi_scatter_lambert(f0, n_dot_v, n_dot_l, surface_data.roughness);
// Throughput multiplier: (f * NoL) / pdf
vec3s f_sum = glms_vec3_add(diff_f, ms_f);
f_eval = f_sum;
}
output.pdf = sample_bsdf_pdf(&surface_data, V, output.wi);
if (output.pdf < 1e-12f)
{
output.state = PS_TERMINATE;
return output;
}
// Throughput multiplier must be: (f * NoL) / pdf_total (mixture PDF)
output.bsdf = glms_vec3_scale(f_eval, n_dot_l / output.pdf);
output.state = PS_SUCCESS;
return output;
}
void standard_lit_render_aov(const standard_lit_properties_t* properties, const shading_context_t* context, aov_output_t* aov_output)
{
standard_lit_surface_data_t surface_data; // Assuming you reuse your struct
get_surface_data(context, properties, &surface_data);
// Keep shading normal in the same hemisphere as the geometric normal to avoid invalid transport.
if (glms_vec3_dot(surface_data.normal, context->normal) < 0.0f)
{
surface_data.normal = glms_vec3_negate(surface_data.normal);
}
aov_output->albedo = glms_vec4(surface_data.albedo, 1.0f);
vec3s n_ws = glms_vec3_normalize(surface_data.normal);
aov_output->normal = (vec4s){n_ws.x * 0.5f + 0.5f, n_ws.y * 0.5f + 0.5f, n_ws.z * 0.5f + 0.5f, 1.0f};
}