HLSL-High Level Shader Language
优点
用来书写Vertex Shader和Pixel Shader程序的代码,语法类似于C/C++,在DirectX 8.x的时代,Shader程序都是用低级Shader汇编语言编写的,姑且称之为LLSL吧,HLSL与之相比具有以下优点:
- 更高的生产力,使用HLSL编程更快更容易,使我们有更多的时间关注与算法而不是编码
- 更好的可读性,使用HLSH编写的程序更易读,易调试及维护
- 编译器将生成更加高效的汇编代码
- 可以将同一份代码编译成任何可用的Shader版本,但是用LLSL则必须按不同版本编写代码
如果你的显卡不支持Shader的话,那么可以使用REF Device模拟,这样可以得到正确的结果,但是速度非常慢
编写Shader代码
你可以将Shader代码写入到你的程序中,但是为了更方便,更模块化,通常将Shader程序放在单独的文件里,比如记事本,然后使用D3DXCompileShaderFromFile函数来编译它,使用记事本写Shader程序可能不太方便,以前微软提供编写Shader的工具,在DirectX SDK当中,但是现在不支持了,大家可以用AMD的RenderMonkey来编写。
一般的Shader程序包含以下几个部分
- 全局变量-viewprojmatrix, color等
- 输入结构和输出结构-VS_INPUT, VS_OUTPUT
- 入口函数-Main, note the name is not mandatory, you can used any valid function name
常量表
每个Shader程序都有一个常量表来存储它的变量。可以用ID3DXConstantTable接口访问长量表,可以设置Shader程序中的全局变量值
实战-一个简单的Shader程序
下面我们就来看看如何编写一个Shader程序,我们还以茶壶为例,看看如何用Shader绘制一个蓝色的茶壶,学习如何用Shader处理颜色,这个例子简单的不能再简单,但是,它确实能反映如何使用Shader。首先用VS建立一个Win32工程,添加必要的框架代码,主要是创建窗口,简单的消息处理以及初始化D3D等,如下
1 #include <d3dx9.h>
2
3 LPDIRECT3D9 g_pD3D = NULL ; // Used to create the D3DDevice
4 LPDIRECT3DDEVICE9 g_pd3dDevice = NULL ; // Our rendering device
5
6 HRESULT InitD3D( HWND hWnd )
7 {
8 // Create the D3D object, which is needed to create the D3DDevice.
9 if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
10 return E_FAIL;
11
12 D3DPRESENT_PARAMETERS d3dpp;
13 ZeroMemory( &d3dpp, sizeof(d3dpp) );
14 d3dpp.Windowed = TRUE;
15 d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
16 d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
17
18 // Create device
19 if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
20 D3DCREATE_SOFTWARE_VERTEXPROCESSING,
21 &d3dpp, &g_pd3dDevice ) ) )
22 {
23 return E_FAIL;
24 }
25
26 return S_OK;
27 }
28
29 VOID Cleanup()
30 {
31 if( g_pd3dDevice != NULL)
32 g_pd3dDevice->Release();
33
34 if( g_pD3D != NULL)
35 g_pD3D->Release();
36 }
37
38 VOID Render()
39 {
40 if( NULL == g_pd3dDevice )
41 return;
42
43 // Clear the back-buffer to a RED color
44 g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );
45
46 // Begin the scene
47 if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
48 {
49 // Rendering of scene objects can happen here
50
51 // End the scene
52 g_pd3dDevice->EndScene();
53 }
54
55 // Present the back-buffer contents to the display
56 g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
57 }
58
59 LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
60 {
61 switch( msg )
62 {
63 case WM_KEYDOWN:
64 {
65 switch( wParam )
66 {
67 case VK_ESCAPE:
68 SendMessage( hWnd, WM_CLOSE, 0, 0 );
69 break ;
70 default:
71 break ;
72 }
73 }
74 break ;
75
76 case WM_DESTROY:
77 Cleanup();
78 PostQuitMessage( 0 );
79 return 0;
80 }
81
82 return DefWindowProc( hWnd, msg, wParam, lParam );
83 }
84
85 INT WINAPI wWinMain( HINSTANCE hInst, HINSTANCE, LPWSTR, INT )
86 {
87 // Register the window class
88 WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
89 GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
90 L"D3D Tutorial", NULL };
91 RegisterClassEx( &wc );
92
93 // Create the application‘s window
94 HWND hWnd = CreateWindow( L"D3D Tutorial", L"D3D Tutorial 01: CreateDevice",
95 WS_OVERLAPPEDWINDOW , 0, 0, 800, 600,
96 NULL, NULL, wc.hInstance, NULL );
97
98 // Initialize Direct3D
99 if( SUCCEEDED( InitD3D( hWnd ) ) )
100 {
101 // Show the window
102 ShowWindow( hWnd, SW_SHOWDEFAULT );
103 UpdateWindow( hWnd );
104
105 // Enter the message loop
106 MSG msg ;
107 ZeroMemory( &msg, sizeof(msg) );
108 PeekMessage( &msg, NULL, 0U, 0U, PM_NOREMOVE );
109
110 while (msg.message != WM_QUIT)
111 {
112 if( PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE) != 0)
113 {
114 TranslateMessage (&msg) ;
115 DispatchMessage (&msg) ;
116 }
117 else // Render the game if no message to process
118 {
119 Render() ;
120 }
121 }
122 }
123
124 UnregisterClass( L"D3D Tutorial", wc.hInstance );
125 return 0;
126 }
然后按照下面的步骤绘制一个茶壶
1.在程序头部添加茶壶对应的全局变量
1 ID3DXMesh* g_pMesh = NULL ;
2.在InitD3D函数尾部(返回语句之前)创建绘制茶壶的代码
1 D3DXCreateTeapot(g_pd3dDevice, &g_pMesh, NULL) ;
2
3. 在Render函数中添加绘制茶壶的代码,位于BeginScene和EndScene之间
1 g_pMesh->DrawSubset(0) ;
4.在Cleanup函数中清除茶壶对应的mesh
1 if(g_pMesh != NULL)
2 g_pMesh->Release() ;
3
下面编写Shader程序,打开记事本,输入如下代码,代码很简单-略加解释,ViewProjMatrix对应是view matrix和projection matrix的乘积,Blue是顶点颜色,input只包含一个信息,顶点位置,output包含两个信息,顶点的位置和颜色,在main函数中,将顶点的位置由local坐标系变换到投影空间,将颜色设置为蓝色,就这么简单。
1
2 matrix ViewProjMatrix;
3 vector Blue = {0.0f, 0.0f, 1.0f, 0.0f} ;
4
5 struct VS_INPUT
6 {
7 vector position : POSITION;
8 };
9
10 struct VS_OUTPUT
11 {
12 vector position : POSITION;
13 vector diffuse : COLOR;
14 };
15
16
17 VS_OUTPUT Main(VS_INPUT input)
18 {
19 VS_OUTPUT output = (VS_OUTPUT)0;
20
21 output.position = mul(input.position, ViewProjMatrix);
22
23 output.diffuse = Blue ;
24
25 return output;
26 }
27
28
回到主程序中,添加全局变量g_pVertexShader用来保存创建的shader
1 IDirect3DVertexShader9* g_pVertexShader = NULL ;
添加全局变量g_pConstantTable用来保存Shader对应的常量表,有了常量表就可以存取和修改shader中的变量
1 ID3DXConstantTable* g_pConstantTable = NULL ;
添加一个变量句柄来存取shader中对应的ViewProjMatrix
1 D3DXHANDLE ViewProjMatrixHanle = 0 ;
添加函数PrepareShader,这个函数负责从文件编译shader,创建shader,设置shader中的变量等
1 bool PrepareShader()
2 {
3 ID3DXBuffer *shader ;
4 ID3DXBuffer *errorBuffer ;
5
6 // Compile shader from file
7 HRESULT hr = D3DXCompileShaderFromFileA("Shader.txt", 0, 0, "Main", "vs_1_1",
8 D3DXSHADER_ENABLE_BACKWARDS_COMPATIBILITY, &shader, &errorBuffer, &g_pConstantTable) ;
9
10 // error handling
11 // output compile error message
12 if( errorBuffer )
13 {
14 MessageBoxA(0, (char*)errorBuffer->GetBufferPointer(), 0, 0);
15 errorBuffer->Release() ;
16 }
17
18 // compile failed
19 if(FAILED(hr))
20 {
21 MessageBox(0, L"D3DXCompileShaderFromFile() - FAILED", 0, 0);
22 return false;
23 }
24
25 // Create shader
26 hr = g_pd3dDevice->CreateVertexShader((DWORD*)shader->GetBufferPointer(), &g_pVertexShader) ;
27
28 // create failed
29 if(FAILED(hr))
30 {
31 MessageBox(0, L"CreateVertexShader - FAILED", 0, 0);
32 return false;
33 }
34
35 shader->Release() ;
36
37 // map handle to variable in shader
38 ViewProjMatrixHanle = g_pConstantTable->GetConstantByName(0, "ViewProjMatrix") ;
39 }
40
最后一行将shader中的变量ViewProjMatrix与ViewProjMatrixHandle相关联,这样接下来就可以用ViewProjMatrixHandle来存取和设置ViewProjMatrix了。注意,GetConstantByName函数的第二个参数是一个字符串,用来表示要获取的变量名字,这个名字必须与Shader文件中指定的名字一样才行,否则的话不起作用,也就是说Shader文件中一定有一个名为ViewProjectMatrix的变量。
添加函数SetupMatrix
这个函数负责设置view matrix, projection matrix, 并将二者的乘积赋值给shader中的ViewProjMatrix以便完成shader中的运算
1 void SetupMatrix()
2 {
3 // set view
4 D3DXVECTOR3 eyePt(0.0f, 0.0f, -10.0f) ;
5 D3DXVECTOR3 upVec(0.0f, 1.0f, 0.0f) ;
6 D3DXVECTOR3 lookCenter(0.0f, 0.0f, 0.0f) ;
7
8 D3DXMATRIX view ;
9 D3DXMatrixLookAtLH(&view, &eyePt, &lookCenter, &upVec) ;
10
11 // set projection
12 D3DXMATRIX proj ;
13 D3DXMatrixPerspectiveFovLH(&proj, D3DX_PI / 4, 1.0f, 1.0f, 1000.0f) ;
14
15 D3DXMATRIX viewproj = view * proj ;
16 g_pConstantTable->SetMatrix(g_pd3dDevice, ViewProjMatrixHanle, &viewproj) ;
17
18 // this line is mandatory
19 g_pConstantTable->SetDefaults(g_pd3dDevice);
20 }
注意最后一行很重要,他是用来设置常量表中所有变量的默认值的,很多时候如果不加这一句,窗口中将看不到任何东西!还有要在InitD3D函数中禁用光照效果,因为我们是自己设置颜色
1 g_pd3dDevice->SetRenderState( D3DRS_LIGHTING , FALSE );
最后,也是最重要的,在Render函数中设置shader,只有这样,它才能生效
1 g_pd3dDevice->SetVertexShader(g_pVertexShader) ;
程序结束后,在Cleanup函数中清除常量表和shader
1 if(g_pConstantTable != NULL)
2 g_pConstantTable->Release() ;
3
4 if(g_pVertexShader != NULL)
5 g_pVertexShader->Release() ;
6
好了,大功告成,现在编译并运行这个程序,你将看到一个蓝色的茶壶!
完整源代码
1 #include <d3dx9.h>
2
3 LPDIRECT3D9 g_pD3D = NULL ; // Used to create the D3DDevice
4 LPDIRECT3DDEVICE9 g_pd3dDevice = NULL ; // Our rendering device
5 ID3DXMesh* g_pMesh = NULL ; // Hold the teapot
6 IDirect3DVertexShader9* g_pVertexShader = NULL ; // vertext shader
7 ID3DXConstantTable* g_pConstantTable = NULL ; // shader constant table
8
9 // variable handle in shader file
10 D3DXHANDLE ViewProjMatrixHanle = 0 ;
11
12 HRESULT InitD3D( HWND hWnd )
13 {
14 // Create the D3D object, which is needed to create the D3DDevice.
15 if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
16 return E_FAIL;
17
18 D3DPRESENT_PARAMETERS d3dpp;
19 ZeroMemory( &d3dpp, sizeof(d3dpp) );
20 d3dpp.Windowed = TRUE;
21 d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
22 d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
23
24 // Create device
25 if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
26 D3DCREATE_SOFTWARE_VERTEXPROCESSING,
27 &d3dpp, &g_pd3dDevice ) ) )
28 {
29 return E_FAIL;
30 }
31
32 //g_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
33 g_pd3dDevice->SetRenderState( D3DRS_LIGHTING , FALSE );
34
35 D3DXCreateTeapot(g_pd3dDevice, &g_pMesh, NULL) ;
36
37 return S_OK;
38 }
39
40 VOID Cleanup()
41 {
42 if( g_pd3dDevice != NULL)
43 g_pd3dDevice->Release();
44
45 if( g_pD3D != NULL)
46 g_pD3D->Release();
47
48 if(g_pMesh != NULL)
49 g_pMesh->Release() ;
50
51 if(g_pConstantTable != NULL)
52 g_pConstantTable->Release() ;
53
54 if(g_pVertexShader != NULL)
55 g_pVertexShader->Release() ;
56 }
57
58 bool PrepareShader()
59 {
60 ID3DXBuffer *shader ;
61 ID3DXBuffer *errorBuffer ;
62
63 // Compile shader from file
64 HRESULT hr = D3DXCompileShaderFromFileA("Shader.txt", 0, 0, "Main", "vs_1_1",
65 D3DXSHADER_ENABLE_BACKWARDS_COMPATIBILITY, &shader, &errorBuffer, &g_pConstantTable) ;
66
67 // error handling
68 // output compile error message
69 if( errorBuffer )
70 {
71 MessageBoxA(0, (char*)errorBuffer->GetBufferPointer(), 0, 0);
72 errorBuffer->Release() ;
73 }
74
75 // compile failed
76 if(FAILED(hr))
77 {
78 MessageBox(0, L"D3DXCompileShaderFromFile() - FAILED", 0, 0);
79 return false;
80 }
81
82 // Create shader
83 hr = g_pd3dDevice->CreateVertexShader((DWORD*)shader->GetBufferPointer(), &g_pVertexShader) ;
84
85 // create failed
86 if(FAILED(hr))
87 {
88 MessageBox(0, L"CreateVertexShader - FAILED", 0, 0);
89 return false;
90 }
91
92 shader->Release() ;
93
94 // map handle to variable in shader
95 ViewProjMatrixHanle = g_pConstantTable->GetConstantByName(0, "ViewProjMatrix") ;
96 }
97
98 void SetupMatrix()
99 {
100 // set view
101 D3DXVECTOR3 eyePt(0.0f, 0.0f, -10.0f) ;
102 D3DXVECTOR3 upVec(0.0f, 1.0f, 0.0f) ;
103 D3DXVECTOR3 lookCenter(0.0f, 0.0f, 0.0f) ;
104
105 D3DXMATRIX view ;
106 D3DXMatrixLookAtLH(&view, &eyePt, &lookCenter, &upVec) ;
107
108 // set projection
109 D3DXMATRIX proj ;
110 D3DXMatrixPerspectiveFovLH(&proj, D3DX_PI / 4, 1.0f, 1.0f, 1000.0f) ;
111
112 D3DXMATRIX viewproj = view * proj ;
113 g_pConstantTable->SetMatrix(g_pd3dDevice, ViewProjMatrixHanle, &viewproj) ;
114
115 // this line is mandatory
116 g_pConstantTable->SetDefaults(g_pd3dDevice);
117 }
118
119 VOID Render()
120 {
121 if(!PrepareShader())
122 return ;
123
124 SetupMatrix() ;
125
126 // Clear the back-buffer to a RED color
127 g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );
128
129 // Begin the scene
130 if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
131 {
132 // Rendering of scene objects can happen here
133 g_pd3dDevice->SetVertexShader(g_pVertexShader) ;
134
135 g_pMesh->DrawSubset(0) ;
136 // End the scene
137 g_pd3dDevice->EndScene();
138 }
139
140 // Present the back-buffer contents to the display
141 g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
142 }
143
144 LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
145 {
146 switch( msg )
147 {
148 case WM_KEYDOWN:
149 {
150 switch( wParam )
151 {
152 case VK_ESCAPE:
153 SendMessage( hWnd, WM_CLOSE, 0, 0 );
154 break ;
155 default:
156 break ;
157 }
158 }
159 break ;
160
161 case WM_DESTROY:
162 Cleanup();
163 PostQuitMessage( 0 );
164 return 0;
165 }
166
167 return DefWindowProc( hWnd, msg, wParam, lParam );
168 }
169
170 INT WINAPI wWinMain( HINSTANCE hInst, HINSTANCE, LPWSTR, INT )
171 {
172 // Register the window class
173 WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
174 GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
175 L"D3D Tutorial", NULL };
176 RegisterClassEx( &wc );
177
178 // Create the application‘s window
179 HWND hWnd = CreateWindow( L"D3D Tutorial", L"Simple shader",
180 WS_OVERLAPPEDWINDOW , 0, 0, 600, 600,
181 NULL, NULL, wc.hInstance, NULL );
182
183 // Initialize Direct3D
184 if( SUCCEEDED( InitD3D( hWnd ) ) )
185 {
186 // Show the window
187 ShowWindow( hWnd, SW_SHOWDEFAULT );
188 UpdateWindow( hWnd );
189
190 // Enter the message loop
191 MSG msg ;
192 ZeroMemory( &msg, sizeof(msg) );
193 PeekMessage( &msg, NULL, 0U, 0U, PM_NOREMOVE );
194
195 while (msg.message != WM_QUIT)
196 {
197 if( PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE) != 0)
198 {
199 TranslateMessage (&msg) ;
200 DispatchMessage (&msg) ;
201 }
202 else // Render the game if no message to process
203 {
204 Render() ;
205 }
206 }
207 }
208
209 UnregisterClass( L"D3D Tutorial", wc.hInstance );
210 return 0;
211 }
212
213
214
215
happy coding!