code wiki / _hdl_build / nx_gx8_ssao_gate.nx
nx_gx8_ssao_gate.nx source
↩ module page · 208 lines · 8924 B
1// nx_gx8_ssao_gate.nx -- Gx-8: AMBIENT OCCLUSION, the contact cue that stops everything floating.
2// Direct lighting alone carries no proximity information, so a shape resting ON a surface and one hovering
3// above it shade identically -- which is why our figures and beacons read as pasted onto the scene rather
4// than sitting in it. sg_ssao adds cavity darkening from the finished depth buffer.
5// THE TEST THAT MATTERS is the neg-control: a naive depth-only SSAO greys out every receding floor, because
6// downhill neighbours are always nearer. This gate renders a big SLOPED PLANE and requires AO to leave it
7// essentially untouched, while requiring the crevice-rich humanoid to darken measurably. Passing ONE of
8// those is easy; passing BOTH is the whole point of the opposite-pair sampling.
9// license_tier: ORIGINAL expect_exit: 0
10import "nx_swgpu.nx"
11import "nx_png_write.nx"
12
13func ao_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
14func ao_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 }
15
16func ao_fb_rgb(base: i64, dst: *u8) -> i64 {
17 let fb: *i64 = sg_fb(base)
18 var p: i64 = 0
19 while p < ww()*hh() {
20 let v: i64 = fb[p]
21 dst[p*3] = (v % 256) as u8
22 dst[p*3+1] = ((v/256) % 256) as u8
23 dst[p*3+2] = ((v/65536) % 256) as u8
24 p = p + 1
25 }
26 return 0
27}
28
29// a large plane receding from the camera -- the surface a depth-only SSAO would wrongly grey out
30func ao_build_plane(base: i64) -> i64 {
31 let px: *i64 = (base + O_PX) as *i64
32 let py: *i64 = (base + O_PY) as *i64
33 let pz: *i64 = (base + O_PZ) as *i64
34 let nx: *i64 = (base + O_NX) as *i64
35 let ny: *i64 = (base + O_NY) as *i64
36 let nz: *i64 = (base + O_NZ) as *i64
37 let ta: *i64 = (base + O_TA) as *i64
38 let tn: *i64 = (base + O_TN) as *i64
39 let vc: *i64 = (base + O_VC) as *i64
40 sg_reset(base)
41 // 12x12 grid so it is finely tessellated (many depth steps) yet perfectly planar
42 let SIDE: i64 = 13
43 var j: i64 = 0
44 while j < SIDE {
45 var i: i64 = 0
46 while i < SIDE {
47 let vi: i64 = j*SIDE + i
48 px[vi] = ((0-24) + i*4) * 4096
49 py[vi] = 0 - 9000
50 pz[vi] = (6 + j*4) * 4096
51 nx[vi] = 0; ny[vi] = 4096; nz[vi] = 0
52 i = i + 1
53 }
54 j = j + 1
55 }
56 vc[0] = SIDE*SIDE
57 var t: i64 = 0
58 j = 0
59 while j < SIDE-1 {
60 var i2: i64 = 0
61 while i2 < SIDE-1 {
62 let v00: i64 = j*SIDE + i2
63 let v10: i64 = j*SIDE + i2 + 1
64 let v01: i64 = (j+1)*SIDE + i2
65 let v11: i64 = (j+1)*SIDE + i2 + 1
66 ta[t*3]=v00; ta[t*3+1]=v10; ta[t*3+2]=v11; t=t+1
67 ta[t*3]=v00; ta[t*3+1]=v11; ta[t*3+2]=v10; t=t+1
68 ta[t*3]=v00; ta[t*3+1]=v11; ta[t*3+2]=v01; t=t+1
69 ta[t*3]=v00; ta[t*3+1]=v01; ta[t*3+2]=v11; t=t+1
70 i2 = i2 + 1
71 }
72 j = j + 1
73 }
74 tn[0] = t
75 return t
76}
77
78// mean luminance over pixels that belong to geometry (z-buffer says something was drawn there)
79func ao_mean_lit(base: i64, rgb: *u8, cnt: *i64) -> i64 {
80 let zb: *i64 = sg_zb(base)
81 var sum: i64 = 0
82 var n: i64 = 0
83 var p: i64 = 0
84 while p < ww()*hh() {
85 if zb[p] < ZFAR {
86 sum = sum + (rgb[p*3] as i64) + (rgb[p*3+1] as i64) + (rgb[p*3+2] as i64)
87 n = n + 1
88 }
89 p = p + 1
90 }
91 cnt[0] = n
92 if n == 0 { return 0 }
93 return sum / (n*3)
94}
95
96func main() -> i64 {
97 ao_puts("=== nx_gx8_ssao_gate -- ambient occlusion: contact darkening without greying out the floor ===\n" as *u8)
98 var fails: i64 = 0
99 let base: i64 = sys_mmap(swgpu_bytes()) as i64
100 let noao: *u8 = sys_mmap(ww()*hh()*3+16)
101 let withao: *u8 = sys_mmap(ww()*hh()*3+16)
102 let cnt: *i64 = sys_mmap(8*4) as *i64
103
104 // ================= SUBJECT: the humanoid -- overlapping ellipsoids, i.e. many real crevices ==========
105 sg_humanoid(base, 20)
106 sg_project(base, 500, 0-5)
107 sg_render(base, 240, 182, 158)
108 ao_fb_rgb(base, noao)
109 let meanBefore: i64 = ao_mean_lit(base, noao, cnt)
110 let bodyPx: i64 = cnt[0]
111 sg_ssao(base, 190)
112 ao_fb_rgb(base, withao)
113 let meanAfter: i64 = ao_mean_lit(base, withao, cnt)
114
115 var darkened: i64 = 0
116 var brightened: i64 = 0
117 var p: i64 = 0
118 while p < ww()*hh() {
119 let a: i64 = noao[p*3] as i64
120 let b: i64 = withao[p*3] as i64
121 if b < a - 3 { darkened = darkened + 1 }
122 if b > a + 3 { brightened = brightened + 1 }
123 p = p + 1
124 }
125 ao_puts(" FIGURE: body px="); ao_pn(bodyPx); ao_puts(" mean lum "); ao_pn(meanBefore)
126 ao_puts(" -> "); ao_pn(meanAfter); ao_puts(" darkened px="); ao_pn(darkened)
127 ao_puts(" brightened px="); ao_pn(brightened); ao_puts("\n" as *u8)
128
129 // ---- T1 AO actually darkens the crevice-rich subject ----
130 var t1: i64 = 0
131 if darkened > 500 { if meanAfter < meanBefore { t1 = 1 } }
132 if t1==1 { ao_puts("T1 PASS occlusion darkens the figure's cavities ("); ao_pn(darkened); ao_puts(" px)\n" as *u8) }
133 else { fails=fails+1; ao_puts("T1 FAIL no darkening\n" as *u8) }
134
135 // ---- T2 it ONLY darkens: ambient occlusion must never ADD light ----
136 var t2: i64 = 0
137 if brightened == 0 { t2 = 1 }
138 if t2==1 { ao_puts("T2 PASS occlusion is subtractive only -- zero pixels brightened\n" as *u8) }
139 else { fails=fails+1; ao_puts("T2 FAIL brightened "); ao_pn(brightened); ao_puts(" px\n" as *u8) }
140
141 // ---- T3 it is SELECTIVE, not a global dimmer: most of the figure is untouched ----
142 var t3: i64 = 0
143 if darkened * 100 / bodyPx < 60 { t3 = 1 }
144 ao_puts(" darkened share of the body = "); ao_pn(darkened*100/bodyPx); ao_puts(" percent\n" as *u8)
145 if t3==1 { ao_puts("T3 PASS selective: cavities darken, open surfaces do not (not a global dimmer)\n" as *u8) }
146 else { fails=fails+1; ao_puts("T3 FAIL blanket darkening\n" as *u8) }
147
148 // ================= NEG-CONTROL: a flat receding plane must come out essentially UNTOUCHED ==========
149 ao_build_plane(base)
150 sg_project(base, 0, 0)
151 sg_render(base, 150, 150, 155)
152 ao_fb_rgb(base, noao)
153 let planePx: i64 = 0
154 ao_mean_lit(base, noao, cnt)
155 let planeCount: i64 = cnt[0]
156 sg_ssao(base, 190)
157 ao_fb_rgb(base, withao)
158 var planeDark: i64 = 0
159 p = 0
160 while p < ww()*hh() {
161 let a: i64 = noao[p*3] as i64
162 let b: i64 = withao[p*3] as i64
163 if b < a - 3 { planeDark = planeDark + 1 }
164 p = p + 1
165 }
166 ao_puts(" PLANE (neg-control): surface px="); ao_pn(planeCount); ao_puts(" darkened px="); ao_pn(planeDark)
167 if planeCount > 0 { ao_puts(" = "); ao_pn(planeDark*100/planeCount); ao_puts(" percent\n" as *u8) } else { ao_puts("\n" as *u8) }
168
169 // ---- T4 THE FALSE-POSITIVE TEST: a receding plane is not a cavity and must not be shaded ----
170 var t4: i64 = 0
171 if planeCount > 10000 { if planeDark * 100 / planeCount < 5 { t4 = 1 } }
172 if t4==1 { ao_puts("T4 PASS receding plane left alone -- the opposite-pair test rejects slopes (a depth-only SSAO fails here)\n" as *u8) }
173 else { fails=fails+1; ao_puts("T4 FAIL plane wrongly occluded\n" as *u8) }
174
175 // ---- artifact: figure without AO | figure with AO ----
176 sg_humanoid(base, 20)
177 sg_project(base, 500, 0-5)
178 sg_render(base, 240, 182, 158)
179 ao_fb_rgb(base, noao)
180 sg_ssao(base, 190)
181 ao_fb_rgb(base, withao)
182 let CW: i64 = 1028
183 let comp: *u8 = sys_mmap(CW*hh()*3+16)
184 var y2: i64 = 0
185 while y2 < hh() {
186 var x2: i64 = 0
187 while x2 < CW {
188 let o: i64 = (y2*CW+x2)*3
189 if x2 < ww() { let s: i64=(y2*ww()+x2)*3; comp[o]=noao[s]; comp[o+1]=noao[s+1]; comp[o+2]=noao[s+2] }
190 else { if x2 >= ww()+4 { let s2: i64=(y2*ww()+(x2-ww()-4))*3; comp[o]=withao[s2]; comp[o+1]=withao[s2+1]; comp[o+2]=withao[s2+2] }
191 else { comp[o]=200 as u8; comp[o+1]=90 as u8; comp[o+2]=110 as u8 } }
192 x2 = x2 + 1
193 }
194 y2 = y2 + 1
195 }
196 nx_png_write_rgb("knowledge/nx_gx8_ssao.png\x00" as *u8, comp, CW, hh())
197 let szp: *i64 = sys_mmap(16) as *i64
198 let rb: *u8 = sys_read_file("knowledge/nx_gx8_ssao.png\x00" as *u8, szp)
199 var t5: i64 = 0
200 if (rb as i64)!=0 { if szp[0]>1000 { t5=1 } }
201 if t5==1 { ao_puts("T5 PASS artifact written (no-AO | AO), "); ao_pn(szp[0]); ao_puts(" bytes\n" as *u8) }
202 else { fails=fails+1; ao_puts("T5 FAIL png\n" as *u8) }
203
204 if fails==0 { ao_puts("GX8-SSAO GREEN -- cavities darken, open surfaces and receding planes do not, occlusion never adds light\n" as *u8); sys_exit(0); return 0 }
205 ao_puts("GX8-SSAO RED fails="); ao_pn(fails); ao_puts("\n" as *u8)
206 sys_exit(1)
207 return 1
208}