From 6169cd05b974759c739f1ec3a29e87cdaa25b4ae Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Sat, 19 Sep 2026 18:40:08 +0300 Subject: [PATCH] feat(render3d): the grass knows a body is standing in it You walked through a meadow and every blade ignored you, which is the most noticeable thing missing from every step the game asks you to take. u_push (x, z, radius) bends blades away from a body and DOWN - a trodden stem is shorter as well as leaning, and leaving the height alone made them splay outward like a fan instead of being walked through. Applied after the blade's own yaw has put it into world axes and before it is tipped onto the ground normal, so the push is a world direction rather than something in the blade's private frame. Radius 0 means nobody is there, so nothing is paid for when it does not apply. Measured: GL 6.9 s, VK 7.3 s over 400 frames, unchanged. Co-Authored-By: Claude Opus 5 --- packages/ludic.render3d/grass.ludic | 8 ++++ packages/ludic.render3d/shaders/grass.vert | 18 +++++++++ .../shaders/spv/7ed7de52.vert.spv | Bin 25008 -> 26708 bytes .../shaders/spv/7f096322.vert.spv | Bin 25340 -> 27040 bytes .../ludic.render3d/shaders/spv/manifest.txt | 36 +++++++++--------- 5 files changed, 45 insertions(+), 17 deletions(-) diff --git a/packages/ludic.render3d/grass.ludic b/packages/ludic.render3d/grass.ludic index c07f1d18..3828670c 100644 --- a/packages/ludic.render3d/grass.ludic +++ b/packages/ludic.render3d/grass.ludic @@ -15,6 +15,13 @@ var grass_prog: int = 0 var grass_mesh: Mesh = null var grass_on: bool = true var grass_wind: int = 0 +# Where a body is standing, and how wide it pushes. The grass has never known the player was +# in it: you walked through a meadow and every blade ignored you, which is the single most +# noticeable thing missing from every step the game asks you to take. The game sets this each +# frame; radius 0 means nobody is there. +var grass_push_x: int = 0 +var grass_push_z: int = 0 +var grass_push_r: int = 0 var grass_s0: int = 0 # float bits: blade spacing at the camera (m) var grass_d0: int = 0 # the distance at which the spacing has doubled (m) var grass_radius: int = 0 # no blades past this (m) @@ -174,6 +181,7 @@ function grass_draw() -> void { u_mat4(gpu_uniform(p, "u_proj"), cam_proj) u_mat4(gpu_uniform(p, "u_vp"), cam_vp_clean) u_f(gpu_uniform(p, "u_wind"), grass_wind) + u_f3(gpu_uniform(p, "u_push"), grass_push_x, grass_push_z, grass_push_r) u_f(gpu_uniform(p, "u_rough_scale"), F_ONE) u_v3(gpu_uniform(p, "u_tint"), sc_blade_tint) u_v3(gpu_uniform(p, "u_blade_base"), sc_blade_base) diff --git a/packages/ludic.render3d/shaders/grass.vert b/packages/ludic.render3d/shaders/grass.vert index 3a7132ff..aa4b8b1f 100644 --- a/packages/ludic.render3d/shaders/grass.vert +++ b/packages/ludic.render3d/shaders/grass.vert @@ -24,6 +24,7 @@ uniform float u_sea_level; // the sea (terrain_sea); the lake's level when th uniform vec4 u_lake; // the carved lake: centre x/z, half extents (z = 0: none) uniform float u_snow_line; uniform float u_wind; +uniform vec3 u_push; // x, z, radius: a body standing in the grass uniform vec2 u_tile; // world xz of this tile's corner uniform int u_tile_cells; // 16 m cells per tile side uniform int u_per_cell; // indices drawn per cell in this tile @@ -157,6 +158,23 @@ void main() { p.x += sway * hgt * hgt * 0.35; p.z += sway * hgt * hgt * 0.15 * cos(ph * 0.7); p = vec3(c_ * p.x + s * p.z, p.y, -s * p.x + c_ * p.z); + // A BODY STANDING IN IT. Applied here, after the blade's own yaw has put p into world axes + // and before it is tipped onto the ground normal, so the push is a world direction rather + // than something in the blade's private frame. The blade bends AWAY and DOWN - a trodden + // stem is shorter as well as leaning, and leaving the height alone made them splay outward + // like a fan instead of being walked through. + if (u_push.z > 0.0) { + vec2 away = xz - u_push.xy; + float pd = length(away); + float push = smoothstep(u_push.z, u_push.z * 0.2, pd); + if (push > 0.0) { + vec2 pdir = (pd > 1e-3) ? away / pd : vec2(1.0, 0.0); + float bh = max(p.y, 0.0); + p.x += pdir.x * push * bh * 1.05; + p.z += pdir.y * push * bh * 1.05; + p.y -= push * bh * 0.40; + } + } n = normalize(vec3(c_ * n.x + s * n.z, n.y, -s * n.x + c_ * n.z)); // stand on the ground: rotate the blade's frame from world-up to the surface normal { diff --git a/packages/ludic.render3d/shaders/spv/7ed7de52.vert.spv b/packages/ludic.render3d/shaders/spv/7ed7de52.vert.spv index 35b48072cf89ecc60dcc15d3af054babac708b55..22392deef6db99bfecdb634ff0e9d61a305ff5d5 100644 GIT binary patch delta 5258 zcmZ{oia=<)$7*%*Q2@0_s!xl8l|Pu+)$oUu4*>+tXkXOSJbu+ zbvA6M&Faz}wJG%s^lVaDpK8-R$i1t?b?F=8YH1_#530Jg8^iWz$SWvZzezOH-#dbv z!I$Ro-T>}H(54N%GRI5618r5Ky5uB!miJ#V61X7^4h@gnTr+(V!#EGm7)l5F_5wE% zS#Db8bB*PtGYc9AqGncsYstCU1hIKA8864WwRB9+q zu52#PtLZCM?kJyMnot=oPc2QV9M?LvG_lgzy0}!X>})-;*(zq>)6Vk+GPDdm*!ohb z)H-z6(NjyA>}|CvHSyG{f#+W2#~!_4LCB9FuW#9U`b?(4;Ym*-GvqXnXkqgl+?qAp z&kXsER6P0a$LEH;40-(i&(CisM-LcRV;z&u$O&7NRK6ZCSef27W8SA>XTx?;*wwI~ zLB5RVKDZrBqcbo0v+(M|Zf9j@+vKG;!|Y^jp>P|!1+1m~ww&7qmVc^{WjAs$%L0y# z+^(TRZR@9GV=?u_It$Ak$I}yAls&Z}YwxKo%8wRg{XNB@@1-$Ma#8j)=kjUQgdXWI zFo4H8WCM7#L$-j6hVDJ}`Hsv1J^j`zZx!12ShCN_E zhv{40?JT)hIh$fP#I!@;NAT$)&J#TEL?&NWO{h^H-)g^FJ!6H=bqYZL=jW?CW`+xg>S*D3%hUsAG`0stBb?* zU9j9=c%XbkfKQJvQpY`Fmiw*p3Imab}pC zb34Ql=V7o#WD)3x2$Fuc-Pa%Sj6muhWBr6>AaRWO$6&`Cf%kz8yd>YI$H8*09Xa;| zSU+@GwM!4bsu}Mtk1k`!K6m{A}0Y3$6qpe>mWe=I(f&JixtUJL`;8V!*jhvze z@~598IR0rYm5$P2egoA-uwQ^pa9bhRFOlUV_|sswC-(nWU~S~X<{7ZDt2Q$U_AG*i zD#G#CVEN}*QNV%9!3)dTZ(t6vw$P*J`nO;?J;L&LVEeXITT#|7^uGt&N^vgFBg;pb ze*hO-`6Dtm=Oh(j^a6Oa7n1!6Sws0~<p!PD_}VTgx{;+ z@UuW=?L4pX&GV_eho@)TXJ?u`@^*aZ^za?*^1ZPBb;avmnhASS5S~&U0SO+#^hNASJ6>$^3hl$SU>q_tf`J~v0%K}8@JD-@?zh)S7Kn1+P5IfgA}`MvV3hLdPqa6Z|rI>t^29aKcs z3&18;7fH_q7n7cKgxrN}5!KN$nT5#O$j5}<1vZYl&XkSZOlKn$N4p5w(aMM8IbfaD zMUm%%EmU3H6X${L+CJ&qwzA8zXxU@_zX0&p-lS zfNT&c3{coV+E!B+yiqTF!1U9$<)Wuo&FxZnETk#QO zZRIU`2~WAO|0q~aT|YUMtNZU*K89eIDhF~|NB!074g>k1b8n2h5g?DS3pQQBC-3ki$GU`{omco1UFebPMhH5AHqiYDzMyH*nBxJU^BA3e`Ii? z5!>e>hHsHqgDvP{_g@DSe~E!Q(rdsPEGDRzkdwI%CwYtOg1;7Qard(u1C;Og+_iGwu1etEXBxzZUD>q32s}?VLr-j&pGO2kH=L1B6TC2 o{@Sazq20vwuT?whxW6nwBdgv3c@sGPdSd6w7^Y$FySc3Y0HrHUU;qFB delta 3551 zcmZ9O=Z}DE$8y=osd5A5ijQd%)@>7vW$Em*W< ziQeYAw@M54y@ z0{9YC;#a`SF~qNd7v#8+23OWKP042G&s%Y&iJK`nv+KgvYZ|6>r{8lR>V4xH#_Chx z+=X0wf$NiV18S9WwLGZc>08^~R_#&E{m0hkmseISd=yLrYgTHa@VDhsOX-N(_tlBD z5$)%c%C%+fLrT4CYxv)*wyu3nsc)^+@A!&kMiVfU&+jNw>6+N@=TfQKHM0MZ()lg0 zZ?V_@k8hixO1m4ST6f+(c_?-}m>`ZMrAfJMD{`S9fc?hnv#0963CGOZ?g67KQ(?!W z>Llm2u+wUTJNjp5!cJoa+X=oD?^cwv+1nIy*u!)+tQ+f7PU$SLvGS{O?rgC9u0oX= z*pY8F`KEG~a%vBxQdd{U;*Rr|;_wp0rnJ2XVfP@Rm!W8TA;MPgr3kw_`byWfQ@$RS zMgI>VFS9!7)pQSnFJL_Y4$VW@^0A+X^|RzdvliUSiahJU=5cSKc?4UYJXw8sT^I)H zhykt#TR>kVdlYQ4^@XM$!%Ma!Wuw3ML5^Rox8_N!!-UbLAv9qVZ2Fgd` zr@_SuJ%eqc;?ADMmXAEofsK=ojXe)GzrMNde>vA_BZfP6HwxNveiImXn!!@z^{}YC z*jsTG0-H3?i`a5uzXV>!=-W7Z{V!vi?`)RZOo55KPC6s-Rj{0mc)z!R4V=!>XN*@) zK1TaG*kZ-wc>_*fy;o8@Ci-S0r(}{1T)I!lz7-5vf57(=@HW`Lzu29=Z7jKcyj8@J z=pC?mqWSG$o1dFc@m;W*SgWb$G^08>W_{53@%Rg7wn%re;v{FuV4%7jH17R zi$#Aw%>D!0CStUEz-r`ULVtqIqc0lT3x3V>kJ0V}J6atPxF2k=zG(6GDGK5M*ylGo{1q zJ6zYOa~bD_Ex{W%hGkLfsHOQ)bwNWeuzSVPy?iGZ&o88=+U^C_tQn?>6)m=aKcnTC zVRvjfe-cDw5AYW17hg-QaPrZ5Pq4X-*B2vc1ABGz*=MN?p;(7)`n4(-0lmO-`c_a> zJAA#t&RgF~eA@ba_4NUN%tn0eRl)xG+LQ0gI*PR~Wes)1oy{Q85s?14DSk=!$CeLG zN3JRE{zy3a_?j9B_D7MtJN2*!fyYuq-&{CUyQuIvnB^6-YhTQE>aGkJ z0=B`>Y8SP}gmx&{d}^;|BHH?F=4kK%rjzNs4yQyoj{&#RY1qTC%h=KNvDj+ljlZ6F zxzHa6_L|4H&~R)yuechI=Xh+6=e%?eqmUQ@p-{)Yxo9L<-ZLD{r+=QI{3w>36B2to zcaZ0V+Qy5j*@-YGuwI58O4Jyz+?nk8{=DKz*z*4JbcT`5&pk}ruEv7xz`3fo(tS+O z8IA+%pF>VxGmhd0V&KQr+v&t{GJ@M6H?iFNEqtB=CvVGY#@BAVxSE{`GoE!Ptg6$% zD&>vz!9N}B!*DJdTbKZr^DnrGIfwiZcSg=pj8p8ECg7X|XS{m+o%KDeA+gTMkT#&m VHkZLo0mm>wgN`qx%2= diff --git a/packages/ludic.render3d/shaders/spv/7f096322.vert.spv b/packages/ludic.render3d/shaders/spv/7f096322.vert.spv index 046c02dd88a42f5e1b4b9946adff1c6f774f7474..97f7b3f0a18dea5f6d799bd6abd0d71ac63816b2 100644 GIT binary patch delta 5321 zcmZ{o3y_vo8OP7JHxPG)<=bt60@pBuAY%h$f2E_jY38_;D(T2HKHavfqt?2yC8gb8+&S1fannfc!j#%lYr3J5(oyxh zTdUJ2rj$ljUA%6?npMMVHf*Rt@o39ECe@#8?U~(q$fI^Bc5<%O<~6(<`KtB4w(8Wb z+_Gh8^}3NFSHs#pwxVrwbJmi+-kj3V=+IU}TT^q|1K+e-+>*X4&WmjDpXRo8Tf+Bu z@C#Uiano7W$fgFK0bXC^doj2M(dQ-L;R3G!k50**T9Q2(S~YS>BXL{WJvM#9wN0}p z(Uvoa%-e%)CZo5c*(|kL^?mKtfw?7%P0}*2q}ilg!x&ksg|?u6Oh>i5zhvnyEQ{(V zbc}Xd;0%-|#_4os_t>2seQlK~_5Ia_O|?q>xURD*o%NBfC6$TwFL$k)ZQHx>euDYu z;NJ^>3TzF*55TKq1Kkf-D&1q#dycMTdVRGyrH;a@jq6_c-h0nnY?S-~_{PaQPMS+! zI9TZsIBic0i<$I!0&VTutCvK~Y^v}1%D(>KL+}X?d}>iIIWGbeYOZPNH^e33b`Eh=K9_#J=w=lZ)Sx0aqvZ0@5A$a`ZZ7#eha#`?G$d2w}OpT z-&ts%0INS(n&p%5$af2Q7jux*^nE^6#)hYEotlj&z>{o8Ecytpp5!w3GzZt;(_H2c zmALVq?yzSp#1mfTp7Mg9m`~_Y4+j%?+=H9IBOlxX){phgzvI}<#@))oZfCw5xq1Rz zXf*B)xVGDAlIQ7bU^RFA8Jcky*x$3PTicxswF8_#v3Z>K8{mhC86?jmTf^4s0ctVfcfe}xEYKal2WXQ;Uy;>hzeutj8%=s}30cewld3$Bq!+s_%lVVFo9 zZT=%Lfla);*rs2D)g0Fp+QVSuu&vBj>ETyANOC835+}0*+I-BEZTdt3zXj`~ zuQ!$4L*_m3Xua>;YW91SM;Yy$8&ASxVEa9bq0QbI?JcJ*qW=IkV|ZNkPYO_y^dPeW|pvr{LM@09-%ySlB`4Pa4WAdlqhm z$epe>4;2sT%_JCo4VqkvAZ z1!Sy0Z`Wi5#Yj>GAHK3KH1#N`8*H3i^1K{uo+$o!@C@cZ)ZSNsb&BB|!EVe$(^-3T zK`j#XfxWF_qF2IQ#Pvn|e6T+15wien=dut^fS4q<;6!+AfzH}vf>(iENlefW)?eN8 zW6=x2POL5FI0@{e!B6)7FAIw>TEtBxaa#u9I3@L%Y%$nG+M=K(U{@RupI3wZ@>Y+y zL9nsfqR6G3zr51i$X+`6QXHbf*ML3AG0`dTa%)e8>!Thqr-93hIvs9~==mfJQr-C+TuC!Ca^2_Iia1yoaI$%@R=XCjn`P&o58UK9<1}=-oH`c zTi}nO=U`){C$&o2UxC(ucqNav{j?SUr4La)T7{cfcLY2{IL3G_5}!vLySl?T;I2{nyp3|W<>34z@{@o zTl8ox*rOZGe`)`4ACzB!{96*T$>&bz>mQ@hW-cZ2=; z+*9Ac7-jg}6T6giNVEyOkx}l+X1IFzY$<%o@64Jvr-pdnT@3bbg&J2^>JwrFJbp z^;7>$YWQ3QR(A{E%hjJ-sJ?@t=Ayw_zCT|e(N0AF^s5vr=q2cCH1&I!O#VKw+A<3G zTv5O^aCQIVU`r#p*QaT|MZO$rXpO+9?BD|{>@e6B~!b@CYVA-Hks`qVPS8z9~)XXE4^?*gm&3+={2!~Kv} xFEr|7kH@8cj5nbfufKK|w2QpjJ`A@2eJpx2TJJ|7@edYPF4vKNuB3j(e*vzQQwsn9 delta 3619 zcmZ9OX>62L6o&6GZK=>gO9v}ep$aaDMNm{wi&jBEQ3~i^D-nJW34#?-qmD~VOpHbx z|LG4CqtU1_q$Wmi-#1iT!3EqA_gyxZ=b3(|emcp`_n!B>=iGD7z4Oi7@m68abA`s@ zps|BfDx{&QKAp9uvA6cXnv@o^X1AvSoT}tKOz6Z$$3^*Ib!g*U~Yf>RCLA2cEjBx~0lpxM=axRlWUuf0k0;%DR=s zdByZbk!@Bak{8oP@cF33?|^$T#GAn9Ww?o=S64MP$p#knth(65wQRVs@6sVR3_7}= z9%e&Sd%-o9)s*AxOsA^_}g_|3=%9%5gc)j^qx_aixqqymD9L>SoIt z4Ks)}E7ed|$FRwDh1N<>X(%eE*Wqaww!m!Htkv-#0S3>$7#Y{llj{Pf`>;v`= z>=AuyM{X??N_}fu#}v-4!~0<|rPhZxPBluq#I{4;yYqy0>;u3AaiA$3pV^ir7y5&+ zH@rG`y8df$EUe5KHDTK6uv1WVk@Ircx!B8jz5#a|&(3VOGvM`w?##*qqgvISQnzb@nEgXK5nc3FU(-(@Y_^1tphJ+-471UdTjaVjiYysu$pq>NEMULme=IC#% z+<$jofY1@0y$Ci?J`%qK&QIuNY!l@>dj(rQ^1KQ*PCgpj05-qAZu{@$TD^v0$9AKj zA>&^M<4!YKYP=Y3Dlhg{T#{W&^Sp^IXVKbkfv@898#sIIx3PJo87#GB1t#)3>5Ra4 z!EzSjx9dHyfhV){8RM0ckI`-hyIKBt-iMP{@0HY!iGEPzluWXYOXmyVBQRw3tNjN7 zTfu&I(T%<>EV&)LFvOAQW3YLm_)owV-M!e`(}^f{Q9 z^uR`6fbHU@EWwvxHS!U&4eaJ&e+4#29Km*Q9Kp6Sh62To`D?J9#0KAh4UqQ&S@pMI zH`W)Md z6!{z2LiIUWH z{A2RcG*s3uZYj&g7poE6kY6ahqQl@UXCljO%v{xGU&!H^-*VMPx$<4yG=G7b!JA2v z{{Z8h8& z#X79opVc;21ndiz)3=IEwZpd`*m>()jZa&jufF}k-_VG!y>_r4UwiUnSQA(t;FzNU zcD8^-W5NEd$&Y9pwtQ#~u4*b<&fU8lzeD1P{pubp<}{@Erke#TWJ@ z>^AHudNQ^ed2?S$yj*9>e(R6K@K(o<&QaKM7r>d+)0l$oY23{uv>m!S`Fk?WF<^O5 zY${Jbo|ycxEIB76_AnkSlV}>oEj$ZZ2kitM2PgjmBJJbBa`PzQzN~-~u;u+5>PREG z&)*onMRtNMsGX(WjqhciPINlBtl%7y`jT-pR}rMc>bl`i1Y6ujlhMhIJWqm?4^3C5 zv5?T5tY4ly#+(8+PF{^~gc*Kw{bI~VWJNQ diff --git a/packages/ludic.render3d/shaders/spv/manifest.txt b/packages/ludic.render3d/shaders/spv/manifest.txt index 93c90128..bf688c9a 100644 --- a/packages/ludic.render3d/shaders/spv/manifest.txt +++ b/packages/ludic.render3d/shaders/spv/manifest.txt @@ -798,7 +798,7 @@ T d48d6a48 u_shadow 9 T d48d6a48 u_tershadow 10 T d48d6a48 u_ts_height 11 P 7ed7de52 grass.vert model.frag #define FOLIAGE;#define BLADE; -B 7ed7de52 vert 0 296 +B 7ed7de52 vert 0 320 U 7ed7de52 vert u_view 0 mat4 1 0 U 7ed7de52 vert u_proj 64 mat4 1 0 U 7ed7de52 vert u_vp 128 mat4 1 0 @@ -812,13 +812,14 @@ U 7ed7de52 vert u_sea_level 228 float 1 0 U 7ed7de52 vert u_lake 240 vec4 1 0 U 7ed7de52 vert u_snow_line 256 float 1 0 U 7ed7de52 vert u_wind 260 float 1 0 -U 7ed7de52 vert u_tile 264 vec2 1 0 -U 7ed7de52 vert u_tile_cells 272 int 1 0 -U 7ed7de52 vert u_per_cell 276 int 1 0 -U 7ed7de52 vert u_s0 280 float 1 0 -U 7ed7de52 vert u_d0 284 float 1 0 -U 7ed7de52 vert u_radius 288 float 1 0 -U 7ed7de52 vert u_dbg 292 int 1 0 +U 7ed7de52 vert u_push 272 vec3 1 0 +U 7ed7de52 vert u_tile 288 vec2 1 0 +U 7ed7de52 vert u_tile_cells 296 int 1 0 +U 7ed7de52 vert u_per_cell 300 int 1 0 +U 7ed7de52 vert u_s0 304 float 1 0 +U 7ed7de52 vert u_d0 308 float 1 0 +U 7ed7de52 vert u_radius 312 float 1 0 +U 7ed7de52 vert u_dbg 316 int 1 0 I 7ed7de52 a_pos 0 vec3 I 7ed7de52 a_uv 2 vec2 B 7ed7de52 frag 1 956 @@ -874,7 +875,7 @@ T 7ed7de52 u_shadow 9 T 7ed7de52 u_tershadow 10 T 7ed7de52 u_ts_height 11 P 7f096322 grass.vert model.frag #define FOLIAGE;#define BLADE;#define TILES; -B 7f096322 vert 0 4400 +B 7f096322 vert 0 4416 U 7f096322 vert u_view 0 mat4 1 0 U 7f096322 vert u_proj 64 mat4 1 0 U 7f096322 vert u_vp 128 mat4 1 0 @@ -888,14 +889,15 @@ U 7f096322 vert u_sea_level 228 float 1 0 U 7f096322 vert u_lake 240 vec4 1 0 U 7f096322 vert u_snow_line 256 float 1 0 U 7f096322 vert u_wind 260 float 1 0 -U 7f096322 vert u_tile 264 vec2 1 0 -U 7f096322 vert u_tile_cells 272 int 1 0 -U 7f096322 vert u_per_cell 276 int 1 0 -U 7f096322 vert u_tiles 288 vec4 256 16 -U 7f096322 vert u_s0 4384 float 1 0 -U 7f096322 vert u_d0 4388 float 1 0 -U 7f096322 vert u_radius 4392 float 1 0 -U 7f096322 vert u_dbg 4396 int 1 0 +U 7f096322 vert u_push 272 vec3 1 0 +U 7f096322 vert u_tile 288 vec2 1 0 +U 7f096322 vert u_tile_cells 296 int 1 0 +U 7f096322 vert u_per_cell 300 int 1 0 +U 7f096322 vert u_tiles 304 vec4 256 16 +U 7f096322 vert u_s0 4400 float 1 0 +U 7f096322 vert u_d0 4404 float 1 0 +U 7f096322 vert u_radius 4408 float 1 0 +U 7f096322 vert u_dbg 4412 int 1 0 I 7f096322 a_pos 0 vec3 I 7f096322 a_uv 2 vec2 B 7f096322 frag 1 956