Added mip selection using ray differentials
This commit is contained in:
@@ -26,6 +26,7 @@ vec4s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, uint16_
|
||||
{
|
||||
.wo = active_ray.direction,
|
||||
.textures = &scene->textures,
|
||||
.spread_angle = active_ray.spread_angle,
|
||||
};
|
||||
path_output sky_output = evaluate_bsdf_sky(&scene->lights, &light_context, throughput, sample_index);
|
||||
|
||||
@@ -48,6 +49,10 @@ vec4s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, uint16_
|
||||
|
||||
uint8_t material_id = scene->triangles.buffer[closest_hit.triangle_id].material_id;
|
||||
const material_t* hit_material = &scene->materials.buffer[material_id];
|
||||
|
||||
// Calculate ray cone width at the hit point
|
||||
float current_cone_width = active_ray.width + closest_hit.distance * active_ray.spread_angle;
|
||||
|
||||
shading_context_t shading_context =
|
||||
{
|
||||
.camera_position = scene->camera.position,
|
||||
@@ -67,6 +72,10 @@ vec4s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, uint16_
|
||||
.triangles = &scene->triangles,
|
||||
.lights = &scene->lights,
|
||||
.textures = &scene->textures,
|
||||
|
||||
.triangle_id = closest_hit.triangle_id,
|
||||
.cone_width = current_cone_width,
|
||||
.spread_angle = active_ray.spread_angle,
|
||||
};
|
||||
|
||||
path_output material_output = render_material(hit_material, &shading_context);
|
||||
@@ -105,15 +114,16 @@ vec4s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, uint16_
|
||||
|
||||
switch (material_output.state)
|
||||
{
|
||||
case PS_TERMINATE:
|
||||
goto end_path_trace;
|
||||
//case PATH_THROUGH:
|
||||
// active_ray = ray_create(BIAS_RAY_ORIGION(closest_hit.point, glms_vec3_negate(closest_hit.normal)), active_ray.direction);
|
||||
// continue;
|
||||
default:
|
||||
active_ray = ray_create(offset_ray_origin(closest_hit.point, closest_hit.normal, shading_context.wo), material_output.wi);
|
||||
case PS_SUCCESS:
|
||||
vec3s origin = offset_ray_origin(closest_hit.point, closest_hit.normal, shading_context.wo);
|
||||
active_ray = ray_create(origin, material_output.wi, current_cone_width, material_output.spread_angle);
|
||||
depth++;
|
||||
break;
|
||||
default:
|
||||
goto end_path_trace;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +161,10 @@ void render_aov(const scene_t* scene, ray_t ray, uint32_t sample_index, uint16_t
|
||||
.triangles = &scene->triangles,
|
||||
.lights = &scene->lights,
|
||||
.textures = &scene->textures,
|
||||
|
||||
.triangle_id = closest_hit.triangle_id,
|
||||
.cone_width = ray.width + closest_hit.distance * ray.spread_angle,
|
||||
.spread_angle = ray.spread_angle,
|
||||
};
|
||||
|
||||
render_material_aov(hit_material, &shading_context, aov_output);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "Geometry/Triangle.h"
|
||||
#include "cglm/struct/vec3.h"
|
||||
|
||||
ray_t ray_create(vec3s origin, vec3s direction)
|
||||
ray_t ray_create(vec3s origin, vec3s direction, float cone_width, float spread_angle)
|
||||
{
|
||||
ray_t ray =
|
||||
{
|
||||
@@ -13,7 +13,9 @@ ray_t ray_create(vec3s origin, vec3s direction)
|
||||
.sign =
|
||||
((direction.x < 0.0f) ? 1 : 0) |
|
||||
((direction.y < 0.0f) ? 2 : 0) |
|
||||
((direction.z < 0.0f) ? 4 : 0)
|
||||
((direction.z < 0.0f) ? 4 : 0),
|
||||
.width = cone_width,
|
||||
.spread_angle = spread_angle
|
||||
};
|
||||
|
||||
ray.esp = glms_vec3_max(glms_vec3_abs(ray.origin)) * gamma(15);
|
||||
|
||||
@@ -27,7 +27,7 @@ path_output evaluate_bsdf_directional(directional_light_t light, const light_sha
|
||||
return output;
|
||||
}
|
||||
|
||||
ray_t shadow_ray = ray_create(offset_ray_origin(context->position, context->normal, context->wo), wi);
|
||||
ray_t shadow_ray = ray_create(offset_ray_origin(context->position, context->normal, context->wo), wi, 0.0f, 0.0f);
|
||||
|
||||
float closest = FLT_MAX;
|
||||
hit_result_t shadow_hit = {0};
|
||||
|
||||
@@ -49,7 +49,7 @@ path_output evaluate_bsdf_const_sky(const void* data, const light_shading_contex
|
||||
|
||||
vec3s wi = random_uniform_cdf_direction(context->normal, sample_index, d1, d2, scramble);
|
||||
|
||||
ray_t shadow_ray = ray_create(offset_ray_origin(context->position, context->normal, context->wo), wi);
|
||||
ray_t shadow_ray = ray_create(offset_ray_origin(context->position, context->normal, context->wo), wi, 0.0f, 0.0f);
|
||||
|
||||
hit_result_t shadow_hit = {0};
|
||||
ray_intersect_bvh_any(&shadow_ray, context->bvh_tree->nodes, context->bvh_tree->primitive_indices, context->bvh_tree->triangles, 0, &shadow_hit);
|
||||
@@ -306,7 +306,18 @@ path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_
|
||||
if (context->bvh_tree == NULL)
|
||||
{
|
||||
vec2s uv = direction_to_equirectangular(context->wo);
|
||||
vec4s sky_light = texture_sample_lod(get_texture(context->textures, sky_data->texture), uv, 0);
|
||||
|
||||
// Calculate LOD based on ray spread angle
|
||||
float lod = 0.0f;
|
||||
if (context->spread_angle > 0.0f)
|
||||
{
|
||||
// Approximate texel footprint: spread_angle * resolution / (2*PI)
|
||||
// This is a rough estimation for equirectangular mapping
|
||||
float texels = context->spread_angle * (float)fmax(sky_data->width, sky_data->height) * INV_PI;
|
||||
lod = log2f(fmaxf(texels, 1.0f));
|
||||
}
|
||||
|
||||
vec4s sky_light = texture_sample_lod(get_texture(context->textures, sky_data->texture), uv, lod);
|
||||
output.direct_lighting = glms_vec3_scale(glms_vec3_mul(glms_vec3(sky_light), throughput), sky_data->intensity);
|
||||
// Return the correct environment PDF for MIS when the BSDF-sampled ray escapes to the sky.
|
||||
output.pdf = hdr_sky_pdf_direction(sky_data, context->wo);
|
||||
@@ -340,7 +351,7 @@ path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_
|
||||
return output;
|
||||
}
|
||||
|
||||
ray_t shadow_ray = ray_create(offset_ray_origin(context->position, context->normal, context->wo), wi);
|
||||
ray_t shadow_ray = ray_create(offset_ray_origin(context->position, context->normal, context->wo), wi, 0.0f, 0.0f);
|
||||
hit_result_t shadow_hit = {0};
|
||||
ray_intersect_bvh_any(&shadow_ray, context->bvh_tree->nodes, context->bvh_tree->primitive_indices, context->bvh_tree->triangles, 0, &shadow_hit);
|
||||
if (shadow_hit.hit)
|
||||
|
||||
@@ -128,21 +128,37 @@ static float oren_nayar_eval(vec3s l, vec3s v, vec3s n, float roughness, float n
|
||||
|
||||
static void get_surface_data(const shading_context_t* context, const standard_lit_properties_t* properties, standard_lit_surface_data_t* data_out)
|
||||
{
|
||||
float camera_distance = glms_vec3_distance(context->camera_position, context->position);
|
||||
vec3s view = (context->camera_direction);
|
||||
// Use the ray cone width (footprint) instead of simple distance for mip selection
|
||||
float footprint = context->cone_width;
|
||||
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 = footprint,
|
||||
.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, context->uv, view, context->normal, camera_distance)));
|
||||
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, context->uv, view, context->normal, camera_distance));
|
||||
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);
|
||||
}
|
||||
@@ -153,14 +169,14 @@ static void get_surface_data(const shading_context_t* context, const standard_li
|
||||
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, context->uv, view, context->normal, camera_distance).x;
|
||||
data_out->roughness = data_out->roughness * texture_sample(roughness_texture, &sample_context, context->uv).x;
|
||||
}
|
||||
|
||||
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, context->uv, view, context->normal, camera_distance).x;
|
||||
data_out->metallic = data_out->metallic * texture_sample(metallic_texture, &sample_context, context->uv).x;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,6 +407,10 @@ path_output standard_lit_render_loop(const standard_lit_properties_t* properties
|
||||
|
||||
// Throughput multiplier must be: (f * NoL) / pdf_total
|
||||
output.bsdf = glms_vec3_scale(spec_f, n_dot_l / pdf_gen);
|
||||
|
||||
// Propagate spread angle for ray cones
|
||||
// Heuristic: spread increases with roughness
|
||||
output.spread_angle = context->spread_angle + surface_data.roughness * 0.2f;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -420,6 +440,9 @@ path_output standard_lit_render_loop(const standard_lit_properties_t* properties
|
||||
vec3s kD = glms_vec3_scale(glms_vec3_sub(glms_vec3_one(), F_est), 1.0f - surface_data.metallic);
|
||||
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);
|
||||
|
||||
// Throughput multiplier: (f * NoL) / pdf_total
|
||||
|
||||
@@ -98,7 +98,11 @@ static void render_pixel(const rendering_config_t* config, scene_t* scene, vec3s
|
||||
image_plane_point = glms_vec3_add(image_plane_point, glms_vec3_scale(camera_right, sensor_offset_x));
|
||||
image_plane_point = glms_vec3_add(image_plane_point, glms_vec3_scale(camera_up, sensor_offset_y));
|
||||
|
||||
ray_t ray = ray_create(scene->camera.position, glms_vec3_normalize(glms_vec3_sub(image_plane_point, scene->camera.position)));
|
||||
// Calculate initial spread angle for ray differentials
|
||||
float pixel_height = scene->camera.size_y / (float)config->height;
|
||||
float spread_angle = atanf(pixel_height / scene->camera.focal_length);
|
||||
|
||||
ray_t ray = ray_create(scene->camera.position, glms_vec3_normalize(glms_vec3_sub(image_plane_point, scene->camera.position)), 0.0f, spread_angle);
|
||||
|
||||
aov_output_t aov_output = {0};
|
||||
if (has_flag(aov_flags, AOV_BEAUTY))
|
||||
@@ -147,7 +151,7 @@ void renderer_start(render_job_t* job)
|
||||
|
||||
vec3s coord = glms_vec3_add(job->scene->camera.position, glms_vec3_scale(quat_get_forward(job->scene->camera.rotation), job->scene->camera.focal_length));
|
||||
|
||||
uint32_t x, y, tile_index; // OpenMP requires these to be declared outside the parallel region.
|
||||
int64_t x, y, tile_index; // OpenMP requires these to be declared outside the parallel region.
|
||||
#pragma omp parallel for schedule(dynamic, 1) default(none) \
|
||||
shared(tile_count_x, tile_count_y, tile_count, coord, job) \
|
||||
private(x, y, tile_index)
|
||||
|
||||
@@ -393,13 +393,33 @@ vec4s texture_get_pixel(const texture_t* texture, vec2s uv, uint8_t lod)
|
||||
return get_pixel_data_from_buffer(mipmap->data, x, y, mipmap->width, texture->channel_count, texture->stride);
|
||||
}
|
||||
|
||||
float texture_get_sample_lod(vec3s view_direction, vec3s normal, float distance)
|
||||
// Calculate LOD based on Ray Cones
|
||||
float texture_get_sample_lod(const texture_t* texture, const texture_sample_context_t* sample_context)
|
||||
{
|
||||
// TODO: Implement a more accurate LOD calculation.
|
||||
const float factor = 1.0f;
|
||||
// 1. Calculate the ray footprint on the surface
|
||||
// If we hit the surface at an angle, the footprint elongates.
|
||||
float cos_theta = fabsf(glms_vec3_dot(sample_context->normal, sample_context->view_direction));
|
||||
float surface_width = sample_context->ray_width / fmaxf(cos_theta, 0.001f); // Project width onto surface
|
||||
|
||||
float n_dot_v = glms_vec3_dot(normal, view_direction);
|
||||
return fmaxf(log2f(distance * factor) - fabsf(n_dot_v), 0.0f);
|
||||
// 2. Estimate UV density (How much UV changes per meter of surface)
|
||||
// This is an approximation. A more accurate way uses Triangle derivatives (Ray Differentials).
|
||||
// For a triangle, we can approximate the scale:
|
||||
float edge1_len = glms_vec3_norm(sample_context->edge1);
|
||||
float edge2_len = glms_vec3_norm(sample_context->edge2);
|
||||
float uv_area = fabsf((sample_context->uv1.x * sample_context->uv2.y) - (sample_context->uv1.y * sample_context->uv2.x)); // Approximation of UV area
|
||||
float geo_area = glms_vec3_norm(glms_vec3_cross(sample_context->edge1, sample_context->edge2));
|
||||
|
||||
// Ratio of Texture Area to Geometric Area
|
||||
float uv_density = sqrtf(uv_area / geo_area);
|
||||
|
||||
// 3. Calculate texture footprint
|
||||
// How many texels does our ray cover?
|
||||
float texels_covered = surface_width * uv_density * fmaxf(texture->width, texture->height);
|
||||
|
||||
// 4. Convert to LOD
|
||||
// LOD 0 = 1 texel. LOD 1 = 2 texels. LOD 2 = 4 texels.
|
||||
// log2(texels_covered) gives the mip level.
|
||||
return log2f(texels_covered);
|
||||
}
|
||||
|
||||
static vec4s nearest_filter(const texture_t* texture, vec2s uv, uint8_t lod)
|
||||
@@ -460,10 +480,11 @@ static inline vec4s filter_texture(const texture_t* texture, vec2s uv, float lod
|
||||
}
|
||||
}
|
||||
|
||||
vec4s texture_sample(const texture_t* texture, vec2s uv, vec3s view_direction, vec3s normal, float distance)
|
||||
vec4s texture_sample(const texture_t* texture, const texture_sample_context_t* sample_context, vec2s uv)
|
||||
{
|
||||
warp_uv(texture->wrap_mode, &uv);
|
||||
return filter_texture(texture, uv, texture_get_sample_lod(view_direction, normal, distance));
|
||||
float lod = texture_get_sample_lod(texture, sample_context);
|
||||
return filter_texture(texture, uv, lod);
|
||||
}
|
||||
|
||||
vec4s texture_sample_lod(const texture_t* texture, vec2s uv, float lod)
|
||||
|
||||
Reference in New Issue
Block a user