code wiki / _hdl_build / nx_gx_pbr_ggx_gate.nx

nx_gx_pbr_ggx_gate.nx source

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