<p>I have to disagree with @jack11111, this is definitely not a clipping issue. I have two possible explanations which you can easily test and verify.</p>
<h3>Clipping theory</h3>
<ul>
<li>The patch looks pretty constant in color, clipping would be sort of a gradient (modulo 255) of full intensity in at least one channel (R/G/B).</li>
<li>There is no direct light source to cause a strong specular highlight. According to the ground shading and the Lambert-shaded green balls a point light source is placed above, little bit in front of the camera. It seems to have normal intensity that should not cause any trouble. In fact I see in your code that you apply only basic Lambert <code>n . l</code> shading, so no specular highlights possible.</li>
<li>There is no indirect source to cause such a peak. The rays reflect to towards ground that is rather dark. The bright spot on the ground is located else where. That is well visible on the red sphere where the error occurs closer than the next green sphere, but the highlight on the ground is even further away.</li>
<li>It is a strange coincidence that the secondary color of the patch is exactly the same for both spheres.</li>
</ul>
<h3>Normals theory</h3>
<ul>
<li>Normals seem to be computed correctly elsewhere, all your shading is consistent with nice gradients.</li>
<li>You reflections are also correct elsewhere, that will be on problem.</li>
</ul>
<h1>It's a trap! (almost)</h1>
<p>Looking closely at your image I believe I can explain what is happening there. The rays hitting the suspicious regions at the sphere will reflect towards ground in a direction very similar to the ground normal.</p>
<p>You ground does not seem to be reflective, but if it were: The rays would reflect back in almost the exact opposite direction hitting back the ball and then heading towards the camera. So they would hit the ball twice which could cause its color to gain dominance. You can test this theory by comparing the rendering results with <code>maxDepth = 1</code>. If the discontinuity disappears then that is the problem.</p>
<h1>Upside down</h1>
<p>My second explanation is much more likely to be the case. It is also based on the observation that the rays reflected from the suspicious patches hit the ground in a direction pointing almost exactly down. I noticed a bit strange setup of your plane in your code. It has its normal pointing down!?</p>
<pre class="lang-py prettyprint-override"><code>self.normal = Vec3(0,-1,0)
</code></pre>
<p>I am not sure how the Lambert shading of the ground works then (should be then all just ambient in fact), but assuming <code>Vec(0,1,0)</code> is your up then the intersection will in fact yield <code>None</code></p>
<pre class="lang-py prettyprint-override"><code>denom = ray.dot(self.normal) #something like 1e-6 in the suspicious area
if abs(denom) &gt; 0.0001: #evaluates to false
    #... true branch ignored
return None #false branch activated, returns no intersection
</code></pre>
<p>Having no intersection means that the hit point is shaded only with a constant color</p>
<blockquote>
<pre class="lang-py prettyprint-override"><code>if closest_object == None:
   return Vec3(173/255, 216/255, 230/255)
</code></pre>
</blockquote>
<p>So to test this explanation, just change the returned color for this case to black and see if the patch darkens.</p>
<pre class="lang-py prettyprint-override"><code>if closest_object == None:
    return Vec3(0, 0, 0)
</code></pre>
<p>I hope that helps. Please let me know the results.</p>
<h1>Update</h1>
<p>After the author confirmed my second explanation I found the reason in the code. The source of the problem is in the ray-plane intersection method.</p>
<p><strong>Most important of all the epsilon in the comparison should be omitted, since that is causing the patch to be discontinuous.</strong> As mentioned earlier, the incoming ray direction is almost <code>Vec3(0,-1,0)</code>. You have set your plane normal to exactly <code>Vec3(0,-1,0)</code> so the dot product yields something very close to <code>0</code>.</p>
<p>So the quick fix would be:</p>
<pre class="lang-py prettyprint-override"><code>denom = ray.dot(self.normal)
if abs(denom) &gt;= 0: #omit the epsilon
    #...
</code></pre>
<p>Your plane normal is inconsistent with how a normal is usually defined. I believe your plane is not supposed to face down. It would be much better and correct to have the plane normal pointing <strong>up</strong> and then testing for a <strong>negative</strong> dot product.</p>
<p>A front-face hit occurs when the face normal and the ray are pointing in opposite directions, i.e. their angle <code>θ</code> is smaller than <code>-π/2</code> or larger than <code>π/2</code>, so <code>cos(θ) &lt; 0</code>. Note that <code>denom</code> is in fact <code>cos(θ)</code>. If <code>|θ| &lt; π/2</code>, i.e. <code>cos(θ) &gt; 0</code>, then it is a back-face hit. You ignore such hits for now but once you add transparency to your raycaster you will need them as well.</p>
<p>The correct way would be:</p>
<pre class="lang-py prettyprint-override"><code>def __init__(self, color, y=0, specular=-1, reflective=0):
    # ...
    self.normal = Vec3(0,1,0) #pointing UP

    def intersect_at_point(self, origin, ray):
        denom = ray.dot(self.normal) # denom == cos(θ)

        if abs(denom) &lt;= 0: #is negative &lt;=&gt; |θ| &gt; π/2; omit the epsilon
            diff  = self.center - ray
            t = diff.dot(self.normal) / denom

            if t &gt; 0: #I suggest to omit the epsilon here as well
                return t
        return None
</code></pre>

CC BY-SA 4.0
2020-05-14T22:59:57.053Z
Glad to hear that! Oh and now I see what you are doing wrong in the plane! I will add that to my answer.
Isolin
CC BY-SA 4.0
2020-05-14T23:04:02.170Z
my y is -ve upwards, even then I changed the plane to `Vec3(0,1,0)` the black patch remains. I think I will rewrite the code with the correct axes. This is my first program in graphics programming so a bit confused with something that should be very obvious.
anekix
CC BY-SA 4.0
2020-05-14T23:30:43.687Z
Nope, the reason is the epsilon in the `denom` check. Changing the normal is just for correctness and then the `denom` check needs to be inverted as well.
Isolin
CC BY-SA 4.0
2020-05-14T22:38:11.737Z
2020-05-14T23:27:49.660Z
2020-06-18T08:32:12.800Z
Isolin
<p>It looks like the light and colour calculation for the sphere reflection is peaking and may need to be clipped to the range 0-255. <a href="https://m.youtube.com/watch?v=vE5c2hTRLZM&amp;list=PLHm_I0tE5kKPPWXkTTtOn8fkcwEGZNETh&amp;index=7" rel="nofollow noreferrer" title="Raytracer from scratch in C++">This</a> video around 1:21:45 mark shows an example of this.</p>

CC BY-SA 4.0
2020-05-14T19:34:32.680Z
2020-05-14T19:34:32.680Z
jackw11111
<p>I am writing a small raytracer in python , everything was working fine but when i implemented plane geometry then the reflective spheres are getting weird patches on lower bottom. image below:</p>

<p><em>I guess this is happening because of some error in calculating lighting with wrong normals? but i am unable to figure out exactly what.</em>
<a href="https://i.sstatic.net/fHB56.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/fHB56.png" alt="enter image description here"></a></p>

<p>code to find palne-ray intersection:</p>

<pre class="lang-py prettyprint-override"><code>class Plane(Shape):
    def __init__(self, color, y=0, specular=-1, reflective=0):
        self.specular = specular
        self.reflective = reflective
        self.center = Vec3(0,y,0)
        self.normal = Vec3(0,-1,0)
        self.color = color

    def intersect_at_point(self, origin, ray):
        denom = ray.dot(self.normal)

        if abs(denom) &gt; 0.0001:
            diff  = self.center - ray
            t = diff.dot(self.normal) / denom

            if t &gt; 0.0001:
                return t
        return None
</code></pre>

<p>trace ray function.</p>

<pre class="lang-py prettyprint-override"><code>def trace_ray(self, origin, direction, t_min, t_max, depth):
    closest_object, closest_t = self.closest_intersection(origin, direction, t_min, t_max)

    if closest_object == None:
        return Vec3(173/255, 216/255, 230/255)

    # Compute local color
    P = origin + closest_t * direction  # Compute intersection
    N = P - closest_object.center  # Compute  normal at intersection
    N = N / N.mag()
    local_color = closest_object.color * self.compute_light(P, N)

    # If we hit the recursion limit or the object is not reflective, we're done
    r = closest_object.reflective
    if depth &lt;= 0 or r &lt;= 0:
        return local_color

    # Compute the reflected color
    R = self.reflect_ray(-1*direction, N)
    reflected_color = self.trace_ray(P, R, 0.001, math.inf, depth - 1)

    return local_color * (1 - r) + reflected_color * r
</code></pre>

<p>closest intersection method.</p>

<pre class="lang-py prettyprint-override"><code>def closest_intersection(self, O, D, t_min, t_max):
    closest_t = math.inf
    closest_object = None

    for object in self.world.objects:
        t1, t2 = object.intersect_at_point(O, D)

        if (t_min &lt; t1 &lt; t_max) and t1 &lt; closest_t:
            closest_t = t1
            closest_object = object

        if (t_min &lt; t2 &lt; t_max) and t2 &lt; closest_t:
            closest_t = t2
            closest_object = object

    for plane in self.world.planes:
        t = plane.intersect_at_point(O,D)

        if t and (t_min &lt; t &lt; t_max) and t &lt; closest_t:
            closest_t = t
            closest_object = plane

    return closest_object, closest_t
</code></pre>

<p>Calculate light method.</p>

<pre class="lang-py prettyprint-override"><code>def compute_light(self, Point, Normal):
    i = 0.0
    P = Point
    N = Normal

    for light in self.world.lights:
        if light.type == 'ambient':
            i += light.intensity
        else:
            if light.type == 'point':
                L = light.position - P
                t_max = 1

            if light.type == 'directional':
                L = light.direction
                t_max = math.inf

            # Shadow check
            shadow_sphere, shadow_t = self.closest_intersection(P, L, 0.001, t_max)
            if shadow_sphere != None:
                continue

            n_dot_l = N.dot(L)

            if n_dot_l &gt; 0:
                # print(f'light intensity {light.intensity}')
                i+= light.intensity * n_dot_l / (N.mag() * L.mag())

    return i
</code></pre>

<raytracing>
Weird patch on reflective spheres on planes in raytracer
anekix
# Weird patch on reflective spheres on planes in raytracer

(Asked by: username_0 on 2020-05-12)

I am writing a small raytracer in python , everything was working fine but when i implemented plane geometry then the reflective spheres are getting weird patches on lower bottom. image below:

*I guess this is happening because of some error in calculating lighting with wrong normals? but i am unable to figure out exactly what.*
[![enter image description here](https://i.sstatic.net/fHB56.png)](https://i.sstatic.net/fHB56.png)

code to find palne-ray intersection:

```py
class Plane(Shape):
    def __init__(self, color, y=0, specular=-1, reflective=0):
        self.specular = specular
        self.reflective = reflective
        self.center = Vec3(0,y,0)
        self.normal = Vec3(0,-1,0)
        self.color = color

    def intersect_at_point(self, origin, ray):
        denom = ray.dot(self.normal)

        if abs(denom) > 0.0001:
            diff  = self.center - ray
            t = diff.dot(self.normal) / denom

            if t > 0.0001:
                return t
        return None

```

trace ray function.

```py
def trace_ray(self, origin, direction, t_min, t_max, depth):
    closest_object, closest_t = self.closest_intersection(origin, direction, t_min, t_max)

    if closest_object == None:
        return Vec3(173/255, 216/255, 230/255)

    # Compute local color
    P = origin + closest_t * direction  # Compute intersection
    N = P - closest_object.center  # Compute  normal at intersection
    N = N / N.mag()
    local_color = closest_object.color * self.compute_light(P, N)

    # If we hit the recursion limit or the object is not reflective, we're done
    r = closest_object.reflective
    if depth <= 0 or r <= 0:
        return local_color

    # Compute the reflected color
    R = self.reflect_ray(-1*direction, N)
    reflected_color = self.trace_ray(P, R, 0.001, math.inf, depth - 1)

    return local_color * (1 - r) + reflected_color * r

```

closest intersection method.

```py
def closest_intersection(self, O, D, t_min, t_max):
    closest_t = math.inf
    closest_object = None

    for object in self.world.objects:
        t1, t2 = object.intersect_at_point(O, D)

        if (t_min < t1 < t_max) and t1 < closest_t:
            closest_t = t1
            closest_object = object

        if (t_min < t2 < t_max) and t2 < closest_t:
            closest_t = t2
            closest_object = object

    for plane in self.world.planes:
        t = plane.intersect_at_point(O,D)

        if t and (t_min < t < t_max) and t < closest_t:
            closest_t = t
            closest_object = plane

    return closest_object, closest_t

```

Calculate light method.

```py
def compute_light(self, Point, Normal):
    i = 0.0
    P = Point
    N = Normal

    for light in self.world.lights:
        if light.type == 'ambient':
            i += light.intensity
        else:
            if light.type == 'point':
                L = light.position - P
                t_max = 1

            if light.type == 'directional':
                L = light.direction
                t_max = math.inf

            # Shadow check
            shadow_sphere, shadow_t = self.closest_intersection(P, L, 0.001, t_max)
            if shadow_sphere != None:
                continue

            n_dot_l = N.dot(L)

            if n_dot_l > 0:
                # print(f'light intensity {light.intensity}')
                i+= light.intensity * n_dot_l / (N.mag() * L.mag())

    return i

```

**Tags:** raytracing


---

## 2 answers

### Answer by username_1 on 2020-05-14. Score: 2 | Accepted answer

I have to disagree with @jack11111, this is definitely not a clipping issue. I have two possible explanations which you can easily test and verify.

### Clipping theory

* The patch looks pretty constant in color, clipping would be sort of a gradient (modulo 255) of full intensity in at least one channel (R/G/B).
* There is no direct light source to cause a strong specular highlight. According to the ground shading and the Lambert-shaded green balls a point light source is placed above, little bit in front of the camera. It seems to have normal intensity that should not cause any trouble. In fact I see in your code that you apply only basic Lambert `n . l` shading, so no specular highlights possible.
* There is no indirect source to cause such a peak. The rays reflect to towards ground that is rather dark. The bright spot on the ground is located else where. That is well visible on the red sphere where the error occurs closer than the next green sphere, but the highlight on the ground is even further away.
* It is a strange coincidence that the secondary color of the patch is exactly the same for both spheres.

### Normals theory

* Normals seem to be computed correctly elsewhere, all your shading is consistent with nice gradients.
* You reflections are also correct elsewhere, that will be on problem.

# It's a trap! (almost)

Looking closely at your image I believe I can explain what is happening there. The rays hitting the suspicious regions at the sphere will reflect towards ground in a direction very similar to the ground normal.

You ground does not seem to be reflective, but if it were: The rays would reflect back in almost the exact opposite direction hitting back the ball and then heading towards the camera. So they would hit the ball twice which could cause its color to gain dominance. You can test this theory by comparing the rendering results with `maxDepth = 1`. If the discontinuity disappears then that is the problem.

# Upside down

My second explanation is much more likely to be the case. It is also based on the observation that the rays reflected from the suspicious patches hit the ground in a direction pointing almost exactly down. I noticed a bit strange setup of your plane in your code. It has its normal pointing down!?

```py
self.normal = Vec3(0,-1,0)

```

I am not sure how the Lambert shading of the ground works then (should be then all just ambient in fact), but assuming `Vec(0,1,0)` is your up then the intersection will in fact yield `None`

```py
denom = ray.dot(self.normal) #something like 1e-6 in the suspicious area
if abs(denom) > 0.0001: #evaluates to false
    #... true branch ignored
return None #false branch activated, returns no intersection

```

Having no intersection means that the hit point is shaded only with a constant color

> ```py
> if closest_object == None:
>    return Vec3(173/255, 216/255, 230/255)
>
> ```

So to test this explanation, just change the returned color for this case to black and see if the patch darkens.

```py
if closest_object == None:
    return Vec3(0, 0, 0)

```

I hope that helps. Please let me know the results.

# Update

After the author confirmed my second explanation I found the reason in the code. The source of the problem is in the ray-plane intersection method.

**Most important of all the epsilon in the comparison should be omitted, since that is causing the patch to be discontinuous.** As mentioned earlier, the incoming ray direction is almost `Vec3(0,-1,0)`. You have set your plane normal to exactly `Vec3(0,-1,0)` so the dot product yields something very close to `0`.

So the quick fix would be:

```py
denom = ray.dot(self.normal)
if abs(denom) >= 0: #omit the epsilon
    #...

```

Your plane normal is inconsistent with how a normal is usually defined. I believe your plane is not supposed to face down. It would be much better and correct to have the plane normal pointing **up** and then testing for a **negative** dot product.

A front-face hit occurs when the face normal and the ray are pointing in opposite directions, i.e. their angle `θ` is smaller than `-π/2` or larger than `π/2`, so `cos(θ) < 0`. Note that `denom` is in fact `cos(θ)`. If `|θ| < π/2`, i.e. `cos(θ) > 0`, then it is a back-face hit. You ignore such hits for now but once you add transparency to your raycaster you will need them as well.

The correct way would be:

```py
def __init__(self, color, y=0, specular=-1, reflective=0):
    # ...
    self.normal = Vec3(0,1,0) #pointing UP

    def intersect_at_point(self, origin, ray):
        denom = ray.dot(self.normal) # denom == cos(θ)

        if abs(denom) <= 0: #is negative <=> |θ| > π/2; omit the epsilon
            diff  = self.center - ray
            t = diff.dot(self.normal) / denom

            if t > 0: #I suggest to omit the epsilon here as well
                return t
        return None

```

> **Comments:**
> **username_1**: Glad to hear that! Oh and now I see what you are doing wrong in the plane! I will add that to my answer.  
> **username_0**: my y is -ve upwards, even then I changed the plane to `Vec3(0,1,0)` the black patch remains. I think I will rewrite the code with the correct axes. This is my first program in graphics programming so a bit confused with something that should be very obvious.  
> **username_1**: Nope, the reason is the epsilon in the `denom` check. Changing the normal is just for correctness and then the `denom` check needs to be inverted as well.  

---

### Answer by username_2 on 2020-05-14. Score: 0

It looks like the light and colour calculation for the sphere reflection is peaking and may need to be clipped to the range 0-255. [This](https://m.youtube.com/watch?v=vE5c2hTRLZM&list=PLHm_I0tE5kKPPWXkTTtOn8fkcwEGZNETh&index=7 "Raytracer from scratch in C++") video around 1:21:45 mark shows an example of this.


---

