GXS.Atmosphere.pas 14 KB

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