diff --git a/src/renderer/rasterizer/rasterizer.h b/src/renderer/rasterizer/rasterizer.h index a3fa7f8..8f3f40e 100644 --- a/src/renderer/rasterizer/rasterizer.h +++ b/src/renderer/rasterizer/rasterizer.h @@ -4,8 +4,6 @@ #include #include -#include -#include #include @@ -55,22 +53,32 @@ namespace cg::renderer std::shared_ptr> in_render_target, std::shared_ptr> in_depth_buffer) { - // TODO Lab: 1.02 Implement `set_render_target`, `set_viewport`, `clear_render_target` methods of `cg::renderer::rasterizer` class - // TODO Lab: 1.06 Adjust `set_render_target`, and `clear_render_target` methods of `cg::renderer::rasterizer` class to consume a depth buffer + if (in_render_target) { + render_target = in_render_target; + } + if (in_depth_buffer) { + depth_buffer = in_depth_buffer; + } } template inline void rasterizer::set_viewport(size_t in_width, size_t in_height) { - // TODO Lab: 1.02 Implement `set_render_target`, `set_viewport`, `clear_render_target` methods of `cg::renderer::rasterizer` class + width = in_width; + height = in_height; } template inline void rasterizer::clear_render_target( const RT& in_clear_value, const float in_depth) { - // TODO Lab: 1.02 Implement `set_render_target`, `set_viewport`, `clear_render_target` methods of `cg::renderer::rasterizer` class - // TODO Lab: 1.06 Adjust `set_render_target`, and `clear_render_target` methods of `cg::renderer::rasterizer` class to consume a depth buffer + for (size_t i = 0; i < render_target->get_number_of_elements(); ++i) { + render_target->item(i) = in_clear_value; + } + + for (size_t i = 0; i < depth_buffer->get_number_of_elements(); ++i) { + depth_buffer->item(i) = in_depth; + } } template @@ -90,23 +98,78 @@ namespace cg::renderer template inline void rasterizer::draw(size_t num_vertexes, size_t vertex_offset) { - // TODO Lab: 1.04 Implement `cg::world::camera` class - // TODO Lab: 1.05 Add `Rasterization` and `Pixel shader` stages to `draw` method of `cg::renderer::rasterizer` - // TODO Lab: 1.06 Add `Depth test` stage to `draw` method of `cg::renderer::rasterizer` + size_t vertex_id = vertex_offset; + while (vertex_id < vertex_offset + num_vertexes) { + std::vector vertices(3); + vertices[0] = vertex_buffer->item(index_buffer->item(vertex_id++)); + vertices[1] = vertex_buffer->item(index_buffer->item(vertex_id++)); + vertices[2] = vertex_buffer->item(index_buffer->item(vertex_id++)); + + for (auto& vertex: vertices) { + float4 coords{vertex.x, vertex.y, vertex.z, 1.f}; + auto processed_vertex = vertex_shader(coords, vertex); + + vertex.x = processed_vertex.first.x / processed_vertex.first.w; + vertex.y = processed_vertex.first.y / processed_vertex.first.w; + vertex.z = processed_vertex.first.z / processed_vertex.first.w; + + vertex.x = (vertex.x + 1.f) * width / 2.f; + vertex.y = (-vertex.y + 1.f) * height / 2.f; + } + + float2 vertex_a = float2{vertices[0].x, vertices[0].y}; + float2 vertex_b = float2{vertices[1].x, vertices[1].y}; + float2 vertex_c = float2{vertices[2].x, vertices[2].y}; + + float2 min_vertex = min(vertex_a, min(vertex_b, vertex_c)); + float2 bounding_box_begin = round(clamp(min_vertex, float2{0.f, 0.f}, + float2{static_cast(width - 1), static_cast(height - 1)})); + + float2 max_vertex = max(vertex_a, max(vertex_b, vertex_c)); + float2 bounding_box_end = round(clamp(max_vertex, float2{0.f, 0.f}, + float2{static_cast(width - 1), static_cast(height - 1)})); + + float edge = edge_function(vertex_a, vertex_b, vertex_c); + + for (float x = bounding_box_begin.x; x <= bounding_box_end.x; x += 1.f) { + for (float y = bounding_box_begin.y; y <= bounding_box_end.y; y += 1.f) { + float2 point{x, y}; + float edge0 = edge_function(vertex_a, vertex_b, point); + float edge1 = edge_function(vertex_b, vertex_c, point); + float edge2 = edge_function(vertex_c, vertex_a, point); + if (edge0 >= 0.f && edge1 >= 0.f && edge2 >= 0.f) { + size_t u_x = static_cast(x); + size_t u_y = static_cast(y); + + float u = edge1 / edge; + float v = edge2 / edge; + float w = edge0 / edge; + + float z = u * vertices[0].z + + v * vertices[1].z + + w * vertices[2].z; + + if (depth_test(z, u_x, u_y)) { + auto pixel_result = pixel_shader(vertices[0], z); + render_target->item(u_x, u_y) = RT::from_color(pixel_result); + depth_buffer->item(u_x, u_y) = z; + } + } + } + } + } } template inline float rasterizer::edge_function(float2 a, float2 b, float2 c) { - // TODO Lab: 1.05 Implement `cg::renderer::rasterizer::edge_function` method - return 0.f; + return (c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x); } template inline bool rasterizer::depth_test(float z, size_t x, size_t y) { - // TODO Lab: 1.06 Implement `depth_test` function of `cg::renderer::rasterizer` class if (!depth_buffer) { return true; diff --git a/src/renderer/rasterizer/rasterizer_renderer.cpp b/src/renderer/rasterizer/rasterizer_renderer.cpp index d1bd605..55f51cb 100644 --- a/src/renderer/rasterizer/rasterizer_renderer.cpp +++ b/src/renderer/rasterizer/rasterizer_renderer.cpp @@ -5,17 +5,74 @@ void cg::renderer::rasterization_renderer::init() { - // TODO Lab: 1.02 Implement image clearing & saving in `cg::renderer::rasterization_renderer` class - // TODO Lab: 1.03 Adjust `cg::renderer::rasterization_renderer` class to consume `cg::world::model` - // TODO Lab: 1.04 Setup an instance of camera `cg::world::camera` class in `cg::renderer::rasterization_renderer` - // TODO Lab: 1.06 Add depth buffer in `cg::renderer::rasterization_renderer` + rasterizer = std::make_shared< + cg::renderer::rasterizer>(); + rasterizer->set_viewport(settings->width, settings->height); + + render_target = std::make_shared>( + settings->width, settings->height); + + rasterizer->set_render_target(render_target); + + model = std::make_shared(); + model->load_obj(settings->model_path); + + camera = std::make_shared(); + camera->set_height(static_cast(settings->height)); + camera->set_width(static_cast(settings->width)); + camera->set_position(float3{ + settings->camera_position[0], + settings->camera_position[1], + settings->camera_position[2], + }); + + camera->set_theta(settings->camera_theta); + camera->set_phi(settings->camera_phi); + camera->set_angle_of_view(settings->camera_angle_of_view); + camera->set_z_near(settings->camera_z_near); + camera->set_z_far(settings->camera_z_far); + + depth_buffer = std::make_shared>( + settings->width, settings->height); + rasterizer->set_render_target(render_target, depth_buffer); } void cg::renderer::rasterization_renderer::render() { - // TODO Lab: 1.02 Implement image clearing & saving in `cg::renderer::rasterization_renderer` class - // TODO Lab: 1.04 Implement `vertex_shader` lambda for the instance of `cg::renderer::rasterizer` - // TODO Lab: 1.05 Implement `pixel_shader` lambda for the instance of `cg::renderer::rasterizer` - // TODO Lab: 1.03 Adjust `cg::renderer::rasterization_renderer` class to consume `cg::world::model` + float4x4 matrix = mul( + camera->get_projection_matrix(), + camera->get_view_matrix(), + model->get_world_matrix()); + rasterizer->vertex_shader = [&](float4 vertex, cg::vertex data) { + auto processed = mul(matrix, vertex); + return std::make_pair(processed, data); + }; + rasterizer->pixel_shader = [](cg::vertex data, float z) { + return cg::color{ + data.ambient_r, + data.ambient_g, + data.ambient_b, + }; + }; + + auto start = std::chrono::high_resolution_clock::now(); + rasterizer->clear_render_target({111, 5, 243}); + auto stop = std::chrono::high_resolution_clock::now(); + std::chrono::duration clear_duration = stop - start; + std::cout << "Clearing took " << clear_duration.count() << "ms\n"; + + start = std::chrono::high_resolution_clock::now(); + for (size_t shape_id = 0; shape_id < model->get_index_buffers().size(); ++shape_id) { + rasterizer->set_vertex_buffer(model->get_vertex_buffers()[shape_id]); + rasterizer->set_index_buffer(model->get_index_buffers()[shape_id]); + rasterizer->draw( + model->get_index_buffers()[shape_id]->get_number_of_elements(), + 0); + } + stop = std::chrono::high_resolution_clock::now(); + std::chrono::duration rendering_duration = stop - start; + std::cout << "Rendering took " << rendering_duration.count() << "ms\n"; + + utils::save_resource(*render_target, settings->result_path); } void cg::renderer::rasterization_renderer::destroy() {} diff --git a/src/renderer/raytracer/raytracer.h b/src/renderer/raytracer/raytracer.h index d3d05e0..e5f422d 100644 --- a/src/renderer/raytracer/raytracer.h +++ b/src/renderer/raytracer/raytracer.h @@ -54,7 +54,20 @@ namespace cg::renderer inline triangle::triangle( const VB& vertex_a, const VB& vertex_b, const VB& vertex_c) { - // TODO Lab: 2.02 Implement a constructor of `triangle` struct + a = float3{vertex_a.x, vertex_a.y, vertex_a.z}; + b = float3{vertex_b.x, vertex_b.y, vertex_b.z}; + c = float3{vertex_c.x, vertex_c.y, vertex_c.z}; + + ba = b - a; + ca = c - a; + + na = float3{vertex_a.nx, vertex_a.ny, vertex_a.nz}; + nb = float3{vertex_b.nx, vertex_b.ny, vertex_b.nz}; + nc = float3{vertex_c.nx, vertex_c.ny, vertex_c.nz}; + + ambient = float3{vertex_a.ambient_r, vertex_a.ambient_g, vertex_a.ambient_b}; + diffuse = float3{vertex_a.diffuse_r, vertex_a.diffuse_g, vertex_a.diffuse_b}; + emissive = float3{vertex_a.emissive_r, vertex_a.emissive_g, vertex_a.emissive_b}; } template @@ -122,42 +135,58 @@ namespace cg::renderer inline void raytracer::set_render_target( std::shared_ptr> in_render_target) { - // TODO Lab: 2.01 Implement `set_render_target`, `set_viewport`, and `clear_render_target` methods of `raytracer` class + render_target = in_render_target; } template inline void raytracer::set_viewport(size_t in_width, size_t in_height) { - // TODO Lab: 2.01 Implement `set_render_target`, `set_viewport`, and `clear_render_target` methods of `raytracer` class - // TODO Lab: 2.06 Add `history` resource in `raytracer` class + width = in_width; + height = in_height; + + history = std::make_shared>(width, height); } template inline void raytracer::clear_render_target( const RT& in_clear_value) { - // TODO Lab: 2.01 Implement `set_render_target`, `set_viewport`, and `clear_render_target` methods of `raytracer` class - // TODO Lab: 2.06 Add `history` resource in `raytracer` class + for (size_t i = 0; i < render_target->get_number_of_elements(); ++i) { + render_target->item(i) = in_clear_value; + history->item(i) = float3{0.f, 0.f, 0.f}; + } } template inline void raytracer::set_vertex_buffers(std::vector>> in_vertex_buffers) { - // TODO Lab: 2.02 Implement `set_vertex_buffers` and `set_index_buffers` of `raytracer` class + vertex_buffers = in_vertex_buffers; } template void raytracer::set_index_buffers(std::vector>> in_index_buffers) { - // TODO Lab: 2.02 Implement `set_vertex_buffers` and `set_index_buffers` of `raytracer` class + index_buffers = in_index_buffers; } template inline void raytracer::build_acceleration_structure() { - // TODO Lab: 2.02 Fill `triangles` vector in `build_acceleration_structure` of `raytracer` class - // TODO Lab: 2.05 Implement `build_acceleration_structure` method of `raytracer` class + for (size_t i = 0; i < index_buffers.size(); ++i) { + auto& index_buffer = index_buffers[i]; + auto& vertex_buffer = vertex_buffers[i]; + size_t index = 0; + aabb aabb; + while (index < index_buffer->get_number_of_elements()) { + triangle triangle( + vertex_buffer->item(index_buffer->item(index++)), + vertex_buffer->item(index_buffer->item(index++)), + vertex_buffer->item(index_buffer->item(index++))); + aabb.add_triangle(triangle); + } + acceleration_structures.push_back(aabb); + } } template @@ -165,52 +194,161 @@ namespace cg::renderer float3 position, float3 direction, float3 right, float3 up, size_t depth, size_t accumulation_num) { - // TODO Lab: 2.01 Implement `ray_generation` and `trace_ray` method of `raytracer` class - // TODO Lab: 2.06 Implement TAA in `ray_generation` method of `raytracer` class + for (size_t frame_id = 0; frame_id < accumulation_num; frame_id++) { + std::cout << "Tracing " << frame_id << "/" << accumulation_num << " frame\n"; + float2 jitter = get_jitter(frame_id); +#pragma omp parallel for + for (int x = 0; x < width; ++x) { + for (int y = 0; y < height; ++y) { + float u = (2.f * x + jitter.x) / static_cast(width) - 1.f; + float v = (2.f * y + jitter.y) / static_cast(height) - 1.f; + u *= static_cast(width) / static_cast(height); + float3 ray_direction = direction + right * u - up * v; + + ray primary_ray(position, ray_direction); + payload payload_var = trace_ray(primary_ray, depth); + + history->item(x, y) += sqrt(payload_var.color.to_float3() / accumulation_num); + if (frame_id + 1 == accumulation_num) { + render_target->item(x, y) = RT::from_float3(history->item(x, y)); + } + } + } + } } template inline payload raytracer::trace_ray( const ray& ray, size_t depth, float max_t, float min_t) const { - // TODO Lab: 2.01 Implement `ray_generation` and `trace_ray` method of `raytracer` class - // TODO Lab: 2.02 Adjust `trace_ray` method of `raytracer` class to traverse geometry and call a closest hit shader - // TODO Lab: 2.04 Adjust `trace_ray` method of `raytracer` to use `any_hit_shader` - // TODO Lab: 2.05 Adjust `trace_ray` method of `raytracer` class to traverse the acceleration structure - return payload{}; + if (depth == 0) { + return miss_shader(ray); + } + --depth; + + payload closest_hit_payload{}; + closest_hit_payload.t = max_t; + const triangle* closest_triangle = nullptr; + + for (auto& aabb: acceleration_structures) { + if (!aabb.aabb_test(ray)) { + continue; + } + for (auto& triangle: aabb.get_triangles()) { + payload payload_var = intersection_shader(triangle, ray); + + if (payload_var.t > min_t && payload_var.t < closest_hit_payload.t) { + closest_hit_payload = payload_var; + closest_triangle = ▵ + if (any_hit_shader) { + return any_hit_shader(ray, payload_var, triangle); + } + } + } + } + + if (closest_hit_payload.t < max_t) { + if (closest_hit_shader) { + return closest_hit_shader(ray, closest_hit_payload, *closest_triangle, depth); + } + } + + return miss_shader(ray); } template inline payload raytracer::intersection_shader( const triangle& triangle, const ray& ray) const { - // TODO Lab: 2.02 Implement an `intersection_shader` method of `raytracer` class - return payload{}; + payload payload_var{}; + payload_var.t = -1.f; + + float3 pvec = cross(ray.direction, triangle.ca); + float det = dot(triangle.ba, pvec); + if (det > -1e-8 && det < 1e-8) { + return payload_var; + } + + float inv_det = 1.f / det; + float3 tvec = ray.position - triangle.a; + float u = dot(tvec, pvec) * inv_det; + if (u < 0.f || u > 1.f) { + return payload_var; + } + + float3 qvec = cross(tvec, triangle.ba); + float v = dot(ray.direction, qvec) * inv_det; + if (v < 0.f || u + v > 1.f) { + return payload_var; + } + + payload_var.t = dot(triangle.ca, qvec) * inv_det; + payload_var.bary = float3{1.f - u - v, u, v}; + return payload_var; } template float2 raytracer::get_jitter(int frame_id) { - // TODO Lab: 2.06 Implement `get_jitter` method of `raytracer` class + float2 result{0.f, 0.f}; + + constexpr int base_x = 2; + int index = frame_id + 1; + float inv_base = 1.f / base_x; + float fraction = inv_base; + while (index > 0) { + result.x += static_cast(index % base_x) * fraction; + index /= base_x; + fraction *= inv_base; + } + + constexpr int base_y = 3; + index = frame_id + 1; + inv_base = 1.f / base_y; + fraction = inv_base; + while (index > 0) { + result.y += static_cast(index % base_y) * fraction; + index /= base_y; + fraction *= inv_base; + } + + return result - 0.5f; } template inline void aabb::add_triangle(const triangle triangle) { - // TODO Lab: 2.05 Implement `aabb` class + if (triangles.empty()) { + aabb_min = aabb_max = triangle.a; + } + + triangles.push_back(triangle); + + aabb_max = max(aabb_max, triangle.a); + aabb_max = max(aabb_max, triangle.b); + aabb_max = max(aabb_max, triangle.c); + + aabb_min = max(aabb_min, triangle.a); + aabb_min = max(aabb_min, triangle.b); + aabb_min = max(aabb_min, triangle.c); } template inline const std::vector>& aabb::get_triangles() const { - // TODO Lab: 2.05 Implement `aabb` class + return triangles; } template inline bool aabb::aabb_test(const ray& ray) const { - // TODO Lab: 2.05 Implement `aabb` class + float3 inv_ray_dir = 1.f / ray.direction; + float3 t0 = (aabb_max - ray.position) * inv_ray_dir; + float3 t1 = (aabb_min - ray.position) * inv_ray_dir; + float3 t_min = min(t0, t1); + float3 t_max = max(t0, t1); + return maxelem(t_min) <= maxelem(t_max); } }// namespace cg::renderer \ No newline at end of file diff --git a/src/renderer/raytracer/raytracer_renderer.cpp b/src/renderer/raytracer/raytracer_renderer.cpp index dda98bf..2bef31c 100644 --- a/src/renderer/raytracer/raytracer_renderer.cpp +++ b/src/renderer/raytracer/raytracer_renderer.cpp @@ -7,9 +7,32 @@ void cg::renderer::ray_tracing_renderer::init() { - // TODO Lab: 2.01 Add `render_target`, `camera`, and `raytracer` in `ray_tracing_renderer` class - // TODO Lab: 2.03 Add light information to `lights` array of `ray_tracing_renderer` - // TODO Lab: 2.04 Initialize `shadow_raytracer` in `ray_tracing_renderer` + render_target = std::make_shared>( + settings->width, settings->height); + + model = std::make_shared(); + model->load_obj(settings->model_path); + + camera = std::make_shared(); + camera->set_height(static_cast(settings->height)); + camera->set_width(static_cast(settings->width)); + camera->set_position(float3{ + settings->camera_position[0], + settings->camera_position[1], + settings->camera_position[2], + }); + + camera->set_theta(settings->camera_theta); + camera->set_phi(settings->camera_phi); + camera->set_angle_of_view(settings->camera_angle_of_view); + camera->set_z_near(settings->camera_z_near); + camera->set_z_far(settings->camera_z_far); + + raytracer = std::make_shared>(); + raytracer->set_viewport(settings->width, settings->height); + raytracer->set_render_target(render_target); + raytracer->set_vertex_buffers(model->get_vertex_buffers()); + raytracer->set_index_buffers(model->get_index_buffers()); } void cg::renderer::ray_tracing_renderer::destroy() {} @@ -18,11 +41,54 @@ void cg::renderer::ray_tracing_renderer::update() {} void cg::renderer::ray_tracing_renderer::render() { - // TODO Lab: 2.01 Implement `miss_shader`, image clearing, calling `ray_generation`, and saving in `ray_tracing_renderer` class - // TODO Lab: 2.02 Add `closest_hit_shader` to `raytracer` class to return diffuse color - // TODO Lab: 2.03 Adjust `closest_hit_shader` of `raytracer` to implement Lambertian shading model - // TODO Lab: 2.04 Define `any_hit_shader` and `miss_shader` for `shadow_raytracer` - // TODO Lab: 2.04 Adjust `closest_hit_shader` of `raytracer` to cast shadows rays and to ignore occluded lights - // TODO Lab: 2.05 Adjust `ray_tracing_renderer` class to build the acceleration structure + raytracer->miss_shader = [](const ray& ray) { + payload payload{}; + payload.color = {0.f, 0.f, 0.f}; + return payload; + }; + + std::random_device random_device; + std::mt19937 random_generator(random_device()); + std::uniform_real_distribution uni_dist(-1.f, 1.f); + + raytracer->closest_hit_shader = [&](const ray& ray, payload& payload, + const triangle& triangle, + size_t depth) { + float3 position = ray.position + ray.direction * payload.t; + float3 normal = payload.bary.x * triangle.na + + payload.bary.y * triangle.nb + + payload.bary.z * triangle.nc; + + float3 res_color = triangle.emissive; + + float3 rand_direction{uni_dist(random_generator), + uni_dist(random_generator), + uni_dist(random_generator)}; + if (dot(rand_direction, normal) < 0.f) { + rand_direction = -rand_direction; + } + + cg::renderer::ray to_rand_direction(position, rand_direction); + auto next_payload = raytracer->trace_ray(to_rand_direction, depth); + res_color += next_payload.color.to_float3() * triangle.diffuse * + std::max(0.f, dot(normal, to_rand_direction.direction)); + + payload.color = cg::color::from_float3(res_color); + return payload; + }; + + raytracer->build_acceleration_structure(); + raytracer->clear_render_target({0, 0, 0}); + + auto start = std::chrono::high_resolution_clock::now(); + raytracer->ray_generation( + camera->get_position(), camera->get_direction(), + camera->get_right(), camera->get_up(), + settings->raytracing_depth, settings->accumulation_num); + auto stop = std::chrono::high_resolution_clock::now(); + std::chrono::duration rt_duration = stop - start; + std::cout << "Ray tracing took " << rt_duration.count() << "ms\n"; + + cg::utils::save_resource(*render_target, settings->result_path); // TODO Lab: 2.06 (Bonus) Adjust `closest_hit_shader` for Monte-Carlo light tracing } \ No newline at end of file diff --git a/src/resource.h b/src/resource.h index 68bdec7..7b53a7d 100644 --- a/src/resource.h +++ b/src/resource.h @@ -36,66 +36,68 @@ namespace cg template inline resource::resource(size_t size) { - // TODO Lab: 1.02 Implement `cg::resource` class + data.resize(size); + stride = 0; } + template inline resource::resource(size_t x_size, size_t y_size) { - // TODO Lab: 1.02 Implement `cg::resource` class + data.resize(x_size * y_size); + stride = x_size; } + template inline resource::~resource() { } + template inline const T* resource::get_data() { - // TODO Lab: 1.02 Implement `cg::resource` class - return nullptr; + return data.data(); } + template inline T& resource::item(size_t item) { - // TODO Lab: 1.02 Implement `cg::resource` class - return T(); + return data.at(item); } + template inline T& resource::item(size_t x, size_t y) { - // TODO Lab: 1.02 Implement `cg::resource` class - return T(); + return data.at(stride * y + x); } + template inline size_t resource::get_size_in_bytes() const { - // TODO Lab: 1.02 Implement `cg::resource` class - return 0; + return data.size() * item_size; } + template inline size_t resource::get_number_of_elements() const { - // TODO Lab: 1.02 Implement `cg::resource` class - return 0; + return data.size(); } template inline size_t resource::get_stride() const { - // TODO Lab: 1.02 Implement `cg::resource` class - return 0; + return stride; } struct color { static color from_float3(const float3& in) { - // TODO Lab: 1.02 Implement `cg::color` and `cg::unsigned_color` structs - return color{}; + return color{in.x, in.y, in.z}; }; + float3 to_float3() const { - // TODO Lab: 1.02 Implement `cg::color` and `cg::unsigned_color` structs - return float3{}; + return float3{r, g, b}; } float r; float g; @@ -106,18 +108,28 @@ namespace cg { static unsigned_color from_color(const color& color) { - // TODO Lab: 1.02 Implement `cg::color` and `cg::unsigned_color` structs - return unsigned_color{}; + return unsigned_color{ + static_cast(std::clamp(color.r * 225.f, 0.f, 255.f)), + static_cast(std::clamp(color.g * 225.f, 0.f, 255.f)), + static_cast(std::clamp(color.b * 225.f, 0.f, 255.f)) + }; }; static unsigned_color from_float3(const float3& color) { - // TODO Lab: 1.02 Implement `cg::color` and `cg::unsigned_color` structs - return unsigned_color{}; + float3 clampled = clamp(255.f * color, 0.f, 255.f); + return unsigned_color{ + static_cast(clampled.x), + static_cast(clampled.y), + static_cast(clampled.z) + }; }; float3 to_float3() const { - // TODO Lab: 1.02 Implement `cg::color` and `cg::unsigned_color` structs - return float3{}; + return float3{ + static_cast(r), + static_cast(g), + static_cast(b), + } / 255.f; }; uint8_t r; uint8_t g; @@ -127,7 +139,28 @@ namespace cg struct vertex { - // TODO Lab: 1.03 Implement `cg::vertex` struct + float x; + float y; + float z; + + float nx; + float ny; + float nz; + + float u; + float v; + + float ambient_r; + float ambient_g; + float ambient_b; + + float diffuse_r; + float diffuse_g; + float diffuse_b; + + float emissive_r; + float emissive_g; + float emissive_b; }; }// namespace cg \ No newline at end of file diff --git a/src/utils/resource_utils.cpp b/src/utils/resource_utils.cpp index dc36d32..6497f5a 100644 --- a/src/utils/resource_utils.cpp +++ b/src/utils/resource_utils.cpp @@ -8,20 +8,18 @@ #include -using namespace cg::utils; - std::string view_command(const std::filesystem::path& path) { #ifdef __APPLE__ return std::string("open ").append(path.string()); #endif -#ifdef __WINDOWS__ +#ifdef _WIN32 return std::string("start ").append(path.string()); #endif - return path.string(); + return ""; } -void save_resource(cg::resource& render_target, const std::filesystem::path& filepath) +void cg::utils::save_resource(cg::resource& render_target, const std::filesystem::path& filepath) { int width = static_cast(render_target.get_stride()); int height = static_cast(render_target.get_number_of_elements()) / width; @@ -33,6 +31,8 @@ void save_resource(cg::resource& render_target, const std::f if (result != 1) THROW_ERROR("Can't save the resource"); - std::system(view_command(filepath).c_str()); + auto command = view_command(filepath); + if (!command.empty()) + std::system(command.c_str()); } diff --git a/src/utils/resource_utils.h b/src/utils/resource_utils.h index 9b83496..96fa322 100644 --- a/src/utils/resource_utils.h +++ b/src/utils/resource_utils.h @@ -7,5 +7,5 @@ namespace cg::utils { - void save_resource(cg::resource& render_target, std::filesystem::path filepath); + void save_resource(cg::resource& render_target, const std::filesystem::path& filepath); } \ No newline at end of file diff --git a/src/world/camera.cpp b/src/world/camera.cpp index c844527..835ddfe 100644 --- a/src/world/camera.cpp +++ b/src/world/camera.cpp @@ -19,106 +19,121 @@ cg::world::camera::~camera() {} void cg::world::camera::set_position(float3 in_position) { - // TODO Lab: 1.04 Implement `cg::world::camera` class + + position = in_position; } void cg::world::camera::set_theta(float in_theta) { - // TODO Lab: 1.04 Implement `cg::world::camera` class + theta = in_theta * static_cast(M_PI) / 180.f; } void cg::world::camera::set_phi(float in_phi) { - // TODO Lab: 1.04 Implement `cg::world::camera` class + phi = in_phi * static_cast(M_PI) / 180.f; } void cg::world::camera::set_angle_of_view(float in_aov) { - // TODO Lab: 1.04 Implement `cg::world::camera` class + angle_of_view = in_aov * static_cast(M_PI) / 180.f; } void cg::world::camera::set_height(float in_height) { - // TODO Lab: 1.04 Implement `cg::world::camera` class + height = in_height; + aspect_ratio = width / height; } void cg::world::camera::set_width(float in_width) { - // TODO Lab: 1.04 Implement `cg::world::camera` class + width = in_width; + aspect_ratio = width / height; } void cg::world::camera::set_z_near(float in_z_near) { - // TODO Lab: 1.04 Implement `cg::world::camera` class + z_near = in_z_near; } void cg::world::camera::set_z_far(float in_z_far) { - // TODO Lab: 1.04 Implement `cg::world::camera` class + z_far = in_z_far; } const float4x4 cg::world::camera::get_view_matrix() const { - // TODO Lab: 1.04 Implement `cg::world::camera` class - return float4x4{}; + float3 up{0.f, 1.f, 0.f}; + float3 eye = position + get_direction(); + + float3 z_axis = normalize(position - eye); + float3 x_axis = normalize(cross(up, z_axis)); + float3 y_axis = cross(z_axis, x_axis); + return float4x4{ + {x_axis.x, y_axis.x, z_axis.x, 0}, + {x_axis.y, y_axis.y, z_axis.y, 0}, + {x_axis.z, y_axis.z, z_axis.z, 0}, + {-dot(x_axis, position), -dot(y_axis, position), -dot(z_axis, position), 1}}; } #ifdef DX12 const DirectX::XMMATRIX cg::world::camera::get_dxm_view_matrix() const { - // TODO Lab: 3.08 Implement `get_dxm_view_matrix`, `get_dxm_projection_matrix`, and `get_dxm_mvp_matrix` methods of `camera` - return DirectX::XMMatrixIdentity(); + return DirectX::XMMatrixIdentity(); } const DirectX::XMMATRIX cg::world::camera::get_dxm_projection_matrix() const { - // TODO Lab: 3.08 Implement `get_dxm_view_matrix`, `get_dxm_projection_matrix`, and `get_dxm_mvp_matrix` methods of `camera` - return DirectX::XMMatrixIdentity(); + return DirectX::XMMatrixIdentity(); } const DirectX::XMMATRIX camera::get_dxm_mvp_matrix() const { - // TODO Lab: 3.08 Implement `get_dxm_view_matrix`, `get_dxm_projection_matrix`, and `get_dxm_mvp_matrix` methods of `camera` - return DirectX::XMMatrixIdentity(); + + return DirectX::XMMatrixIdentity(); } #endif const float4x4 cg::world::camera::get_projection_matrix() const { - // TODO Lab: 1.04 Implement `cg::world::camera` class - return float4x4{}; + float f = 1.f / tanf(angle_of_view / 2.f); + return float4x4{ + {f / aspect_ratio, 0, 0, 0}, + {0, f, 0, 0}, + {0, 0, z_far / (z_near - z_far), -1}, + {0, 0, (z_far * z_near) / (z_near - z_far), 0}}; } const float3 cg::world::camera::get_position() const { - // TODO Lab: 1.04 Implement `cg::world::camera` class - return float3{}; + return position; } const float3 cg::world::camera::get_direction() const { - // TODO Lab: 1.04 Implement `cg::world::camera` class - return float3{}; + + return float3{ + sin(theta) * cos(phi), + sin(phi), + -cos(theta) * cos(phi)}; } const float3 cg::world::camera::get_right() const { - // TODO Lab: 1.04 Implement `cg::world::camera` class - return float3{}; + return cross(get_direction(), float3(0.f, 1.f, 0.f)); } const float3 cg::world::camera::get_up() const { - // TODO Lab: 1.04 Implement `cg::world::camera` class - return float3{}; + + return cross(get_right(), get_direction()); } + const float camera::get_theta() const { - // TODO Lab: 1.04 Implement `cg::world::camera` class - return 0.f; + return theta; } + const float camera::get_phi() const { - // TODO Lab: 1.04 Implement `cg::world::camera` class - return 0.f; + return phi; } diff --git a/src/world/model.cpp b/src/world/model.cpp index 161c21d..c330551 100644 --- a/src/world/model.cpp +++ b/src/world/model.cpp @@ -4,6 +4,7 @@ #include "utils/error_handler.h" +#include #include @@ -16,42 +17,182 @@ cg::world::model::~model() {} void cg::world::model::load_obj(const std::filesystem::path& model_path) { - // TODO Lab: 1.03 Using `tinyobjloader` implement `load_obj`, `allocate_buffers`, `compute_normal`, `fill_vertex_data`, `fill_buffers`, `get_vertex_buffers`, `get_index_buffers` methods of `cg::world::model` class + tinyobj::ObjReaderConfig readerConfig; + readerConfig.mtl_search_path = model_path.parent_path().string(); + readerConfig.triangulate = true; + + tinyobj::ObjReader reader; + if (!reader.ParseFromFile(model_path.string(), readerConfig)) { + if (!reader.Error().empty()) { + THROW_ERROR(reader.Error()); + } + } + + auto& shapes = reader.GetShapes(); + auto& attrib = reader.GetAttrib(); + auto& materials = reader.GetMaterials(); + + allocate_buffers(shapes); + fill_buffers(shapes, attrib, materials, model_path.parent_path()); } void model::allocate_buffers(const std::vector& shapes) { - // TODO Lab: 1.03 Using `tinyobjloader` implement `load_obj`, `allocate_buffers`, `compute_normal`, `fill_vertex_data`, `fill_buffers`, `get_vertex_buffers`, `get_index_buffers` methods of `cg::world::model` class + for (const auto& shape: shapes) { + size_t index_offset = 0; + unsigned int vertex_buffer_size = 0; + unsigned int index_buffer_size = 0; + std::map, unsigned int> index_map; + const auto& mesh = shape.mesh; + + for (const auto& fv: mesh.num_face_vertices) { + for (size_t v = 0; v < fv; ++v) { + tinyobj::index_t idx = mesh.indices[index_offset + v]; + auto idx_tuple = std::make_tuple( + idx.vertex_index, + idx.normal_index, + idx.texcoord_index); + + if (index_map.count(idx_tuple) == 0) { + index_map[idx_tuple] = vertex_buffer_size; + ++vertex_buffer_size; + } + + index_buffer_size++; + } + index_offset += fv; + } + vertex_buffers.push_back(std::make_shared>(vertex_buffer_size)); + index_buffers.push_back(std::make_shared>(index_buffer_size)); + + std::cout << "Vertex buffer size " << vertex_buffer_size * sizeof(cg::vertex) << '\n'; + std::cout << "Index buffer size " << index_buffer_size * sizeof(unsigned int) << '\n'; + + std::cout << "Pure vertex buffer size " << index_buffer_size * sizeof(cg::vertex) << '\n'; + std::cout << "Saving " << index_buffer_size * sizeof(cg::vertex) - vertex_buffer_size * sizeof(cg::vertex) - index_buffer_size * sizeof(unsigned int) << '\n'; + } + textures.resize(shapes.size()); } float3 cg::world::model::compute_normal(const tinyobj::attrib_t& attrib, const tinyobj::mesh_t& mesh, size_t index_offset) { - // TODO Lab: 1.03 Using `tinyobjloader` implement `load_obj`, `allocate_buffers`, `compute_normal`, `fill_vertex_data`, `fill_buffers`, `get_vertex_buffers`, `get_index_buffers` methods of `cg::world::model` class - return float3{}; + auto a_id = mesh.indices[index_offset]; + auto b_id = mesh.indices[index_offset + 1]; + auto c_id = mesh.indices[index_offset + 2]; + + float3 a{ + attrib.vertices[3 * a_id.vertex_index], + attrib.vertices[3 * a_id.vertex_index + 1], + attrib.vertices[3 * a_id.vertex_index + 2], + }; + + float3 b{ + attrib.vertices[3 * b_id.vertex_index], + attrib.vertices[3 * b_id.vertex_index + 1], + attrib.vertices[3 * b_id.vertex_index + 2], + }; + + float3 c{ + attrib.vertices[3 * c_id.vertex_index], + attrib.vertices[3 * c_id.vertex_index + 1], + attrib.vertices[3 * c_id.vertex_index + 2], + }; + + return normalize(cross(b - a, c - a)); } void model::fill_vertex_data(cg::vertex& vertex, const tinyobj::attrib_t& attrib, const tinyobj::index_t idx, const float3 computed_normal, const tinyobj::material_t material) { - // TODO Lab: 1.03 Using `tinyobjloader` implement `load_obj`, `allocate_buffers`, `compute_normal`, `fill_vertex_data`, `fill_buffers`, `get_vertex_buffers`, `get_index_buffers` methods of `cg::world::model` class + vertex.x = attrib.vertices[3 * idx.vertex_index]; + vertex.y = attrib.vertices[3 * idx.vertex_index + 1]; + vertex.z = attrib.vertices[3 * idx.vertex_index + 2]; + + if (idx.normal_index < 0) { + vertex.nx = computed_normal.x; + vertex.ny = computed_normal.y; + vertex.nz = computed_normal.z; + } + else { + vertex.nx = attrib.normals[3 * idx.normal_index]; + vertex.ny = attrib.normals[3 * idx.normal_index + 1]; + vertex.nz = attrib.normals[3 * idx.normal_index + 2]; + } + + if (idx.texcoord_index < 0) { + vertex.u = 0.f; + vertex.v = 0.f; + } + else { + vertex.u = attrib.texcoords[2 * idx.texcoord_index]; + vertex.v = attrib.texcoords[2 * idx.texcoord_index + 1]; + } + + vertex.ambient_r = material.ambient[0]; + vertex.ambient_g = material.ambient[1]; + vertex.ambient_b = material.ambient[2]; + + vertex.diffuse_r = material.diffuse[0]; + vertex.diffuse_g = material.diffuse[1]; + vertex.diffuse_b = material.diffuse[2]; + + vertex.emissive_r = material.emission[0]; + vertex.emissive_g = material.emission[1]; + vertex.emissive_b = material.emission[2]; } void model::fill_buffers(const std::vector& shapes, const tinyobj::attrib_t& attrib, const std::vector& materials, const std::filesystem::path& base_folder) { - // TODO Lab: 1.03 Using `tinyobjloader` implement `load_obj`, `allocate_buffers`, `compute_normal`, `fill_vertex_data`, `fill_buffers`, `get_vertex_buffers`, `get_index_buffers` methods of `cg::world::model` class + for (size_t s = 0; s < shapes.size(); ++s) { + size_t index_offset = 0; + unsigned int vertex_buffer_id = 0; + unsigned int index_buffer_id = 0; + auto vertex_buffer = vertex_buffers[s]; + auto index_buffer = index_buffers[s]; + std::map, unsigned int> index_map; + const auto& mesh = shapes[s].mesh; + + for (size_t f = 0; f < mesh.num_face_vertices.size(); ++f) { + int fv = mesh.num_face_vertices[f]; + float3 normal; + if (mesh.indices[index_offset].normal_index < 0) { + normal = compute_normal(attrib, mesh, index_offset); + } + + for (size_t v = 0; v < fv; ++v) { + tinyobj::index_t idx = mesh.indices[index_offset + v]; + auto idx_tuple = std::make_tuple( + idx.vertex_index, + idx.normal_index, + idx.texcoord_index); + + if (index_map.count(idx_tuple) == 0) { + cg::vertex& vertex = vertex_buffer->item(vertex_buffer_id); + const auto& material = materials[mesh.material_ids[f]]; + fill_vertex_data(vertex, attrib, idx, normal, material); + index_map[idx_tuple] = vertex_buffer_id; + vertex_buffer_id++; + } + index_buffer->item(index_buffer_id) = index_map[idx_tuple]; + index_buffer_id++; + } + index_offset += fv; + } + if (!materials[mesh.material_ids[0]].diffuse_texname.empty()) { + textures[s] = base_folder / materials[mesh.material_ids[0]].diffuse_texname; + } + } } const std::vector>>& cg::world::model::get_vertex_buffers() const { - // TODO Lab: 1.03 Using `tinyobjloader` implement `load_obj`, `allocate_buffers`, `compute_normal`, `fill_vertex_data`, `fill_buffers`, `get_vertex_buffers`, `get_index_buffers` methods of `cg::world::model` class return vertex_buffers; } const std::vector>>& cg::world::model::get_index_buffers() const { - // TODO Lab: 1.03 Using `tinyobjloader` implement `load_obj`, `allocate_buffers`, `compute_normal`, `fill_vertex_data`, `fill_buffers`, `get_vertex_buffers`, `get_index_buffers` methods of `cg::world::model` class return index_buffers; }