GLS.PerlinPFX.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. //
  2. // The graphics engine GLScene https://github.com/glscene
  3. //
  4. unit GLS.PerlinPFX;
  5. (* PFX particle effects revolving around the use of Perlin noise *)
  6. interface
  7. {$I GLScene.Defines.inc}
  8. uses
  9. Winapi.OpenGL,
  10. System.Classes,
  11. System.Math,
  12. GLScene.OpenGLTokens,
  13. GLS.ParticleFX,
  14. GLS.Graphics,
  15. GLScene.VectorTypes,
  16. GLScene.VectorGeometry;
  17. const
  18. cPERLIN_TABLE_SIZE = 256; // must be a power of two
  19. type
  20. (* A sprite-based particles FX manager using perlin-based sprites.
  21. This PFX manager is more suited for smoke or fire effects, and with proper
  22. tweaking of the texture and perlin parameters, may help render a convincing
  23. effect with less particles.
  24. The sprite generate by this manager is the composition of a distance-based
  25. intensity and a perlin noise. *)
  26. TGLPerlinPFXManager = class(TGLBaseSpritePFXManager)
  27. private
  28. FTexMapSize: Integer;
  29. FNoiseSeed: Integer;
  30. FNoiseScale: Integer;
  31. FNoiseAmplitude: Integer;
  32. FSmoothness: Single;
  33. FBrightness, FGamma: Single;
  34. protected
  35. procedure PrepareImage(bmp32: TGLBitmap32; var texFormat: Integer); override;
  36. procedure SetTexMapSize(const val: Integer);
  37. procedure SetNoiseSeed(const val: Integer);
  38. procedure SetNoiseScale(const val: Integer);
  39. procedure SetNoiseAmplitude(const val: Integer);
  40. procedure SetSmoothness(const val: Single);
  41. procedure SetBrightness(const val: Single);
  42. procedure SetGamma(const val: Single);
  43. public
  44. constructor Create(aOwner: TComponent); override;
  45. destructor Destroy; override;
  46. published
  47. (* Underlying texture map size, as a power of two.
  48. Min value is 3 (size=8), max value is 9 (size=512). *)
  49. property TexMapSize: Integer read FTexMapSize write SetTexMapSize default 6;
  50. (* Smoothness of the distance-based intensity.
  51. This value is the exponent applied to the intensity in the texture,
  52. basically with a value of 1 (default) the intensity decreases linearly,
  53. with higher values, it will remain 'constant' in the center then
  54. fade-off more abruptly, and with values below 1, there will be a
  55. sharp spike in the center. *)
  56. property Smoothness: Single read FSmoothness write SetSmoothness;
  57. (* Brightness factor applied to the perlin texture intensity.
  58. Brightness acts as a scaling, non-saturating factor. Examples:
  59. Brightness = 1 : intensities in the [0; 1] range
  60. Brightness = 2 : intensities in the [0.5; 1] range
  61. Brightness = 0.5 : intensities in the [0; 0.5] range
  62. Brightness is applied to the final texture (and thus affects
  63. the distance based intensity). *)
  64. property Brightness: Single read FBrightness write SetBrightness;
  65. property Gamma: Single read FGamma write SetGamma;
  66. // Random seed to use for the perlin noise.
  67. property NoiseSeed: Integer read FNoiseSeed write SetNoiseSeed default 0;
  68. // Scale applied to the perlin noise (stretching).
  69. property NoiseScale: Integer read FNoiseScale write SetNoiseScale default 100;
  70. (* Amplitude applied to the perlin noise (intensity).
  71. This value represent the percentage of the sprite luminance affected by
  72. the perlin texture. *)
  73. property NoiseAmplitude: Integer read FNoiseAmplitude write SetNoiseAmplitude default 50;
  74. property ColorMode default scmInner;
  75. property SpritesPerTexture default sptFour;
  76. property ParticleSize;
  77. property ColorInner;
  78. property ColorOuter;
  79. property LifeColors;
  80. end;
  81. (* Generates Perlin Noise in the [-1; 1] range.
  82. 2D noise requests are taken in the Z=0 slice *)
  83. TGLPerlin3DNoise = class(TObject)
  84. protected
  85. FPermutations: packed array [0 .. cPERLIN_TABLE_SIZE - 1] of Integer;
  86. FGradients: packed array [0 .. cPERLIN_TABLE_SIZE * 3 - 1] of Single;
  87. protected
  88. function Lattice(ix, iy, iz: Integer; fx, fy, fz: Single): Single; overload;
  89. function Lattice(ix, iy: Integer; fx, fy: Single): Single; overload;
  90. public
  91. constructor Create(randomSeed: Integer);
  92. procedure Initialize(randomSeed: Integer);
  93. function Noise(const x, y: Single): Single; overload;
  94. function Noise(const x, y, z: Single): Single; overload;
  95. function Noise(const v: TAffineVector): Single; overload;
  96. function Noise(const v: TGLVector): Single; overload;
  97. end;
  98. // ------------------------------------------------------------------
  99. implementation
  100. // ------------------------------------------------------------------
  101. // ------------------
  102. // ------------------ TGLPerlinPFXManager ------------------
  103. // ------------------
  104. constructor TGLPerlinPFXManager.Create(aOwner: TComponent);
  105. begin
  106. inherited;
  107. FTexMapSize := 6;
  108. FNoiseScale := 100;
  109. FNoiseAmplitude := 50;
  110. FSmoothness := 1;
  111. FBrightness := 1;
  112. FGamma := 1;
  113. SpritesPerTexture := sptFour;
  114. ColorMode := scmInner;
  115. end;
  116. destructor TGLPerlinPFXManager.Destroy;
  117. begin
  118. inherited Destroy;
  119. end;
  120. procedure TGLPerlinPFXManager.SetTexMapSize(const val: Integer);
  121. begin
  122. if val <> FTexMapSize then
  123. begin
  124. FTexMapSize := val;
  125. if FTexMapSize < 3 then
  126. FTexMapSize := 3;
  127. if FTexMapSize > 9 then
  128. FTexMapSize := 9;
  129. NotifyChange(Self);
  130. end;
  131. end;
  132. procedure TGLPerlinPFXManager.SetNoiseSeed(const val: Integer);
  133. begin
  134. if val <> FNoiseSeed then
  135. begin
  136. FNoiseSeed := val;
  137. NotifyChange(Self);
  138. end;
  139. end;
  140. procedure TGLPerlinPFXManager.SetNoiseScale(const val: Integer);
  141. begin
  142. if val <> FNoiseScale then
  143. begin
  144. FNoiseScale := val;
  145. NotifyChange(Self);
  146. end;
  147. end;
  148. procedure TGLPerlinPFXManager.SetNoiseAmplitude(const val: Integer);
  149. begin
  150. if val <> FNoiseAmplitude then
  151. begin
  152. FNoiseAmplitude := val;
  153. if FNoiseAmplitude < 0 then
  154. FNoiseAmplitude := 0;
  155. if FNoiseAmplitude > 100 then
  156. FNoiseAmplitude := 100;
  157. NotifyChange(Self);
  158. end;
  159. end;
  160. procedure TGLPerlinPFXManager.SetSmoothness(const val: Single);
  161. begin
  162. if FSmoothness <> val then
  163. begin
  164. FSmoothness := ClampValue(val, 1E-3, 1E3);
  165. NotifyChange(Self);
  166. end;
  167. end;
  168. procedure TGLPerlinPFXManager.SetBrightness(const val: Single);
  169. begin
  170. if FBrightness <> val then
  171. begin
  172. FBrightness := ClampValue(val, 1E-3, 1E3);
  173. NotifyChange(Self);
  174. end;
  175. end;
  176. procedure TGLPerlinPFXManager.SetGamma(const val: Single);
  177. begin
  178. if FGamma <> val then
  179. begin
  180. FGamma := ClampValue(val, 0.1, 10);
  181. NotifyChange(Self);
  182. end;
  183. end;
  184. procedure TGLPerlinPFXManager.PrepareImage(bmp32: TGLBitmap32; var texFormat: Integer);
  185. procedure PrepareSubImage(dx, dy, s: Integer; noise: TGLPerlin3DNoise);
  186. var
  187. s2: Integer;
  188. x, y, d: Integer;
  189. is2, f, fy, pf, nBase, nAmp, df, dfg: Single;
  190. invGamma: Single;
  191. scanLine: PGLPixel32Array;
  192. gotIntensityCorrection: Boolean;
  193. begin
  194. s2 := s shr 1;
  195. is2 := 1 / s2;
  196. pf := FNoiseScale * 0.05 * is2;
  197. nAmp := FNoiseAmplitude * (0.01);
  198. nBase := 1 - nAmp * 0.5;
  199. if Gamma < 0.1 then
  200. invGamma := 10
  201. else
  202. invGamma := 1 / Gamma;
  203. gotIntensityCorrection := (Gamma <> 1) or (Brightness <> 1);
  204. for y := 0 to s - 1 do
  205. begin
  206. fy := Sqr((y + 0.5 - s2) * is2);
  207. scanLine := bmp32.scanLine[y + dy];
  208. for x := 0 to s - 1 do
  209. begin
  210. f := Sqr((x + 0.5 - s2) * is2) + fy;
  211. if f < 1 then
  212. begin
  213. df := nBase + nAmp * noise.noise(x * pf, y * pf);
  214. if gotIntensityCorrection then
  215. df := EnsureRange(Power(df, invGamma) * Brightness, 0, 1);
  216. dfg := EnsureRange(Power((1 - Sqrt(f)), FSmoothness), 0, 1);
  217. d := Trunc(df * 255);
  218. if d > 255 then
  219. d := 255;
  220. with scanLine^[x + dx] do
  221. begin
  222. r := d;
  223. g := d;
  224. b := d;
  225. a := Trunc(dfg * 255);
  226. end;
  227. end
  228. else
  229. PInteger(@scanLine[x + dx])^ := 0;
  230. end;
  231. end;
  232. end;
  233. var
  234. s, s2: Integer;
  235. noise: TGLPerlin3DNoise;
  236. begin
  237. s := (1 shl TexMapSize);
  238. bmp32.Width := s;
  239. bmp32.Height := s;
  240. bmp32.Blank := false;
  241. texFormat := GL_LUMINANCE_ALPHA;
  242. noise := TGLPerlin3DNoise.Create(NoiseSeed);
  243. try
  244. case SpritesPerTexture of
  245. sptOne:
  246. PrepareSubImage(0, 0, s, noise);
  247. sptFour:
  248. begin
  249. s2 := s div 2;
  250. PrepareSubImage(0, 0, s2, noise);
  251. noise.Initialize(NoiseSeed + 1);
  252. PrepareSubImage(s2, 0, s2, noise);
  253. noise.Initialize(NoiseSeed + 2);
  254. PrepareSubImage(0, s2, s2, noise);
  255. noise.Initialize(NoiseSeed + 3);
  256. PrepareSubImage(s2, s2, s2, noise);
  257. end;
  258. else
  259. Assert(false);
  260. end;
  261. finally
  262. noise.Free;
  263. end;
  264. end;
  265. // ------------------
  266. // ------------------ TGLPerlin3DNoise ------------------
  267. // ------------------
  268. constructor TGLPerlin3DNoise.Create(randomSeed: Integer);
  269. begin
  270. inherited Create;
  271. Initialize(randomSeed);
  272. end;
  273. procedure TGLPerlin3DNoise.Initialize(randomSeed: Integer);
  274. var
  275. seedBackup: Integer;
  276. i, t, j: Integer;
  277. z, r: Single;
  278. begin
  279. seedBackup := RandSeed;
  280. RandSeed := randomSeed;
  281. // Generate random gradient vectors.
  282. for i := 0 to cPERLIN_TABLE_SIZE - 1 do
  283. begin
  284. z := 1 - 2 * Random;
  285. r := Sqrt(1 - z * z);
  286. SinCosine(c2PI * Random, r, FGradients[i * 3], FGradients[i * 3 + 1]);
  287. FGradients[i * 3 + 2] := z;
  288. end;
  289. // Initialize permutations table
  290. for i := 0 to cPERLIN_TABLE_SIZE - 1 do
  291. FPermutations[i] := i;
  292. // Shake up
  293. for i := 0 to cPERLIN_TABLE_SIZE - 1 do
  294. begin
  295. j := Random(cPERLIN_TABLE_SIZE);
  296. t := FPermutations[i];
  297. FPermutations[i] := FPermutations[j];
  298. FPermutations[j] := t;
  299. end;
  300. RandSeed := seedBackup;
  301. end;
  302. function TGLPerlin3DNoise.Lattice(ix, iy, iz: Integer;
  303. fx, fy, fz: Single): Single;
  304. const
  305. cMask = cPERLIN_TABLE_SIZE - 1;
  306. var
  307. g: Integer;
  308. begin
  309. g := FPermutations[(ix + FPermutations[(iy + FPermutations[iz and cMask]) and
  310. cMask]) and cMask] * 3;
  311. Result := FGradients[g] * fx + FGradients[g + 1] * fy + FGradients
  312. [g + 2] * fz;
  313. end;
  314. function TGLPerlin3DNoise.Lattice(ix, iy: Integer; fx, fy: Single): Single;
  315. const
  316. cMask = cPERLIN_TABLE_SIZE - 1;
  317. var
  318. g: Integer;
  319. begin
  320. g := FPermutations[(ix + FPermutations[(iy + FPermutations[0]) and cMask])
  321. and cMask] * 3;
  322. Result := FGradients[g] * fx + FGradients[g + 1] * fy;
  323. end;
  324. function TGLPerlin3DNoise.Noise(const v: TAffineVector): Single;
  325. function Smooth(var x: Single): Single;
  326. begin
  327. Result := x * x * (3 - 2 * x);
  328. end;
  329. var
  330. ix, iy, iz: Integer;
  331. fx0, fx1, fy0, fy1, fz0, fz1: Single;
  332. wx, wy, wz: Single;
  333. vy0, vy1, vz0, vz1: Single;
  334. begin
  335. ix := Floor(v.x);
  336. fx0 := v.x - ix;
  337. fx1 := fx0 - 1;
  338. wx := Smooth(fx0);
  339. iy := Floor(v.y);
  340. fy0 := v.y - iy;
  341. fy1 := fy0 - 1;
  342. wy := Smooth(fy0);
  343. iz := Floor(v.z);
  344. fz0 := v.z - iz;
  345. fz1 := fz0 - 1;
  346. wz := Smooth(fz0);
  347. vy0 := Lerp(Lattice(ix, iy, iz, fx0, fy0, fz0), Lattice(ix + 1, iy, iz, fx1,
  348. fy0, fz0), wx);
  349. vy1 := Lerp(Lattice(ix, iy + 1, iz, fx0, fy1, fz0),
  350. Lattice(ix + 1, iy + 1, iz, fx1, fy1, fz0), wx);
  351. vz0 := Lerp(vy0, vy1, wy);
  352. vy0 := Lerp(Lattice(ix, iy, iz + 1, fx0, fy0, fz1),
  353. Lattice(ix + 1, iy, iz + 1, fx1, fy0, fz1), wx);
  354. vy1 := Lerp(Lattice(ix, iy + 1, iz + 1, fx0, fy1, fz1),
  355. Lattice(ix + 1, iy + 1, iz + 1, fx1, fy1, fz1), wx);
  356. vz1 := Lerp(vy0, vy1, wy);
  357. Result := Lerp(vz0, vz1, wz);
  358. end;
  359. function TGLPerlin3DNoise.Noise(const x, y: Single): Single;
  360. function Smooth(var x: Single): Single;
  361. begin
  362. Result := x * x * (3 - 2 * x);
  363. end;
  364. var
  365. ix, iy: Integer;
  366. fx0, fx1, fy0, fy1: Single;
  367. wx, wy: Single;
  368. vy0, vy1: Single;
  369. begin
  370. ix := Floor(x);
  371. fx0 := x - ix;
  372. fx1 := fx0 - 1;
  373. wx := Smooth(fx0);
  374. iy := Floor(y);
  375. fy0 := y - iy;
  376. fy1 := fy0 - 1;
  377. wy := Smooth(fy0);
  378. vy0 := Lerp(Lattice(ix, iy, fx0, fy0), Lattice(ix + 1, iy, fx1, fy0), wx);
  379. vy1 := Lerp(Lattice(ix, iy + 1, fx0, fy1), Lattice(ix + 1, iy + 1, fx1,
  380. fy1), wx);
  381. Result := Lerp(vy0, vy1, wy);
  382. end;
  383. function TGLPerlin3DNoise.Noise(const x, y, z: Single): Single;
  384. begin
  385. Result := Noise(AffineVectorMake(x, y, z));
  386. end;
  387. function TGLPerlin3DNoise.Noise(const v: TGLVector): Single;
  388. begin
  389. Result := Noise(PAffineVector(@v)^);
  390. end;
  391. // ------------------------------------------------------------------
  392. initialization
  393. // ------------------------------------------------------------------
  394. RegisterClasses([TGLPerlinPFXManager]);
  395. end.