From 95e709f820401e0db56b364f8c78ebdd565c6f52 Mon Sep 17 00:00:00 2001 From: "rem..om" Date: Sun, 12 Jun 2011 19:49:13 +0000 Subject: [PATCH] Water Filter : underwater shader git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7599 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../MatDefs/Water/Textures/caustics.jpg | Bin 0 -> 57225 bytes .../core-data/Common/MatDefs/Water/Water.frag | 117 ++++++++++++- .../core-data/Common/MatDefs/Water/Water.j3md | 15 +- .../Common/MatDefs/Water/Water15.frag | 158 +++++++++++++++--- .../com/jme3/post/filters/FogFilter.java | 6 + .../post/filters/LightScatteringFilter.java | 4 +- .../com/jme3/water/WaterFilter.java | 96 +++++++++-- .../test/jme3test/water/TestPostWater.java | 69 ++++++-- 8 files changed, 400 insertions(+), 65 deletions(-) create mode 100644 engine/src/core-data/Common/MatDefs/Water/Textures/caustics.jpg diff --git a/engine/src/core-data/Common/MatDefs/Water/Textures/caustics.jpg b/engine/src/core-data/Common/MatDefs/Water/Textures/caustics.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fb4f21cfeae7c59b8f40a12b07f1b170b1f85af9 GIT binary patch literal 57225 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≪&aYB6xTC+2Vesn(hr8%4vr|IznMCH$kte87hQGO z*2w9yGdAn*LFw4h9iParaBj=Fc|hhZ8At zEU3W4 zp|4=qyowLdR`&d{Tkjl`-i)4?II=wZ3ar%0iFKAdDzJ)Y(i2wWCLkJ7bn_+{&gFrp zoY=okjD-XR5LIudTFhK@Sevx;rNX=G&96RsLzner zu(Rj<0X>b9syw56Jo@922*^Ubbu@-j68MT^VGKCvza-{j3BrB(x;&V3%2{@lBzFmB zib-nMQMkn1nwhzM1jNm^Zxx@HiY;=+=0_yO-N zs9s&K`i*X3+|Uy>y)C?l*VN~ky5JwVTgwC73C_S9x3?9t#Nkbde;UCtFr zy{4YQt|xB{|2p3?KqPr~J3N-@$z|lsp43zl^=?b*goa=C1ZRK&)8Rmt>Mc<7@B_XiD{1uVYT5Hd@|KR4;Xvoe`D2!N4JRK6qNz{>)6yyw zZ?kK538*e&rT!&eq90N#xD6ypa?RRS`n06US|uJxknc~$=dVod2xbK^|2i?vbEaaZ z{+0Vx1ydW<4=g-M(sATs`tXf3fiu;{@nPr*m+s)Kz(KQ$rn5^yR_c@7Aek8iMvRz~ zwFNHl1yH{51x7ygXE7Pp*jv!j%KJHICOTe!IUDV`5+nJla>FyUhn5n)tXbdZZn4OD z`EEOoRhq5jIK{1+p=WTM;UwJcTs8VYMdY_#%!E%t2Fq}ywLXRqj#ZbQn7i-h8=?lz zAQqC7a(m9vb9+zN<@xQgMDt||K-E1wY< zBzq?iqtt-=n=x6A6j<-7H}3rr-bGEr)AZvlJ1Tzgy1hrEM7dgp_i)%EcxhSHJ0+y} zdqCyIs6e9a3JM zO0nMPBvhA|+s>E!_cw)@C%s${Q7JN3#9|xtyT*50L80GGT-Ue5iLeikS<47|aHD*ykDALE>3&lA}*WRDYNb?)qyqg0eAtCMV!Q990f+YZ??-5I5bC=v>h zk#&rQnZ2?{DD-{#eE)&-!&&$9`Mj>{agBM`sqcS!RVk#J5Igo~fDg;|mR%wM2g93i z1-f5Uhj$9{yqcPvV98GdHVUwh)C!$yEB5}F+<*@V2}KRd%O<4mMM%W7x`=KE{B>|4 zs4Le!4to^gz}^%mz{vR34*);*()k5F`fT#FGe+*RnL)|_`)1FpyT1r1I;%o=d{gR> zFy0|j{e1BIGF~KcmeQ|gI^Erm94{=f9GZhD$vJb1m&Y4`Cbrx{%sJcc%E|fn#Wf=R znlzOl+cWH|JfmVBe%uEUYbR{3gF&QXIeUYdMnPn!bi@N^ugmEPLlj__up7Bsa^B3&(Bjpy?>@Pxfcg$l>$A0xRe53G-C{O{l= zDWeb}As<7|7Pt@mhP-+q zy|hP*QnMrk2Z15x?D7A^mjapn;Dy45p{h8U^;=!hz9WpP61=YTJC{my!r{4%9r?g^ zJ3L@8Ly=ClVlzE zG?3Voa5iKNo){K-koJN8K9BH|OGs6^U?vJJeD=&;r>U;hlNWzs5Ww|J3;r+V4RkR4 z870+EUE8Ea`c%5=Yu*a1?LC*DiqLy;VZup}R=^vctdPvI9kY$TZI>3H1H$Ftqoz4+ zbBKj=C)cqkixWS#eK8#D1-#lR_Cy8!Bulkss)-HR+ucY4`ZhgBnY%%5F7IgMKlS%2 zJM?Eju(%d=w5FU8CrwClDP}?!sZ9y!edmu6hd4@O$g{GqJf+z*!D^%LB z$e4W9cnNz~3(N2YQCzqh8>$!hE3yr#)$zLQ5nUkEj;(0Bksm&emeDKF$pe_l@Mk2tg_`t7hUs=1?KXpiH)--P>gX@BVb9$FvA-Z zi4u@!Ke{bMz1BUiTXDF*3c0P<{B5z4Kx=)ee~=}+9t0$ ziOTs8C_{Z`u2B@xQHH;TwCh;U-DsqSD-6l@XR|&wMl9jOIvWO`Vg~vDfEhG7RsPnX zqYRA%g^p^$fFk?3J~>vf=6gP4dZKJphZT~q<11GklNRnccC%So*1zJ(-J&xK;ThD6 zZpZ?lW_(}&ool$NBJq;6&nqFTwgBh(nwLJ1BGpo5nZ?qKbF|%$poAKeZNu+u-maKB zw#5bbPFCgV3}V)jcjlP}_n(q$T>?2)tcnC}+JA@{sh2MSW3fl|9*y{*@5Z$zWdjb5 zmnwdPA^~^Hobt%+*SE^AF~?&#*IDVJWw7dv0^R&pI|Sx;MgDm7}t94p*T$h z%)iC!RGAY@z`zk$E)$EluLnLDlD>Y5ar&XIy8u&%0^Y{@w4|N4`jX(PttHP-_UpE1 zJ3k@iPTQe!BJ!dG1ud<`aCz=yNd8{}LTYyI2^a~i+1SB#j-b5Kt2mf6shxfMb80ne zF*>3CPzaGQ^I+RgfMKG#%}~~QZBoh|?a3l#zS3i;Y zdcCXHA~R9~xOjK7T1YdwA|5nwW->=SlTinxAE5@K*2|DBh7~%6Si^Gq%eoUJfc;4P zGZQwcydsab)AH$MVDT^8SiVoy&(A@JiC7lTQ~d>W0p}y5B9Oz-tn{(`J06|Tuc*+u zkCQXg$I=jCrv+@k9e%B3wWgt|>{=?SzeGJGL#Kw6UG}vjqV_NQ?6cy`?JYaAT1Dh| zEDJ^B{vkSHUDkX$XDPddsrF8~m8Znr#Nv=Xv&L(d)hT?LeBLHvjut&!7W6DXJZtzz zf-RZxya_X%EW?8LXD(|`iO+&5tfGE>Y&{`fbuw^6rEK_S;HZ&9r1brV=}=f2N$EW_ zXn(7ON;|+JqttStUp(H0cz{K%az%D$)(nxcxnOrhqli&uTRV=MaL_0 zIe2QVE&Xj@!y`Ysb1w&pYsX?9-HEs~nIyJSa|)#==)6mzXS1k{;qAWzvg*|JVyZ)o zhpLD%{NIP)AP?0=i{ZkcaQ+Qls{;22IAXo$6rYX9uznqpO6Tn4s=N6Q!F3{l zZVAw2p0x2i0)jO#{N+a!I68N%y!sf-GD$d&zs4JgcSx!VW@4U6=9 z&AYNFc=kkgL&BA+(Z+pizVG$i-GCBzJyJ?yfS?o~r1bMn@H|^kmN8T>ix8@&>RqBM ziF>wataD?CShQo&C}ijS#q{1?ErADl1jTGIH7$4A&X5Lg`t4T;OFk6ogVYw)Lb~6 z>wvzvKvLSzG`5S`^g})YbHPK9w#Z^$-!;uGhxkI~zJWX(0}~^gKgt-3i=F>~D4_CE zDpPus6-x7Pk;%;%bXB7(whBCU z5$AR0G;VHFP*sLA8BM@bzG*Ixzj-xZbOOOWB!H{TOT9${OH>88 z6Pp6X9kyo2O+;aNqVIZp<&q!>?rVsWQgl8z$&f*y_Zl*;yJ+bO3AM-EeLshBGy^FDonL*LthV{XB`R1i{m^&Uqst{tshF3 z!j|V!KfF(bPS?nskXxsFNur_9a41DJ`jafswpC^`mj>UcfB1G0W!+gukzr{oIej*I zVzD3Uo^?MVa9P;=jE}Ir0O{wA!6HCkG3!Ki@i{m!v3fjlb?7vbx)@F|K4HY=fqS=H zf7veSQ)43KGm7Prapc=)q*qd;5yF^B;xRKVWhU4zd7hShOUeDdBkH2OVqnyd(dBiS zl>;{#7G=&Ge-iSDK~)Q1?;BhPz=(t-qIiLTFp6Z=V)FyfW7;GXsfJSlYdc;Xs5!ib z`mylNhb*SY?jX+x24(s_bw3vjy79l4bSAAYg29(vRKMo(r%_t6qHUI5PdL(_4GhVK z(M>Nv986W^yH6{(-ao_z(&q;gD+fveWXubC;Dn}S1;PX}MuxTVf(sqI2HXDV!Gzmz zx*@&{0U7EWY|VhbCcr_^P_}NboGqFwv>bF}IfEIY;>4uGX>{3%VFjzXi~kU};bSAo ze%-*2WH@BU&#J(Cvo#FIPI%iI z4OmS58=q}v`A_gJ-UK&f1ufwKt(NY{#^kkvcXXak3wY@DF_Uj5I_JVZ(XYr@T7n-c zS8u$M=)uF6#1HQRhu2hjZc!=_vw2^1dtvIAFRdeu?JXd$Zm&hYL$ykX8e|c#?C{3W zdLCyFPwK5MuUv+5#*=Xid`?hM8;4@X-?{9E6+|gtiyk#dX2#`)^^a{Tp)nJihK$~( za;m#OlBRRjE>e5q-7XED=!z3EtB9$-vxAW1^mtq7AFg^XktM89)v!`pm6Ck>`)0}X zyD7JE)^zt)4(2xU9qt$f1BKq#r7d$dWz0xJiuB4Tk67Z-DlHUtd;$1d5-E z!hTyuF1_9j!*Gw>uT}gWe~UM;rtOWxBs}Nr+vNu_p^rvpE<;f&rhTdzIQv7zw-fF^ zJcbB2QKvn)3{#s13v+wa81m;@Ghv-_pYpWcfZ;KTa72Im#7S43{4ZoF+pC7}XCS7d zT+umVoqT%hBhFEs35fb^v~uO;i5ykChXbE*+Glp7M$_O@k0*{1%C7?}>dh-STL)sa zuzZ{D&XmCMia(mVrZnjukJ-l*6Bl4jWNQ5D`=ym^?nRBLD$y7($(Gl5xvQNZxca_xs~Qw7wWJ#q)zhn3zgeWQNy-b2UnIuKJDaKZHWUyCuADc1e)(tQil!Up7}qe60QA3O|CCjvom< zjZ})Fw`kbX$Rl;WP&`-Wb6V*y0Zl&!ybqg#gr?WjhTJe8Xz|c3{r0Dg%PbYgZWri3 z<~06ZQkvXRQy1a^P4j8xRH(?%D&-M1(hPN;0d){yQq{VcBcs2Zp0*w$KOemRU0A!# z&-;p4%FoY~I;n|Oge#i#DX6w?<$Xv>kb>5A z|Flbjk?qjuBViMi1AzjRscvdp#mUL~LmTSd7c;LjJbP?e$dlV-hew?Uj(Gn>&fB z8h8y1^Wjur!dAoc0&gsDS%y?c#kB0}1$F)pH^H))q@S{=N%pz?3q{n*VwRBFm%)m6 z=W;N;rw=AbEB?(CeH$-1zf5eM(>?-o2h6_IIX8gGslu|U=I*m1_x9K&t)5E$he!W{ zJEg+tTFB?mL`=j~_xW@ZADYi)!&BoL{}8QEhCwutV#9)&%bqIq1yK!LUAAJFF9&8n zaXkV90+{Tn9;9Fw+5m;`8FC&zD56=b36V}%#rt+LND4cJ9m%*yu-_?)=sO2lBp@=; zP0g6m7t5c&U@%z%^#A0ES%bZd6#1OOIA7LPrB4HV?yU4Ol6mASZhAa*`3>Ta(R~I* zYBQ?Al>_JyUMC^07x1D_p6z&|rgQW_@D?x; z32-*7NrVCGJ!AL;gu-wFtYnjffNV0(Q~jrO6h$!w2We8O?a946I{y$Jg8>emPBe+> z88ucxEyzd@u+7Z^oTjIVW=zZT_^6h|!y%eeT%N$~u?fi03@4=qcuEw$LMoiFV-;M5 znJU`35)BLAlAi&9DX>{oN%wt+hgxF~^aQ8Mrpk^#(D69{jV3Cv_mHt@JU#xZn98f+ zuthk}gcT`2z^Q->gOdPbXrJt@V@)gU56s>HhAhuIpN+@gh@ta4<}1Nh9?QGbou6kJ za(Ds|ZL^e2W+2`>Sqyj`G+4uLyYj3}jgz;ix>DDsoaNhl$AoTdV(3>-$N0}R4v|6+ z-pl<%titWsqp3A-nyrR5y01zI1DGq{VF?~T?)8;-DB&1pUorYc`z#t$szY>)YpAw_ zz2fLOVoph6pIGc0{xu9j&7On;k2%+$LO*S%WfVrPtvGJI5uwVct%BF_9^hg#KpVVq zRcmVaJvHTZ2@%rr^O-|eL%om6vdnM6b-Ar;gRp=Itu4cc#0PWg7y(WX0r95$kH6B_ zPZ$;^^x$~(1@t9%-8cSNq#wMcQ8#Da^^sYWrXW2sPmc396?q>3rcH?@cQC7+dqVfP z&_}%>@!F}M1;YCeTT+g9$CX&GNrclIAY)s|dc1eCB`76zJugW+pJEhwnLR-pKzcI2 zuDjhf4MdkJz>MsZ;X8L`0&?#Sw5j>9>GcTjs-ejM=-nX1Xz@{xkt_K|oflLz=4vu> z=)}+C)YQsA|HLGr5=zgRXuqogB(`Wc?C9+j3Zimq_y-pufxhPh-Wpj(bJauoCcQsO zqYqyaBowti%NaJkebzb+$5O-xFn}~%bDTMQ!)nZ_7Xr&R0EL3ovwGc+Np?!osY!Ee zO>QjUv{buqI@#NpJi)AU6-}b8$%AlLCE&jAUL4X9EE3l1g{**oQUMil=KFDU2K-)q z^Q6)!^!djIEh#Q1m3jOVwz>WcKrhjE0ZIr+SEHIacJVUW!wKz>;e6pu;&$5M7nWgg zJ*0|3&tIPx8}(@l#gs}sy07T}bCDX*?=;o0G0DLoS(^Xtv^_a;l*U(Q_f|oi z>zt5S`-KjuYsTZf&x=PbSC!VetLkr`!XvDy*HLoDX2P9!KL6NaLTYz-v8g;eHLYp# z%k5|t{es5ulrhnj-#b&j^sWZa)5I*}7I-OowtWy}^Pg@U>RyXlpLTwfE&EO&_a&2t z&U0zE^o>1cSCLY(LXnso(v6V@=gFWegHD`)igV3RBY7h#l%SsXl2qJLfZLxAza2YF zK|l96wEEN?H|^|lcul~tDCaqfXK8`=JIE3wWUDx~Bpr9uOl3wij0kK|{ZfvWj$+8& zYrLkd&X(ckFW|a!eHg!x$X8?jbl^{`$PoPbXM0MM*1fHH>4LB?WPXxUR3-h@BZpcz za$QS;mNg0<2=jSkBl+t#JB|3}-h)N9gpe_Hk8C=2kKyuRX;p@jK`tA8!KCi{x?7Z@ z112`Epu*f+S*}WQkI&C#9zET`*mS3x1h(#Pys+Ob3|*ZwFv!Dd{K|=Mm)irC3bGK> zfHk!Mmu_)guA~u(7W!j!R02gQ5~KrWNMr?1^X0`8mcL-eNPb2m`ig9EfIw7btx<*^ ze{hr0@{)5Imy-PJ*!pz#O{?f!5!>>iEQ_W3^jivx`JYY=d~i4lQ1J}4B1wjnR?{RI zjo;zJ_v(t)HbKvVd80&Ul&zVtJy!3{c3)Us&&+ZUA+wxc4q_a?k`2JW9UxcRyFM9L zaw&{nfCCWqgba|8MDSy07cAqe$qts$6e4#qSGh>(@0z6M*A&LA&x1Je_tAUiXR)|u zI8sN;>c>vxu1E0@2yHRGc8SZ5rQ&aTzC?3qT%vEr`N1D~85&|(&7Uk*P5%0v4}9tk z{-RkZW9QVjhK1Qhcp>5TM}c4Tor*KzYL7r4pVZiE{cembo#qaI0>54xtL*8)UH&Hw^eGKjbI7#e*xrczD;L_or6y6TL^mG>hBbZ)&jqR3e{}oav>CnAjAEN zLazXPfyN5RqB;UwbIQ5KgQsA>tP$yKeiMJfB6|jI5eJGL6C)BXzf6)`hqJhG7tWm+ zNAojZbnSHI7k7O>al?ZCT@f31DO&2k2dn+sHV^DUv=DC?#zd>~(=2vFd!E6g_QY7P zekVHzZo&zYs5E~P*CCR(gXP>W)$~i@XOwlKYy3U~|J?Z9b6761+^hITJe?)C-SYNF z5l7PFo(}v{f5w|a6#mq;ya7Jnxtpzn@ynj!JR)aczIRH#qB`@Mvw0gL;Hh<=5@$p6QFF{(&$;^0~L$Mki24hcL5+ zfDe58U#h7>L4nulZ(1|vmc7& z_KcXpoF8$~@ia*WJD2*HK0m&62b4QV3R%->KO^Of!KV8Jjr;XGgCc1rlY z?_m>v*=)M)dCb$kdQYP3CVkOIVXR7{NDX->yKaD{qt>;(YdM%ED@dfSnX1nZ?l0A_ zE=x(A5!a^~A&W);hgeWm(crx;9WCDBDP`P}>i2DZNAG}%WhrV%O4gv}6;6&Dq6N_k zvu@&Qr&)YrR8D?a@r5LIkxO9PAK16&^DdbBDHQqFZ15n9`DV+|h7E)2LnW~pVI$;r zVDXQ?*1Vqf4kNFf;%&#hXdRj|L#!$f26=wrxR&3`pThKgI8hy1gK{VIlTS*uml(Q^o&A2zKt2-AoORif)R(`MJmVfz@x+r zYVL&U1=K=F+HcoY`c~GrROnv6T3cMj`|E~`l)p9Cc0n?I1rRo>lj{=s6q}ByfdD64DzIO=?{}g%Sdh zyGD3m(1;>m9huT*dVu8zAzN0&n|7xHe>{+>AU z5F6Hp5Xjb_TR~9E&$6LR2Ize&C|iWz-_vQdR+<$on&yi>SY*y0Q~g@ntl?ll6Z<&! zvCCualLwhb5?k|fftOf|j$^LUigr>IOHPT6cRtlDYY{#h<7IewD#x|H56R7(1_@1<0*?7;_ zwX=3C&(}$(np+7! zU9MlFI!(9BmtHlLAH79EVpzfdpXW`}fUfriGpSj+Oa3K#5%pzDcxPkz2bg03h;@#9 zr2|-_f8>X}I}j(zmvUF{QV;+Z?eoHwsjhtj0uce0_WO1bo?#7mE1)SNqng17EteE^ zxbgF^4Dy}^NS1{9BX8d7qR1qvmkTmzL&Cq<5~1?Mu!QXR_dlGBJ-PK(G@1^Vi`1^M zo!)I54?aV^?9!T0qZr(U{81Yju4=54+yOuos8N;sj4KgqrrO3fzOuo(N%yW2!40Db zlmfc3j#l!bdE5ujV#*?Ew|N0y#gyfR^FTB8(o0gCt4j4vC_jsj3bY?8 zq_A9I|8L~>S*v-K>EZ4(%N5xn1tMRe7v*#9;iq%C3-c$R_v28@*;4j)$>uZDzGi9y zNfsz4hUiHHxjft568`Ei-E&N6C5qlCz0o?A+(y{WLw>Lgal4$l-)zCk=@3C}(UbVux zE%RB@P&Vw1U}JizdCUN56a~Ks|NqCLdyvmv*C*$A_QK%q4vn^CU_+(Fpl=uqaf#rM zn0$GjUcfJYBzW5Hub^aX=Mso=^BjHu z-Nr3=>?M}>v>#=P`ix!MS^hf&gxYeKmSp`Lug^C3Y(hF9(3BFx>*vA?E1rrG4!Ctd zOX|#t2s~yo;Y&~({2wcsPJO?O&4DqiU7C4Yqk^FEyYA!;%a7Ne}aESyACCtdLLpZ~%Y+ zXrxCtL}$xK*s7*_v1k%m<0IGkL@dWl{$d2n&fdZIbvHC2g#fw=dm7*tBajctQcpD!MF4t>utdTsvQC4{s` zTYUhj?RVVsi5p6NoxD2~RPN~X{hUFXCmOumB12BHCej1p0#Ev#VGXU0(DOr&)XxW; zH_uNwIiXlq{wgu_1Bz!Nos!aUPFpZ=R}V2=e6SirKJyfmLM22rY{0kThPE)NFb?l8 ze2HY)Wu|fyYJ6042X(omd&GBDIC##4Uzr$SlBL8ONPW!~`YmMNDetjzEF= zK+TcEi}299!QJQIEGaPu1KmbN`PB7CYtvqk!gp!DI2o8&WN>(uepke;z95)rwmR;8e1$ffX=YM$r6ix%)v9T! zqmgPqcq>>s$6t$0d*cuVV1l(aL!cJ$2)a%4*JJ30CPIp2+sA?ZnF)7GfenZj!_(SK z<#$fvz^Yt8LvX42Ncwoc7F%8ms1$;hMRG}@+L1$Nb?6xR_buqm#3~Up6goF7bF(xrrz1hznLxKnjj}o8DCZ`>7U@L{zvB}`@g~e1BPg^MgRZ+ literal 0 HcmV?d00001 diff --git a/engine/src/core-data/Common/MatDefs/Water/Water.frag b/engine/src/core-data/Common/MatDefs/Water/Water.frag index 89c98e24f..5cf91d504 100644 --- a/engine/src/core-data/Common/MatDefs/Water/Water.frag +++ b/engine/src/core-data/Common/MatDefs/Water/Water.frag @@ -8,6 +8,7 @@ uniform sampler2D m_Texture; uniform sampler2D m_DepthTexture; uniform sampler2D m_NormalMap; uniform sampler2D m_FoamMap; +uniform sampler2D m_CausticsMap; uniform sampler2D m_ReflectionMap; uniform mat4 m_ViewProjectionMatrixInverse; @@ -114,6 +115,112 @@ float fresnelTerm(in vec3 normal,in vec3 eyeVec){ return saturate(fresnel * (1.0 - saturate(m_R0)) + m_R0 - m_RefractionStrength); } +vec2 m_FrustumNearFar=vec2(1.0,50); +const float LOG2 = 1.442695; + +vec4 underWater(){ + + + float sceneDepth = texture2D(m_DepthTexture, texCoord).r; + vec3 color2 = texture2D(m_Texture, texCoord).rgb; + + vec3 position = getPosition(sceneDepth, texCoord); + float level = m_WaterHeight; + + vec3 eyeVec = position - m_CameraPosition; + + // Find intersection with water surface + vec3 eyeVecNorm = normalize(eyeVec); + float t = (level - m_CameraPosition.y) / eyeVecNorm.y; + vec3 surfacePoint = m_CameraPosition + eyeVecNorm * t; + + vec2 texC = vec2(0.0); + + float cameraDepth = length(m_CameraPosition - surfacePoint); + texC = (surfacePoint.xz + eyeVecNorm.xz) * scale + m_Time * 0.03 * m_WindDirection; + float bias = texture(m_HeightMap, texC).r; + level += bias * m_MaxAmplitude; + t = (level - m_CameraPosition.y) / eyeVecNorm.y; + surfacePoint = m_CameraPosition + eyeVecNorm * t; + eyeVecNorm = normalize(m_CameraPosition - surfacePoint); + + // Find normal of water surface + float normal1 = textureOffset(m_HeightMap, texC, ivec2(-1, 0)).r; + float normal2 = textureOffset(m_HeightMap, texC, ivec2( 1, 0)).r; + float normal3 = textureOffset(m_HeightMap, texC, ivec2( 0, -1)).r; + float normal4 = textureOffset(m_HeightMap, texC, ivec2( 0, 1)).r; + + vec3 myNormal = normalize(vec3((normal1 - normal2) * m_MaxAmplitude,m_NormalScale,(normal3 - normal4) * m_MaxAmplitude)); + vec3 normal = myNormal*-1.0; + float fresnel = fresnelTerm(normal, eyeVecNorm); + + vec3 refraction = color2; + #ifdef ENABLE_REFRACTION + texC = texCoord.xy *sin (fresnel+1.0); + refraction = texture2D(m_Texture, texC).rgb; + #endif + + float waterCol = saturate(length(m_LightColor.rgb) / m_SunScale); + refraction = mix(mix(refraction, m_DeepWaterColor.rgb * waterCol, m_WaterTransparency), m_WaterColor.rgb* waterCol,m_WaterTransparency); + + vec3 foam = vec3(0.0); + #ifdef ENABLE_FOAM + texC = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * m_WindDirection + sin(m_Time * 0.001 + position.x) * 0.005; + vec2 texCoord2 = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.1 * m_WindDirection + sin(m_Time * 0.001 + position.z) * 0.005; + + if(m_MaxAmplitude - m_FoamExistence.z> 0.0001){ + foam += ((texture2D(m_FoamMap, texC) + texture2D(m_FoamMap, texCoord2)) * m_FoamIntensity * m_FoamIntensity * 0.3 * + saturate((level - (m_WaterHeight + m_FoamExistence.z)) / (m_MaxAmplitude - m_FoamExistence.z))).rgb; + } + foam *= m_LightColor.rgb; + #endif + + + + vec3 specular = vec3(0.0); + vec3 color ; + float fogFactor; + + if(position.y>level){ + #ifdef ENABLE_SPECULAR + if(step(0.9999,sceneDepth)==1.0){ + vec3 lightDir=normalize(m_LightDir); + vec3 mirrorEye = (2.0 * dot(eyeVecNorm, normal) * normal - eyeVecNorm); + float dotSpec = saturate(dot(mirrorEye.xyz, -lightDir) * 0.5 + 0.5); + specular = vec3((1.0 - fresnel) * saturate(-lightDir.y) * ((pow(dotSpec, 512.0)) * (m_Shininess * 1.8 + 0.2))); + specular += specular * 25.0 * saturate(m_Shininess - 0.05); + specular=specular * m_LightColor.rgb * 100.0; + } + #endif + float fogIntensity= 8 * m_WaterTransparency; + fogFactor = exp2( -fogIntensity * fogIntensity * cameraDepth * 0.03 * LOG2 ); + fogFactor = clamp(fogFactor, 0.0, 1.0); + color =mix(m_DeepWaterColor.rgb,refraction,fogFactor); + specular=specular*fogFactor; + color = saturate(color + max(specular, foam )); + }else{ + vec3 caustics = vec3(0.0); + #ifdef ENABLE_CAUSTICS + vec2 windDirection=m_WindDirection; + texC = (position.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * windDirection + sin(m_Time + position.x) * 0.01; + vec2 texCoord2 = (position.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * windDirection + sin(m_Time + position.z) * 0.01; + caustics += (texture2D(m_CausticsMap, texC)+ texture2D(m_CausticsMap, texCoord2)).rgb; + caustics *= m_WaterColor.rgb; + color=mix(color2, caustics,0.6); + #else + color=color2; + #endif + + float fogDepth= (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - sceneDepth* (m_FrustumNearFar.y-m_FrustumNearFar.x)); + float fogIntensity= 18 * m_WaterTransparency; + fogFactor = exp2( -fogIntensity * fogIntensity * fogDepth * fogDepth * LOG2 ); + fogFactor = clamp(fogFactor, 0.0, 1.0); + color =mix(m_DeepWaterColor.rgb,color,fogFactor); + } + + return vec4(color, 1.0); +} + void main(){ float sceneDepth = texture2D(m_DepthTexture, texCoord).r; float isAtFarPlane = step(0.99998, sceneDepth); @@ -125,10 +232,10 @@ void main(){ float level = m_WaterHeight; - // If we are underwater let's leave out complex computations + // If we are underwater let's go to under water function if(level >= m_CameraPosition.y){ - gl_FragColor = vec4(color2, 1.0); - return; + gl_FragColor = underWater(); + return ; } //#ifndef ENABLE_RIPPLES @@ -284,10 +391,6 @@ void main(){ // to calculate the derivatives for all these pixels by using step()! // That way we won't get pixels around the edges of the terrain, // Where the derivatives are undefined -/* float coef=1.0; - if(position.y level){ color = color2; } diff --git a/engine/src/core-data/Common/MatDefs/Water/Water.j3md b/engine/src/core-data/Common/MatDefs/Water/Water.j3md index b1bda5481..4d8a55a68 100644 --- a/engine/src/core-data/Common/MatDefs/Water/Water.j3md +++ b/engine/src/core-data/Common/MatDefs/Water/Water.j3md @@ -4,6 +4,7 @@ MaterialDef Advanced Water { Int NumSamples Int NumSamplesDepth Texture2D FoamMap + Texture2D CausticsMap Texture2D NormalMap Texture2D ReflectionMap Texture2D HeightMap @@ -39,10 +40,12 @@ MaterialDef Advanced Water { Boolean UseHQShoreline Boolean UseSpecular Boolean UseFoam + Boolean UseCaustics Boolean UseRefraction + } - Technique { + Technique { VertexShader GLSL150 : Common/MatDefs/Post/Post15.vert FragmentShader GLSL150 : Common/MatDefs/Water/Water15.frag @@ -51,8 +54,14 @@ MaterialDef Advanced Water { } Defines { - RESOLVE_MS : NumSamples + RESOLVE_MS : NumSamples RESOLVE_DEPTH_MS : NumSamplesDepth + ENABLE_RIPPLES : UseRipples + ENABLE_HQ_SHORELINE : UseHQShoreline + ENABLE_SPECULAR : UseSpecular + ENABLE_FOAM : UseFoam + ENABLE_CAUSTICS : UseCaustics + ENABLE_REFRACTION : UseRefraction } } @@ -68,7 +77,9 @@ MaterialDef Advanced Water { ENABLE_HQ_SHORELINE : UseHQShoreline ENABLE_SPECULAR : UseSpecular ENABLE_FOAM : UseFoam + ENABLE_CAUSTICS : UseCaustics ENABLE_REFRACTION : UseRefraction + } } diff --git a/engine/src/core-data/Common/MatDefs/Water/Water15.frag b/engine/src/core-data/Common/MatDefs/Water/Water15.frag index ee3d74487..8a9d7a736 100644 --- a/engine/src/core-data/Common/MatDefs/Water/Water15.frag +++ b/engine/src/core-data/Common/MatDefs/Water/Water15.frag @@ -12,6 +12,7 @@ uniform DEPTHTEXTURE m_DepthTexture; uniform sampler2D m_HeightMap; uniform sampler2D m_NormalMap; uniform sampler2D m_FoamMap; +uniform sampler2D m_CausticsMap; uniform sampler2D m_ReflectionMap; uniform mat4 m_ViewProjectionMatrixInverse; @@ -38,11 +39,6 @@ uniform vec2 m_WindDirection; uniform float m_SunScale; uniform float m_WaveScale; -uniform bool m_UseRipples, - m_UseHQShoreline, - m_UseSpecular, - m_UseFoam, - m_UseRefraction; vec2 scale = vec2(m_WaveScale, m_WaveScale); float refractionScale = m_WaveScale; @@ -115,8 +111,124 @@ float fresnelTerm(in vec3 normal,in vec3 eyeVec){ return saturate(fresnel * (1.0 - saturate(m_R0)) + m_R0 - m_RefractionStrength); } +vec2 m_FrustumNearFar=vec2(1.0,50); +const float LOG2 = 1.442695; + +vec4 underWater(int sampleNum){ + + + float sceneDepth = fetchTextureSample(m_DepthTexture, texCoord, sampleNum).r; + vec3 color2 = fetchTextureSample(m_Texture, texCoord, sampleNum).rgb; + + vec3 position = getPosition(sceneDepth, texCoord); + float level = m_WaterHeight; + + vec3 eyeVec = position - m_CameraPosition; + + // Find intersection with water surface + vec3 eyeVecNorm = normalize(eyeVec); + float t = (level - m_CameraPosition.y) / eyeVecNorm.y; + vec3 surfacePoint = m_CameraPosition + eyeVecNorm * t; + + vec2 texC = vec2(0.0); + + float cameraDepth = length(m_CameraPosition - surfacePoint); + texC = (surfacePoint.xz + eyeVecNorm.xz) * scale + m_Time * 0.03 * m_WindDirection; + float bias = texture(m_HeightMap, texC).r; + level += bias * m_MaxAmplitude; + t = (level - m_CameraPosition.y) / eyeVecNorm.y; + surfacePoint = m_CameraPosition + eyeVecNorm * t; + eyeVecNorm = normalize(m_CameraPosition - surfacePoint); + + // Find normal of water surface + float normal1 = textureOffset(m_HeightMap, texC, ivec2(-1, 0)).r; + float normal2 = textureOffset(m_HeightMap, texC, ivec2( 1, 0)).r; + float normal3 = textureOffset(m_HeightMap, texC, ivec2( 0, -1)).r; + float normal4 = textureOffset(m_HeightMap, texC, ivec2( 0, 1)).r; + + vec3 myNormal = normalize(vec3((normal1 - normal2) * m_MaxAmplitude,m_NormalScale,(normal3 - normal4) * m_MaxAmplitude)); + vec3 normal = myNormal*-1.0; + float fresnel = fresnelTerm(normal, eyeVecNorm); + + vec3 refraction = color2; + #ifdef ENABLE_REFRACTION + texC = texCoord.xy *sin (fresnel+1.0); + #ifdef RESOLVE_MS + ivec2 iTexC = ivec2(texC * textureSize(m_Texture)); + refraction = texelFetch(m_Texture, iTexC, sampleNum).rgb; + #else + ivec2 iTexC = ivec2(texC * textureSize(m_Texture, 0)); + refraction = texelFetch(m_Texture, iTexC, 0).rgb; + #endif + #endif + + float waterCol = saturate(length(m_LightColor.rgb) / m_SunScale); + refraction = mix(mix(refraction, m_DeepWaterColor.rgb * waterCol, m_WaterTransparency), m_WaterColor.rgb* waterCol,m_WaterTransparency); + + vec3 foam = vec3(0.0); + #ifdef ENABLE_FOAM + texC = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * m_WindDirection + sin(m_Time * 0.001 + position.x) * 0.005; + vec2 texCoord2 = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.1 * m_WindDirection + sin(m_Time * 0.001 + position.z) * 0.005; + + if(m_MaxAmplitude - m_FoamExistence.z> 0.0001){ + foam += ((texture2D(m_FoamMap, texC) + texture2D(m_FoamMap, texCoord2)) * m_FoamIntensity * m_FoamIntensity * 0.3 * + saturate((level - (m_WaterHeight + m_FoamExistence.z)) / (m_MaxAmplitude - m_FoamExistence.z))).rgb; + } + foam *= m_LightColor.rgb; + #endif + + + + vec3 specular = vec3(0.0); + vec3 color ; + float fogFactor; + + if(position.y>level){ + #ifdef ENABLE_SPECULAR + if(step(0.9999,sceneDepth)==1.0){ + vec3 lightDir=normalize(m_LightDir); + vec3 mirrorEye = (2.0 * dot(eyeVecNorm, normal) * normal - eyeVecNorm); + float dotSpec = saturate(dot(mirrorEye.xyz, -lightDir) * 0.5 + 0.5); + specular = vec3((1.0 - fresnel) * saturate(-lightDir.y) * ((pow(dotSpec, 512.0)) * (m_Shininess * 1.8 + 0.2))); + specular += specular * 25.0 * saturate(m_Shininess - 0.05); + specular=specular * m_LightColor.rgb * 100.0; + } + #endif + float fogIntensity= 8 * m_WaterTransparency; + fogFactor = exp2( -fogIntensity * fogIntensity * cameraDepth * 0.03 * LOG2 ); + fogFactor = clamp(fogFactor, 0.0, 1.0); + color =mix(m_DeepWaterColor.rgb,refraction,fogFactor); + specular=specular*fogFactor; + color = saturate(color + max(specular, foam )); + }else{ + vec3 caustics = vec3(0.0); + #ifdef ENABLE_CAUSTICS + vec2 windDirection=m_WindDirection; + texC = (position.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * windDirection + sin(m_Time + position.x) * 0.01; + vec2 texCoord2 = (position.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * windDirection + sin(m_Time + position.z) * 0.01; + caustics += (texture2D(m_CausticsMap, texC)+ texture2D(m_CausticsMap, texCoord2)).rgb; + caustics *= m_WaterColor.rgb; + color=mix(color2, caustics,0.6); + #else + color=color2; + #endif + + float fogDepth= (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - sceneDepth* (m_FrustumNearFar.y-m_FrustumNearFar.x)); + float fogIntensity= 18 * m_WaterTransparency; + fogFactor = exp2( -fogIntensity * fogIntensity * fogDepth * fogDepth * LOG2 ); + fogFactor = clamp(fogFactor, 0.0, 1.0); + color =mix(m_DeepWaterColor.rgb,color,fogFactor); + } + + return vec4(color, 1.0); +} // NOTE: This will be called even for single-sampling vec4 main_multiSample(int sampleNum){ + // If we are underwater let's call the underwater function + if(m_WaterHeight >= m_CameraPosition.y){ + + return underWater(sampleNum); + } float sceneDepth = fetchTextureSample(m_DepthTexture, texCoord, sampleNum).r; vec3 color2 = fetchTextureSample(m_Texture, texCoord, sampleNum).rgb; @@ -125,22 +237,17 @@ vec4 main_multiSample(int sampleNum){ vec3 position = getPosition(sceneDepth, texCoord); float level = m_WaterHeight; - - // If we are underwater let's leave out complex computations - if(level >= m_CameraPosition.y){ - return vec4(color2, 1.0); - } - + float isAtFarPlane = step(0.99998, sceneDepth); //#ifndef ENABLE_RIPPLES // This optimization won't work on NVIDIA cards if ripples are enabled if(position.y > level + m_MaxAmplitude + isAtFarPlane * 100.0){ + return vec4(color2, 1.0); } //#endif - vec3 eyeVec = position - m_CameraPosition; - float diff = level - position.y; + vec3 eyeVec = position - m_CameraPosition; float cameraDepth = m_CameraPosition.y - position.y; // Find intersection with water surface @@ -150,9 +257,9 @@ vec4 main_multiSample(int sampleNum){ vec2 texC = vec2(0.0); int samples = 1; - if (m_UseHQShoreline){ + #ifdef ENABLE_HQ_SHORELINE samples = 10; - } + #endif float biasFactor = 1.0 / samples; for (int i = 0; i < samples; i++){ @@ -187,7 +294,7 @@ vec4 main_multiSample(int sampleNum){ vec3 myNormal = normalize(vec3((normal1 - normal2) * m_MaxAmplitude,m_NormalScale,(normal3 - normal4) * m_MaxAmplitude)); vec3 normal = vec3(0.0); - if (m_UseRipples){ + #ifdef ENABLE_RIPPLES texC = surfacePoint.xz * 0.8 + m_WindDirection * m_Time* 1.6; mat3 tangentFrame = computeTangentFrame(myNormal, eyeVecNorm, texC); vec3 normal0a = normalize(tangentFrame*(2.0 * texture(m_NormalMap, texC).xyz - 1.0)); @@ -213,12 +320,12 @@ vec4 main_multiSample(int sampleNum){ // gl_FragColor = vec4(color2 + normal*0.0001, 1.0); // return; //} - }else{ + #else normal = myNormal; - } + #endif vec3 refraction = color2; - if (m_UseRefraction){ + #ifdef ENABLE_REFRACTION // texC = texCoord.xy+ m_ReflectionDisplace * normal.x; texC = texCoord.xy; texC += sin(m_Time*1.8 + 3.0 * abs(position.y)) * (refractionScale * min(depth2, 1.0)); @@ -229,7 +336,7 @@ vec4 main_multiSample(int sampleNum){ ivec2 iTexC = ivec2(texC * textureSize(m_Texture, 0)); refraction = texelFetch(m_Texture, iTexC, 0).rgb; #endif - } + #endif vec3 waterPosition = surfacePoint.xyz; waterPosition.y -= (level - m_WaterHeight); @@ -249,8 +356,11 @@ vec4 main_multiSample(int sampleNum){ refraction = mix(mix(refraction, m_WaterColor.rgb * waterCol, saturate(depthN / visibility)), m_DeepWaterColor.rgb * waterCol, saturate(depth2 / m_ColorExtinction)); + + + vec3 foam = vec3(0.0); - if (m_UseFoam){ + #ifdef ENABLE_FOAM texC = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * m_WindDirection + sin(m_Time * 0.001 + position.x) * 0.005; vec2 texCoord2 = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.1 * m_WindDirection + sin(m_Time * 0.001 + position.z) * 0.005; @@ -267,10 +377,10 @@ vec4 main_multiSample(int sampleNum){ saturate((level - (m_WaterHeight + m_FoamExistence.z)) / (m_MaxAmplitude - m_FoamExistence.z))).rgb; } foam *= m_LightColor.rgb; - } + #endif vec3 specular = vec3(0.0); - if (m_UseSpecular){ + #ifdef ENABLE_SPECULAR vec3 lightDir=normalize(m_LightDir); vec3 mirrorEye = (2.0 * dot(eyeVecNorm, normal) * normal - eyeVecNorm); float dotSpec = saturate(dot(mirrorEye.xyz, -lightDir) * 0.5 + 0.5); @@ -278,7 +388,7 @@ vec4 main_multiSample(int sampleNum){ specular += specular * 25.0 * saturate(m_Shininess - 0.05); //foam does not shine specular=specular * m_LightColor.rgb - (5.0 * foam); - } + #endif color = mix(refraction, reflection, fresnel); color = mix(refraction, color, saturate(depth * m_ShoreHardness)); diff --git a/engine/src/desktop-fx/com/jme3/post/filters/FogFilter.java b/engine/src/desktop-fx/com/jme3/post/filters/FogFilter.java index 1739756d4..b075476b9 100644 --- a/engine/src/desktop-fx/com/jme3/post/filters/FogFilter.java +++ b/engine/src/desktop-fx/com/jme3/post/filters/FogFilter.java @@ -58,6 +58,12 @@ public class FogFilter extends Filter { super("FogFilter"); } + /** + * Create a fog filter + * @param fogColor the color of the fog (default is white) + * @param fogDensity the density of the fog (default is 0.7) + * @param fogDistance the distance of the fog (default is 1000) + */ public FogFilter(ColorRGBA fogColor, float fogDensity, float fogDistance) { this(); this.fogColor = fogColor; diff --git a/engine/src/desktop-fx/com/jme3/post/filters/LightScatteringFilter.java b/engine/src/desktop-fx/com/jme3/post/filters/LightScatteringFilter.java index 1dda176e0..0056506d7 100644 --- a/engine/src/desktop-fx/com/jme3/post/filters/LightScatteringFilter.java +++ b/engine/src/desktop-fx/com/jme3/post/filters/LightScatteringFilter.java @@ -59,7 +59,7 @@ public class LightScatteringFilter extends Filter { private float lightDensity = 1.4f; private boolean adaptative = true; Vector3f viewLightPos = new Vector3f(); - private boolean display; + private boolean display=true; private float innerLightDensity; public LightScatteringFilter() { @@ -101,6 +101,8 @@ public class LightScatteringFilter extends Filter { //System.err.println("screenLightPos "+screenLightPos); if (adaptative) { innerLightDensity = Math.max(lightDensity - Math.max(screenLightPos.x, screenLightPos.y), 0.0f); + }else{ + innerLightDensity=lightDensity; } } diff --git a/engine/src/desktop-fx/com/jme3/water/WaterFilter.java b/engine/src/desktop-fx/com/jme3/water/WaterFilter.java index 01b66ebae..67e2cdda8 100644 --- a/engine/src/desktop-fx/com/jme3/water/WaterFilter.java +++ b/engine/src/desktop-fx/com/jme3/water/WaterFilter.java @@ -67,6 +67,7 @@ public class WaterFilter extends Filter { protected ViewPort reflectionView; private Texture2D normalTexture; private Texture2D foamTexture; + private Texture2D causticsTexture; private Texture2D heightTexture; private Plane plane; private Camera reflectionCam; @@ -102,11 +103,13 @@ public class WaterFilter extends Filter { private boolean useHQShoreline = true; private boolean useSpecular = true; private boolean useFoam = true; + private boolean useCaustics = true; private boolean useRefraction = true; private float time = 0; private float savedTpf = 0; private float reflectionDisplace = 30; private float foamIntensity = 0.5f; + private boolean underWater; /** * Create a Water Filter @@ -126,11 +129,6 @@ public class WaterFilter extends Filter { return true; } - @Override - protected Format getDefaultPassDepthFormat() { - return Format.Depth; - } - @Override public void preFrame(float tpf) { time = time + (tpf * speed); @@ -181,17 +179,23 @@ public class WaterFilter extends Filter { reflectionCam.setAxes(reflectionCam.getLeft().negateLocal(), reflectionCam.getUp(), reflectionCam.getDirection().negateLocal()); } - boolean rtb = true; - if (!renderManager.isHandleTranslucentBucket()) { - renderManager.setHandleTranslucentBucket(true); - rtb = false; - } - renderManager.renderViewPort(reflectionView, savedTpf); - if (!rtb) { - renderManager.setHandleTranslucentBucket(false); + //if we're under water no need to compute reflection + if (sceneCam.getLocation().y >= waterHeight) { + boolean rtb = true; + if (!renderManager.isHandleTranslucentBucket()) { + renderManager.setHandleTranslucentBucket(true); + rtb = false; + } + renderManager.renderViewPort(reflectionView, savedTpf); + if (!rtb) { + renderManager.setHandleTranslucentBucket(false); + } + renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer()); + renderManager.setCamera(sceneCam, false); + underWater=false; + }else{ + underWater=true; } - renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer()); - renderManager.setCamera(sceneCam, false); } @Override @@ -217,14 +221,19 @@ public class WaterFilter extends Filter { if (foamTexture == null) { foamTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/foam.jpg"); } + if (causticsTexture == null) { + causticsTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/caustics.jpg"); + } heightTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/heightmap.jpg"); normalTexture.setWrap(WrapMode.Repeat); foamTexture.setWrap(WrapMode.Repeat); + causticsTexture.setWrap(WrapMode.Repeat); heightTexture.setWrap(WrapMode.Repeat); material = new Material(manager, "Common/MatDefs/Water/Water.j3md"); material.setTexture("HeightMap", heightTexture); + material.setTexture("CausticsMap", causticsTexture); material.setTexture("FoamMap", foamTexture); material.setTexture("NormalMap", normalTexture); material.setTexture("ReflectionMap", reflectionPass.getRenderedTexture()); @@ -250,6 +259,7 @@ public class WaterFilter extends Filter { material.setBoolean("UseHQShoreline", useHQShoreline); material.setBoolean("UseSpecular", useSpecular); material.setBoolean("UseFoam", useFoam); + material.setBoolean("UseCaustics", useCaustics); material.setBoolean("UseRefraction", useRefraction); material.setFloat("ReflectionDisplace", reflectionDisplace); material.setFloat("FoamIntensity", foamIntensity); @@ -293,6 +303,10 @@ public class WaterFilter extends Filter { this.waterHeight = waterHeight; } + /** + * sets the scene to render in the reflection map + * @param reflectionScene + */ public void setReflectionScene(Spatial reflectionScene) { this.reflectionScene = reflectionScene; } @@ -340,6 +354,10 @@ public class WaterFilter extends Filter { } } + /** + * returns the refractoin constant + * @return + */ public float getRefractionConstant() { return refractionConstant; } @@ -360,6 +378,10 @@ public class WaterFilter extends Filter { } } + /** + * return the maximum wave amplitude + * @return + */ public float getMaxAmplitude() { return maxAmplitude; } @@ -437,7 +459,7 @@ public class WaterFilter extends Filter { } /** - * retunrs the foam hardness + * returns the foam hardness * @return */ public float getFoamHardness() { @@ -739,7 +761,37 @@ public class WaterFilter extends Filter { } /** - * + * sets the texture to use to render caustics on the ground underwater + * @param causticsTexture + */ + public void setCausticsTexture(Texture2D causticsTexture) { + this.causticsTexture = causticsTexture; + if (material != null) { + material.setTexture("causticsMap", causticsTexture); + } + } + + /** + * returns true if caustics are rendered + * @return + */ + public boolean isUseCaustics() { + return useCaustics; + } + + /** + * set to true if you want caustics to be rendered on the ground underwater, false otherwise + * @param useCaustics + */ + public void setUseCaustics(boolean useCaustics) { + this.useCaustics = useCaustics; + if (material != null) { + material.setBoolean("UseCaustics", useCaustics); + } + } + + /** + * return true * @return */ public boolean isUseHQShoreline() { @@ -810,7 +862,15 @@ public class WaterFilter extends Filter { if (material != null) { material.setFloat("m_ReflectionDisplace", reflectionDisplace); } + } - + /** + * returns true if the camera is under the water level + * @return + */ + public boolean isUnderWater() { + return underWater; } + + } diff --git a/engine/src/test/jme3test/water/TestPostWater.java b/engine/src/test/jme3test/water/TestPostWater.java index c76174c2b..0ed88ad68 100644 --- a/engine/src/test/jme3test/water/TestPostWater.java +++ b/engine/src/test/jme3test/water/TestPostWater.java @@ -2,6 +2,8 @@ package jme3test.water; import com.jme3.app.SimpleApplication; import com.jme3.audio.AudioNode; +import com.jme3.audio.Filter; +import com.jme3.audio.LowPassFilter; import com.jme3.bounding.BoundingBox; import com.jme3.effect.ParticleEmitter; import com.jme3.effect.ParticleMesh; @@ -16,7 +18,9 @@ import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.BloomFilter; import com.jme3.post.filters.DepthOfFieldFilter; +import com.jme3.post.filters.FogFilter; import com.jme3.post.filters.LightScatteringFilter; import com.jme3.post.filters.TranslucentBucketFilter; import com.jme3.renderer.Camera; @@ -34,6 +38,7 @@ import com.jme3.texture.Texture.WrapMode; import com.jme3.texture.Texture2D; import com.jme3.util.SkyFactory; import com.jme3.water.WaterFilter; +import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; import jme3tools.converters.ImageToAwt; @@ -48,6 +53,10 @@ public class TestPostWater extends SimpleApplication { private WaterFilter water; TerrainQuad terrain; Material matRock; + AudioNode waves; + LowPassFilter underWaterAudioFilter = new LowPassFilter(0.5f, 0.1f); + LowPassFilter underWaterReverbFilter = new LowPassFilter(0.5f, 0.1f); + LowPassFilter aboveWaterAudioFilter = new LowPassFilter(1, 1); public static void main(String[] args) { TestPostWater app = new TestPostWater(); @@ -57,6 +66,8 @@ public class TestPostWater extends SimpleApplication { @Override public void simpleInitApp() { + setDisplayFps(false); + setDisplayStatView(false); Node mainScene = new Node("Main Scene"); rootNode.attachChild(mainScene); @@ -72,9 +83,14 @@ public class TestPostWater extends SimpleApplication { l.setColor(ColorRGBA.White.clone().multLocal(0.3f)); mainScene.addLight(l); - flyCam.setMoveSpeed(100); + flyCam.setMoveSpeed(50); - cam.setLocation(new Vector3f(-700, 100, 300)); + //cam.setLocation(new Vector3f(-700, 100, 300)); + //cam.setRotation(new Quaternion().fromAngleAxis(0.5f, Vector3f.UNIT_Z)); + cam.setLocation(new Vector3f(-327.21957f, 61.6459f, 126.884346f)); + cam.setRotation(new Quaternion(0.052168474f, 0.9443102f, -0.18395276f, 0.2678024f)); + + cam.setRotation(new Quaternion().fromAngles(new float[]{FastMath.PI * 0.06f, FastMath.PI * 0.65f, 0})); @@ -83,9 +99,8 @@ public class TestPostWater extends SimpleApplication { mainScene.attachChild(sky); cam.setFrustumFar(4000); //cam.setFrustumNear(100); - AudioNode waves = new AudioNode(audioRenderer, assetManager, "Sound/Environment/Ocean Waves.ogg", false); - waves.setLooping(true); - audioRenderer.playSource(waves); + + //private FilterPostProcessor fpp; @@ -94,15 +109,22 @@ public class TestPostWater extends SimpleApplication { FilterPostProcessor fpp = new FilterPostProcessor(assetManager); fpp.addFilter(water); - + BloomFilter bloom=new BloomFilter(); + bloom.setExposurePower(55); + fpp.addFilter(bloom); + LightScatteringFilter lsf = new LightScatteringFilter(lightDir.mult(-300)); + lsf.setLightDensity(1.0f); + fpp.addFilter(lsf); DepthOfFieldFilter dof=new DepthOfFieldFilter(); dof.setFocusDistance(0); - dof.setFocusRange(100); - fpp.addFilter(new TranslucentBucketFilter()); + dof.setFocusRange(100); fpp.addFilter(dof); +// + // fpp.addFilter(new TranslucentBucketFilter()); + // - // fpp.setNumSamples(4); + // fpp.setNumSamples(4); water.setWaveScale(0.003f); @@ -116,8 +138,17 @@ public class TestPostWater extends SimpleApplication { //water.setFoamHardness(0.6f); water.setWaterHeight(initialWaterHeight); - + uw=cam.getLocation().y