首先我们来看下这个项目的效果,一个照片被"黑白化"
下面这张是原图。
我们来看下是如何实现的,首先你要有点知识关于“DirectX Texture(纹理)”. 如果你还没有的话,可以看下这篇文章 “DirectX 9.0 C++ tutorial Texture ”.
今天的主题是 HLSL and Pixel Shader(像素着色器), 这两个主题有点难。
HLSL 是 “High-LevelShading Language” 的简写. 我们可以使用 HLSL去写一些小的 Pixel Shade 项目.简要地说, 顶点着色器和像素着色器就是我们自行编写的一些规模较小的定制程序。这些定制程序可取代固定功能流水线中的某一功能,并在图形卡的GPU中执行. 通过这种功能替换,我们便在实现各种图形效果时获得了巨大的灵活性。我们不再受制于那些预定义的固定运算。
HLSL 的语法跟C,C++相似,我们可以很容易地学习它。
HLSL着色器程序可以以一个长字符串的形式出现在应用程序的源文件中. 但更方便也更模块化的做法是将着色器代码跟应用程序代码分离. 我们可在记事本编写着色器代码并将其保存为常见的ASCII文本文件. 接下来我们使用 D3DXComplileShaderFromFile 函数去编译我们的着色器。
下面是Pixel Shader 例子
// // 全局变量 // sampler BaseTex; // // input structures(输入结构) // struct PS_INPUT { float2 base : TEXCOORD; }; // // output structures(输出结构) // struct PS_OUTPUT { vector color : COLOR; }; // // Main Function // PS_OUTPUT Main(PS_INPUT input) { // zero out members of output PS_OUTPUT output = (PS_OUTPUT)0; vector b = tex2D(BaseTex, input.base); //核心代码,学过图形学的应该知道,对一个像素的颜色进行重新计算 vector c = 0.3 * b.x + 0.59 * b.y + 0.11 * b.z ; output.color = c; return output; }
开始我们初始化一个全局变量:
sampler BaseTex;
声明了全局变量之后, 我们定义了两个特殊结构.对于pixel shaders, 这些结构定义了我们着色器输入输出的 texture(纹理) 数据 。
struct PS_INPUT { float2 base : TEXCOORD; }; struct PS_OUTPUT { vector color : COLOR; };
在这个例子中,我们着色器的输入纹理坐标只有一个。输出包含了一个颜色信息。
其中 “: COLOR” 是说 vector(向量) color 是用于描述输出顶点的颜色信息。
像C++程序一样,每个HLSL程序都有一个入口点. 在我们的着色器例子中,入口函数为Main; 但是,这个名称并非强制性的. 着色器的入口函数的命名可以自由选择. 入口函数必须有一个接受输入结构的参数, 用来将texture coordinate 传给着色器。 而且入口函数必须返回一个输出结构的实例, 用来将经过处理的顶点自着色器输出。
每个着色器都用Constant Table来存储其变量. 为了使应用程序能够访问着色器的Constant Table,D3DX提供了接口 ID3DXConstantTable. 通过这接口,我们可以在应用程序源代码中对着色器源代码的变量进行设置。
D3DXHANDLE ID3DXConstantTable::GetConstantByName( D3DXHANDLE hConstant, // scope of constant LPCSTR pName // name of constant );
举一个例子,如果期望引用的着色器中的变量名称为 “BaseTex” 而且该变量为顶级参数,我们可以这样引用:
D3DXHANDLE h0;
H0 = ConstTable->GetConstantByName(0, “BaseTex”);
一旦应用程序获取了着色器代码中希望被引用的对那个变量的 D3DXHANDLE 类型的句柄, 我们就可在应用程序中使用方法 ID3DXConstantTable::SetXXX 对该变量进行设置, 其中XXX表示被设置变量的类型名称。 如果我们希望设置的变量为一个Sampler State, 那么该方法对应为 SetSampleState.
方法 ID3DXConstantTable::SetXXX 的通用签名:HRESULT ID3DXConstantTable::SetXXX( LPDIRECT3DDEVICE9 pDevice, D3DXHANDLE hConstant, XXX value );
一个例子:
LPDIRECT3DDEVICE9 g_pd3dDevice;
D3DXCONSTANT_DESC BaseTexDesc;
g_pd3dDevice->SetSamplerState(BaseTexDesc.RegisterIndex,D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
HRESULT D3DXCompileShaderFromFile( LPCSTR pSrcFile, CONST D3DXMACRO* pDefines, LPD3DXINCLUDE pInclude, LPCSTR pFunctionName, LPCSTR pTarget, DWORD Flags, LPD3DXBUFFER* ppShader, LPD3DXBUFFER* ppErrorMsgs, LPD3DXCONSTANTTABLE* ppConstantTable );
Here is an example call of D3DXCompileShaderFromFile: // // Compile shader // ID3DXConstantTable* TransformConstantTable = 0; ID3DXBuffer* shader = 0; ID3DXBuffer* errorBuffer = 0; hr = D3DXCompileShaderFromFile("hlslTexture.txt", 0, 0, "Main", // entry point function name "ps_1_1", // HLSL shader name D3DXSHADER_DEBUG, &shader, // containing the created shader &errorBuffer, // containing a listing of errors and warnings &TextCT); // used to access shader constants // output any error messages if( errorBuffer ) { ::MessageBox(0, (char*)errorBuffer->GetBufferPointer(), 0, 0); d3d::Release<ID3DXBuffer*>(errorBuffer); } if (FAILED (hr)) { ::MessageBox(0, "D3DXCreateEffectFromFile() - FAILED", 0, 0); return false; }
Pixel Shader 的输入包括颜色和纹理坐标. 都是针对当个像素. 每个像素的纹理坐标其实就是指定了纹理中将被映射到当前像素的纹理元的坐标 (u,v)。. Direct3D 先根据顶点颜色和顶点纹理坐标计算出每个像素的颜色和纹理坐标. 输入像素着色器的颜色和纹理坐标对的个数由顶点着色器输出的颜色和纹理坐标对的个数决定. 例如,如果一个顶点着色器输出两种颜色和3个纹理坐标对, 则Direct3D 将计算出每个像素的两种颜色和3个纹理坐标对,并将这些结果输入像素着色器. 我们需要借助语义语法来将输入的颜色和纹理坐标映射为像素着色器程序中的变量. 我们可以写做如下:
struct PS_INPUT { vector c0 : COLOR0; vector c1 : COLOR1; float2 t0 : TEXCOORD0; float2 t1 : TEXCOORD1; float2 t2 : TEXCOORD2; }; For output, a pixel shader outputs a single computed color value for the pixel: struct PS_OUTPUT { vector finalPixelColor : COLOR0; };
下面列出了要创建和使用Pixel Shader所必须采取的步骤
1. 编写 pixel shader.并进行编译。
2. Create an IDirect3DPixelShader9 interface to represent the pixel shader based on the compiled shader code.
3. 用 IDirect3DDevice9::SetPixelShader 方法启用Pixel Shader.
当然,当Pixel Shader使用完毕后,我们必须对其进行销毁。下面我们将更详细地解释。
一旦着色器代码经过了编译,我们就可以借助下述方法获取指向IDirect3DPixelShader9接口的指针, 该接口代表了一个像素着色器。
HRESULT IDirect3DDevice9::CreatePixelShader( CONST DWORD *pFunction, IDirect3DPixelShader9** ppShader );
例如,假定变量shader 是一个 ID3DXBuffer 接口的对象,它包含了经过编译的着色器代码. 为了获取 IDirect3DPixelShader9 接口的指针,我们可以这样做:
IDirect3DPixelShader9* MultiTexPS = 0; hr = Device->CreatePixelShader( (DWORD*)shader->GetBufferPointer(), &MultiTexPS);
当获取了代表像素着色器的接口IDirect3DPixelShader9的指针后,我们可用下述的方法来将其启用:
HRESULT IDirect3DDevice9::SetPixelShader( IDirect3DPixelShader9* pShader );
该方法只接收一个参数。
Device->SetPixelShader(MultiTexPS);
跟其他 Direct3D 接口一样, 使用完毕之后也必须调用自身的Release方法来释放它所占用的资源。
//----------------------------------------------------------------------------- // File: Textures.cpp // About Using HLSL pixel Shader // Copyright (c) Microsoft Corporation & Waitingfy.com. All rights reserved. //----------------------------------------------------------------------------- #include #include #include #include //----------------------------------------------------------------------------- // Global variables //----------------------------------------------------------------------------- LPDIRECT3D9 g_pD3D = NULL; // Used to create the D3DDevice LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // Our rendering device LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; // Buffer to hold vertices LPDIRECT3DTEXTURE9 g_pTexture = NULL; // Our texture IDirect3DPixelShader9* TexPS = 0; ID3DXConstantTable* TextCT = 0; D3DXHANDLE BaseTexHandle = 0; D3DXCONSTANT_DESC BaseTexDesc; // A structure for our custom vertex type. We added texture coordinates struct CUSTOMVERTEX { float x, y, z; unsigned long color; FLOAT tu, tv; // 纹理坐标 }; // 多了纹理,增加了D3DFVF_TEX1 #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1) //----------------------------------------------------------------------------- // Name: InitD3D() // Desc: Initializes Direct3D //----------------------------------------------------------------------------- HRESULT InitD3D( HWND hWnd ) { // Create the D3D object. if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) return E_FAIL; // Set up the structure used to create the D3DDevice. Since we are now // using more complex geometry, we will create a device with a zbuffer. D3DPRESENT_PARAMETERS d3dpp; ZeroMemory( &d3dpp, sizeof(d3dpp) ); d3dpp.Windowed = TRUE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; d3dpp.EnableAutoDepthStencil = TRUE; d3dpp.AutoDepthStencilFormat = D3DFMT_D16; // Create the D3DDevice if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice ) ) ) { return E_FAIL; } // Turn off culling g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); // Turn off D3D lighting g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE ); // Turn on the zbuffer g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE ); HRESULT hr = 0; ID3DXBuffer* shader = 0; ID3DXBuffer* errorBuffer = 0; //从文件名中编译shader hr = D3DXCompileShaderFromFile("hlslTexture.txt", 0, 0, "Main", //入口名称 "ps_1_1", //shader版本 D3DXSHADER_DEBUG, &shader, &errorBuffer, //包含错误和警告信息 &TextCT); //访问shader constants if(errorBuffer) { ::MessageBox(0,(char*)errorBuffer->GetBufferPointer(), 0, 0); errorBuffer->Release(); } if(FAILED(hr)) { ::MessageBox(0,"D3DXCompileShaderFromFile() - FAILED", 0, 0); return false; } hr = g_pd3dDevice->CreatePixelShader((DWORD*)shader->GetBufferPointer(),&TexPS); if(FAILED(hr)) { ::MessageBox(0,"CreateVertexShader - FAILED", 0, 0); return false; } shader->Release(); // 得到一个 constant BaseTexHandle = TextCT->GetConstantByName(0, "BaseTex"); UINT count; // 设置 constant descriptions TextCT->GetConstantDesc(BaseTexHandle, &BaseTexDesc, &count); return S_OK; } //----------------------------------------------------------------------------- // Name: InitGeometry() // Desc: Create the Textures and vertex buffers //----------------------------------------------------------------------------- HRESULT InitGeometry() { HRESULT hr = 0; // 创建纹理 hr = D3DXCreateTextureFromFile( g_pd3dDevice, "objective-c.png", &g_pTexture ); if(FAILED(hr)) { MessageBox(NULL, "Could not find objective-c.png", "Textures.exe", MB_OK); return E_FAIL; } // 构造组成物体的顶点,并为其指定纹理坐标 CUSTOMVERTEX Vertices[] = { { -1.0f, -1.0f, 0.0f, D3DCOLOR_XRGB(255,255,255),0,1 }, // x, y, z, rhw, color,tu,tv { -1.0f, 1.0f, 0.0f, D3DCOLOR_XRGB(255,255,255), 0,0}, { 1.0f, -1.0f, 0.0f, D3DCOLOR_XRGB(255,255,255), 1,1}, { 1.0f, 1.0f, 0.0f, D3DCOLOR_XRGB(255,255,255), 1,0}, }; // Create the vertex buffer. hr = g_pd3dDevice->CreateVertexBuffer(sizeof(Vertices), D3DUSAGE_WRITEONLY,D3DFVF_CUSTOMVERTEX, D3DPOOL_MANAGED,&g_pVB,0); if(hr) { return E_FAIL; } VOID* pVertices; if( FAILED( g_pVB->Lock( 0, sizeof(Vertices), (void**)&pVertices, 0 ) ) ) return E_FAIL; memcpy( pVertices, Vertices, sizeof(Vertices) ); g_pVB->Unlock(); return S_OK; } //----------------------------------------------------------------------------- // Name: Cleanup() // Desc: Releases all previously initialized objects //----------------------------------------------------------------------------- VOID Cleanup() { if( g_pTexture != NULL ) g_pTexture->Release(); if( g_pVB != NULL ) g_pVB->Release(); if( g_pd3dDevice != NULL ) g_pd3dDevice->Release(); if( g_pD3D != NULL ) g_pD3D->Release(); if(TexPS != NULL ) TexPS->Release(); if(TextCT != NULL) TextCT->Release(); } //----------------------------------------------------------------------------- // Name: SetupMatrices() // Desc: Sets up the world, view, and projection transform matrices. //----------------------------------------------------------------------------- VOID SetupMatrices() { // 一定时间绕Y轴旋转 D3DXMATRIXA16 matWorld; D3DXMatrixIdentity( &matWorld ); //移除下面的注释即可旋转 //D3DXMatrixRotationY( &matWorld, timeGetTime()/1000.0f ); g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld ); // 设置摄像头的位置等 D3DXVECTOR3 vEyePt( 0.0f, 0.0f,-2.5f ); D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f ); D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f ); D3DXMATRIXA16 matView; D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec ); g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView ); // 设置视锥体 D3DXMATRIXA16 matProj; D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 100.0f ); g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj ); } //----------------------------------------------------------------------------- // Name: Render() // Desc: Draws the scene //----------------------------------------------------------------------------- VOID Render() { // Clear the backbuffer and the zbuffer g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 ); // Begin the scene if( SUCCEEDED( g_pd3dDevice->BeginScene() ) ) { // Setup the world, view, and projection matrices SetupMatrices(); g_pd3dDevice->SetPixelShader(TexPS); // 绘制物体前,设置物体关联的纹理 g_pd3dDevice->SetTexture(BaseTexDesc.RegisterIndex, g_pTexture); g_pd3dDevice->SetSamplerState(BaseTexDesc.RegisterIndex, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); g_pd3dDevice->SetSamplerState(BaseTexDesc.RegisterIndex, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); g_pd3dDevice->SetSamplerState(BaseTexDesc.RegisterIndex, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR); // Render the vertex buffer contents g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof(CUSTOMVERTEX) ); g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX ); //两个三角形组成矩形 g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2); // End the scene g_pd3dDevice->EndScene(); } // Present the backbuffer contents to the display g_pd3dDevice->Present( NULL, NULL, NULL, NULL ); } //----------------------------------------------------------------------------- // Name: MsgProc() // Desc: The window's message handler //----------------------------------------------------------------------------- LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) { switch( msg ) { case WM_DESTROY: Cleanup(); PostQuitMessage( 0 ); return 0; } return DefWindowProc( hWnd, msg, wParam, lParam ); } //----------------------------------------------------------------------------- // Name: WinMain() // Desc: The application's entry point //----------------------------------------------------------------------------- INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT ) { // Register the window class WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, "D3D Tutorial", NULL }; RegisterClassEx( &wc ); // Create the application's window HWND hWnd = CreateWindow( "D3D Tutorial", "D3D Tutorial 05: Textures", WS_OVERLAPPEDWINDOW, 100, 100, 300, 300, GetDesktopWindow(), NULL, wc.hInstance, NULL ); // Initialize Direct3D if( SUCCEEDED( InitD3D( hWnd ) ) ) { // Create the scene geometry if( SUCCEEDED( InitGeometry() ) ) { // Show the window ShowWindow( hWnd, SW_SHOWDEFAULT ); UpdateWindow( hWnd ); // Enter the message loop MSG msg; ZeroMemory( &msg, sizeof(msg) ); while( msg.message!=WM_QUIT ) { if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } else Render(); } } } UnregisterClass( "D3D Tutorial", wc.hInstance ); return 0; }
引用:
1. <Introduction to 3D Game Programming with DirectX 9.0> Chapter 16:Introduction to the High-Level Shading Language,Chapter 18:Introduction to Pixel Shaders
2. DirectX 9.0 SDK's Tutorial 5: Using Texture Maps
3. HLSL初步知识讲解 http://download.csdn.net/detail/chengscga/3948392
项目下载:
HLSL+Pixel+Shader (www.Waitingfy.com Download)
http://www.waitingfy.com/?p=395