日期:2014-05-17  浏览次数:21549 次

Windows 8 Directx 开发学习笔记(十)纹理贴图实现旋转的木箱

纹理贴图映射(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