From 532580b9bd4f70a6c29b26fa5bf7d15794a510e4 Mon Sep 17 00:00:00 2001 From: Albin Chaboissier Date: Tue, 15 Sep 2026 13:39:56 +0200 Subject: [PATCH] more occup --- shaders/voxel.slang | 159 ++++++++++++++++++++++++-------------------- shaders/voxel.spv | Bin 23904 -> 16948 bytes src/main.rs | 21 +++++- 3 files changed, 108 insertions(+), 72 deletions(-) diff --git a/shaders/voxel.slang b/shaders/voxel.slang index 7cf7cc8..d6a31d3 100644 --- a/shaders/voxel.slang +++ b/shaders/voxel.slang @@ -96,6 +96,11 @@ struct StructurePointer return (this.value & 0x40000000) != 0; } + bool subdivided_valid() + { + return (this.value & 0xC0000000) == 0xC0000000; + } + uint32_t pointer() { return this.value & 0x3FFFFFFF; @@ -193,7 +198,14 @@ struct HitInformation float4 color; } -HitInformation ray_march(float3 ray_direction, float3 ray_origin, uint32_t root_id, float dist_offset) +// struct NodeStack +// { +// uint32_t node_stack[5]; +// } + +groupshared uint32_t stack[256 * 5]; + +HitInformation ray_march(float3 ray_direction, float3 ray_origin, uint32_t root_id, float dist_offset, uint32_t stack_index) { float fov_deg = 100. / 1920.; float fov_rad = (float.getPi() * fov_deg) / 180.; @@ -217,34 +229,34 @@ HitInformation ray_march(float3 ray_direction, float3 ray_origin, uint32_t root_ } ray_origin += float3(1.); + ray_origin = clamp(ray_origin , float(1.), asfloat(0x3fffffff)); + ray_origin = select(ray_direction > 0., asfloat(asuint(ray_origin) ^ 0x007fffff), ray_origin); + uint32_t child_mirror = 0; + if(ray_direction.x > 0.) {child_mirror |= 3;} + if(ray_direction.y > 0.) {child_mirror |= 3 << 2;} + if(ray_direction.z > 0.) {child_mirror |= 3 << 4;} + ray_direction = -abs(ray_direction); + float3 pos = ray_origin; - pos = clamp(pos, float(1.), asfloat(0x3fffffff)); uint32_t scale_exp = 23 - 2; uint32_t node_stack[5] = { 0 }; + uint32_t current_node_index = structure_table_pointer[root_id].pointer(); - - node_stack[10 - scale_exp / 2] = current_node_index; - - uint32_t child_index = get_children_index(pos, scale_exp); - StructurePointer current_node = structure_pool[current_node_index].pointers[child_index]; - usage_buffer[current_node_index] = constants.frame_timestamp; + stack[stack_index * 5 + 10 - scale_exp / 2] = current_node_index; + //node_stack[10 - scale_exp / 2] = current_node_index; - + [loop] for(uint32_t iter = 0; iter < 500; iter ++) { - //scale_exp = 23 - 2; - //current_node_index = structure_table_pointer[root_id].pointer(); - - child_index = get_children_index(pos, scale_exp); - current_node = structure_pool[current_node_index].pointers[child_index]; + uint32_t child_index = get_children_index(pos, scale_exp) ^ child_mirror; + StructurePointer current_node = structure_pool[current_node_index].pointers[child_index]; // Scale computations - //return float4(cone_size * 1000.); let cone_size = (length(ray_origin - pos) + dist_offset) * cone_factor; let exponent = select( @@ -254,15 +266,15 @@ HitInformation ray_march(float3 ray_direction, float3 ray_origin, uint32_t root_ ); while( - current_node.subdivided() && - current_node.pointer_valid() && + current_node.subdivided_valid() && scale_exp - 2 > exponent ) { scale_exp -= 2; current_node_index = current_node.pointer(); - node_stack[10 - scale_exp / 2] = current_node_index; - child_index = get_children_index(pos, scale_exp); + stack[stack_index * 5 + 10 - scale_exp / 2] = current_node_index; + //node_stack[10 - scale_exp / 2] = current_node_index; + child_index = get_children_index(pos, scale_exp) ^ child_mirror; current_node = structure_pool[current_node_index].pointers[child_index]; // Write usage usage_buffer[current_node_index] = constants.frame_timestamp; @@ -276,8 +288,6 @@ HitInformation ray_march(float3 ray_direction, float3 ray_origin, uint32_t root_ if(color_pool[current_node_index].colors[child_index].byte_a != 0) { - //hit_pos = pos - float3(1.); - //return color_pool[current_node_index].colors[child_index].float_color; var hit: HitInformation; hit.hit = true; hit.hit_pos = pos - float3(1.); @@ -285,7 +295,6 @@ HitInformation ray_march(float3 ray_direction, float3 ray_origin, uint32_t root_ return hit; } - //uint64_t occupancy = get_child_mask(structure_pool[current_node_index].occupancy_low, structure_pool[current_node_index].occupancy_high); uint64_t occupancy = get_child_mask(structure_pool[current_node_index].occupancy_low, structure_pool[current_node_index].occupancy_high); uint32_t adv_scale_exp = scale_exp; if(((occupancy >> (child_index & 0b101010)) & 0x00330033) == 0) @@ -295,26 +304,16 @@ HitInformation ray_march(float3 ray_direction, float3 ray_origin, uint32_t root_ // Perform dda // Compute correct exponent, and shift it into the exponent part of floatt - let child_scale : float = asfloat((adv_scale_exp - 23 + 127) << 23); let child_pos : float3 = floor_scale(pos, adv_scale_exp); - let child_far : float3 = child_pos + select(ray_direction > 0., float3(child_scale), float3(0.)); // Intersection t - let inter_ts : float3 = (child_far - ray_origin) / ray_direction; + let inter_ts : float3 = (child_pos - ray_origin) / ray_direction; float inter_t = min(inter_ts.x, min(inter_ts.y, inter_ts.z)); //return float4(inter_t); // Perform dda step - let neighbor_min : float3 = select(float3(inter_t) == inter_ts, child_pos + copysign(child_scale, ray_direction), child_pos); - let neighbor_max : float3 = asfloat(asint(neighbor_min) + ((1 << adv_scale_exp) - 1)); - let previous_pos : float3 = pos; - pos = clamp(ray_origin + ray_direction * inter_t, neighbor_min, neighbor_max); - - /* - if(any(pos >= 2.) || any(pos < 1.)) - { - discard; - } - */ + //let neighbor_max = asint(child_pos) + select(inter_t == inter_ts, -1, (1 << adv_scale_exp) - 1); + let neighbor_max = asint(child_pos) + select(inter_t == inter_ts, -1, (1 << adv_scale_exp) - 1); + pos = min(ray_origin + ray_direction * inter_t, asfloat(neighbor_max)); // Find most common ancestor uint32_t3 diffs = asuint(child_pos) ^ asuint(pos); @@ -327,7 +326,8 @@ HitInformation ray_march(float3 ray_direction, float3 ray_origin, uint32_t root_ } scale_exp = 23 - common_depth; - current_node_index = node_stack[10 - scale_exp / 2]; + current_node_index = stack[stack_index * 5 + 10 - scale_exp / 2]; + //current_node_index = node_stack[10 - scale_exp / 2]; } @@ -342,6 +342,7 @@ struct FragmentOutput float4 color : SV_Target<0>; } +/* //[earlydepthstencil] [shader("fragment")] FragmentOutput fragment(VertexOutput vertex_out) @@ -361,6 +362,7 @@ FragmentOutput fragment(VertexOutput vertex_out) return frag_out; } +*/ bool3 min_mask(float3 val) { @@ -372,31 +374,47 @@ bool3 min_mask(float3 val) [[format("rgba32f")]] WTexture2D output_texture; +float3 get_ray_direction(uint32_t2 pixel_loc) +{ + let ndc_loc_x = (float)pixel_loc.x / (float)constants.width * 2. - 1.; + let ndc_loc_y = 1. - (float)pixel_loc.y / (float)constants.height * 2.; + var world_loc = mul(constants.view_proj, float4(ndc_loc_x, ndc_loc_y, 1., 1.)); + world_loc /= world_loc.w; + return normalize(world_loc.xyz - constants.cam_pos); +} + [shader("compute")] [numthreads(16, 16, 1)] -void ray_march_compute(uint32_t3 location : SV_DispatchThreadID) +void ray_march_compute(uint32_t3 location : SV_DispatchThreadID, uint32_t3 local_location: SV_GroupThreadID) { if(location.x >= constants.width || location.y >= constants.height) { return; } - uint32_t2 pixel_loc = uint32_t2(location.x, location.y); - - let ndc_loc_x = (float)location.x / (float)constants.width * 2. - 1.; - let ndc_loc_y = 1. - (float)location.y / (float)constants.height * 2.; - - var world_loc = mul(constants.view_proj, float4(ndc_loc_x, ndc_loc_y, 1., 1.)); - world_loc /= world_loc.w; - let ray_direction = normalize(world_loc.xyz - constants.cam_pos); - + let stack_index = local_location.x + local_location.y * 16; + let ray_direction = get_ray_direction(location.xy); var inter = box_intersect(constants.cam_pos, ray_direction, float3(0.), float3(constants.chunk_width, constants.chunk_alt, constants.chunk_height)); + + // Clear if out of box if(inter.y <= inter.x || inter.y <= 0.) { - output_texture.Store(pixel_loc, float4(0.)); + output_texture.Store(location.xy, float4(0.)); return; } inter.x = max(0., inter.x); + // float3 position = constants.cam_pos + ray_direction * inter.x; + // int32_t3 current_voxel = clamp( + // int32_t3(floor(position)), + // int32_t3(0), + // int32_t3(constants.chunk_width - 1, constants.chunk_alt - 1, constants.chunk_height - 1) + // ); + // let chunk_index = current_voxel.y + current_voxel.z * constants.chunk_alt + current_voxel.x * constants.chunk_alt * constants.chunk_height; + // let hit = ray_march(ray_direction, position - float3(current_voxel), chunk_index, length(position - constants.cam_pos), stack_index); + // if(hit.hit) + // { + // output_texture.Store(location.xy, float4(hit.color)); + // } let start_position = inter.x * ray_direction + constants.cam_pos; @@ -406,38 +424,37 @@ void ray_march_compute(uint32_t3 location : SV_DispatchThreadID) int32_t3(0), int32_t3(constants.chunk_width - 1, constants.chunk_alt - 1, constants.chunk_height - 1) ); - int32_t3 offset = int32_t3(sign(ray_direction)); - float3 delta = abs(1. / ray_direction); - float3 position = start_position; - float3 t = select(ray_direction > 0., current_voxel + int32_t3(1) - start_position, start_position - current_voxel) * delta; + //int32_t3 offset = int32_t3(sign(ray_direction)); + //float3 delta = abs(1. / ray_direction); + float3 t = select(ray_direction > 0., current_voxel + int32_t3(1) - start_position, start_position - current_voxel) / abs(ray_direction); + t += inter.x; - var color = float4(0.); - var hit_pos = float3(0.); - for(int32_t i = 0; i < 256; i++) + [loop] + while(true) { - let min_mask = min_mask(t); - let t_adv = select(min_mask, t, float3(0.)); - let t_ray = t_adv.x + t_adv.y + t_adv.z; - - - let chunk_index = current_voxel.y + current_voxel.z * constants.chunk_alt + current_voxel.x * constants.chunk_alt * constants.chunk_height; - let hit = ray_march(ray_direction, position - float3(current_voxel), chunk_index, length(position - constants.cam_pos)); - position = start_position + ray_direction * t_ray; - if(hit.hit) { - color = hit.color; - break; + let off = t - abs(1. / ray_direction); + let t_adv = max(max(off.x, max(off.y, off.z)), 0.); + let chunk_index = current_voxel.y + current_voxel.z * constants.chunk_alt + current_voxel.x * constants.chunk_alt * constants.chunk_height; + let position = constants.cam_pos + ray_direction * t_adv; + let hit = ray_march(ray_direction, position - float3(current_voxel), chunk_index, t_adv, stack_index); + if(hit.hit) + { + output_texture.Store(location.xy, hit.color); + return; + } } - t += select(min_mask, delta, float3(0.)); - current_voxel += select(min_mask, offset, int32_t3(0)); + + let min_mask = min_mask(t); + t += select(min_mask, abs(1. / ray_direction), float3(0.)); + current_voxel += select(min_mask, int32_t3(sign(ray_direction)), int32_t3(0)); if(any(current_voxel < 0) || any(current_voxel >= int32_t3(constants.chunk_width, constants.chunk_alt, constants.chunk_height))) { - color = float4(0.); - break; + output_texture.Store(location.xy, float4(0.)); + return; } } - output_texture.Store(pixel_loc, float4(color)); } float2 box_intersect(float3 origin, float3 ray_direction, float3 box_min, float3 box_max) diff --git a/shaders/voxel.spv b/shaders/voxel.spv index 98e889eab1844c8d33e0dc8d8f4f435b7d6eee4d..c406950f0473499f6eef80a570c1d73e0f4e6cce 100644 GIT binary patch literal 16948 zcmZ{q2b@*a5r-cvEL~KvD`HnfEHp*wDmDZ`vAaB$1y+|`+>51vFYZCH(_nlb|DA%3e%>1XEIdjUryI9kCyKbefol2$B&ZYZ1!+16*buRTv zTuV>1=~HLa9eU8Ty0%J7Yk6)%-K_RGa~fJZ)cU4!Tbr@HO0^|pCLK2Yupw=g*+Yg6 z-$Q)8QjM&z^+oe>xcYhR&8NV%c7eJ=wNN*xJJbW}30Y6yQvbr<8@aW-q;7t>wSHb* zearj>?Ue>FbM%4C-WOUQ+5p-R>Ie0QHi9;WHi6b>By;JbuVn2t{O5SeUGZ`JqRzSn zEp3gJ#+GLBs!!u=<=&-D@u{0!uCz8To}OAXr>Uh}8Mb&>-Ncrr$>oz(5` z2>pF#mn&s?S7y5zd}@2!yooK%ZIyC!rH%GhBaEpn?D{TjY*>WOEs3t@Y*k zRtVOu!0hjw*7E#@x=Q2x22##n(BW^d7B$YU%!5Y``#-Otaqhgz>hWBU>fEc|k=r$@ z;t{br#3%1xxALc0YP^D403pruknbES33)RxBP%G5HY))jF>7PYiC&Ca@Q4tLhN z{M#z6?e&%R)`q(3dh{yx&u8aOUFulJ-o-v!hp)SHtvb4_v2UH3`>n$6cMUg3o*vZ{ z^D0hBv5fXvyU)UMQ@eY%E0t~mpLTRd)a(hx*{bGqAFrNWr97*tfqH0HLu(Pcz>iar=`BWeL=aoeo0+Z%Odo!xqnyN=CKK@`^h->ahh{+$Jn#2!9KfZ zwZ&Oq-LnNPElt%v4NTqF>reNj3vVUv zu(rmzT)E7owzLbpzP+{8?X|FFaRWjuIpVfiF4q)#e04b zr0zW*`&3<Xn`8C-Yl^vh*Xe66ZPu;syIQ>_ z>Aqvl?fs8dgOPGaaTqTvFPvGSnLUNho80RXK$^aPlqCZ zcbwJtTtlxZ?!>6y`_U)H(Z3(M_4|aL*S{CI^}FG<4_!<5C6Lc=Ai7VCVcn5GF!Kz@ zC(k=#4ZWu5VbmS<I5iH{RYlmnrapkdOY}!=0e&9J`c`NV++-PfNOSjxV1MIrq@Lx6L2* z9fj-@`V3^p2>oc}&YA8V@B9Yy_OfTkLgp||U%5WY)^r@)Ia+h%JRUi(!FPmrX4p?e zHfAt4u6;fUGOy#9*Zk%%PG8qDY6v{)(MNm4DH~%BW0Z|?Y7%|N>0v)L>5ku@UCSqGajy7$EcTzVp0f&EA9eGz!#hFZ z>I>nHtIsZYnP(B?+KVlQyvNM71TGe5=52 zd2Y^vbaWlgMs&Vn*18mKzr@v-C0!ruTMoB==NjXjgY5YTay|VXavtXJJE%7#wiUL) zN!D)*WOe<{h3h94b2<;1XKCo?SM?721yy}q^yWhJaz+t%5!k9#XLrP1jP4x5ehIQ; zhkhyY9+`d_@~Ty<##-lY$oe`z=QAY9&Sy(xb^R`f>n9d@uRxBw$@nXwp2_aoTm|Xi z+FXsOkC^$cfx9;1>enV+pU8h5@(PU;x~_ zlDOm959g!JdGDEIW4A_D*Y8HSeq!o-B|Y*CO}b-Qw{_hFZIX1yycyzoX%M;HZ?`}( z#}&w(A?Gj*u7Av7AhMV>Z39=ghT(8)60`OZN!D*$WOe;+h3hA#J~HV8lfHM-V~p+4 z9p75^fvZO?+arrP#tv|GbB=;LhM2YNn`HfVL{``DHn@Ib>iZ=<@{CTpV`v|fbZhW^ ze>;2}{@T6`%RQ^oEEizn9L9sth6s>pk9(&J9J4_z!M z;_e6A7z+IXP&~?F#ypZh zeFyU<^3H!0s#}Y~_Sjmue*2PVD!d0|UiBm3p5M@?A@|DkQ;=&QWAtxOjIZuF3VkW^dXQr}-nsC;ko{Blj@ls8y<;|n!r!ypFSC0- z`e(X()cYp#xi+p}==Qx2xffi&C!m#wHji=-Rgj;AF3KqIr@$`G z$n||1-5w8u)SpSZ<3;|@A$y0%yW&}Nv7p$K&w<^T_O^9B4=r1|bl*uyW(z zvFFAke~9*ZxV=39{vzc1sUHOYLZ(kf{$kQcW1oQhCCGW4fj$NP5+tVm5ctbb&!lUA z1#-WgPc7e()nAnt3a$Yl_*yI07xM!jZvT@2?;pSAXg};*IZg6A7yTh&H zzjSqPDDJ4LJsa;r+U3?$fxicBoayf)ZvwfFQ{W#!`uI4n zh42p{=XDHZ9{oOo#2>@QyZ>XzSaECl1n%=O{@VTn{TH$xd5rfzWXJ0S#dxcb9q)K3 z#?wJO#_NP`toTRdbUbrAPOgi;$GTf%XMBxw{6&zy)>;GRW3BphfyB(GY(9IdE*`b} zPt*3#C$QRt!FWGcd)f^}d@)$uul}9Xcx_XtZwa;@$o3-okIFYi^?lkC#r}rg3*BB{ z$Qa&1z0vg%KQCc@ki}2Keon&HLl-ZDMa_NDV+|tz`e5R*CL18Hy(SxiiAPL7WU;8J zKeBnmqo$3J#q*jrMlVy3HplZ_unDr`9R-O!4c`<@+`TRK4165Y^K4H!~ z{B{7dR-eFjOqg(BJ0&d7w{ybse7k@-FQ14VlrZ7Ic1>8GZ*ao$e7k|!1D}W;k}%=G zc28KIZ;ynTFYfI)wpR0pZ7*bd>HMwPal$qf*`C@5Yj?b`4MVoK_QN?iuD1D% zI~>_D&20{SBW?t;=Oy-M8UMKdM}liNKIXVLvUon9ebB{o@_e{YMR?XKJWH%$mTl%io13^ zn0TD)3HnPZW+JjNCuTAGgNaAX0mxzzb0D%Y6SJ5}VB!&T5VF|RRP({ej%_Y&XQ%ld zg6!PvqjNhHS`4-P{%KKGl9F0Nm1*5BB}k@+e9E!RBygl$T)?U2UM7Hd5f*;t>z zj!2krVAB%j8k*NTVmg>U<~2s2upOD%97Es8briBS`2;p2VZwnOov?h&W5D#;88Swn z81vZ7<{0|ENN;^-9*1mA`uMIq9`572Qrij8iIDF`dBmNBY+N0_zCVrANB>+GkN0yO zvT^3|{j9HJ&V>6oraomz%zVo3+lR5wLhb{vM_29vJ%*mYzm1D|*CX>&^u#)y>g=(8+g4anl|sW?}2kohUT)wDM=b{qO!WNpid^?PX^vS-M5kiTm1}3=HfYOLl=+pS3wrDC*jks4293aWEX!ho%uz`j%mF1 z=<{M^ei*G)Il6N;ZWnap-e(r)z?D6tj(IM!W6I-RKM&dY#9Yruo{PWh z5q-G;nV+IB+M~A@A{(zg?uCnx#eD+1IAOwpU6Qc;{tikPw~DLL#pC<>8e}ogPJCZqi)=1!k?T5S zbNNKB>(Rv{*A2*G<_hdaWO2`3z~_owwg0d> z@=7p$UZ*$ek0XmcnS9Lg1iEEMp>0`d#+&_9oU33}AQe*GT0dsN%^Gn?!A19bPCwjU;&@2DRo*|oHuAER53{PDDxe}ZiM z_4v3Ke~Rqm+Gw1-Z^5wLKc4t z*?s7o-a;4m3GCMi6OMKI4T|`YV6jfWMK@ks#QqN1eD)$@e~&Jn|6S(~=*DaF{;-xm zB8$6M-Di7%{Rz1r+`ign~{{>y#++yCBe?=B|En|=V z4P89mHGfBTAJ}WZA^d&hAISU^?_}-E$S3|!WNnv2@%Nj5A&WnO{b~HX&;E@r?)i@Q z>N^Q@-R!};=wde^N1xwA7k>^c?x6S4&F37nyI($lyIGk2BsrxGp`BZ-JXze{)(#FSw6& z=%el&9akQ6>W%E2{7!f{joSxI-1RZnddNQJ($*JRA2OGDV-EH@<`DT^8}obr#=m)O zK#ac5HDWge^X}BULnrAVk2X_P-yfTx zi~GbKuxY{w@3;dt0~62hkIm7I*A}r`Ae)b$j@T_x#Pj=OD|F+v#r@&_7SHdG0qAjm zIB)yn{joK;w*3AWh%TPrAKRddn>+50ZPCT^`@@38h2fq?vvOD+atRV@^8i+z{IU3k25~@_m0T!Z*%K!PWR+aa3A-iK08BVj;rik zhGX9axj)>#cxTv$k;sFdKLmLr zcs~Aq=(~e!i?K$7?SU>H*ch-q(ZwA<)^IO$@q7)3qKkV5yo-k+iyep@=XyB0xN-Jy z1hS7k)HV_lvxmy@e%Tw@+_9GXAd5XfZ2Vns6uNl4L-s`$dmf*tX+L!14h4%`qtV6l zTw~D1BiC4Du{_r}baOqJ!@$H(W$kW9 zpNuT_D(kO)II`Fs$l)^u-Q2GwpQ*_DiN|^zf$UzplQ`GST+_hx)ux}=bY$_nkmFr= zB)a(BVD7J@kbT@=+GapUL+&rRz0yZrTYNJdgY0|6^OgJP8}I*PlaG1yH{QA1o8yr0 zf$J0RisO;RgPe!&iW9(mS7`UU#_#A8k;UVC>Lg?_zp3KyG(U;7H<^!YudHhse)hQ; z*`8{P-nSqd@7(qA_n7F5{@V3*?G_+wkKf@>MYk8?@lI|<7WWCPEn(*MEVySXVESqc zzxLz@>+oBMqOUf;t&LrTY%cFL?eWx_ez){N8aovbcWE zcY+*U{3-JL?RO@!n0JTg?JQ&;&zrWhAu-RFa(*|7$C@uizKJ+>?T9={WNkG{wMUFBTQ zCH_owf4^LWJ~HXfOCSG6d!Az?AMa*;)WgR-=Y#n@k>^p@#~xk)_pyii-bTEb^~tSI zOkMsS|2LO;&q6lOeThE=w`S)cZmho6xeRP&@|Ejr?fRNz%S0QT;ihJs6Fl*87J!8GsAd6qXK&PX}Jgxlli z;_v7?6Kk!t&{%}{cbx0-(H8gG4d{+1?!TYJy|xSTa^kcBJ?`W?(2dbg+sahWoygXsJ?gm& zS={|#J&tW1xh@{x+;<}zV||`AeVxlaa3ANQ&%KbC`IKWn-iI9fQQ7_YC~^6IG*10& WY{tvg&q%uaR{b5~w9DiFk@`P)u2h=< literal 23904 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 a&EEvS4$g0aVt*4f&YYW