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}