I am currently building a pathtracer, which has support for vertex normals and face normals.
There seems to be an issue when it comes to rendering objects with triangles exported from Blender. Regardless of how I calculate the normal, the result always shows inconsistent shading of triangles.
The shading seems to change per triangle, with the ones more aligned to the camera being correct. It seems to depend on where the objects are in the scene relative to the camera. Below is the intersection algorithm for the triangle.
bool Triangle::intersection(Ray &ray, HitInfo &hit)
{
float3 edge1 = v1->pos - v0->pos;
float3 edge2 = v2->pos - v0->pos;
float3 h = ray.getDirection().cross(edge2);
float a = edge1.dot(h);
float f = 1.0 / a;
float3 s = ray.getOrigin() - v0->pos;
float u = f * (s.dot(h));
if (u < 0.0 || u > 1.0)
return false;
float3 q = s.cross(edge1);
float v = f * ray.getDirection().dot(q);
if (v < 0.0 || u + v > 1.0)
return false;
float t = edge2.dot(q) * f;
if (t > 1e-9 && t < ray.tmin)
{
hit.t = t;
float3 temp_normal = unit(edge2.cross(edge1)); //unit(v1->normal*u + v2->normal*v + v0->normal*(1. - u - v));
if (unit(ray.getDirection()).dot(temp_normal)< 1e-9) temp_normal = temp_normal * (-1);
hit.normal = temp_normal;
return true;
}
else
{
return false;
}
}
The intersection algorithm calculate the normal using the cross product of the edges, however using the normals provided by blender does not change much either. I use the Moller Trumbore intersection algorithm, exporting obj's from Blender (enabling "keep vertex order" doesn't change anything). I have also tried different materials but the problem still persists.
The above image is the normals being rendered with an emissive shader. This is the result I would expect, so I don't think this is the issue.
float3 cosineSampleHemi(float u1, float u2)
{
float z = std::pow(1.0 - u1, 3.0 / 5.0);
float phi = 2.0 * M_PI * u2;
float theta = sqrt(std::max(0.0, 1.0 - z * z));
float x = theta * cos(phi);
float y = theta * sin(phi);
return float3(x, y, z);
}
float3 random_in_unit_sphere() {
float3 p;
do {
p = float3(drand48(),drand48(),drand48())*2.0 - float3(1.0);
} while (p.dot(p) >= 1.0);
return p;
}
The above code is for the sampling, I have tried both functions and it produces similar results. Below is the lambertian scatter function, which uses the hemisphere sampling methods.
bool Lambertian::scatter(Ray &ray, HitInfo &hit, float3 &attenuation, Ray &new_ray) const
{
new_ray = Ray(ray.getHit(hit.t), cosineSampleHemi(drand48(), drand48()));
attenuation = m_colour;
return true;
}
If any more info is needed I will be happy to supply it, as I am completely stumped at this point.