日期:2014-05-17 浏览次数:21549 次
纹理贴图映射(texturemapping)是可以显著提高场景细节和真实感的一种技术,基本原理是将图像数据映射到3D三角形表面(之前的文章提到过,三维模型其实是由很多个三角形拼接而成)。当使用纹理资源时,只要将每个3D三角形与纹理资源上的三角形对应,就可以实现贴图效果。如图1,有一个立方体模型和纹理贴图,将立方体上的点与纹理贴图上的点对应,就像给一个没有颜色的正方体贴一层木纹包装纸。
Direct3D的纹理坐标系由表示图像水平方向的u轴和表示图像垂直方向的v轴组成。坐标 (u,v) 指定了纹理上的一个元素,该元素称为纹理元素(texture element),其中 0≤u,v≤1。将规范化坐标区间设为[0,1]是因为这样可以使Direct3D拥有一个独立于纹理尺寸的坐标空间。即无论纹理的实际尺寸是 256×256还是512×512,(0.5,0.5)永远表示中间的纹理元素。
另外还有一个问题,纹理资源不管多精细都是由离散的数据点组成,如果指定的纹理坐标(u,v)与任何一个纹理元素点都不对应时该怎么办?如图1中的立方体。假设木头纹理的分辨率为 512×512,显示器的分辨率为 1024×1024,当观察点逐渐靠近立方体,立方体会被放大,甚至能盖住整个屏幕。这时就需要用很少的纹理元素来覆盖很多的像素,称为倍增。与倍增相反的问题是缩减,要用很多的纹理元素来覆盖很少的像素。DirectX为解决这些问题定义了多种过滤器,如点过滤和线性过滤。使用时只要对应好顶点和其纹理坐标,过滤器就能通过插值或抽取估计顶点之间每个像素的颜色。
下面就来实现纹理贴图映射。
使用模版新建Direct3D立方体项目。首先依然是更改HLSL代码。
顶点着色器部分:使用纹理资源时不需要指定颜色,所以用这部分空间存储顶点的法向量,用于计算光照效果。添加纹理坐标成员,它与3D顶点坐标对应。这样,每3个顶点构成的3D三角形在纹理空间中都会有一个对应的2D纹理三角形。代码如下:
cbuffer ModelViewProjectionConstantBuffer : register(b0) { matrix model; matrix view; matrix projection; }; struct VertexShaderInput { float3 pos : POSITION; float3 normal : NORMAL; float2 tex : TEXCOOD; }; struct VertexShaderOutput { float4 pos : SV_POSITION; float3 normal : NORMAL; float2 tex : TEXCOOD; }; VertexShaderOutput main(VertexShaderInput input) { VertexShaderOutputoutput; float4 pos = float4(input.pos, 1.0f); // 转换坐标到投影空间 pos = mul(pos,model); pos = mul(pos,view); pos = mul(pos,projection); output.pos =pos; // 转换法向量到世界空间用于光照计算 float4 normal = float4(normalize(input.normal),0.0f); normal =mul(normal, model); output.normal =normalize(normal.xyz); // 纹理坐标不需要改动 output.tex =input.tex; return output; }像素着色器部分:输入结构体定义必须和顶点着色器的输出结构体格式一致。还要添加纹理资源,并在main方法中添加简单漫反射光的计算。另外,使用过滤器访问纹理资源需要通过采样器。代码如下:
SamplerState samplerLinear : register(s0); Texture2D woodDiffuse : register(t0); struct PixelShaderInput { float4 pos : SV_POSITION; float3 normal : NORMAL; float2 tex : TEXCOOD; }; float4 main(PixelShaderInput input) : SV_TARGET { float3 lightDirection =normalize(float3(1, -1, 0)); float4 texelColor = woodDiffuse.Sample(samplerLinear,input.tex); // 计算简单漫反射 float lightMagnitude =0.8f * saturate(dot(input.normal, -lightDirection)) + 0.2f; return texelColor *lightMagnitude; }
HLSL代码完成后就要修改主程序。在Windows 8 Store App中载入纹理资源可以使用WICTextureLoader。它支持读取多种图片资源(jpg、png)创建纹理。使用时将.cpp和.h文件加入项目即可。如果想载入DDS格式的资源可以看DirectXTex的说明。
完成后在CubeRenderer类里更改结构体定义,与着色器对应:
struct VertexPositionColor { DirectX::XMFLOAT3 pos; DirectX::XMFLOAT3 normal; DirectX::XMFLOAT2 tex; };
然后添加三个成员以使用纹理资源和采样器:
ID3D11Resource* tex; Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_WoodSRV; Microsoft::WRL::ComPtr<ID3D11SamplerState> m_Sampler;
接着就开始修改初始化部分。在CreateDeviceResources方法中在载入顶点着色器和像素着色器之后添加createWoodTexTask,用于初始化纹理资源和采样器:
auto createWoodTexTask = (createPSTask &&createVSTask).then([this] () { DX::ThrowIfFailed( CreateWICTextureFromFile( m_d3dDevice.Get(), m_d3dContext.Get(), L"wood.jpg", &tex, m_WoodSRV.GetAddressOf() ) ); D3D11_SAMPLER_DESC samplerDesc; samplerDesc.Filter= D3D11_FILTER_MIN_MAG_MIP_LINEAR; samplerDesc.AddressU= D3D11_TEXTURE_ADDRESS_WRAP; samplerDesc.AddressV= D3D11_TEXTURE_ADDRESS_WRAP; samplerDes