Vertex Blending

Download: Vertex Skinning.zip (58kb)


Contents of this lesson
1. What is Vertex Blending?
2. How to set up simple vertex blending
3. Extending this to use all 4 matrices
4. Taking it all the way: Indexed vertex blending


1. What is Vertex Blending?什么是顶点混合?

如果你对Direct3D管道技术不太了解的话,这篇文章可能改变你的思想。

当你发送顶点数据到渲染设备,它在世界坐标中的数据流中经过矩阵处理(视图和投影还未进行)世界矩阵按常规旋转,移动对象。

一般,世界矩阵转换每一个顶点用同一种方法,整个网格被旋转,移动。这种方法不能应用于每个独立的单元,缺乏柔性。

看一下以下画面。

没有比这更兴奋的,不更改原始格式将上面的网格渲染成下面的样子:

顶点弯曲需要我们建立四个矩阵,及变形的几何学分布。上图用了两个矩阵,第2个矩阵主要集中于下方,所以弯曲主要在下方。


2. How to set up simple vertex blending 如何设置一个简单的顶点混合

首先要考查硬件是否支持顶点混合,对于所有硬件每个顶点最多使用四个矩阵

Dim DevCaps As D3DCAPS8
D3D.GetDeviceCaps D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, DevCaps
If DevCaps.MaxVertexBlendMatrices < 2 Then
    Unload Me
    End
End If

上面代码最重要的是MaxVertexBlendMatrices这个数值,我们现在只用两个
矩阵,所以只要核查2。

Pfinal = Pvertex * W1*M1 * W2*M2 * W3*M3 * (1.0-W1-W2-W3)*M4

Pfinal代表世界空间坐标 Pvertex代表原始的顶点模型坐标,W1, W2W3是三个浮点弯曲值  M1, M2, M3 and M4代表四个矩阵。

下一布确定顶点格式,顶点格式如下描述:

Const FVF_BLENDEDVERTEX2 = (D3DFVF_XYZB1 Or D3DFVF_NORMAL Or D3DFVF_TEX1)

Private Type BLENDEDVERTEX2
    P As D3DVECTOR  '位置坐标
    B1 As Single
      ' B1指示我们顶点中定义了一维。
    N As D3DVECTOR 法向坐标
    T As D3DVECTOR2  '贴图坐标
End Type

在Direct3DDevice8.SetTransform(  )函数中定义两个矩阵D3DTS_WORLD 及 D3DTS_WORLD1。从建立的以下下方程看出,B1越靠近1,顶点受D3DTS_WORLD影响越大,B1越靠近0,顶点受D3DTS_WORLD1影响越大。

Pfinal = Pvertex * B1*D3DTS_WORLD * (1.0-B1)*D3DTS_WORLD1

还有一个问题,从.x文件读入的文件并不知道顶点混合的权重值,所以在开始读入后改为下面的顶点格式

If Not (TubeMesh.GetFVF = FVF_BLENDEDVERTEX2) Then
   
Set TubeMesh = TubeMesh.CloneMeshFVF(D3DXMESH_MANAGED, FVF_BLENDEDVERTEX2, D3DDevice)
End If

CloneMeshFVF( )会分配权重值,但它不会有任何数据,下步是最重要的-设置权重值:
Dim MeshVerts() As BLENDEDVERTEX2
ReDim MeshVerts(TubeMesh.GetNumVertices) As BLENDEDVERTEX2
D3DXMeshVertexBuffer8GetData TubeMesh, 0, TubeMesh.GetNumVertices * Len(MeshVerts(0)), 0, MeshVerts(0)
'处理每个顶点

'取得最大高度TubeMesh_YHeight

For I = 0 To TubeMesh.GetNumVertices
   
If MeshVerts(I).P.Y > TubeMesh_YHeight Then TubeMesh_YHeight = MeshVerts(I).P.Y
Next I

'计算弯曲值
For I = 0 To TubeMesh.GetNumVertices
    MeshVerts(I).B1 = Sin(((MeshVerts(I).P.Y - 0) / TubeMesh_YHeight) * (PI / 2))
Next I
'设置弯曲值
D3DXMeshVertexBuffer8SetData TubeMesh, 0, TubeMesh.GetNumVertices * Len(MeshVerts(0)), 0, MeshVerts(0)

本质上这个方法同关键贞动画是一样的 (original, extended). 早被使用。你可以改变弯曲值随你的需要,但是要记着处理1000个点可能会让程序减慢

这是渲染网格的初始化:

'D3DVBF_DISABLE - render using the standard 1-matrix transformation pipeline
'D3DVBF_0WEIGHTS - use first matrix only, but no weights specified in each vertex
'D3DVBF_1WEIGHT   - one weight value per vertex, use first two matrices
'D3DVBF_2WEIGHTS - two weight values per vertex, use first three matrices
'D3DVBF_3WEIGHTS - three weight values per vertex, use all four matrices

D3DDevice.SetRenderState D3DRS_VERTEXBLEND, D3DVBF_1WEIGHT

'standard code: tell D3D what data is being rendered next...
D3DDevice.SetVertexShader FVF_BLENDEDVERTEX2

D3DVBF_DISABLE更快点

示例代码演示了绕 (Y and Z) 旋转的结果


3. Extending this to use all 4 matrices 扩展到4矩阵

The previous section dealt with using only the first two matrix slots. Just to cover all the bases I'm going to give a quick demonstration of how you could use all 4 matrices - with these two examples you should be able to work out how to do 3-matrix blending. The downloadable sample code does not include any of the source from this point onwards, but you should see how it will easily fit in...

For using all 4 matrices we must change the FVF and the vertex declarator again:

'increase _XYZB1 to _XYZB3
Const FVF_BLENDEDVERTEX4 = (D3DFVF_XYZB3 Or D3DFVF_NORMAL Or D3DFVF_TEX1)

'add B2 and B3
Private Type BLENDEDVERTEX4
    P
As D3DVECTOR
    B1
As Single
    B2 As Single
    B3 As Single
    N As D3DVECTOR
    T
As D3DVECTOR2
End Type

By using this data format, you need to put valid matrices in all four slots: D3DTS_WORLD, D3DTS_WORLD1, D3DTS_WORLD2, D3DTS_WORLD3. The weighting of all 4 matrices is calculated as follows:
D3DTS_WORLD = B1
D3DTS_WORLD1 = B2
D3DTS_WORLD2 = B3
D3DTS_WORLD3 = (1.0 - B1 - B2 - B3)
which follows the format of the general equation stated earlier.

When you are ready to render the geometry you must use the following two lines of code:

D3DDevice.SetRenderState D3DRS_VERTEXBLEND, D3DVBF_3WEIGHTS
D3DDevice.SetVertexShader FVF_BLENDEDVERTEX4

upon doing this you will be ready to go!

4. Taking it all the way: Indexed vertex blending

Vertex blending has been available through Direct3D for several versions now, the methods discussed thus far are nothing new. However, there is one new addition to vertex blending for Direct3D8 - indexed vertex blending.

This still limits you to a maximum of 4 matrices per vertex, BUT it allows you to specify up to 256 matrices for each call. How does this work? well, it's actually very clever...

If you're an experienced DirectX programmer, you may well remember using/learning how 8-bit palletized textures work. Each pixel stores an 8bit number - this indexes to to a palette of 256 different colors, but each color in the palette is defined as a 24 bit RGB triplet. A similar idea applies here.

For each vertex we still have the blending weights (1,2 or 3 of them), but we also add a 4-byte packed index value. This 32bit long is 4 indices packed into one variable. The idea being that you can store the 3 weights as appropriate, but then use the indices to select which 4 matrices they apply to - from a list of up to 256.

Say for example you had a list of 35 matrices setup, and for a triangle of 3 vertices you wanted 11 different matrices (based on each vertices distance from a bone node for example). This wouldn't be possible using the traditional method.

For example:
V0 = matrices 1, 3, 5, 7 weights of 0.2, 0.3, 0.4
V1 = matrices 1, 3, 6, 9 weights of 0.2, 0.6, 0.1
V2 = matrices 2, 4, 6, 8 weights of 0.1, 0.05, 0.3

to use indexed vertex blending we must (again) alter the vertex structure and the FVF:

Const FVF_INDEXBLENDEDVERTEX4 = (D3DFVF_XYZB3 Or D3DFVF_LASTBETA_UBYTE4 Or _
                                                         D3DFVF_NORMAL Or D3DFVF_TEX1)

Private Type INDEXBLENDEDVERTEX4
    P
As D3DVECTOR
    B1
As Single
    B2
As Single
    B3
As Single
    IdxList
As Long
    N
As D3DVECTOR
    T
As D3DVECTOR
End Type

A useful trick for setting the four indices is to use the D3DColorARGB( ) function. Because VB doesn't have an unsigned 32bit integer variable, when bit shifting/packing the 4 indices we have to mess around with the signed bit. Using D3DColorARGB( ) avoids this problem. For example:
'//based on the data from the example above.
V(0).IdxList = D3DColorARGB(1, 3, 5, 7)
V(1).IdxList = D3DColorARGB(1, 3, 6, 9)
V(2).IdxList = D3DColorARGB(2, 4, 6, 8)

If you do use the above technique, but choose not to use 4 matrices then the color components refer to:
A=D3DTS_WORLD3
R=D3DTS_WORLD2
G=D3DTS_WORLD1
B=D3DTS_WORLD

Before you actually use indexed vertex blending you need to check hardware support. Only the more recent hardware will support any significant number of matrices - my ATI Radeon8500 supports 58 matrices (slots 0 to 57), and not the full 256 possible.

Dim DevCaps As D3DCAPS8
D3D.GetDeviceCaps D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, DevCaps
DevCaps.MaxVertexBlendMatrixIndex
'this stores the number you mustn't exceed.

When you actually want to use indexed vertex blending there are 3 things you must do:

first: create and apply each matrix. You can't apply more matrices to the device than specified in the above enumeration. The following code will allow you to set a matrix in slot "I":

D3DDevice.SetTransform D3DTS_WORLD + I, matTheMatrix
'More specifically, D3DTS_WORLD = 256, therefore, slots 256 to 511 are reserved for
'the matrix palette.

secondly, you must enable indexed vertex blending:
D3DDevice.SetRenderState D3DRS_INDEXVERTEXBLENDENABLE, 1

lastly, you must set the FVF:
D3DDevice.SetVertexShader FVF_INDEXBLENDEDVERTEX4

Having completed all these steps, all geometry rendered from now on will be probably blended based on the matrix palette indices and the weighting values.

Configuring Direct3D to let you use indexed vertex blending is not a complicated task - however, writing an algorithm that selects the correct weights and matrix indices is going to be the far harder challenge. It may well be worth writing a plugin/tool for a 3D renderer that allows an artist to specify weighting values.

One solution I have thought of - assuming you were using this technique in conjunction with skeletal animation (I would!):
assume a reasonable skeleton with 58 bones (the same number of matrices my Radeon8500 supports). We can generate a matrix for each bone node (as a trivial example - shoulder->elbow->wrist nodes) quite easily. Then, we loop through every vertex in the mesh/skin, and calculate a list of distances from the vertex to each bone-node. Sort this list so we can pop-out the 4 closest bones (this isn't a very smart method, but it would work). We can then work out as a proportional value how close each bone is to the vertex and generate 3 blending weights. Algorithm complete.

In using a solution like that the artist need only specify a skeleton animation and a mesh-skin, and the resulting animation would be a very convincing and fluid system.