Negrini085 / PhotoNim

PhotoNim: a CPU raytracer with BVH optimization based on kmeans clustering
https://negrini085.github.io/PhotoNim/
GNU General Public License v3.0
4 stars 1 forks source link

Weighting the radiance in the rendering equation with BRDF.eval #61

Closed lorenzoliuzzo closed 4 months ago

lorenzoliuzzo commented 5 months ago
proc eval*(brdf: BRDF; surfacePoint: Point2D, normal, inDir, outDir: Vec3f): float32 {.inline.} =
    case brdf.kind: 
    of DiffuseBRDF: return brdf.reflectance / PI

    of SpecularBRDF: return if abs(arccos(dot(normal, inDir)) - arccos(dot(normal, outDir))) < brdf.thresholdAngle: 1.0 else: 0.0

proc sampleRay(camera: Camera; sceneTree: SceneNode, worldRay: Ray, bgColor: Color, rg: var PCG): Color =
    result = bgColor

    let hitLeafNodes = sceneTree.getHitLeafs(worldRay)
    if hitLeafNodes.isNone: return result

    case camera.renderer.kind
    of rkOnOff: ...
    of rkFlat: ...
    of rkPathTracer: 
        if (worldRay.depth > camera.renderer.maxDepth): return BLACK

        let hitRecord = hitLeafNodes.get.getHitRecord(worldRay)
        if hitRecord.isNone: return result

        let                
            closestHit = hitRecord.get.sorted(proc(a, b: HitPayload): int = cmp(a.t, b.t))[0]

            shapeLocalHitPt = closestHit.ray.at(closestHit.t)
            shapeLocalHitNormal = closestHit.handler.shape.getNormal(shapeLocalHitPt, closestHit.ray.dir)

            surfacePt = closestHit.handler.shape.getUV(shapeLocalHitPt)

        result = closestHit.handler.shape.material.radiance.getColor(surfacePt)

        var hitCol = closestHit.handler.shape.material.brdf.pigment.getColor(surfacePt)
        if worldRay.depth >= camera.renderer.rouletteLimit:
            let q = max(0.05, 1 - hitCol.luminosity)
            if rg.rand > q: hitCol /= (1.0 - q)
            else: return result

        if hitCol.luminosity > 0.0:
            var accumulatedRadiance = BLACK
            for _ in 0..<camera.renderer.numRays:
                let 
                    localInDir = closestHit.ray.dir.normalize
                    localOutDir = closestHit.handler.shape.material.brdf.scatterDir(shapeLocalHitNormal, localInDir, rg).normalize
                    scatteredRay = Ray(
                        origin: apply(closestHit.handler.transformation, shapeLocalHitPt), 
                        dir: apply(closestHit.handler.transformation, localOutDir),
                        tSpan: (1e-5.float32, Inf.float32),
                        depth: closestHit.ray.depth + 1
                    )

                hitCol *= closestHit.handler.shape.material.brdf.eval(surfacePt, shapeLocalHitNormal.Vec3f, localInDir, localOutDir)
                accumulatedRadiance += hitCol * camera.sampleRay(sceneTree, scatteredRay, bgColor, rg)

            result += accumulatedRadiance / camera.renderer.numRays.float32
lorenzoliuzzo commented 5 months ago

I think there is a bug also here:

proc newSpecularBRDF*(pigment = newUniformPigment(WHITE), angle = 180.0): BRDF {.inline.} =
    BRDF(kind: SpecularBRDF, pigment: pigment, thresholdAngle: degToRad(angle)) 

If the angle is set to 90, the following demo image is produced: demo90 This is wrong because we cannot see the reflection of the spheres between the central specular one and the camera, but we can see the spheres behind.

However, if angle = 180, we can see the frontal spheres being reflected: demo180

In my opinion, we shouldn't be able to see reflected the spheres behind the central one, at most we should barely see the lateral one.

lorenzoliuzzo commented 5 months ago

If we do not weight using the BRDF.eval, the output with the thresholdAngle set to PI/2 is this: demo

The problem we had before with the thresholdAngle no longer exists since we are no longer calling the function to evaluate the brdf..... However, you can see on the diffuse materials some weird points: these are the points where the rays scattered after an initial hit have hit the shape. The reason why we see them more luminous is because we are just adding the brdf.pigment color to the shape color without any information on how to weight it.

var hitCol = closestHit.handler.shape.material.brdf.pigment.getColor(surfacePt)
lorenzoliuzzo commented 5 months ago

Il tipo BRDF dovrà diventare più complesso di come l’abbiamo implementato oggi (in un path tracer non serve valutare $f_r$, e quindi useremo BRDF.eval per altri scopi!), quindi è meglio non usare scorciatoie..

lorenzoliuzzo commented 5 months ago

Cornell box with 1 samplePerSide - 4 numRays - 3 maxDepth: without BRDF.eval: singlesample_4_3_old

with BRDF.eval: singlesample_4_3_new