Clearly the code was written during the time I did not fully understand the essence of MC path tracing. Starting from line 102 in emitters/abstract_source.py
:
if is_sphere:
tri_id = prim_info[self.obj_ref_id, 0]
center = dvs[tri_id, 0]
radius = dvs[tri_id, 1][0]
to_hit = (hit_pos - center).normalized()
local_dir, pdf = cosine_hemisphere()
normal, _ = delocalize_rotate(to_hit, local_dir)
ret_pos = center + normal * radius
# We only choose the hemisphere, therefore we have a 0.5. Also, this is both sa & area measure
ret_pdf = 0.5 * pdf
The normal (defining the final position on the sphere), is sampled locally and transformed around the hit pos. First, we can't have something being both area and solid angle measure unless the geometric term is exactly 1. Second, why should we have 0.5 here? We are sampling the same hemisphere with proba 1, i.e, the whole proba (consists of a conditional proba, which means that given the condition that we sample the same hemisphere, the proba of sampling the point, and the a marginal proba, meaning the proba of sampling the same hemisphere, which should be 1 here).
Also, there should be geometric term, now I think this comment made by me is totally bullshit:
We only choose the hemisphere, therefore we have a 0.5. Also, this is both sa & area measure
Yeah, cosine_hemisphere
will produce PDF of which the measure is solid angle, but this PDF is still not what we are looking for! We need to now the PDF for the point that reflects the light ray and hit the diffusive area emitter.
The best (and also, the simplest) practice for this sampling problem is to use the current vertex position and the sphere, get the tangent (angles) for the sphere and do a uniform sampling (for polar angles), convert the result to the solid angle measure.