文章目錄
  1. 1. Rendering pipeline in GPU
  2. 2. WavesDemo

手撸了第六章的5个例子(box/hills/skull/shapes/waves),顺便大致过了一遍第七章:因为光(Light)在《Unity-shader入门精要》已经看过了,而且我觉得那个说的更好(

总之先输出一点

Rendering pipeline in GPU

渲染管线大致分解为以下几个主要阶段:

[输入装配(IA)阶段]

Input Assembelr Stage

[顶点着色器(VS)阶段]

Vertex Shader Stage

[细分着色器]

Hull Shader Stage (外壳着色器阶段)

Tessellator Stage (曲面细分)

Domain Shader Stage (域着色器)

[几何着色器(GS)阶段]

Geometry Shader Stage

(Stream Output Stage)(流输出)

[裁剪阶段]

[光栅化(RS)阶段]

Rasterizer Stage

[像素着色器(PS)]

Pixel Shader Stage

[和输出合并器(OM)阶段]

Output Merger Stage


举例来说基本上你看到函数名字是以IA开头的,就是在进行输入装配阶段。细分着色器属于高阶阶段,目前我还木有遇到细分处理的例子。

WavesDemo

用Demo做例子,这里skull及之后的例子才用到了光栅化。最后一个例子WavesDemo由两部分组成,z=f(x, y)的山谷和波动的水面网格。

wavesdemo.gif

先不管update函数直接看draw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//MountainDemo.cpp (WavesDemo.cpp)
...
void MountainApp::DrawScene()
{
assert(md3dImmediateContext);
assert(mSwapChain);
md3dImmediateContext->ClearRenderTargetView(mRenderTargetView, reinterpret_cast<const float*>(&Colors::LightSteelBlue));
md3dImmediateContext->ClearDepthStencilView(mDepthStencilView, D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL, 1.0f, 0);
md3dImmediateContext->IASetInputLayout(mInputLayout);
md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
UINT strike = sizeof(Vertex);
UINT offset = 0;
//XMATRIX mxfWorldViewProj
XMMATRIX view = XMLoadFloat4x4(&mView);
XMMATRIX proj = XMLoadFloat4x4(&mProj);
D3DX11_TECHNIQUE_DESC techDesc;
mTech->GetDesc(&techDesc);
for ( UINT p = 0; p < techDesc.Passes; ++p)
{
// the land
md3dImmediateContext->IASetVertexBuffers(0, 1, &mLandVB, &strike, &offset);
md3dImmediateContext->IASetIndexBuffer(mLandIB, DXGI_FORMAT_R32_UINT, 0);
XMMATRIX world = XMLoadFloat4x4(&mGridWorld);
XMMATRIX worldViewProj = world*view*proj;
mfxWorldViewProj->SetMatrix(reinterpret_cast<float*>(&worldViewProj));
mTech->GetPassByIndex(p)->Apply(0, md3dImmediateContext);
md3dImmediateContext->DrawIndexed(mGridIndexCount, 0, 0);
// the waves
md3dImmediateContext->RSSetState(mWireframeRS); // wf set
md3dImmediateContext->IASetVertexBuffers(0, 1, &mWavesVB, &strike, &offset);
md3dImmediateContext->IASetIndexBuffer(mWavesIB, DXGI_FORMAT_R32_UINT, 0);
world = XMLoadFloat4x4(&mWavesWorld);
worldViewProj = world*view*proj;
mfxWorldViewProj->SetMatrix(reinterpret_cast<float*>(&worldViewProj));
mTech->GetPassByIndex(p)->Apply(0, md3dImmediateContext);
md3dImmediateContext->DrawIndexed(3*mWaves.TriangleCount(), 0, 0);
md3dImmediateContext->RSSetState(0); // wf reset
}
HR(mSwapChain->Present(0, 0));
}
...

mTech是fx文件(HLSL,包含VS和PS)编译后赋值的私有ID3DX11EffectTechnique*成员

mVB和mIB(根据对象取别名)是私有函数BuildGeometryBuffers(根据对象取别名)的最终产物(存放顶点和索引缓冲,这是三角形带的一种表示方法)

draw函数依赖于md3dImmediateContext这个上下文环境(和以前写WebCanvas类似),按照流程很明显的就是 IA->IA(VS/PS)->IA(GS)->IA(RS)->DrawIndexed 尽管顺序略有不同,但是基本上包含了所有阶段需要的参数。细分着色器中部分是DirectX11的新特性,目前还没有用到

接着看update部分(即mainloop),这里涉及到动画处理,如果涉及过设计模式会好理解很多,这是典型的序列型模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//MountainDemo.cpp
...
void MountainApp::UpdateScene(float dt)
{
// Convert Spherical to Cartesian coordinates.
float x = mRadius*sinf(mPhi)*cosf(mTheta);
float z = mRadius*sinf(mPhi)*sinf(mTheta);
float y = mRadius*cosf(mPhi);
// Build the view matrix.
XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
XMVECTOR target = XMVectorZero();
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
XMMATRIX V = XMMatrixLookAtLH(pos, target, up);
XMStoreFloat4x4(&mView, V);
//
// Every quarter second, generate a random wave.
//
static float t_base = 0.0f;
if( (mTimer.TotalTime() - t_base) >= 0.25f )
{
t_base += 0.25f;
DWORD i = 5 + rand() % 190;
DWORD j = 5 + rand() % 190;
float r = MathHelper::RandF(1.0f, 2.0f);
mWaves.Disturb(i, j, r);
}
mWaves.Update(dt);
//
// Update the wave vertex buffer with the new solution.
//
D3D11_MAPPED_SUBRESOURCE mappedData;
HR(md3dImmediateContext->Map(mWavesVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));
Vertex* v = reinterpret_cast<Vertex*>(mappedData.pData);
for(UINT i = 0; i < mWaves.VertexCount(); ++i)
{
v[i].Pos = mWaves[i];
v[i].Color = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
}
md3dImmediateContext->Unmap(mWavesVB, 0);
}
...

update的前面部分是把球坐标转换成直角坐标,来构筑mView(最终worldViewProj = world*view*proj,记得这个公式),中后部分用于处理水面动画,Waves对象使用了设计模式中更新方法,设计一个update的方法,把mainloop中的deltatime作为参数传入子部件的update中,以保持同步的时间步长,后面的一个for主要是将waves同步的数据更新到mVB中,先来看看waves的update是如何设计的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//Waves.cpp
...
void Waves::Update(float dt)
{
static float t = 0;
// Accumulate time.
t += dt;
// Only update the simulation at the specified time step.
if( t >= mTimeStep )
{
// Only update interior points; we use zero boundary conditions.
for(DWORD i = 1; i < mNumRows-1; ++i)
{
for(DWORD j = 1; j < mNumCols-1; ++j)
{
// After this update we will be discarding the old previous
// buffer, so overwrite that buffer with the new update.
// Note how we can do this inplace (read/write to same element)
// because we won't need prev_ij again and the assignment happens last.
// Note j indexes x and i indexes z: h(x_j, z_i, t_k)
// Moreover, our +z axis goes "down"; this is just to
// keep consistent with our row indices going down.
mPrevSolution[i*mNumCols+j].y =
mK1*mPrevSolution[i*mNumCols+j].y +
mK2*mCurrSolution[i*mNumCols+j].y +
mK3*(mCurrSolution[(i+1)*mNumCols+j].y +
mCurrSolution[(i-1)*mNumCols+j].y +
mCurrSolution[i*mNumCols+j+1].y +
mCurrSolution[i*mNumCols+j-1].y);
}
}
// We just overwrote the previous buffer with the new data, so
// this data needs to become the current solution and the old
// current solution becomes the new previous solution.
std::swap(mPrevSolution, mCurrSolution);
t = 0.0f; // reset time
}
}
...

典型的双缓冲,避免同时读写同一块内存。这里注意到三个mk参数,找到其赋值的init函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//Waves.cpp
...
void Waves::Init(UINT m, UINT n, float dx, float dt, float speed, float damping)
{
// mWaves.Init(200, 200, 0.8f, 0.03f, 3.25f, 0.4f);
mNumRows = m;
mNumCols = n;
mVertexCount = m*n;
mTriangleCount = (m-1)*(n-1)*2;
mTimeStep = dt; // 时间步长
mSpatialStep = dx; // 空间步长
float d = damping*dt+2.0f; // 阻尼系数(速度?)*dt + 常数
float e = (speed*speed)*(dt*dt)/(dx*dx); // pow((水波速度 / dv), 2)
mK1 = (damping*dt-2.0f)/ d; // (d(dt) - a0) / d(dt)
mK2 = (4.0f-8.0f*e) / d; // (a0 - 2 * a0 * e(dt)) / d(dt)
mK3 = (2.0f*e) / d; // (2 * a0) / d(dt) / 4
// In case Init() called again.
delete[] mPrevSolution;
delete[] mCurrSolution;
mPrevSolution = new XMFLOAT3[m*n];
mCurrSolution = new XMFLOAT3[m*n];
// Generate grid vertices in system memory.
float halfWidth = (n-1)*dx*0.5f; // 这样看来dx = mSpatialStep = 网格边长
float halfDepth = (m-1)*dx*0.5f;
for(UINT i = 0; i < m; ++i)
{
float z = halfDepth - i*dx;
for(UINT j = 0; j < n; ++j)
{
float x = -halfWidth + j*dx;
mPrevSolution[i*n+j] = XMFLOAT3(x, 0.0f, z);
mCurrSolution[i*n+j] = XMFLOAT3(x, 0.0f, z);
}
}
}
...

理论上水波模型一般使用正弦函数,这里用了近似的函数组合来模拟,水波的某一点取决于上一帧的状态和周围四格的状态。

将 speed=3.25, damping=0.4, dx=0.8 带入得:

mK1 = ( 0.4 * dt - 2.0 ) / ( 0.4 * dt + 2.0 )
mK2 = ( 4.0 - 132.0 * dt * dt )  / ( 0.4 * dt + 2.0 )

研究了一会之后,我只能说这是经验公式,这样只有前两个水波比较明显(由函数图像可得)

文章目錄
  1. 1. Rendering pipeline in GPU
  2. 2. WavesDemo