Initial upload
This commit is contained in:
147
source/Algorithm/BSDF.c
Normal file
147
source/Algorithm/BSDF.c
Normal file
@@ -0,0 +1,147 @@
|
||||
#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);
|
||||
}
|
||||
132
source/Algorithm/PathTracing.c
Normal file
132
source/Algorithm/PathTracing.c
Normal file
@@ -0,0 +1,132 @@
|
||||
#include "Algorithm/PathTracing.h"
|
||||
#include "Common.h"
|
||||
#include "Material.h"
|
||||
|
||||
static hit_result_t ray_intersect(const triangle_t triangle, const ray_t ray)
|
||||
{
|
||||
hit_result_t result = {0};
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!closest_hit.hit)
|
||||
{
|
||||
// accumulated_color = glms_vec3_add(accumulated_color, throughput); // TODO: Skybox
|
||||
break;
|
||||
}
|
||||
|
||||
material_t* hit_material = &materials->buffer[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)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
shading_context_t shading_context = {
|
||||
.normal = closest_hit.normal,
|
||||
.wi = wi,
|
||||
.wo = wo
|
||||
};
|
||||
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
|
||||
if (depth > 2)
|
||||
{
|
||||
float q = fminf(glms_vec3_max(throughput), 0.95f);
|
||||
if (random_float() > q)
|
||||
{
|
||||
break; // Terminate the path
|
||||
}
|
||||
}
|
||||
|
||||
ray.origin = glms_vec3_add(closest_hit.point, glms_vec3_scale(closest_hit.normal, FLT_EPSILON));
|
||||
ray.direction = wi;
|
||||
|
||||
depth++;
|
||||
}
|
||||
|
||||
return accumulated_color;
|
||||
}
|
||||
21
source/Geometry.c
Normal file
21
source/Geometry.c
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "Geometry.h"
|
||||
|
||||
void quad_create(vec3s center, vec3s forward, vec3s up, float size, uint8_t material_id, triangle_collection_t* collection)
|
||||
{
|
||||
float half_size = size / 2.0f;
|
||||
vec3s right = glms_vec3_cross(forward, up);
|
||||
|
||||
vec3s scaled_right = glms_vec3_scale(right, half_size);
|
||||
vec3s scaled_up = glms_vec3_scale(up, half_size);
|
||||
|
||||
vec3s temp_sub = glms_vec3_sub(center, scaled_right);
|
||||
vec3s temp_add = glms_vec3_add(center, scaled_right);
|
||||
|
||||
vec3s top_left = glms_vec3_add(temp_sub, scaled_up);
|
||||
vec3s top_right = glms_vec3_add(temp_add, scaled_up);
|
||||
vec3s bottom_right = glms_vec3_sub(temp_add, scaled_up);
|
||||
vec3s bottom_left = glms_vec3_sub(temp_sub, scaled_up);
|
||||
|
||||
triangle_create(top_left, bottom_left, top_right, material_id, collection);
|
||||
triangle_create(top_right, bottom_right, bottom_left, material_id, collection);
|
||||
}
|
||||
80
source/Material.c
Normal file
80
source/Material.c
Normal file
@@ -0,0 +1,80 @@
|
||||
#include "Material.h"
|
||||
|
||||
material_collection_t material_collection_create(size_t size)
|
||||
{
|
||||
if (size > 254)
|
||||
{
|
||||
size = 254; // Limit the count to 254 to fit in a uint8_t
|
||||
}
|
||||
|
||||
material_collection_t collection = {0};
|
||||
collection.buffer = (material_t*)malloc(size * sizeof(material_t));
|
||||
collection.size = (uint8_t)size;
|
||||
return collection;
|
||||
}
|
||||
|
||||
void material_collection_resize(material_collection_t* materials, size_t size)
|
||||
{
|
||||
if (size > 254)
|
||||
{
|
||||
size = 254; // Limit the count to 254 to fit in a uint8_t
|
||||
}
|
||||
|
||||
material_t* temp = 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)
|
||||
{
|
||||
free(materials->buffer);
|
||||
materials->buffer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
material_t material_create(const sample_bsdf_f sample, const evaluate_bsdf_f evaluate, void* data, material_collection_t* collection)
|
||||
{
|
||||
material_t material = {0};
|
||||
|
||||
if (collection->count >= collection->size)
|
||||
{
|
||||
material_collection_resize(collection, collection->size * 2);
|
||||
}
|
||||
|
||||
material.sample_bsdf = sample;
|
||||
material.evaluate_bsdf = evaluate;
|
||||
material.data = data;
|
||||
material.id = collection->count;
|
||||
|
||||
collection->buffer[collection->count] = material;
|
||||
collection->count++;
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
vec3s sample_material_bsdf(material_t* material, const vec3s normal, const vec3s wo, float* pdf_out)
|
||||
{
|
||||
vec3s wi = glms_vec3_zero();
|
||||
if (material->sample_bsdf != NULL)
|
||||
{
|
||||
wi = material->sample_bsdf(material->data, normal, wo, pdf_out);
|
||||
}
|
||||
|
||||
return wi;
|
||||
}
|
||||
|
||||
vec3s evaluate_material_bsdf(material_t* material, const shading_context_t* context)
|
||||
{
|
||||
vec3s bsdf_color = glms_vec3_zero();
|
||||
if (material->evaluate_bsdf != NULL)
|
||||
{
|
||||
bsdf_color = material->evaluate_bsdf(context, material->data);
|
||||
}
|
||||
|
||||
return bsdf_color;
|
||||
}
|
||||
19
source/Rendering/Camera.c
Normal file
19
source/Rendering/Camera.c
Normal file
@@ -0,0 +1,19 @@
|
||||
#include "Rendering/Camera.h"
|
||||
|
||||
camera_t camera_create(vec3s position, vec3s forward, vec3s up, float focal_length, float size_x, float aspect_ratio)
|
||||
{
|
||||
camera_t camera =
|
||||
{
|
||||
.position = position,
|
||||
.forward = glms_vec3_normalize(forward),
|
||||
.up = glms_vec3_normalize(up),
|
||||
.right = glms_vec3_cross(forward, up), // Assuming forward and up are not parallel
|
||||
.focal_length = focal_length,
|
||||
.size_x = size_x,
|
||||
.size_y = size_x / aspect_ratio,
|
||||
.fov_x = 2.0f * (float)atan(size_x / (2.0f * focal_length)),
|
||||
.fov_y = 2.0f * (float)atan(size_x / (2.0f * focal_length * aspect_ratio)),
|
||||
};
|
||||
|
||||
return camera;
|
||||
}
|
||||
44
source/Rendering/RenderTarget.c
Normal file
44
source/Rendering/RenderTarget.c
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "Rendering/RenderTarget.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
render_target_t render_target_create(uint32_t width, uint32_t height)
|
||||
{
|
||||
render_target_t target;
|
||||
target.width = width;
|
||||
target.height = height;
|
||||
|
||||
size_t buffer_size = (size_t)width * height * sizeof(vec4s);
|
||||
target.buffer = (vec4s*)malloc(buffer_size);
|
||||
memset(target.buffer, 0, buffer_size);
|
||||
return target;
|
||||
}
|
||||
|
||||
vec4s render_target_get_pixel(render_target_t* render_target, uint32_t x, uint32_t y)
|
||||
{
|
||||
if (x < render_target->width && y < render_target->height)
|
||||
{
|
||||
size_t index = (size_t)y * render_target->width + x;
|
||||
return render_target->buffer[index];
|
||||
}
|
||||
|
||||
return (vec4s){0.0f, 0.0f, 0.0f, 0.0f}; // Return black if out of bounds
|
||||
}
|
||||
|
||||
void render_target_set_pixel(render_target_t* render_target, uint32_t x, uint32_t y, vec4s color)
|
||||
{
|
||||
if (x < render_target->width && y < render_target->height)
|
||||
{
|
||||
size_t index = (size_t)y * render_target->width + x;
|
||||
render_target->buffer[index] = color;
|
||||
}
|
||||
}
|
||||
|
||||
void render_target_free(render_target_t* target)
|
||||
{
|
||||
if (target->buffer != NULL)
|
||||
{
|
||||
free(target->buffer);
|
||||
target->buffer = NULL;
|
||||
}
|
||||
}
|
||||
124
source/Rendering/Scene.c
Normal file
124
source/Rendering/Scene.c
Normal file
@@ -0,0 +1,124 @@
|
||||
#define FLIP_Y
|
||||
|
||||
#include "Rendering/Scene.h"
|
||||
#include "Algorithm/PathTracing.h"
|
||||
|
||||
scene_t scene_create(uint64_t triangle_count, uint8_t material_count)
|
||||
{
|
||||
scene_t scene = {0};
|
||||
|
||||
scene.triangles = triangle_collection_create(triangle_count);
|
||||
scene.materials = material_collection_create(material_count);
|
||||
scene.camera = camera_create(
|
||||
(vec3s){0.0f, 0.0f, 5.0f},
|
||||
(vec3s){0.0f, 0.0f, -1.0f},
|
||||
(vec3s){0.0f, 1.0f, 0.0f},
|
||||
0.025f,
|
||||
0.036f,
|
||||
1.777777f
|
||||
);
|
||||
|
||||
return scene;
|
||||
}
|
||||
|
||||
static inline vec2s compute_ndc(uint32_t x, uint32_t y, uint32_t width, uint32_t height)
|
||||
{
|
||||
return (vec2s){
|
||||
.x = (float)x / (float)width,
|
||||
#ifdef FLIP_Y
|
||||
.y = 1.0f - (float)y / (float)height
|
||||
#else
|
||||
.y = (float)y / (float)height
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
render_target_t scene_render(scene_t* scene, rendering_config_t config)
|
||||
{
|
||||
if (fabsf((float)config.width / config.height - scene->camera.aspect_ratio) > FLT_EPSILON)
|
||||
{
|
||||
scene->camera = camera_create(
|
||||
scene->camera.position,
|
||||
scene->camera.forward,
|
||||
scene->camera.up,
|
||||
scene->camera.focal_length,
|
||||
scene->camera.size_x,
|
||||
(float)config.width / (float)config.height
|
||||
);
|
||||
}
|
||||
|
||||
// The actual float buffer inside the render target is on the heap, copy return shoudl be fine.
|
||||
render_target_t img = render_target_create(config.width, config.height);
|
||||
|
||||
uint32_t tile_count_x = (config.width + config.tile_size - 1) / config.tile_size;
|
||||
uint32_t tile_count_y = (config.height + config.tile_size - 1) / config.tile_size;
|
||||
uint32_t tile_count = tile_count_x * tile_count_y;
|
||||
|
||||
float brightness_per_sample = ((float)M_PI * 2.0f) * (1.0f / config.sample_count);
|
||||
|
||||
int64_t x, y, tile_index; // OpenMP requires these to be declared outside the parallel region. Also, they need to be signed integers for OpenMP to work correctly with the loop bounds. To avoid overflow, we need to use int64_t
|
||||
#ifndef DEBUG
|
||||
#pragma omp parallel for schedule(dynamic, 1) default(none) \
|
||||
shared(tile_count_x, tile_count_y, tile_count) \
|
||||
private(x, y, tile_index)
|
||||
#endif
|
||||
for (tile_index = 0; tile_index < tile_count; tile_index++)
|
||||
{
|
||||
uint32_t tile_x_0 = tile_index % tile_count_x * config.tile_size;
|
||||
uint32_t tile_y_0 = tile_index / tile_count_x * config.tile_size;
|
||||
uint32_t tile_x_1 = (uint32_t)fmin(tile_x_0 + config.tile_size, config.width);
|
||||
uint32_t tile_y_1 = (uint32_t)fmin(tile_y_0 + config.tile_size, config.height);
|
||||
|
||||
vec3s coord = glms_vec3_add(scene->camera.position, glms_vec3_scale(scene->camera.forward, scene->camera.focal_length));
|
||||
|
||||
for (y = tile_y_0; y < tile_y_1; y++)
|
||||
{
|
||||
for (x = tile_x_0; x < tile_x_1; x++)
|
||||
{
|
||||
for (uint16_t k = 0; k < config.sample_count; k++)
|
||||
{
|
||||
vec2s position_ndc = compute_ndc(x, y, config.width, config.height);
|
||||
float screen_x = position_ndc.x * 2.0f - 1.0f;
|
||||
float screen_y = position_ndc.y * 2.0f - 1.0f;
|
||||
float sensor_offset_x = screen_x * scene->camera.size_x / 2.0f;
|
||||
float sensor_offset_y = screen_y * scene->camera.size_y / 2.0f;
|
||||
|
||||
vec3s image_plane_point = coord;
|
||||
image_plane_point = glms_vec3_add(image_plane_point, glms_vec3_scale(scene->camera.right, sensor_offset_x));
|
||||
image_plane_point = glms_vec3_add(image_plane_point, glms_vec3_scale(scene->camera.up, sensor_offset_y));
|
||||
|
||||
ray_t ray = {
|
||||
.origin = scene->camera.position,
|
||||
.direction = glms_vec3_normalize(glms_vec3_sub(image_plane_point, scene->camera.position))
|
||||
};
|
||||
|
||||
vec3s out_color = path_trace(&scene->triangles, &scene->materials, ray, 4);
|
||||
out_color = glms_vec3_scale(out_color, brightness_per_sample);
|
||||
if (glms_vec3_eq(out_color, 0.0f))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//TODO: Handle alpha
|
||||
vec4s color = {
|
||||
.x = out_color.x,
|
||||
.y = out_color.y,
|
||||
.z = out_color.z,
|
||||
.w = 1.0f
|
||||
};
|
||||
vec4s old_color = render_target_get_pixel(&img, x, y);
|
||||
|
||||
render_target_set_pixel(&img, x, y, glms_vec4_add(old_color, color));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
void scene_free(scene_t* scene)
|
||||
{
|
||||
triangle_collection_free(&scene->triangles);
|
||||
material_collection_free(&scene->materials);
|
||||
}
|
||||
61
source/Triangle.c
Normal file
61
source/Triangle.c
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "Triangle.h"
|
||||
|
||||
triangle_collection_t triangle_collection_create(size_t size)
|
||||
{
|
||||
if (size > UINT64_MAX)
|
||||
{
|
||||
size = UINT64_MAX;
|
||||
}
|
||||
|
||||
triangle_collection_t collection = {0};
|
||||
collection.buffer = (triangle_t*)malloc(size * sizeof(triangle_t));
|
||||
collection.size = (uint64_t)size;
|
||||
return collection;
|
||||
}
|
||||
|
||||
void triangle_collection_resize(triangle_collection_t* collection, size_t size)
|
||||
{
|
||||
if (size > UINT64_MAX)
|
||||
{
|
||||
size = UINT64_MAX;
|
||||
}
|
||||
|
||||
triangle_t* temp = realloc(collection->buffer, size * sizeof(triangle_t));
|
||||
if (temp != NULL)
|
||||
{
|
||||
collection->buffer = temp;
|
||||
collection->size = (uint64_t)size;
|
||||
}
|
||||
}
|
||||
|
||||
void triangle_collection_free(triangle_collection_t* collection)
|
||||
{
|
||||
if (collection->buffer != NULL)
|
||||
{
|
||||
free(collection->buffer);
|
||||
collection->buffer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void triangle_create(vec3s point1, vec3s point2, vec3s point3, uint8_t material_id, triangle_collection_t* collection)
|
||||
{
|
||||
if (collection->count >= collection->size)
|
||||
{
|
||||
triangle_collection_resize(collection, collection->size * 2);
|
||||
}
|
||||
|
||||
triangle_t new_triangle;
|
||||
new_triangle.point1 = point1;
|
||||
new_triangle.point2 = point2;
|
||||
new_triangle.point3 = point3;
|
||||
new_triangle.material_id = material_id;
|
||||
|
||||
vec3s edge1 = glms_vec3_sub(point2, point1);
|
||||
vec3s edge2 = glms_vec3_sub(point3, point1);
|
||||
new_triangle.normal = glms_vec3_normalize(glms_vec3_cross(edge1, edge2));
|
||||
|
||||
collection->buffer[collection->count] = new_triangle;
|
||||
collection->count++;
|
||||
}
|
||||
|
||||
|
||||
140
source/main.c
Normal file
140
source/main.c
Normal file
@@ -0,0 +1,140 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <svpng.inc>
|
||||
#include <omp.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "MaterialExtra.h"
|
||||
#include "Rendering/Scene.h"
|
||||
#include "Algorithm/BSDF.h"
|
||||
#include "Material.h"
|
||||
#include "Triangle.h"
|
||||
#include "Geometry.h"
|
||||
|
||||
|
||||
static inline void save_img(render_target_t* source, const uint32_t width, const uint32_t height, const char* filename)
|
||||
{
|
||||
FILE* fileStream;
|
||||
fopen_s(&fileStream, filename, "wb");
|
||||
if (fileStream == NULL)
|
||||
{
|
||||
fprintf(stderr, "Failed to open file for writing: %s\n", filename);
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned char* img = (unsigned char*)malloc((size_t)width * height * 4);
|
||||
if (img == NULL)
|
||||
{
|
||||
fprintf(stderr, "Failed to allocate memory for image\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t y = 0; y < height; y++)
|
||||
{
|
||||
for (uint32_t x = 0; x < width; x++)
|
||||
{
|
||||
size_t index = ((size_t)y * width + x) * (size_t)4.0;
|
||||
vec4s color = render_target_get_pixel(source, x, y);
|
||||
|
||||
img[index] = (unsigned char)fminf(color.x * 255.0f, 255.0f);
|
||||
img[index + 1] = (unsigned char)fminf(color.y * 255.0f, 255.0f);
|
||||
img[index + 2] = (unsigned char)fminf(color.z * 255.0f, 255.0f);
|
||||
img[index + 3] = (unsigned char)fminf(color.w * 255.0f, 255.0f);
|
||||
}
|
||||
}
|
||||
|
||||
svpng(fileStream, width, height, img, 1);
|
||||
fclose(fileStream);
|
||||
free(img);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
omp_set_num_threads(24);
|
||||
|
||||
const int SAMPLE_COUNT = 256;
|
||||
|
||||
const uint32_t WIDTH = 640;
|
||||
const uint32_t HEIGHT = 360;
|
||||
|
||||
scene_t scene = scene_create(64, 4);
|
||||
|
||||
simple_lit_data_t gray_lit_data = {
|
||||
.albedo = (vec3s){0.73f, 0.73f, 0.73f},
|
||||
.roughness = 1.0f,
|
||||
.metallic = 0.0f,
|
||||
};
|
||||
simple_lit_data_t blue_lit_data = {
|
||||
.albedo = (vec3s){0.0f, 0.0f, 1.0f},
|
||||
.roughness = 0.5f,
|
||||
.metallic = 1.0f,
|
||||
};
|
||||
material_t top_light_material = material_create_simple_lit(&gray_lit_data, &scene.materials);
|
||||
material_t gray_material = material_create_simple_lit(&gray_lit_data, &scene.materials);
|
||||
material_t blue_material = material_create_simple_lit(&blue_lit_data, &scene.materials);
|
||||
|
||||
scene.materials.buffer[top_light_material.id].emission = (vec3s){4.0f, 4.0f, 4.0f};
|
||||
|
||||
quad_create(
|
||||
(vec3s){0.0f, 1.95f, 0.0f},
|
||||
(vec3s){0.0f, -1.0f, 0.0f},
|
||||
(vec3s){0.0f, 0.0f, 1.0f},
|
||||
1.0f, top_light_material.id, &scene.triangles
|
||||
);
|
||||
|
||||
triangle_create(
|
||||
(vec3s){-1.0f, -1.0f, 0.0f},
|
||||
(vec3s){1.0f, -1.0f, 0.0f},
|
||||
(vec3s){0.0f, 1.0f, 0.0f},
|
||||
gray_material.id, &scene.triangles
|
||||
);
|
||||
|
||||
quad_create(
|
||||
(vec3s){0.0f, -2.0f, 0.0f},
|
||||
(vec3s){0.0f, 1.0f, 0.0f},
|
||||
(vec3s){0.0f, 0.0f, 1.0f},
|
||||
4.0f, blue_material.id, &scene.triangles
|
||||
);
|
||||
quad_create(
|
||||
(vec3s){0.0f, 2.0f, 0.0f},
|
||||
(vec3s){0.0f, -1.0f, 0.0f},
|
||||
(vec3s){0.0f, 0.0f, 1.0f},
|
||||
4.0f, gray_material.id, &scene.triangles
|
||||
);
|
||||
quad_create(
|
||||
(vec3s){0.0f, 0.0f, -2.0f},
|
||||
(vec3s){0.0f, 0.0f, 1.0f},
|
||||
(vec3s){0.0f, 1.0f, 0.0f},
|
||||
4.0f, gray_material.id, &scene.triangles
|
||||
);
|
||||
quad_create(
|
||||
(vec3s){-2.0f, 0.0f, 0.0f},
|
||||
(vec3s){1.0f, 0.0f, 0.0f},
|
||||
(vec3s){0.0f, 1.0f, 0.0f},
|
||||
4.0f, gray_material.id, &scene.triangles
|
||||
);
|
||||
quad_create(
|
||||
(vec3s){2.0f, 0.0f, 0.0f},
|
||||
(vec3s){-1.0f, 0.0f, 0.0f},
|
||||
(vec3s){0.0f, 1.0f, 0.0f},
|
||||
4.0f, gray_material.id, &scene.triangles
|
||||
);
|
||||
|
||||
rendering_config_t config = {
|
||||
.width = WIDTH,
|
||||
.height = HEIGHT,
|
||||
.sample_count = SAMPLE_COUNT,
|
||||
.max_depth = 4,
|
||||
.tile_size = 16,
|
||||
};
|
||||
|
||||
render_target_t img = scene_render(&scene, config);
|
||||
|
||||
save_img(&img, WIDTH, HEIGHT, "output.png");
|
||||
|
||||
render_target_free(&img);
|
||||
scene_free(&scene);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user