diff --git a/header/Algorithm/RayIntersection.h b/header/Algorithm/RayIntersection.h index 7511af2..867c4f7 100644 --- a/header/Algorithm/RayIntersection.h +++ b/header/Algorithm/RayIntersection.h @@ -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); diff --git a/header/Common.h b/header/Common.h index cca7389..988907c 100644 --- a/header/Common.h +++ b/header/Common.h @@ -29,7 +29,6 @@ typedef enum typedef struct { vec3s wi; - vec3s normal; vec3s direct_lighting; vec3s bsdf; float pdf; diff --git a/header/Material/Material.h b/header/Material/Material.h index 9bad89e..3766ace 100644 --- a/header/Material/Material.h +++ b/header/Material/Material.h @@ -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; diff --git a/header/Rendering/Texture.h b/header/Rendering/Texture.h index 01e5d2a..be210d7 100644 --- a/header/Rendering/Texture.h +++ b/header/Rendering/Texture.h @@ -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() diff --git a/source/Algorithm/PathTracing.c b/source/Algorithm/PathTracing.c index 5bf2444..a2925ea 100644 --- a/source/Algorithm/PathTracing.c +++ b/source/Algorithm/PathTracing.c @@ -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, diff --git a/source/Algorithm/RayIntersection.c b/source/Algorithm/RayIntersection.c index d4db14c..a7a27e2 100644 --- a/source/Algorithm/RayIntersection.c +++ b/source/Algorithm/RayIntersection.c @@ -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); } diff --git a/source/Geometry/Mesh.c b/source/Geometry/Mesh.c index 0bef724..ee29575 100644 --- a/source/Geometry/Mesh.c +++ b/source/Geometry/Mesh.c @@ -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(); } diff --git a/source/Lighting/LightEvaluation.c b/source/Lighting/LightEvaluation.c index ab4d959..7df4f53 100644 --- a/source/Lighting/LightEvaluation.c +++ b/source/Lighting/LightEvaluation.c @@ -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); diff --git a/source/Lighting/SkyLight.c b/source/Lighting/SkyLight.c index a901039..5410ff2 100644 --- a/source/Lighting/SkyLight.c +++ b/source/Lighting/SkyLight.c @@ -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); diff --git a/source/Material/SimpleLit.c b/source/Material/SimpleLit.c index 33531c7..44967c9 100644 --- a/source/Material/SimpleLit.c +++ b/source/Material/SimpleLit.c @@ -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; diff --git a/source/Rendering/Texture.c b/source/Rendering/Texture.c index 0c6f674..5e3047a 100644 --- a/source/Rendering/Texture.c +++ b/source/Rendering/Texture.c @@ -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); + } } } diff --git a/source/main.c b/source/main.c index 717205f..3386182 100644 --- a/source/main.c +++ b/source/main.c @@ -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;