/* * Generates a camera ray based on the given information. */ WorldRay generate_ray(float x, float y, float dx, float dy, float time, float u, float v) const { WorldRay wray; wray.type = WorldRay::CAMERA; wray.time = time; // Get time-interpolated camera settings const Transform transform = lerp_seq(time, transforms); const float tfov = lerp_seq(time, tfovs); const float aperture_radius = lerp_seq(time, aperture_radii); const float focus_distance = lerp_seq(time, focus_distances); // Ray origin wray.o.x = aperture_radius * ((u * 2) - 1); wray.o.y = aperture_radius * ((v * 2) - 1); wray.o.z = 0.0; square_to_circle(&wray.o.x, &wray.o.y); // Ray direction wray.d.x = (x * tfov) - (wray.o.x / focus_distance); wray.d.y = (y * tfov) - (wray.o.y / focus_distance); wray.d.z = 1.0; wray.d.normalize(); // Ray image plane differentials wray.odx = Vec3(0.0f, 0.0f, 0.0f); wray.ody = Vec3(0.0f, 0.0f, 0.0f); wray.ddx = Vec3(dx*tfov, 0.0f, 0.0f); wray.ddy = Vec3(0.0f, dy*tfov, 0.0f); // Transform the ray return wray.transformed(transform); }
void LightTree::build(const Assembly& assembly_) { assembly = &assembly_; // Populate the build nodes for (size_t i = 0; i < assembly->instances.size(); ++i) { const auto& instance = assembly->instances[i]; // Shorthand // If it's an object if (instance.type == Instance::OBJECT) { const Object* obj = assembly->objects[instance.data_index].get(); // Shorthand if (obj->total_emitted_color().energy() > 0.0f) { build_nodes.push_back(BuildNode()); build_nodes.back().instance_index = i; build_nodes.back().bbox = assembly->instance_bounds_at(0.5f, i); build_nodes.back().center = build_nodes.back().bbox.center(); const Vec3 scale = assembly->instance_xform_at(0.5f, i).get_inv_scale(); const float surface_scale = ((scale[0]*scale[1]) + (scale[0]*scale[2]) + (scale[1]*scale[2])) * 0.33333333f; build_nodes.back().energy = obj->total_emitted_color().energy() / surface_scale; ++total_lights; } } // If it's an assembly else if (instance.type == Instance::ASSEMBLY) { const Assembly* asmb = assembly->assemblies[instance.data_index].get(); // Shorthand const auto count = asmb->light_accel.light_count(); const float energy = asmb->light_accel.total_emitted_color().energy(); if (count > 0 && energy > 0.0f) { build_nodes.push_back(BuildNode()); build_nodes.back().instance_index = i; if (instance.transform_count > 0) { auto xstart = assembly->xforms.cbegin() + instance.transform_index; auto xend = xstart + instance.transform_count; build_nodes.back().bbox = lerp_seq(0.5f, asmb->light_accel.bounds()).inverse_transformed(lerp_seq(0.5f, xstart, xend)); } else { build_nodes.back().bbox = lerp_seq(0.5f, asmb->light_accel.bounds()); } build_nodes.back().center = build_nodes.back().bbox.center(); const Vec3 scale = assembly->instance_xform_at(0.5f, i).get_inv_scale(); const float surface_scale = ((scale[0]*scale[1]) + (scale[0]*scale[2]) + (scale[1]*scale[2])) * 0.33333333f; build_nodes.back().energy = energy / surface_scale; total_lights += count; } } } if (build_nodes.size() > 0) { recursive_build(build_nodes.begin(), build_nodes.end()); bounds_ = nodes[0].bounds; total_energy = nodes[0].energy; } else { bounds_.clear(); bounds_.emplace_back(BBox()); } }
void Tracer::trace_surface(Surface* surface, Ray* rays, Ray* end) { // Get parent transforms const auto parent_xforms = xform_stack.top_frame<Transform>(); const size_t parent_xforms_count = std::distance(parent_xforms.first, parent_xforms.second); // Trace! for (auto ritr = rays; ritr != end; ++ritr) { Ray& ray = *ritr; // Shorthand reference to the ray Intersection& inter = intersections[ritr->id()]; // Shorthand reference to ray's intersection // Test against the ray if (surface->intersect_ray(ray, &inter)) { inter.hit = true; inter.id = element_id; if (ray.is_occlusion()) { ray.set_done_true(); // Early out for shadow rays } else { ray.max_t = inter.t; inter.space = parent_xforms_count > 0 ? lerp_seq(ray.time, parent_xforms.first, parent_xforms.second) : Transform(); // Do shading auto shader = surface_shader_stack.back(); if (shader != nullptr) { shader->shade(&inter); } else { inter.surface_closure.init(EmitClosure(Color(1.0, 0.0, 1.0))); } } } } }
float LightTree::node_prob(const LightQuery& lq, uint32_t index) const { const BBox bbox = lerp_seq(lq.time, nodes[index].bounds); const Vec3 d = bbox.center() - lq.pos; const float dist2 = d.length2(); const float r = bbox.diagonal() * 0.5f; const float r2 = r * r; const float inv_surface_area = 1.0f / r2; float cos_theta_max; if (dist2 <= r2) { cos_theta_max = -1.0f; } else { const float sin_theta_max2 = std::min(1.0f, r2 / dist2); cos_theta_max = std::sqrt(1.0f - sin_theta_max2); } // Get the approximate amount of light contribution from the // composite light source. const float approx_contrib = std::max(0.0f, lq.bsdf->estimate_eval_over_solid_angle(lq.d, d, cos_theta_max, lq.nor, lq.wavelength)); return nodes[index].energy * inv_surface_area * approx_contrib; }
void Tracer::trace_lightsource(Light* light, Ray* rays, Ray* end) { // Get parent transforms const auto parent_xforms = xform_stack.top_frame<Transform>(); const size_t parent_xforms_count = std::distance(parent_xforms.first, parent_xforms.second); // Trace! for (auto ritr = rays; ritr != end; ++ritr) { Ray& ray = *ritr; // Shorthand reference to the ray Intersection& inter = intersections[ritr->id()]; // Shorthand reference to ray's intersection // Test against the ray if (light->intersect_ray(ray, &inter)) { inter.hit = true; inter.id = element_id; if (ray.is_occlusion()) { ray.set_done_true(); // Early out for shadow rays } else { ray.max_t = inter.t; inter.space = parent_xforms_count > 0 ? lerp_seq(ray.time, parent_xforms.first, parent_xforms.second) : Transform(); } } } }
void LightArray::sample(LightQuery* query) const { // Handle empty light accel if (light_indices.size() == 0 && assembly_lights.size() == 0) { query->spec_samp = SpectralSample(query->spec_samp.hero_wavelength, 0.0f); return; } const float local_prob = static_cast<double>(light_indices.size()) / (total_assembly_lights + light_indices.size()); const float child_prob = 1.0f - local_prob; // If we're sampling a light in this assembly if (query->n <= local_prob) { // Update probabilities query->n /= local_prob; // Get light instance const auto index = light_indices[static_cast<uint32_t>(query->n * light_indices.size()) % light_indices.size()]; const Instance& instance = assembly->instances[index]; // Shorthand // Get light data Light* light = dynamic_cast<Light*>(assembly->objects[instance.data_index].get()); /// Get transforms if any if (instance.transform_count > 0) { auto cbegin = assembly->xforms.cbegin() + instance.transform_index; auto cend = cbegin + instance.transform_count; auto instance_xform = lerp_seq(query->time, cbegin, cend); query->pos = instance_xform.pos_to(query->pos); query->nor = instance_xform.nor_to(query->nor).normalized(); query->xform *= instance_xform; } // Sample the light float p; query->spec_samp = light->sample(query->pos, query->u, query->v, query->wavelength, query->time, &(query->to_light), &p); query->to_light = query->xform.dir_from(query->to_light); query->light_sample_pdf = p; // FIll in the light's instance ID query->id.push_back(index, assembly->element_id_bits()); } // If we're sampling a light in a child assembly else { // Update probabilities query->n = (query->n - local_prob) / child_prob; // Select assembly // TODO: a binary search would be faster size_t index = 0; const size_t target_index = static_cast<size_t>(total_assembly_lights * query->n) % total_assembly_lights; for (const auto& al: assembly_lights) { if (std::get<0>(al) <= target_index && target_index < (std::get<0>(al) + std::get<1>(al))) { index = std::get<2>(al); break; } } // Get assembly instance shorthand const Instance& instance = assembly->instances[index]; // Get assembly Assembly* child_assembly = assembly->assemblies[instance.data_index].get(); // Get transforms if any if (instance.transform_count > 0) { auto cbegin = assembly->xforms.cbegin() + instance.transform_index; auto cend = cbegin + instance.transform_count; auto instance_xform = lerp_seq(query->time, cbegin, cend); query->pos = instance_xform.pos_to(query->pos); query->xform *= instance_xform; } // Push the assembly's instance ID query->id.push_back(index, assembly->element_id_bits()); // Traverse into child assembly child_assembly->light_accel.sample(query); } // Selection PDF is just one, since all lights have equal probability of // being selected. query->selection_pdf = 1.0f; }
void Tracer::trace_assembly(Assembly* assembly, Ray* rays, Ray* rays_end) { BVH4StreamTraverser traverser; // Initialize traverser traverser.init_accel(assembly->object_accel); traverser.init_rays(rays, rays_end); // Trace rays one object at a time std::tuple<Ray*, Ray*, size_t> hits = traverser.next_object(); while (std::get<0>(hits) != std::get<1>(hits)) { const auto& instance = assembly->instances[std::get<2>(hits)]; // Short-hand for the current instance // Push the current instance index onto the element id const auto element_id_bits = assembly->element_id_bits(); element_id.push_back(std::get<2>(hits), element_id_bits); // Propagate transforms (if necessary) const auto parent_xforms = xform_stack.top_frame<Transform>(); const size_t parent_xforms_count = std::distance(parent_xforms.first, parent_xforms.second); if (instance.transform_count > 0) { const auto xbegin = &(*(assembly->xforms.begin() + instance.transform_index)); const auto xend = xbegin + instance.transform_count; const auto larger_xform_count = std::max(instance.transform_count, parent_xforms_count); // Push merged transforms onto transform stack auto xforms = xform_stack.push_frame<Transform>(larger_xform_count); merge(xforms.first, parent_xforms.first, parent_xforms.second, xbegin, xend); for (auto ray = std::get<0>(hits); ray != std::get<1>(hits); ++ray) { w_rays[ray->id()].update_ray(ray, lerp_seq(ray->time, xforms.first, xforms.second)); } } // Check for shader on the instance, and push to shader stack if it // has one. if (instance.surface_shader != nullptr) { surface_shader_stack.emplace_back(instance.surface_shader); } // Trace against the object or assembly if (instance.type == Instance::OBJECT) { Object* obj = assembly->objects[instance.data_index].get(); // Short-hand for the current object // Branch to different code path based on object type switch (obj->get_type()) { case Object::SURFACE: trace_surface(reinterpret_cast<Surface*>(obj), std::get<0>(hits), std::get<1>(hits)); break; case Object::COMPLEX_SURFACE: trace_complex_surface(reinterpret_cast<ComplexSurface*>(obj), std::get<0>(hits), std::get<1>(hits)); break; case Object::PATCH_SURFACE: trace_patch_surface(reinterpret_cast<PatchSurface*>(obj), std::get<0>(hits), std::get<1>(hits)); break; case Object::LIGHT: trace_lightsource(reinterpret_cast<Light*>(obj), std::get<0>(hits), std::get<1>(hits)); break; default: //std::cout << "WARNING: unknown object type, skipping." << std::endl; break; } Global::Stats::object_ray_tests += std::distance(std::get<0>(hits), std::get<1>(hits)); } else { /* Instance::ASSEMBLY */ Assembly* asmb = assembly->assemblies[instance.data_index].get(); // Short-hand for the current object trace_assembly(asmb, std::get<0>(hits), std::get<1>(hits)); } // Pop shader stack if we pushed onto it earlier if (instance.surface_shader != nullptr) { surface_shader_stack.pop_back(); } // Un-transform rays if we transformed them earlier if (instance.transform_count > 0) { if (parent_xforms_count > 0) { for (auto ray = std::get<0>(hits); ray != std::get<1>(hits); ++ray) { w_rays[ray->id()].update_ray(ray, lerp_seq(ray->time, parent_xforms.first, parent_xforms.second)); } } else { for (auto ray = std::get<0>(hits); ray != std::get<1>(hits); ++ray) { w_rays[ray->id()].update_ray(ray); } } // Pop top off of xform stack xform_stack.pop_frame(); } // Pop the index of this instance off the element id element_id.pop_back(element_id_bits); // Get next object to test against hits = traverser.next_object(); } }
bool Sphere::intersect_ray(const Ray &ray, Intersection *intersection) { // Get the center and radius of the sphere at the ray's time const Vec3 cent = lerp_seq(ray.time, center); // Center of the sphere const float radi = lerp_seq(ray.time, radius); // Radius of the sphere // Calculate the relevant parts of the ray for the intersection Vec3 o = ray.o - cent; // Ray origin relative to sphere center Vec3 d = ray.d; // Code taken shamelessly from https://github.com/Tecla/Rayito // Ray-sphere intersection can result in either zero, one or two points // of intersection. It turns into a quadratic equation, so we just find // the solution using the quadratic formula. Note that there is a // slightly more stable form of it when computing it on a computer, and // we use that method to keep everything accurate. // Calculate quadratic coeffs float a = d.length2(); float b = 2.0f * dot(d, o); float c = o.length2() - radi * radi; float t0, t1, discriminant; discriminant = b * b - 4.0f * a * c; if (discriminant < 0.0f) { // Discriminant less than zero? No solution => no intersection. return false; } discriminant = std::sqrt(discriminant); // Compute a more stable form of our param t (t0 = q/a, t1 = c/q) // q = -0.5 * (b - sqrt(b * b - 4.0 * a * c)) if b < 0, or // q = -0.5 * (b + sqrt(b * b - 4.0 * a * c)) if b >= 0 float q; if (b < 0.0f) { q = -0.5f * (b - discriminant); } else { q = -0.5f * (b + discriminant); } // Get our final parametric values t0 = q / a; if (q != 0.0f) { t1 = c / q; } else { t1 = ray.max_t; } // Swap them so they are ordered right if (t0 > t1) { float temp = t1; t1 = t0; t0 = temp; } // Check our intersection for validity against this ray's extents if (t0 >= ray.max_t || t1 < 0.0001f) return false; float t; if (t0 >= 0.0001f) { t = t0; } else if (t1 < ray.max_t) { t = t1; } else { return false; } if (intersection && !ray.is_occlusion()) { intersection->t = t; intersection->geo.p = ray.o + (ray.d * t); intersection->geo.n = intersection->geo.p - cent; intersection->geo.n.normalize(); intersection->backfacing = dot(intersection->geo.n, ray.d.normalized()) > 0.0f; // Calculate the latitude and longitude of the hit point on the sphere const Vec3 unit_p = intersection->geo.n; const Vec3 p = unit_p * radi; const float lat_cos = unit_p.z; const float lat_sin = std::sqrt((unit_p.x * unit_p.x) + (unit_p.y * unit_p.y)); const float long_cos = unit_p.x / lat_sin; const float long_sin = unit_p.y / lat_sin; float latitude = std::acos(lat_cos); float longitude = 0.0f; if (unit_p.x != 0.0f || unit_p.y != 0.0f) { longitude = std::acos(long_cos); if (unit_p.y < 0.0f) longitude = (2.0f * M_PI) - longitude; } // UV const float pi2 = M_PI * 2; intersection->geo.u = longitude / pi2; intersection->geo.v = latitude / M_PI; // Differential position intersection->geo.dpdu = Vec3(p.y * -1.0f, p.x, 0.0f) * pi2; intersection->geo.dpdv = Vec3(p.z * long_cos, p.z * long_sin, -radi * lat_sin) * M_PI; // Differential normal // Calculate second derivatives const Vec3 d2pduu = Vec3(p.x, p.y, 0.0f) * (-pi2 * pi2); const Vec3 d2pduv = Vec3(-long_sin, long_cos, 0.0f) * M_PI * p.z * pi2; const Vec3 d2pdvv = Vec3(p.x, p.y, p.z) * (-M_PI * M_PI); // Calculate surface normal derivatives const float E = dot(intersection->geo.dpdu, intersection->geo.dpdu); const float F = dot(intersection->geo.dpdu, intersection->geo.dpdv); const float G = dot(intersection->geo.dpdv, intersection->geo.dpdv); const float e = dot(intersection->geo.n, d2pduu); const float f = dot(intersection->geo.n, d2pduv); const float g = dot(intersection->geo.n, d2pdvv); const float invEGF2 = 1.0f / ((E*G) - (F*F)); intersection->geo.dndu = (((f*F) - (e*G)) * invEGF2 * intersection->geo.dpdu) + (((e*F) - (f*E)) * invEGF2 * intersection->geo.dpdv); intersection->geo.dndv = (((g*F) - (f*G)) * invEGF2 * intersection->geo.dpdu) + (((f*F) - (g*E)) * invEGF2 * intersection->geo.dpdv); intersection->offset = intersection->geo.n * 0.000001f; } return true; }
void LightTree::sample(LightQuery* query) const { const Node* node = &(nodes[0]); float tot_prob = 1.0f; // Traverse down the tree, keeping track of the relative probabilities while (!node->is_leaf) { // Calculate the relative probabilities of the two children float p1 = node_prob(*query, node->index1); float p2 = node_prob(*query, node->index2); const float total = p1 + p2; if (total <= 0.0f) { p1 = 0.5f; p2 = 0.5f; } else { p1 /= total; p2 /= total; } if (query->n <= p1) { tot_prob *= p1; node = &(nodes[node->index1]); query->n /= p1; } else { tot_prob *= p2; node = &(nodes[node->index2]); query->n = (query->n - p1) / p2; } } // Instance shorthand const Instance& instance = assembly->instances[node->instance_index]; // Push the instance index onto the ID query->id.push_back(node->instance_index, assembly->element_id_bits()); // Get transforms if any if (instance.transform_count > 0) { auto cbegin = assembly->xforms.cbegin() + instance.transform_index; auto cend = cbegin + instance.transform_count; auto instance_xform = lerp_seq(query->time, cbegin, cend); query->pos = instance_xform.pos_to(query->pos); query->nor = instance_xform.nor_to(query->nor).normalized(); query->xform *= instance_xform; } // Do light sampling if (instance.type == Instance::OBJECT) { const Object* obj = assembly->objects[instance.data_index].get(); // Shorthand if (obj->get_type() == Object::LIGHT) { const Light* light = dynamic_cast<const Light*>(obj); float p = 1.0f; query->spec_samp = light->sample(query->pos, query->u, query->v, query->wavelength, query->time, &(query->to_light), &p); query->to_light = query->xform.dir_from(query->to_light); query->selection_pdf *= (tot_prob * light_count()); query->light_sample_pdf = p; } // TODO: handle non-light objects that emit light } else if (instance.type == Instance::ASSEMBLY) { const Assembly* asmb = assembly->assemblies[instance.data_index].get(); // Shorthand query->selection_pdf *= (tot_prob * light_count()) / asmb->light_accel.light_count(); asmb->light_accel.sample(query); } }