GXS.BumpmapHDS.pas 10 KB

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