Clouds.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. #include "../Shaders/!Header CPU.h"
  4. namespace EE{
  5. /******************************************************************************/
  6. #define LIGHT_DENSITY_FACTOR 0.5f // 0..1, 0.5 gives good results, should be 1 however because volume rendering is not perfect (when iterating samples, voxel texture coordinates may be chosen between between voxels, which may make them look blurry, darker, or brighter)
  7. /******************************************************************************/
  8. static Image detail;
  9. AllClouds Clouds;
  10. /******************************************************************************/
  11. // LAYERED CLOUDS
  12. /******************************************************************************/
  13. LayeredClouds::LayeredClouds()
  14. {
  15. draw_in_mirror=false;
  16. merge_with_sky=MOBILE;
  17. layer[0].color=WHITE; layer[0].scale=1.0f/2.8f; layer[0].position.set(0.00f, 0.0f); layer[0].velocity=0.010f;
  18. layer[1].color=WHITE; layer[1].scale=1.0f/2.4f; layer[1].position.set(0.25f, 0.2f); layer[1].velocity=0.008f;
  19. layer[2].color=WHITE; layer[2].scale=1.0f/2.0f; layer[2].position.set(0.50f, 0.1f); layer[2].velocity=0.006f;
  20. layer[3].color=WHITE; layer[3].scale=1.0f/1.6f; layer[3].position.set(0.75f, 0.8f); layer[3].velocity=0.004f;
  21. frac(0.9f);
  22. _scale_y=1.05f; // set manually instead of calling method to avoid accessing shader param map, which could crash if not yet initialized
  23. _rmc =4 ; // set manually instead of calling method to avoid accessing shader param map, which could crash if not yet initialized
  24. }
  25. void LayeredClouds::del()
  26. {
  27. _mshr.del();
  28. REPAO(layer).image=null;
  29. }
  30. void LayeredClouds::create()
  31. {
  32. {Flt v= scaleY(); _scale_y=-1; scaleY(v);}
  33. {Flt v=rayMaskContrast(); _rmc =-1; rayMaskContrast(v);}
  34. MeshBase mshb;
  35. mshb.createIcoHalf(Ball(1), 0, 3); // 3 give 'dist'=0.982246876
  36. _mshr.create(mshb.reverse());
  37. #if DEBUG && 0 // calculate actual distance
  38. Flt dist=1; C Vec *pos=mshb.vtx.pos();
  39. REPA(mshb.tri ){C VecI &t=mshb.tri .ind(i); MIN(dist, Dist(VecZero, Tri (pos[t.x], pos[t.y], pos[t.z])));}
  40. REPA(mshb.quad){C VecI4 &q=mshb.quad.ind(i); MIN(dist, Dist(VecZero, Quad(pos[q.x], pos[q.y], pos[q.z], pos[q.w])));}
  41. int z=0;
  42. #endif
  43. #define CLOUD_MESH_MIN_DIST 0.98f // it's good to make it a bit smaller than 'dist' to have some epsilon for precision issues, this is the closest point on the mesh to the Vec(0,0,0), it's not equal to radius=1, because the mesh is composed out of triangles, and the triangle surfaces are closer
  44. }
  45. /******************************************************************************/
  46. LayeredClouds& LayeredClouds::set(Byte active_layers, C ImagePtr &image)
  47. {
  48. _layers=Mid(active_layers, 0, 4);
  49. if(image)REP(_layers)layer[i].image=image;
  50. return T;
  51. }
  52. LayeredClouds& LayeredClouds::frac(Flt frac)
  53. {
  54. SAT(frac); T._frac=frac;
  55. return T;
  56. }
  57. LayeredClouds& LayeredClouds::scaleY(Flt scale)
  58. {
  59. Clamp(scale, 1, 2);
  60. if(T._scale_y!=scale)SPSet("LCScaleY", T._scale_y=scale);
  61. return T;
  62. }
  63. LayeredClouds& LayeredClouds::rayMaskContrast(Flt contrast)
  64. {
  65. MAX(contrast, 1);
  66. if(T._rmc!=contrast)SPSet("LCMaskContrast", Vec2(T._rmc=contrast, contrast*-0.5f+0.5f));
  67. return T;
  68. }
  69. /******************************************************************************/
  70. void LayeredClouds::update()
  71. {
  72. REP(_layers)layer[i].position+=layer[i].velocity*Time.d();
  73. }
  74. #pragma pack(push, 4)
  75. struct GpuCloudLayer
  76. {
  77. Vec4 color;
  78. Vec2 scale, position;
  79. };
  80. #pragma pack(pop)
  81. inline void LayeredCloudsFx::load()
  82. {
  83. if(!shader)
  84. {
  85. shader=ShaderFiles("Layered Clouds");
  86. range=GetShaderParam("LCRange");
  87. h_CL[0]=GetShaderParam("CL[0]");
  88. h_CL[1]=GetShaderParam("CL[1]");
  89. h_CL[2]=GetShaderParam("CL[2]");
  90. h_CL[3]=GetShaderParam("CL[3]");
  91. }
  92. }
  93. inline Shader* LayeredCloudsFx::get(Int layers, Bool blend, Bool mask)
  94. {
  95. Shader* &s=h_Clouds[layers][blend][mask]; if(!s)s=shader->get(S+"Clouds"+(layers+1)+(blend?'B':'\0')+(mask?'M':'\0'));
  96. return s;
  97. }
  98. void LayeredClouds::commit()
  99. {
  100. LC.load();
  101. REP(_layers)
  102. {
  103. GpuCloudLayer cl;
  104. cl.color =layer[i].color.asVec4();
  105. cl.scale =layer[i].scale/LCScale;
  106. #if MOBILE
  107. cl.position=Frac(layer[i].position); // mobile devices can have low precision for tex coords, so use fraction to always keep in 0..1 range
  108. #else
  109. cl.position=layer[i].position;
  110. #endif
  111. LC.h_CL[i]->set(cl);
  112. }
  113. Sh.h_ImageCol[0]->set(layer[0].image());
  114. Sh.h_ImageCol[1]->set(layer[1].image());
  115. Sh.h_ImageCol[2]->set(layer[2].image());
  116. Sh.h_ImageCol[3]->set(layer[3].image());
  117. MaterialClear();
  118. }
  119. Bool LayeredClouds::wantDepth()C {return _layers && !merge_with_sky && frac()<1;}
  120. void LayeredClouds::draw()
  121. {
  122. if(_layers && !merge_with_sky && (draw_in_mirror || !Renderer.mirror()))
  123. {
  124. commit();
  125. Renderer.set(Renderer._col(), Renderer._sky_coverage(), null, null, Renderer._ds(), true, WANT_DEPTH_READ); // use DS for depth tests
  126. Flt from=D.viewRange()*frac(),
  127. to =D.viewRange();
  128. MAX(from, FrustumMain.view_quad_max_dist/CLOUD_MESH_MIN_DIST); // make sure we don't intersect with the near plane
  129. Bool blend=(Renderer.canReadDepth1S() && from<to-EPS_SKY_MIN_LERP_DIST); // !! set after 'Renderer.set' !! set blend mode if 'from' is far from 'to', and yes use < and not <= in case of precision issues for big values
  130. if( blend)
  131. {
  132. //Flt sky_opacity=Length(O.pos)*MulAdd.x+MulAdd.y;
  133. // 0= from *MulAdd.x+MulAdd.y;
  134. // 1= to *MulAdd.x+MulAdd.y;
  135. Vec2 mul_add; mul_add.x=1/(to-from); mul_add.y=-from*mul_add.x;
  136. LC.range->setConditional(mul_add);
  137. }
  138. #if !REVERSE_DEPTH
  139. MIN(from, to*EPS_SKY_MIN_VIEW_RANGE);
  140. #endif
  141. SetOneMatrix(MatrixM(from, CamMatrix.pos));
  142. D.alpha (ALPHA_BLEND_DEC);
  143. D.depthWrite(false);
  144. D.depthFunc (FUNC_LESS_EQUAL); // to make sure we draw at the end of viewRange
  145. D.depth (true );
  146. D.cull (true );
  147. D.sampler3D ( );
  148. Shader *shader=LC.get(_layers-1, blend, Renderer._sky_coverage!=null);
  149. REPS(Renderer._eye, Renderer._eye_num)
  150. {
  151. Renderer.setEyeViewport();
  152. shader->begin(); _mshr.set().drawFull(); ShaderEnd();
  153. }
  154. D.sampler2D ();
  155. D.depthWrite(true);
  156. D.depthFunc (FUNC_LESS);
  157. }
  158. }
  159. void LayeredClouds::shadowMap()
  160. {
  161. }
  162. /******************************************************************************/
  163. // VOLUMETRIC CLOUD
  164. /******************************************************************************
  165. Voxel arrangement is set so that height is the first coordinate, to improve cache performance on GPU.
  166. Normally : x=left/right size , y=up/down height , z=forward/back size
  167. However clouds have: up/down height (level), then left/right size (x), then forward/back size (y)
  168. That's why we need to use a CWW sampler (Clamp, Wrap, Wrap).
  169. Building first sets a software copy of the image in the system, memory, so we don't have to make any locks or use extra GPU memory for a secondary image.
  170. Once build is complete, then on the main thread, GPU Image is updated from that software data.
  171. /******************************************************************************/
  172. static Vec LightPos(C VolumetricCloud::Settings &s) // !! assumes that 's.light_pos' is normalized !!
  173. {
  174. Vec pos=s.light_pos;
  175. if( pos.y<SQRT2_2){pos.y=SQRT2_2; pos.normalize();} // this is how build works, it treats all light positions as if they were above the clouds
  176. return pos*s.light_power; // apply light power already
  177. }
  178. static Byte Difference(C VolumetricCloud::Settings &a, C VolumetricCloud::Settings &b) // 0=can be ignored, 1=medium difference (schedule a new build, and use it when finished), 2=big difference (schedule a new build and wait for it to finish)
  179. {
  180. Flt d=Abs ( a.density - b.density )
  181. +Abs ( a.noise_min - b.noise_min )
  182. +Abs ( a.noise_max - b.noise_max )
  183. //+Abs ( a.brightness - b.brightness ) already applied onto ambient and light
  184. +Abs (SqrtFast(a.ambient) - SqrtFast(b.ambient) ) //+Abs(a.ambient - b.ambient ) use Sqrt because changes are more noticeable in the dark
  185. //+Abs ( a.light_power - b.light_power) check light_pos together with light_power in 'LightPos'
  186. +Dist(LightPos(a) , LightPos(b) );
  187. return (d>=0.05f) ? 2 : (d>=1.0f/256 || a.detail!=b.detail) ? 1 : 0; // return difference (0=ignore, 1=update, 2=update now)
  188. }
  189. /******************************************************************************/
  190. static void SetDensityRow(IntPtr elm_index, Ptr user, Int thread_index) {VolumetricCloud &vc=*(VolumetricCloud*)user; vc.setDensityRow(elm_index);}
  191. void VolumetricCloud::setDensityRow(Int z)
  192. {
  193. Vec pos;
  194. pos.y=_level*_noise_scale;
  195. pos.z= z *_noise_scale;
  196. if(_object)REPD(x, width())
  197. {
  198. pos.x=x*_noise_scale;
  199. Flt n=_noise.noise3(pos.x, pos.y, pos.z, 8, _noise_gain);
  200. voxel(x, _level, z).set(n);
  201. }else
  202. REPD(x, width())
  203. {
  204. pos.x=x*_noise_scale;
  205. Flt n=_noise.tiledNoise3(pos.x, pos.y, pos.z, _tile, 8, _noise_gain);
  206. voxel(x, _level, z).set(n);
  207. }
  208. }
  209. static void SetDensity(VolumetricCloud &vc, Ptr user, Int thread_index) {vc.setDensity();}
  210. void VolumetricCloud::setDensity()
  211. {
  212. REPS(_level, height())
  213. {
  214. if(_threads)_threads->process1(depth(), SetDensityRow, this);
  215. else REPD(z, depth())setDensityRow(z);
  216. }
  217. _creating=false; // notify that finished creating
  218. }
  219. /******************************************************************************/
  220. void VolumetricCloud::cancelCreate()
  221. {
  222. if(_creating) // if not finished
  223. {
  224. if(_threads)
  225. {
  226. _threads->cancel(T, SetDensity);
  227. _threads->wait (T, SetDensity);
  228. }
  229. }
  230. }
  231. /******************************************************************************/
  232. #if 1 // this is the best, contrast is not too strong for high density/coverage (noise_min) and doesn't produce dark spots for high density with small coverage (noise_min)
  233. static Flt AOFunc(Flt density) {return 1-SqrtFast(density)*0.31f;}
  234. #elif 1 // contrast too strong
  235. static Flt AOFunc(Flt density) {return 1-(1-Sqr(1-density))*0.31f;}
  236. #else // contrast way too strong
  237. static Flt AOFunc(Flt density) {return 1-density*0.5f;}
  238. #endif
  239. static void SetImageRow(IntPtr elm_index, Ptr user, Int thread_index) {VolumetricCloud &vc=*(VolumetricCloud*)user; vc.setImageRow(elm_index);}
  240. void VolumetricCloud::setImageRow(Int z)
  241. {
  242. if(!_building)return; // if cancel was requested
  243. Int width=T.width(), height=T.height(), depth=T.depth();
  244. Flt *light_cur=_light, *light_prev=light_cur+width*depth; if(_level&1)Swap(light_cur, light_prev); // swap every level
  245. Byte *image_data=_image_data + _image.bytePP()*(_level + z*_pitch); Int image_add=_image.bytePP()*height;
  246. C Voxel *voxel =_voxels + (_level + z*_pitch); Int voxel_add=SIZE(Voxel)*_image.w();
  247. C Voxel *detail[3];
  248. Flt x_mul, x_add, dy2_dz2;
  249. if(_object)
  250. {
  251. x_mul=2.0f/(width+(2-1)); x_add=-1+x_mul; // +2 because we're treating as if we have 2 extra pixels at the start and end, so first/last actual pixels will have some density and not zero (because that's a waste of memory to have all zero pixels)
  252. Flt z_mul=2.0f/(depth+(2-1)), z_add=-1+z_mul; // +2 because we're treating as if we have 2 extra pixels at the start and end, so first/last actual pixels will have some density and not zero (because that's a waste of memory to have all zero pixels)
  253. dy2_dz2=_dy2 + Sqr(z*z_mul + z_add);
  254. }
  255. if(_build.detail)
  256. {
  257. Int yd=_level, zd=z; REP(_build.detail){yd*=2; zd*=2; detail[i]=_voxels + (yd%height + (zd%depth)*_pitch);}
  258. }
  259. FREPD(x, width) // go from the start because of 'image_data' and 'voxel'
  260. {
  261. //if(!_building)return; // if cancel was requested
  262. Flt density;
  263. Flt light;
  264. if(_object)
  265. {
  266. Flt dx=x*x_mul+x_add;
  267. //density=Min(1, voxel->positive()-SqrtFast(dx*dx + dy2_dz2)); // do just "Min(1" instead of 'Sat', because we check anyway if "density>0" below
  268. density=voxel->positive()-SqrtFast(dx*dx + dy2_dz2); // !! if changing the formula then add "Min(1" !! for this formula we don't need "Min(1" because 'voxel->positive' is always 0..1 and from it we subtract
  269. if(_level==0)light=_build.light_power;else // start
  270. {
  271. light=0; REP(_srcs)
  272. {
  273. C Src &src=_src[i];
  274. Int xp=x+src.dir.x, zp=z+src.dir.y;
  275. if(InRange(xp, width) && InRange(zp, depth))light+=src.mul*light_prev[xp + zp*width]; // TODO: diagonals should have extra density scale, as below, with 'light_cur' not being affected by density, however that would be slower
  276. else light+=src.mul*_build.light_power; // if coordinates are out of range, then use full light
  277. }
  278. }
  279. }else
  280. {
  281. density=Min(1, voxel->density*_noise_mul+_noise_add); // do just "Min(1" instead of 'Sat', because we check anyway if "density>0" below
  282. if(_level==0)light=_build.light_power;else // start
  283. {
  284. light=0; REP(_srcs)
  285. {
  286. C Src &src=_src[i];
  287. Int xp=(x+src.dir.x)%width, zp=(z+src.dir.y)%depth;
  288. light+=src.mul*light_prev[xp + zp*width]; // TODO: diagonals should have extra density scale, as below, with 'light_cur' not being affected by density, however that would be slower
  289. // Flt df=1-Pow(1-light_density_factor, Vec(src.dir.x, 1, src.dir.y).length()); this entire line could be precomputed
  290. // light+=src.mul*light_prev[xp + zp*width]*(1-prev_density*df); 'prev_density' must be of previous voxel, not this one !!
  291. }
  292. }
  293. }
  294. Byte a;
  295. if(density>0) // there are a lot of cases which don't have density, so perform some calculations only if we have it
  296. {
  297. if(_build.detail) // add details, this must be done here and not in create, because it depends on 'noise_min,noise_max' which are configurable
  298. {
  299. Int xd=x; Flt mul=density; REP(_build.detail)
  300. {
  301. xd*=2; density+=mul*detail[i][(xd%width)*height].get(); mul*=0.5f; // 'height' is the pitch for X coordinate
  302. }
  303. SAT(density);
  304. }
  305. density*=_sqr_density;
  306. light_cur[x + z*width]=light*(1-density*LIGHT_DENSITY_FACTOR); // store current light for use in next level, adjust by density here already
  307. light+=_build.ambient*AOFunc(density); // this simulates AO (dense voxels are most likely surrounded by others, so ambient light can't reach it much)
  308. a=FltToByte(density);
  309. }else
  310. {
  311. light_cur[x + z*width]=light; // store current light for use in next level
  312. light+=_build.ambient;
  313. a=0;
  314. }
  315. Byte l=FltToByte(light); // no need to mul 'light' by 'brightness' because light and AO were already multiplied by it
  316. #if DX9
  317. switch(_image.bytePP())
  318. {
  319. case 2: image_data[0]=l; image_data[1]=a; break; // L8A8
  320. case 4: image_data[2]=image_data[1]=image_data[0]=l; image_data[3]=a; break; // B8G8R8A8/R8G8B8A8
  321. }
  322. #else
  323. image_data[0]=l; image_data[1]=a; // R8G8/R8G8B8A8
  324. #endif
  325. image_data+=image_add;
  326. voxel=(Voxel*)((Byte*)voxel+voxel_add);
  327. }
  328. }
  329. /******************************************************************************/
  330. static void Build(VolumetricCloud &vc, Ptr user, Int thread_index) {vc.build();}
  331. void VolumetricCloud::build()
  332. {
  333. if(_creating) // if hasn't finished creating
  334. {
  335. // it's possible that create function hasn't started yet, in that case we need to try to cancel it (remove from the queue) and create it ourselves
  336. if(_threads->cancel(T, SetDensity))setDensity(); // if removed from queue (hasn't started yet) then create here
  337. else _threads->wait(T, SetDensity); // if didn't remove, then it means it's processing now, so wait for it
  338. }
  339. // density = Flt(noise-noise_min)/(noise_max-noise_min)
  340. _noise_mul=1/Max(EPS, _build.noise_max-_build.noise_min);
  341. Flt noise_add=-_build.noise_min*_noise_mul, dy_mul=_noise_mul;
  342. if(MEMBER_SIZE(Voxel, density)==1) // if density stored as byte, then adjust '_noise_mul' '_noise_add'
  343. {
  344. // UByteToSFlt(ubyte)*_noise_mul + _noise_add
  345. // (-1+ubyte*(2.0f/255))*_noise_mul + _noise_add
  346. // ubyte*(2.0f/255)*_noise_mul - _noise_mul + _noise_add
  347. noise_add-=_noise_mul;
  348. _noise_mul*=2.0f/255;
  349. }
  350. Flt top=LerpR(PI_4, 0.0f, Acos(_build.light_pos.y)); // same as LerpR(PI_4, PI_2, Asin(light_pos.y));
  351. _srcs=0;
  352. if(_build.light_power>EPS_COL)
  353. {
  354. if(top>0)
  355. {
  356. Src &s=_src[_srcs++];
  357. s.dir.zero();
  358. s.mul=top;
  359. }
  360. Flt bottom=1-top;
  361. if( bottom>0)
  362. {
  363. MIN(bottom, 1);
  364. Vec2 light_pos_xy=_build.light_pos.xz();
  365. VecI2 diagonal(Sign(light_pos_xy.x), Sign(light_pos_xy.y)); if(!diagonal.all())
  366. {
  367. Src &s=_src[_srcs++];
  368. s.dir=diagonal;
  369. s.mul=bottom;
  370. }else
  371. {
  372. Flt a; VecI2 axis;
  373. // TODO: this looks orthogonal, probably have to adjust density factor for diagonal movements as mentioned above, then select which option should be used below
  374. if(1) // produces smoother results
  375. {
  376. light_pos_xy.normalize();
  377. Flt axis_dot;
  378. if(Abs(light_pos_xy.x)>Abs(light_pos_xy.y)){axis.set(diagonal.x, 0); axis_dot=Abs(light_pos_xy.x);}
  379. else {axis.set(0, diagonal.y); axis_dot=Abs(light_pos_xy.y);}
  380. a=LerpR(PI_4, 0.0f, Acos(axis_dot));
  381. }else // this version matches direction "!light_pos_xy == !Lerp(diagonal, axis, a)"
  382. {
  383. if(Abs(light_pos_xy.x)>Abs(light_pos_xy.y))axis.set(diagonal.x, 0);
  384. else axis.set(0, diagonal.y);
  385. VecI2 dir=axis-diagonal, normal=Perp(dir); // here 'dir' and 'normal' are always normalized
  386. Vec2 point=light_pos_xy*DistPointPlaneRay(axis, normal, light_pos_xy); // optimized version of "PointOnPlaneRay(Vec2(0), axis, normal, light_pos_xy);"
  387. a=DistPointPlane(point, diagonal, dir);
  388. }
  389. if(a>0)
  390. {
  391. Src &s=_src[_srcs++];
  392. s.dir=axis;
  393. s.mul=bottom*a;
  394. }
  395. if(a<1)
  396. {
  397. Src &s=_src[_srcs++];
  398. s.dir=diagonal;
  399. s.mul=bottom*(1-a);
  400. }
  401. }
  402. }
  403. if(!_object)REP(_srcs) // coords must be >=0, so we can use only % in the loop without checking for sign
  404. {
  405. Src &src=_src[i];
  406. if(src.dir.x<0)src.dir.x+=width();
  407. if(src.dir.y<0)src.dir.y+=depth();
  408. }
  409. }
  410. _sqr_density=Sqr(_build.density); // use 'Sqr' because it represents density transitions more naturally, if this 'Sqr' would be removed, then 'Difference' should compare 'Sqrt' of densities "Abs(SqrtFast(a.density) - SqrtFast(b.density))"
  411. Flt y_mul=2.0f/(height()+(2-1)), y_add=-1+y_mul; // +2 because we're treating as if we have 2 extra pixels at the start and end, so first/last actual pixels will have some density and not zero (because that's a waste of memory to have all zero pixels)
  412. FREPS(_level, height())
  413. {
  414. if(!_building)return; // if cancel was requested
  415. Flt dy=_level*y_mul+y_add; _dy2=Sqr(dy);
  416. _noise_add=noise_add-dy_mul* // this is only for !object, because object ignores '_noise_add'
  417. Sqr(LerpRS(0.75f, 1.0f, Abs(dy))); // the best, alternatives: LerpRS(0.65f, 1.0f, _dy2), LerpRS(0.8f, 1.0f, Abs(dy))
  418. if(_threads)_threads->process1(depth(), SetImageRow, this);
  419. else REPD(z, depth())setImageRow(z);
  420. }
  421. _build_finished=true;
  422. }
  423. /******************************************************************************/
  424. void VolumetricCloud::cancelBuild()
  425. {
  426. if(_building)
  427. {
  428. _building=0; // set this ASAP so functions can break/return
  429. if(_threads)
  430. {
  431. _threads->cancel(T, Build);
  432. // TODO: there may be 'SetImageRow' processing that could be cancelled here, however there's no way currently to cancel 'Threads.process', so instead, functions check for '_building' and break/return
  433. _threads->wait (T, Build);
  434. }
  435. _build_finished=false;
  436. }
  437. }
  438. /******************************************************************************/
  439. VolumetricCloud::VolumetricCloud() {zero();}
  440. void VolumetricCloud::zero()
  441. {
  442. _build_finished=_creating=_object=false;
  443. _building=_srcs=0;
  444. _pitch=_level=0;
  445. _noise_scale=_noise_gain=_noise_mul=_noise_add=_sqr_density=_dy2=0;
  446. _tile.zero();
  447. _voxels=null;
  448. _light=null;
  449. _image_data=null;
  450. _threads=null;
  451. _cur.zero(); _build.zero();
  452. }
  453. void VolumetricCloud::del()
  454. {
  455. cancelBuild (); // cancel build first because it can create too
  456. cancelCreate();
  457. Free(_voxels);
  458. _image.del();
  459. zero();
  460. }
  461. void VolumetricCloud::create (Int size , Int height, Int frequency, Threads *threads, UInt seed, Flt noise_gain) {createEx(size , height, size , frequency, threads, seed, noise_gain, false);}
  462. void VolumetricCloud::createAsObject(Int width, Int height, Int depth, Flt frequency, Threads *threads, UInt seed, Flt noise_gain) {createEx(width, height, depth, frequency, threads, seed, noise_gain, true );}
  463. void VolumetricCloud::createEx (Int width, Int height, Int depth, Flt frequency, Threads *threads, UInt seed, Flt noise_gain, Bool object)
  464. {
  465. del();
  466. if(width>0 && height>0 && depth>0)
  467. if(_image.create3DTry(height, width, depth, DX9 ? IMAGE_L8A8 : IMAGE_R8G8, 1))
  468. {
  469. _image.clear();
  470. _object=object;
  471. _pitch=height*width;
  472. Int voxels=_pitch*depth,
  473. voxels_size=SIZE(Voxel)*voxels,
  474. light_size=SIZE(Flt )*2*width*depth, // 2 levels (current and previous)
  475. image_size=_image.bytePP()*voxels;
  476. _voxels =(Voxel*)Alloc(voxels_size+light_size+image_size);
  477. _light =(Flt*)((Byte*)_voxels+voxels_size);
  478. _image_data= (Byte*)_light + light_size;
  479. _noise=seed;
  480. _noise_gain=noise_gain;
  481. if(_object)_noise_scale=frequency;else
  482. {
  483. Int freq=Round(frequency);
  484. _noise_scale=Flt(freq)/width;
  485. _tile.set(freq, Trunc(height*_noise_scale)*2+1, freq); // set 'tile.y' to bigger than max, to disable wrapping level coordinates
  486. }
  487. _threads=threads;
  488. _creating=true; // set before create so it can clear it when finished
  489. if(_threads)_threads->queue(T, SetDensity);else setDensity(); // call this last, once all members are set
  490. }
  491. }
  492. /******************************************************************************/
  493. void VolumetricCloud::update(C Settings &settings)
  494. {
  495. Settings temp;
  496. temp.detail =Mid(settings.detail, 0, 3);
  497. temp.density =Sat(settings.density);
  498. temp.noise_min =Mid(settings.noise_min, -2.0f, 2.0f);
  499. temp.noise_max =Mid(settings.noise_max, -2.0f, 2.0f);
  500. temp.brightness =Sat(settings.brightness);
  501. temp.ambient =Sat(settings.ambient);
  502. temp.light_power=Sat(settings.light_power);
  503. temp.light_pos = settings.light_pos; temp.light_pos.normalize(); // build funtion and 'LightPos' assume it's normalized
  504. temp.ambient *=temp.brightness;
  505. temp.light_power*=temp.brightness;
  506. Byte difference=Difference(temp, _cur);
  507. if( !difference)cancelBuild();else // no build needed
  508. { // want build
  509. if(_building) // already building
  510. {
  511. if(Difference(temp, _build)<=1) // if similar to what we're already building, here we check 0 and 1, because for 1 there's no hurry, so it can be built in the future
  512. {_building=difference; return;} // adjust building before returning
  513. cancelBuild();
  514. }
  515. _build=temp; _building=difference; // remember what kind of build we want, before build starts
  516. if(_threads)_threads->queue(T, Build);else build();
  517. }
  518. }
  519. /******************************************************************************/
  520. void VolumetricCloud::checkBuild()
  521. {
  522. if(_building)
  523. {
  524. again:
  525. if(_build_finished) // check if finished first
  526. {
  527. _building=0;
  528. _build_finished=false;
  529. _image.setFrom(_image_data, _image.w()*_image.bytePP());
  530. _cur=_build;
  531. }else
  532. if(_building==2) // have to wait
  533. {
  534. SyncUnlocker unlocker(D._lock); // this may be called in Draw where we have display lock, so unlock it before waiting, so other threads may use it while we wait, this is also needed to avoid potential deadlocks
  535. _threads->wait(T, Build);
  536. goto again;
  537. }
  538. }
  539. }
  540. /******************************************************************************/
  541. void VolumetricCloud::draw(Flt size, C VecD &pos)
  542. {
  543. if(_cur.density>0 || _building)
  544. {
  545. OBox obox;
  546. obox.matrix.x.set(0, -1, 0); // rotated XY
  547. obox.matrix.y.set(1, 0, 0); // rotated XY
  548. obox.matrix.z.set(0, 0, 1);
  549. obox.matrix.pos=pos;
  550. size/=_image.size3().max();
  551. Flt l=_image.w()*size; obox.box.setX(-l, l);
  552. l=_image.h()*size; obox.box.setY(-l, l);
  553. l=_image.d()*size; obox.box.setZ(-l, l);
  554. if(Frustum(obox))
  555. {
  556. checkBuild();
  557. _image.drawVolume(WHITE, TRANSPARENT, obox, 1, 1, 2, 1024);
  558. }
  559. }
  560. }
  561. /******************************************************************************/
  562. // VOLUMETRIC CLOUDS
  563. /******************************************************************************/
  564. VolumetricClouds::VolumetricClouds()
  565. {
  566. draw_in_mirror=false;
  567. res_h=1080/2;
  568. size =100;
  569. curve =0.05f;
  570. tex_scale=0.5f;
  571. shadow =0.35f;
  572. color =1;
  573. pos.zero();
  574. }
  575. /******************************************************************************/
  576. Bool VolumetricClouds::drawable()C {return (cloud._cur.density>0 || cloud._building) && (draw_in_mirror || !Renderer.mirror());} // check if already has density, or is building something, so we can call 'checkBuild' later that will perform updates
  577. /******************************************************************************/
  578. #pragma pack(push, 4)
  579. struct GpuCloud
  580. {
  581. Flt AC4_bottom,
  582. AC4_top ,
  583. A2_inv ,
  584. max_steps ;
  585. Vec2 pos ;
  586. Vec pixels ;
  587. };
  588. struct GpuCloudMap
  589. {
  590. Flt curve, curve2, height, thickness, tex_scale, steps, shadow;
  591. Vec2 pos;
  592. Vec cam;
  593. };
  594. #pragma pack(pop)
  595. /******************************************************************************
  596. clouds = -Cloud.curve*x*x + Cloud.height
  597. ray = m*x = dir.y/Length(dir.xz)*x
  598. m*x = -Cloud.curve*x*x + Cloud.height
  599. Cloud.curve*x*x + m*x - Cloud.height = 0
  600. A=Cloud.curve, B=m, C=-Cloud.height
  601. Delta=B*B - 4*A*C = m*m + 4*Cloud.curve*Cloud.height
  602. SqrtDelta=Sqrt(m*m + 4*Cloud.curve*Cloud.height)
  603. x0=(-m-SqrtDelta)/(2*Cloud.curve)
  604. x1=(-m+SqrtDelta)/(2*Cloud.curve)
  605. x1=(SqrtDelta-m)/(2*Cloud.curve)
  606. /******************************************************************************/
  607. void VolumetricClouds::draw()
  608. {
  609. if(drawable())
  610. {
  611. VolCloud.load();
  612. VecI2 res;
  613. res.y=Max(1, Min(Renderer.fxH(), res_h)); // do Min first and Max last
  614. res.x=Max(1, Round(res.y*D._unscaled_size.div())); // calculate proportionally to 'res.y' and current mode aspect (do not use 'D.aspectRatio' because that's the entire monitor screen aspect, and not application window), all of this is needed because we need to have square pixels for motion blur render targets, however the main application resolution may not have square pixels
  615. ImageRTPtr dest(ImageRTDesc(res.x, res.y, IMAGERT_TWO)); // here Red is for brightness, Green is used for density/opacity
  616. Renderer.set(dest(), null, false); D.alpha(ALPHA_NONE);
  617. GpuCloud c;
  618. Flt curve =Max(1.0f/512, T.curve)/size, // use Min because smaller values caused artifacts when looking up (at the top point of the cloud dome)
  619. height=Max(1, size+pos.y-ActiveCam.matrix.pos.y); // shader currently doesn't support being inside/above the clouds
  620. c.AC4_bottom=4*curve*(height ); // bottom cloud layer (under clouds)
  621. c.AC4_top =4*curve*(height+size*Flt(cloud.height())/(cloud.width()*tex_scale)); // top cloud layer (above clouds)
  622. c.A2_inv =tex_scale*0.5f/(curve*size); // 0.5f/curve; // 1/(2*curve);
  623. Flt s=tex_scale/size; c.pos.set(ActiveCam.matrix.pos.x*s-pos.x, ActiveCam.matrix.pos.z*s-pos.z);
  624. c.max_steps=cloud.height()*2;
  625. c.pixels =cloud._image.size3();
  626. VolCloud.h_Cloud->set(c);
  627. cloud.checkBuild(); // check if there are any finished image builds
  628. Sh.h_ImageVol[0]->set(cloud._image); Sh.h_ImageVol[0]->_sampler=&SamplerLinearCWW;
  629. Sh.h_ImageVol[1]->_sampler=&SamplerLinearWrap;
  630. Rect ext_rect, *rect=null; // set rect, after setting render target
  631. if(!D._view_main.full){ext_rect=D.viewRect(); rect=&ext_rect.extend(Renderer.pixelToScreenSize(1));} // when not rendering entire viewport, then extend the rectangle, add +1 because of texture filtering, have to use 'Renderer.pixelToScreenSize' and not 'D.pixelToScreenSize'
  632. VolCloud.h_Clouds->draw(null, rect);
  633. Sh.h_ImageVol[0]->_sampler=null;
  634. Sh.h_ImageVol[1]->_sampler=null;
  635. Renderer.set(Renderer._col(), null, true);
  636. D.alpha(ALPHA_BLEND_DEC);
  637. Flt to=D.viewRange(), from=Min(to*Sky.frac(), to-0.01f);
  638. Vec2 mul_add; mul_add.x=1/(to-from); mul_add.y=-from*mul_add.x;
  639. Sh.h_SkyFracMulAdd->set(mul_add);
  640. Sh.h_Color[0]->set(color);
  641. VolCloud.h_CloudsDraw->draw(dest());
  642. }
  643. }
  644. void VolumetricClouds::shadowMap()
  645. {
  646. if(drawable() && Renderer._cld_map.is())
  647. {
  648. VolCloud.load();
  649. GpuCloudMap c;
  650. c.curve =Max(1.0f/512, T.curve)/size; // use Min because smaller values caused artifacts when looking up (at the top point of the cloud dome)
  651. c.curve2=c.curve*2;
  652. c.height=Max(1, size+pos.y-ActiveCam.matrix.pos.y); // shader currently doesn't support being inside/above the clouds
  653. c.thickness=size*Flt(cloud.height())/(cloud.width()*tex_scale);
  654. c.tex_scale=tex_scale/size;
  655. c.steps =cloud.height();
  656. c.shadow=shadow;
  657. Flt s=tex_scale/size; c.pos.set(ActiveCam.matrix.pos.x*s-pos.x, ActiveCam.matrix.pos.z*s-pos.z);
  658. c.cam =CamMatrix.pos-ActiveCam.matrix.pos; // calculate current camera (light) position relative to the main camera
  659. VolCloud.h_CloudMap->set(c);
  660. cloud.checkBuild(); // check if there are any finished image builds
  661. Sh.h_ImageVol[0]->set(cloud._image);
  662. Sh.h_ImageVol[0]->_sampler=&SamplerLinearCWW; VolCloud.h_CloudsMap->draw();
  663. Sh.h_ImageVol[0]->_sampler=null;
  664. }
  665. }
  666. /******************************************************************************/
  667. void AllClouds::drawAll()
  668. {
  669. if(draw)
  670. {
  671. if(Renderer.canReadDepth())
  672. {
  673. volumetric.draw();
  674. }
  675. layered.draw();
  676. if(Renderer.canReadDepth())
  677. {
  678. Sky.setFracMulAdd();
  679. Renderer.set(Renderer._col(), Renderer._sky_coverage(), null, null, Renderer._ds(), true, WANT_DEPTH_READ); Renderer.setDSLookup(); // we may use soft cloud, 'setDSLookup' after 'set'
  680. D.alpha (ALPHA_BLEND_DEC);
  681. D.depthWrite(false); REPS(Renderer._eye, Renderer._eye_num){Renderer.setEyeViewport(); Renderer.mode(RM_CLOUD); Renderer._render();}
  682. D.depthWrite(true );
  683. }
  684. }
  685. }
  686. /******************************************************************************/
  687. }
  688. /******************************************************************************/