使用DX9几何实例化(Instancing)模拟Tessellation

发表于2016-01-27
评论6 1.8k浏览

                     使用DX9几何实例化(Instancing)模拟Tessellation

             Heatonlan

 

  本文不是介绍DX11Tessellation的使用,也不是直接用DX9去模拟DX11的3个Tessellation Stage,而是在DX9下通过一种比较简单的方法让一个模型做Tessellation,进而思考更多的可能性。

 通常情况,几何实例化(Instancing)一个5000三角面的模型,16个实例,将产生一次16个5000面的渲染,结果是不同位置朝向和颜色等的16个实例;设象一下另外一种情况,将这个模型每个三角面都Tessellation成16个小三角形(标记为Tri0-Tri15),假设第一个实例是所有的Tri0构成的部分,第二个是所有Tri1构成的,以此类推,共16个实例;综合以上两点,设想首先让16个5000面的实例先重叠在一起,然后分别计算出各个实例对应的Tri0到Tri15,这样就完成了提交16个实例5000面模型到16 x 5000面一个模型的转变,具体如下:

 均匀的Tessellation(每个三角形Tessellation成一样数量)

 处理原模型:让每个顶点包含整个三角面的信息,并且记录当前顶点在这个三角形的顺序0或1或2

 示例顶点结构

struct PatchTessellation

{

D3DXVECTOR3 pos0;

D3DXVECTOR3 pos1;

D3DXVECTOR3 pos2;

D3DXVECTOR2 uv0;

D3DXVECTOR2 uv1;

D3DXVECTOR2 uv2;

DWORDv012;//顶点顺序

};

  生成Tessellation Palette:以5000面16实例为例子,这里会生成16组参数,每组9个值,对应某个小三角形(Tri0到Tri15)的三个顶点在大三角形的权重.

示例代码

//tessellation palette

void CalcWeightNew(

int j0, int i0, int j1, int i1, int j2, int i2, int ijMax,//3 index pair

D3DXVECTOR3& w0, D3DXVECTOR3& w1, D3DXVECTOR3& w2)

{

// ^

// |

// p0(i = 0)

//   | 

//   |   

//   |   

//   p1--------p2-->j

 

//  (0,0)

//  |  

//|

//(1,0)---(1,1)

//  |     |  

//|     |

//(2,0)---(2,1)--(2,2)

//

 

w0.x = 1.f - (float)i0 / (float)ijMax;

w0.y = (float)(i0 - j0) / (float)ijMax;

w0.z = (float)j0 / (float)ijMax;

 

w1.x = 1.f - (float)i1 / (float)ijMax;

w1.y = (float)(i1 - j1) / (float)ijMax;

w1.z = (float)j1 / (float)ijMax;

 

w2.x = 1.f - (float)i2 / (float)ijMax;

w2.y = (float)(i2 - j2) / (float)ijMax;

w2.z = (float)j2 / (float)ijMax;

}

 

//process for level n

// level = 3

//  0---1---2---3

//  |   | | |

//  4---5---6---7

//  | |   | |

//  8---9--10--11

//  | | |    |

// 12--13--14--15

for (int i = 0; i < level; ++i)

{

for (int j = 0; j <= i; ++j)

{

D3DXVECTOR3 weight[3];

 

//0

//  |

//  |_

//1  2

CalcWeightNew(j, i, j, i+1, j+1, i+1, level,

weight[0], weight[1], weight[2]);

 

// copy weight[3] to instancing buffer, and instancing buffer++

 

// 0__2

//  |

//   |

//  1

if(j != i)//skip last

{

CalcWeightNew(j, i, j+1, i+1, j+1, i, level,

weight[0], weight[1], weight[2]);

// copy weight[3] to instancing buffer, and instancing buffer++

}

}

}

   在Vertex Shader里计算Tessellation:有了1和2的数据,在VS里可以直接计算了,VS示例如下:

//VS input

struct VSInTess

{

//对应1的PatchTessellation

float4 Position0 : POSITION0;

float4 Position1 : POSITION1;

float4 Position2 : POSITION2;

float4 UV0 : TEXCOORD0;

float4 UV1 : TEXCOORD1;

float4 UV2 : TEXCOORD2;

float4 Color : COLOR;

//对应2的Tessellation Palette,有效部分是3个float3

    float4 Tessllation0 : TEXCOORD3;

float4 Tessllation1 : TEXCOORD4;

float4 Tessllation2 : TEXCOORD5;

};

//这里在VS前面处理Tessellation

VSOutTess VSMainTess(VSInTess IN_tess)

{

VSOutTess Output;

float4 curTessllation = 0;

//代码略恶心,但生成的asm很简短

//目前在VS里没找到更合适的索引VSInput的方法

if (IN_tess.Color.x < 0.5f/255.f)

{

curTessllation = IN_tess.Tessllation0;

}

else if (IN_tess.Color.x < 1.5f/255.f)

{

curTessllation = IN_tess.Tessllation1;

}

else if (IN_tess.Color.x < 2.5f/255.f)

{

curTessllation = IN_tess.Tessllation2;

}

float4 InPosition = IN_tess.Position0 * curTessllation.x

  + IN_tess.Position1 * curTessllation.y

  + IN_tess.Position2 * curTessllation.z;

float2 InUV = IN_tess.UV0 * curTessllation.x

+ IN_tess.UV1 * curTessllation.y

+ IN_tess.UV2 * curTessllation.z;

//使用InPosition 和InUV继续做后面的VS计算

//带Skinning可以在World变换之后

}

  有了1.2.3,最基本的目的达成了,5000面被Tessellation成了16x5000面了,后续按需要可以采样位移贴图,做Bezier曲面等等.

  非均匀的Tessellation(不同三角形Tessellation成不同数量,并处理接边问题)

 因为DX9只能CPU发起渲染,变长的三角形序列是无法操作的,有个变通的方法,就是控制三角形最大Tessellation数字,通过在VS里对多余的三角形进行退化,达到变长输出的目的.

 关于接边,可以定义每条边的TessFactor,即TF0,TF1,TF2,定义中间区域的InsideTessFactor,即ITF,这样三角形的Tessellation Palette就由TF0,TF1,TF2,ITF完全确定了,VS里只要保证TF0,TF1,TF2在共享边算出一样的值就可以了,比如通过边的长度来计算TessFactor.

 实现上

 1.离线构造两张表,存入浮点纹理

Tex0: Tessellation Palette大小 <- (TF0,TF1,TF2,ITF)

TEX1:float x 3 <-(TF0,TF1,TF2,ITF,InstanceID,VtxID)

  例如,定义最大Tessellation数字为3,这样TF0,TF1,TF2,ITF的范围就是0到3,枚举所有Tessellation Palette并写入浮点纹理TEX1,Tessellation Palette的大小写入TEX0;在TF0 = TF1 = TF2 = ITF = 最大3时,假设能Tessellation成64个小三角形,这时纹理大小为3float(权重) x 3(VtxID) x 64(InstanceID) x 4(TF0) x 4(TF1) x 4(TF2) x 4(ITF),约600KB,当然中间有很多空值,只是为了索引方便,有较大优化余地.  注:3float(权重) x 3(VtxID)对应前面均匀Tessellation的9个float.


 2.渲染5000面模型时,提交最大的64个实例的渲染,实例化数据里只需要包含InstanceID(实例序号,这里是0到63)即可.


 3.Vertex Shader里先计算TF0,TF1,TF2,ITF,查TEX0先取得对应Tessellation Palette的大小,例如32,对大于等于32的实例的各个三角形顶点,直接退化,避免后面的渲染计算,小于32的部分,查找TEX1取得3个float,计算顶点位置即可.

更具体的实现下回分解,上面其实只是一个构思。。。

      好吧,尽管现在都DX11和DX12了,但是DX9在国内确实无法直接放弃,上面的小技巧也给DX9增添了新技能,怎么应用就看想象力了。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引