GLBumpmapHDS.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. //
  2. // This unit is part of the GLScene Engine, http://glscene.org
  3. //
  4. unit GLBumpmapHDS;
  5. (*
  6. Implements a HDS that automatically generates an elevation bumpmap.
  7. The object-space elevation bumpmap can be used for dynamic terrain lighting.
  8. A bumpmap texture is generated for each terrain tile, and placed into a TGLMaterialLibrary.
  9. *)
  10. interface
  11. {$I GLScene.inc}
  12. uses
  13. System.Classes,
  14. System.SysUtils,
  15. System.SyncObjs,
  16. OpenGLTokens,
  17. GLCoordinates,
  18. GLHeightData,
  19. GLGraphics,
  20. GLVectorGeometry,
  21. GLTexture,
  22. GLMaterial,
  23. GLS.Utils,
  24. GLVectorTypes;
  25. type
  26. TGLBumpmapHDS = class;
  27. TNewTilePreparedEvent = procedure(Sender: TGLBumpmapHDS;
  28. heightData: TGLHeightData; normalMapMaterial: TGLLibMaterial) of object;
  29. (* An Height Data Source that generates elevation bumpmaps automatically.
  30. The HDS must be connected to another HDS, which will provide the elevation
  31. data, and to a MaterialLibrary where bumpmaps will be placed. *)
  32. TGLBumpmapHDS = class(TGLHeightDataSourceFilter)
  33. private
  34. // FElevationHDS : TGLHeightDataSource;
  35. FBumpmapLibrary: TGLMaterialLibrary;
  36. FOnNewTilePrepared: TNewTilePreparedEvent;
  37. FBumpScale: Single;
  38. FSubSampling: Integer;
  39. FMaxTextures: Integer;
  40. Uno: TCriticalSection;
  41. protected
  42. procedure SetBumpmapLibrary(const val: TGLMaterialLibrary);
  43. procedure SetBumpScale(const val: Single);
  44. function StoreBumpScale: Boolean;
  45. procedure SetSubSampling(const val: Integer);
  46. procedure Trim(MaxTextureCount: Integer);
  47. public
  48. constructor Create(AOwner: TComponent); override;
  49. destructor Destroy; override;
  50. procedure Release(aHeightData: TGLHeightData); override;
  51. procedure Notification(AComponent: TComponent;
  52. Operation: TOperation); override;
  53. procedure GenerateNormalMap(heightData: TGLHeightData; normalMap: TGLImage;
  54. scale: Single);
  55. (* This will repeatedly delete the oldest unused texture from the TGLMaterialLibrary,
  56. until the texture count drops to MaxTextureCount.
  57. DONT use this if you used TGLHeightData.MaterialName to link your terrain textures.
  58. Either use with TGLHeightData.LibMaterial, or manually delete unused Normal-Map textures.*)
  59. procedure TrimTextureCache(MaxTextureCount: Integer);
  60. // procedure TileTextureCoordinates(heightData : TGLHeightData; TextureScale:TTexPoint; TextureOffset:TTexPoint);
  61. procedure PreparingData(heightData: TGLHeightData); override;
  62. published
  63. property BumpmapLibrary: TGLMaterialLibrary read FBumpmapLibrary
  64. write SetBumpmapLibrary;
  65. property OnNewTilePrepared: TNewTilePreparedEvent read FOnNewTilePrepared
  66. write FOnNewTilePrepared;
  67. property BumpScale: Single read FBumpScale write SetBumpScale
  68. stored StoreBumpScale;
  69. (* Specifies the amount of subsampling for the bump texture.
  70. This value must be a power of 2, and is used to divide the height
  71. tile resolution to determine the bump texture resolution (f.i.
  72. a tile size of 128 with a subsampling of 4 will result in textures
  73. of a resolution of 32x32. SubSampling won't allow texture resolution
  74. to get below 16x16 (minimal bumpmap resolution). *)
  75. property SubSampling: Integer read FSubSampling write SetSubSampling
  76. default 1;
  77. property MaxPoolSize;
  78. (* If MaxTextures>0 then the Bumpmap library is trimmed down to size whenever
  79. the texture count is larger than MaxTextures. The oldest, unused texture is trimmed first.
  80. However, if you used TGLHeightData.MaterialName, instead of TGLHeightData.LibMaterial,
  81. then the TGLHeightData component does not register the texture as being used.
  82. So, if you use TGLHeightData.MaterialName then make sure MaxTextures=0.
  83. If MaxTextures=0 or if treads(GLAsyncHDS) are used, then the texture cache
  84. is NOT trimmed automatically.
  85. You will have to manually trim the cache from the main thread, by
  86. calling 'TrimTextureCache'. (GLAsyncHDS.OnIdle is a good place.) *)
  87. property MaxTextures: Integer read FMaxTextures write FMaxTextures;
  88. property OnSourceDataFetched;
  89. end;
  90. // ------------------------------------------------------------------
  91. implementation
  92. // ------------------------------------------------------------------
  93. // ------------------
  94. // ------------------ TGLBumpmapHDS ------------------
  95. // ------------------
  96. const
  97. cDefaultBumpScale = 0.01;
  98. constructor TGLBumpmapHDS.Create(AOwner: TComponent);
  99. begin
  100. inherited Create(AOwner);
  101. FBumpScale := cDefaultBumpScale;
  102. FSubSampling := 1;
  103. Uno := TCriticalSection.Create;
  104. end;
  105. destructor TGLBumpmapHDS.Destroy;
  106. begin
  107. BumpmapLibrary := nil;
  108. Uno.Free;
  109. inherited Destroy;
  110. end;
  111. procedure TGLBumpmapHDS.Notification(AComponent: TComponent;
  112. Operation: TOperation);
  113. begin
  114. if Operation = opRemove then
  115. begin
  116. if AComponent = FBumpmapLibrary then
  117. BumpmapLibrary := nil;
  118. end;
  119. inherited;
  120. end;
  121. procedure TGLBumpmapHDS.Release(aHeightData: TGLHeightData);
  122. var
  123. libMat: TGLLibMaterial;
  124. begin
  125. libMat := aHeightData.LibMaterial;
  126. aHeightData.MaterialName := '';
  127. if (FMaxTextures > 0) and (assigned(libMat)) and (libMat.IsUsed = false) then
  128. libMat.Free;
  129. inherited;
  130. end;
  131. procedure TGLBumpmapHDS.TrimTextureCache(MaxTextureCount: Integer);
  132. // Thread-safe Version
  133. begin
  134. if assigned(self) then
  135. begin
  136. Uno.Acquire;
  137. Trim(MaxTextureCount);
  138. Uno.Release;
  139. end;
  140. end;
  141. procedure TGLBumpmapHDS.Trim(MaxTextureCount: Integer); // internal use only
  142. var
  143. matLib: TGLMaterialLibrary;
  144. libMat: TGLLibMaterial;
  145. i: Integer;
  146. cnt: Integer;
  147. begin
  148. matLib := FBumpmapLibrary;
  149. if matLib <> nil then
  150. begin
  151. cnt := matLib.Materials.Count;
  152. i := 0;
  153. while (i < cnt) and (cnt >= MaxTextureCount) do
  154. begin
  155. libMat := matLib.Materials[i];
  156. if libMat.IsUsed then
  157. i := i + 1
  158. else
  159. libMat.Free;
  160. cnt := matLib.Materials.Count;
  161. end;
  162. end;
  163. end;
  164. procedure TGLBumpmapHDS.PreparingData(heightData: TGLHeightData);
  165. var
  166. TmpHD: TGLHeightData;
  167. libMat: TGLLibMaterial;
  168. bmp32: TGLImage;
  169. MatName: string;
  170. begin
  171. if not assigned(FBumpmapLibrary) then
  172. exit;
  173. // --Generate Normal Map for tile--
  174. heightData.TextureCoordinatesMode := tcmLocal;
  175. heightData.TextureCoordinatesOffset := NullTexPoint;
  176. heightData.TextureCoordinatesScale := XYTexPoint;
  177. MatName := 'BumpHDS_x' + IntToStr(heightData.XLeft) + 'y' +
  178. IntToStr(heightData.YTop) + '.';
  179. // name contains xy coordinates of the current tile
  180. Uno.Acquire;
  181. libMat := FBumpmapLibrary.Materials.GetLibMaterialByName(MatName);
  182. // Check if Tile Texture already exists
  183. if libMat = nil then
  184. begin
  185. if (FMaxTextures > 0) then
  186. begin
  187. if heightData.Thread = nil { //Dont trim the cache from a sub-thread; }
  188. then
  189. TrimTextureCache(FMaxTextures)
  190. // Trim unused textures from the material library
  191. end;
  192. // Generate new NormalMap texture for this tile
  193. libMat := FBumpmapLibrary.Materials.Add;
  194. libMat.Name := MatName;
  195. // Transfer tile texture coordinates to generated texture
  196. libMat.TextureScale.X := heightData.TextureCoordinatesScale.S;
  197. libMat.TextureScale.Y := heightData.TextureCoordinatesScale.T;
  198. libMat.TextureOffset.X := heightData.TextureCoordinatesOffset.S;
  199. libMat.TextureOffset.Y := heightData.TextureCoordinatesOffset.T;
  200. // ------------------------------------------------------
  201. // --Set up new Normalmap texture for the current tile--
  202. libMat.Material.MaterialOptions := [moNoLighting];
  203. with libMat.Material.Texture do
  204. begin
  205. ImageClassName := TGLBlankImage.ClassName;
  206. Enabled := True;
  207. MinFilter := miNearestMipmapNearest;
  208. MagFilter := maLinear; // MagFilter:=maNearest;
  209. TextureMode := tmReplace;
  210. TextureWrap := twNone;
  211. TextureFormat := tfRGB16;
  212. // TextureFormat:=tfRGBA16;
  213. bmp32 := (Image as TGLBlankImage).GetBitmap32;
  214. TmpHD := HeightDataSource.GetData(heightData.XLeft - 1,
  215. heightData.YTop - 1, heightData.Size + 1, heightData.DataType);
  216. GenerateNormalMap(TmpHD, bmp32, FBumpScale);
  217. TmpHD.Release;
  218. end;
  219. // ----------------------------------------------------
  220. end;
  221. // HD.MaterialName:=LibMat.Name;
  222. heightData.LibMaterial := libMat; // attach texture to current tile
  223. if assigned(FOnNewTilePrepared) then
  224. FOnNewTilePrepared(self, heightData, libMat);
  225. Uno.Release;
  226. end;
  227. procedure TGLBumpmapHDS.GenerateNormalMap(heightData: TGLHeightData;
  228. normalMap: TGLImage; scale: Single);
  229. var
  230. MapSize: Integer;
  231. HD: TGLHeightData;
  232. X, Y: Integer;
  233. scaleVec: TAffineVector;
  234. vec: TAffineVector;
  235. nmRow: PPixel32Array;
  236. px, py: Integer;
  237. begin
  238. HD := heightData;
  239. MapSize := (HD.Size - 1);
  240. MapSize := MapSize div SubSampling;
  241. normalMap.Height := MapSize;
  242. normalMap.Width := MapSize;
  243. normalMap.Blank := false;
  244. SetVector(scaleVec, 1, 1, FBumpScale);
  245. for Y := 0 to MapSize - 1 do
  246. begin
  247. nmRow := normalMap.ScanLine[MapSize - 1 - Y];
  248. for X := 0 to MapSize - 1 do
  249. begin
  250. px := X * SubSampling;
  251. py := Y * SubSampling;
  252. vec := HD.NormalAtNode(px, py, scaleVec);
  253. nmRow[X].r := round(128 + 127 * vec.X); // nmRow[x].r:=0; //Red
  254. nmRow[X].g := round(128 + 127 * vec.Y);
  255. // nmRow[x].g:=0; //Green
  256. nmRow[X].b := round(128 + 127 * vec.Z);
  257. // nmRow[x].b:=0; //Blue
  258. nmRow[X].a := 255;
  259. end;
  260. end;
  261. end;
  262. procedure TGLBumpmapHDS.SetBumpmapLibrary(const val: TGLMaterialLibrary);
  263. begin
  264. if val <> FBumpmapLibrary then
  265. begin
  266. if assigned(FBumpmapLibrary) then
  267. FBumpmapLibrary.RemoveFreeNotification(self);
  268. FBumpmapLibrary := val;
  269. if assigned(FBumpmapLibrary) then
  270. FBumpmapLibrary.FreeNotification(self);
  271. MarkDirty;
  272. end;
  273. end;
  274. procedure TGLBumpmapHDS.SetBumpScale(const val: Single);
  275. begin
  276. if FBumpScale <> val then
  277. begin
  278. FBumpScale := val;
  279. MarkDirty;
  280. end;
  281. end;
  282. function TGLBumpmapHDS.StoreBumpScale: Boolean;
  283. begin
  284. Result := (FBumpScale <> cDefaultBumpScale);
  285. end;
  286. procedure TGLBumpmapHDS.SetSubSampling(const val: Integer);
  287. begin
  288. if val <> FSubSampling then
  289. begin
  290. FSubSampling := RoundDownToPowerOf2(val);
  291. if FSubSampling < 1 then
  292. FSubSampling := 1;
  293. MarkDirty;
  294. end;
  295. end;
  296. // ------------------------------------------------------------------
  297. initialization
  298. // ------------------------------------------------------------------
  299. RegisterClass(TGLBumpmapHDS);
  300. end.