I am trying to implement a simple shader. A round ball object is rendered with uniform albedo and I noticed some strange white spots on the outputs. I perform importance sampling with 512 light directions on the cosine-weighted hemisphere to render the scene. For debugging purposes, I dropped the specular component from the visualization and only show the albedo*light component:

enter image description here

[higher resolution]

The spots roughly align with the position of the sun in the env map. From what I have understood the issue is that some values in the hdr are very high (up to ~8384, 12% of the environment map pixels have a value over 1). So if one of the very high values is sampled, then the resulting average light intensity will be very high. However, I don't see why the resulting diffuse color is sparse like this.

From this render, I expect to see a uniform color with a bright side on the left of the sphere. Could this issue be fixed with a more advanced sampling method based on the env map intensity, or am I missing something with the current approach?

For additional reference, I use the following python code to query the env map:

def get_light(self, incident_dir, envir_map):
        envir_map: shape (1, 3, H, W)
        incident_dir: (3, point_num*samples_num)
    # convert from cartesian to spherical
    x,y,z = incident_dir
    phi = torch.arccos(z).flatten() - 1e-6
    theta = torch.atan2(y, x).flatten()

    # normalize to [-1, 1]
    query_y = (phi / np.pi) * 2 - 1
    query_x = - theta / np.pi

    # sampling grid is shaped (1, 1, pn*sn, 2)
    grid = torch.stack((query_x, query_y)).permute(1, 0).unsqueeze(0).unsqueeze(0)
    # interpolate to query env map, 
    sampled = F.grid_sample(envir_map, grid, align_corners=True) # [1, 3, 1, pn*sn]
    light_rgbs = sampled.squeeze().permute(1, 0).reshape(pn, -1, 3) # [pn, sn, 3]
    return light_rgbs

The environment map looks like so:

enter image description here


1 Answer 1


I think this question lacks some additional information. I used your code to implement a simple diffusive color visualizer, and found nothing strange:

'Guassian' surface Lambertian surface
enter image description here enter image description here

(Note that, Guassian surface means that the outgoing ray samples is subject to a normal distribution around the normal vector, Lambertian surface just means the cosine-weighted sampling used by you).

These images are the surface reflected color (cosine-weighted) visualized as a 2D image (mapped to theta and phi). The code can be found here: Gist: env_map.py, simply run this code with:

python3 ./env_map.py

and you can get these results. So I have no idea why these white spots are there, it seems to me that the code given works, even when I modify how the theta and phi are computed (in case there is something wrong with your coordinate frame).

My suggestion is: input other environment maps to see what will happen to the diffuse component. For example, use an environment map with only one white circle and the other parts are black. More inputs get you more information.

Update: I implemented the Hammersley sequence based 'sampling' you mentioned in the comment in my code and the result is like the following. I'm not sure if I get this correctly, and the code is here: hammersley.cc, to use it, you can follow the comment in that gist. The original code is also updated.

enter image description here

There are indeed some artifacts, but not the kind you are looking for (if I get it right).

  • $\begingroup$ Hi and thank you for your answer. I have looked at your code and could not find any specific element that was different from my code. I think the difference is that I am using a hdr env map while the one that I provided in the question is already clamped, which means the problem doesn't appear. $\endgroup$
    – Ivan
    Aug 19, 2023 at 12:16
  • $\begingroup$ Another thread caught my attention: the spots come from the light sampling which is deterministic and yields identical sequences of rays for each fragment (locally speaking)... In turn, close-by-points that have a similar normal will return the same color since the light integral is very close. I have tried again with resampling per fragment, and this gives me this. $\endgroup$
    – Ivan
    Aug 19, 2023 at 12:16
  • $\begingroup$ Let me know if that makes sense. I have found in some references that they sample from a fixed Hammersley sequence instead of sampling with a probabilistic distribution, eg. here. Hence my confusion when I was setting the initial "random" sequence identical to all fragments. Do you know why that is, can this be actually considered valid MC sampling? $\endgroup$
    – Ivan
    Aug 19, 2023 at 12:19
  • $\begingroup$ This would make a pretty good point so I tried this out. It seems that I can't put any image in a comment so I would open the answer (edit). $\endgroup$ Aug 19, 2023 at 15:24
  • $\begingroup$ It looks like you only changed the shader to gaussian in your gist, correct? I found from here that "sampling" from Hammersley ensures a better distribution of sampling directions than when using a uniform generator. What I don't understand is that this sequence doesn't have a random seed so it remains identical accross fragments. For instance here $\endgroup$
    – Ivan
    Aug 19, 2023 at 21:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.