Reputation: 11
I am writing a raytracer and shading using the Phong illumination model. In this example, my material does not have any ambient term. When I render, I get the following image:
As you can see, there appears to be a lighter ring around the specularity. If I disable specularity entirely and only render the diffuse, I obtain the following:
So, this only happens when I introduce specularity. I have done shading multiple times and I have never seen this before. The entire shading code is as follows (I normalized a few more vectors than I needed to just to make sure this wasn't the issue):
glm::vec3 shading(std::shared_ptr<scene::scene> scene, std::shared_ptr<hit> hit, uint16_t level)
{
glm::vec3 normal = glm::normalize(hit->normal);
glm::vec3 result = hit->mat->ka; // Initialize ambient lighting with unit (global) intensity.
glm::vec3 point = hit->r.origin + (glm::normalize(hit->r.direction) * hit->t); // Surface vertex.
glm::vec3 viewing = glm::normalize(scene->camera_pos - point); // Direction vector to the camera.
// Iterate through every light source in the scene, as this contributes to the overall lighting.
for (auto& light_source : scene->lights)
{
glm::vec3 light = glm::normalize(light_source.position - point); // Direction vector to the light.
// Calculate diffuse.
float diffuse_dot = glm::dot(normal, light); // N . L
if (diffuse_dot > 0.0f)
{
glm::vec3 diffuse = light_source.intensity * (diffuse_dot * hit->mat->kd); // Compute diffuse component.
result += diffuse; // Add diffuse component to result.
}
// Calculate specularity.
glm::vec3 reflected = glm::reflect(-light, normal); // GLM takes opposite light vector.
float specularity_dot = glm::dot(viewing, reflected); // R . V
if (specularity_dot > 0.0f)
{
float specularity_coefficient = glm::pow(specularity_dot, hit->mat->ns); // Add specularity component to result.
glm::vec3 specularity = light_source.intensity * (specularity_coefficient * hit->mat->ks);
result += specularity; // Add specularity.
}
}
return glm::clamp(result, 0.0f, 1.0f);
}
The colour is written to a PPM file, and each pixel is written as such:
// Writes the color; currently not thread safe.
void write_color(std::ostream& out, glm::vec3& color)
{
out << static_cast<int>(255.999 * color.x) << ' '
<< static_cast<int>(255.999 * color.y) << ' '
<< static_cast<int>(255.999 * color.z) << '\n';
}
Lastly, the material is defined as follows:
static std::shared_ptr<rt::material> material_blue = std::make_shared<rt::material>(
glm::vec3{ 0.0f }, // Ka
glm::vec3{ 0.0f, 0.0f, 1.0f }, // Kd
glm::vec3{ 1.0f, 1.0f, 1.0f }, // Ks
10.0f, // Ns/Shininess/the power the specularity is raised to
// -snip-
);
I suspect that this may have something to do with glm::clamp
. If I change my code at the end to be the code below, I get the debug render shown below.
if (result.x > 1.0f || result.y > 1.0f || result.z > 1.0f)
{
return glm::vec3{ 1.0f, 0.0f, 0.0f };
}
return glm::clamp(result, 0.0f, 1.0f);
This shape looks awfully familiar to the shape the outline of this ring makes. Therefore I suspect it could be an issue related to clamping, but countless hours of debugging have not gotten me further.
TL;DR I have a weird "ring" around my specularity (see first image) and I want to know how to get rid of it.
Upvotes: 0
Views: 316
Reputation: 599
Phong model is phenomenological, not physically based, so it can produce some artifacts. This ring is "normal", and to verify that, you can recreate the same scene with OpenGL 1.x (fixed pipeline, so Phong model is in the driver, you can't introduce a bug if you don't have to code it).
I think the main reason can be related to the lack of energy conservation principle (and from that follows the need to clamp, that potentially exacerbate some defects). If you try to steal some energy conservation idea from PBR models the problem disappears. As simple idea, you can say think that the same "piece of light energy" (let say a photon) cannot add to both specular and diffuse reflections at the same time. An easy patch that improves the situation a lot is to weight the diffuse term with 1-specularity_coefficient, so we ensure that both contributions sum 1 at most.
Upvotes: 0