首页 > 其他分享 >DX12 实现 天空盒

DX12 实现 天空盒

时间:2023-05-05 14:33:40浏览次数:51  
标签:天空 pRenderer 实现 pSkybox register 纹理 DX12 opaquePSODesc D3D12

前言

本篇将展示如何使用DX12实现天空盒

源代码cubemap

创建dds立方体贴图

微软官方提供了一个命令行工具Texassemble,该工具可以将输入的几个图片转换为GPU可识别的DDS贴图,如立方体贴图

语法

texassemble <command>
    [-r] [-flist <filename>]
    [-w width] [-h height] [-f format]
    [-if filter] [-srgb | -srgbi | -srgbo]
    [-sepalpha] [-nowic] [-wrap | -mirror] [-alpha]
    [-o <outputfile>] [-l] [-y] [-dx10] [-tonemap]
    [-nologo]
    [-fl feature-level]
    [-bgcolor]
    [-swizzle rgbamask]
    [-stripmips]
    <file-name(s)>

常用指令:

  • file-name :生成的dds、tga、hdr等格式的纹理名称
  • cube:创建一个cubemap,必须有六张图片,顺序为+x, -x, +y, -y, +z, -z
  • cubearray:创建一个cubmap数组
  • array:创建一个1D/2D的纹理数组,至少有两张图片
  • -w:输出纹理的宽
  • -h:输出纹理的高
  • -f:输出纹理的格式
  • -o:输出的纹理名。默认为".DDS"

例子

image-20230505141906187image-20230505141925491image-20230505142000005

要点

  • 立方体贴图用 6 幅二维 纹理图像构成一个以原点为中心的纹理立方体,每个 2D 纹理是一个立方体的一个面

  • 在D3D中,立方体图是一个含有6个元素构成的纹理数组,从索引0~5依次是+x,-x,+y,-y+z,-z。在使用texassemble创建立方体贴图时,按照对应顺序写入即可
    image-20230505132902880

  • 对立方体贴图进行采样时和2d贴图有些许差别,需要使用3d的方向向量来查找纹理坐标,这是因为纹理坐标(s, t, r)被当作方向向量看待,每个纹素(texel)都表示从原点所看到的纹理立方体上的图像

  • 确定纹理坐标:选择具有最大绝对值的纹理坐标对应的相应的面

    如,对于给定的矢量(−3.2, 5.1, −8.4),就选择-Z 面,而对剩下的两个坐标除以最大绝对值坐标的绝对值, 即 8.4。 那么就将剩下的两个坐标的范围转换到了-1 到 1,然后重映射到[0,1]范围

  • 所谓天空盒其实是对一个超级大的球体进行纹理贴图,并且球体始终以摄像机作为原点,这样才不会露馅

绘制天空盒

生成球体

Model::GeometryGenerator geoGen;
Model::GeometryGenerator::MeshData box = geoGen.CreateBox(1.f, 1.f, 1.f, 5);

// GeometryGenerator'draw
{
    auto pDraw = std::make_unique<Model::Geometrie::Draw>();

    pDraw->baseVertex = (UINT)totalVertex.size();
    pDraw->startIndex = (UINT)totalIndex.size();
    pDraw->indexCount = (UINT)box.Indices32.size();

    m_draws.push_back(std::move(pDraw));

    totalVertex.insert(totalVertex.end(), box.Vertices.begin(), box.Vertices.end());
    totalIndex.insert(totalIndex.end(), box.Indices32.begin(), box.Indices32.end());
}

加载立方体纹理

LoadDDSTextureFromFile()会自动识别cube纹理,并存储它

// sky box
{
    auto pSkybox = std::make_unique<Core::Texture>();

    pSkybox->name = "skybox";
    pSkybox->fileName = Util::UTF8ToWideString("ModelFile/skybox/skybox.dds");
    OutputDebugStringA("ModelFile/skybox2/skybox.dds");

    ThrowIfFailed(LoadDDSTextureFromFile(
        m_device.Get(),
        pSkybox->fileName.c_str(),
        &pSkybox->defaultTexture,
        pSkybox->ddsData,
        pSkybox->subResources
    ));

    Core::CreateD3DResource(m_device.Get(), m_commandList.Get(), pSkybox->subResources, pSkybox->defaultTexture, pSkybox->uploadTexture);

    m_cubemaps[pSkybox->name] = std::move(pSkybox);
}

球体材质

// cubemap's material
{
    auto pMaterial = std::make_unique<Core::Material>();

    int index = m_models.size();
    pMaterial->name = "skybox";
    pMaterial->materialCBIndex = index;
    pMaterial->diffuseSrvHeapIndex = 2 * index;
    pMaterial->numFramesDirty = FrameCount;
    pMaterial->specualrShiness = 64;
    pMaterial->ambientAlbedo = 0.1;

    m_materials.push_back(std::move(pMaterial));
}

draw call项

{
    auto pRenderer = std::make_unique<Core::Renderer>();

    // 非常大的球体
    XMStoreFloat4x4(&pRenderer->world, DirectX::XMMatrixScaling(5000.f, 5000.f, 5000.f));
    pRenderer->objectIndex = i;
    pRenderer->numFramesDirty = FrameCount;
    pRenderer->pGeometry = m_geometries[0].get();
    pRenderer->pMaterail = m_materials[i].get();
    pRenderer->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
    pRenderer->indexCount = m_draws[i]->indexCount;
    pRenderer->startIndex = m_draws[i]->startIndex;
    pRenderer->baseVertex = m_draws[i]->baseVertex;

    m_renderLayers[(int)Core::RenderLayer::Sky].push_back(pRenderer.get());
    m_allRenderers.push_back(std::move(pRenderer));
}

SRV

// create a const buffer view(CBV) descriptor heap
D3D12_DESCRIPTOR_HEAP_DESC cbvSrvHeapDesc = {};
cbvSrvHeapDesc.NumDescriptors = m_diffuseTextures.size() + m_specularTextures.size() + m_cubemaps.size();
cbvSrvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvSrvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(m_device->CreateDescriptorHeap(&cbvSrvHeapDesc, IID_PPV_ARGS(&m_cbvSrvHeap)));
NAME_D3D12_OBJECT(m_cbvSrvHeap);

CD3DX12_CPU_DESCRIPTOR_HANDLE hSRVDescriptor(m_cbvSrvHeap->GetCPUDescriptorHandleForHeapStart());

D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{};
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Texture2D.MostDetailedMip = 0;  // the index of the most detailed mipmap level to use
srvDesc.Texture2D.ResourceMinLODClamp = 0.f;    // min mipmap level that you can access. 0.0f means access all of mipmap levels

// cubemap texture
{
    auto pCurrTexture = m_cubemaps["skybox"]->defaultTexture;

    srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
    srvDesc.TextureCube.MostDetailedMip = 0;
    srvDesc.TextureCube.MipLevels = pCurrTexture->GetDesc().MipLevels;
    srvDesc.Format = pCurrTexture->GetDesc().Format;

    m_device->CreateShaderResourceView(pCurrTexture.Get(), &srvDesc, hSRVDescriptor);

    hSRVDescriptor.Offset(1, m_cbvSrvDescriptorSize);
}

PSO

由于需要先绘制模型,再绘制天空盒(天空盒的深度测试和模型的不同),因此需要两个shader文件和两个PSO

m_shaders["MainVS"] = CompileShader(GetAssetFullPath(L"MainVS.hlsl").c_str(), nullptr, "main", "vs_5_1");
m_shaders["MainPS"] = CompileShader(GetAssetFullPath(L"MainPS.hlsl").c_str(), nullptr, "main", "ps_5_1");

m_shaders["CubemapVS"] = CompileShader(GetAssetFullPath(L"CubemapVS.hlsl").c_str(), nullptr, "main", "vs_5_1");
m_shaders["CubemapPS"] = CompileShader(GetAssetFullPath(L"CubemapPS.hlsl").c_str(), nullptr, "main", "ps_5_1");

// opaque PSO
D3D12_GRAPHICS_PIPELINE_STATE_DESC opaquePSODesc = {};

opaquePSODesc.InputLayout = { m_inputLayout.data(), (uint32_t)m_inputLayout.size() };
opaquePSODesc.pRootSignature = m_rootSignature.Get();
opaquePSODesc.VS =
{
    reinterpret_cast<BYTE*>(m_shaders["MainVS"]->GetBufferPointer()),
    m_shaders["MainVS"]->GetBufferSize()
};
opaquePSODesc.PS =
{
    reinterpret_cast<BYTE*>(m_shaders["MainPS"]->GetBufferPointer()),
    m_shaders["MainPS"]->GetBufferSize()
};
opaquePSODesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
opaquePSODesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
opaquePSODesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
opaquePSODesc.SampleMask = UINT_MAX;  //set the sampling for each pixel(Multiple sampling takes up to 32 samples)
opaquePSODesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
opaquePSODesc.NumRenderTargets = 1;   //The number of render target formats in the RTVFormats member
opaquePSODesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
opaquePSODesc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
opaquePSODesc.SampleDesc.Count = 1;

ThrowIfFailed(m_device->CreateGraphicsPipelineState(&opaquePSODesc, IID_PPV_ARGS(&m_PSOs["opaque"])));

NAME_D3D12_OBJECT(m_PSOs["opaque"]);

// skybox PSO
D3D12_GRAPHICS_PIPELINE_STATE_DESC cubemapPSODesc = opaquePSODesc;
// forbide back culling 
cubemapPSODesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
// let cubemap z = 1 pass z-test, otherwise it'll be failed in z-test because data of zbuffer is 1
cubemapPSODesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL;
cubemapPSODesc.pRootSignature = m_rootSignature.Get();
cubemapPSODesc.VS =
{
    reinterpret_cast<BYTE*>(m_shaders["CubemapVS"]->GetBufferPointer()),
    m_shaders["CubemapVS"]->GetBufferSize()
};
cubemapPSODesc.PS =
{
    reinterpret_cast<BYTE*>(m_shaders["CubemapPS"]->GetBufferPointer()),
    m_shaders["CubemapPS"]->GetBufferSize()
};

ThrowIfFailed(m_device->CreateGraphicsPipelineState(&cubemapPSODesc, IID_PPV_ARGS(&m_PSOs["cubemap"])));

NAME_D3D12_OBJECT(m_PSOs["cubemap"]);

shader

cbuffer ObjectCB : register(b0)
{
    float4x4 world;
}

cbuffer PassCB : register(b1)
{
    float4x4 view;
    float4x4 inverseView;
    float4x4 projection;
    float4x4 inverseProjection;
    float4x4 viewProjection;
    float4x4 inverseViewProjection;
    
    float3 eyeWorldPosition;

    Light lights[MAX_LIGHT_COUNT];
}

cbuffer MaterialCB : register(b2)
{
    int specualrShiness;
    float ambientAlbedo;
}

SamplerState g_SamperPointWrap : register(s0);
SamplerState g_SamperPointClamp : register(s1);
SamplerState g_SamperLinerWrap : register(s2);
SamplerState g_SamperLinerClamp : register(s3);
SamplerState g_SamperAnisotropyWrap : register(s4);
SamplerState g_SamperAnisotropyClamp : register(s5);

Texture2D g_diffuseTexture : register(t0, space0);
Texture2D g_specularTexture : register(t1, space0);
TextureCube g_cubemap : register(t2, space0);

struct VSInput
{
    float3 positionL : POSITION;
    float3 normalL : NORMAL;
    float2 uv : TEXCOORD;
};

struct PSInput
{
    float3 positionL : POSITION;
    float4 positionH : SV_POSITION;
};

// VS
PSInput main(VSInput input)
{
    PSInput output;
    
    // 用于立方体贴图采样的查找向量
    output.positionL = input.positionL;
    
    float4 positionW = mul(float4(output.positionL, 1.f), world);
    
    // 天空球始终以相机为中心
    positionW.xyz += eyeWorldPosition;
    
    //  Set z = w so that z/w = 1,使得立方体纹理始终位于远平面
    output.positionH = mul(positionW, viewProjection).xyww;
    
    return output;
}

// PS
float4 main(PSInput input) : SV_TARGET
{
    return g_cubemap.Sample(g_SamperLinerWrap, input.positionL);
}

输出

image-20230505134807377

reference

Texassemble · microsoft/DirectXTex Wiki)

[Game-Programmer-Study-Notes/README.md at master · QianMo](https://github.com/QianMo/Game-Programmer-Study-Notes/blob/master/Content/《Real-Time Rendering 3rd》读书笔记/README.md)

标签:天空,pRenderer,实现,pSkybox,register,纹理,DX12,opaquePSODesc,D3D12
From: https://www.cnblogs.com/chenglixue/p/17374050.html

相关文章

  • C# Winfom实现Toast功能
    publicclassToast{publicclassToastInfo{publicFormOwner{get;set;}publicstringText{get;set;}="";publicboolIncrease{get;set;}=true;publicintSt......
  • jsp Web超大文件上传和断点续传的实现
    ​ 对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送、接收都是不可取,很容易导致内存问题。所以对于大文件上传,采用切块分段上传,从上传的效率来看,利用多线程并发上传能够达到最大效率。 本文是基于springboot+vue实现的文件上传,本文主要介绍服务端实现文件......
  • WebAPi实现多文件上传,并附带参数
    1、目的及需求需要实现的效果为,通过WebAPI实现多文件上传功能,并且在上传时需要能附带文件说明参数,用于保存文件记录 2、参数说明这里先说明以下需要的文件说明参数类///<summary>///前端文件上传时参数数据///</summary>publicclassDistributionDat......
  • vue-print 实现打印功能
    一、安装1.Vue2npminstallvue-print-nb--saveimportPrintfrom'vue-print-nb'//GlobalinstructionVue.use(Print);//or//Localinstructionimportprintfrom'vue-print-nb'directives:{print}2.Vue3npminstallvue-pri......
  • vue 实现页面跳转
    1、router-link跳转//直接写上跳转的地址<router-linkto="/detail/one"><spanclass="spanfour">link跳转</span></router-link>//添加参数<router-link:to="{path:'/detail/two',query:{id:1,name:......
  • Java Web超大文件上传和断点续传的实现
    ​前言文件上传是一个老生常谈的话题了,在文件相对比较小的情况下,可以直接把文件转化为字节流上传到服务器,但在文件比较大的情况下,用普通的方式进行上传,这可不是一个好的办法,毕竟很少有人会忍受,当文件上传到一半中断后,继续上传却只能重头开始上传,这种让人不爽的体验。那有没有比......
  • java 实现简单的http客户端
    1、废话不多说,代码如下packagecom.linhuaming.test;importjava.io.InputStream;importjava.io.OutputStream;importjava.net.MalformedURLException;importjava.net.Socket;importjava.net.URL;/***http客户端测试*/publicclassHttpClientTest{pub......
  • 【解决办法】三层交换机通过静态路由和接口互联,实现不同VLAN间通信的两种方法
    环境:工具:锐捷EVE模拟器远程工具:SecureCRT系统版本:Windows10问题描述:描述:通过两台三层交换机的互联,完成全网互通。拓扑如下图:解决方法-视频与文字教程:视频教程:文字教程:方法1:通过建立TRUNK链路,通过SVI虚拟接口实现三层交换机的互联,各交换机下的终端的网关指向各自交......
  • Avalonia实现滑动加载
    Avalonia版本V0.10.18privatevoidScrollViewer_OnScrollChanged(object?sender,ScrollChangedEventArgse){varvm=(MainWindowViewModel)DataContext;vart=(ScrollViewer)sender;//Console.WriteLine($"偏移量:{t.O......
  • 基于stm32实现DS18B20温度检测仿真
    一、cubmax设置PA0作为DS18B20数据口 打开串口1,显示温度信息时钟树设置72MHZ。二、程序头文件/*USERCODEBEGINIncludes*/#include"DS18B20.h"#include"stdio.h"/*USERCODEENDIncludes*/串口重定向/*USERCODEBEGIN0*/intfputc(intch,FILE*f......