<p>Your normal calculation looks backwards: <code>edge2.cross(edge1)</code>, but (assuming CCW winding) it should be edge1 × edge2.</p>

<p>The sign flip for back facing normals is also reversed; the ray direction and triangle normal <em>should</em> be facing in opposite directions, thus should have negative dot product, but it looks like you're flipping it to make the dot product always positive.</p>

<p>Maybe there's another differing convention elsewhere in your shading code that accounts for this? In any case, I'm not sure this is actually related to your problem, but it jumped out at me as being odd.</p>

CC BY-SA 3.0
2018-01-30T21:34:17.907Z
Yeah I should have pointed out I've tried doing edge1.cross(egde2) without any noticeable difference. The dot product of the incident ray and the normal also does not seem to have a noticeable effect when I change the comparison sign or just remove it completely.
Oliver Cruickshank
CC BY-SA 3.0
2018-01-30T21:24:42.060Z
2018-01-30T21:24:42.060Z
Nathan Reed
<p>I have managed to fix the error. It turns out the error was never situated in the normal calulation it was the shading algorith. The floating point precision error caused the new ray to be slightly below the surface, which meant it would intersect itself and become darker. This is a problem common in biased raytracers, the phenomenon known as "Shadow acne".</p>

<p>To fix it I added the following code:</p>

<pre><code>bool Lambertian::scatter(Ray &amp;ray, HitInfo &amp;hit, float3 &amp;attenuation, Ray &amp;new_ray) const
{
    float3 offset = hit.normal*(1e-5);
    new_ray = Ray(ray.getHit(hit.t) + offset, cosineSampleHemi(drand48(), drand48()));
    attenuation = m_colour;
    return true;
}
</code></pre>

<p>the offset variable moves the ray origin to slightly above the surface by a small amount and that seems to fix the issue.</p>

CC BY-SA 3.0
2018-01-31T18:45:43.530Z
The problem is common to all ray based rendering in general for what that's worth!
Alan Wolfe
CC BY-SA 3.0
2018-02-12T06:02:51.487Z
Would backface culling be a solution to this?
PaulHK
CC BY-SA 3.0
2018-01-31T17:35:15.170Z
2018-01-31T17:35:15.170Z
Oliver Cruickshank
<p>I am currently building a pathtracer, which has support for vertex normals and face normals.</p>

<p>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. 
<a href="https://i.sstatic.net/53xUF.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/53xUF.png" alt="enter image description here"></a></p>

<p>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.</p>

<pre><code>bool Triangle::intersection(Ray &amp;ray, HitInfo &amp;hit)
{
    float3 edge1 = v1-&gt;pos - v0-&gt;pos;
    float3 edge2 = v2-&gt;pos - v0-&gt;pos;

    float3 h = ray.getDirection().cross(edge2);
    float a = edge1.dot(h);

    float f = 1.0 / a;
    float3 s = ray.getOrigin() - v0-&gt;pos;
    float u = f * (s.dot(h));

    if (u &lt; 0.0 || u &gt; 1.0)
        return false;

    float3 q = s.cross(edge1);
    float v = f * ray.getDirection().dot(q);
    if (v &lt; 0.0 || u + v &gt; 1.0)
        return false;

    float t = edge2.dot(q) * f;
    if (t &gt; 1e-9 &amp;&amp; t &lt; ray.tmin)
    {
        hit.t = t;
        float3 temp_normal = unit(edge2.cross(edge1)); //unit(v1-&gt;normal*u + v2-&gt;normal*v + v0-&gt;normal*(1. - u - v));
        if (unit(ray.getDirection()).dot(temp_normal)&lt; 1e-9) temp_normal = temp_normal * (-1);
        hit.normal = temp_normal;
        return true;
    }
    else
    {
        return false;
    }
}
</code></pre>

<p>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.</p>

<p><a href="https://i.sstatic.net/cWBqI.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/cWBqI.png" alt="enter image description here"></a></p>

<p><a href="https://i.sstatic.net/PWyxJ.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/PWyxJ.png" alt="enter image description here"></a></p>

<p>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.</p>

<pre><code>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) &gt;= 1.0);
    return p;
}
</code></pre>

<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.</p>

<pre><code>bool Lambertian::scatter(Ray &amp;ray, HitInfo &amp;hit, float3 &amp;attenuation, Ray &amp;new_ray) const
{
    new_ray = Ray(ray.getHit(hit.t), cosineSampleHemi(drand48(), drand48()));
    attenuation = m_colour;
    return true;
}
</code></pre>

<p>If any more info is needed I will be happy to supply it, as I am completely stumped at this point.</p>

CC BY-SA 3.0
2018-01-31T02:21:01.513Z
The look of the gradients in your first image looks like swapped normals to me. Have you tried rendering your normals to check that they are correct? My suspicion would be directed toward vertex indices.
Julien Guertault
CC BY-SA 3.0
2018-01-31T09:39:40.003Z
I have updated my answer to include an image of the normals, and it seems to be what I would expect. Or rather, the normals are consistent on the triangles that make up a plane.
Oliver Cruickshank
CC BY-SA 3.0
2018-01-31T15:47:17.417Z
I don't think the problem is in the code you've shown.  It looks more like the light isn't being transformed into the coordinate system of the object correctly.  Where's the code that shades the pixel based on the normal?  Does it perform the N·L calculation in world space or object space? (Transform light to object space or transform object to light space, or transform both to world space?)
Wyck
CC BY-SA 3.0
2018-01-31T16:10:28.110Z
The lighting is done by sampling a unit sphere or hemisphere. I just use the normal as calculated by the cross product of the edges or the normals from blender. I'll link the code that samples the hemisphere.
Oliver Cruickshank
<raytracing><pathtracing><shading><normal-mapping>
Inconsistent shading in pathtraced image
Oliver Cruickshank
# Inconsistent shading in pathtraced image

(Asked by: username_0 on 2018-01-30)

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.
[![enter image description here](https://i.sstatic.net/53xUF.png)](https://i.sstatic.net/53xUF.png)

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.

[![enter image description here](https://i.sstatic.net/cWBqI.png)](https://i.sstatic.net/cWBqI.png)

[![enter image description here](https://i.sstatic.net/PWyxJ.png)](https://i.sstatic.net/PWyxJ.png)

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.

**Tags:** raytracing, pathtracing, shading, normal-mapping

> **Comments:**
> **username_1**: The look of the gradients in your first image looks like swapped normals to me. Have you tried rendering your normals to check that they are correct? My suspicion would be directed toward vertex indices.  
> **username_0**: I have updated my answer to include an image of the normals, and it seems to be what I would expect. Or rather, the normals are consistent on the triangles that make up a plane.  
> **username_2**: I don't think the problem is in the code you've shown.  It looks more like the light isn't being transformed into the coordinate system of the object correctly.  Where's the code that shades the pixel based on the normal?  Does it perform the N·L calculation in world space or object space? (Transform light to object space or transform object to light space, or transform both to world space?)  
> **username_0**: The lighting is done by sampling a unit sphere or hemisphere. I just use the normal as calculated by the cross product of the edges or the normals from blender. I'll link the code that samples the hemisphere.  

---

## 2 answers

### Answer by username_0 on 2018-01-31. Score: 4 | Accepted answer

I have managed to fix the error. It turns out the error was never situated in the normal calulation it was the shading algorith. The floating point precision error caused the new ray to be slightly below the surface, which meant it would intersect itself and become darker. This is a problem common in biased raytracers, the phenomenon known as "Shadow acne".

To fix it I added the following code:

```
bool Lambertian::scatter(Ray &ray, HitInfo &hit, float3 &attenuation, Ray &new_ray) const
{
    float3 offset = hit.normal*(1e-5);
    new_ray = Ray(ray.getHit(hit.t) + offset, cosineSampleHemi(drand48(), drand48()));
    attenuation = m_colour;
    return true;
}

```

the offset variable moves the ray origin to slightly above the surface by a small amount and that seems to fix the issue.

> **Comments:**
> **username_4**: The problem is common to all ray based rendering in general for what that's worth!  
> **username_5**: Would backface culling be a solution to this?  

---

### Answer by username_3 on 2018-01-30. Score: 1

Your normal calculation looks backwards: `edge2.cross(edge1)`, but (assuming CCW winding) it should be edge1 × edge2.

The sign flip for back facing normals is also reversed; the ray direction and triangle normal *should* be facing in opposite directions, thus should have negative dot product, but it looks like you're flipping it to make the dot product always positive.

Maybe there's another differing convention elsewhere in your shading code that accounts for this? In any case, I'm not sure this is actually related to your problem, but it jumped out at me as being odd.

> **Comments:**
> **username_0**: Yeah I should have pointed out I've tried doing edge1.cross(egde2) without any noticeable difference. The dot product of the incident ray and the normal also does not seem to have a noticeable effect when I change the comparison sign or just remove it completely.  

---

