Skip to content

PathTraceGltfGpuSmoke.kt

src/jvmMain/kotlin/afield/graphics/scene/fabricated/PathTraceGltfGpuSmoke.kt · 61 lines

package afield.graphics.scene.fabricated

import afield.graphics.vulkan.Renderer
import afield.document.context.*
import afield.graphics.render.*
import afield.graphics.scene.*
import afield.graphics.shader.*
import afield.graphics.vulkan.util.Camera
import afield.math.linalg.Vector4f
import kotlin.math.abs
import kotlin.math.pow

object PathTraceGltfGpuSmoke

Known texture values checked through actual GPU traversal, material evaluation and accumulation.

suspend fun run(renderer: Renderer, backend: PathTracingRenderer.Backend)

body — 46 lines, from line 15
    suspend fun run(renderer: Renderer, backend: PathTracingRenderer.Backend) {
        val camera=Camera(renderer).also { it.at.set(0f,0f,3f);it.target.set(0f,0f,0f);it.p.fov=0.6 }
        val bytes=listOf(intArrayOf(128,64,32),intArrayOf(32,128,64),intArrayOf(64,32,128),intArrayOf(255,255,255))
        val alpha=floatArrayOf(0f,0.5f,128f/255f*0.5f,0.5f)
        fun linear(v: Int): Float { val c=v/255f;return if(c<=0.04045f)c/12.92f else ((c+0.055f)/1.055f).pow(2.4f) }
        for (mode in PathTraceMaterial.AlphaMode.entries) {
            val scene=PathTraceScene()
            scene.importGltf("dev_docs/agents/fixtures/path_tracing/quad.glb",material={ _,m -> m.copy(alphaMode=mode,alphaCutoff=0.3f) })
            PathTracingRenderer(renderer,scene,PathTraceEnvironment.constant(0f),24,24,camera,backend).use { tracer ->
                tracer.maxBounces=1;tracer.samplesPerFrame=16;tracer.showEnvironment=false;tracer.background=Vector4f(0f)
                tracer.install();PathTraceGpuSmoke.awaitSamples(tracer,1024);tracer.paused=true
                val pixels=tracer.readLinear()
                for(q in 0..3) {
                    val x=if(q%2==0)6 else 18;val y=if(q<2)6 else 18;val index=(y*24+x)*4
                    val coverage=when(mode) { PathTraceMaterial.AlphaMode.OPAQUE->1f;PathTraceMaterial.AlphaMode.MASK->if(alpha[q]>=0.3f)1f else 0f;PathTraceMaterial.AlphaMode.BLEND->alpha[q] }
                    val tolerance=if(mode==PathTraceMaterial.AlphaMode.BLEND)0.035f else 1e-5f
                    for(c in 0..2) check(abs(pixels[index+c]-linear(bytes[q][c])*coverage)<tolerance) {
                        "$mode quad=$q channel=$c expected=${linear(bytes[q][c])*coverage} actual=${pixels[index+c]}"
                    }
                    check(abs(pixels[index+3]-coverage)<tolerance) { "$mode coverage ${pixels[index+3]} vs $coverage" }
                }
                println("PASS GPU $backend glTF $mode: texture orientation, sRGB emission and alpha coverage")
            }
        }
        val scene=PathTraceScene.fromGltf("dev_docs/agents/fixtures/path_tracing/quad.gltf")
        for (normal in listOf(false,true)) {
            val hook=if(normal) "material.emission.rgb = surface.normal*0.5+0.5;" else "material.emission.rgb = vec3(material.base.w,material.emission.w,material.base.r);"
            PathTracingRenderer(renderer,scene,PathTraceEnvironment.constant(0f),24,24,camera,backend,
                moduleFactory={ p ->
                    p.compute=p.compute.replace("Material shadeMaterial(Material material, PathTraceSurface surface) { return material; }",
                        "Material shadeMaterial(Material material, PathTraceSurface surface) { $hook return material; }")
                    ComputeModule(p).pointer()
                }).use { tracer ->
                tracer.maxBounces=1;tracer.install();PathTraceGpuSmoke.awaitSamples(tracer,8);tracer.paused=true
                val pixels=tracer.readLinear();val offset=(6*24+6)*4
                val n=afield.math.linalg.Vector3f(128f/255f*2-1,-(204f/255f*2-1),230f/255f*2-1).normalize()
                val expected=if(normal) floatArrayOf(n.x*.5f+.5f,n.y*.5f+.5f,n.z*.5f+.5f)
                    else floatArrayOf(.8f*64f/255f,.6f*128f/255f,.8f*.5f*linear(128))
                for(c in 0..2) check(abs(pixels[offset+c]-expected[c])<0.002f) {
                    "glTF normal=$normal channel=$c expected=${expected[c]} actual=${pixels[offset+c]}"
                }
                println("PASS GPU $backend glTF ${if(normal) "normal map" else "metallic/roughness and vertex color"}")
            }
        }
    }
}