源代码下载:dx10tut04.zip。
本教程介绍如何在DirectX 10编写顶点着色器和像素着色器,还会介绍如何使用顶点缓存和索引缓存,这些都是需要理解的最基本的概念。
顶点缓存
第一个要理解的概念是顶点缓存(vertex buffer)。要理解这个概念可以先看一下一个3D的球模型:
3D球模型实际上是由几百个三角形构成的,如下图所示:
每个三角形有三个点,我们称之为顶点。要绘制球模型,我们需要将所有顶点放置到一个称为顶点缓存的特殊数据数组中,之后就可以将顶点缓存发送到GPU绘制模型了。
索引缓存
索引缓存与顶点缓存密切相关,它记录的是顶点在缓存中的位置,GPU就可以使用索引快速地在顶点缓存中找到指定的顶点。索引缓存的概念类似于书中使用的索引,它可以帮助你快速地找到要想的文章。DirectX SDK文档还指出使用索引缓存还可以增加显存中顶点数据缓存的命中率。因此强烈推荐使用索引缓存提高性能。
顶点着色器
顶点着色器是一些小程序,主要用于将顶点缓存中的顶点变换到3D空间。同时它还进行另外的操作,例如计算顶点的法线。顶点着色器由GPU调用处理每个顶点。例如,一个拥有5000个多边形的模型每帧会调用15000次顶点着色器,因此如果程序的帧频为60fps的话,那么你的顶点着色器每秒会调用900,000次,所以编写有效率的顶点着色器是很重要的。
像素着色器
像素着色器是一些小程序,主要用于赋予多边形颜色。像素着色器由GPU调用处理每个可见像素。赋予颜色,光照,纹理和大多数你想施加在多边形表面的效果都是由像素着色器处理的。
HLSL
HLSL是用来编写顶点和像素着色器程序的语言。它的语法与C语言类似。HLSL程序文件是由全局变量,类型定义,顶点着色器,像素着色器和几何着色器组成的。本教程我们只编写一个非常简单的HLSL程序用来入门。
更新的框架
本教程的框架需要更新。GraphicsClass中添加了三个新类:CameraClass,ModelClass和ColorShaderClass。CameraClass生成视矩阵,它处理相机在世界空间的位置并传入shader中。ModelClass处理3D模型的几何数据。本教程中3D模型只是一个简单的三角形。ColorShaderClass负责调用HLSL shader在屏幕上绘制模型。首先让我们看一下HLSL shader编程。
Color.fx
这是我们的第一个shader程序。Shader是进行模型绘制操作的小程序。本例中的shader用HLSL编写,存储在color.fx文件中。这个shader的目的就是绘制一个三角形。
1
2
3
|
// Filename: color.fx
|
Shader中的全局变量可以让我们从外部访问并修改。例如我们可以在GraphicsClass中设置这三个矩阵,然后将它们传递到shader,这样就可以被ColorVertexShader使用了。你可以使用很多类型的变量,例如整数int或浮点数float。
1
2
3
4
5
6
|
/
// GLOBALS //
/
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
|
类似于C,我们也可以创建自己的数据类型。我们可以使用不同类型的变量使shader编程更容易更易读。本例中我们创建了包含x,y,z,w位置信息的矢量和red,green,blue,alpha颜色信息的结构体。POSITION,COLOR和SV_POSITION是语义,告知GPU变量的用途。这里必须创建两个不同的结构,因为虽然结构相同,但顶点着色器和像素着色器的语义是不同的。POSITION用于顶点着色器,而SV_POSITION用于像素着色器,COLOR两个着色器都适用。如果你想使用一个以上相同类型的变量,需要在后面添加一个数字,例如COLOR0,COLOR1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//
// TYPEDEFS //
//
struct
VertexInputType
{
float4 position : POSITION;
float4 color : COLOR;
};
struct
PixelInputType
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
|
当顶点着色器处理从顶点缓存发送的数据时由GPU调用,对每个顶点缓存中的顶点都会调用一次名为ColorVertexShader的顶点着色器程序。顶点着色器的输入必须与顶点缓存的格式和shader源文件的类型定义(本例中为VertexInputType)匹配。顶点着色器的输出发送到像素着色器。本例中输出类型叫做PixelInputType。顶点着色器首先创建了一个PixelInputType类型的变量。然后将输入的位置数据乘以世界、观察和投影矩阵。之后复制输入的颜色,然后返回输出结构体作为像素着色器的输入。因为我们只读取位置的XYZ分量,所以将输入位置的w分量手动设置为1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// Vertex Shader
PixelInputType ColorVertexShader(VertexInputType input)
{
PixelInputType output;
// Change the position vector to be 4 units for proper matrix calculations.
input.position.w = 1.0f;
// Calculate the position of the vertex against the world, view, and projection matrices.
output.position = mul(input.position, worldMatrix);
output.position = mul(output.position, viewMatrix);
output.position = mul(output.position, projectionMatrix);
// Store the input color for the pixel shader to use.
output.color = input.color;
return
output;
}
|
像素着色器绘制多边形上的每个像素。在像素着色器中使用PixelInputType作为输入,输出为float4表示最终的像素颜色。本例中我们告知像素着色器输出的颜色与输入的颜色相同。注意,像素着色器输入来自于顶点着色器的输出。
1
2
3
4
5
6
7
|
// Pixel Shader
float4 ColorPixelShader(PixelInputType input) : SV_Target
{
return
input.color;
}
|
Technique才是实际的“shader”。它用来绘制多边形和调用顶点、像素着色器。你可以将它想象成HLSL shader的main()方法。你可以设置多个pass,调用不同的顶点和像素着色器创建所需的效果。本例中我们只使用一个简单的pass,只调用一次前面定义的顶点和像素着色器。本教程不使用几何着色器。我们通过将SetVertexShader第一个参数设置为vs_4_0使顶点着色器设置为版本4.0,这样我们可以访问对应4.0版本的DirectX 10 HLSL的函数。同理也将像素着色器设置为4.0版本。
1
2
3
4
5
6
7
8
9
10
11
12
|
// Technique
technique10 ColorTechnique
{
pass pass0
{
SetVertexShader(CompileShader(vs_4_0, ColorVertexShader()));
SetPixelShader(CompileShader(ps_4_0, ColorPixelShader()));
SetGeometryShader(NULL);
}
}
|
Modelclass.h
ModelClass负责封装3D模型的几何数据。本教程中我们手动创建三角形的顶点数据,我还创建了一个顶点缓存和索引缓存用于绘制三角形。
1
2
3
4
5
6
7
8
9
10
11
12
|
// Filename: modelclass.h
#ifndef _MODELCLASS_H_ #define _MODELCLASS_H_ // // INCLUDES //
// #include &ly;d3d10.h>
#include <d3dx10math.h>
// Class name: ModelClass
class
ModelClass {
private
:
|
下面是顶点类型定义,注意这个定义需要匹配下面的ColorShaderClass中的定义。
1
2
3
4
5
6
7
8
9
10
|
struct
VertexType
{
D3DXVECTOR3 position;
D3DXVECTOR4 color;
};
public
:
ModelClass();
ModelClass(
const
ModelClass&);
~ModelClass();
|
下面两个方法处理模型顶点缓存和索引缓存的初始化和清除。Render方法将模型几何数据发送到显卡做好被shader绘制的准备。
1
2
3
4
5
6
7
8
9
10
|
bool
Initialize(ID3D10Device*);
void
Shutdown();
void
Render(ID3D10Device*);
int
GetIndexCount();
private
:
bool
InitializeBuffers(ID3D10Device*);
void
ShutdownBuffers();
void
RenderBuffers(ID3D10Device*);
|
ModelClass中的私有变量为顶点缓存和索引缓存,以及两个整数保存缓存的大小。所有DirectX 10缓存通常使用ID3D10Buffer类型,当缓存创建时,通常由一个缓存描述进行定义。
1
2
3
4
5
6
|
private
:
ID3D10Buffer *m_vertexBuffer, *m_indexBuffer;
int
m_vertexCount, m_indexCount;
};
#endif
|
Modelclass.cpp
1
2
3
4
|
// Filename: modelclass.cpp
#include "modelclass.h"
|
构造函数将顶点和索引缓存初始化为null。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
ModelClass::ModelClass()
{
m_vertexBuffer = 0;
m_indexBuffer = 0;
}
ModelClass::ModelClass(
const
ModelClass& other)
{
}
ModelClass::~ModelClass()
{
}
|
Initialize方法会调用顶点和索引缓存的初始化方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
bool
ModelClass::Initialize(ID3D10Device* device)
{
bool
result;
// Initialize the vertex and index buffer that hold the geometry for the triangle.
result = InitializeBuffers(device);
if
(!result)
{
return
false
;
}
return
true
;
}
|
Shutdown方法调用顶点和索引缓存的清除方法。
1
2
3
4
5
6
7
|
void
ModelClass::Shutdown()
{
// Release the vertex and index buffers.
ShutdownBuffers();
return
;
}
|
Render从GraphicsClass::Render方法调用。它调用RenderBuffers将顶点和索引缓存绑定到图形管线,这样shader才能绘制它们。
1
2
3
4
5
6
7
|
void
ModelClass::Render(ID3D10Device* device)
{
// Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
RenderBuffers(device);
return
;
}
|
GetIndexCount返回索引的大小,ColorShader类需要这个信息绘制模型。
1
2
3
4
|
int
ModelClass::GetIndexCount()
{
return
m_indexCount;
}
|
在InitializeBuffers方法中创建顶点和索引缓存。通常你会读取一个模型从数据中创建缓存。本教程中因为只有一个三角形,所以是手动在顶点和索引缓存中设置了三个顶点。
1
2
3
4
5
6
7
|
bool
ModelClass::InitializeBuffers(ID3D10Device* device)
{
VertexType* vertices;
unsigned
long
* indices;
D3D10_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
D3D10_SUBRESOURCE_DATA vertexData, indexData;
HRESULT
result;
|
首先创建两个临时数组保存顶点和索引数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|