GLPostEffects.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. //
  2. // This unit is part of the GLScene Engine, http://glscene.org
  3. //
  4. unit GLPostEffects;
  5. (* A collection of components that generate post effects *)
  6. interface
  7. {$I GLScene.inc}
  8. uses
  9. Winapi.OpenGL,
  10. System.Classes,
  11. System.SysUtils,
  12. OpenGLTokens,
  13. GLScene,
  14. GLState,
  15. GLContext,
  16. GLPersistentClasses,
  17. GLTexture,
  18. GLGraphics,
  19. GLS.Strings,
  20. GLS.ShaderCustom,
  21. GLVectorGeometry,
  22. GLRenderContextInfo,
  23. GLMaterial,
  24. GLTextureFormat;
  25. type
  26. EGLPostShaderHolderException = class(Exception);
  27. TGLPostShaderHolder = class;
  28. TGLPostShaderCollectionItem = class(TCollectionItem)
  29. private
  30. FShader: TGLShader;
  31. FPostShaderInterface: IGLPostShader;
  32. procedure SetShader(const Value: TGLShader);
  33. protected
  34. function GetRealOwner: TGLPostShaderHolder;
  35. function GetDisplayName: string; override;
  36. public
  37. procedure Assign(Source: TPersistent); override;
  38. published
  39. property Shader: TGLShader read FShader write SetShader;
  40. end;
  41. TGLPostShaderCollection = class(TOwnedCollection)
  42. private
  43. function GetItems(const Index: Integer): TGLPostShaderCollectionItem;
  44. procedure SetItems(const Index: Integer;
  45. const Value: TGLPostShaderCollectionItem);
  46. public
  47. procedure Remove(const Item: TGLShader);
  48. function Add: TGLPostShaderCollectionItem;
  49. property Items[const Index: Integer]: TGLPostShaderCollectionItem
  50. read GetItems write SetItems; default;
  51. end;
  52. (* A class that allows several post-shaders to be applied to the scene,
  53. one after another. It does not provide any optimizations related to
  54. multi-shader rendering, just a convenient interface. *)
  55. TGLPostShaderHolder = class(TGLBaseSCeneObject)
  56. private
  57. FShaders: TGLPostShaderCollection;
  58. FTempTexture: TGLTextureHandle;
  59. FPreviousViewportSize: TGLSize;
  60. FTempTextureTarget: TGLTextureTarget;
  61. procedure SetShaders(const Value: TGLPostShaderCollection);
  62. protected
  63. procedure Notification(AComponent: TComponent;
  64. Operation: TOperation); override;
  65. public
  66. constructor Create(Owner: TComponent); override;
  67. destructor Destroy; override;
  68. procedure Assign(Source: TPersistent); override;
  69. procedure DoRender(var rci: TGLRenderContextInfo;
  70. renderSelf, renderChildren: Boolean); override;
  71. published
  72. property TempTextureTarget: TGLTextureTarget read FTempTextureTarget
  73. write FTempTextureTarget default ttTexture2d;
  74. property Shaders: TGLPostShaderCollection read FShaders write SetShaders;
  75. // Publish some stuff from TGLBaseSceneObject.
  76. property Visible;
  77. property OnProgress;
  78. end;
  79. TGLPostEffectColor = record
  80. R, G, B, A: Byte;
  81. end;
  82. TGLPostEffectBuffer = array of TGLPostEffectColor;
  83. TGLOnCustomPostEffectEvent = procedure(Sender: TObject;
  84. var rci: TGLRenderContextInfo; var Buffer: TGLPostEffectBuffer) of object;
  85. (* Some presets for TGLPostEffect:
  86. pepNone - does nothing.
  87. pepGray - makes picture gray.
  88. pepNegative - inverts all colors.
  89. pepDistort - simulates shaky TV image.
  90. pepNoise - just adds random niose.
  91. pepNightVision - simulates nightvision goggles.
  92. pepBlur - blurs the scene.
  93. pepCustom - calls the OnCustomEffect event. *)
  94. TGLPostEffectPreset = (pepNone, pepGray, pepNegative, pepDistort, pepNoise,
  95. pepNightVision, pepBlur, pepCustom);
  96. (* Provides a simple way to producing post-effects without shaders.
  97. It is slow as hell, but it's worth it in some cases. *)
  98. TGLPostEffect = class(TGLBaseSCeneObject)
  99. private
  100. FOnCustomEffect: TGLOnCustomPostEffectEvent;
  101. FPreset: TGLPostEffectPreset;
  102. FRenderBuffer: TGLPostEffectBuffer;
  103. protected
  104. // May be should be private...
  105. procedure MakeGrayEffect; virtual;
  106. procedure MakeNegativeEffect; virtual;
  107. procedure MakeDistortEffect; virtual;
  108. procedure MakeNoiseEffect; virtual;
  109. procedure MakeNightVisionEffect; virtual;
  110. procedure MakeBlurEffect(var rci: TGLRenderContextInfo); virtual;
  111. procedure DoOnCustomEffect(var rci: TGLRenderContextInfo;
  112. var Buffer: TGLPostEffectBuffer); virtual;
  113. public
  114. procedure DoRender(var rci: TGLRenderContextInfo;
  115. renderSelf, renderChildren: Boolean); override;
  116. procedure Assign(Source: TPersistent); override;
  117. published
  118. property Preset: TGLPostEffectPreset read FPreset write FPreset
  119. default pepNone;
  120. // User creates this effect.
  121. property OnCustomEffect: TGLOnCustomPostEffectEvent read FOnCustomEffect
  122. write FOnCustomEffect;
  123. // Publish some stuff from TGLBaseSCeneObject.
  124. property Visible;
  125. property OnProgress;
  126. end;
  127. // -------------------------------------------------------------------------
  128. implementation
  129. // -------------------------------------------------------------------------
  130. { TGLPostEffect }
  131. procedure TGLPostEffect.Assign(Source: TPersistent);
  132. begin
  133. inherited;
  134. if Source is TGLPostEffect then
  135. begin
  136. FPreset := TGLPostEffect(Source).FPreset;
  137. end;
  138. end;
  139. procedure TGLPostEffect.DoOnCustomEffect(var rci: TGLRenderContextInfo;
  140. var Buffer: TGLPostEffectBuffer);
  141. begin
  142. if Assigned(FOnCustomEffect) then
  143. FOnCustomEffect(Self, rci, Buffer);
  144. end;
  145. procedure TGLPostEffect.DoRender(var rci: TGLRenderContextInfo;
  146. renderSelf, renderChildren: Boolean);
  147. var
  148. NewScreenSize: Integer;
  149. begin
  150. if (not rci.ignoreMaterials) and (FPreset <> pepNone) and
  151. (rci.drawState <> dsPicking) then
  152. begin
  153. NewScreenSize := rci.viewPortSize.cx * rci.viewPortSize.cy;
  154. if NewScreenSize <> Length(FRenderBuffer) then
  155. SetLength(FRenderBuffer, NewScreenSize);
  156. gl.ReadPixels(0, 0, rci.viewPortSize.cx, rci.viewPortSize.cy, GL_RGBA,
  157. GL_UNSIGNED_BYTE, FRenderBuffer);
  158. case FPreset of
  159. // pepNone is handled in the first line.
  160. pepGray:
  161. MakeGrayEffect;
  162. pepNegative:
  163. MakeNegativeEffect;
  164. pepDistort:
  165. MakeDistortEffect;
  166. pepNoise:
  167. MakeNoiseEffect;
  168. pepNightVision:
  169. MakeNightVisionEffect;
  170. pepBlur:
  171. MakeBlurEffect(rci);
  172. pepCustom:
  173. DoOnCustomEffect(rci, FRenderBuffer);
  174. else
  175. Assert(False, strErrorEx + strUnknownType);
  176. end;
  177. gl.DrawPixels(rci.viewPortSize.cx, rci.viewPortSize.cy, GL_RGBA,
  178. GL_UNSIGNED_BYTE, FRenderBuffer);
  179. end;
  180. // Start rendering children (if any).
  181. if renderChildren then
  182. Self.renderChildren(0, Count - 1, rci);
  183. end;
  184. procedure TGLPostEffect.MakeGrayEffect;
  185. var
  186. I: Longword;
  187. gray: Byte;
  188. begin
  189. for I := 0 to High(FRenderBuffer) do
  190. begin
  191. gray := Round((0.30 * FRenderBuffer[I].R) + (0.59 * FRenderBuffer[I].G) +
  192. (0.11 * FRenderBuffer[I].B));
  193. FRenderBuffer[I].R := gray;
  194. FRenderBuffer[I].G := gray;
  195. FRenderBuffer[I].B := gray;
  196. end;
  197. end;
  198. procedure TGLPostEffect.MakeNegativeEffect;
  199. var
  200. I: Longword;
  201. begin
  202. for I := 0 to High(FRenderBuffer) do
  203. begin
  204. FRenderBuffer[I].R := 255 - FRenderBuffer[I].R;
  205. FRenderBuffer[I].G := 255 - FRenderBuffer[I].G;
  206. FRenderBuffer[I].B := 255 - FRenderBuffer[I].B;
  207. end;
  208. end;
  209. procedure TGLPostEffect.MakeDistortEffect;
  210. var
  211. I: Integer;
  212. lMaxLength: Integer;
  213. lNewIndex: Integer;
  214. begin
  215. lMaxLength := High(FRenderBuffer);
  216. for I := 0 to lMaxLength do
  217. begin
  218. lNewIndex := MaxInteger(0, MinInteger(lMaxLength, I + Random(10) - 5));
  219. FRenderBuffer[I].R := FRenderBuffer[lNewIndex].R;
  220. FRenderBuffer[I].G := FRenderBuffer[lNewIndex].G;
  221. FRenderBuffer[I].B := FRenderBuffer[lNewIndex].B;
  222. end;
  223. end;
  224. procedure TGLPostEffect.MakeNoiseEffect;
  225. var
  226. I: Longword;
  227. rnd: Single;
  228. begin
  229. for I := 0 to High(FRenderBuffer) do
  230. begin
  231. rnd := 0.25 + Random(75) / 100;
  232. FRenderBuffer[I].R := Round(FRenderBuffer[I].R * rnd);
  233. FRenderBuffer[I].G := Round(FRenderBuffer[I].G * rnd);
  234. FRenderBuffer[I].B := Round(FRenderBuffer[I].B * rnd);
  235. end;
  236. end;
  237. procedure TGLPostEffect.MakeNightVisionEffect;
  238. var
  239. gray: Single;
  240. I: Integer;
  241. lNewIndex, lMaxLength: Integer;
  242. begin
  243. lMaxLength := High(FRenderBuffer);
  244. for I := 0 to lMaxLength do
  245. begin
  246. lNewIndex := MaxInteger(0, MinInteger(lMaxLength, I + Random(20) - 10));
  247. gray := 60 + (0.30 * FRenderBuffer[lNewIndex].R) +
  248. (0.59 * FRenderBuffer[lNewIndex].G) + (0.11 * FRenderBuffer[lNewIndex].B);
  249. FRenderBuffer[I].R := Round(gray * 0.25);
  250. FRenderBuffer[I].G := Round((gray + 4) * 0.6);
  251. FRenderBuffer[I].B := Round((gray + 4) * 0.11);
  252. end;
  253. end;
  254. procedure TGLPostEffect.MakeBlurEffect(var rci: TGLRenderContextInfo);
  255. const
  256. lOffset: Integer = 2;
  257. var
  258. I: Integer;
  259. lUp: Integer;
  260. begin
  261. lUp := rci.viewPortSize.cx * lOffset;
  262. for I := lUp to High(FRenderBuffer) - lUp do
  263. begin
  264. FRenderBuffer[I].R := (FRenderBuffer[I].R + FRenderBuffer[I - lOffset].R +
  265. FRenderBuffer[I + lOffset].R + FRenderBuffer[I - lUp].R + FRenderBuffer
  266. [I + lUp].R) div 5;
  267. FRenderBuffer[I].G := (FRenderBuffer[I].G + FRenderBuffer[I - lOffset].G +
  268. FRenderBuffer[I + lOffset].G + FRenderBuffer[I - lUp].G + FRenderBuffer
  269. [I + lUp].R) div 5;
  270. FRenderBuffer[I].B := (FRenderBuffer[I].B + FRenderBuffer[I - lOffset].B +
  271. FRenderBuffer[I + lOffset].B + FRenderBuffer[I - lUp].G + FRenderBuffer
  272. [I + lUp].R) div 5;
  273. end;
  274. end;
  275. { TGLPostShaderCollectionItem }
  276. procedure TGLPostShaderCollectionItem.Assign(Source: TPersistent);
  277. begin
  278. if Source is TGLPostShaderCollectionItem then
  279. begin
  280. SetShader(TGLPostShaderCollectionItem(Source).FShader);
  281. end
  282. else
  283. inherited; // Die!!!
  284. end;
  285. function TGLPostShaderCollectionItem.GetDisplayName: string;
  286. begin
  287. if FShader = nil then
  288. Result := ''
  289. else
  290. begin
  291. if FShader.Name <> '' then
  292. Result := FShader.Name
  293. else
  294. Result := FShader.ClassName;
  295. end;
  296. end;
  297. type
  298. // Required for Delphi5 compatibility.
  299. THackCollection = class(TOwnedCollection)
  300. end;
  301. function TGLPostShaderCollectionItem.GetRealOwner: TGLPostShaderHolder;
  302. begin
  303. if Collection = nil then
  304. Result := nil
  305. else
  306. Result := TGLPostShaderHolder(THackCollection(Collection).GetOwner);
  307. end;
  308. procedure TGLPostShaderCollectionItem.SetShader(const Value: TGLShader);
  309. var
  310. RealOwner: TGLPostShaderHolder;
  311. begin
  312. if FShader = Value then
  313. Exit;
  314. RealOwner := GetRealOwner;
  315. if FShader <> nil then
  316. FShader.RemoveFreeNotification(RealOwner);
  317. if not Supports(TObject(Value), IGLPostShader, FPostShaderInterface) then
  318. raise EGLPostShaderHolderException.Create
  319. ('Shader must support interface IGLPostShader!');
  320. if RealOwner <> nil then
  321. if FPostShaderInterface.GetTextureTarget <> RealOwner.TempTextureTarget then
  322. raise EGLPostShaderHolderException.Create
  323. (strErrorEx + 'TextureTarget is not compatible!');
  324. // If RealOwner = nil, we ignore this case and hope it will turn out ok...
  325. FShader := Value;
  326. if FShader <> nil then
  327. if RealOwner <> nil then
  328. FShader.FreeNotification(RealOwner);
  329. end;
  330. { TGLPostShaderHolder }
  331. procedure TGLPostShaderHolder.Assign(Source: TPersistent);
  332. begin
  333. if Source is TGLPostShaderHolder then
  334. begin
  335. FShaders.Assign(TGLPostShaderHolder(Source).FShaders);
  336. FTempTextureTarget := TGLPostShaderHolder(Source).FTempTextureTarget;
  337. end;
  338. inherited;
  339. end;
  340. constructor TGLPostShaderHolder.Create(Owner: TComponent);
  341. begin
  342. inherited;
  343. FTempTexture := TGLTextureHandle.Create;
  344. FTempTextureTarget := ttTexture2d;
  345. FShaders := TGLPostShaderCollection.Create(Self, TGLPostShaderCollectionItem);
  346. end;
  347. destructor TGLPostShaderHolder.Destroy;
  348. begin
  349. FShaders.Destroy;
  350. FTempTexture.Destroy;
  351. inherited;
  352. end;
  353. procedure TGLPostShaderHolder.DoRender(var rci: TGLRenderContextInfo;
  354. renderSelf, renderChildren: Boolean);
  355. var
  356. I: Integer;
  357. begin
  358. if not(rci.ignoreMaterials) and not(csDesigning in ComponentState) and
  359. (rci.drawState <> dsPicking) then
  360. begin
  361. if (FPreviousViewportSize.cx <> rci.viewPortSize.cx) or
  362. (FPreviousViewportSize.cy <> rci.viewPortSize.cy) then
  363. begin
  364. InitTexture(FTempTexture.Handle, rci.viewPortSize, FTempTextureTarget);
  365. FPreviousViewportSize := rci.viewPortSize;
  366. end;
  367. if FShaders.Count <> 0 then
  368. begin
  369. for I := 0 to FShaders.Count - 1 do
  370. begin
  371. Assert(Assigned(FShaders[I].FShader));
  372. if FShaders[I].FShader.Enabled then
  373. begin
  374. rci.GLStates.ActiveTextureEnabled[FTempTextureTarget] := True;
  375. FShaders[I].FShader.Apply(rci, Self);
  376. repeat
  377. CopyScreenToTexture(rci.viewPortSize,
  378. DecodeTextureTarget(FTempTextureTarget));
  379. FShaders[I].FPostShaderInterface.DoUseTempTexture(FTempTexture,
  380. FTempTextureTarget);
  381. DrawTexturedScreenQuad5(rci.viewPortSize);
  382. until not FShaders[I].FShader.UnApply(rci);
  383. rci.GLStates.ActiveTextureEnabled[FTempTextureTarget] := False;
  384. end;
  385. end;
  386. end;
  387. end;
  388. if renderChildren then
  389. Self.renderChildren(0, Count - 1, rci);
  390. end;
  391. procedure TGLPostShaderHolder.Notification(AComponent: TComponent;
  392. Operation: TOperation);
  393. begin
  394. inherited;
  395. if Operation = opRemove then
  396. begin
  397. if AComponent is TGLShader then
  398. FShaders.Remove(TGLShader(AComponent));
  399. end;
  400. end;
  401. procedure TGLPostShaderHolder.SetShaders(const Value: TGLPostShaderCollection);
  402. begin
  403. FShaders.Assign(Value);
  404. end;
  405. { TGLPostShaderCollection }
  406. function TGLPostShaderCollection.Add: TGLPostShaderCollectionItem;
  407. begin
  408. Result := TGLPostShaderCollectionItem(inherited Add);
  409. end;
  410. function TGLPostShaderCollection.GetItems(const Index: Integer)
  411. : TGLPostShaderCollectionItem;
  412. begin
  413. Result := TGLPostShaderCollectionItem(GetItem(Index));
  414. end;
  415. procedure TGLPostShaderCollection.Remove(const Item: TGLShader);
  416. var
  417. I: Integer;
  418. begin
  419. if Count <> 0 then
  420. for I := Count - 1 downto 0 do
  421. if GetItems(I).FShader = Item then
  422. Delete(I);
  423. // Don't exit because the same shader might be applied more than once.
  424. end;
  425. procedure TGLPostShaderCollection.SetItems(const Index: Integer;
  426. const Value: TGLPostShaderCollectionItem);
  427. begin
  428. GetItems(Index).Assign(Value);
  429. end;
  430. // ------------------------------------------------
  431. initialization
  432. // ------------------------------------------------
  433. RegisterClasses([TGLPostEffect, TGLPostShaderHolder, TGLPostShaderCollection,
  434. TGLPostShaderCollectionItem]);
  435. end.