十五、D3D12学习笔记——曲面细分

曲面细分:将几何体细分为更小的三角形并偏移产生曲面,达到细节丰富的效果。工作都在shader中,进行格式的配置即可。

一、CPU侧

启用了曲面细分,就要把点作为控制点来传输:

1.图元装配阶段

cmdList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST);

2.PSO定义阶段

opaquePsoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH;

opaquePsoDesc.HS =
    {
        reinterpret_cast<BYTE*>(mShaders["tessHS"]->GetBufferPointer()),
        mShaders["tessHS"]->GetBufferSize()
    };
    opaquePsoDesc.DS =
    {
        reinterpret_cast<BYTE*>(mShaders["tessDS"]->GetBufferPointer()),
        mShaders["tessDS"]->GetBufferSize()
    };

以上就是在CPU端从设置PSO和绑定渲染流水线的控制点图元设置全过程。其中输入装配可以根据控制点个数具有不同的类型(修改格式而已,从1-32)

二、GPU侧

应用曲面细分之后,顶点着色器处理对象就是单个控制点,可用于对控制点进行调整:

1.顶点着色器

VertexOut VS(VertexIn vin)
{
    VertexOut vout;
    
    vout.PosL = vin.PosL;//不调整数据,只做传递用

    return vout;
}

出了顶点着色器,就进入外壳着色器

2.外壳着色器

所谓外壳着色器就是一种规则的制定者,共分为两种:常量外壳着色器控制点外壳着色器

1).常量外壳着色器HS

如上,指定4个控制点,实际意义就是将4个控制点组成一个单个面片。常量HS的作用就是处理单个面片(处理一个面片就调用一次),输出该面片细分的因子(边缘与内部细分方式):

struct PatchTess
{
    float EdgeTess[4]   : SV_TessFactor;//四条边细分因子,几边形就是几条边
    float InsideTess[2] : SV_InsideTessFactor;//内部细分因子,三角形一个,四边形两个(横竖)
};

PatchTess ConstantHS(InputPatch<VertexOut, 4> patch, uint patchID : SV_PrimitiveID)
{
    PatchTess pt;

//各边均分为3等份,
    pt.EdgeTess[0] = 3;
    pt.EdgeTess[1] = 3;
    pt.EdgeTess[2] = 3;
    pt.EdgeTess[3] = 3;    

//四边形内部行列数
    pt.InsideTess[0] = 3;
    pt.InsideTess[1] = 3;    
    return pt;
}

注意:

  1. InputPatch<VertexOut, 4> patch表示是由顶点着色器输出的4个控制点构成的一个面片;
  2. 每个面片具有独立的图元索引:uint patchID : SV_PrimitiveID;
  3. 返回值    return pt;将细分因子返回至流水线以供调用。

2).控制点外壳着色器HS

以大量的控制点作为输入与输出,每输出一个控制点着色器被调用一次。

龙书对这个部分有大量的叙述,什么PN三角形法,N-patches方法啥的。个人感觉没太大感触,这个控制点外壳着色器,就是细分规则的制定者。

struct HullOut
{
    float3 PosL : POSITION;
};

[domain("quad")]//面片类型(quad / tri / isoline)
[partitioning("integer")]//剔除小数部分,使用细分因子整数部分,如果考虑小数(fractional_even / fractional_odd)
[outputtopology("triangle_cw")]//细分生成的三角形绕序(自动组织定义正反面),对线段细分则:line
[outputcontrolpoints(4)]//控制点个数,一个控制点HS执行一次
[patchconstantfunc("ConstantHS")]//指定常量外壳着色器以获得细分因子
[maxtessfactor(64.0f)]//钳制最大细分因子
HullOut HS(InputPatch<VertexOut, 4> p, //同常量HS,从顶点着色器过来
           uint i : SV_OutputControlPointID,//控制点ID,单次执行用于索引点
           uint patchId : SV_PrimitiveID)
{
    HullOut hout;

//更复杂的逻辑,可以调整输出控制点点位置    
    hout.PosL = p[i].PosL;    
    return hout;
}

3.镶嵌化

通过两个HS,我们似乎只是制定了曲面划分的规则,但是控制点还是那些控制点,没有任何变化。那我们的规则到底谁来执行呢?答案就是镶嵌化。

镶嵌化会根据我们指定的规则:

1.ConstantHS部分细分因子以SV_TessFactor,SV_InsideTessFactor标记的内容被系统识别;

2.HS部分通过[]指定了一堆规则

通过硬件在两个控制点之间进行插值,并给出在控制点构成的面片空间下的uv(w)坐标,表示插值生成的新点位置。提供给域着色器使用。

4.域着色器

struct DomainOut
{
    float4 PosH : SV_POSITION;//此时才产生真正用于场景顶点的坐标,代替原顶点着色器部分
};

[domain("quad")]//面片类型,4个控制点的,注意全过程的对应
DomainOut DS(PatchTess patchTess, //虽然这里有曲面细分但是似乎没使用过
             float2 uv : SV_DomainLocation, //由镶嵌化对控制点间使用细分因子插值得到的uv(w)
             const OutputPatch<HullOut, 4> quad)//从HS穿过来的控制点
{
    DomainOut dout;
    // 使用uv双线性插值.对于三角形就是uvw的中心坐标插值
    float3 v1 = lerp(quad[0].PosL, quad[1].PosL, uv.x); 
    float3 v2 = lerp(quad[2].PosL, quad[3].PosL, uv.x); 
    float3 p  = lerp(v1, v2, uv.y); 
    // 偏移使之成为曲面,丰富细节
    p.y = 0.3f*( p.z*sin(p.x) + p.x*cos(p.z) );
  

float4 posW = mul(float4(p, 1.0f), gWorld);

 dout.PosH = mul(posW, gViewProj);  
    return dout;
}

之后交给PS,就好像VS交给PS数据一样处理。

三、基于视点距离的曲面细分例子

这里只分享shader部分代码,因为CPU段顶点配置是非常easy的任务。

struct VertexIn
{
    float3 PosL    : POSITION;
};

struct VertexOut
{
    float3 PosL    : POSITION;
};

VertexOut VS(VertexIn vin)
{
    VertexOut vout;
    
    vout.PosL = vin.PosL;

    return vout;
}
 
struct PatchTess
{
    float EdgeTess[4]   : SV_TessFactor;
    float InsideTess[2] : SV_InsideTessFactor;
};

PatchTess ConstantHS(InputPatch<VertexOut, 4> patch, uint patchID : SV_PrimitiveID)
{
    PatchTess pt;
    
    float3 centerL = 0.25f*(patch[0].PosL + patch[1].PosL + patch[2].PosL + patch[3].PosL);
    float3 centerW = mul(float4(centerL, 1.0f), gWorld).xyz;
    
    float d = distance(centerW, gEyePosW);

    // Tessellate the patch based on distance from the eye such that
    // the tessellation is 0 if d >= d1 and 64 if d <= d0.  The interval
    // [d0, d1] defines the range we tessellate in.
    
    const float d0 = 20.0f;
    const float d1 = 100.0f;

//注意等于1表示不细分,0表示剔除
    float tess = max(64.0f*saturate( (d1-d)/(d1-d0) ),1.0);

    // Uniformly tessellate the patch.

    pt.EdgeTess[0] = tess;
    pt.EdgeTess[1] = tess;
    pt.EdgeTess[2] = tess;
    pt.EdgeTess[3] = tess;
    
    pt.InsideTess[0] = tess;
    pt.InsideTess[1] = tess;
    
    return pt;
}

struct HullOut
{
    float3 PosL : POSITION;
};

[domain("quad")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(4)]
[patchconstantfunc("ConstantHS")]
[maxtessfactor(64.0f)]
HullOut HS(InputPatch<VertexOut, 4> p, 
           uint i : SV_OutputControlPointID,
           uint patchId : SV_PrimitiveID)
{
    HullOut hout;
    
    hout.PosL = p[i].PosL;
    
    return hout;
}

struct DomainOut
{
    float4 PosH : SV_POSITION;
};

// The domain shader is called for every vertex created by the tessellator.  
// It is like the vertex shader after tessellation.
[domain("quad")]
DomainOut DS(PatchTess patchTess, 
             float2 uv : SV_DomainLocation, 
             const OutputPatch<HullOut, 4> quad)
{
    DomainOut dout;
    
    // Bilinear interpolation.
    float3 v1 = lerp(quad[0].PosL, quad[1].PosL, uv.x); 
    float3 v2 = lerp(quad[2].PosL, quad[3].PosL, uv.x); 
    float3 p  = lerp(v1, v2, uv.y); 
    
    // Displacement mapping
    p.y = 0.3f*( p.z*sin(p.x) + p.x*cos(p.z) );
    
    float4 posW = mul(float4(p, 1.0f), gWorld);
    dout.PosH = mul(posW, gViewProj);
    
    return dout;
}

float4 PS(DomainOut pin) : SV_Target
{
    return float4(1.0f, 1.0f, 1.0f, 1.0f);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值