GLS.Atmosphere.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. //
  2. // The graphics engine GLXEngine. The unit of GLScene for Delphi
  3. //
  4. unit GLS.Atmosphere;
  5. (*
  6. This unit contains classes that imitate an atmosphere around a planet.
  7. 1) Eats a lot of CPU (reduces FPS from 1240 to 520 on my PC with cSlices=100)
  8. 2) Alpha in LowAtmColor, HighAtmColor is ignored.
  9. *)
  10. interface
  11. {$I Stage.Defines.inc}
  12. uses
  13. Winapi.OpenGL,
  14. System.SysUtils,
  15. System.Classes,
  16. Stage.OpenGLTokens,
  17. GLS.Scene,
  18. GLS.Objects,
  19. GLS.Cadencer,
  20. Stage.VectorTypes,
  21. Stage.VectorGeometry,
  22. GLS.Context,
  23. Stage.Strings,
  24. GLS.Color,
  25. GLS.RenderContextInfo,
  26. GLS.State;
  27. type
  28. EGLAtmosphereException = class(Exception);
  29. (*
  30. With atmosphere options:
  31. - aabmOneMinusSrcAlpha is transparent to other objects,
  32. but has problems, which are best seen when the Atmosphere radius is big;
  33. - bmOneMinusDstColor atmosphere doesn't have these problems, but offers
  34. limited transparency (when you look closely on the side).
  35. *)
  36. TGLAtmosphereBlendingMode = (abmOneMinusDstColor, abmOneMinusSrcAlpha);
  37. // This class imitates an atmosphere around a planet
  38. TGLCustomAtmosphere = class(TGLBaseSceneObject)
  39. private
  40. // Used in DoRenderl
  41. cosCache, sinCache: array of Single;
  42. pVertex, pColor: PVectorArray;
  43. FSlices: Integer;
  44. FBlendingMode: TGLAtmosphereBlendingMode;
  45. FPlanetRadius: Single;
  46. FAtmosphereRadius: Single;
  47. FOpacity: Single;
  48. FLowAtmColor: TGLColor;
  49. FHighAtmColor: TGLColor;
  50. FSun: TGLBaseSceneObject;
  51. procedure SetSun(const Value: TGLBaseSceneObject);
  52. procedure SetAtmosphereRadius(const Value: Single);
  53. procedure SetPlanetRadius(const Value: Single);
  54. procedure EnableGLBlendingMode(StateCache: TGLStateCache);
  55. function StoreAtmosphereRadius: Boolean;
  56. function StoreOpacity: Boolean;
  57. function StorePlanetRadius: Boolean;
  58. procedure SetSlices(const Value: Integer);
  59. procedure SetLowAtmColor(const AValue: TGLColor);
  60. procedure SetHighAtmColor(const AValue: TGLColor);
  61. function StoreLowAtmColor: Boolean;
  62. function StoreHighAtmColor: Boolean;
  63. protected
  64. procedure Notification(AComponent: TComponent;
  65. Operation: TOperation); override;
  66. public
  67. property Sun: TGLBaseSceneObject read FSun write SetSun;
  68. property Slices: Integer read FSlices write SetSlices default 60;
  69. property Opacity: Single read FOpacity write FOpacity stored StoreOpacity;
  70. // AtmosphereRadius > PlanetRadius!
  71. property AtmosphereRadius: Single read FAtmosphereRadius
  72. write SetAtmosphereRadius stored StoreAtmosphereRadius;
  73. property PlanetRadius: Single read FPlanetRadius write SetPlanetRadius
  74. stored StorePlanetRadius;
  75. // Use value slightly lower than actual radius, for antialiasing effect
  76. property LowAtmColor: TGLColor read FLowAtmColor write SetLowAtmColor
  77. stored StoreLowAtmColor;
  78. property HighAtmColor: TGLColor read FHighAtmColor write SetHighAtmColor
  79. stored StoreHighAtmColor;
  80. property BlendingMode: TGLAtmosphereBlendingMode read FBlendingMode
  81. write FBlendingMode default abmOneMinusSrcAlpha;
  82. procedure SetOptimalAtmosphere(const ARadius: Single); // absolute
  83. procedure SetOptimalAtmosphere2(const ARadius: Single); // relative
  84. procedure TogleBlendingMode; // changes between 2 blending modes
  85. // Standard component stuff
  86. procedure Assign(Source: TPersistent); override;
  87. constructor Create(AOwner: TComponent); override;
  88. destructor Destroy; override;
  89. // Main rendering procedure
  90. procedure DoRender(var rci: TGLRenderContextInfo;
  91. renderSelf, renderChildren: Boolean); override;
  92. // Used to determine extents
  93. function AxisAlignedDimensionsUnscaled: TGLVector; override;
  94. end;
  95. TGLAtmosphere = class(TGLCustomAtmosphere)
  96. published
  97. property Sun;
  98. property Slices;
  99. property Opacity;
  100. property AtmosphereRadius;
  101. property PlanetRadius;
  102. property LowAtmColor;
  103. property HighAtmColor;
  104. property BlendingMode;
  105. property Position;
  106. property ObjectsSorting;
  107. property ShowAxes;
  108. property Visible;
  109. property OnProgress;
  110. property Behaviours;
  111. property Effects;
  112. end;
  113. // ---------------------------------------------------------------------
  114. implementation
  115. // ---------------------------------------------------------------------
  116. const
  117. EPS = 0.0001;
  118. cIntDivTable: array [2 .. 20] of Single = (1 / 2, 1 / 3, 1 / 4, 1 / 5, 1 / 6,
  119. 1 / 7, 1 / 8, 1 / 9, 1 / 10, 1 / 11, 1 / 12, 1 / 13, 1 / 14, 1 / 15, 1 / 16,
  120. 1 / 17, 1 / 18, 1 / 19, 1 / 20);
  121. procedure TGLCustomAtmosphere.SetOptimalAtmosphere(const ARadius: Single);
  122. begin
  123. FAtmosphereRadius := ARadius + 0.25;
  124. FPlanetRadius := ARadius - 0.07;
  125. end;
  126. procedure TGLCustomAtmosphere.SetOptimalAtmosphere2(const ARadius: Single);
  127. begin
  128. FAtmosphereRadius := ARadius + ARadius / 15;
  129. FPlanetRadius := ARadius - ARadius / 50;
  130. end;
  131. constructor TGLCustomAtmosphere.Create(AOwner: TComponent);
  132. begin
  133. inherited;
  134. FLowAtmColor := TGLColor.Create(Self);
  135. FHighAtmColor := TGLColor.Create(Self);
  136. FOpacity := 2.1;
  137. SetSlices(60);
  138. FAtmosphereRadius := 3.55;
  139. FPlanetRadius := 3.395;
  140. FLowAtmColor.Color := VectorMake(1, 1, 1, 1);
  141. FHighAtmColor.Color := VectorMake(0, 0, 1, 1);
  142. FBlendingMode := abmOneMinusSrcAlpha;
  143. end;
  144. destructor TGLCustomAtmosphere.Destroy;
  145. begin
  146. FreeAndNil(FLowAtmColor);
  147. FreeAndNil(FHighAtmColor);
  148. FreeMem(pVertex);
  149. FreeMem(pColor);
  150. inherited;
  151. end;
  152. procedure TGLCustomAtmosphere.DoRender(var rci: TGLRenderContextInfo;
  153. renderSelf, renderChildren: Boolean);
  154. var
  155. radius, invAtmosphereHeight: Single;
  156. sunPos, eyePos, lightingVector: TGLVector;
  157. diskNormal, diskRight, diskUp: TGLVector;
  158. function AtmosphereColor(const rayStart, rayEnd: TGLVector): TGLColorVector;
  159. var
  160. I, n: Integer;
  161. atmPoint, normal: TGLVector;
  162. altColor: TGLColorVector;
  163. alt, rayLength, contrib, decay, intensity, invN: Single;
  164. begin
  165. Result := clrTransparent;
  166. rayLength := VectorDistance(rayStart, rayEnd);
  167. n := Round(3 * rayLength * invAtmosphereHeight) + 2;
  168. if n > 10 then
  169. n := 10;
  170. invN := cIntDivTable[n]; // 1/n;
  171. contrib := rayLength * invN * Opacity;
  172. decay := 1 - contrib * 0.5;
  173. contrib := contrib * (1 / 1.1);
  174. for I := n - 1 downto 0 do
  175. begin
  176. VectorLerp(rayStart, rayEnd, I * invN, atmPoint);
  177. // diffuse lighting normal
  178. normal := VectorNormalize(atmPoint);
  179. // diffuse lighting intensity
  180. intensity := VectorDotProduct(normal, lightingVector) + 0.1;
  181. if PInteger(@intensity)^ > 0 then
  182. begin
  183. // sample on the lit side
  184. intensity := intensity * contrib;
  185. alt := (VectorLength(atmPoint) - FPlanetRadius) * invAtmosphereHeight;
  186. VectorLerp(LowAtmColor.Color, HighAtmColor.Color, alt, altColor);
  187. Result.X := Result.X * decay + altColor.X * intensity;
  188. Result.Y := Result.Y * decay + altColor.Y * intensity;
  189. Result.Z := Result.Z * decay + altColor.Z * intensity;
  190. end
  191. else
  192. begin
  193. // sample on the dark sid
  194. Result.X := Result.X * decay;
  195. Result.Y := Result.Y * decay;
  196. Result.Z := Result.Z * decay;
  197. end;
  198. end;
  199. Result.W := n * contrib * Opacity * 0.1;
  200. end;
  201. function ComputeColor(var rayDest: TGLVector; mayHitGround: Boolean)
  202. : TGLColorVector;
  203. var
  204. ai1, ai2, pi1, pi2: TGLVector;
  205. rayVector: TGLVector;
  206. begin
  207. rayVector := VectorNormalize(VectorSubtract(rayDest, eyePos));
  208. if RayCastSphereIntersect(eyePos, rayVector, NullHmgPoint,
  209. FAtmosphereRadius, ai1, ai2) > 1 then
  210. begin
  211. // atmosphere hit
  212. if mayHitGround and (RayCastSphereIntersect(eyePos, rayVector,
  213. NullHmgPoint, FPlanetRadius, pi1, pi2) > 0) then
  214. begin
  215. // hit ground
  216. Result := AtmosphereColor(ai1, pi1);
  217. end
  218. else
  219. begin
  220. // through atmosphere only
  221. Result := AtmosphereColor(ai1, ai2);
  222. end;
  223. rayDest := ai1;
  224. end
  225. else
  226. Result := clrTransparent;
  227. end;
  228. var
  229. I, J, k0, k1: Integer;
  230. begin
  231. if FSun <> nil then
  232. begin
  233. Assert(FAtmosphereRadius > FPlanetRadius);
  234. sunPos := VectorSubtract(FSun.AbsolutePosition, AbsolutePosition);
  235. eyePos := VectorSubtract(rci.CameraPosition, AbsolutePosition);
  236. diskNormal := VectorNegate(eyePos);
  237. NormalizeVector(diskNormal);
  238. diskRight := VectorCrossProduct(rci.CameraUp, diskNormal);
  239. NormalizeVector(diskRight);
  240. diskUp := VectorCrossProduct(diskNormal, diskRight);
  241. NormalizeVector(diskUp);
  242. invAtmosphereHeight := 1 / (FAtmosphereRadius - FPlanetRadius);
  243. lightingVector := VectorNormalize(sunPos); // sun at infinity
  244. rci.GLStates.DepthWriteMask := False;
  245. rci.GLStates.Disable(stLighting);
  246. rci.GLStates.Enable(stBlend);
  247. EnableGLBlendingMode(rci.GLStates);
  248. for I := 0 to 13 do
  249. begin
  250. if I < 5 then
  251. radius := FPlanetRadius * Sqrt(I * (1 / 5))
  252. else
  253. radius := FPlanetRadius + (I - 5.1) *
  254. (FAtmosphereRadius - FPlanetRadius) * (1 / 6.9);
  255. radius := SphereVisibleRadius(VectorLength(eyePos), radius);
  256. k0 := (I and 1) * (FSlices + 1);
  257. k1 := (FSlices + 1) - k0;
  258. for J := 0 to FSlices do
  259. begin
  260. VectorCombine(diskRight, diskUp, cosCache[J] * radius,
  261. sinCache[J] * radius, pVertex[k0 + J]);
  262. if I < 13 then
  263. pColor[k0 + J] := ComputeColor(pVertex[k0 + J], I <= 7);
  264. if I = 0 then
  265. Break;
  266. end;
  267. if I > 1 then
  268. begin
  269. if I = 13 then
  270. begin
  271. // gl.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  272. gl.Begin_(GL_QUAD_STRIP);
  273. for J := FSlices downto 0 do
  274. begin
  275. gl.Color4fv(@pColor[k1 + J]);
  276. gl.Vertex3fv(@pVertex[k1 + J]);
  277. gl.Color4fv(@clrTransparent);
  278. gl.Vertex3fv(@pVertex[k0 + J]);
  279. end;
  280. gl.End_;
  281. end
  282. else
  283. begin
  284. // gl.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_DST_COLOR);
  285. gl.Begin_(GL_QUAD_STRIP);
  286. for J := FSlices downto 0 do
  287. begin
  288. gl.Color4fv(@pColor[k1 + J]);
  289. gl.Vertex3fv(@pVertex[k1 + J]);
  290. gl.Color4fv(@pColor[k0 + J]);
  291. gl.Vertex3fv(@pVertex[k0 + J]);
  292. end;
  293. gl.End_;
  294. end;
  295. end
  296. else if I = 1 then
  297. begin
  298. // gl.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  299. gl.Begin_(GL_TRIANGLE_FAN);
  300. gl.Color4fv(@pColor[k1]);
  301. gl.Vertex3fv(@pVertex[k1]);
  302. for J := k0 + FSlices downto k0 do
  303. begin
  304. gl.Color4fv(@pColor[J]);
  305. gl.Vertex3fv(@pVertex[J]);
  306. end;
  307. gl.End_;
  308. end;
  309. end;
  310. end;
  311. inherited;
  312. end;
  313. procedure TGLCustomAtmosphere.TogleBlendingMode;
  314. begin
  315. if FBlendingMode = abmOneMinusSrcAlpha then
  316. FBlendingMode := abmOneMinusDstColor
  317. else
  318. FBlendingMode := abmOneMinusSrcAlpha;
  319. end;
  320. procedure TGLCustomAtmosphere.Assign(Source: TPersistent);
  321. begin
  322. inherited;
  323. if Source is TGLCustomAtmosphere then
  324. begin
  325. SetSlices(TGLCustomAtmosphere(Source).FSlices);
  326. FOpacity := TGLCustomAtmosphere(Source).FOpacity;
  327. FAtmosphereRadius := TGLCustomAtmosphere(Source).FAtmosphereRadius;
  328. FPlanetRadius := TGLCustomAtmosphere(Source).FPlanetRadius;
  329. FLowAtmColor.Color := TGLCustomAtmosphere(Source).FLowAtmColor.Color;
  330. FHighAtmColor.Color := TGLCustomAtmosphere(Source).FHighAtmColor.Color;
  331. FBlendingMode := TGLCustomAtmosphere(Source).FBlendingMode;
  332. SetSun(TGLCustomAtmosphere(Source).FSun);
  333. end;
  334. end;
  335. procedure TGLCustomAtmosphere.SetSun(const Value: TGLBaseSceneObject);
  336. begin
  337. if FSun <> nil then
  338. FSun.RemoveFreeNotification(Self);
  339. FSun := Value;
  340. if FSun <> nil then
  341. FSun.FreeNotification(Self);
  342. end;
  343. function TGLCustomAtmosphere.AxisAlignedDimensionsUnscaled: TGLVector;
  344. begin
  345. Result.X := FAtmosphereRadius;
  346. Result.Y := Result.X;
  347. Result.Z := Result.X;
  348. Result.W := 0;
  349. end;
  350. procedure TGLCustomAtmosphere.Notification(AComponent: TComponent;
  351. Operation: TOperation);
  352. begin
  353. inherited;
  354. if (Operation = opRemove) and (AComponent = FSun) then
  355. FSun := nil;
  356. end;
  357. procedure TGLCustomAtmosphere.SetAtmosphereRadius(const Value: Single);
  358. begin
  359. FAtmosphereRadius := Value;
  360. if Value <= FPlanetRadius then
  361. FPlanetRadius := FAtmosphereRadius / 1.01;
  362. end;
  363. procedure TGLCustomAtmosphere.SetPlanetRadius(const Value: Single);
  364. begin
  365. FPlanetRadius := Value;
  366. if Value >= FAtmosphereRadius then
  367. FAtmosphereRadius := FPlanetRadius * 1.01;
  368. end;
  369. procedure TGLCustomAtmosphere.EnableGLBlendingMode(StateCache: TGLStateCache);
  370. begin
  371. case FBlendingMode of
  372. abmOneMinusDstColor:
  373. StateCache.SetBlendFunc(bfDstAlpha, bfOneMinusDstColor);
  374. abmOneMinusSrcAlpha:
  375. StateCache.SetBlendFunc(bfDstAlpha, bfOneMinusSrcAlpha);
  376. else
  377. Assert(False, strErrorEx + strUnknownType);
  378. end;
  379. StateCache.Enable(stAlphaTest);
  380. end;
  381. function TGLCustomAtmosphere.StoreAtmosphereRadius: Boolean;
  382. begin
  383. Result := Abs(FAtmosphereRadius - 3.55) > EPS;
  384. end;
  385. function TGLCustomAtmosphere.StoreOpacity: Boolean;
  386. begin
  387. Result := Abs(FOpacity - 2.1) > EPS;
  388. end;
  389. function TGLCustomAtmosphere.StorePlanetRadius: Boolean;
  390. begin
  391. Result := Abs(FPlanetRadius - 3.395) > EPS;
  392. end;
  393. procedure TGLCustomAtmosphere.SetSlices(const Value: Integer);
  394. begin
  395. if Value > 0 then
  396. begin
  397. FSlices := Value;
  398. SetLength(cosCache, FSlices + 1);
  399. SetLength(sinCache, FSlices + 1);
  400. PrepareSinCosCache(sinCache, cosCache, 0, 360);
  401. GetMem(pVertex, 2 * (FSlices + 1) * SizeOf(TGLVector));
  402. GetMem(pColor, 2 * (FSlices + 1) * SizeOf(TGLVector));
  403. end
  404. else
  405. raise EGLAtmosphereException.Create('Slices must be more than0!');
  406. end;
  407. procedure TGLCustomAtmosphere.SetHighAtmColor(const AValue: TGLColor);
  408. begin
  409. FHighAtmColor.Assign(AValue);
  410. end;
  411. procedure TGLCustomAtmosphere.SetLowAtmColor(const AValue: TGLColor);
  412. begin
  413. FLowAtmColor.Assign(AValue);
  414. end;
  415. function TGLCustomAtmosphere.StoreHighAtmColor: Boolean;
  416. begin
  417. Result := not VectorEquals(FHighAtmColor.Color, VectorMake(0, 0, 1, 1));
  418. end;
  419. function TGLCustomAtmosphere.StoreLowAtmColor: Boolean;
  420. begin
  421. Result := not VectorEquals(FLowAtmColor.Color, VectorMake(1, 1, 1, 1));
  422. end;
  423. // --------------------------------------
  424. initialization
  425. // --------------------------------------
  426. RegisterClasses([TGLCustomAtmosphere, TGLAtmosphere]);
  427. end.