dx12纹理贴图(代码片段)

chenglixue chenglixue     2023-02-24     694

关键词:

前言

​ 本篇重在展示如何进行纹理贴图,理论还请看这https://www.cnblogs.com/chenglixue/category/2175285.html

指定uv坐标

​ 为了简易性,只展示立方体盒设置的uv坐标。理论篇提到过,求各顶点的uv坐标用两次线性插值即可求得

GeometryGenerator::MeshData GeometryGenerator::CreateBox(float width, float height, float depth, uint32 numSubdivisions)

    MeshData meshData;

    //
	// Create the vertices.
	//

	Vertex v[24];

	float w2 = 0.5f*width;
	float h2 = 0.5f*height;
	float d2 = 0.5f*depth;
    
	// Fill in the front face vertex data.
	v[0] = Vertex(-w2, -h2, -d2, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
	v[1] = Vertex(-w2, +h2, -d2, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
	v[2] = Vertex(+w2, +h2, -d2, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
	v[3] = Vertex(+w2, -h2, -d2, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);

	// Fill in the back face vertex data.
	v[4] = Vertex(-w2, -h2, +d2, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
	v[5] = Vertex(+w2, -h2, +d2, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
	v[6] = Vertex(+w2, +h2, +d2, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
	v[7] = Vertex(-w2, +h2, +d2, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f);

	// Fill in the top face vertex data.
	v[8]  = Vertex(-w2, +h2, -d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
	v[9]  = Vertex(-w2, +h2, +d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
	v[10] = Vertex(+w2, +h2, +d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
	v[11] = Vertex(+w2, +h2, -d2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);

	// Fill in the bottom face vertex data.
	v[12] = Vertex(-w2, -h2, -d2, 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
	v[13] = Vertex(+w2, -h2, -d2, 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
	v[14] = Vertex(+w2, -h2, +d2, 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
	v[15] = Vertex(-w2, -h2, +d2, 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f);

	// Fill in the left face vertex data.
	v[16] = Vertex(-w2, -h2, +d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
	v[17] = Vertex(-w2, +h2, +d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
	v[18] = Vertex(-w2, +h2, -d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
	v[19] = Vertex(-w2, -h2, -d2, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);

	// Fill in the right face vertex data.
	v[20] = Vertex(+w2, -h2, -d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f);
	v[21] = Vertex(+w2, +h2, -d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
	v[22] = Vertex(+w2, +h2, +d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);
	v[23] = Vertex(+w2, -h2, +d2, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f);

	meshData.Vertices.assign(&v[0], &v[24]);
 
	//
	// Create the indices.
	//

	uint32 i[36];

	// Fill in the front face index data
	i[0] = 0; i[1] = 1; i[2] = 2;
	i[3] = 0; i[4] = 2; i[5] = 3;

	// Fill in the back face index data
	i[6] = 4; i[7]  = 5; i[8]  = 6;
	i[9] = 4; i[10] = 6; i[11] = 7;

	// Fill in the top face index data
	i[12] = 8; i[13] =  9; i[14] = 10;
	i[15] = 8; i[16] = 10; i[17] = 11;

	// Fill in the bottom face index data
	i[18] = 12; i[19] = 13; i[20] = 14;
	i[21] = 12; i[22] = 14; i[23] = 15;

	// Fill in the left face index data
	i[24] = 16; i[25] = 17; i[26] = 18;
	i[27] = 16; i[28] = 18; i[29] = 19;

	// Fill in the right face index data
	i[30] = 20; i[31] = 21; i[32] = 22;
	i[33] = 20; i[34] = 22; i[35] = 23;

	meshData.Indices32.assign(&i[0], &i[36]);

    // Put a cap on the number of subdivisions.
    numSubdivisions = std::min<uint32>(numSubdivisions, 6u);

    for(uint32 i = 0; i < numSubdivisions; ++i)
        Subdivide(meshData);

    return meshData;

创建启用纹理

加载DDS文件

​ DDS文件是图形文件格式,它是一个纹理资源,于是我们创建一个纹理结构体用于存储纹理的名字,加载路径,存储资源的默认堆,上传资源的中间层上传堆

struct Texture

	// 自定义的纹理名字
	std::string Name;
	//图像的加载路径
	std::wstring Filename;
	//存储图像数据的纹理资源
	Microsoft::WRL::ComPtr<ID3D12Resource> Resource = nullptr;	//默认堆
	Microsoft::WRL::ComPtr<ID3D12Resource> UploadHeap = nullptr;	//上传堆
;

std::unordered_map<std::string, std::unique_ptr<Texture>> mTextures;


auto woodCrateTex = std::make_unique<Texture>();
woodCrateTex->Name = "woodCrateTex";
woodCrateTex->Filename = L"../../Textures/WoodCrate01.dds";
ThrowIfFailed(
    DirectX::CreateDDSTextureFromFile12(md3dDevice.Get(),
	mCommandList.Get(),
    woodCrateTex->Filename.c_str(),
	woodCrateTex->Resource, 
    woodCrateTex->UploadHeap)
);
 	
mTextures[woodCrateTex->Name] = std::move(woodCrateTex);	

着色器资源视图描述符和着色器资源描述符堆

​ 创建纹理资源后,我们需要创建SRV(着色器资源视图)描述符来描述如何使用它及它的属性(格式,维度,mipmap最小层级,最大层级及数量,对纹理向量的分量排序,平面切片索引),随后将它们全部存储在SRV堆

//创建SRV堆
D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = ;
srvHeapDesc.NumDescriptors = 3;
srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&srvHeapDesc,IID_PPV_ARGS(&mSrvDescriptorHeap)));

//-----------------
//创建SRV描述符,并放入描述符堆
//-----------------

//获得描述符堆的起始句柄
CD3DX12_CPU_DESCRIPTOR_HANDLE hDescriptor(mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());

auto grassTex = mTextures["grassTex"]->Resource;
auto waterTex = mTextures["waterTex"]->Resource;
auto fenceTex = mTextures["fenceTex"]->Resource;

D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = ;
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = grassTex->GetDesc().Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = -1;
md3dDevice->CreateShaderResourceView(grassTex.Get(), &srvDesc, hDescriptor);

// next descriptor
hDescriptor.Offset(1, mCbvSrvDescriptorSize);

srvDesc.Format = waterTex->GetDesc().Format;
md3dDevice->CreateShaderResourceView(waterTex.Get(), &srvDesc, hDescriptor);

// next descriptor
hDescriptor.Offset(1, mCbvSrvDescriptorSize);

srvDesc.Format = fenceTex->GetDesc().Format;
md3dDevice->CreateShaderResourceView(fenceTex.Get(), &srvDesc, hDescriptor);

将纹理绑定至管线

​ 由于大部分场景中的纹理各不相同,若按更新材质常量缓冲区的方式为几何体添加材质属性,导致所有几何体都使用同一组材质,很显然这无法满足需求。因此,在这里我们将使用纹理映射——以texture map取代材质常量缓冲区来获取材质数据

​ 为了实例的简易性,我们只添加漫反射反射率的texture map

struct Material

    //...
    //当前漫反射反射率纹理在SRV堆中的索引
    int DiffuseSrvHeapIndex = -1;
    //漫反射反射率
    DirectX::XMFLOAT4 DiffuseAlbedo =  1.0f, 1.0f, 1.0f, 1.0f ;	//此值根据需求设置,设为1即不发生任何变化

​ 虽然我们把相应纹理的漫反射反射率加入texture map,但大部分时候根据所需,还需更新纹理的漫反射反射率,因此材质常量缓冲区内的漫反射反射率还需保留

在着色器中更新纹理的漫反射反射率

//.hlsl
//纹理对象
Texture2D    gDiffuseMap : register(t0);
//对纹理进行采样,提取其中当前纹理的漫反射反射率,再乘以常量缓冲区根据需求更新后的漫反射反射率,此即为所求的漫反射反射率
float4 diffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC) * gDiffuseAlbedo;

将SRV构成的描述符表绑定至槽

CD3DX12_DESCRIPTOR_RANGE texTable;
texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);

CD3DX12_ROOT_PARAMETER slotRootParameter[4];
slotRootParameter[0].InitAsDescriptorTable(1, &texTable, D3D12_SHADER_VISIBILITY_PIXEL);

void TexWavesApp::DrawRenderItems(ID3D12GraphicsCommandList* cmdList, const std::vector<RenderItem*>& ritems)

    UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
    UINT matCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(MaterialConstants));

    auto objectCB = mCurrFrameResource->ObjectCB->Resource();
    auto matCB = mCurrFrameResource->MaterialCB->Resource();

    // For each render item...
    for(size_t i = 0; i < ritems.size(); ++i)
    
        auto ri = ritems[i];

        cmdList->IASetVertexBuffers(0, 1, &ri->Geo->VertexBufferView());
        cmdList->IASetIndexBuffer(&ri->Geo->IndexBufferView());
        cmdList->IASetPrimitiveTopology(ri->PrimitiveType);

        //获取对应的SRV描述符起始句柄
        CD3DX12_GPU_DESCRIPTOR_HANDLE tex(mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
        tex.Offset(ri->Mat->DiffuseSrvHeapIndex, mCbvSrvDescriptorSize);

        D3D12_GPU_VIRTUAL_ADDRESS objCBAddress = objectCB->GetGPUVirtualAddress() + ri->ObjCBIndex*objCBByteSize;
        D3D12_GPU_VIRTUAL_ADDRESS matCBAddress = matCB->GetGPUVirtualAddress() + ri->Mat->MatCBIndex*matCBByteSize;

        cmdList->SetGraphicsRootDescriptorTable(0, tex);
        cmdList->SetGraphicsRootConstantBufferView(1, objCBAddress);
        cmdList->SetGraphicsRootConstantBufferView(3, matCBAddress);

        cmdList->DrawIndexedInstanced(ri->IndexCount, 1, ri->StartIndexLocation, ri->BaseVertexLocation, 0);
    

采样器

​ 采样器对象用于定义创建纹理资源所用的寻址模式和过滤器,而一个app通常使用多个采样器对象以不同的方式采集纹理

创建采样器描述符

D3D12_SAMPLER_DESC samplerDesc = ;
samplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samplerDesc.MinLOD = 0;
samplerDesc.MaxLOD = D3D12_FLOAT32_MAX;
samplerDesc.MipLODBias = 0.0f;
samplerDesc.MaxAnisotropy = 1;
samplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;

创建并填充采样器描述符堆

D3D12_DESCRIPTOR_HEAP_DESC descHeapSampler = ;
descHeapSampler.NumDescriptors = 1;
descHeapSampler.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
descHeapSampler.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ComPtr<ID3D12DescriptorHeap> mSamplerDescriptorHeap;
ThrowIfFailed(mDevice->CreateDescriptorHeap(&descHeapSampler, __uuidof(ID3D12DescriptorHeap), (void**)&mSamplerDescriptorHeap));

md3dDevice->CreateSampler(&samplerDesc,mSamplerDescriptorHeap->GetCPUDescriptorHandleForHeapStart());

将采样器描述符绑定至管线

​ 因为采样器会被着色器使用,因此我们需要将采样器描述符绑定根签名,将其提交至管线

CD3DX12_DESCRIPTOR_RANGE descRange[3];
descRange[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);
descRange[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0);	//指定此槽的类型是包含采样器描述符的描述符表
descRange[2].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);

CD3DX12_ROOT_PARAMETER rootParameters[3];
rootParameters[0].InitAsDescriptorTable(1, &descRange[0], D3D12_SHADER_VISIBILITY_PIXEL);
rootParameters[1].InitAsDescriptorTable(1, &descRange[1], D3D12_SHADER_VISIBILITY_PIXEL);
rootParameters[2].InitAsDescriptorTable(1, &descRange[2], D3D12_SHADER_VISIBILITY_ALL);

CD3DX12_ROOT_SIGNATURE_DESC descRootSignature;
descRootSignature.Init(3, rootParameters, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

//绑定采样器描述符至管线
commandList->SetGraphicsRootDescriptorTable(1,samplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart());

静态采样器

​ 一般而言,app通常不会使用很多采样器,因此D3D提供一种方式来定义采样器数组,使得在不创建采样器堆的情况下也能对采样器进行配置

创建静态采样器描述符

创建静态采样器描述符数组

//一个数组,用于存储六种不同的静态采样器描述符
std::array<const CD3DX12_STATIC_SAMPLER_DESC, 6> GetStaticSamplers();

std::array<const CD3DX12_STATIC_SAMPLER_DESC, 6> TexWavesApp::GetStaticSamplers()

	//app一般只会用到这些描述符的一部分  

	const CD3DX12_STATIC_SAMPLER_DESC pointWrap(
		0, // 着色器寄存器
		D3D12_FILTER_MIN_MAG_MIP_POINT, // 采样纹理的方式
		D3D12_TEXTURE_ADDRESS_MODE_WRAP,  // U轴上的寻址模式
		D3D12_TEXTURE_ADDRESS_MODE_WRAP,  // V轴上的寻址模式
		D3D12_TEXTURE_ADDRESS_MODE_WRAP); // W轴上的寻址模式

	const CD3DX12_STATIC_SAMPLER_DESC pointClamp(
		1, // shaderRegister
		D3D12_FILTER_MIN_MAG_MIP_POINT, // filter
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressU
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressV
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP); // addressW

	const CD3DX12_STATIC_SAMPLER_DESC linearWrap(
		2, // shaderRegister
		D3D12_FILTER_MIN_MAG_MIP_LINEAR, // filter
		D3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressU
		D3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressV
		D3D12_TEXTURE_ADDRESS_MODE_WRAP); // addressW

	const CD3DX12_STATIC_SAMPLER_DESC linearClamp(
		3, // shaderRegister
		D3D12_FILTER_MIN_MAG_MIP_LINEAR, // filter
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressU
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressV
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP); // addressW

	const CD3DX12_STATIC_SAMPLER_DESC anisotropicWrap(
		4, // shaderRegister
		D3D12_FILTER_ANISOTROPIC, // filter
		D3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressU
		D3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressV
		D3D12_TEXTURE_ADDRESS_MODE_WRAP,  // addressW
		0.0f,                             // mipmap层级的offset
		8);                               // 各向异性过滤的最大值

	const CD3DX12_STATIC_SAMPLER_DESC anisotropicClamp(
		5, // shaderRegister
		D3D12_FILTER_ANISOTROPIC, // filter
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressU
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressV
		D3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // addressW
		0.0f,                              // mipLODBias
		8);                                // maxAnisotropy

	return  pointWrap, pointClamp, linearWrap, linearClamp, anisotropicWrap, anisotropicClamp ;

为这些静态采样器描述符绑定根签名

void TexWavesApp::BuildRootSignature()

	CD3DX12_DESCRIPTOR_RANGE texTable;
	texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);

    CD3DX12_ROOT_PARAMETER slotRootParameter[4];

	// 性能提示: 从最频繁的到最不频繁的进行排序
	slotRootParameter[0].InitAsDescriptorTable(1, &texTable, D3D12_SHADER_VISIBILITY_PIXEL);
    slotRootParameter[1].InitAsConstantBufferView(0);
    slotRootParameter[2].InitAsConstantBufferView(1);
    slotRootParameter[3].InitAsConstantBufferView(2);

    //创建静态采样器描述符数组
	auto staticSamplers = GetStaticSamplers();

	CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(4, slotRootParameter,
		(UINT)staticSamplers.size(), staticSamplers.data(),
		D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

    ComPtr<ID3DBlob> serializedRootSig = nullptr;
    ComPtr<ID3DBlob> errorBlob = nullptr;
    HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,
        serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());

    if(errorBlob != nullptr)
    
        ::OutputDebugStringA((char*)errorBlob->GetBufferPointer());
    
    ThrowIfFailed(hr);

    ThrowIfFailed(md3dDevice->CreateRootSignature(
		0,
        serializedRootSig->GetBufferPointer(),
        serializedRootSig->GetBufferSize(),
        IID_PPV_ARGS(mRootSignature.GetAddressOf())));

在着色器中对纹理采样

定义纹理对象和采样器对象

//纹理对象
Texture2D    gDiffuseMap : register(t0);

//采样器对象
//对应采样器描述符数组
SamplerState gsamPointWrap        : register(s0);
SamplerState gsamPointClamp       : register(s1);
SamplerState gsamLinearWrap       : register(s2);
SamplerState gsamLinearClamp      : register(s3);
SamplerState gsamAnisotropicWrap  : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);

采样

struct VertexOut

	float4 PosH    : SV_POSITION;
    float3 PosW    : POSITION;
    float3 NormalW : NORMAL;
	float2 TexC    : TEXCOORD;
;

float4 PS(VertexOut pin) : SV_Target

    //对纹理中的像素进行采样
    float4 diffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC) * gDiffuseAlbedo;
    //...

纹理变换

​ 还有两个常量缓冲区中的两个变量我们还未曾提其作用:TexTransform和MatTransform

cbuffer cbPerObject : register(b0)

    //...
	float4x4 gTexTransform;
;

cbuffer cbMaterial : register(b2)

	//...
	float4x4 gMatTransform;
;

struct MaterialConstants

    DirectX::XMFLOAT4X4 MatTransform = MathHelper::Identity4x4();


struct ObjectConstants

    DirectX::XMFLOAT4X4 TexTransform = MathHelper::Identity4x4();

​ 这两个变量用于在顶点着色器中对输入的uv坐标进行变换,可以对uv坐标进行缩放、平移、旋转,实现许多效果。如:

  • 让纹理拉伸放大。若当前uv范围为[0,1],放大四倍则为[0,4],若指定为重复寻址模式,则会重复贴图4x4次
  • 通过时间函数平移纹理坐标。如白云飘动
  • 纹理旋转

​ 然后,运用两个变换矩阵可以实现uv动画:一个关于材质的纹理变换(比如水),一个是关于物体属性的纹理变换

VertexOut VS(VertexIn vin)

	VertexOut vout = (VertexOut)0.0f;
	
    // Transform to world space.
    float4 posW = mul(float4(vin.PosL, 1.0f), gWorld);
    vout.PosW = posW.xyz;

    // Assumes nonuniform scaling; otherwise, need to use inverse-transpose of world matrix.
    vout.NormalW = mul(vin.NormalL, (float3x3)gWorld);

    // Transform to homogeneous clip space.
    vout.PosH = mul(posW, gViewProj);
	
	// 此处运用纹理变换矩阵
	float4 texC = mul(float4(vin.TexC, 0.0f, 1.0f), gTexTransform);	//z值不影响uv
	vout.TexC = mul(texC, gMatTransform).xy;
	
    return vout;

HLSL新增内容

cbuffer cbPerObject : register(b0)

    //...
	float4x4 gTexTransform;	//UV缩放矩阵,因为每个贴图的分辨率不同,所以需要进行拉伸
;

struct VertexIn

	//...
	float2 TexC    : TEXCOORD;
;

struct VertexOut

	//...
	float2 TexC    : TEXCOORD;
;

VertexOut VS(VertexIn vin)

    //...
	// Output vertex attributes for interpolation across triangle.
	float4 texC = mul(float4(vin.TexC, 0.0f, 1.0f), gTexTransform);
	vout.TexC = mul(texC, gMatTransform).xy;


float4 PS(VertexOut pin) : SV_Target

    float4 diffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC) * gDiffuseAlbedo;
    //...

示例

​ 在此实例,我们将向陆地和河流添加纹理。但存在两个问题:

  1. 给曲面陆地分配纹理。由于陆地是个大的曲面,简单地沿其形状进行纹理拉伸,会导致每个三角形分配到极少的纹素,从而产生失真
  2. 如何让水流纹理沿波浪动起来

​ 解决之道是:

  1. 向陆地网格重复铺设草地纹理,这样就可以实现高分辨率
  2. 时间函数

生成uv坐标

​ 下图左侧是一个位于xz平面的m x n栅格,而右侧是栅格的归一化的uv空间,则uv空间中第i行、第j列的顶点坐标为:\\(u_ij = j · \\Delta_u, v_ij = i · \\Delta_v\\),而\\(\\Delta_u = \\frac1n - 1, \\Delta_v = \\frac1m - 1\\)

因此,生成栅格纹理坐标如下

GeometryGenerator::MeshData GeometryGenerator::CreateGrid(float width, float depth, uint32 m, uint32 n)

    MeshData meshData;

	uint32 vertexCount = m*n;
	uint32 faceCount   = (m-1)*(n-1)*2;

	//
	// Create the vertices.
	//

	float halfWidth = 0.5f*width;
	float halfDepth = 0.5f*depth;

	float dx = width / (n-1);
	float dz = depth / (m-1);

	float du = 1.0f / (n-1);
	float dv = 1.0f / (m-1);

	meshData.Vertices.resize(vertexCount);
	for(uint32 i = 0; i < m; ++i)
	
		float z = halfDepth - i*dz;
		for(uint32 j = 0; j < n; ++j)
		
			float x = -halfWidth + j*dx;

			meshData.Vertices[i*n+j].Position = XMFLOAT3(x, 0.0f, z);
			meshData.Vertices[i*n+j].Normal   = XMFLOAT3(0.0f, 1.0f, 0.0f);
			meshData.Vertices[i*n+j].TangentU = XMFLOAT3(1.0f, 0.0f, 0.0f);

			// 为了后续铺设纹理,根据栅格拉伸纹理来对应后续铺设的纹理坐标
			meshData.Vertices[i*n+j].TexC.x = j*du;
			meshData.Vertices[i*n+j].TexC.y = i*dv;
		
	
 
    //
	// Create the indices.
	//

	meshData.Indices32.resize(faceCount*3); // 3 indices per face

	// Iterate over each quad and compute indices.
	uint32 k = 0;
	for(uint32 i = 0; i < m-1; ++i)
	
		for(uint32 j = 0; j < n-1; ++j)
		
			meshData.Indices32[k]   = i*n+j;
			meshData.Indices32[k+1] = i*n+j+1;
			meshData.Indices32[k+2] = (i+1)*n+j;

			meshData.Indices32[k+3] = (i+1)*n+j;
			meshData.Indices32[k+4] = i*n+j+1;
			meshData.Indices32[k+5] = (i+1)*n+j+1;

			k += 6; // next quad
		
	

    return meshData;

铺设纹理

先把栅格纹理拉伸放大,再以重复寻址模式来多次铺设纹理

void TexWavesApp::BuildRenderItems()

    //...
    auto gridRitem = std::make_unique<RenderItem>();
    gridRitem->World = MathHelper::Identity4x4();
	XMStoreFloat4x4(&gridRitem->TexTransform, XMMatrixScaling(5.0f, 5.0f, 1.0f));	//让uv放大5倍,重复铺设5x5次
    //...

纹理动画

​ 为了让水动起来,需要根据时间函数再纹理平面内平移uv坐标,并以重复寻址模式进行贴图,如此才不会暴露。值得注意的是每帧的位移量要小一点,如此动画才会丝滑

void TexWavesApp::AnimateMaterials(const GameTimer& gt)

	auto waterMat = mMaterials["water"].get();

	float& tu = waterMat->MatTransform(3, 0);
	float& tv = waterMat->MatTransform(3, 1);

	tu += 0.1f * gt.DeltaTime();
	tv += 0.02f * gt.DeltaTime();

	if(tu >= 1.0f)
		tu -= 1.0f;

	if(tv >= 1.0f)
		tv -= 1.0f;

	waterMat->MatTransform(3, 0) = tu;
	waterMat->MatTransform(3, 1) = tv;

	// 材质属性发生变化,需要更新常量缓冲区
	waterMat->NumFramesDirty = gNumFrameResources;

reference

Directx12 3D 游戏开发实战

一步步学metal图形引擎2-《纹理贴图》(代码片段)

教程2纹理贴图教程源码下载地址:https://github.com/jiangxh1992/MetalTutorialDemosCSDN完整版专栏:https://blog.csdn.net/cordova/category_9734156.html一、关键词UV坐标系MTLTextureMTKTextureLoader贴图加载Metal采样对象samplerFiltering纹理滤波MipmapsAddres... 查看详情

opengl进阶02.为三角形添加纹理贴图(代码片段)

这篇文章基于上一篇的基础上,为三角形添加纹理贴图。首先我们要解码bmp文件,然后创建纹理对象,再将bmp数据转化成2D贴图,贴到三角形上。在Utils.h中添加所需接口:#pragmaonce#include"ggl.h"unsignedchar*Loa... 查看详情

opengl——opencv读取图片进行纹理贴图(代码片段)

使用OpenCV读取图片代码如下img=imread(m_fileName);if(img.empty())fprintf(stderr,"Cannotloadimage%s",m_fileName);return-1;//设置长宽intwidth=img.cols;intheight=img.rows;intchannel=img.channels();printf("depth%d 查看详情

opengl入门05.纹理贴图(代码片段)

在OpenGL中,我们渲染出来的画面是一张图片,纹理贴图由像素组成,每个像素又包含4个通道的数据,即RGBA,我们能看到RGB的颜色。这篇文章将介绍如何加载图片,解码图片,并把它贴到几何形体上。... 查看详情

osg学习(五十三)绘制纹理贴图texture示例(代码片段)

效果图:代码://osg纹理贴图osg::ref_ptr<osg::Geometry>geom=newosg::Geometry;osg::ref_ptr<osg::Geode>geode=newosg::Geode;geode->addDrawable(geom.get());osg::ref_ptr<osg::Ve 查看详情

✠opengl-8-阴影(代码片段)

...置“绘制”物体阴影贴图(中间步骤)——将Z缓冲区复制到纹理阴影贴图(第2轮)——渲染带阴影的场景齐次纹理坐标——vec4类型纹理坐标渲染的像素和阴影纹理中的值的深度比较背面剔除与阴影的区别阴影贴图示例及分析探讨第1... 查看详情

unityshaders学习笔记之通过修改uv坐标实现纹理贴图的滚动(代码片段)

一、简介 纹理贴图可以使我们的着色器更有生命力,而且可以快速地实现非常逼真的效果。然而略显遗憾的是,你需要小心翼翼地控制用于着色器地纹理贴图的数目,因为如果添加的纹理图片过多,会非常影响... 查看详情

光照镜面光贴图示例(代码片段)

...我们更高一层的控制。step1.对镜面光贴图使用一个不同的纹理单元(见纹理),在渲染之前先把它绑定到合适的纹理单元上:lightingShader.setIn 查看详情

纹理贴图原理与实践图形学基础(代码片段)

纹理贴图是20世纪90年代CG的主要创新之一。它允许我们在不添加大量几何基元(线、顶点、面)的情况下添加大量表面细节。想一想Caroline的loadedDemo的所有纹理映射是多么有趣:推荐:使用NSDT场景编辑器快速搭建... 查看详情

opengl进阶02.为三角形添加纹理贴图(代码片段)

这篇文章基于上一篇的基础上,为三角形添加纹理贴图。首先我们要解码bmp文件,然后创建纹理对象,再将bmp数据转化成2D贴图,贴到三角形上。在Utils.h中添加所需接口:#pragmaonce#include"ggl.h"unsignedchar*Loa... 查看详情

cocos2dx内存优化(代码片段)

纹理消耗了大量内存在大部分情况下,是纹理(textures)消耗了游戏程序大量的内存。因此,纹理是我们首要考虑优化的对象纹理加载cocos2d里面纹理加载分为两个阶段:从图片文件中创建一个Image对象;以这个创建好的Image对象来... 查看详情

Unity shader - 如何根据纹理贴图生成法线贴图

】Unityshader-如何根据纹理贴图生成法线贴图【英文标题】:Unityshader-Howtogeneratenormalmapbasedontexturemap【发布时间】:2021-12-0216:02:33【问题描述】:我目前被困在我正在编写的着色器中。我正在尝试创建一个雨水着色器。我设置了3... 查看详情

应用纹理贴图

为了在OpenGL ES中启用纹理贴图功能,可以在Renderer实现类的onSurfaceCreated(GL10 gl , EGLConfig config)方法中启动纹理贴图,例如如下代码://启用2D纹理贴图 gl.glEnable(GL10.GL_TEXTURE_2D);接下来就需要准备一张图片来作为... 查看详情

webgl学习之法线贴图(代码片段)

实际效果请看demo:纹理贴图为了增加额外细节,提升真实感,我们使用了漫反射贴图和高光贴图,它们都是向三角形进行附加纹理。但是从光的视角来看是表面法线向量使表面被视为平坦光滑的表面。以光照算法的视角考虑的... 查看详情

关于多站点全景纹理贴图问题

前言对于多站点全景纹理贴图,主要是怎么处理遮挡、以及站点与站点过度问题,本文设计了一下方法进行多站点全景的纹理贴图。  图1方法步骤代码(略):效果:  查看详情

✠opengl-10-增强表面细节(代码片段)

目录凹凸贴图法线贴图切线空间——TBN矩阵纹理加法线贴图高度贴图一个完整示例补充说明我们将探讨几种与实现凹凸表面相关的方法,通过使用光照效果,即使在实际对象模型表面平滑的情况下,也能使对象看起来... 查看详情

directx11withwindowssdk--25法线贴图(代码片段)

前言在很早之前的纹理映射中,纹理存放的元素是像素的颜色,通过纹理坐标映射到目标像素以获取其颜色。但是我们的法向量依然只是定义在顶点上,对于三角形面内一点的法向量,也只是通过比较简单的插值法计算出相应的... 查看详情

光照镜面光贴图示例(代码片段)

...我们更高一层的控制。step1.对镜面光贴图使用一个不同的纹理单元(见纹理),在渲染之前先把它绑定到合适的纹理单元上:lightingShader.setInt("material.specular",1);...glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D,sp... 查看详情