crossxtex/DirectXTex/DirectXTexNormalMaps.cpp
2018-02-23 22:24:46 -08:00

394 lines
12 KiB
C++

//-------------------------------------------------------------------------------------
// DirectXTexNormalMaps.cpp
//
// DirectX Texture Library - Normal map operations
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// http://go.microsoft.com/fwlink/?LinkId=248926
//-------------------------------------------------------------------------------------
#include "directxtexp.h"
using namespace DirectX;
namespace
{
#pragma prefast(suppress : 25000, "FXMVECTOR is 16 bytes")
inline float EvaluateColor(_In_ FXMVECTOR val, _In_ DWORD flags)
{
XMFLOAT4A f;
static XMVECTORF32 lScale = { { { 0.2125f, 0.7154f, 0.0721f, 1.f } } };
static_assert(CNMAP_CHANNEL_RED == 0x1, "CNMAP_CHANNEL_ flag values don't match mask");
switch (flags & 0xf)
{
case 0:
case CNMAP_CHANNEL_RED: return XMVectorGetX(val);
case CNMAP_CHANNEL_GREEN: return XMVectorGetY(val);
case CNMAP_CHANNEL_BLUE: return XMVectorGetZ(val);
case CNMAP_CHANNEL_ALPHA: return XMVectorGetW(val);
case CNMAP_CHANNEL_LUMINANCE:
{
XMVECTOR v = XMVectorMultiply(val, lScale);
XMStoreFloat4A(&f, v);
return f.x + f.y + f.z;
}
break;
default:
assert(false);
return 0.f;
}
}
void EvaluateRow(
_In_reads_(width) const XMVECTOR* pSource,
_Out_writes_(width + 2) float* pDest,
size_t width,
DWORD flags)
{
assert(pSource && pDest);
assert(width > 0);
for (size_t x = 0; x < width; ++x)
{
pDest[x + 1] = EvaluateColor(pSource[x], flags);
}
if (flags & CNMAP_MIRROR_U)
{
// Mirror in U
pDest[0] = EvaluateColor(pSource[0], flags);
pDest[width + 1] = EvaluateColor(pSource[width - 1], flags);
}
else
{
// Wrap in U
pDest[0] = EvaluateColor(pSource[width - 1], flags);
pDest[width + 1] = EvaluateColor(pSource[0], flags);
}
}
HRESULT ComputeNMap(_In_ const Image& srcImage, _In_ DWORD flags, _In_ float amplitude,
_In_ DXGI_FORMAT format, _In_ const Image& normalMap)
{
if (!srcImage.pixels || !normalMap.pixels)
return E_INVALIDARG;
const DWORD convFlags = _GetConvertFlags(format);
if (!convFlags)
return E_FAIL;
if (!(convFlags & (CONVF_UNORM | CONVF_SNORM | CONVF_FLOAT)))
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
const size_t width = srcImage.width;
const size_t height = srcImage.height;
if (width != normalMap.width || height != normalMap.height)
return E_FAIL;
// Allocate temporary space (4 scanlines and 3 evaluated rows)
ScopedAlignedArrayXMVECTOR scanline(reinterpret_cast<XMVECTOR*>(_aligned_malloc((sizeof(XMVECTOR)*width * 4), 16)));
if (!scanline)
return E_OUTOFMEMORY;
ScopedAlignedArrayFloat buffer(reinterpret_cast<float*>(_aligned_malloc(((sizeof(float) * (width + 2)) * 3), 16)));
if (!buffer)
return E_OUTOFMEMORY;
uint8_t* pDest = normalMap.pixels;
if (!pDest)
return E_POINTER;
XMVECTOR* row0 = scanline.get();
XMVECTOR* row1 = row0 + width;
XMVECTOR* row2 = row1 + width;
XMVECTOR* target = row2 + width;
float* val0 = buffer.get();
float* val1 = val0 + width + 2;
float* val2 = val1 + width + 2;
const size_t rowPitch = srcImage.rowPitch;
const uint8_t* pSrc = srcImage.pixels;
// Read first scanline row into 'row1'
if (!_LoadScanline(row1, width, pSrc, rowPitch, srcImage.format))
return E_FAIL;
// Setup 'row0'
if (flags & CNMAP_MIRROR_V)
{
// Mirror first row
memcpy_s(row0, rowPitch, row1, rowPitch);
}
else
{
// Read last row (Wrap V)
if (!_LoadScanline(row0, width, pSrc + (rowPitch * (height - 1)), rowPitch, srcImage.format))
return E_FAIL;
}
// Evaluate the initial rows
EvaluateRow(row0, val0, width, flags);
EvaluateRow(row1, val1, width, flags);
pSrc += rowPitch;
for (size_t y = 0; y < height; ++y)
{
// Load next scanline of source image
if (y < (height - 1))
{
if (!_LoadScanline(row2, width, pSrc, rowPitch, srcImage.format))
return E_FAIL;
}
else
{
if (flags & CNMAP_MIRROR_V)
{
// Use last row of source image
if (!_LoadScanline(row2, width, srcImage.pixels + (rowPitch * (height - 1)), rowPitch, srcImage.format))
return E_FAIL;
}
else
{
// Use first row of source image (Wrap V)
if (!_LoadScanline(row2, width, srcImage.pixels, rowPitch, srcImage.format))
return E_FAIL;
}
}
// Evaluate row
EvaluateRow(row2, val2, width, flags);
// Generate target scanline
XMVECTOR *dptr = target;
for (size_t x = 0; x < width; ++x)
{
// Compute normal via central differencing
float totDelta = (val0[x] - val0[x + 2]) + (val1[x] - val1[x + 2]) + (val2[x] - val2[x + 2]);
float deltaZX = totDelta * amplitude / 6.f;
totDelta = (val0[x] - val2[x]) + (val0[x + 1] - val2[x + 1]) + (val0[x + 2] - val2[x + 2]);
float deltaZY = totDelta * amplitude / 6.f;
XMVECTOR vx = XMVectorSetZ(g_XMNegIdentityR0, deltaZX); // (-1.0f, 0.0f, deltaZX)
XMVECTOR vy = XMVectorSetZ(g_XMNegIdentityR1, deltaZY); // (0.0f, -1.0f, deltaZY)
XMVECTOR normal = XMVector3Normalize(XMVector3Cross(vx, vy));
// Compute alpha (1.0 or an occlusion term)
float alpha = 1.f;
if (flags & CNMAP_COMPUTE_OCCLUSION)
{
float delta = 0.f;
float c = val1[x + 1];
float t = val0[x] - c; if (t > 0.f) delta += t;
t = val0[x + 1] - c; if (t > 0.f) delta += t;
t = val0[x + 2] - c; if (t > 0.f) delta += t;
t = val1[x] - c; if (t > 0.f) delta += t;
// Skip current pixel
t = val1[x + 2] - c; if (t > 0.f) delta += t;
t = val2[x] - c; if (t > 0.f) delta += t;
t = val2[x + 1] - c; if (t > 0.f) delta += t;
t = val2[x + 2] - c; if (t > 0.f) delta += t;
// Average delta (divide by 8, scale by amplitude factor)
delta *= 0.125f * amplitude;
if (delta > 0.f)
{
// If < 0, then no occlusion
float r = sqrtf(1.f + delta*delta);
alpha = (r - delta) / r;
}
}
// Encode based on target format
if (convFlags & CONVF_UNORM)
{
// 0.5f*normal + 0.5f -or- invert sign case: -0.5f*normal + 0.5f
XMVECTOR n1 = XMVectorMultiplyAdd((flags & CNMAP_INVERT_SIGN) ? g_XMNegativeOneHalf : g_XMOneHalf, normal, g_XMOneHalf);
*dptr++ = XMVectorSetW(n1, alpha);
}
else if (flags & CNMAP_INVERT_SIGN)
{
*dptr++ = XMVectorSetW(XMVectorNegate(normal), alpha);
}
else
{
*dptr++ = XMVectorSetW(normal, alpha);
}
}
if (!_StoreScanline(pDest, normalMap.rowPitch, format, target, width))
return E_FAIL;
// Cycle buffers
float* temp = val0;
val0 = val1;
val1 = val2;
val2 = temp;
pSrc += rowPitch;
pDest += normalMap.rowPitch;
}
return S_OK;
}
}
//=====================================================================================
// Entry points
//=====================================================================================
//-------------------------------------------------------------------------------------
// Generates a normal map from a height-map
//-------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT DirectX::ComputeNormalMap(
const Image& srcImage,
DWORD flags,
float amplitude,
DXGI_FORMAT format,
ScratchImage& normalMap)
{
if (!srcImage.pixels || !IsValid(format))
return E_INVALIDARG;
static_assert(CNMAP_CHANNEL_RED == 0x1, "CNMAP_CHANNEL_ flag values don't match mask");
switch (flags & 0xf)
{
case 0:
case CNMAP_CHANNEL_RED:
case CNMAP_CHANNEL_GREEN:
case CNMAP_CHANNEL_BLUE:
case CNMAP_CHANNEL_ALPHA:
case CNMAP_CHANNEL_LUMINANCE:
break;
default:
return E_INVALIDARG;
}
if (IsCompressed(format) || IsCompressed(srcImage.format)
|| IsTypeless(format) || IsTypeless(srcImage.format)
|| IsPlanar(format) || IsPlanar(srcImage.format)
|| IsPalettized(format) || IsPalettized(srcImage.format))
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
// Setup target image
normalMap.Release();
HRESULT hr = normalMap.Initialize2D(format, srcImage.width, srcImage.height, 1, 1);
if (FAILED(hr))
return hr;
const Image *img = normalMap.GetImage(0, 0, 0);
if (!img)
{
normalMap.Release();
return E_POINTER;
}
hr = ComputeNMap(srcImage, flags, amplitude, format, *img);
if (FAILED(hr))
{
normalMap.Release();
return hr;
}
return S_OK;
}
_Use_decl_annotations_
HRESULT DirectX::ComputeNormalMap(
const Image* srcImages,
size_t nimages,
const TexMetadata& metadata,
DWORD flags,
float amplitude,
DXGI_FORMAT format,
ScratchImage& normalMaps)
{
if (!srcImages || !nimages || !IsValid(format))
return E_INVALIDARG;
if (IsCompressed(format) || IsCompressed(metadata.format)
|| IsTypeless(format) || IsTypeless(metadata.format)
|| IsPlanar(format) || IsPlanar(metadata.format)
|| IsPalettized(format) || IsPalettized(metadata.format))
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
static_assert(CNMAP_CHANNEL_RED == 0x1, "CNMAP_CHANNEL_ flag values don't match mask");
switch (flags & 0xf)
{
case 0:
case CNMAP_CHANNEL_RED:
case CNMAP_CHANNEL_GREEN:
case CNMAP_CHANNEL_BLUE:
case CNMAP_CHANNEL_ALPHA:
case CNMAP_CHANNEL_LUMINANCE:
break;
default:
return E_INVALIDARG;
}
normalMaps.Release();
TexMetadata mdata2 = metadata;
mdata2.format = format;
HRESULT hr = normalMaps.Initialize(mdata2);
if (FAILED(hr))
return hr;
if (nimages != normalMaps.GetImageCount())
{
normalMaps.Release();
return E_FAIL;
}
const Image* dest = normalMaps.GetImages();
if (!dest)
{
normalMaps.Release();
return E_POINTER;
}
for (size_t index = 0; index < nimages; ++index)
{
assert(dest[index].format == format);
const Image& src = srcImages[index];
if (IsCompressed(src.format) || IsTypeless(src.format))
{
normalMaps.Release();
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
}
if (src.width != dest[index].width || src.height != dest[index].height)
{
normalMaps.Release();
return E_FAIL;
}
hr = ComputeNMap(src, flags, amplitude, format, dest[index]);
if (FAILED(hr))
{
normalMaps.Release();
return hr;
}
}
return S_OK;
}