void PathVertex::compute_emitted_radiance( TextureCache& texture_cache, Spectrum& radiance) const { assert(m_edf); // No radiance if we're too close to the light. if (m_shading_point->get_distance() < m_edf->get_light_near_start()) { radiance.set(0.0f); return; } // Evaluate the input values of the EDF. InputEvaluator input_evaluator(texture_cache); const void* edf_data = input_evaluator.evaluate( m_edf->get_inputs(), m_shading_point->get_uv(0)); // Compute the emitted radiance. m_edf->evaluate( edf_data, m_shading_point->get_geometric_normal(), m_shading_point->get_shading_basis(), m_outgoing, radiance); }
void ShadingEngine::shade_environment( SamplingContext& sampling_context, const ShadingContext& shading_context, const ShadingPoint& shading_point, ShadingResult& shading_result) const { // Retrieve the environment shader of the scene. const EnvironmentShader* environment_shader = shading_point.get_scene().get_environment()->get_environment_shader(); if (environment_shader) { // There is an environment shader: execute it. InputEvaluator input_evaluator(shading_context.get_texture_cache()); const ShadingRay& ray = shading_point.get_ray(); const Vector3d direction = normalize(ray.m_dir); environment_shader->evaluate( input_evaluator, direction, shading_result); // Set environment shader AOV. shading_result.set_entity_aov(*environment_shader); } else { // No environment shader: shade as transparent black. shading_result.set_main_to_transparent_black_linear_rgba(); shading_result.set_aovs_to_transparent_black_linear_rgba(); } }
void PathVertex::compute_emitted_radiance( const ShadingContext& shading_context, TextureCache& texture_cache, Spectrum& radiance) const { assert(m_edf); // No radiance if we're too close to the light. if (m_shading_point->get_distance() < m_edf->get_light_near_start()) { radiance.set(0.0f); return; } if (const ShaderGroup* sg = get_material()->get_render_data().m_shader_group) shading_context.execute_osl_emission(*sg, *m_shading_point); // Evaluate the EDF inputs. InputEvaluator input_evaluator(texture_cache); m_edf->evaluate_inputs(input_evaluator, *m_shading_point); // Compute the emitted radiance. m_edf->evaluate( input_evaluator.data(), Vector3f(m_shading_point->get_geometric_normal()), Basis3f(m_shading_point->get_shading_basis()), Vector3f(m_outgoing.get_value()), radiance); }
void DirectLightingIntegrator::add_non_physical_light_sample_contribution( const LightSample& sample, Spectrum& radiance, SpectrumStack& aovs) { const Light* light = sample.m_light; // No contribution if we are computing indirect lighting but this light does not cast indirect light. if (m_indirect && !(light->get_flags() & Light::CastIndirectLight)) return; // Evaluate the light. InputEvaluator input_evaluator(m_shading_context.get_texture_cache()); Vector3d emission_position, emission_direction; Spectrum light_value; light->evaluate( input_evaluator, sample.m_light_transform.point_to_local(m_point), emission_position, emission_direction, light_value); // Transform the emission position and direction from assembly space to world space. emission_position = sample.m_light_transform.point_to_parent(emission_position); emission_direction = normalize(sample.m_light_transform.vector_to_parent(emission_direction)); // Compute the incoming direction in world space. const Vector3d incoming = -emission_direction; // Cull light samples behind the shading surface. double cos_in = dot(incoming, m_shading_basis.get_normal()); if (m_bsdf.get_type() == BSDF::Transmissive) cos_in = -cos_in; if (cos_in <= 0.0) return; // Compute the transmission factor between the light sample and the shading point. const double transmission = m_shading_context.get_tracer().trace_between( m_shading_point, emission_position, ShadingRay::ShadowRay); // Discard occluded samples. if (transmission == 0.0) return; // Evaluate the BSDF. Spectrum bsdf_value; const double bsdf_prob = m_bsdf.evaluate( m_bsdf_data, false, // not adjoint true, // multiply by |cos(incoming, normal)| m_geometric_normal, m_shading_basis, m_outgoing, incoming, m_light_sampling_modes, bsdf_value); if (bsdf_prob == 0.0) return; // Add the contribution of this sample to the illumination. const double attenuation = light->compute_distance_attenuation(m_point, emission_position); const double weight = (transmission * attenuation) / sample.m_probability; light_value *= static_cast<float>(weight); light_value *= bsdf_value; radiance += light_value; aovs.add(light->get_render_layer_index(), light_value); }
void compute_ibl_bsdf_sampling( SamplingContext& sampling_context, const ShadingContext& shading_context, const EnvironmentEDF& environment_edf, const ShadingPoint& shading_point, const Vector3d& outgoing, const BSDF& bsdf, const void* bsdf_data, const int bsdf_sampling_modes, const size_t bsdf_sample_count, const size_t env_sample_count, Spectrum& radiance) { assert(is_normalized(outgoing)); const Vector3d& geometric_normal = shading_point.get_geometric_normal(); const Basis3d& shading_basis = shading_point.get_shading_basis(); radiance.set(0.0f); for (size_t i = 0; i < bsdf_sample_count; ++i) { // Sample the BSDF. // todo: rendering will be incorrect if the BSDF value returned by the sample() method // includes the contribution of a specular component since these are explicitly rejected // afterward. We need a mechanism to indicate that we want the contribution of some of // the components only. Vector3d incoming; Spectrum bsdf_value; double bsdf_prob; const BSDF::Mode bsdf_mode = bsdf.sample( sampling_context, bsdf_data, false, // not adjoint true, // multiply by |cos(incoming, normal)| geometric_normal, shading_basis, outgoing, incoming, bsdf_value, bsdf_prob); // Filter scattering modes. if (!(bsdf_sampling_modes & bsdf_mode)) return; // Discard occluded samples. const double transmission = shading_context.get_tracer().trace( shading_point, incoming, ShadingRay::ShadowRay); if (transmission == 0.0) continue; // Evaluate the environment's EDF. InputEvaluator input_evaluator(shading_context.get_texture_cache()); Spectrum env_value; double env_prob; environment_edf.evaluate( input_evaluator, incoming, env_value, env_prob); // Apply all weights, including MIS weight. if (bsdf_mode == BSDF::Specular) env_value *= static_cast<float>(transmission); else { const double mis_weight = mis_power2( bsdf_sample_count * bsdf_prob, env_sample_count * env_prob); env_value *= static_cast<float>(transmission / bsdf_prob * mis_weight); } // Add the contribution of this sample to the illumination. env_value *= bsdf_value; radiance += env_value; } if (bsdf_sample_count > 1) radiance /= static_cast<float>(bsdf_sample_count); }
void compute_ibl_environment_sampling( SamplingContext& sampling_context, const ShadingContext& shading_context, const EnvironmentEDF& environment_edf, const ShadingPoint& shading_point, const Vector3d& outgoing, const BSDF& bsdf, const void* bsdf_data, const int env_sampling_modes, const size_t bsdf_sample_count, const size_t env_sample_count, Spectrum& radiance) { assert(is_normalized(outgoing)); const Vector3d& geometric_normal = shading_point.get_geometric_normal(); const Basis3d& shading_basis = shading_point.get_shading_basis(); radiance.set(0.0f); // todo: if we had a way to know that a BSDF is purely specular, we could // immediately return black here since there will be no contribution from // such a BSDF. sampling_context.split_in_place(2, env_sample_count); for (size_t i = 0; i < env_sample_count; ++i) { // Generate a uniform sample in [0,1)^2. const Vector2d s = sampling_context.next_vector2<2>(); // Sample the environment. InputEvaluator input_evaluator(shading_context.get_texture_cache()); Vector3d incoming; Spectrum env_value; double env_prob; environment_edf.sample( input_evaluator, s, incoming, env_value, env_prob); // Cull samples behind the shading surface. assert(is_normalized(incoming)); const double cos_in = dot(incoming, shading_basis.get_normal()); if (cos_in < 0.0) continue; // Discard occluded samples. const double transmission = shading_context.get_tracer().trace( shading_point, incoming, ShadingRay::ShadowRay); if (transmission == 0.0) continue; // Evaluate the BSDF. Spectrum bsdf_value; const double bsdf_prob = bsdf.evaluate( bsdf_data, false, // not adjoint true, // multiply by |cos(incoming, normal)| geometric_normal, shading_basis, outgoing, incoming, env_sampling_modes, bsdf_value); if (bsdf_prob == 0.0) continue; // Compute MIS weight. const double mis_weight = mis_power2( env_sample_count * env_prob, bsdf_sample_count * bsdf_prob); // Add the contribution of this sample to the illumination. env_value *= static_cast<float>(transmission / env_prob * mis_weight); env_value *= bsdf_value; radiance += env_value; } if (env_sample_count > 1) radiance /= static_cast<float>(env_sample_count); }
bool DirectLightingIntegrator::compute_incoming_radiance( SamplingContext& sampling_context, Vector3d& incoming, double& incoming_prob, Spectrum& radiance) const { if (!m_light_sampler.has_lights_or_emitting_triangles()) return false; sampling_context.split_in_place(3, 1); const Vector3d s = sampling_context.next_vector2<3>(); LightSample sample; m_light_sampler.sample(m_time, s, sample); if (sample.m_triangle) { const Material* material = sample.m_triangle->m_material; const Material::RenderData& material_data = material->get_render_data(); const EDF* edf = material_data.m_edf; // No contribution if we are computing indirect lighting but this light does not cast indirect light. if (m_indirect && !(edf->get_flags() & EDF::CastIndirectLight)) return false; // Compute the incoming direction in world space. incoming = sample.m_point - m_point; // No contribution if the shading point is behind the light. double cos_on_light = dot(-incoming, sample.m_shading_normal); if (cos_on_light <= 0.0) return false; // Compute the transmission factor between the light sample and the shading point. const double transmission = m_shading_context.get_tracer().trace_between( m_shading_point, sample.m_point, VisibilityFlags::ShadowRay); // Discard occluded samples. if (transmission == 0.0) return false; // Don't use this sample if we're closer than the light near start value. const double square_distance = square_norm(incoming); if (square_distance < square(edf->get_light_near_start())) return false; // Normalize the incoming direction. const double rcp_square_distance = 1.0 / square_distance; const double rcp_distance = sqrt(rcp_square_distance); incoming *= rcp_distance; cos_on_light *= rcp_distance; // Build a shading point on the light source. ShadingPoint light_shading_point; sample.make_shading_point( light_shading_point, sample.m_shading_normal, m_shading_context.get_intersector()); #ifdef APPLESEED_WITH_OSL if (material_data.m_shader_group) { m_shading_context.execute_osl_emission( *material_data.m_shader_group, light_shading_point); } #endif // Evaluate the EDF inputs. InputEvaluator edf_input_evaluator(m_shading_context.get_texture_cache()); edf->evaluate_inputs(edf_input_evaluator, light_shading_point); // Evaluate the EDF. edf->evaluate( edf_input_evaluator.data(), sample.m_geometric_normal, Basis3d(sample.m_shading_normal), -incoming, radiance); // Compute probability with respect to solid angle of incoming direction. const double g = cos_on_light * rcp_square_distance; incoming_prob = sample.m_probability / g; // Compute and return the incoming radiance. radiance *= static_cast<float>(transmission * g / sample.m_probability); } else { const Light* light = sample.m_light; // No contribution if we are computing indirect lighting but this light does not cast indirect light. if (m_indirect && !(light->get_flags() & Light::CastIndirectLight)) return false; // Evaluate the light. InputEvaluator input_evaluator(m_shading_context.get_texture_cache()); Vector3d emission_position, emission_direction; light->evaluate( input_evaluator, sample.m_light_transform, m_point, emission_position, emission_direction, radiance); // Compute the transmission factor between the light sample and the shading point. const double transmission = m_shading_context.get_tracer().trace_between( m_shading_point, emission_position, VisibilityFlags::ShadowRay); // Discard occluded samples. if (transmission == 0.0) return false; // Compute the incoming direction in world space. incoming = -emission_direction; incoming_prob = BSDF::DiracDelta; // Compute and return the incoming radiance. const double attenuation = light->compute_distance_attenuation(m_point, emission_position); radiance *= static_cast<float>(transmission * attenuation / sample.m_probability); } return true; }
void compute_ibl_environment_sampling( SamplingContext& sampling_context, const ShadingContext& shading_context, const EnvironmentEDF& environment_edf, const BSSRDF& bssrdf, const void* bssrdf_data, const ShadingPoint& incoming_point, const ShadingPoint& outgoing_point, const Dual3d& outgoing, const size_t bssrdf_sample_count, const size_t env_sample_count, Spectrum& radiance) { assert(is_normalized(outgoing.get_value())); const Basis3d& shading_basis = incoming_point.get_shading_basis(); radiance.set(0.0f); sampling_context.split_in_place(2, env_sample_count); for (size_t i = 0; i < env_sample_count; ++i) { // Generate a uniform sample in [0,1)^2. const Vector2d s = sampling_context.next_vector2<2>(); // Sample the environment. InputEvaluator input_evaluator(shading_context.get_texture_cache()); Vector3d incoming; Spectrum env_value; double env_prob; environment_edf.sample( shading_context, input_evaluator, s, incoming, env_value, env_prob); // Cull samples behind the shading surface. assert(is_normalized(incoming)); const double cos_in = dot(incoming, shading_basis.get_normal()); if (cos_in <= 0.0) continue; // Discard occluded samples. const double transmission = shading_context.get_tracer().trace( incoming_point, incoming, VisibilityFlags::ShadowRay); if (transmission == 0.0) continue; // Evaluate the BSSRDF. Spectrum bssrdf_value; bssrdf.evaluate( bssrdf_data, outgoing_point, outgoing.get_value(), incoming_point, incoming, bssrdf_value); // Compute MIS weight. const double bssrdf_prob = cos_in * RcpPi; const double mis_weight = mis_power2( env_sample_count * env_prob, bssrdf_sample_count * bssrdf_prob); // Add the contribution of this sample to the illumination. env_value *= static_cast<float>(transmission * cos_in / env_prob * mis_weight); env_value *= bssrdf_value; radiance += env_value; } if (env_sample_count > 1) radiance /= static_cast<float>(env_sample_count); }
void compute_ibl_bssrdf_sampling( SamplingContext& sampling_context, const ShadingContext& shading_context, const EnvironmentEDF& environment_edf, const BSSRDF& bssrdf, const void* bssrdf_data, const ShadingPoint& incoming_point, const ShadingPoint& outgoing_point, const Dual3d& outgoing, const size_t bssrdf_sample_count, const size_t env_sample_count, Spectrum& radiance) { assert(is_normalized(outgoing.get_value())); radiance.set(0.0f); sampling_context.split_in_place(2, bssrdf_sample_count); for (size_t i = 0; i < bssrdf_sample_count; ++i) { // Generate a uniform sample in [0,1)^2. const Vector2d s = sampling_context.next_vector2<2>(); // Sample the BSSRDF (hemisphere cosine). Vector3d incoming = sample_hemisphere_cosine(s); const double cos_in = incoming.y; const double bssrdf_prob = cos_in * RcpPi; incoming = incoming_point.get_shading_basis().transform_to_parent(incoming); if (incoming_point.get_side() == ObjectInstance::BackSide) incoming = -incoming; assert(is_normalized(incoming)); // Discard occluded samples. const double transmission = shading_context.get_tracer().trace( incoming_point, incoming, VisibilityFlags::ShadowRay); if (transmission == 0.0) continue; // Evaluate the BSSRDF. Spectrum bssrdf_value; bssrdf.evaluate( bssrdf_data, outgoing_point, outgoing.get_value(), incoming_point, incoming, bssrdf_value); // Evaluate the environment's EDF. InputEvaluator input_evaluator(shading_context.get_texture_cache()); Spectrum env_value; double env_prob; environment_edf.evaluate( shading_context, input_evaluator, incoming, env_value, env_prob); // Compute MIS weight. const double mis_weight = mis_power2( bssrdf_sample_count * bssrdf_prob, env_sample_count * env_prob); // Add the contribution of this sample to the illumination. env_value *= static_cast<float>(transmission * cos_in / bssrdf_prob * mis_weight); env_value *= bssrdf_value; radiance += env_value; } if (bssrdf_sample_count > 1) radiance /= static_cast<float>(bssrdf_sample_count); }
void DiagnosticSurfaceShader::evaluate( SamplingContext& sampling_context, const PixelContext& pixel_context, const ShadingContext& shading_context, const ShadingPoint& shading_point, ShadingResult& shading_result) const { switch (m_shading_mode) { case Color: { shading_result.set_main_to_opaque_pink_linear_rgba(); const Material* material = shading_point.get_material(); if (material) { const Material::RenderData& material_data = material->get_render_data(); #ifdef APPLESEED_WITH_OSL // Execute the OSL shader if there is one. if (material_data.m_shader_group) { shading_context.execute_osl_shading( *material_data.m_shader_group, shading_point); } #endif if (material_data.m_bsdf) { InputEvaluator input_evaluator(shading_context.get_texture_cache()); material_data.m_bsdf->evaluate_inputs( shading_context, input_evaluator, shading_point); const Vector3d direction = -normalize(shading_point.get_ray().m_dir); material_data.m_bsdf->evaluate( input_evaluator.data(), false, false, shading_point.get_geometric_normal(), shading_point.get_shading_basis(), direction, direction, ScatteringMode::All, shading_result.m_main.m_color); shading_result.m_color_space = ColorSpaceSpectral; } } } break; case Coverage: shading_result.set_main_to_linear_rgb(Color3f(1.0f)); break; case Barycentric: shading_result.set_main_to_linear_rgb( vector2_to_color(shading_point.get_bary())); break; case UV: shading_result.set_main_to_linear_rgb( uvs_to_color(shading_point.get_uv(0))); break; case Tangent: case Bitangent: case ShadingNormal: { #ifdef APPLESEED_WITH_OSL const Material* material = shading_point.get_material(); if (material) { const Material::RenderData& material_data = material->get_render_data(); // Execute the OSL shader if there is one. if (material_data.m_shader_group) { sampling_context.split_in_place(2, 1); shading_context.execute_osl_bump( *material_data.m_shader_group, shading_point, sampling_context.next_vector2<2>()); } } #endif const Vector3d v = m_shading_mode == ShadingNormal ? shading_point.get_shading_basis().get_normal() : m_shading_mode == Tangent ? shading_point.get_shading_basis().get_tangent_u() : shading_point.get_shading_basis().get_tangent_v(); shading_result.set_main_to_linear_rgb(vector3_to_color(v)); } break; case GeometricNormal: shading_result.set_main_to_linear_rgb( vector3_to_color(shading_point.get_geometric_normal())); break; case OriginalShadingNormal: shading_result.set_main_to_linear_rgb( vector3_to_color(shading_point.get_original_shading_normal())); break; case WorldSpacePosition: { const Vector3d& p = shading_point.get_point(); shading_result.set_main_to_linear_rgb( Color3f(Color3d(p.x, p.y, p.z))); } break; case Sides: shading_result.set_main_to_linear_rgb( shading_point.get_side() == ObjectInstance::FrontSide ? Color3f(0.0f, 0.0f, 1.0f) : Color3f(1.0f, 0.0f, 0.0f)); break; case Depth: shading_result.set_main_to_linear_rgb( Color3f(static_cast<float>(shading_point.get_distance()))); break; case ScreenSpaceWireframe: { // Initialize the shading result to the background color. shading_result.set_main_to_linear_rgba(Color4f(0.0f, 0.0f, 0.8f, 0.5f)); if (shading_point.is_triangle_primitive()) { // Film space thickness of the wires. const double SquareWireThickness = square(0.00025); // Retrieve the time, the scene and the camera. const double time = shading_point.get_time().m_absolute; const Scene& scene = shading_point.get_scene(); const Camera& camera = *scene.get_camera(); // Compute the film space coordinates of the intersection point. Vector2d point_ndc; camera.project_point(time, shading_point.get_point(), point_ndc); // Loop over the triangle edges. for (size_t i = 0; i < 3; ++i) { // Retrieve the end points of this edge. const size_t j = (i + 1) % 3; const Vector3d vi = shading_point.get_vertex(i); const Vector3d vj = shading_point.get_vertex(j); // Compute the film space coordinates of the edge's end points. Vector2d vi_ndc, vj_ndc; if (!camera.project_segment(time, vi, vj, vi_ndc, vj_ndc)) continue; // Compute the film space distance from the intersection point to the edge. const double d = square_distance_point_segment(point_ndc, vi_ndc, vj_ndc); // Shade with the wire's color if the hit point is close enough to the edge. if (d < SquareWireThickness) { shading_result.set_main_to_linear_rgba(Color4f(1.0f)); break; } } } else { assert(shading_point.is_curve_primitive()); // todo: implement. } } break; case WorldSpaceWireframe: { // Initialize the shading result to the background color. shading_result.set_main_to_linear_rgba(Color4f(0.0f, 0.0f, 0.8f, 0.5f)); if (shading_point.is_triangle_primitive()) { // World space thickness of the wires. const double SquareWireThickness = square(0.0015); // Retrieve the world space intersection point. const Vector3d& point = shading_point.get_point(); // Loop over the triangle edges. for (size_t i = 0; i < 3; ++i) { // Retrieve the end points of this edge. const size_t j = (i + 1) % 3; const Vector3d& vi = shading_point.get_vertex(i); const Vector3d& vj = shading_point.get_vertex(j); // Compute the world space distance from the intersection point to the edge. const double d = square_distance_point_segment(point, vi, vj); // Shade with the wire's color if the hit point is close enough to the edge. if (d < SquareWireThickness) { shading_result.set_main_to_linear_rgba(Color4f(1.0f)); break; } } } else { assert(shading_point.is_curve_primitive()); // todo: implement. } } break; case AmbientOcclusion: { // Compute the occlusion. const double occlusion = compute_ambient_occlusion( sampling_context, sample_hemisphere_uniform<double>, shading_context.get_intersector(), shading_point, m_ao_max_distance, m_ao_samples); // Return a gray scale value proportional to the accessibility. const float accessibility = static_cast<float>(1.0 - occlusion); shading_result.set_main_to_linear_rgb(Color3f(accessibility)); } break; case AssemblyInstances: shading_result.set_main_to_linear_rgb( integer_to_color(shading_point.get_assembly_instance().get_uid())); break; case ObjectInstances: shading_result.set_main_to_linear_rgb( integer_to_color(shading_point.get_object_instance().get_uid())); break; case Regions: { const uint32 h = mix_uint32( static_cast<uint32>(shading_point.get_object_instance().get_uid()), static_cast<uint32>(shading_point.get_region_index())); shading_result.set_main_to_linear_rgb(integer_to_color(h)); } break; case Primitives: { const uint32 h = mix_uint32( static_cast<uint32>(shading_point.get_object_instance().get_uid()), static_cast<uint32>(shading_point.get_region_index()), static_cast<uint32>(shading_point.get_primitive_index())); shading_result.set_main_to_linear_rgb(integer_to_color(h)); } break; case Materials: { const Material* material = shading_point.get_material(); if (material) shading_result.set_main_to_linear_rgb(integer_to_color(material->get_uid())); else shading_result.set_main_to_opaque_pink_linear_rgba(); } break; case RaySpread: { const ShadingRay& ray = shading_point.get_ray(); if (!ray.m_has_differentials) break; const Material* material = shading_point.get_material(); if (material) { const Material::RenderData& material_data = material->get_render_data(); #ifdef APPLESEED_WITH_OSL // Execute the OSL shader if there is one. if (material_data.m_shader_group) { shading_context.execute_osl_shading( *material_data.m_shader_group, shading_point); } #endif if (material_data.m_bsdf) { const Dual3d outgoing( -ray.m_dir, ray.m_dir - ray.m_rx.m_dir, ray.m_dir - ray.m_ry.m_dir); InputEvaluator input_evaluator(shading_context.get_texture_cache()); material_data.m_bsdf->evaluate_inputs( shading_context, input_evaluator, shading_point); const void* bsdf_data = input_evaluator.data(); BSDFSample sample(shading_point, outgoing); material_data.m_bsdf->sample( sampling_context, bsdf_data, false, false, sample); if (!sample.m_incoming.has_derivatives()) break; // The 3.0 factor is chosen so that ray spread from Lambertian BRDFs is approximately 1. const double spread = max( norm(sample.m_incoming.get_dx()), norm(sample.m_incoming.get_dy())) * 3.0; shading_result.set_main_to_linear_rgb( Color3f(static_cast<float>(spread))); } } } break; case FacingRatio: { const Vector3d& normal = shading_point.get_shading_normal(); const Vector3d& view = shading_point.get_ray().m_dir; const double facing = abs(dot(normal, view)); shading_result.set_main_to_linear_rgb( Color3f(static_cast<float>(facing))); } break; default: assert(false); shading_result.set_main_to_transparent_black_linear_rgba(); break; } }
void compute_ibl_bsdf_sampling( SamplingContext& sampling_context, const ShadingContext& shading_context, const EnvironmentEDF& environment_edf, const ShadingPoint& shading_point, const Dual3d& outgoing, const BSDF& bsdf, const void* bsdf_data, const int bsdf_sampling_modes, const size_t bsdf_sample_count, const size_t env_sample_count, Spectrum& radiance) { assert(is_normalized(outgoing.get_value())); radiance.set(0.0f); for (size_t i = 0; i < bsdf_sample_count; ++i) { // Sample the BSDF. // todo: rendering will be incorrect if the BSDF value returned by the sample() method // includes the contribution of a specular component since these are explicitly rejected // afterward. We need a mechanism to indicate that we want the contribution of some of // the components only. BSDFSample sample(shading_point, outgoing); bsdf.sample( sampling_context, bsdf_data, false, // not adjoint true, // multiply by |cos(incoming, normal)| sample); // Filter scattering modes. if (!(bsdf_sampling_modes & sample.m_mode)) continue; // Discard occluded samples. const float transmission = shading_context.get_tracer().trace( shading_point, Vector3d(sample.m_incoming.get_value()), VisibilityFlags::ShadowRay); if (transmission == 0.0f) continue; // Evaluate the environment's EDF. InputEvaluator input_evaluator(shading_context.get_texture_cache()); Spectrum env_value; float env_prob; environment_edf.evaluate( shading_context, input_evaluator, sample.m_incoming.get_value(), env_value, env_prob); // Apply all weights, including MIS weight. if (sample.m_mode == ScatteringMode::Specular) env_value *= transmission; else { const float mis_weight = mis_power2( bsdf_sample_count * sample.m_probability, env_sample_count * env_prob); env_value *= transmission / sample.m_probability * mis_weight; } // Add the contribution of this sample to the illumination. env_value *= sample.m_value; radiance += env_value; } if (bsdf_sample_count > 1) radiance /= static_cast<float>(bsdf_sample_count); }