GLPostEffects.pas 14 KB

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