DirectX11学习笔记四 纹理入门

  纹理这东西经常玩游戏的多少都应该听说过,以前我经常把纹理、材质、贴图、模型、图片等等混为一谈,学了计算机图形学之后终于有些眉目了。对于基础入门来说(比如我),纹理可以理解为把一张图片贴在模型上。

加载纹理资源

  原来的龙书所推荐的读取贴图文件的方法已经被弃用,微软的说明文档上推荐使用DirectXTK提供的库,用来读取DDS文件。
函数定义

HRESULT CreateDDSTextureFromFile(
    ID3D11Device* d3dDevice,                // [In]D3D设备
    const wchar_t* szFileName,              // [In]dds图片文件名
    ID3D11Resource** texture,               // [Out]输出一个指向资源接口类的指针,也可以填nullptr
    ID3D11ShaderResourceView** textureView, // [Out]输出一个指向着色器资源视图的指针,也可以填nullptr
    size_t maxsize = 0,                     // [In]忽略
    DDS_ALPHA_MODE* alphaMode = nullptr);  // [In]忽略

调用示例

// 初始化木箱纹理
	HR(CreateDDSTextureFromFile(m_pd3dDevice.Get(), L"Texture\\WoodCrate.dds", nullptr, m_pWoodCrate.GetAddressOf()));

  显然,这里用到了一个叫ID3D11ShaderResourceView的类对象m_pWoodCrate,微软的定义如下。

A shader-resource-view interface specifies the subresources a shader can access during rendering. Examples of shader resources include a constant buffer, a texture buffer, and a texture.

  读着很熟悉,用来绑定常量缓冲区、纹理缓冲区和纹理等,提供着色器在渲染时的访问,可是我们之前渲染光照或者渲染立方体的时候也用到了常量缓冲区,但是并没有用到ShaderResourceView,这是怎么回事呢?
复习一遍常量缓冲区的绑定流程

  • D3D11_BUFFER_DESC和m_pd3dDevice->CreateBuffer()来创建常量缓冲区
  • D3D11_MAPPED_SUBRESOURCE和Map方法映射对应的缓冲区动态修改
  • m_pd3dImmediateContext->VSSetConstantBuffers() 将缓冲区与渲染管线绑定

  而反观ComPtr m_pWoodCrate; // 木盒纹理

  • CreateDDSTextureFromFile()
  • m_pd3dImmediateContext->PSSetShaderResources();
    好像缺少了Create这个步骤,可能是封装到了CreateDDSTextureFromFile方法中。
    读取完后绑定到渲染管线。
	m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pWoodCrate.GetAddressOf());

初识纹理

  纹理分为三种,一维,二维和三维,只讨论二维纹理,也就是图片。通过纹理映射,设置相应的坐标与顶点坐标对应,来将一张图片“钉”在三角形面上,因此在顶点着色器中纹理坐标不需要做类似法线变换。根据透视的不同,纹理不可能保持原始大小,所以必须要进行插值,插值原理跟像素着色器插值类似。
  这就要求在传入的顶点输入格式中再定义一个TEXCOORD,并设置相应与纹理对应的数值,如何对应?这就引入了纹理坐标系
DirectX11学习笔记四 纹理入门_第1张图片
  纹理坐标系的轴用u和v表示,范围[0,1]用来规范化坐标系。以立方体中正对着摄像机的面为例
DirectX11学习笔记四 纹理入门_第2张图片
  三个数值依次是顶点序列、顶点坐标(省略z)、纹理坐标,顶点索引是1、2、3和3、4、1,处在同一个平面的两个三角形,两个三角形面“共享”同一张纹理。那么一张纹理是如何精确的贴到两个面上的呢?
  这就要引入采样器的概念。

采样器

  要创建采样器(Sampler),首先需要采样器描述(D3D11_SAMPLER_DESC),这与之前创建缓冲区的步骤类似,都需要DESC描述
示例代码

// 初始化采样器状态
	D3D11_SAMPLER_DESC sampDesc;
	ZeroMemory(&sampDesc, sizeof(sampDesc));
	sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
	sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
	sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
	sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
	sampDesc.MinLOD = 0;
	sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
	HR(m_pd3dDevice->CreateSamplerState(&sampDesc, m_pSamplerState.GetAddressOf()));
D3D11_SAMPLER_DESC成员 描述
D3D11_FILTER Filter 过滤器 它指定被采样的纹理怎样过滤。纹理过滤描述从源数据怎样被读取和联合,使得用于着色器。过滤能够提升画质但是在纹理采样时会有更多的性能开销,因为一些过滤类型是由于读取和结合多个纹理元素值来产生一个在着色器中看到的单一的颜色值。
D3D11_TEXTURE_ADDRESS_MODE AddressU 纹理寻址模式
D3D11_TEXTURE_ADDRESS_MODE AddressV 纹理寻址模式
D3D11_TEXTURE_ADDRESS_MODE AddressW 纹理寻址模式
FLOAT MipLODBias 用于 MIP 的级别细节(level-of-detail,简称 LOD)偏移。该值是一个通过 Direct3D 使用的 MIP 级别机制的偏移量。例如,如果 Direct3D 指定使用第 2 级别的 MIP 并且偏移量为 3,则实际上 MIP 使用的是第 5 级别。
UINT MaxAnisotropy 最大各向异性值,用于各向异性过滤和比较函数。该值范围是 1-16 之间的整数,它不用于点和双线性过滤。
D3D11_COMPARISON_FUNC ComparisonFunc COMPARISON函数的返回值用于判定贴图过滤标识的比较。比较标识在D3D11_COMPARISON_FUNC 中指定,其比较状态的本质是比较两个值之间的等于,大于小于关系。
FLOAT BorderColor[ 4 ] 如果贴图地址模式使用了D3D11_TEXTURE_ADDRESS_BORDER,这个成员就可以指定颜色值(RGBA值必须在0~1)。
FLOAT MinLOD 若mipmap等级低于MinLOD,则使用等级MinLOD。最小允许设为0。
FLOAT MaxLOD 若mipmap等级高于MaxLOD,则使用等级MaxLOD。必须比MinLOD大。

过滤器 D3D11_FILTER

参考这篇博客

由于纹理实际上是由多个离散的颜色值组成的,因此,对于一张256256尺寸的纹理,如果它正好投影在屏幕上256256尺寸的区域内,那么一个像素对应一个纹理值,这是最理想的情形。但是,当这些颜色值与屏幕上的像素不能够一一对应时,如何计算特定像素处的颜色值?在这种情况下,就需要用到纹理过滤。设想如下两种情形:
1. 当照相机与一张纹理不断靠近时,即使该纹理尺寸远小于屏幕,当距离足够近时,该纹理也有可能投影在整个屏幕上。这时,纹理上的一个元素将覆盖很多个屏幕像素。
2. 当照相机不断远离时,纹理在屏幕上的投影会越来越小,当距离足够远时,有可能带个纹理会投影在一个屏幕像素内。
在这两种情况下,如果计算纹理在屏幕投影范围内对应各个像素的值,即需要纹理过滤。
纹理过滤分为两种:一种为放大,即Magnification; 一种为缩小,即Minification。当一个纹理元素覆盖屏幕上多个像素时,使用的过滤为Magnification,对应于上述第一种情形;当多个纹理元素投影在一个屏幕像素内时,使用的过滤为Minification,对应于上述第二个例子。

过滤器类型 描述
D3D11_FILTER_MIN_MAG_MIP_POINT Use point sampling for minification, magnification, and mip-level sampling.
D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR Use point sampling for minification and magnification; use linear interpolation for mip-level sampling.
D3D11_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT Use point sampling for minification; use linear interpolation for magnification; use point sampling for mip-level sampling.
D3D11_FILTER_MIN_POINT_MAG_MIP_LINEAR Use point sampling for minification; use linear interpolation for magnification and mip-level sampling.
D3D11_FILTER_MIN_LINEAR_MAG_MIP_POINT Use linear interpolation for minification; use point sampling for magnification and mip-level sampling.
D3D11_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR Use linear interpolation for minification; use point sampling for magnification; use linear interpolation for mip-level sampling.
D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT Use linear interpolation for minification and magnification; use point sampling for mip-level sampling.
D3D11_FILTER_MIN_MAG_MIP_LINEAR Use linear interpolation for minification, magnification, and mip-level sampling.
D3D11_FILTER_ANISOTROPIC Use anisotropic interpolation for minification, magnification, and mip-level sampling.
D3D11_FILTER_COMPARISON_MIN_MAG_MIP_POINT
D3D11_FILTER_COMPARISON_MIN_MAG_POINT_MIP_LINEAR
D3D11_FILTER_COMPARISON_MIN_POINT_MAG_LINEAR_MIP_POINT
D3D11_FILTER_COMPARISON_MIN_POINT_MAG_MIP_LINEAR
D3D11_FILTER_COMPARISON_MIN_LINEAR_MAG_MIP_POINT
D3D11_FILTER_COMPARISON_MIN_LINEAR_MAG_POINT_MIP_LINEAR
D3D11_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT
D3D11_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR
D3D11_FILTER_COMPARISON_ANISOTROPIC
D3D11_FILTER_MINIMUM_MIN_MAG_MIP_POINT
D3D11_FILTER_MINIMUM_MIN_MAG_POINT_MIP_LINEAR
D3D11_FILTER_MINIMUM_MIN_POINT_MAG_LINEAR_MIP_POINT
D3D11_FILTER_MINIMUM_MIN_POINT_MAG_MIP_LINEAR
D3D11_FILTER_MINIMUM_MIN_LINEAR_MAG_MIP_POINT
D3D11_FILTER_MINIMUM_MIN_LINEAR_MAG_POINT_MIP_LINEAR
D3D11_FILTER_MINIMUM_MIN_MAG_LINEAR_MIP_POINT
D3D11_FILTER_MINIMUM_MIN_MAG_MIP_LINEAR
D3D11_FILTER_MINIMUM_ANISOTROPIC
D3D11_FILTER_MAXIMUM_MIN_MAG_MIP_POINT
D3D11_FILTER_MAXIMUM_MIN_MAG_POINT_MIP_LINEAR
D3D11_FILTER_MAXIMUM_MIN_POINT_MAG_LINEAR_MIP_POINT
D3D11_FILTER_MAXIMUM_MIN_POINT_MAG_MIP_LINEAR
D3D11_FILTER_MAXIMUM_MIN_LINEAR_MAG_MIP_POINT
D3D11_FILTER_MAXIMUM_MIN_LINEAR_MAG_POINT_MIP_LINEAR
D3D11_FILTER_MAXIMUM_MIN_MAG_LINEAR_MIP_POINT
D3D11_FILTER_MAXIMUM_MIN_MAG_MIP_LINEAR
D3D11_FILTER_MAXIMUM_ANISOTROPIC

  其实上面这些都是几种设置组合出来的。
  参考DX11龙书
MAG:magnification 倍增
  纹理贴图元素应该被视为在一个连续图像上的离散颜色采样;不应该被视为矩形区域。那么问题是:当我们指定的纹理坐标(u,v)与任何一个纹理元素点都不对应时会产生什么结果?这一问题会发生在如下情景中:当观察点离场景中的一面墙很近时,墙会被放大,以至于会盖住整个屏幕。如果显示器的分辨率为1024×1024,墙体纹理的分辨率为256×256,那么就会出现倍增(magnification)问题——我们将要用很少的纹理元素来覆盖很多的像素。在本例中,每个纹理元素要覆盖4个像素。当顶点纹理坐标在三角形表面上进行插值时,每个像素都会得到一对唯一的纹理坐标。所以,像素的纹理坐标不会与任何一个纹理元素点对应。 我们可以使用插值方法来估算纹理元素之间的颜色。图形硬件提供了两种插值方法:常量插值和线性插值。线性插值是我们最常用的插值方法。
  下图说明了在1D空间中的插值方法:假设有一个包含256个采样点的1D纹理以及一个插值纹理坐标u = 0.126484375。那该纹理坐标对应的纹理元素为0.126484375×256 = 32.38。当然,这个值位于两个纹理采样点之间,我们必须使用插值来估算它。
DirectX11学习笔记四 纹理入门_第3张图片
  a为点过滤,b为线性过滤。
  2D线性插值也称为双线性插值(bilinear interpolation),如下图所示。给出一对位于4个纹理元素之间的纹理坐标,我们在u方向上进行两次1D线性插值,然后在v方向上进行一次1D线性插值。
DirectX11学习笔记四 纹理入门_第4张图片
  这里有4个纹理元素点cij、ci,j+1,ci+1,j、ci+1,j+1。我们想要估算c点的颜色,它位于这4个纹理元素点之间。在本例中,c位于cij右侧0.75单位、cij下方0.38单位处。我们先在上面的两个颜色之间进行1D线性插值得到cT。然后,在下面的两个颜色之间进行1D线性插值得到cB。最后,在cT和cB之间进行线性插值得到c。
  下图说明了常量插值和线性插值之间的区别。可以看到,常量插值会使图像出现明显的块状。而线性插值较为平滑,但是与真实数据(例如,一幅高分辨率的纹理)相比,通过插值得到的衍生数据仍然不够理想。
DirectX11学习笔记四 纹理入门_第5张图片
  很容易推出来左侧是点过滤,右侧是线性过滤,因为点过滤是离散的,因此颗粒状很明显,而线性过滤是通过线性关系计算的,所以过渡效果较好。
MIN: minification 缩减
  缩减(minification)与倍增的情况恰好相反。在缩减中,较多的纹理元素会被映射为较少的像素。例如,考虑下面的情景:我们将一幅256×256的纹理映射到墙体上。然后把观察点对准墙体,并向后移动观察点,使墙体逐渐变小,直到墙体在屏幕上只占64×64的像素区域为止。现在,我们要把256×256个纹理元素映射为64×64个屏幕像素。在这一情景中,像素的纹理坐标不会与纹理贴图中的任何一个纹理元素对应。常量和线性插值过滤器仍然适用于缩减情况。不过,缩减的处理工作稍多一些。简单的讲,我们要把256×256个纹理元素均匀地缩减为64×64个纹理元素。多级渐近贴图映射(mipmapping)为这一工作提供了一种高效的估值手段,只是要额外占用一些内存。在初始化时(或创建资源时),通过对图像进行降阶采样生成纹理的多个缩略版本来创建多级渐近纹理链(mipmap chain,参见图8.8)。求平均值的工作是根据多级渐近贴图的大小提前计算出来的。在运行时,图形硬件会根据程序员指定的多级渐近贴图参数执行两种不同的操作:
  1.为纹理映射挑选一个与屏幕几何体分辨率最匹配的多级渐近纹理层,根据需要在多级渐近纹理层上使用常量插值或线性插值。这种用于多级渐近纹理的操作称为点过滤(point filtering),因为它与常量插值很像——它只为纹理映射挑选一个最接近的多级渐近纹理层。
  2.为纹理映射挑选两个与屏幕几何体分辨率最匹配的多级渐近纹理层(其中,一个比屏幕几何体分辨率大一些,另一个比屏幕几何体分辨率小一些)。然后,在这两个多级渐近纹理层上使用常量插值或线性插值,分别取出一个纹理颜色。最后,在这两个纹理颜色之间进行插值。这种用于多级渐近纹理的操作称为线性过滤(linear filtering),因为它与线性插值很像——它在两个最匹配的多级渐近纹理层之间进行线性插值。
DirectX11学习笔记四 纹理入门_第6张图片
  通过在多级渐近纹理链中选择最佳纹理层,可以使缩减操作的总开销降至最低。
  那么如何创建多级渐进纹理(mipmapping)?
  多级渐近纹理层即可以由美术师手工创建,也可以由过滤算法生成。其实不用很关心,渲染API都自动处理了。DDS可以直接把多级渐近纹理层存储在文件中。
ANISOTROPIC:anisotropic filter 各向异性过滤
  我们还可以使用各向异性过滤(anisotropic filter)。当多边形的法线向量与摄像机的观察向量夹角过大时(例如,当多边形垂直于观察窗口时),这种过滤可以有效缓解图像的失真问题。只是它的资源占用量较大。不过,当你需要对失真进行校正时,这些消耗是值得的。
DirectX11学习笔记四 纹理入门_第7张图片
  左侧线性过滤,右侧各向异性过滤

D3D11_TEXTURE_ADDRESS_MODE 寻址模式

参考微软文档

D3D11_TEXTURE_ADDRESS_MODE成员 描述
D3D11_TEXTURE_ADDRESS_WRAP 通过在每个整点连接处重复图像来扩展纹理DirectX11学习笔记四 纹理入门_第8张图片
D3D11_TEXTURE_ADDRESS_MIRROR 通过在每个整点连接处镜像图像来扩展纹理DirectX11学习笔记四 纹理入门_第9张图片
D3D11_TEXTURE_ADDRESS_CLAMP 通过将每个不在[0,1]2区间内的(u,v)映射为颜色T(u0,v0)来扩展纹理。其中,(u0,v0)∈[0,1]2,(u0,v0)是与(u,v)距离最近的点DirectX11学习笔记四 纹理入门_第10张图片
D3D11_TEXTURE_ADDRESS_BORDER 通过将每个不在[0,1]2区间内的(u,v)映射为程序员指定的某个颜色来扩展纹理DirectX11学习笔记四 纹理入门_第11张图片
D3D11_TEXTURE_ADDRESS_MIRROR_ONCE 相当于MIRROR和CLAMP的结合,仅[-1,1]的范围内镜像有效,其余范围都会取到-1或者1DirectX11学习笔记四 纹理入门_第12张图片

  那么到目前为止,采样器状态(ID3D11SamplerState)创建好了,下一步是跟渲染管线中的像素着色器绑定。

	m_pd3dImmediateContext->PSSetSamplers(0, 1, m_pSamplerState.GetAddressOf());

  采样器状态和纹理资源在着色器中用如下定义,相当于一个缓冲区资源了。

Texture2D g_Tex : register(t0);
SamplerState g_SamLinear : register(s0);

  Finally,顶点属性中的纹理坐标设置好了,相应的纹理读取和绑定像素着色器了,采样器也创建和绑定像素着色器了,下一步就是在着色器中对其进行操作了。

顶点着色器

// 顶点着色器(3D)
VertexPosHWNormalTex VS_3D(VertexPosNormalTex vIn)
{
    VertexPosHWNormalTex vOut;  //定义传出顶点属性
    matrix viewProj = mul(g_View, g_Proj); //视图投影矩阵
    float4 posW = mul(float4(vIn.PosL, 1.0f), g_World);//计算顶点的世界坐标

    vOut.PosH = mul(posW, viewProj);  //计算顶点MVP变换后的齐次坐标
    vOut.PosW = posW.xyz;  //保存世界坐标
    vOut.NormalW = mul(vIn.NormalL, (float3x3) g_WorldInvTranspose);  //法线变换
    vOut.Tex = vIn.Tex;  // 纹理不需要变换,原样赋值
    return vOut;
}

像素着色器

// 像素着色器(3D)
float4 PS_3D(VertexPosHWNormalTex pIn) : SV_Target
{
    // 标准化法向量
    pIn.NormalW = normalize(pIn.NormalW);

    // 顶点指向眼睛的向量
    float3 toEyeW = normalize(g_EyePosW - pIn.PosW);

    // 初始化为0 
    float4 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 spec = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 A = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 D = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 S = float4(0.0f, 0.0f, 0.0f, 0.0f);
    int i;
	// 强制展开循环以减少指令数
    
	[unroll]
    for (i = 0; i < g_NumPointLight; ++i)
    {
        ComputePointLight(g_Material, g_PointLight[i], pIn.PosW, pIn.NormalW, toEyeW, A, D, S);
        ambient += A;
        diffuse += D;
        spec += S;
    }  // 以上为计算点光源光照模型

    float4 texColor = g_Tex.Sample(g_SamLinear, pIn.Tex);
    float4 litColor = texColor * (ambient + diffuse) + spec;
    litColor.a = texColor.a * g_Material.Diffuse.a;
	
    return litColor;
}

  只需要关注这两行

    float4 texColor = g_Tex.Sample(g_SamLinear, pIn.Tex);
    float4 litColor = texColor * (ambient + diffuse) + spec;
    litColor.a = texColor.a * g_Material.Diffuse.a;

  查询微软文档,关于Texture2D和SamplerState。Sample()方法,传入之前创建的采样器和顶点属性中的纹理坐标(插值后的)。
  计算出该坐标处的纹理颜色值之后,作为像素的材质颜色与光照系数相乘。最后处理下透明度alpha即可。

简易2D纹理动画

  要实现2D动画,可以用顺序存储(vector)动画的每一帧,然后按照顺序逐帧显示。这次的纹理素材不再是DDS了,而是普通的图片格式(.bmp)。命名格式Fire001.bmp,从001到120。
定义

	std::vector<ComPtr<ID3D11ShaderResourceView>> m_pFireAnims; // 火焰纹理集

读取

// 初始化火焰纹理
	WCHAR strFile[40];
	m_pFireAnims.resize(120);
	for (int i = 1; i <= 120; ++i)
	{
		wsprintf(strFile, L"Texture\\FireAnim\\Fire%03d.bmp", i);
		HR(CreateWICTextureFromFile(m_pd3dDevice.Get(), strFile, nullptr, m_pFireAnims[static_cast<size_t>(i) - 1].GetAddressOf()));
	}

  wsprintf是格式化字符串,类似于printf,只不过是将输出保存到了strFile缓冲区中。
  static_cast(i) - 1将i静态转换为size_t无符号整数。
绑定到渲染管线和限制播放帧率

// 用于限制在1秒60帧
		static float totDeltaTime = 0;

		totDeltaTime += dt;
		if (totDeltaTime > 1.0f / 60)
		{
			totDeltaTime = 0;
			m_CurrFrame = (m_CurrFrame + 1) % 120;
			m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pFireAnims[m_CurrFrame].GetAddressOf());
		}		

  其实可以有两种办法,一种是先限帧,然后每帧更新一次动画,第二种是不限帧,然后给动画更新单独加一个计时器。
  上面的代码使用的是第二种,dt表示一帧所用的时间,每帧把所用的时间累加到totDeltaTime中。当totDeltaTime超过1/60秒时,更新动画,然后重置时间计数器(为什么不直接改成0?),很好理解。
重新设置顶点属性和shader
  之前box是3D,又需要光照,所以需要的属性比现在的多一点。而我们现在需要修改它。

// 创建顶点着色器(2D)
	HR(CreateShaderFromFile(L"HLSL\\Basic_VS_2D.cso", L"HLSL\\Basic_VS_2D.hlsl", "VS_2D", "vs_5_0", blob.ReleaseAndGetAddressOf()));
	HR(m_pd3dDevice->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pVertexShader2D.GetAddressOf()));
	// 创建顶点布局(2D)
	HR(m_pd3dDevice->CreateInputLayout(VertexPosTex::inputLayout, ARRAYSIZE(VertexPosTex::inputLayout),
		blob->GetBufferPointer(), blob->GetBufferSize(), m_pVertexLayout2D.GetAddressOf()));

	// 创建像素着色器(2D)
	HR(CreateShaderFromFile(L"HLSL\\Basic_PS_2D.cso", L"HLSL\\Basic_PS_2D.hlsl", "PS_2D", "ps_5_0", blob.ReleaseAndGetAddressOf()));
	HR(m_pd3dDevice->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pPixelShader2D.GetAddressOf()));

  其中,VertexPosTex只包含了顶点的坐标和纹理坐标。

m_pd3dImmediateContext->IASetInputLayout(m_pVertexLayout2D.Get());
		auto meshData = Geometry::Create2DShow();
		ResetMesh(meshData);
		m_pd3dImmediateContext->VSSetShader(m_pVertexShader2D.Get(), nullptr, 0);
		m_pd3dImmediateContext->PSSetShader(m_pPixelShader2D.Get(), nullptr, 0);

  播放2D纹理动画的“画布”是两个三角形拼起来的一个正方形,所以又回到了学习笔记第一节渲染三角形的时候了,只不过纹理替代了颜色。

// 顶点着色器(2D)
VertexPosHTex VS_2D(VertexPosTex vIn)
{
    VertexPosHTex vOut;
    vOut.PosH = float4(vIn.PosL, 1.0f);
    vOut.Tex = vIn.Tex;
    return vOut;
}
// 像素着色器(2D)
float4 PS_2D(VertexPosHTex pIn) : SV_Target
{
    return g_Tex.Sample(g_SamLinear, pIn.Tex);
}

思考

按照惯例

  1. 自己动手将过滤器修改为常量插值法、线性插值法、各向异性过滤,观察立方体盒的效果
  2. 尝试在使用Geometry::MeshData创建的立方体网格数据(不能对其修改)的基础上,让立方体的六个面使用不同的纹理来绘制,可以使用魔方项目里的纹理
  3. 使用教程项目第26章Texture文件夹中的flare.dds和flarealpha.dds,在着色器中通过分量乘法实现
    DirectX11学习笔记四 纹理入门_第13张图片
    然后让纹理在立方体的表面旋转(考虑对纹理坐标的变换),纹理采样器的寻址模式使用BORDER_COLOR,边界色为黑色。效果如下:

    着色器需要提供两个纹理,并在一个顶点着色器、一个像素着色器完成任务。
  4. 如果你阅读过"深入理解与使用2D纹理资源"这篇,那么尝试用一个纹理数组存储所有的火焰纹理,在HLSL中使用Texture2DArray

1.前面列表中都有提到了
2.差点走了弯路,学习了一下StackOverFlow
有两种办法,第一种我们可以将要显示的几个纹理放到同一个dds中,然后修改box每个顶点的纹理坐标,从同一张大纹理中取小纹理,但是题意是不能修改顶点属性。第二种我们可以根据drawIndexed来分别渲染6次,一次绑定一个纹理并渲染一个面。
定义,载入等细节已略。

// 绘制几何模型
	for (int i = 0; i < m_IndexCount; i += 3) 
	{
		if(i==0)
			m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pWoodCrate.GetAddressOf());
		else if(i==6)
			m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pFlare.GetAddressOf());
		else if (i == 12)
			m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pFlareAlpha.GetAddressOf());
		else if (i == 18)
			m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pWoodCrate.GetAddressOf());
		else if (i == 24)
			m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pFlare.GetAddressOf());
		else if (i == 30)
			m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pFlareAlpha.GetAddressOf());
		m_pd3dImmediateContext->DrawIndexed(3, i, 0);
	}

if写的比较繁琐,当然也可以将三个纹理放到一个数组里。
DirectX11学习笔记四 纹理入门_第14张图片
3.需要对纹理坐标进行变换
纹理坐标的变换与二维坐标变换类似,但是在习题中,纹理是按照纹理图像的中心旋转的,如果直接旋转纹理坐标的z轴,那结果会变成纹理按照左上角坐标轴原点旋转,所以就要做些修改。
方法是先把图像移动到坐标轴原点,再旋转,然后再反向移动回去,即可。



static float phi2 = 0.0f;
		phi2 += 0.01f;
		XMMATRIX texMat = XMMatrixTranslation(-0.5f, -0.5f, 0.0f) * XMMatrixRotationZ(phi2) * XMMatrixTranslation(0.5f, 0.5f, 0.0f);
		m_PSConstantBuffer.texMat = XMMatrixTranspose(texMat);

着色器

// 像素着色器(3D)
float4 PS_3D(VertexPosHWNormalTex pIn) : SV_Target
{
	//变换纹理坐标
	pIn.Tex = mul(float4(pIn.Tex,0.0f,1.0f),g_TexMat);

    // 标准化法向量
    pIn.NormalW = normalize(pIn.NormalW);

    // 顶点指向眼睛的向量
    float3 toEyeW = normalize(g_EyePosW - pIn.PosW);

    // 初始化为0 
    float4 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 spec = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 A = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 D = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 S = float4(0.0f, 0.0f, 0.0f, 0.0f);
    int i;
    
	[unroll]
    for (i = 0; i < g_NumPointLight; ++i)
    {
        ComputePointLight(g_Material, g_PointLight[i], pIn.PosW, pIn.NormalW, toEyeW, A, D, S);
        ambient += A;
        diffuse += D;
        spec += S;
    }

    float4 texColor = g_FlareTex.Sample(g_SamLinear, pIn.Tex);
	float4 texAlpha = g_FlareATex.Sample(g_SamLinear, pIn.Tex);
    float4 litColor = texColor * (ambient + diffuse) + spec;
    litColor = litColor * texAlpha;
	
    return litColor;
}

一些变量的声明和传递已省略。
两个图像叠加直接相乘就好了,这个"alpha"图的alpha其实都是1,不能使用,其他颜色是白色的深浅,意味着rgb都相等且变化一致,那就直接乘咯。
DirectX11学习笔记四 纹理入门_第15张图片
4.暂时还没看到那里

你可能感兴趣的:(DirectX)