使用Bezier曲面渲染飘动的红旗
本例中我们使用的纹理如下:
Bezier曲线大家应该都很熟悉了,Bezier曲线由4个控制点定义,Bezier曲线的数学定义为:
其中,p0~p3定义了4个控制点,b0~b3为伯恩斯坦多项式的项,s的范围为0.0~1.0从而覆盖了曲线上的所有点,如图:
伯恩斯坦多项式的形式如下:
在3D世界中,我们需要用Bezier曲面来代替Bezier曲线,由Bezier曲线扩展到Bezier曲面是十分简单的,只需将4个控制点扩展为4*4控制网格即可,Bezier曲面的数学定义如下:
其中,p0~p15代表4*4控制网格上的16个控制点,b0~b3与Bezier曲线一样,代表伯恩斯坦多项式的项,s与t代表2个方向,范围为0.0~1.0从而覆盖了曲面上的所有点。
关于Bezier曲面的知识了解这些就够了,我们就可以使用它来渲染飘动的红旗了,但是在渲染时,我们还有一个问题需要考虑,就是法线的问题,该如何求曲面上的点的法线呢?
我们知道,导数描述了任一给定点处的曲线的斜率,下图描述了及其导数的图形表示:
所以,我们只要使用Bezier曲线的导数形式就可以求出对于Bezier曲面上的任一给定点在s和t两个方向上的曲线的斜率,然后使用向量叉积就求得了法线,如图:
有许多求导数的方法,在这里我们使用最简单的形式,我们定义:
的导数形式为。记住,常数的导数为0。
根据如上的定义,我们就可以得到伯恩斯坦多项式的项的导数的定义:
好了,至此我们已经了解了所有所需的知识,接下来,就是用代码实现的时刻了^-^!
首先我们定义x,z方向上顶点的数量:
1 const int VERTEX_ROW_COUNT = 20;
2 const int VERTEX_COL_COUNT = 20;
接着我们定义顶点结构:
1 struct BEZIER_VERTEX
2 {
3 float x, y, z; // 顶点坐标
4 float u, v; // 纹理坐标
5 float Bs0, Bs1, Bs2, Bs3; // 关于s的Bernstein多项式的4项的值
6 float Bt0, Bt1, Bt2, Bt3; // 关于t的Bernstein多项式的4项的值
7 float dBs0, dBs1, dBs2, dBs3; // 关于s的Bernstein多项式的4项的导数的值
8 float dBt0, dBt1, dBt2, dBt3; // 关于t的Bernstein多项式的4项的导数的值
9
10 static LPDIRECT3DVERTEXDECLARATION9 pVertexDecl;
11 };
其中,Bs0~Bs3定义某个顶点所对应在s方向上的伯恩斯坦多项式的项的值,而Bt0~Bt3则定义某个顶点所对应在t方向上的伯恩斯坦多项式的项的值。dBs0~dBs3则是s方向上相应的伯恩斯坦多项式的项的导数的值,而dBt0~dBt3则是t方向上相应的伯恩斯坦多项式的项的导数的值。
顶点声明定义如下,我们把数据当做纹理传入顶点着色器:
1 D3DVERTEXELEMENT9 BezierVertexElements[] =
2 {
3 { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
4 { 0, 3*sizeof(float), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
5 { 0, 5*sizeof(float), D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1 },
6 { 0, 9*sizeof(float), D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 2 },
7 { 0, 13*sizeof(float), D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 3 },
8 { 0, 17*sizeof(float), D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 4 },
9
10 D3DDECL_END(),
11 };
12
13 if ( FAILED(m_pD3DDevice->CreateVertexDeclaration(BezierVertexElements, &BEZIER_VERTEX::pVertexDecl)) )
14 {
15 return E_FAIL;
16 }
为了方便起见,我们创建了在x,z方向上范围在0.0~1.0的顶点数据,这样的话,我们就不需要在将顶点数据规范化到0.0~1.0后再进行计算,这只是一种偷懒的方法^-^。接着,我们填入了顶点数据以及索引数据:
1 if ( FAILED(m_pD3DDevice->CreateVertexBuffer(VERTEX_ROW_COUNT*VERTEX_COL_COUNT*sizeof(BEZIER_VERTEX),
2 D3DUSAGE_WRITEONLY,
3 0,
4 D3DPOOL_MANAGED,
5 &m_pFlagVB,
6 NULL)) )
7 {
8 return E_FAIL;
9 }
10
11 int nTriangleCount = (VERTEX_ROW_COUNT - 1) * (VERTEX_COL_COUNT - 1) * 2;
12 if ( FAILED(m_pD3DDevice->CreateIndexBuffer(nTriangleCount * 3 * sizeof(WORD),
13 D3DUSAGE_WRITEONLY,
14 D3DFMT_INDEX16,
15 D3DPOOL_MANAGED,
16 &m_pFlagIB,
17 NULL)) )
18 {
19 return E_FAIL;
20 }
21 //////////////////////////////////////////////////////////////////////////
22
23 BEZIER_VERTEX *pVertices = NULL;
24 m_pFlagVB->Lock( 0, 0, reinterpret_cast<void**>(&pVertices), 0 );
25
26 WORD *pIndices = NULL;
27 m_pFlagIB->Lock( 0, 0, reinterpret_cast<void**>(&pIndices), 0 );
28
29 float fRowStride = 1.0f / (VERTEX_ROW_COUNT - 1);
30 float fColStride = 1.0f / (VERTEX_COL_COUNT - 1);
31 int nIndex = 0;
32 for ( int nRowIndex = 0; nRowIndex < VERTEX_ROW_COUNT; ++nRowIndex )
33 {
34 for ( int nColIndex = 0; nColIndex < VERTEX_COL_COUNT; ++nColIndex )
35 {
36 // 写入顶点数据
37 int nVertexIndex = VERTEX_COL_COUNT*nRowIndex + nColIndex;
38
39 pVertices[nVertexIndex].x = nColIndex * fColStride;
40 pVertices[nVertexIndex].y = 0.0f;
41 pVertices[nVertexIndex].z = nRowIndex * fRowStride;
42
43 pVertices[nVertexIndex].u = pVertices[nVertexIndex].x;
44 pVertices[nVertexIndex].v = pVertices[nVertexIndex].z;
45
46 float fS = pVertices[nVertexIndex].x;
47 float fT = pVertices[nVertexIndex].z;
48
49 pVertices[nVertexIndex].Bs0 = (1.0f - fS) * (1.0f - fS) * (1.0f - fS);
50 pVertices[nVertexIndex].Bs1 = 3.0f * fS * (1.0f - fS) * (1.0f - fS);
51 pVertices[nVertexIndex].Bs2 = 3.0f * fS * fS * (1.0f - fS);
52 pVertices[nVertexIndex].Bs3 = fS * fS * fS;
53
54 pVertices[nVertexIndex].Bt0 = (1.0f - fT) * (1.0f - fT) * (1.0f - fT);
55 pVertices[nVertexIndex].Bt1 = 3.0f * fT * (1.0f - fT) * (1.0f - fT);
56 pVertices[nVertexIndex].Bt2 = 3.0f * fT * fT * (1.0f - fT);
57 pVertices[nVertexIndex].Bt3 = fT * fT * fT;
58
59 pVertices[nVertexIndex].dBs0 = -3.0f * (1.0f - fS) * (1.0f - fS);
60 pVertices[nVertexIndex].dBs1 = 3.0f - (12.0f * fS) + (9.0f * fS * fS);
61 pVertices[nVertexIndex].dBs2 = (6.0f * fS) - (9.0f * fS * fS);
62 pVertices[nVertexIndex].dBs3 = 3.0f * fS * fS;
63
64 pVertices[nVertexIndex].dBt0 = -3.0f * (1.0f - fT) * (1.0f - fT);
65 pVertices[nVertexIndex].dBt1 = 3.0f - (12.0f * fT) + (9.0f * fT * fT);
66 pVertices[nVertexIndex].dBt2 = (6.0f * fT) - (9.0f * fT * fT);
67 pVertices[nVertexIndex].dBt3 = 3.0f * fT * fT;
68 //////////////////////////////////////////////////////////////////////////
69
70 /*
71 写入索引数据,索引格式:
72 2 3,5
73 1,4 6
74 */
75 if ( nRowIndex != (VERTEX_ROW_COUNT-1) )
76 {
77 if ( nColIndex != (VERTEX_COL_COUNT-1) )
78 {
79 pIndices[nIndex++] = nRowIndex*VERTEX_COL_COUNT + nColIndex;
80 pIndices[nIndex++] = (nRowIndex+1)*VERTEX_COL_COUNT + nColIndex;
81 pIndices[nIndex++] = (nRowIndex+1)*VERTEX_COL_COUNT + nColIndex + 1;
82
83 pIndices[nIndex++] = nRowIndex*VERTEX_COL_COUNT + nColIndex;
84 pIndices[nIndex++] = (nRowIndex+1)*VERTEX_COL_COUNT + nColIndex + 1;
85 pIndices[nIndex++] = nRowIndex*VERTEX_COL_COUNT + nColIndex + 1;
86 }
87 }
88 }
89 }
90
91 m_pFlagIB->Unlock();
92 m_pFlagVB->Unlock();
93
94 return S_OK;
在顶点着色器中,首先我们定义数据:
1 vs.1.1
2
3 dcl_position v0
4 dcl_texcoord0 v1
5 dcl_texcoord1 v7
6 dcl_texcoord2 v8
7 dcl_texcoord3 v9
8 dcl_texcoord4 v10
9
10 // v7 - 关于s的Bernstein多项式的4项的值
11 // v8 - 关于t的Bernstein多项式的4项的值
12 mov r7, v7
13 mov r8, v8
14
15 // v9 - 关于s的用于求切线的导数的值
16 // v10 - 关于t的用于求切线的导数的值
17 mov r9, v9
18 mov r10, v10
接着,我们使用常量寄存器c10~c25表示4*4控制网格的16个控制点,我们计算在每个控制点的作用下,顶点的位置,然后与前一个控制点计算出的顶点的位置进行向量相加。同时,我们计算在每个控制点的作用下,两个方向上的斜率,然后再与前一个控制点计算出的两个方向上的斜率进行向量相加:
1 // c10 - s0t0控制点
2 mul r0.x, r7.x, r8.x
3 mul r1, c10, r0.x
4
5 mul r2, c10, r9.x
6 mul r3, c10, r10.x
7
8 // c11 - s0t1控制点
9 mul r0.x, r7.x, r8.y
10 mad r1, c11, r0.x, r1
11
12 mad r2, c11, r9.x, r2
13 mad r3, c11, r10.y, r3
14
15 // c12 - s0t2控制点
16 mul r0.x, r7.x, r8.z
17 mad r1, c12, r0.x, r1
18
19 mad r2, c12, r9.x, r2
20 mad r3, c12, r10.z, r3
21
22 // c13 - s0t3控制点
23 mul r0.x, r7.x, r8.w
24 mad r1, c13, r0.x, r1
25
26 mad r2, c13, r9.x, r2
27 mad r3, c13, r10.w, r3
28
29 // c14 - s1t0控制点
30 mul r0.x, r7.y, r8.x
31 mad r1, c14, r0.x, r1
32
33 mad r2, c14, r9.y, r2
34 mad r3, c14, r10.x, r3
35
36 // c15 - s1t1控制点
37 mul r0.x, r7.y, r8.y
38 mad r1, c15, r0.x, r1
39
40 mad r2, c15, r9.y, r2
41 mad r3, c15, r10.y, r3
42
43 // c16 - s1t2控制点
44 mul r0.x, r7.y, r8.z
45 mad r1, c16, r0.x, r1
46
47 mad r2, c16, r9.y, r2
48 mad r3, c16, r10.z, r3
49
50 // c17 - s1t3控制点
51 mul r0.x, r7.y, r8.w
52 mad r1, c17, r0.x, r1
53
54 mad r2, c17, r9.y, r2
55 mad r3, c17, r10.w, r3
56
57 // c18 - s2t0控制点
58 mul r0.x, r7.z, r8.x
59 mad r1, c18, r0.x, r1
60
61 mad r2, c18, r9.z, r2
62 mad r3, c18, r10.x, r3
63
64 // c19 - s2t1控制点
65 mul r0.x, r7.z, r8.y
66 mad r1, c19, r0.x, r1
67
68 mad r2, c19, r9.z, r2
69 mad r3, c19, r10.y, r3
70
71 // c20 - s2t2控制点
72 mul r0.x, r7.z, r8.z
73 mad r1, c20, r0.x, r1
74
75 mad r2, c20, r9.z, r2
76 mad r3, c20, r10.z, r3
77
78 // c21 - s2t3控制点
79 mul r0.x, r7.z, r8.w
80 mad r1, c21, r0.x, r1
81
82 mad r2, c21, r9.z, r2
83 mad r3, c21, r10.w, r3
84
85 // c22 - s3t0控制点
86 mul r0.x, r7.w, r8.x
87 mad r1, c22, r0.x, r1
88
89 mad r2, c22, r9.w, r2
90 mad r3, c22, r10.x, r3
91
92 // c23 - s3t1控制点
93 mul r0.x, r7.w, r8.y
94 mad r1, c23, r0.x, r1
95
96 mad r2, c23, r9.w, r2
97 mad r3, c23, r10.y, r3
98
99 // c24 - s3t2控制点
100 mul r0.x, r7.w, r8.z
101 mad r1, c24, r0.x, r1
102
103 mad r2, c24, r9.w, r2
104 mad r3, c24, r10.z, r3
105
106 // c25 - s3t3控制点
107 mul r0.x, r7.w, r8.w
108 mad r1, c25, r0.x, r1
109
110 mad r2, c25, r9.w, r2
111 mad r3, c25, r10.w, r3
最后,我们对最终的两个方向上的斜率进行叉积,计算法线,然后变换最后计算完毕的顶点坐标,同时传递纹理数据:
1 // 计算法线 = 2个方向上切线的叉积
2 mul r6, r3.yzxw, r2.zxyw
3 mad r6, -r2.yzxw, r3.zxyw, r6
4
5 // 单位化法线
6 dp3 r5.w, r6, r6
7 rsq r5.w, r5.w
8 mul r6, r6, r5.w
9
10 // c5 - 光的方向
11 dp3 oD0, r6, -c5
12
13 // c0 ~ c3 - 世界变换 + 视图变换 + 投影变换矩阵
14 dp4 oPos.x, r1, c0
15 dp4 oPos.y, r1, c1
16 dp4 oPos.z, r1, c2
17 dp4 oPos.w, r1, c3
18
19 mov oT0, v1
在应用程序中,我使用了一些三角函数来计算4*4控制网格的数据,大家可以随意修改看看不同的效果:
1 float fWrap1 = 2.0f * sin( GetTickCount()*0.001f );
2 float fWrap2 = 2.0f * cos( GetTickCount()*0.001f );
3
4 D3DXVECTOR4 v4ControlS0T0( 0.0f, 0.0f, 0.0f, 1.0f );
5 D3DXVECTOR4 v4ControlS0T1( 0.0f, 0.0f, 0.33f, 1.0f );
6 D3DXVECTOR4 v4ControlS0T2( 0.0f, 0.0f, 0.66f, 1.0f );
7 D3DXVECTOR4 v4ControlS0T3( 0.0f, 0.0f, 1.0f, 1.0f );
8
9 D3DXVECTOR4 v4ControlS1T0( 0.33f, 0.0f, 0.0f, 1.0f );
10 D3DXVECTOR4 v4ControlS1T1( 0.33f, 0.15f*fWrap1 + 0.15f*fWrap2, 0.33f, 1.0f );
11 D3DXVECTOR4 v4ControlS1T2( 0.33f, -0.15f*fWrap1 + 0.33f*fWrap2, 0.66f, 1.0f );
12 D3DXVECTOR4 v4ControlS1T3( 0.33f, 0.0f, 1.0f, 1.0f );
13
14 D3DXVECTOR4 v4ControlS2T0( 0.66f, 0.0f, 0.0f, 1.0f );
15 D3DXVECTOR4 v4ControlS2T1( 0.66f, 0.33f*fWrap1 + 0.15f*fWrap2, 0.33f, 1.0f );
16 D3DXVECTOR4 v4ControlS2T2( 0.66f, -0.33f*fWrap1 + 0.33f*fWrap2, 0.66f, 1.0f );
17 D3DXVECTOR4 v4ControlS2T3( 0.66f, 0.0f, 1.0f, 1.0f );
18
19 D3DXVECTOR4 v4ControlS3T0( 1.0f, 0.25f*fWrap2, 0.0f, 1.0f );
20 D3DXVECTOR4 v4ControlS3T1( 1.0f, 0.33f*fWrap1 + 0.33f*fWrap2, 0.33f, 1.0f );
21 D3DXVECTOR4 v4ControlS3T2( 1.0f, -0.33f*fWrap1 + 0.33f*fWrap2, 0.66f, 1.0f );
22 D3DXVECTOR4 v4ControlS3T3( 1.0f, 0.25f*fWrap2, 1.0f, 1.0f );
23
24 m_pD3DDevice->SetVertexShaderConstantF( 10, v4ControlS0T0, 1 );
25 m_pD3DDevice->SetVertexShaderConstantF( 11, v4ControlS0T1, 1 );
26 m_pD3DDevice->SetVertexShaderConstantF( 12, v4ControlS0T2, 1 );
27 m_pD3DDevice->SetVertexShaderConstantF( 13, v4ControlS0T3, 1 );
28 m_pD3DDevice->SetVertexShaderConstantF( 14, v4ControlS1T0, 1 );
29 m_pD3DDevice->SetVertexShaderConstantF( 15, v4ControlS1T1, 1 );
30 m_pD3DDevice->SetVertexShaderConstantF( 16, v4ControlS1T2, 1 );
31 m_pD3DDevice->SetVertexShaderConstantF( 17, v4ControlS1T3, 1 );
32 m_pD3DDevice->SetVertexShaderConstantF( 18, v4ControlS2T0, 1 );
33 m_pD3DDevice->SetVertexShaderConstantF( 19, v4ControlS2T1, 1 );
34 m_pD3DDevice->SetVertexShaderConstantF( 20, v4ControlS2T2, 1 );
35 m_pD3DDevice->SetVertexShaderConstantF( 21, v4ControlS2T3, 1 );
36 m_pD3DDevice->SetVertexShaderConstantF( 22, v4ControlS3T0, 1 );
37 m_pD3DDevice->SetVertexShaderConstantF( 23, v4ControlS3T1, 1 );
38 m_pD3DDevice->SetVertexShaderConstantF( 24, v4ControlS3T2, 1 );
39 m_pD3DDevice->SetVertexShaderConstantF( 25, v4ControlS3T3, 1 );
好了,让我们来欣赏我们的成果吧^-^: