首页 > 其他分享 >DX 几何着色器

DX 几何着色器

时间:2023-02-28 21:57:20浏览次数:29  
标签:std 1.0 DX 几何 indices 顶点 geo 着色器

前言

​ 本篇为个人学习笔记,主要展示什么是几何着色器,及为什么需要它,如何使用几何着色器,以及用公告牌实现森林

什么是几何着色器?

​ 几何着色器是顶点和片段着色器间一个可选的着色器,位于曲面细分和光栅化阶段之间,随着 2006 年底 DirectX 10 的发布被加入到硬件加速图形管线中.几何着色器作为 Shader Model 4.0 的一部分,不能在早期着色模型(<= SM 3.0)中使用

​ 几何着色器以完整的图元(任意类型的图元)作为顶点的数组作为输入,如三角形、线段、点,输出对应的图元。与顶点着色器不同,几何着色器可以创建/销毁图元

​ 几何着色器可以改变输入的图元拓扑结构,但只能输出点、折线和三角形条

​ 几何着色器输出的图元由顶点列表定义,经过几何着色器阶段的处理后,便得到位于齐次裁剪空间中的一系列图元

image-20230119182402532

为什么需要几何着色器?

​ 由于几何着色器可以创建/销毁图元,因此根据该特性可以实现许多效果,如将输入的图元扩展为一个或更多其他类型的图元

几何着色器的编写

​ 几何着色器的编写与顶点着色器、片段着色器类似,有几个重点:

  1. 必须先指定几何着色器单次调用输出的顶点数量的最大值。但出于性能考虑,该值需尽可能小.一般来说标量数量1-20最佳,在27-40会下降至峰值性能的50%,标量个数等于顶点数量的最大值*顶点结构体中标量个数的乘积,如一个顶点结构体定义"float3 pos : POSITION"和"float2 tex : TEXCOORD",那么该结构体包含五个标量

  2. 几何着色器的输入顶点类型为顶点着色器输出的顶点类型,该输入参数必须是一个包含特定图元的顶点数组——点为一个顶点,线条为两个顶点,三角形为三个顶点,线及其邻接图元为4个顶点,三角形及其邻接图元为6个顶点,且输入参数必须以图元类型作为前缀,前缀类型如下:

    1. point:输入图元为点
    2. line:输入图元为线列表/线条带
    3. triangle:输入图元为三角形列表/三角形带
    4. lineadj:输入图元为线列表及其邻接图元/线条带及其邻接图元
    5. triangleadj:输入图元为三角形列表及其邻接图元/三角形带及其邻接图元
  3. 几何着色器不会区分输入的图元是列表(list)结构还是带状(strip)结构。如,若绘制的图元为三角形带,几何着色器仍把三角形带看作多个三角形并对其拆分单独进行处理——三角形三个顶点.但是绘制strip结构会产生额外开销,因为几何着色器会多次处理共用的顶点(毕竟这里没有索引)

  4. 输出是一种流(stream)类型,且输入必须标有inout修饰符。stream是模板类型,存有一系列顶点,它们定义了几何着色器输出的几何图形,这些顶点会构成图元

    • stream类型有如下三种

      1. PointStream<OutputVertexType> :一系列顶点定义的点列表
      2. LineStream<OutputVertexType>:一系列顶点定义的线条带
      3. TriangleStream<OutputVertexType>:一系列顶点定义的三角形带
    • 对于线条和三角形来说,几何着色器输出的对应图元默认为线条带和三角形带,若想输出线条列表和三角形列表需借助函数RestartStrip()

      void StreamOutputObject<OutputVertexType>::RestartStrip();
      
    • 向流列表中添加顶点:Append()

      void StreamOutputObject<OutputVertexType>::Append(OutputVertexType v);
      

以下为它的模板:

[maxvertexcount(N)]	//单次调用所输出的顶点数量的最大值
void ShaderName (PrimitiveType InputVertexType InputName[NumElements], inout StreamOutputObject<OutputVertexType>OutputName)
{
	// Geometry shader body…
}

​ 来看一些示例:

  • GS最多输出4个顶点,输入图元为一根线条,输出为一个三角形带

    [maxvertexcount(4)]
    void GS(line VertexOut gin[2], inout TriangleStream<GeoOut> triStream)
    {
        //...
    }
    
  • GS最多输出32个顶点,输入图元为一个三角形,输出为一个三角形带

    [maxvertexcount(32)]
    void GS(triangle VertexOut gin[3], inout TriangleStream<GeoOut> triStream)
    {
        //...
    }
    
  • GS最多输出4个顶点,输入图元为一个点,输出为一个三角形带

    [maxvertexcount(4)]
    void GS(point VertexOut gin[1], inout TriangleStream<GeoOut> triStream)
    {
        //...
    }
    
  • Append()和RestartStrip()的使用方法,该实例将输入的三角形细分,并输出细分后的4个三角形
    image-20230227203916770

    struct VertexOut
    {
        float3 PosL : POSITION;
        float3 NormalL : NORMAL;
    	float2 Tex : TEXCOORD;
    }
    
    struct GeoOut
    {
        float4 PosH : SV_POSITION;
    	float3 PosW : POSITION;
    	float3 NormalW : NORMAL;
    	float2 Tex : TEXCOORD;
        float FogLerp : FOG;
    }
    
    //细分
    void Subdivide(VertexOut inVerts[3], out VertexOut  outVerts[6])
    {
        // 		  1
    	// 		  *
    	// 	     / \
    	// 	    /   \
    	//     m0*–—*m1
    	//    / \    / \
    	//   /   \  /   \
    	//  *–-----*--–--—*
    	//  0      m2      2
        VertexOut m[3];
        
        //计算三角形每条边的中点
    	m[0].PosL = 0.5f*(inVerts[0].PosL+inVerts[1].PosL);
    	m[1].PosL = 0.5f*(inVerts[1].PosL+inVerts[2].PosL);
    	m[2].PosL = 0.5f*(inVerts[2].PosL+inVerts[0].PosL);
    
        //规范化
        m[0].PosL = normalize(m[0].PosL);
    	m[1].PosL = normalize(m[1].PosL);
    	m[2].PosL = normalize(m[2].PosL);
        
        //求法线
        m[0].NormalL = m[0].PosL;
    	m[1].NormalL = m[1].PosL;
    	m[2].NormalL = m[2].PosL;
        
        //纹理坐标插值
        m[0].Tex = 0.5f*(inVerts[0].Tex+inVerts[1].Tex);
    	m[1].Tex = 0.5f*(inVerts[1].Tex+inVerts[2].Tex);
    	m[2].Tex = 0.5f*(inVerts[2].Tex+inVerts[0].Tex);
    
    	outVerts[0] = inVerts[0];	
    	outVerts[1] = m[0];
    	outVerts[2] = m[2];
    	outVerts[3] = m[1];
    	outVerts[4] = inVerts[2];
    	outVerts[5] = inVerts[1];
    }
    
    void OutputSubdivision(VertexOut v[6], inout TriangleStream<GeoOut> triStream)
    {
        GeoOut gout[6];
        
        [unroll]
    	for(int i = 0; i < 6; ++i)
        {
            //顶点变换至世界空间
            gout[i].PosW = mul(float4(v[i].PosL, 1.0f), gWorld).xyz;
    		gout[i].NormalW = mul(v[i].NormalL, (float3x3)gWorldInvTranspose);
            
            //顶点变换至齐次裁剪空间
    		gout[i].PosH = mul(float4(v[i].PosL, 1.0f), gWorldViewProj);
    		gout[i].Tex = v[i].Tex;
            
            //以两个三角形带来绘制细分的三角形
            //三角形带1 : 底部的三个三角形
            //三角形带2 : 顶部的一个三角形
            [unroll]
        for(int j = 0; j < 5; ++j)
        {
        	triStream.Append(gout[j]);
        }
        triStream.RestartStrip();
            
        triStream.Append(gout[1]);
        triStream.Append(gout[5]);
        triStream.Append(gout[3]);
    }
        
    [maxvertexcount(8)]
    void GS(triangle VertexOut gin[3], inout TriangleStream<GeoOut>)
    {
        VertexOut v[6];
    	Subdivide(gin, v);
    	OutputSubdivision(v, triStream);
    }
    

几何着色器的编译,绑定至流水线

​ 几何着色器的编译方式和绑定至流水线的方式与VS、PS相同。示例如下:

mShaders["treeSpritePS"] = d3dUtil::CompileShader(L"Shaders\\TreeSprite.hlsl", alphaTestDefines, "PS", "ps_5_0");
D3D12_GRAPHICS_PIPELINE_STATE_DESC treeSpritePsoDesc = opaquePsoDesc;
treeSpritePsoDesc.PS =
{
    reinterpret_cast<BYTE*>(mShaders["treeSpritePS"]->GetBufferPointer()),
    mShaders["treeSpritePS"]->GetBufferSize()
};

用公告板实现森林

  • 何为公告板(Billboarding)?

    公告板是一种用3D物体图片的四边形来替代3D物体的渲染,他会根据观察方向来确定多边形面朝方向,随着观察角度的变化,公告板多边形的方向也会根据需求随之改变

    如下图,所有公告牌都面对着摄像机
    image-20230227222928031

  • 公告板有什么用处?

    与 alpha 纹理 和 动画相结合,可以用公告板技术表示许多不具有平滑实体表面的现象,如烟,火,雾,爆炸效果,云朵等

求四边形公告板的顶点坐标

​ 若给定 世界空间中四边形公告板的中心位置为\(C(C_x, C_y, C_z)\),摄像机位置\(E(E_x, E_y, E_z)\),公告牌的局部坐标系和世界空间的关系,公告牌在世界空间中的大小,即可求得公告牌的四个顶点坐标
\(w = \frac{(E_x - C_x, 0, E_z - C_z)}{\|E_x - C_x, 0, E_z - C_z \|} \\ v = (0,1,0) \\ u = v \times w\)

v[0] = float4(gin[0].CenterW + halfWidth*right - halfHeight*up, 1.0f);
v[1] = float4(gin[0].CenterW + halfWidth*right + halfHeight*up, 1.0f);
v[2] = float4(gin[0].CenterW - halfWidth*right - halfHeight*up, 1.0f);
v[3] = float4(gin[0].CenterW - halfWidth*right + halfHeight*up, 1.0f);

image-20230227223709931

HLSL示例

​ 由于需要确定公告板的中心点,因此我们需要点图元
将PSO的PrimitiveTopologyType设为D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT,ID3D12GraphicsCommandList::IASetPrimitiveTopology设为D3D_PRIMITIVE_TOPOLOGY_POINTLIST。而在几何着色器中,我们需要把这些中心点扩展为公告板的四边形,还需要计算公告板的世界矩阵

  • 顶点结构体

    在顶点结构体中存储公告板的中心点的位置,以及公告板的宽高,如此即可表示其大小
    image-20230228141455637

    struct TreeSpriteVertex
    {
        XMFLOAT3 Pos;
        XMFLOAT2 Size;
    };
    
    mTreeSpriteInputLayout =
    {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "SIZE", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
    };
    
  • HLSL实现:以下将展示VS、PS、GS三种着色器是如何搭配使用的

    该例中GS用于把一个点扩展为一个四边形,并让其和y轴对齐来正对相机

    #ifndef NUM_DIR_LIGHTS
        #define NUM_DIR_LIGHTS 3
    #endif
    
    #ifndef NUM_POINT_LIGHTS
        #define NUM_POINT_LIGHTS 0
    #endif
    
    #ifndef NUM_SPOT_LIGHTS
        #define NUM_SPOT_LIGHTS 0
    #endif
    
    // 包含光照所需的结构体和函数
    #include "LightingUtil.hlsl"
    
    //纹理数组
    Texture2DArray gTreeMapArray : 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);
    
    //与物体相关的常量数据
    cbuffer cbPerObject : register(b0)
    {
        float4x4 gWorld;
    	float4x4 gTexTransform;
    };
    
    // 绘制所用的杂项常量数据,每帧更新
    cbuffer cbPass : register(b1)
    {
        float4x4 gView;
        float4x4 gInvView;
        float4x4 gProj;
        float4x4 gInvProj;
        float4x4 gViewProj;
        float4x4 gInvViewProj;
        float3 gEyePosW;
        float cbPerObjectPad1;
        float2 gRenderTargetSize;
        float2 gInvRenderTargetSize;
        float gNearZ;
        float gFarZ;
        float gTotalTime;
        float gDeltaTime;
        float4 gAmbientLight;
    
    	float4 gFogColor;
    	float gFogStart;
    	float gFogRange;
    	float2 cbPerObjectPad2;
    
        // Indices [0, NUM_DIR_LIGHTS) are directional lights;
        // indices [NUM_DIR_LIGHTS, NUM_DIR_LIGHTS+NUM_POINT_LIGHTS) are point lights;
        // indices [NUM_DIR_LIGHTS+NUM_POINT_LIGHTS, NUM_DIR_LIGHTS+NUM_POINT_LIGHT+NUM_SPOT_LIGHTS)
        // are spot lights for a maximum of MaxLights per object.
        Light gLights[MaxLights];
    };
    
    //材质
    cbuffer cbMaterial : register(b2)
    {
    	float4   gDiffuseAlbedo;
        float3   gFresnelR0;
        float    gRoughness;
    	float4x4 gMatTransform;
    };
    
    //输入的顶点结构体
    struct VertexIn
    {
    	float3 PosW  : POSITION;
    	float2 SizeW : SIZE;
    };
    
    //输出的顶点结构体
    struct VertexOut
    {
    	float3 CenterW : POSITION;
    	float2 SizeW   : SIZE;
    };
    
    //GS的输出结构体
    struct GeoOut
    {
    	float4 PosH    : SV_POSITION;
        float3 PosW    : POSITION;
        float3 NormalW : NORMAL;
        float2 TexC    : TEXCOORD;
        uint   PrimID  : SV_PrimitiveID;
    };
    
    VertexOut VS(VertexIn vin)
    {
    	VertexOut vout;
    
    	vout.CenterW = vin.PosW;
    	vout.SizeW   = vin.SizeW;
    
    	return vout;
    }
    
     // 因为需要将一个点扩展为一个四边形,所以每次调用GS最多输出四个顶点
    [maxvertexcount(4)]
    void GS(point VertexOut gin[1], 
            uint primID : SV_PrimitiveID, 
            inout TriangleStream<GeoOut> triStream)
    {	
    	//计算公告板的局部坐标系和世界空间的相对关系,使得公告板和y轴对齐且面向相机
    	float3 up = float3(0.0f, 1.0f, 0.0f);
    	float3 look = gEyePosW - gin[0].CenterW;
    	look.y = 0.0f; // 使公告板立于xz平面
    	look = normalize(look);
    	float3 right = cross(up, look);
    
    	// 计算世界空间中四边形的顶点
    	float halfWidth  = 0.5f*gin[0].SizeW.x;
    	float halfHeight = 0.5f*gin[0].SizeW.y;
    	
    	float4 v[4];
    	v[0] = float4(gin[0].CenterW + halfWidth*right - halfHeight*up, 1.0f);
    	v[1] = float4(gin[0].CenterW + halfWidth*right + halfHeight*up, 1.0f);
    	v[2] = float4(gin[0].CenterW - halfWidth*right - halfHeight*up, 1.0f);
    	v[3] = float4(gin[0].CenterW - halfWidth*right + halfHeight*up, 1.0f);
    
    	//将四边形的顶点变换到世界空间,将其以三角形带的形式输出
    	float2 texC[4] = 
    	{
    		float2(0.0f, 1.0f),
    		float2(0.0f, 0.0f),
    		float2(1.0f, 1.0f),
    		float2(1.0f, 0.0f)
    	};
    	
    	GeoOut gout;
    	[unroll]
    	for(int i = 0; i < 4; ++i)
    	{
    		gout.PosH     = mul(v[i], gViewProj);
    		gout.PosW     = v[i].xyz;
    		gout.NormalW  = look;
    		gout.TexC     = texC[i];
    		gout.PrimID   = primID;
    		
    		triStream.Append(gout);
    	}
    }
    
    float4 PS(GeoOut pin) : SV_Target
    {
    	float3 uvw = float3(pin.TexC, pin.PrimID%3);
        float4 diffuseAlbedo = gTreeMapArray.Sample(gsamAnisotropicWrap, uvw) * gDiffuseAlbedo;
    	
    #ifdef ALPHA_TEST
    	// Discard pixel if texture alpha < 0.1.  We do this test as soon 
    	// as possible in the shader so that we can potentially exit the
    	// shader early, thereby skipping the rest of the shader code.
    	clip(diffuseAlbedo.a - 0.1f);
    #endif
    
        // Interpolating normal can unnormalize it, so renormalize it.
        pin.NormalW = normalize(pin.NormalW);
    
        // 光线经物体表面一点反射到相机方向的向量
    	float3 toEyeW = gEyePosW - pin.PosW;
    	float distToEye = length(toEyeW);
    	toEyeW /= distToEye; // normalize
    
        // Light terms.
        float4 ambient = gAmbientLight*diffuseAlbedo;
    
        const float shininess = 1.0f - gRoughness;
        Material mat = { diffuseAlbedo, gFresnelR0, shininess };
        float3 shadowFactor = 1.0f;
        float4 directLight = ComputeLighting(gLights, mat, pin.PosW,
            pin.NormalW, toEyeW, shadowFactor);
    
        float4 litColor = ambient + directLight;
    
    #ifdef FOG
    	float fogAmount = saturate((distToEye - gFogStart) / gFogRange);
    	litColor = lerp(litColor, gFogColor, fogAmount);
    #endif
    
        // Common convention to take alpha from diffuse albedo.
        litColor.a = diffuseAlbedo.a;
    
        return litColor;
    }
    

    可以看到其中有两个未见过的声明,它们分别意为语义和纹理数组:

    uint primID : SV_PrimitiveID
    Texture2DArray gTreeMapArray : register(t0);
    
    1. SV_PrimitiveID语义

      1. 在VS中使用SV_PrimitiveID语义

        在输入装配器阶段会自动为每个图元生成图元ID,假设绘制n个图元,第一个图元标记为0,第二个标记为1······最后一个为n-1

        VertexOut VS(VertexIn vin, uint vertID : SV_VertexID)
        
      2. 在GS中使用SV_PrimitiveID语义

        仅用于将图元ID写入输出的顶点

        [maxvertexcount(4)]
        void GS(point VertexOut gin[1], 
                uint primID : SV_PrimitiveID, 
                inout TriangleStream<GeoOut> triStream)
        
      3. 在PS中SV_PrimitiveID语义

        PS将图元ID用作纹理数组的索引

        float4 PS(VertexOut pin, uint primID : SV_PrimitiveID : SV_Target)
        
    2. 纹理数组:存放纹理的数组,用接口ID3D12Resource表示

      1. 创建方式

        Texture2DArray gTreeMapArray : register(t0);
        

        那为什么不使用C样式的数组初始化方式呢?

        Texture2D TexArray[4];
        

        在着色器5.1模型中可以这样写,但在这之前的版本不可以。而且,该实现方式可能会产生额外开销

      2. 指定纹理数组存储元素个数:设置DepthOrArraySize属性来指定纹理数组存储元素个数。若是3D纹理,该项指定为深度值

      3. 对纹理数组采样:使用纹理数组需要三个坐标,前两个为2D纹理坐标,第三个为该纹理的索引

        float3 uvw = float3(pin.TexC, pin.PrimID%3);
        float4 diffuseAlbedo = gTreeMapArray.Sample(gsamAnisotropicWrap, uvw) * gDiffuseAlbedo;
        
      4. 好处

        1. 在一次绘制调用中,可以画出不同纹理的图元。一般而言,必须用不同纹理对每个有着独立渲染项的网格进行处理,但使用纹理数组,便只需调用一次

          SetTextureArray();
          DrawPrimitivesWithTextureArray();
          
      5. 加载纹理数组

        微软公司提供了加载纹理数组的工具texassemble

        • 合并多个.dds图像为一个纹理数组treeArray.dds

          texassemble -array -o treeArray.dds t0.dds t1.dds t2.dds t3.dds
          
        • 需要注意的是使用texassemble构建纹理数组时,输入的多个图像只能各有一种mipmap层级,创建后可以通过texconv工具生成多种mipmap层级,并改变纹理格式

          texconv -m 10 -f BC3_UNORM treeArray.dds
          
      6. 纹理子资源(subresource)

        下图展示了一个拥有多个纹理的纹理数组,其中每个纹理都有自己的mipmap链。我们用数组切片(array slice)表示纹理数组中的某个纹理及其mipmap链,用切片(mip slice)表示纹理数组中某一层级的所有mipmap,而子资源是指纹理数组中某纹理的单个mipmap层级,而子资源也是以线性索引进行标记的
        image-20230228164227446
        image-20230228165308103

        D3D也提供了相应的函数来计算子资源的线性索引,给定mip切片索引、数组切片索引、平面切片(对某些YUV平面格式的分量以索引方式进行单独提取,不需要则设为0)索引、mipmap层级、纹理数组大小即可

      最后再结合上篇所讲的AlphaToCoverage技术解决森林边缘失真的问题实现该示例

大致流程

  1. 加载纹理

    auto grassTex = std::make_unique<Texture>();
    grassTex->Name = "grassTex";
    grassTex->Filename = L"../../Textures/grass.dds";
    ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(md3dDevice.Get(),
    		mCommandList.Get(), grassTex->Filename.c_str(),
    		grassTex->Resource, grassTex->UploadHeap));
    
    auto waterTex = std::make_unique<Texture>();
    waterTex->Name = "waterTex";
    waterTex->Filename = L"../../Textures/water1.dds";
    ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(md3dDevice.Get(),
    		mCommandList.Get(), waterTex->Filename.c_str(),
    		waterTex->Resource, waterTex->UploadHeap));
    
    auto fenceTex = std::make_unique<Texture>();
    fenceTex->Name = "fenceTex";
    fenceTex->Filename = L"../../Textures/WireFence.dds";
    ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(md3dDevice.Get(),
    		mCommandList.Get(), fenceTex->Filename.c_str(),
    		fenceTex->Resource, fenceTex->UploadHeap));
    
    auto treeArrayTex = std::make_unique<Texture>();
    treeArrayTex->Name = "treeArrayTex";
    treeArrayTex->Filename = L"../../Textures/treeArray2.dds";
    ThrowIfFailed(DirectX::CreateDDSTextureFromFile12(md3dDevice.Get(),
    		mCommandList.Get(), treeArrayTex->Filename.c_str(),
    		treeArrayTex->Resource, treeArrayTex->UploadHeap));
    
    mTextures[grassTex->Name] = std::move(grassTex);
    mTextures[waterTex->Name] = std::move(waterTex);
    mTextures[fenceTex->Name] = std::move(fenceTex);
    mTextures[treeArrayTex->Name] = std::move(treeArrayTex);
    
  2. 创建根签名

    //四个根参数:一个描述符表,三个描述符
    CD3DX12_DESCRIPTOR_RANGE texTable;
    texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);
    
    // Root parameter can be a table, root descriptor or root constants.
    CD3DX12_ROOT_PARAMETER slotRootParameter[4];
    
    // Perfomance TIP: Order from most frequent to least frequent.
    slotRootParameter[0].InitAsDescriptorTable(1, &texTable, D3D12_SHADER_VISIBILITY_PIXEL);
    slotRootParameter[1].InitAsConstantBufferView(0);
    slotRootParameter[2].InitAsConstantBufferView(1);
    slotRootParameter[3].InitAsConstantBufferView(2);
    
    auto staticSamplers = GetStaticSamplers();
    
    // A root signature is an array of root parameters.
    CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(4, slotRootParameter,
                                            (UINT)staticSamplers.size(), staticSamplers.data(),
                                            D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
    
    // create a root signature with a single slot which points to a descriptor range consisting of a single constant buffer
    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())));
    
  3. 创建描述符堆

    //创建SRV描述符堆,存储四个不同的纹理资源的描述符
    D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
    srvHeapDesc.NumDescriptors = 4;
    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)));
    
    //
    // Fill out the heap with actual descriptors.
    //
    CD3DX12_CPU_DESCRIPTOR_HANDLE hDescriptor(mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
    
    auto grassTex = mTextures["grassTex"]->Resource;
    auto waterTex = mTextures["waterTex"]->Resource;
    auto fenceTex = mTextures["fenceTex"]->Resource;
    auto treeArrayTex = mTextures["treeArrayTex"]->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);
    
    // next descriptor
    hDescriptor.Offset(1, mCbvSrvDescriptorSize);
    
    auto desc = treeArrayTex->GetDesc();
    srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
    srvDesc.Format = treeArrayTex->GetDesc().Format;
    srvDesc.Texture2DArray.MostDetailedMip = 0;
    srvDesc.Texture2DArray.MipLevels = -1;
    srvDesc.Texture2DArray.FirstArraySlice = 0;
    srvDesc.Texture2DArray.ArraySize = treeArrayTex->GetDesc().DepthOrArraySize;
    md3dDevice->CreateShaderResourceView(treeArrayTex.Get(), &srvDesc, hDescriptor);
    
  4. 创建着色器和输入布局

    //着色器使用的宏,是否实现雾和alpha test
    const D3D_SHADER_MACRO defines[] =
    {
        "FOG", "1",
        NULL, NULL
    };
    
    const D3D_SHADER_MACRO alphaTestDefines[] =
    {
        "FOG", "1",
        "ALPHA_TEST", "1",
        NULL, NULL
    };
    
    mShaders["standardVS"] = d3dUtil::CompileShader(L"Shaders\\Default.hlsl", nullptr, "VS", "vs_5_0");
    mShaders["opaquePS"] = d3dUtil::CompileShader(L"Shaders\\Default.hlsl", defines, "PS", "ps_5_0");	//开启雾效
    mShaders["alphaTestedPS"] = d3dUtil::CompileShader(L"Shaders\\Default.hlsl", alphaTestDefines, "PS", "ps_5_0");	//开启雾效,alpha test
    
    mShaders["treeSpriteVS"] = d3dUtil::CompileShader(L"Shaders\\TreeSprite.hlsl", nullptr, "VS", "vs_5_0");
    mShaders["treeSpriteGS"] = d3dUtil::CompileShader(L"Shaders\\TreeSprite.hlsl", nullptr, "GS", "gs_5_0");
    mShaders["treeSpritePS"] = d3dUtil::CompileShader(L"Shaders\\TreeSprite.hlsl", alphaTestDefines, "PS", "ps_5_0");
    
    mStdInputLayout =
    {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
    };
    
    //用于实现公告板的顶点结构体布局
    mTreeSpriteInputLayout =
    {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "SIZE", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
    };
    
  5. 生成陆地、海洋、立方体

    //生成陆地
    GeometryGenerator geoGen;
    GeometryGenerator::MeshData grid = geoGen.CreateGrid(160.0f, 160.0f, 50, 50);
    
    // 获取所需要的顶点,并利用高度函数计算每个顶点的高度值
    // 顶点的颜色基于高度而定
    
    std::vector<Vertex> vertices(grid.Vertices.size());
    for(size_t i = 0; i < grid.Vertices.size(); ++i)
    {
        auto& p = grid.Vertices[i].Position;
        vertices[i].Pos = p;
        vertices[i].Pos.y = GetHillsHeight(p.x, p.z);
        vertices[i].Normal = GetHillsNormal(p.x, p.z);
        vertices[i].TexC = grid.Vertices[i].TexC;
    }
    
    const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);
    
    std::vector<std::uint16_t> indices = grid.GetIndices16();
    const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);
    
    auto geo = std::make_unique<MeshGeometry>();
    geo->Name = "landGeo";
    
    ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->VertexBufferCPU));
    CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);
    
    ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU));
    CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);
    
    geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
                                                        mCommandList.Get(), vertices.data(), vbByteSize, geo->VertexBufferUploader);
    
    geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
                                                       mCommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader);
    
    geo->VertexByteStride = sizeof(Vertex);
    geo->VertexBufferByteSize = vbByteSize;
    geo->IndexFormat = DXGI_FORMAT_R16_UINT;
    geo->IndexBufferByteSize = ibByteSize;
    
    SubmeshGeometry submesh;
    submesh.IndexCount = (UINT)indices.size();
    submesh.StartIndexLocation = 0;
    submesh.BaseVertexLocation = 0;
    
    geo->DrawArgs["grid"] = submesh;
    
    mGeometries["landGeo"] = std::move(geo);
    
    //生成海洋
    std::vector<std::uint16_t> indices(3 * mWaves->TriangleCount()); // 3 indices per face
    assert(mWaves->VertexCount() < 0x0000ffff);
    
    // Iterate over each quad.
    int m = mWaves->RowCount();
    int n = mWaves->ColumnCount();
    int k = 0;
    for(int i = 0; i < m - 1; ++i)
    {
        for(int j = 0; j < n - 1; ++j)
        {
            indices[k] = i*n + j;
            indices[k + 1] = i*n + j + 1;
            indices[k + 2] = (i + 1)*n + j;
    
            indices[k + 3] = (i + 1)*n + j;
            indices[k + 4] = i*n + j + 1;
            indices[k + 5] = (i + 1)*n + j + 1;
    
            k += 6; // next quad
        }
    }
    
    UINT vbByteSize = mWaves->VertexCount()*sizeof(Vertex);
    UINT ibByteSize = (UINT)indices.size()*sizeof(std::uint16_t);
    
    auto geo = std::make_unique<MeshGeometry>();
    geo->Name = "waterGeo";
    
    // Set dynamically.
    geo->VertexBufferCPU = nullptr;
    geo->VertexBufferGPU = nullptr;
    
    ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU));
    CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);
    
    geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
                                                       mCommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader);
    
    geo->VertexByteStride = sizeof(Vertex);
    geo->VertexBufferByteSize = vbByteSize;
    geo->IndexFormat = DXGI_FORMAT_R16_UINT;
    geo->IndexBufferByteSize = ibByteSize;
    
    SubmeshGeometry submesh;
    submesh.IndexCount = (UINT)indices.size();
    submesh.StartIndexLocation = 0;
    submesh.BaseVertexLocation = 0;
    
    geo->DrawArgs["grid"] = submesh;
    
    mGeometries["waterGeo"] = std::move(geo);
    
    //生成立方体
    GeometryGenerator geoGen;
    GeometryGenerator::MeshData box = geoGen.CreateBox(8.0f, 8.0f, 8.0f, 3);
    
    std::vector<Vertex> vertices(box.Vertices.size());
    for (size_t i = 0; i < box.Vertices.size(); ++i)
    {
        auto& p = box.Vertices[i].Position;
        vertices[i].Pos = p;
        vertices[i].Normal = box.Vertices[i].Normal;
        vertices[i].TexC = box.Vertices[i].TexC;
    }
    
    const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);
    
    std::vector<std::uint16_t> indices = box.GetIndices16();
    const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);
    
    auto geo = std::make_unique<MeshGeometry>();
    geo->Name = "boxGeo";
    
    ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->VertexBufferCPU));
    CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);
    
    ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU));
    CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);
    
    geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
                                                        mCommandList.Get(), vertices.data(), vbByteSize, geo->VertexBufferUploader);
    
    geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
                                                       mCommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader);
    
    geo->VertexByteStride = sizeof(Vertex);
    geo->VertexBufferByteSize = vbByteSize;
    geo->IndexFormat = DXGI_FORMAT_R16_UINT;
    geo->IndexBufferByteSize = ibByteSize;
    
    SubmeshGeometry submesh;
    submesh.IndexCount = (UINT)indices.size();
    submesh.StartIndexLocation = 0;
    submesh.BaseVertexLocation = 0;
    
    geo->DrawArgs["box"] = submesh;
    
    mGeometries["boxGeo"] = std::move(geo);
    
  6. 生成公告板

    //公告板的中心位置、宽高
    struct TreeSpriteVertex
    {
        XMFLOAT3 Pos;
        XMFLOAT2 Size;
    };
    
    static const int treeCount = 16;	//树的个数
    std::array<TreeSpriteVertex, 16> vertices;
    for(UINT i = 0; i < treeCount; ++i)
    {
        //随机生成公告板的位置
        float x = MathHelper::RandF(-45.0f, 45.0f);
        float z = MathHelper::RandF(-45.0f, 45.0f);
        float y = GetHillsHeight(x, z);
    
        // 绘制的树要比陆地高
        y += 8.0f;
    
        vertices[i].Pos = XMFLOAT3(x, y, z);
        vertices[i].Size = XMFLOAT2(20.0f, 20.0f);
    }
    
    std::array<std::uint16_t, 16> indices =
    {
        0, 1, 2, 3, 4, 5, 6, 7,
        8, 9, 10, 11, 12, 13, 14, 15
    };
    
    const UINT vbByteSize = (UINT)vertices.size() * sizeof(TreeSpriteVertex);
    const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);
    
    auto geo = std::make_unique<MeshGeometry>();
    geo->Name = "treeSpritesGeo";
    
    ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->VertexBufferCPU));
    CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);
    
    ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU));
    CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);
    
    geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
                                                        mCommandList.Get(), vertices.data(), vbByteSize, geo->VertexBufferUploader);
    
    geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
                                                       mCommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader);
    
    geo->VertexByteStride = sizeof(TreeSpriteVertex);
    geo->VertexBufferByteSize = vbByteSize;
    geo->IndexFormat = DXGI_FORMAT_R16_UINT;
    geo->IndexBufferByteSize = ibByteSize;
    
    SubmeshGeometry submesh;
    submesh.IndexCount = (UINT)indices.size();
    submesh.StartIndexLocation = 0;
    submesh.BaseVertexLocation = 0;
    
    geo->DrawArgs["points"] = submesh;
    
    mGeometries["treeSpritesGeo"] = std::move(geo);
    
  7. 定义材质

    auto grass = std::make_unique<Material>();
    grass->Name = "grass";
    grass->MatCBIndex = 0;
    grass->DiffuseSrvHeapIndex = 0;
    grass->DiffuseAlbedo = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
    grass->FresnelR0 = XMFLOAT3(0.01f, 0.01f, 0.01f);
    grass->Roughness = 0.125f;
    
    // This is not a good water material definition, but we do not have all the rendering
    // tools we need (transparency, environment reflection), so we fake it for now.
    auto water = std::make_unique<Material>();
    water->Name = "water";
    water->MatCBIndex = 1;
    water->DiffuseSrvHeapIndex = 1;
    water->DiffuseAlbedo = XMFLOAT4(1.0f, 1.0f, 1.0f, 0.5f);
    water->FresnelR0 = XMFLOAT3(0.1f, 0.1f, 0.1f);
    water->Roughness = 0.0f;
    
    auto wirefence = std::make_unique<Material>();
    wirefence->Name = "wirefence";
    wirefence->MatCBIndex = 2;
    wirefence->DiffuseSrvHeapIndex = 2;
    wirefence->DiffuseAlbedo = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
    wirefence->FresnelR0 = XMFLOAT3(0.02f, 0.02f, 0.02f);
    wirefence->Roughness = 0.25f;
    
    auto treeSprites = std::make_unique<Material>();
    treeSprites->Name = "treeSprites";
    treeSprites->MatCBIndex = 3;
    treeSprites->DiffuseSrvHeapIndex = 3;
    treeSprites->DiffuseAlbedo = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
    treeSprites->FresnelR0 = XMFLOAT3(0.01f, 0.01f, 0.01f);
    treeSprites->Roughness = 0.125f;
    
    mMaterials["grass"] = std::move(grass);
    mMaterials["water"] = std::move(water);
    mMaterials["wirefence"] = std::move(wirefence);
    mMaterials["treeSprites"] = std::move(treeSprites);
    
  8. 生成渲染项

    //每帧渲染需要的数据
    auto wavesRitem = std::make_unique<RenderItem>();
    wavesRitem->World = MathHelper::Identity4x4();
    XMStoreFloat4x4(&wavesRitem->TexTransform, XMMatrixScaling(5.0f, 5.0f, 1.0f));
    wavesRitem->ObjCBIndex = 0;
    wavesRitem->Mat = mMaterials["water"].get();
    wavesRitem->Geo = mGeometries["waterGeo"].get();
    wavesRitem->PrimitiveType = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
    wavesRitem->IndexCount = wavesRitem->Geo->DrawArgs["grid"].IndexCount;
    wavesRitem->StartIndexLocation = wavesRitem->Geo->DrawArgs["grid"].StartIndexLocation;
    wavesRitem->BaseVertexLocation = wavesRitem->Geo->DrawArgs["grid"].BaseVertexLocation;
    
    mWavesRitem = wavesRitem.get();
    
    mRitemLayer[(int)RenderLayer::Transparent].push_back(wavesRitem.get());
    
    auto gridRitem = std::make_unique<RenderItem>();
    gridRitem->World = MathHelper::Identity4x4();
    XMStoreFloat4x4(&gridRitem->TexTransform, XMMatrixScaling(5.0f, 5.0f, 1.0f));
    gridRitem->ObjCBIndex = 1;
    gridRitem->Mat = mMaterials["grass"].get();
    gridRitem->Geo = mGeometries["landGeo"].get();
    gridRitem->PrimitiveType = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
    gridRitem->IndexCount = gridRitem->Geo->DrawArgs["grid"].IndexCount;
    gridRitem->StartIndexLocation = gridRitem->Geo->DrawArgs["grid"].StartIndexLocation;
    gridRitem->BaseVertexLocation = gridRitem->Geo->DrawArgs["grid"].BaseVertexLocation;
    
    mRitemLayer[(int)RenderLayer::Opaque].push_back(gridRitem.get());
    
    auto boxRitem = std::make_unique<RenderItem>();
    XMStoreFloat4x4(&boxRitem->World, XMMatrixTranslation(3.0f, 2.0f, -9.0f));
    boxRitem->ObjCBIndex = 2;
    boxRitem->Mat = mMaterials["wirefence"].get();
    boxRitem->Geo = mGeometries["boxGeo"].get();
    boxRitem->PrimitiveType = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
    boxRitem->IndexCount = boxRitem->Geo->DrawArgs["box"].IndexCount;
    boxRitem->StartIndexLocation = boxRitem->Geo->DrawArgs["box"].StartIndexLocation;
    boxRitem->BaseVertexLocation = boxRitem->Geo->DrawArgs["box"].BaseVertexLocation;
    
    mRitemLayer[(int)RenderLayer::AlphaTested].push_back(boxRitem.get());
    
    auto treeSpritesRitem = std::make_unique<RenderItem>();
    treeSpritesRitem->World = MathHelper::Identity4x4();
    treeSpritesRitem->ObjCBIndex = 3;
    treeSpritesRitem->Mat = mMaterials["treeSprites"].get();
    treeSpritesRitem->Geo = mGeometries["treeSpritesGeo"].get();
    treeSpritesRitem->PrimitiveType = D3D11_PRIMITIVE_TOPOLOGY_POINTLIST;
    treeSpritesRitem->IndexCount = treeSpritesRitem->Geo->DrawArgs["points"].IndexCount;
    treeSpritesRitem->StartIndexLocation = treeSpritesRitem->Geo->DrawArgs["points"].StartIndexLocation;
    treeSpritesRitem->BaseVertexLocation = treeSpritesRitem->Geo->DrawArgs["points"].BaseVertexLocation;
    
    mRitemLayer[(int)RenderLayer::AlphaTestedTreeSprites].push_back(treeSpritesRitem.get());
    
    mAllRitems.push_back(std::move(wavesRitem));
    mAllRitems.push_back(std::move(gridRitem));
    mAllRitems.push_back(std::move(boxRitem));
    mAllRitems.push_back(std::move(treeSpritesRitem));
    
  9. 生成帧资源

    void TreeBillboardsApp::BuildFrameResources()
    {
        for(int i = 0; i < gNumFrameResources; ++i)
        {
            mFrameResources.push_back(std::make_unique<FrameResource>(md3dDevice.Get(),
                1, (UINT)mAllRitems.size(), (UINT)mMaterials.size(), mWaves->VertexCount()));
        }
    }
    

reference

Directx12 3D 游戏开发实战

标签:std,1.0,DX,几何,indices,顶点,geo,着色器
From: https://www.cnblogs.com/chenglixue/p/17166168.html

相关文章

  • 毕达哥拉斯学派与几何学的诞生
    数的宇宙观数本是人类用来整理外部事物的方法,怎么能够充当宇宙的本源?整个宇宙由数来构造的。他所说的就是他看到的如此,数与数之间的和谐比例的关系,不仅是人类的数学思......
  • 细分着色器的一些注意事项(持续更新)
     ①细分着色器似乎不能支持GPUinstancing,图片来源:Tessellation(catlikecoding.com)......
  • 大学物理——几何光学
    几何光学基本定律折射定律:\({\displaystylen_1\sini=n_2\sin\gamma}\)反射定律可当作折射定律在\(n_1=-n_2\)下的特例,得\(i=-γ\),负号表示反射线和入射线......
  • 扫描线(计算几何)
    semi-AFO选手的DS记录(您将在这里见到最垃圾的扫描线写法.1.面积扫描线本身还是很好理解的.偷一张图(图源OI-wiki)下面的\(cnt\)表示对应区域被矩形覆盖的次......
  • Docker CLI docker buildx 常用命令
    Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化。Doc......
  • DX 混合
    什么是混合?​ 混合可以使得我们将要光栅化的像素(源像素)和先前已光栅化至后台缓冲区的像素进行融合为什么需要混合?​ 混合主要用于渲染半透明物体,如水混合的基本操......
  • 【YBT2023寒假Day14 A】切割蛋糕(计算几何)
    切割蛋糕题目链接:YBT2023寒假Day14A题目大意给你一个圆,圆心在原点,每次有一条直线,切掉圆中不包含原点的部分。(直线给出的部分是它在于圆两个交点形成的线段的垂直平分......
  • DX12 纹理贴图
    前言​ 本篇重在展示如何进行纹理贴图,理论还请看这https://www.cnblogs.com/chenglixue/category/2175285.html指定uv坐标​ 为了简易性,只展示立方体盒设置的uv坐标。理......
  • PyTorch——SummaryWriter,TensorboardX
    Tensorboard的工作流程将代码运行过程中的,某些你关心的数据保存在一个文件夹中:这一步由代码中的writer完成再读取这个文件夹中的数据,用浏览器显示出来:这一步通过在命令行运......
  • 代数转几何:应用复数的locus
    先上个匪夷所思的东西:FractionalLinearTransformationAfractionallineartransformationisafunctionoftheform$T(z)=\frac{(az+b)}{(cz+d)}$,where$a,......