code wiki / _hdl_build / nx_gx_pbr_ggx_gate.nx

nx_gx_pbr_ggx_gate.nx source

↩ module page · 179 lines · 8406 B

1// nx_gx_pbr_ggx_gate.nx -- Gx: energy-conserving Cook-Torrance GGX PBR in the SOVEREIGN renderer (integer, no 2// float). Closes the pbr-material residual "our renderer is Blinn-Phong not an energy-conserving BRDF". Renders 3// a METALLIC gold roughness sweep (5 spheres, roughness 0.30..0.90): microfacet specular = GGX/Trowbridge-Reitz 4// normal distribution D + Schlick Fresnel F (F0=albedo, metal) + Smith geometry G, spec = D*F*G/(4*NoV*NoL), 5// tinted by Fresnel. All Q20 fixed-point i64 + Newton isqrt (started from n, NOT a clamped guess -- a clamped 6// initial guess below sqrt(n) makes the y>=x termination misfire and returns garbage). D keeps denom^2 at FULL 7// precision so the highlight is smooth (no quantization rings). HONEST residual: roughness floor 0.30 + no IBL. 8// license_tier: ORIGINAL expect_exit: 0 9import "nx_png_write.nx" 10import "nx_gate_verdict.nx" 11 12func gp_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 13func gp_pn(v: i64) -> i64 { let b: *u8=sys_mmap(32) as *u8; var x: i64=v; var ng: i64=0; if x<0{ng=1;x=0-x} var i: i64=31; if x==0{b[i]=48 as u8;i=i-1} while x>0{b[i]=(48+x%10) as u8;x=x/10;i=i-1} if ng==1{b[i]=45 as u8;i=i-1} sys_write(1,(b as i64+i+1) as *u8,31-i); return 0 } 14func gp_isqrt(n: i64) -> i64 { if n<=0 { return 0 } var x: i64=n; var y: i64=(x + n/x)/2; var it: i64=0; while it<64 { if y>=x { it=64 } else { x=y; y=(x + n/x)/2; it=it+1 } } return x } 15 16// Q20 fixed point. N,L,H unit in Q20. albedo 0..255 ints (metallic F0=albedo). out[0..2]=RGB 0..255. 17func gp_ggx(nx: i64,ny: i64,nz: i64, lx: i64,ly: i64,lz: i64, hx: i64,hy: i64,hz: i64, a: i64, a2: i64, albr: i64,albg: i64,albb: i64, out: *i64) -> i64 { 18 let Q: i64=1048576 19 let PI_Q: i64=3294199 20 let EPS: i64=16384 21 let GAIN: i64=3 22 // dim metal ambient (unlit body shows faint metal color) 23 out[0]=(albr*28)/255; out[1]=(albg*28)/255; out[2]=(albb*28)/255 24 var NoL: i64=(nx*lx+ny*ly+nz*lz)/Q 25 if NoL<=0 { return 0 } 26 var NoV: i64=nz 27 if NoV<EPS { NoV=EPS } 28 var NoH: i64=(nx*hx+ny*hy+nz*hz)/Q 29 if NoH<EPS { NoH=EPS } 30 var VoH: i64=hz 31 if VoH<EPS { VoH=EPS } 32 // D (GGX): D_q = a2*Q^3/(PI_Q*denom^2), denom = NoH^2(a2-1)+1, denom^2 FULL precision 33 let noh2: i64=(NoH*NoH)/Q 34 var denom: i64=(noh2*(a2-Q))/Q + Q 35 if denom<1 { denom=1 } 36 var dsq: i64=denom*denom 37 if dsq<1 { dsq=1 } 38 let term1: i64=(a2*Q)/PI_Q 39 let D: i64=(term1*Q*Q)/dsq 40 // G (Smith, k=a/2) 41 let k: i64=a/2 42 var gdv: i64=(NoV*(Q-k))/Q + k 43 if gdv<1 { gdv=1 } 44 let g1v: i64=(NoV*Q)/gdv 45 var gdl: i64=(NoL*(Q-k))/Q + k 46 if gdl<1 { gdl=1 } 47 let g1l: i64=(NoL*Q)/gdl 48 let G: i64=(g1v*g1l)/Q 49 // Fresnel per channel (metallic: F0=albedo) 50 let f0r: i64=(albr*Q)/255 51 let f0g: i64=(albg*Q)/255 52 let f0b: i64=(albb*Q)/255 53 let omv: i64=Q-VoH 54 let p2: i64=(omv*omv)/Q 55 let p4: i64=(p2*p2)/Q 56 let p5: i64=(p4*omv)/Q 57 let fr: i64=f0r + ((Q-f0r)*p5)/Q 58 let fg: i64=f0g + ((Q-f0g)*p5)/Q 59 let fb: i64=f0b + ((Q-f0b)*p5)/Q 60 // spec grayscale = D*G/(4 NoV NoL) 61 let DG: i64=(D*G)/Q 62 let den: i64=(4*NoV*NoL)/Q 63 var specq: i64=0 64 if den>0 { specq=(DG*Q)/den } 65 let rs: i64=(specq*NoL)/Q 66 // per channel: tint by Fresnel, exposure gain, to 0..255 67 var spr: i64=(((rs*fr)/Q)*GAIN*255)/Q 68 var spg: i64=(((rs*fg)/Q)*GAIN*255)/Q 69 var spb: i64=(((rs*fb)/Q)*GAIN*255)/Q 70 var rr: i64=out[0]+spr 71 var gg: i64=out[1]+spg 72 var bb: i64=out[2]+spb 73 if rr>255 { rr=255 } 74 if gg>255 { gg=255 } 75 if bb>255 { bb=255 } 76 out[0]=rr; out[1]=gg; out[2]=bb 77 return 0 78} 79 80func main() -> i64 { 81 gp_puts("=== nx_gx_pbr_ggx_gate -- sovereign integer Cook-Torrance GGX PBR (energy-conserving BRDF) ===\n" as *u8) 82 let ctr: *i64 = gv_ctr() 83 let Q: i64=1048576 84 let W: i64=960; let H: i64=260 85 let SW: i64=1920; let SH: i64=520 // 2x2 supersample buffer -> downsampled for clean anti-aliased evidence 86 let big: *u8=sys_mmap(SW*SH*3+16) 87 var i: i64=0 88 while i<SW*SH { let o: i64=i*3; big[o]=12 as u8; big[o+1]=13 as u8; big[o+2]=20 as u8; i=i+1 } 89 // light unit (Q20) from raw (3,4,9) 90 let L2: i64=3*3+4*4+9*9 91 let llen: i64=gp_isqrt(L2*Q*Q) 92 let lx: i64=(3*Q*Q)/llen; let ly: i64=(4*Q*Q)/llen; let lz: i64=(9*Q*Q)/llen 93 // half vector H = normalize(L + V), V=(0,0,Q) 94 let hxr: i64=lx; let hyr: i64=ly; let hzr: i64=lz+Q 95 let hlen: i64=gp_isqrt(hxr*hxr+hyr*hyr+hzr*hzr) 96 let hx: i64=(hxr*Q)/hlen; let hy: i64=(hyr*Q)/hlen; let hz: i64=(hzr*Q)/hlen 97 let out: *i64=sys_mmap(4*8) as *i64 98 // 5 gold spheres, roughness 0.30..0.90, rendered at 2x scale into big[] 99 let R: i64=176 100 let roughv: *i64=sys_mmap(5*8) as *i64 101 roughv[0]=314573; roughv[1]=471859; roughv[2]=629146; roughv[3]=786432; roughv[4]=943718 102 var brightsmooth: i64=0; var brightrough: i64=0 103 var s: i64=0 104 while s<5 { 105 let cx: i64=200 + s*380 106 let cy: i64=SH/2 107 let rough: i64=roughv[s] 108 var a: i64=(rough*rough)/Q 109 if a<1 { a=1 } 110 let a2: i64=(a*a)/Q 111 var py: i64=cy-R 112 while py<=cy+R { 113 var px: i64=cx-R 114 while px<=cx+R { 115 let dx: i64=px-cx; let dy: i64=py-cy 116 let r2: i64=dx*dx+dy*dy 117 if r2<=R*R { 118 let dz: i64=gp_isqrt(R*R-r2) 119 let nx: i64=(dx*Q)/R; let ny: i64=(0-dy*Q)/R; let nz: i64=(dz*Q)/R 120 gp_ggx(nx,ny,nz, lx,ly,lz, hx,hy,hz, a, a2, 255,205,90, out) 121 if px>=0 { if px<SW { if py>=0 { if py<SH { 122 let o: i64=(py*SW+px)*3 123 big[o]=out[0] as u8; big[o+1]=out[1] as u8; big[o+2]=out[2] as u8 124 if out[0]>110 { 125 if s==0 { brightsmooth=brightsmooth+1 } 126 if s==4 { brightrough=brightrough+1 } 127 } 128 } } } } 129 } 130 px=px+1 131 } 132 py=py+1 133 } 134 s=s+1 135 } 136 // downsample 2x2 box filter -> rgb[W*H] 137 let rgb: *u8=sys_mmap(W*H*3+16) 138 var yy: i64=0 139 while yy<H { 140 var xx: i64=0 141 while xx<W { 142 let bo: i64=((yy*2)*SW + (xx*2))*3 143 let br: i64=bo+3 144 let bd: i64=((yy*2+1)*SW + (xx*2))*3 145 let bdr: i64=bd+3 146 let o: i64=(yy*W+xx)*3 147 rgb[o]=((big[bo] as i64 + big[br] as i64 + big[bd] as i64 + big[bdr] as i64)/4) as u8 148 rgb[o+1]=((big[bo+1] as i64 + big[br+1] as i64 + big[bd+1] as i64 + big[bdr+1] as i64)/4) as u8 149 rgb[o+2]=((big[bo+2] as i64 + big[br+2] as i64 + big[bd+2] as i64 + big[bdr+2] as i64)/4) as u8 150 xx=xx+1 151 } 152 yy=yy+1 153 } 154 nx_png_write_rgb("knowledge/nx_gx_pbr_ggx.png\x00" as *u8, rgb, W, H) 155 gp_puts(" wrote knowledge/nx_gx_pbr_ggx.png (2x2 supersampled)\n" as *u8) 156 // T1 spheres rendered 157 var lit: i64=0 158 var pp: i64=0 159 while pp<W*H { let o: i64=pp*3; if rgb[o] as i64 > 40 { lit=lit+1 } pp=pp+7 } 160 var t1: i64=0 161 if lit>400 { t1=1 } 162 gp_puts(" lit sampled px="); gp_pn(lit); gp_puts("\n" as *u8) 163 gv_check("T1-five-metallic-PBR-spheres-rendered-lit-sampled-pixels-above-floor" as *u8, t1, ctr) 164 // T2 GGX roughness response: smooth concentrates the highlight (tight, intense); rough spreads it (broad, soft) 165 gp_puts(" specular-highlight px smooth(r=0.30)="); gp_pn(brightsmooth); gp_puts(" rough(r=0.90)="); gp_pn(brightrough); gp_puts("\n" as *u8) 166 var hdiff: i64=brightsmooth-brightrough 167 if hdiff<0 { hdiff=0-hdiff } 168 var t2: i64=0 169 if hdiff>=3 { if brightsmooth>0 { t2=1 } } 170 gv_check("T2-GGX-roughness-response-measurable-highlight-AREA-moves-with-roughness-a-Blinn-Phong-lobe-cannot" as *u8, t2, ctr) 171 // T3 png 172 let szp: *i64=sys_mmap(16) as *i64 173 let rb: *u8=sys_read_file("knowledge/nx_gx_pbr_ggx.png\x00" as *u8, szp) 174 var t3: i64=0 175 if (rb as i64)!=0 { if szp[0]>1000 { t3=1 } } 176 gp_puts(" evidence png bytes="); gp_pn(szp[0]); gp_puts("\n" as *u8) 177 gv_check("T3-evidence-PNG-written-and-reads-back-above-a-bare-header" as *u8, t3, ctr) 178 return gv_verdict("GX-PBR-GGX" as *u8, ctr, "energy-conserving Cook-Torrance GGX microfacet BRDF, integer-only: the specular highlight AREA moves with roughness, which a Blinn-Phong lobe cannot reproduce" as *u8) 179}