DirectXTexNormalMaps.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. //-------------------------------------------------------------------------------------
  2. // DirectXTexNormalMaps.cpp
  3. //
  4. // DirectX Texture Library - Normal map operations
  5. //
  6. // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
  7. // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
  8. // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
  9. // PARTICULAR PURPOSE.
  10. //
  11. // Copyright (c) Microsoft Corporation. All rights reserved.
  12. //
  13. // http://go.microsoft.com/fwlink/?LinkId=248926
  14. //-------------------------------------------------------------------------------------
  15. #include "directxtexp.h"
  16. namespace DirectX
  17. {
  18. #pragma prefast(suppress : 25000, "FXMVECTOR is 16 bytes")
  19. static inline float _EvaluateColor( _In_ FXMVECTOR val, _In_ DWORD flags )
  20. {
  21. XMFLOAT4A f;
  22. static XMVECTORF32 lScale = { 0.2125f, 0.7154f, 0.0721f, 1.f };
  23. static_assert( CNMAP_CHANNEL_RED == 0x1, "CNMAP_CHANNEL_ flag values don't match mask" );
  24. switch( flags & 0xf )
  25. {
  26. case 0:
  27. case CNMAP_CHANNEL_RED: return XMVectorGetX( val );
  28. case CNMAP_CHANNEL_GREEN: return XMVectorGetY( val );
  29. case CNMAP_CHANNEL_BLUE: return XMVectorGetZ( val );
  30. case CNMAP_CHANNEL_ALPHA: return XMVectorGetW( val );
  31. case CNMAP_CHANNEL_LUMINANCE:
  32. {
  33. XMVECTOR v = XMVectorMultiply( val, lScale );
  34. XMStoreFloat4A( &f, v );
  35. return f.x + f.y + f.z;
  36. }
  37. break;
  38. default:
  39. assert(false);
  40. return 0.f;
  41. }
  42. }
  43. static void _EvaluateRow( _In_reads_(width) const XMVECTOR* pSource, _Out_writes_(width+2) float* pDest,
  44. _In_ size_t width, _In_ DWORD flags )
  45. {
  46. assert( pSource && pDest );
  47. assert( width > 0 );
  48. for( size_t x = 0; x < width; ++x )
  49. {
  50. pDest[x+1] = _EvaluateColor( pSource[x], flags );
  51. }
  52. if ( flags & CNMAP_MIRROR_U )
  53. {
  54. // Mirror in U
  55. pDest[0] = _EvaluateColor( pSource[0], flags );
  56. pDest[width+1] = _EvaluateColor( pSource[width-1], flags );
  57. }
  58. else
  59. {
  60. // Wrap in U
  61. pDest[0] = _EvaluateColor( pSource[width-1], flags );
  62. pDest[width+1] = _EvaluateColor( pSource[0], flags );
  63. }
  64. }
  65. static HRESULT _ComputeNMap( _In_ const Image& srcImage, _In_ DWORD flags, _In_ float amplitude,
  66. _In_ DXGI_FORMAT format, _In_ const Image& normalMap )
  67. {
  68. if ( !srcImage.pixels || !normalMap.pixels )
  69. return E_INVALIDARG;
  70. const DWORD convFlags = _GetConvertFlags( format );
  71. if ( !convFlags )
  72. return E_FAIL;
  73. if ( !( convFlags & (CONVF_UNORM | CONVF_SNORM | CONVF_FLOAT) ) )
  74. return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
  75. const size_t width = srcImage.width;
  76. const size_t height = srcImage.height;
  77. if ( width != normalMap.width || height != normalMap.height )
  78. return E_FAIL;
  79. // Allocate temporary space (4 scanlines and 3 evaluated rows)
  80. ScopedAlignedArrayXMVECTOR scanline( reinterpret_cast<XMVECTOR*>( _aligned_malloc( (sizeof(XMVECTOR)*width*4), 16 ) ) );
  81. if ( !scanline )
  82. return E_OUTOFMEMORY;
  83. ScopedAlignedArrayFloat buffer( reinterpret_cast<float*>( _aligned_malloc( ( ( sizeof(float) * ( width + 2 ) ) * 3 ), 16 ) ) );
  84. if ( !buffer )
  85. return E_OUTOFMEMORY;
  86. uint8_t* pDest = normalMap.pixels;
  87. if ( !pDest )
  88. return E_POINTER;
  89. XMVECTOR* row0 = scanline.get();
  90. XMVECTOR* row1 = row0 + width;
  91. XMVECTOR* row2 = row1 + width;
  92. XMVECTOR* target = row2 + width;
  93. float* val0 = buffer.get();
  94. float* val1 = val0 + width + 2;
  95. float* val2 = val1 + width + 2;
  96. const size_t rowPitch = srcImage.rowPitch;
  97. const uint8_t* pSrc = srcImage.pixels;
  98. // Read first scanline row into 'row1'
  99. if ( !_LoadScanline( row1, width, pSrc, rowPitch, srcImage.format ) )
  100. return E_FAIL;
  101. // Setup 'row0'
  102. if ( flags & CNMAP_MIRROR_V )
  103. {
  104. // Mirror first row
  105. memcpy_s( row0, rowPitch, row1, rowPitch );
  106. }
  107. else
  108. {
  109. // Read last row (Wrap V)
  110. if ( !_LoadScanline( row0, width, pSrc + (rowPitch * (height-1)), rowPitch, srcImage.format ) )
  111. return E_FAIL;
  112. }
  113. // Evaluate the initial rows
  114. _EvaluateRow( row0, val0, width, flags );
  115. _EvaluateRow( row1, val1, width, flags );
  116. pSrc += rowPitch;
  117. for( size_t y = 0; y < height; ++y )
  118. {
  119. // Load next scanline of source image
  120. if ( y < (height-1) )
  121. {
  122. if ( !_LoadScanline( row2, width, pSrc, rowPitch, srcImage.format ) )
  123. return E_FAIL;
  124. }
  125. else
  126. {
  127. if ( flags & CNMAP_MIRROR_V )
  128. {
  129. // Use last row of source image
  130. if ( !_LoadScanline( row2, width, srcImage.pixels + (rowPitch * (height-1)), rowPitch, srcImage.format ) )
  131. return E_FAIL;
  132. }
  133. else
  134. {
  135. // Use first row of source image (Wrap V)
  136. if ( !_LoadScanline( row2, width, srcImage.pixels, rowPitch, srcImage.format ) )
  137. return E_FAIL;
  138. }
  139. }
  140. // Evaluate row
  141. _EvaluateRow( row2, val2, width, flags );
  142. // Generate target scanline
  143. XMVECTOR *dptr = target;
  144. for( size_t x = 0; x < width; ++x )
  145. {
  146. // Compute normal via central differencing
  147. float totDelta = ( val0[x] - val0[x+2] ) + ( val1[x] - val1[x+2] ) + ( val2[x] - val2[x+2] );
  148. float deltaZX = totDelta * amplitude / 6.f;
  149. totDelta = ( val0[x] - val2[x] ) + ( val0[x+1] - val2[x+1] ) + ( val0[x+2] - val2[x+2] );
  150. float deltaZY = totDelta * amplitude / 6.f;
  151. XMVECTOR vx = XMVectorSetZ( g_XMNegIdentityR0, deltaZX ); // (-1.0f, 0.0f, deltaZX)
  152. XMVECTOR vy = XMVectorSetZ( g_XMNegIdentityR1, deltaZY ); // (0.0f, -1.0f, deltaZY)
  153. XMVECTOR normal = XMVector3Normalize( XMVector3Cross( vx, vy ) );
  154. // Compute alpha (1.0 or an occlusion term)
  155. float alpha = 1.f;
  156. if ( flags & CNMAP_COMPUTE_OCCLUSION )
  157. {
  158. float delta = 0.f;
  159. float c = val1[x+1];
  160. float t = val0[x] - c; if ( t > 0.f ) delta += t;
  161. t = val0[x+1] - c; if ( t > 0.f ) delta += t;
  162. t = val0[x+2] - c; if ( t > 0.f ) delta += t;
  163. t = val1[x] - c; if ( t > 0.f ) delta += t;
  164. // Skip current pixel
  165. t = val1[x+2] - c; if ( t > 0.f ) delta += t;
  166. t = val2[x] - c; if ( t > 0.f ) delta += t;
  167. t = val2[x+1] - c; if ( t > 0.f ) delta += t;
  168. t = val2[x+2] - c; if ( t > 0.f ) delta += t;
  169. // Average delta (divide by 8, scale by amplitude factor)
  170. delta *= 0.125f * amplitude;
  171. if ( delta > 0.f )
  172. {
  173. // If < 0, then no occlusion
  174. float r = sqrtf( 1.f + delta*delta );
  175. alpha = (r - delta) / r;
  176. }
  177. }
  178. // Encode based on target format
  179. if ( convFlags & CONVF_UNORM )
  180. {
  181. // 0.5f*normal + 0.5f -or- invert sign case: -0.5f*normal + 0.5f
  182. XMVECTOR n1 = XMVectorMultiplyAdd( (flags & CNMAP_INVERT_SIGN) ? g_XMNegativeOneHalf : g_XMOneHalf, normal, g_XMOneHalf );
  183. *dptr++ = XMVectorSetW( n1, alpha );
  184. }
  185. else if ( flags & CNMAP_INVERT_SIGN )
  186. {
  187. *dptr++ = XMVectorSetW( XMVectorNegate( normal ), alpha );
  188. }
  189. else
  190. {
  191. *dptr++ = XMVectorSetW( normal, alpha );
  192. }
  193. }
  194. if ( !_StoreScanline( pDest, normalMap.rowPitch, format, target, width ) )
  195. return E_FAIL;
  196. // Cycle buffers
  197. float* temp = val0;
  198. val0 = val1;
  199. val1 = val2;
  200. val2 = temp;
  201. pSrc += rowPitch;
  202. pDest += normalMap.rowPitch;
  203. }
  204. return S_OK;
  205. }
  206. //=====================================================================================
  207. // Entry points
  208. //=====================================================================================
  209. //-------------------------------------------------------------------------------------
  210. // Generates a normal map from a height-map
  211. //-------------------------------------------------------------------------------------
  212. _Use_decl_annotations_
  213. HRESULT ComputeNormalMap( const Image& srcImage, DWORD flags, float amplitude,
  214. DXGI_FORMAT format, ScratchImage& normalMap )
  215. {
  216. if ( !srcImage.pixels || !IsValid(format) )
  217. return E_INVALIDARG;
  218. static_assert( CNMAP_CHANNEL_RED == 0x1, "CNMAP_CHANNEL_ flag values don't match mask" );
  219. switch( flags & 0xf )
  220. {
  221. case 0:
  222. case CNMAP_CHANNEL_RED:
  223. case CNMAP_CHANNEL_GREEN:
  224. case CNMAP_CHANNEL_BLUE:
  225. case CNMAP_CHANNEL_ALPHA:
  226. case CNMAP_CHANNEL_LUMINANCE:
  227. break;
  228. default:
  229. return E_INVALIDARG;
  230. }
  231. if ( IsCompressed(format) || IsCompressed(srcImage.format)
  232. || IsTypeless(format) || IsTypeless(srcImage.format)
  233. || IsPlanar(format) || IsPlanar(srcImage.format)
  234. || IsPalettized(format) || IsPalettized(srcImage.format) )
  235. return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
  236. // Setup target image
  237. normalMap.Release();
  238. HRESULT hr = normalMap.Initialize2D( format, srcImage.width, srcImage.height, 1, 1 );
  239. if ( FAILED(hr) )
  240. return hr;
  241. const Image *img = normalMap.GetImage( 0, 0, 0 );
  242. if ( !img )
  243. {
  244. normalMap.Release();
  245. return E_POINTER;
  246. }
  247. hr = _ComputeNMap( srcImage, flags, amplitude, format, *img );
  248. if ( FAILED(hr) )
  249. {
  250. normalMap.Release();
  251. return hr;
  252. }
  253. return S_OK;
  254. }
  255. _Use_decl_annotations_
  256. HRESULT ComputeNormalMap( const Image* srcImages, size_t nimages, const TexMetadata& metadata,
  257. DWORD flags, float amplitude, DXGI_FORMAT format, ScratchImage& normalMaps )
  258. {
  259. if ( !srcImages || !nimages || !IsValid(format) )
  260. return E_INVALIDARG;
  261. if ( IsCompressed(format) || IsCompressed(metadata.format)
  262. || IsTypeless(format) || IsTypeless(metadata.format)
  263. || IsPlanar(format) || IsPlanar(metadata.format)
  264. || IsPalettized(format) || IsPalettized(metadata.format) )
  265. return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
  266. static_assert( CNMAP_CHANNEL_RED == 0x1, "CNMAP_CHANNEL_ flag values don't match mask" );
  267. switch( flags & 0xf )
  268. {
  269. case 0:
  270. case CNMAP_CHANNEL_RED:
  271. case CNMAP_CHANNEL_GREEN:
  272. case CNMAP_CHANNEL_BLUE:
  273. case CNMAP_CHANNEL_ALPHA:
  274. case CNMAP_CHANNEL_LUMINANCE:
  275. break;
  276. default:
  277. return E_INVALIDARG;
  278. }
  279. normalMaps.Release();
  280. TexMetadata mdata2 = metadata;
  281. mdata2.format = format;
  282. HRESULT hr = normalMaps.Initialize( mdata2 );
  283. if ( FAILED(hr) )
  284. return hr;
  285. if ( nimages != normalMaps.GetImageCount() )
  286. {
  287. normalMaps.Release();
  288. return E_FAIL;
  289. }
  290. const Image* dest = normalMaps.GetImages();
  291. if ( !dest )
  292. {
  293. normalMaps.Release();
  294. return E_POINTER;
  295. }
  296. for( size_t index=0; index < nimages; ++index )
  297. {
  298. assert( dest[ index ].format == format );
  299. const Image& src = srcImages[ index ];
  300. if ( IsCompressed( src.format ) || IsTypeless( src.format ) )
  301. {
  302. normalMaps.Release();
  303. return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
  304. }
  305. if ( src.width != dest[ index ].width || src.height != dest[ index ].height )
  306. {
  307. normalMaps.Release();
  308. return E_FAIL;
  309. }
  310. hr = _ComputeNMap( src, flags, amplitude, format, dest[ index ] );
  311. if ( FAILED(hr) )
  312. {
  313. normalMaps.Release();
  314. return hr;
  315. }
  316. }
  317. return S_OK;
  318. }
  319. }; // namespace