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}