Add mipmap support and refactor texture handling
Note: Currently version still have lots of fireflies after applying normal map. And those fireflies are mostly coming from the nee sky. Need to double check the sky cdf and ray intersection. Changed the `hit_result_t` structure to rename a parameter in `RayIntersection.h`. Removed the `normal` field from the `path_output` structure in `Common.h`. Added new fields `screen_size`, `camera_position`, and `camera_direction` in `Material.h`. Changed the `mipmap_t` structure in `Texture.h` to include a `max_mip` field and modify the `data` field. Changed the `texture_load` function in `Texture.c` to include a `mipmap` parameter and improve texture data handling. Changed the `path_trace` function in `PathTracing.c` to update the `shading_context_t` structure and ray creation. Changed the `evaluate_bsdf_directional` function in `LightEvaluation.c` to modify angular radius calculation. Changed the `sky_create_hdr_sky` and `evaluate_bsdf_hdr_sky` functions in `SkyLight.c` to enhance texture sampling. Changed the `get_surface_data` function in `SimpleLit.c` to incorporate camera distance and view direction in calculations. Changed the `texture_get_pixel` function in `Texture.c` to improve pixel data retrieval. Changed the `warp_uv` function to use a `vec2s` structure for UV coordinates. Changed the `texture_sample` function to include additional parameters for improved sampling accuracy. Changed the `scene_setup` function in `main.c` to adjust sun light intensity and HDRI texture loading.
This commit is contained in:
@@ -28,7 +28,7 @@ typedef struct
|
||||
} hit_result_t;
|
||||
|
||||
ray_t ray_create(vec3s origin, vec3s direction);
|
||||
vec3s offset_ray_origin(vec3s point, vec3s normal, vec3s wo);
|
||||
vec3s offset_ray_origin(vec3s point, vec3s normal, vec3s w);
|
||||
|
||||
hit_result_t ray_intersect_triangle(const ray_t* ray, const triangle_t* triangle);
|
||||
bool ray_intersect_aabb(const ray_t* ray, aabb_t aabb, float* enter_out, float* exit_out);
|
||||
|
||||
@@ -29,7 +29,6 @@ typedef enum
|
||||
typedef struct
|
||||
{
|
||||
vec3s wi;
|
||||
vec3s normal;
|
||||
vec3s direct_lighting;
|
||||
vec3s bsdf;
|
||||
float pdf;
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
|
||||
typedef struct
|
||||
{
|
||||
vec4s screen_size; // w, h, 1/w, 1/h
|
||||
|
||||
vec3s camera_position;
|
||||
vec3s camera_direction;
|
||||
|
||||
vec3s position;
|
||||
vec3s normal;
|
||||
vec3s tangent;
|
||||
|
||||
@@ -32,11 +32,22 @@ typedef struct
|
||||
{
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
char* data;
|
||||
} mipmap_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
vec2s texel_size;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
|
||||
wrap_mode_t wrap_mode;
|
||||
filter_mode_t filter_mode;
|
||||
uint8_t channel_count;
|
||||
stride_t stride;
|
||||
char* data;
|
||||
|
||||
uint8_t channel_count;
|
||||
uint8_t max_mip;
|
||||
mipmap_t* data;
|
||||
} texture_t;
|
||||
|
||||
typedef struct
|
||||
@@ -61,9 +72,11 @@ bool texture_collection_init(uint16_t size, texture_collection_t* textures);
|
||||
void texture_collection_resize(texture_collection_t* textures, uint16_t size);
|
||||
void texture_collection_free(texture_collection_t* textures);
|
||||
|
||||
texture_entity_t texture_load(const char* filename, bool srgb, stride_t stride, texture_collection_t* textures);
|
||||
vec4s texture_get_pixel(const texture_t* texture, uint32_t x, uint32_t y);
|
||||
vec4s texture_sample(const texture_t* texture, float u, float v);
|
||||
texture_entity_t texture_load(const char* filename, bool srgb, bool mipmap, stride_t stride, texture_collection_t* textures);
|
||||
vec4s texture_get_pixel(const texture_t* texture, vec2s uv, uint8_t lod);
|
||||
float texture_get_sample_lod(vec3s view_direction, vec3s normal, float distance);
|
||||
vec4s texture_sample(const texture_t* texture, vec2s uv, vec3s view_direction, vec3s normal, float distance);
|
||||
vec4s texture_sample_lod(const texture_t* texture, vec2s uv, float lod);
|
||||
void texture_free(texture_t* texture);
|
||||
|
||||
inline texture_entity_t invalid_texture_entity()
|
||||
|
||||
@@ -32,6 +32,9 @@ vec4s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, uint16_
|
||||
const material_t* hit_material = &scene->materials.buffer[scene->triangles.buffer[closest_hit.triangle_id].material_id];
|
||||
shading_context_t shading_context =
|
||||
{
|
||||
.camera_position = scene->camera.position,
|
||||
.camera_direction = glms_vec3_normalize(glms_vec3_sub(closest_hit.point, scene->camera.position)),
|
||||
|
||||
.position = closest_hit.point,
|
||||
.normal = closest_hit.normal,
|
||||
.tangent = closest_hit.tangent,
|
||||
@@ -52,7 +55,6 @@ vec4s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, uint16_
|
||||
accumulated_color = glms_vec4_add(accumulated_color, glms_vec4(material_output.direct_lighting, 0.0f));
|
||||
pdf_bsdf = material_output.pdf;
|
||||
|
||||
float cos_theta = fmaxf(0.0f, glms_vec3_dot(material_output.wi, closest_hit.normal));
|
||||
throughput = glms_vec3_mul(throughput, material_output.bsdf);
|
||||
|
||||
// We do Russian roulette to decide whether to continue tracing or terminate the path
|
||||
@@ -76,7 +78,7 @@ vec4s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, uint16_
|
||||
// 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, material_output.normal, shading_context.wo), material_output.wi);
|
||||
active_ray = ray_create(offset_ray_origin(closest_hit.point, closest_hit.normal, shading_context.wo), material_output.wi);
|
||||
depth++;
|
||||
break;
|
||||
}
|
||||
@@ -99,6 +101,9 @@ void render_aov(const scene_t* scene, ray_t ray, uint32_t sample_index, uint16_t
|
||||
const material_t* hit_material = &scene->materials.buffer[scene->triangles.buffer[closest_hit.triangle_id].material_id];
|
||||
shading_context_t shading_context =
|
||||
{
|
||||
.camera_position = scene->camera.position,
|
||||
.camera_direction = glms_vec3_normalize(glms_vec3_sub(closest_hit.point, scene->camera.position)),
|
||||
|
||||
.position = closest_hit.point,
|
||||
.normal = closest_hit.normal,
|
||||
.tangent = closest_hit.tangent,
|
||||
|
||||
@@ -16,7 +16,7 @@ ray_t ray_create(vec3s origin, vec3s direction)
|
||||
((direction.z < 0.0f) ? 4 : 0)
|
||||
};
|
||||
|
||||
ray.esp = glms_vec3_max(glms_vec3_abs(ray.origin)) * gamma(10);
|
||||
ray.esp = glms_vec3_max(glms_vec3_abs(ray.origin)) * gamma(15);
|
||||
return ray;
|
||||
}
|
||||
|
||||
@@ -40,14 +40,16 @@ static inline float next_float_down(float value)
|
||||
return nextafterf(value, -INFINITY);
|
||||
}
|
||||
|
||||
vec3s offset_ray_origin(vec3s point, vec3s normal, vec3s wo)
|
||||
vec3s offset_ray_origin(vec3s point, vec3s normal, vec3s w)
|
||||
{
|
||||
vec3s abs_normal = glms_vec3_abs(normal);
|
||||
float c = glms_vec3_max(glms_vec3_abs(point)) * gamma(10);
|
||||
float d = glms_vec3_dot(abs_normal, (vec3s){c, c, c});
|
||||
float c = glms_vec3_max(glms_vec3_abs(point)) * gamma(15);
|
||||
vec3s error = (vec3s){c, c, c};
|
||||
// float g = gamma(10);
|
||||
// vec3s error = {fabsf(point.x) * g, fabsf(point.y) * g, fabsf(point.z) * g};
|
||||
float d = glms_vec3_dot(glms_vec3_abs(normal), error);
|
||||
|
||||
vec3s offset = glms_vec3_scale(normal, d);
|
||||
if (glms_vec3_dot(glms_vec3_negate(wo), normal) < 0.0f)
|
||||
if (glms_vec3_dot(glms_vec3_negate(w), normal) < 0.0f)
|
||||
{
|
||||
offset = glms_vec3_negate(offset);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ static texture_entity_t load_material_texture(const struct aiMaterial* material,
|
||||
string_join(directory, image_path, path.data, 1024);
|
||||
}
|
||||
|
||||
return texture_load(path.data, true, UINT_8, &scene->textures);
|
||||
return texture_load(path.data, true, true, UINT_8, &scene->textures);
|
||||
}
|
||||
return invalid_texture_entity();
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ path_output evaluate_bsdf_directional(directional_light_t light, const light_sha
|
||||
return output;
|
||||
}
|
||||
|
||||
float angular_radius = glm_rad(light.angular_diameter / 2.0f);
|
||||
float angular_radius = glm_rad(light.angular_diameter * 0.5f);
|
||||
|
||||
uint16_t d1 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_U);
|
||||
uint16_t d2 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_V);
|
||||
|
||||
@@ -148,7 +148,8 @@ sky_light_t sky_create_hdr_sky(const texture_collection_t* textures, texture_ent
|
||||
|
||||
for (uint32_t j = 0; j < data.width; ++j)
|
||||
{
|
||||
vec4s pixel = texture_get_pixel(hdri, j, i);
|
||||
vec2s uv = {(float)j / (float)data.width, (float)i / (float)data.height};
|
||||
vec4s pixel = texture_get_pixel(hdri, uv, 0);
|
||||
float lum = luminance(glms_vec3(pixel)); // * sin_theta;
|
||||
|
||||
p_xy_original_pdf[i * data.width + j] = lum;
|
||||
@@ -297,7 +298,7 @@ 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(get_texture(context->textures, sky_data->texture), uv.x, uv.y);
|
||||
vec4s sky_light = texture_sample_lod(get_texture(context->textures, sky_data->texture), uv, 0);
|
||||
output.direct_lighting = glms_vec3_scale(glms_vec3_mul(glms_vec3(sky_light), throughput), sky_data->intensity);
|
||||
output.pdf = 1.0f / (4.0f * PI);
|
||||
return output;
|
||||
@@ -305,12 +306,12 @@ path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_
|
||||
|
||||
uint32_t i,j;
|
||||
// Should we also use sobol numbers for the sky sampling?
|
||||
//uint16_t d1 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_U);
|
||||
//uint16_t d2 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_V);
|
||||
//float r1 = sobol_sample(sample_index, d1);
|
||||
//float r2 = sobol_sample(sample_index, d2);
|
||||
float r1 = random_float();
|
||||
float r2 = random_float();
|
||||
uint16_t d1 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_U);
|
||||
uint16_t d2 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_V);
|
||||
float r1 = sobol_sample(sample_index, d1);
|
||||
float r2 = sobol_sample(sample_index, d2);
|
||||
//float r1 = random_float();
|
||||
//float r2 = random_float();
|
||||
|
||||
uv_to_index((vec2s){r1, r2}, sky_data->width, sky_data->height, &j, &i);
|
||||
vec3s cdf = sky_data->cdf_cache[i * sky_data->width + j];
|
||||
@@ -323,7 +324,6 @@ path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_
|
||||
}
|
||||
|
||||
ray_t shadow_ray = ray_create(offset_ray_origin(context->position, context->normal, context->wo), wi);
|
||||
|
||||
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)
|
||||
@@ -334,7 +334,7 @@ path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_
|
||||
vec2s uv = direction_to_equirectangular(wi);
|
||||
|
||||
const texture_t* hdri = get_texture(context->textures, sky_data->texture);
|
||||
vec4s pixel = texture_sample(hdri, uv.x, uv.y);
|
||||
vec4s pixel = texture_sample_lod(hdri, uv, 0);
|
||||
vec3s sky_light = glms_vec3_scale(glms_vec3(pixel), sky_data->intensity);
|
||||
|
||||
float pdf = hdr_pdf(sky_data->cdf_cache, wi, sky_data->height, sky_data->width);
|
||||
|
||||
@@ -12,11 +12,14 @@ static void get_surface_data(const shading_context_t* context, const void* prope
|
||||
{
|
||||
const simple_lit_properties_t* prop = (simple_lit_properties_t*)properties;
|
||||
|
||||
float camera_distance = glms_vec3_distance(context->camera_position, context->position);
|
||||
vec3s view = (context->camera_direction);
|
||||
|
||||
data_out->albedo = prop->albedo;
|
||||
const texture_t* albedo_texture = get_texture(context->textures, prop->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.x, context->uv.y)));
|
||||
data_out->albedo = glms_vec3_mul(data_out->albedo, glms_vec3(texture_sample(albedo_texture, context->uv, view, context->normal, camera_distance)));
|
||||
}
|
||||
|
||||
// NOTE: Currently normal map will produce lots of fireflies, need to be fixed.
|
||||
@@ -25,7 +28,7 @@ static void get_surface_data(const shading_context_t* context, const void* prope
|
||||
const texture_t* normal_texture = get_texture(context->textures, prop->normal_texture);
|
||||
if (normal_texture != NULL && normal_texture->data != NULL)
|
||||
{
|
||||
vec3s normal_sample = glms_vec3(texture_sample(normal_texture, context->uv.x, context->uv.y));
|
||||
vec3s normal_sample = glms_vec3(texture_sample(normal_texture, context->uv, view, context->normal, camera_distance));
|
||||
normal_sample = normal_unpack(normal_sample);
|
||||
data_out->normal = normal_ts_to_ws(normal_sample, context->normal, context->tangent);
|
||||
}
|
||||
@@ -34,14 +37,14 @@ static void get_surface_data(const shading_context_t* context, const void* prope
|
||||
const texture_t* roughness_texture = get_texture(context->textures, prop->roughness_texture);
|
||||
if (roughness_texture != NULL && roughness_texture->data != NULL)
|
||||
{
|
||||
data_out->roughness = data_out->roughness * texture_sample(roughness_texture, context->uv.x, context->uv.y).x;
|
||||
data_out->roughness = data_out->roughness * texture_sample(roughness_texture, context->uv, view, context->normal, camera_distance).x;
|
||||
}
|
||||
|
||||
data_out->metallic = prop->metallic;
|
||||
const texture_t* metallic_texture = get_texture(context->textures, prop->metallic_texture);
|
||||
if (metallic_texture != NULL && metallic_texture->data != NULL)
|
||||
{
|
||||
data_out->metallic = data_out->metallic * texture_sample(metallic_texture, context->uv.x, context->uv.y).x;
|
||||
data_out->metallic = data_out->metallic * texture_sample(metallic_texture, context->uv, view, context->normal, camera_distance).x;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,15 +91,16 @@ static vec3s sample_bsdf_simple_lit(const shading_context_t* context, const surf
|
||||
// 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(surface_data->roughness);
|
||||
|
||||
float theta = acosf(powf(sobol_sample(sample_index, d1), 1.0f / (specular_exponent + 1.0f)));
|
||||
// float theta = acosf(powf(sobol_sample(sample_index, d1), 1.0f / (specular_exponent + 1.0f)));
|
||||
float cos_theta = powf(sobol_sample(sample_index, d1), 1.0f / (specular_exponent + 1.0f));
|
||||
float sin_theta = sin_from_cos(cos_theta);
|
||||
float phi = 2.0f * PI * sobol_sample(sample_index, d2);
|
||||
// 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)
|
||||
sin_theta * cosf(phi),
|
||||
sin_theta * sinf(phi),
|
||||
cos_theta
|
||||
};
|
||||
|
||||
vec3s tangent_u; // World-space tangent (U)
|
||||
@@ -259,7 +263,6 @@ path_output simple_lit_render_loop(const shading_context_t* properties, const sh
|
||||
vec3s bsdf = evaluate_bsdf_simple_lit(context, &surface_data, output.wi);
|
||||
float cos_theta = fmaxf(0.0f, glms_vec3_dot(output.wi, surface_data.normal));
|
||||
|
||||
output.normal = surface_data.normal;
|
||||
output.bsdf = glms_vec3_scale(bsdf, cos_theta / output.pdf);
|
||||
output.state = PS_NORMAL;
|
||||
return output;
|
||||
|
||||
@@ -64,7 +64,195 @@ void texture_collection_free(texture_collection_t* textures)
|
||||
textures->buffer = NULL;
|
||||
}
|
||||
|
||||
texture_entity_t texture_load(const char* filename, bool srgb, stride_t stride, texture_collection_t* textures)
|
||||
|
||||
static inline void read_pixel_raw(const char* data, uint32_t x, uint32_t y, uint32_t width, uint8_t channel_count, stride_t stride, char* out_pixel_data)
|
||||
{
|
||||
size_t pixel_offset = (size_t)(y * width + x) * channel_count * stride;
|
||||
memcpy(out_pixel_data, data + pixel_offset, (size_t)channel_count * stride);
|
||||
}
|
||||
|
||||
static inline void write_pixel_raw(char* data, uint32_t x, uint32_t y, uint32_t width, uint8_t channel_count, stride_t stride, const char* in_pixel_data)
|
||||
{
|
||||
size_t pixel_offset = (size_t)(y * width + x) * channel_count * stride;
|
||||
memcpy(data + pixel_offset, in_pixel_data, (size_t)channel_count * stride);
|
||||
}
|
||||
|
||||
static void average_pixels_box(const char* current_data, uint32_t current_width, uint32_t current_height,
|
||||
uint32_t src_x, uint32_t src_y, uint8_t channel_count, stride_t stride, char* out_averaged_pixel) {
|
||||
|
||||
size_t pixel_byte_size = (size_t)channel_count * stride;
|
||||
float pixel_count = 0.0f;
|
||||
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
char pixel_data[pixel_byte_size]; // Buffer to read individual pixel data
|
||||
|
||||
// Use a float buffer to accumulate the sum for each channel
|
||||
// This allows us to sum values from different strides by converting them to float
|
||||
float sum_float[channel_count];
|
||||
memset(sum_float, 0, sizeof(float) * channel_count);
|
||||
#else
|
||||
char* pixel_data = (char*)malloc(pixel_byte_size); // Buffer to read individual pixel data
|
||||
if (pixel_data == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
float* sum_float = (float*)calloc(channel_count, sizeof(float));
|
||||
if (sum_float == NULL)
|
||||
{
|
||||
free(pixel_data);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Loop through the 2x2 block in the current level
|
||||
for (int dy = 0; dy < 2; ++dy)
|
||||
{
|
||||
for (int dx = 0; dx < 2; ++dx)
|
||||
{
|
||||
uint32_t current_x = src_x + dx;
|
||||
uint32_t current_y = src_y + dy;
|
||||
|
||||
// Check if the pixel is within the bounds of the current level
|
||||
if (current_x < current_width && current_y < current_height) {
|
||||
// Read the raw pixel data
|
||||
read_pixel_raw(current_data, current_x, current_y, current_width, channel_count, stride, pixel_data);
|
||||
|
||||
// Sum the pixel data channel by channel, converting to float for summation
|
||||
for (uint8_t c = 0; c < channel_count; c++)
|
||||
{
|
||||
switch (stride)
|
||||
{
|
||||
case UINT_8:
|
||||
sum_float[c] += (float)(((uint8_t*)pixel_data)[c]);
|
||||
break;
|
||||
case UINT_16:
|
||||
sum_float[c] += (float)(((uint16_t*)pixel_data)[c]);
|
||||
break;
|
||||
case FLOAT_32:
|
||||
sum_float[c] += ((float*)pixel_data)[c];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
pixel_count += 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Divide the sum by the pixel count to get the average for each channel
|
||||
if (pixel_count > 0.0f)
|
||||
{
|
||||
// Convert the averaged float values back to the original stride type and write to the output buffer
|
||||
for (uint8_t c = 0; c < channel_count; c++) {
|
||||
float average_value = sum_float[c] / pixel_count;
|
||||
|
||||
switch (stride)
|
||||
{
|
||||
case UINT_8:
|
||||
((uint8_t*)out_averaged_pixel)[c] = (uint8_t)glm_clamp(average_value, 0.0f, 255.0f);
|
||||
break;
|
||||
case UINT_16:
|
||||
((uint16_t*)out_averaged_pixel)[c] = (uint16_t)glm_clamp(average_value, 0.0f, 65535.0f);
|
||||
break;
|
||||
case FLOAT_32:
|
||||
((float*)out_averaged_pixel)[c] = average_value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This case should ideally not happen if current_width or current_height > 1,
|
||||
// but as a safeguard, zero out the output buffer.
|
||||
memset(out_averaged_pixel, 0, pixel_byte_size);
|
||||
}
|
||||
|
||||
#if !defined(__clang__) && !defined(__GNUC__)
|
||||
free(pixel_data);
|
||||
free(sum_float);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void generate_mipmap(char* raw_data, mipmap_t* texture_data, uint32_t width, uint32_t height, uint8_t channel_count, uint8_t max_level, stride_t stride)
|
||||
{
|
||||
// Store the base level (Level 0)
|
||||
texture_data[0] = (mipmap_t)
|
||||
{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.data = raw_data,
|
||||
};
|
||||
|
||||
char* current_data = raw_data;
|
||||
uint32_t current_width = width;
|
||||
uint32_t current_height = height;
|
||||
int level = 1;
|
||||
|
||||
uint32_t pixel_byte_size = channel_count * stride;
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
char averaged_pixel_buffer[pixel_byte_size];
|
||||
#else
|
||||
char* averaged_pixel_buffer = (char*)malloc(pixel_byte_size);
|
||||
if (averaged_pixel_buffer == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Continue generating levels as long as at least one dimension is greater than 1
|
||||
while ((current_width > 1 || current_height > 1) && level <= max_level)
|
||||
{
|
||||
uint32_t next_width = max(1u, current_width / 2);
|
||||
uint32_t next_height = max(1u, current_height / 2);
|
||||
|
||||
size_t next_level_size = (size_t)next_width * next_height * channel_count * stride;
|
||||
char* next_data = (char*)malloc(next_level_size);
|
||||
if (next_data == NULL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Iterate through each pixel in the NEXT mipmap level
|
||||
for (uint32_t y = 0; y < next_height; ++y)
|
||||
{
|
||||
for (uint32_t x = 0; x < next_width; ++x)
|
||||
{
|
||||
// Calculate the starting coordinates (top-left corner) of the 2x2 block in the CURRENT level
|
||||
uint32_t src_x = x * 2;
|
||||
uint32_t src_y = y * 2;
|
||||
|
||||
// Average the pixels in the 2x2 block from the CURRENT level using Box filter
|
||||
average_pixels_box(current_data, current_width, current_height,
|
||||
src_x, src_y, channel_count, stride, averaged_pixel_buffer);
|
||||
|
||||
// Write the averaged pixel value to the corresponding location in the NEXT level
|
||||
write_pixel_raw(next_data, x, y, next_width, channel_count, stride, averaged_pixel_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
texture_data[level] = (mipmap_t)
|
||||
{
|
||||
.width = next_width,
|
||||
.height = next_height,
|
||||
.data = next_data,
|
||||
};
|
||||
|
||||
// Update for the next iteration
|
||||
current_data = next_data;
|
||||
current_width = next_width;
|
||||
current_height = next_height;
|
||||
level++;
|
||||
}
|
||||
|
||||
#if !defined(__clang__) && !defined(__GNUC__)
|
||||
free(averaged_pixel_buffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
texture_entity_t texture_load(const char* filename, bool srgb, bool mipmap, stride_t stride, texture_collection_t* textures)
|
||||
{
|
||||
// TODO: This hurts performance, consider using a hash map or similar structure for faster lookups
|
||||
|
||||
@@ -77,33 +265,47 @@ texture_entity_t texture_load(const char* filename, bool srgb, stride_t stride,
|
||||
// }
|
||||
|
||||
int width, height, channels;
|
||||
char* data = NULL;
|
||||
char* raw_data = NULL;
|
||||
|
||||
switch (stride)
|
||||
{
|
||||
case UINT_8:
|
||||
data = stbi_load(filename, &width, &height, &channels, 0);
|
||||
raw_data = (char*)stbi_load(filename, &width, &height, &channels, 0);
|
||||
break;
|
||||
|
||||
case UINT_16:
|
||||
data = (char*)stbi_load_16(filename, &width, &height, &channels, 0);
|
||||
raw_data = (char*)stbi_load_16(filename, &width, &height, &channels, 0);
|
||||
break;
|
||||
case FLOAT_32:
|
||||
data = (char*)stbi_loadf(filename, &width, &height, &channels, 0);
|
||||
raw_data = (char*)stbi_loadf(filename, &width, &height, &channels, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
if (data == NULL)
|
||||
if (raw_data == NULL)
|
||||
{
|
||||
return invalid_texture_entity();
|
||||
}
|
||||
|
||||
uint8_t max_mip_level = mipmap ? (uint8_t)log2f(fmaxf((float)width, (float)height)) : 0;
|
||||
mipmap_t* temp_texture_data = (mipmap_t*)calloc((size_t)max_mip_level + 1, sizeof(mipmap_t));
|
||||
if (temp_texture_data == NULL)
|
||||
{
|
||||
stbi_image_free(raw_data);
|
||||
return invalid_texture_entity();
|
||||
}
|
||||
|
||||
generate_mipmap(raw_data, temp_texture_data, (uint32_t)width, (uint32_t)height, (uint8_t)channels, max_mip_level, stride);
|
||||
|
||||
texture_t texture = {0};
|
||||
|
||||
texture.texel_size = (vec2s){1.0f / (float)width, 1.0f / (float)height};
|
||||
texture.width = (uint32_t)width;
|
||||
texture.height = (uint32_t)height;
|
||||
|
||||
texture.channel_count = (uint8_t)channels;
|
||||
texture.max_mip = max_mip_level;
|
||||
texture.stride = stride;
|
||||
texture.data = data;
|
||||
texture.data = temp_texture_data;
|
||||
|
||||
texture.wrap_mode = REPEAT;
|
||||
texture.filter_mode = LINEAR;
|
||||
@@ -121,131 +323,163 @@ texture_entity_t texture_load(const char* filename, bool srgb, stride_t stride,
|
||||
return entity;
|
||||
}
|
||||
|
||||
static inline void warp_uv(wrap_mode_t mode, float* u, float* v)
|
||||
static inline void warp_uv(wrap_mode_t mode, vec2s* uv)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case REPEAT:
|
||||
*u = fmodf(fabsf(*u), 1.0f);
|
||||
*v = fmodf(fabsf(*v), 1.0f);
|
||||
uv->x = fmodf(fabsf(uv->x), 1.0f);
|
||||
uv->y = fmodf(fabsf(uv->y), 1.0f);
|
||||
break;
|
||||
case CLAMP:
|
||||
*u = fminf(fmaxf(*u, 0.0f), 1.0f);
|
||||
*v = fminf(fmaxf(*v, 0.0f), 1.0f);
|
||||
*uv = glms_vec2_clamp(*uv, 0.0f, 1.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
vec4s texture_get_pixel(const texture_t* texture, uint32_t x, uint32_t y)
|
||||
static vec4s get_pixel_data_from_buffer(const char* data, uint32_t x, uint32_t y, uint32_t width, uint8_t channel_count, stride_t stride)
|
||||
{
|
||||
if (x >= texture->width || y >= texture->height)
|
||||
{
|
||||
return (vec4s){0.0f, 0.0f, 0.0f, 1.0f};
|
||||
}
|
||||
size_t pixel_start_offset = (size_t)(y * width + x) * channel_count * stride;
|
||||
|
||||
uint32_t pixel_index = y * texture->width + x;
|
||||
size_t pixel_offset = (size_t)pixel_index * texture->channel_count * texture->stride;
|
||||
vec4s out = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
|
||||
uint8_t* base8 = (uint8_t*)texture->data + pixel_offset;
|
||||
uint16_t* base16 = (uint16_t*)(texture->data) + ((y * texture->width + x) * texture->channel_count);
|
||||
float* base32 = (float*)(texture->data) + ((y * texture->width + x) * texture->channel_count);
|
||||
|
||||
vec4s out = {0, 0, 0, 1}; // default alpha = 1
|
||||
|
||||
for (int c = 0; c < texture->channel_count && c < 4; ++c)
|
||||
for (int c = 0; c < channel_count && c < 4; c++)
|
||||
{
|
||||
float value = 0.0f;
|
||||
switch (texture->stride)
|
||||
size_t channel_offset = pixel_start_offset + (size_t)c * stride;
|
||||
|
||||
if (channel_offset >= (size_t)y * width * channel_count * stride + (size_t)width * channel_count * stride)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (stride)
|
||||
{
|
||||
case UINT_8:
|
||||
value = base8[c] / 255.0f;
|
||||
value = (float)(((uint8_t*)data)[channel_offset]) / 255.0f;
|
||||
break;
|
||||
case UINT_16:
|
||||
value = base16[c] / 65535.0f;
|
||||
value = (float)(*((uint16_t*)(data + channel_offset))) / 65535.0f;
|
||||
break;
|
||||
case FLOAT_32:
|
||||
value = base32[c];
|
||||
value = *((float*)(data + channel_offset));
|
||||
break;
|
||||
default:
|
||||
value = (c == 3) ? 1.0f : 0.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
out.raw[c] = value;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static vec4s nearest_filter(const texture_t* texture, float u, float v)
|
||||
vec4s texture_get_pixel(const texture_t* texture, vec2s uv, uint8_t lod)
|
||||
{
|
||||
uint32_t x = (uint32_t)floorf(u * (texture->width - 1));
|
||||
uint32_t y = (uint32_t)floorf(v * (texture->height - 1));
|
||||
uint8_t mip_level = (uint8_t)glm_clamp(lod, 0, texture->max_mip);
|
||||
const mipmap_t* mipmap = &texture->data[mip_level];
|
||||
if (mipmap->data == NULL)
|
||||
{
|
||||
return (vec4s){0.0f, 0.0f, 0.0f, 1.0f};
|
||||
}
|
||||
|
||||
x = x < texture->width ? x : texture->width - 1;
|
||||
y = y < texture->height ? y : texture->height - 1;
|
||||
uint32_t x = (uint32_t)floorf(uv.x * (mipmap->width - 1));
|
||||
uint32_t y = (uint32_t)floorf(uv.y * (mipmap->height - 1));
|
||||
|
||||
return texture_get_pixel(texture, x, y);
|
||||
x = x < mipmap->width ? x : mipmap->width - 1;
|
||||
y = y < mipmap->height ? y : mipmap->height - 1;
|
||||
|
||||
return get_pixel_data_from_buffer(mipmap->data, x, y, mipmap->width, texture->channel_count, texture->stride);
|
||||
}
|
||||
|
||||
static vec4s linear_filter(const texture_t* texture, float u, float v)
|
||||
float texture_get_sample_lod(vec3s view_direction, vec3s normal, float distance)
|
||||
{
|
||||
float x = u * (texture->width - 1);
|
||||
float y = v * (texture->height - 1);
|
||||
// TODO: Implement a more accurate LOD calculation based on the distance to the surface and the view direction.
|
||||
const float factor = 1.0f;
|
||||
|
||||
float n_dot_v = glms_vec3_dot(normal, view_direction);
|
||||
return fmaxf(log2f(distance * factor) - fabsf(n_dot_v), 0.0f);
|
||||
}
|
||||
|
||||
static vec4s nearest_filter(const texture_t* texture, vec2s uv, uint8_t lod)
|
||||
{
|
||||
return texture_get_pixel(texture, uv, lod);
|
||||
}
|
||||
|
||||
static vec4s linear_filter(const texture_t* texture, vec2s uv, uint8_t lod)
|
||||
{
|
||||
uint8_t mip_level = (uint8_t)glm_clamp((float)lod, 0.0f, (float)texture->max_mip);
|
||||
const mipmap_t* mipmap = &texture->data[mip_level];
|
||||
|
||||
if (mipmap->data == NULL)
|
||||
{
|
||||
return (vec4s){0.0f, 0.0f, 0.0f, 1.0f};
|
||||
}
|
||||
|
||||
float x = uv.x * (float)(mipmap->width - 1);
|
||||
float y = uv.y * (float)(mipmap->height - 1);
|
||||
|
||||
uint32_t x0 = (uint32_t)floorf(x);
|
||||
uint32_t x1 = x0 + 1;
|
||||
uint32_t y0 = (uint32_t)floorf(y);
|
||||
|
||||
uint32_t x1 = x0 + 1;
|
||||
uint32_t y1 = y0 + 1;
|
||||
|
||||
float sx = x - (float)x0;
|
||||
float sy = y - (float)y0;
|
||||
|
||||
// Clamp to edges
|
||||
x0 = x0 < texture->width ? x0 : texture->width - 1;
|
||||
x1 = x1 < texture->width ? x1 : texture->width - 1;
|
||||
y0 = y0 < texture->height ? y0 : texture->height - 1;
|
||||
y1 = y1 < texture->height ? y1 : texture->height - 1;
|
||||
x0 = glm_clamp((float)x0, 0.0f, (float)mipmap->width - 1);
|
||||
x1 = glm_clamp((float)x1, 0.0f, (float)mipmap->width - 1);
|
||||
y0 = glm_clamp((float)y0, 0.0f, (float)mipmap->height - 1);
|
||||
y1 = glm_clamp((float)y1, 0.0f, (float)mipmap->height - 1);
|
||||
|
||||
// Sample 4 texels
|
||||
vec4s c00 = texture_get_pixel(texture, x0, y0);
|
||||
vec4s c10 = texture_get_pixel(texture, x1, y0);
|
||||
vec4s c01 = texture_get_pixel(texture, x0, y1);
|
||||
vec4s c11 = texture_get_pixel(texture, x1, y1);
|
||||
// Get the pixel values for the four corners of the 2x2 block
|
||||
vec4s c00 = get_pixel_data_from_buffer(mipmap->data, x0, y0, mipmap->width, texture->channel_count, texture->stride);
|
||||
vec4s c10 = get_pixel_data_from_buffer(mipmap->data, x1, y0, mipmap->width, texture->channel_count, texture->stride);
|
||||
vec4s c01 = get_pixel_data_from_buffer(mipmap->data, x0, y1, mipmap->width, texture->channel_count, texture->stride);
|
||||
vec4s c11 = get_pixel_data_from_buffer(mipmap->data, x1, y1, mipmap->width, texture->channel_count, texture->stride);
|
||||
|
||||
// Interpolate along x
|
||||
vec4s c0 = glms_vec4_lerp(c00, c10, sx);
|
||||
vec4s c1 = glms_vec4_lerp(c01, c11, sx);
|
||||
|
||||
// Interpolate along y
|
||||
vec4s result = glms_vec4_lerp(c0, c1, sy);
|
||||
vec4s c0 = glms_vec4_lerp(c00, c10, sx); // Interpolate along x for the top row
|
||||
vec4s c1 = glms_vec4_lerp(c01, c11, sx); // Interpolate along x for the bottom row
|
||||
vec4s result = glms_vec4_lerp(c0, c1, sy); // Interpolate along y
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline vec4s filter_texture(const texture_t* texture, float u, float v)
|
||||
static inline vec4s filter_texture(const texture_t* texture, vec2s uv, float lod)
|
||||
{
|
||||
switch (texture->filter_mode)
|
||||
{
|
||||
case NEAREST:
|
||||
return nearest_filter(texture, u, v);
|
||||
return nearest_filter(texture, uv, (uint8_t)lod);
|
||||
case LINEAR:
|
||||
return linear_filter(texture, u, v);
|
||||
return linear_filter(texture, uv, (uint8_t)lod);
|
||||
default:
|
||||
return (vec4s){0.0f, 0.0f, 0.0f, 1.0f};
|
||||
}
|
||||
}
|
||||
|
||||
vec4s texture_sample(const texture_t* texture, float u, float v)
|
||||
vec4s texture_sample(const texture_t* texture, vec2s uv, vec3s view_direction, vec3s normal, float distance)
|
||||
{
|
||||
warp_uv(texture->wrap_mode, &u, &v);
|
||||
return filter_texture(texture, u, v);
|
||||
warp_uv(texture->wrap_mode, &uv);
|
||||
return filter_texture(texture, uv, texture_get_sample_lod(view_direction, normal, distance));
|
||||
}
|
||||
|
||||
vec4s texture_sample_lod(const texture_t* texture, vec2s uv, float lod)
|
||||
{
|
||||
lod = glm_clamp(lod, 0.0f, texture->max_mip);
|
||||
warp_uv(texture->wrap_mode, &uv);
|
||||
return filter_texture(texture, uv, lod);
|
||||
}
|
||||
|
||||
void texture_free(texture_t* texture)
|
||||
{
|
||||
if (texture != NULL && texture->data != NULL)
|
||||
{
|
||||
stbi_image_free(texture->data);
|
||||
stbi_image_free(texture->data[0].data);
|
||||
for (uint8_t i = 1; i <= texture->max_mip; i++)
|
||||
{
|
||||
free(texture->data[i].data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ static bool scene_setup(scene_t* scene)
|
||||
directional_light_t* sun_light = &scene->lights.directional_lights[sun.id];
|
||||
sun_light->direction = glms_vec3_normalize((vec3s){0.6f, 1.0f, 0.25f});
|
||||
sun_light->color = (vec3s){1.0f, 0.93f, 0.87f};
|
||||
sun_light->intensity = 1.0f;
|
||||
sun_light->intensity = 2.0f;
|
||||
sun_light->angular_diameter = 0.53f;
|
||||
|
||||
//scene->lights.sky_light = sky_create_constant_sky(&(constant_sky_data_t)
|
||||
@@ -40,7 +40,8 @@ static bool scene_setup(scene_t* scene)
|
||||
// .color = (vec3s){0.73f, 0.82f, 1.0f},
|
||||
// .intensity = 1.0f,
|
||||
//});
|
||||
texture_entity_t hdri = texture_load(HDRI_PATH, false, FLOAT_32, &scene->textures);
|
||||
texture_entity_t hdri = texture_load(HDRI_PATH, false, false, FLOAT_32, &scene->textures);
|
||||
scene->textures.buffer[hdri.id].texture.wrap_mode = CLAMP;
|
||||
scene->lights.sky_light = sky_create_hdr_sky(&scene->textures, hdri, 1.0f);
|
||||
|
||||
return scene->lights.sky_light.data != NULL;
|
||||
|
||||
Reference in New Issue
Block a user