GPU Shader 编程基础
轉載自:http://www.cnblogs.com/youthlion/archive/2012/12/07/2807919.html
幾個基本概念:
Vertex buffer:存儲頂點的數組。當構成模型的所有頂點都放進vertex buffer后,就可以把vertex buffer送進GPU,然后GPU就可以渲染模型了。
Index buffer:這個buffer的作用是索引。記錄每個頂點在vertex buffer中的位置。DX SDK里說使用index buffer可以增加頂點數據被緩存在顯存里的概率,所以就效率而言應該使用index buffer。
Vertex Shader:vertex shader是一類小程序,主要用來把vertex buffer里的頂點變換到3D空間中去。也可以用vertex shader干點別的,比如算頂點的法向量。對于每個要處理的頂點,GPU都會調用vertex shader。比如5000個三角形的網格模型,每一幀就得調用vertex shader 15000次,每秒60幀的話,vertex shader還真得寫得靠譜點。
Pixel Shader:是一般用來處理多邊形顏色的小程序。對于場景里每個要畫到屏幕上的可見像素,GPU都會調用Pixel Shader來處理。像著色、光照,還有大多數用在多邊形上的其他效果,都要靠Pixel Shader來搞定。沒錯,這個東西也得寫的靠譜點。效率,效率啊。要不可能GPU還沒有CPU算得快。
HLSL:這就是用來寫各種shader程序的語言了。HLSL程序包括全局變量、類型定義、vertex shaders,pixel shaders還有geometry shaders。
程序的整體結構:
這一次接著上一篇筆記的程序繼續擴展。在這篇筆記中,我們會用shader繪制一個綠色的三角形。這里三角形是要繪制的對象,實際上是一個簡單的多邊形模型,也就是數據,所以把它封裝到一個模型類(ModelClass)中,并集成到文檔類里去。視圖類負責顯示,而顯示的功能是由shader完成的。正如前面說的,shader是一段小程序,上面的圖中集成到視圖類中的ColorShaderClass負責調用shader,也就是讓這段shader小程序運行起來。這就是這篇筆記中程序的最宏觀結構。
第一個shader程序:
在工程中添加一個名為color.fx的源文件,看來shader程序源文件的擴展名是.fx了。這個shader的目的是繪制一個綠色的三角形。
這個shader首先聲明了三個全局矩陣變量,便于其他類從外部訪問,再傳回shader。
/ // GLOBALS // / matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix;下面幾行代碼里,使用HLSL中的float4類型創建了一個位置向量,包括x、y、z、w,以及一個顏色向量,包括red、green、blue、alpha分量。其中POSITION、COLOR和SV_POSITION是傳遞給GPU的語義信息,讓GPU知道這些變量是干嘛用的。下面的兩個類型貌似作用是一樣的,但是必須分別創建。因為對于vertex shader和pixel shader,需要不同的語義。POSITION是對應vertex shader,SV_POSITION適用于pixel shader,COLOR則是兩者通用。如果需要同一類型的多個成員,就得在類型后面加上個數值后綴,像COLOR0、COLOR1這種。
// // TYPEDEFS // // struct VertexInputType { float4 position : POSITION; float4 color : COLOR; }; ? struct PixelInputType { float4 position : SV_POSITION; float4 color : COLOR; };?
當vertex buffer中的數據被送進GPU進行處理的時候,GPU會調用vertex shader。下面定義了一個名為ColorVertexShader的函數,該函數在處理vertex buffer中每個頂點的時候都會被調用。vertex shader的輸入必須與vertex buffer緩沖區中的數據以及shader源文件中的類型定義相匹配。這里就是VertexInputType。vertex shader的輸出會被送進pixel shader,這里輸出類型為上面定義的PixelInputType。
下面代碼中的vertex shader流程是這樣的:他先創建一個輸出變量,類型為PixelInputType,然后拿到輸入頂點的坐標,把世界矩陣、視點矩陣、投影矩陣挨個乘上去,對頂點進行變換,最后頂點會被變換到我們視點所觀察的3D空間中的正確位置。然后拿到輸入的顏色值,放進輸出變量里,把輸出變量返回,返回的輸出變量接下來會被送進pixel shader。
// 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; } 接下來就是pixel shader接班,由pixel shader把多邊形上那些要渲染到屏幕上的像素繪制出來。下面的這個pixel shader以PixelInputType作為輸入,返回一個float4類型,這個float4就是最后的像素顏色值。下面這個pixel shader僅僅是把像素著色為輸入的顏色值。重申,vertex shader的輸出是pixel shader的輸入。 // Pixel Shader float4 ColorPixelShader(PixelInputType input) : SV_Target { return input.color; }下面幾行代碼里的technique才是真正意義的shader。這個東西是用來渲染多邊形、調用vertex shader和pixel shader的,可以把它看做是HLSL的main()函數。在technique里面可以設定多個pass,調用各種vertex shader和pixel shader來組合出想要的效果。這個例子里只使用了一個pass,也只調用了上面寫好的vertex和pixel shader。geometry shader暫時不用,這里也沒有調用。
還有個值得注意的事兒,代碼里用vs_4_0指定vertex shader的版本為4.0,這是SetVertexShader函數的第一個參數。這樣我們才可以使用DX10 HLSL中vertex shader4.0相應的功能。pixel shader也是類似。
// Technique technique10 ColorTechnique { pass pass0 { SetVertexShader(CompileShader(vs_4_0, ColorVertexShader())); SetPixelShader(CompileShader(ps_4_0, ColorPixelShader())); SetGeometryShader(NULL); } }以上是這個例子的shader部分。也就是負責實際渲染工作的模塊。那么shader渲染的是神馬?恩,模型。
所以要在工程里再添加個模型類:
這個例子里,我們的模型僅僅是個三角形,暫時用原教程給的一個模型類ModelClass,后面如果需要,爭取把這個模型類用CGAL的Polyhedron替換掉。下面先看一下ModelClass的頭文件:
首先在ModelClass中添加頂點類型的定義。這也是vertex buffer的類型。
struct VertexType { D3DXVECTOR3 position; D3DXVECTOR4 color; };構造和析構函數:
ModelClass(); ModelClass(const ModelClass&); ~ModelClass();下面的幾個函數負責初始化和釋放模型的vertex和index buffer。Render函數負責把模型的幾何屬性送到顯卡上,準備讓shader繪制。
bool Initialize(ID3D10Device*); void Shutdown(); void Render(ID3D10Device*); ? int GetIndexCount();上面的幾個公有函數的功能通過調用下面的幾個私有函數實現:
private: bool InitializeBuffers(ID3D10Device*); void ShutdownBuffers(); void RenderBuffers(ID3D10Device*);添加幾個私有變量,分別作為vertex buffer和index buffer的指針,另外還有兩個整型,用來記錄兩塊buffer的大小。注意DX10里buffer一般用通用的ID3D10Buffer類型,這種類型的變量在創建的時候可以用buffer description進行描述。
private: ID3D10Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount;ModelClass類的實現部分,先是構造和析構函數:
ModelClass::ModelClass() { m_vertexBuffer = NULL; m_indexBuffer = NULL; } ? ? ModelClass::ModelClass(const ModelClass& other) { } ? ? ModelClass::~ModelClass() { }初始化函數:
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; }釋放buffer:
void ModelClass::Shutdown() { // Release the vertex and index buffers. ShutdownBuffers(); ? return; }Render函數實際是在框架的繪制模塊里調用的,也就是我第二篇筆記中的視圖類,再具體點,應該就是在視圖類的OnPaint方法里。
void ModelClass::Render(ID3D10Device* device) { // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing. RenderBuffers(device); ? return; }GetIndexCount函數返回index的數量:
int ModelClass::GetIndexCount() { return m_indexCount; }接下來是Initialize、ShutDown和Render對應的幾個私有方法的具體實現,首先是InitializeBuffers,這個函數負責創建vertex buffer和index buffer。在實際的應用里,一般是從數據文件里把模型讀進來(.off,.obj,.ply等等)然后創建buffer,在這個例子里,因為模型只是一個三角形,所以直接在vertex buffer和index buffer里人工設置了三個點。
bool ModelClass::InitializeBuffers(ID3D10Device* device) { VertexType* vertices; unsigned long* indices; D3D10_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D10_SUBRESOURCE_DATA vertexData, indexData; HRESULT result;首先創建兩個數組,用來存儲頂點和索引數據。后面會用這兩個數組去填充最終的buffer。
// Set the number of vertices in the vertex array. m_vertexCount = 3; ? // Set the number of indices in the index array. m_indexCount = 3; ? // Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } ? // Create the index array. indices = new unsigned long[m_indexCount]; if(!indices) { return false; }然后分別對頂點屬性和頂點索引賦值。留心,下面的代碼是按照順時針的順序創建頂點的。如果逆時針創建的話,程序會認為這個三角形是屁股朝著屏幕。如果恰好又設置了背面剔除的話,程序就不再繪制這個三角形了。所以說,被送進GPU的頂點順序是有講究的。
// Load the vertex array with data. vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f); // Bottom left. vertices[0].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f); ? vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f); // Top middle. vertices[1].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f); ? vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f); // Bottom right. vertices[2].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f); ? // Load the index array with data. indices[0] = 0; // Bottom left. indices[1] = 1; // Top middle. indices[2] = 2; // Bottom right.vetex數組和index數組搞定后,可以用它們來創建vertex buffer和index buffer。兩種buffer的創建方式是一樣的:首先填好buffer的description。在這個description里面ByteWidth(buffer的大小)和BindFlags(buffer類型)必須得填對。填好description后,還要分別填一個subresource指針,這量個指針分別指向前面創建的vertex數組和index數組。description和subresource都填好之后,就可以用D3D device調用CreateBuffer,這個函數會返回指向新創建buffer的指針。
// Set up the description of the vertex buffer. vertexBufferDesc.Usage = D3D10_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; vertexBufferDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; ? // Give the subresource structure a pointer to the vertex data. vertexData.pSysMem = vertices; ? // Now finally create the vertex buffer. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; } ? // Set up the description of the index buffer. indexBufferDesc.Usage = D3D10_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; indexBufferDesc.BindFlags = D3D10_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; ? // Give the subresource structure a pointer to the index data. indexData.pSysMem = indices; ? // Create the index buffer. result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer); if(FAILED(result)) { return false; }vertex buffer和index buffer創建后,就可以卸磨殺驢,干掉vertex數組和index數組了:
// Release the arrays now that the vertex and index buffers have been created and loaded. delete [] vertices; vertices = 0; ? delete [] indices; indices = 0; ? return true; }接下來是負責釋放vertex buffer和index buffer的ShutdownBuffers函數:
void ModelClass::ShutdownBuffers() { // Release the index buffer. if(m_indexBuffer) { m_indexBuffer->Release(); m_indexBuffer = 0; } ? // Release the vertex buffer. if(m_vertexBuffer) { m_vertexBuffer->Release(); m_vertexBuffer = 0; } ? return; }下面是對應Render函數的私有函數RenderBuffers。這個函數的作用是把GPU中input assembler上的vertex buffer和index buffer設置為激活狀態。一旦有了一塊激活的vertex buffer,GPU就可以用我們寫的HLSL shader去渲染這塊buffer。RenderBuffers函數還規定了這些buffer的繪制方式,比如繪制三角形、繪制直線神馬的。這一篇筆記里,我們在input assembler上激活index buffer和vertex buffer,并通過DX10的IASetPrimitiveTopology函數告訴GPU,這塊buffer要以三角形的方式繪制。
void ModelClass::RenderBuffers(ID3D10Device* device) { unsigned int stride; unsigned int offset; ? ? // Set vertex buffer stride and offset. stride = sizeof(VertexType); offset = 0; // Set the vertex buffer to active in the input assembler so it can be rendered. device->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset); ? // Set the index buffer to active in the input assembler so it can be rendered. device->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0); ? // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles. device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST); ? return; }Model類搞定。整理一下宏觀的思路:現在我們有了模型,也有了shader,可以用shader去渲染模型了。
問題在于,shader是怎樣開始運行的呢?
我們用下面這個ColorShaderClass類來調用shader。
這個類的Initialize和Shutdown兩個成員函數完成對shader的初始化和關閉,Render成員函數負責設置shader的參數,然后用shader去繪制模型。
ColorShaderClass類包含的頭文件及類聲明如下:
#include <d3d10.h> #include <d3dx10math.h> #include <fstream> using namespace std; ? class ColorShaderClass { public: ColorShaderClass(); ColorShaderClass(const ColorShaderClass&); ~ColorShaderClass(); ? bool Initialize(ID3D10Device*, HWND); void Shutdown(); void Render(ID3D10Device*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX); ? private: bool InitializeShader(ID3D10Device*, HWND, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); ? void SetShaderParameters(D3DXMATRIX, D3DXMATRIX, D3DXMATRIX); void RenderShader(ID3D10Device*, int); ? private: ID3D10Effect* m_effect; ID3D10EffectTechnique* m_technique; ID3D10InputLayout* m_layout; ? ID3D10EffectMatrixVariable* m_worldMatrixPtr; ID3D10EffectMatrixVariable* m_viewMatrixPtr; ID3D10EffectMatrixVariable* m_projectionMatrixPtr; }; 這個類和模型類的結構類似。在Initialize函數里,真正負責shader初始化的是InitializeShader,我們要傳給這個函數三個參數:device、窗口句柄和shader的文件名。 bool ColorShaderClass::Initialize(ID3D10Device* device, HWND hwnd) { bool result; ? ? // Initialize the shader that will be used to draw the triangle. result = InitializeShader(device, hwnd, L"../02_01/color.fx"); if(!result) { return false; } ? return true; }Shutdown調用ShutdownShader關閉shader:
void ColorShaderClass::Shutdown() { // Shutdown the shader effect. ShutdownShader(); ? return; }Render函數里做兩件事:1、設置shader參數,通過SetShaderParameters完成;2、用shader繪制綠三角,調用RenderShader完成:
在下面的InitializeShader函數中我們可以看到,shader實際上是在這里加載的。在這個函數里,我們還需要設置一個layout,這個layout需要與模型類及color.fx類中定義的頂點類相匹配:
bool ColorShaderClass::InitializeShader(ID3D10Device* device, HWND hwnd, WCHAR* filename) { HRESULT result; ID3D10Blob* errorMessage; D3D10_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D10_PASS_DESC passDesc; ? ? // Initialize the error message. errorMessage = 0;在D3DX10CreateEffectFromFile函數中,shader程序被編譯為一個effect。這個函數的幾個重要參數包括shader文件名、shader版本(DX10是4.0)、還要制定要把shader編譯到哪個effect里去(對應ColorShaderClass類的m_effect成員)。如果在編譯shader的過程中失敗的話,D3DX10CreateEffectFromFile會把一條錯誤消息放到errorMessage里,我們會把這個字符串塞給另一個函數OutputShaderErrorMessage去輸出錯誤信息。要是編譯失敗了,卻還沒有錯誤信息的話,可能是找不到shader文件,對這種情況我們會彈出一個對話框作為提示。
// Load the shader in from the file. result = D3DX10CreateEffectFromFile(filename, NULL, NULL, "fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, device, NULL, NULL, &m_effect, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, filename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, filename, L"Missing Shader File", MB_OK); } ? return false; }當shader文件成功編譯為effect后,就可以用這個effect找到shader里的那個technique。我們后面用這個technique進行繪制:
// Get a pointer to the technique inside the shader. m_technique = m_effect->GetTechniqueByName("ColorTechnique"); if(!m_technique) { return false; }下一步,shader所處理的頂點,還需要創建并設置一個layout。在這一篇筆記里,shader使用了一個位置向量和一個顏色向量,所以我們在layout中也要創建對應的元素,用來指明位置和顏色信息的內存占用情況。首先要填充的是語義信息,這樣shader才能知道這個layout元素的用途。對于位置信息,我們使用POSITION,顏色信息用COLOR。另一個重要信息是格式,位置信息我們用DXGI_FORMAT_R32G32B32_FLOAT,顏色信息用DXGI_FORMAT_R32G32B32A32_FLOAT。最后要注意的是AlignedByteOffset,這個字段指定了buffer中數據存儲的起點。對于本例來說,前12個字節是位置,隨后的16個字節是顏色。這個字段可以用D3D10_APPEND_ALIGNED_ELEMENT代替,表示DX10會自動計算。layout的其他字段暫時不會用到,這里使用默認設置:
// Now setup the layout of the data that goes into the shader. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; ? polygonLayout[1].SemanticName = "COLOR"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D10_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0;layout數組設置好之后,我們計算一下它包含的元素個數,然后用device創建input layout。
// Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); ? // Get the description of the first pass described in the shader technique. m_technique->GetPassByIndex(0)->GetDesc(&passDesc); ? // Create the input layout. result = device->CreateInputLayout(polygonLayout, numElements, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, &m_layout); if(FAILED(result)) { return false; }下面要做的是獲取shader里面那三個矩陣的指針,這樣以后就能用這三個指針設置矩陣的值:
// Get pointers to the three matrices inside the shader so we can update them from this class. m_worldMatrixPtr = m_effect->GetVariableByName("worldMatrix")->AsMatrix(); m_viewMatrixPtr = m_effect->GetVariableByName("viewMatrix")->AsMatrix(); m_projectionMatrixPtr = m_effect->GetVariableByName("projectionMatrix")->AsMatrix(); ? return true; }ShutdownShader函數負責釋放資源:
void ColorShaderClass::ShutdownShader() { // Release the pointers to the matrices inside the shader. m_worldMatrixPtr = 0; m_viewMatrixPtr = 0; m_projectionMatrixPtr = 0; ? // Release the pointer to the shader layout. if(m_layout) { m_layout->Release(); m_layout = 0; } ? // Release the pointer to the shader technique. m_technique = 0; ? // Release the pointer to the shader. if(m_effect) { m_effect->Release(); m_effect = 0; } ? return; }在編譯vertex shader或pixelshader時若發生問題,錯誤信息由OutputShaderErrorMessage函數輸出:
void ColorShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout; ? ? // Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); ? // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); ? // Open a file to write the error message to. fout.open("shader-error.txt"); ? // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } ? // Close the file. fout.close(); ? // Release the error message. errorMessage->Release(); errorMessage = 0; ? // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); ? return; } SetShaderParameters函數便于我們設置shader中的全局變量。這個函數中的三個矩陣是在上一篇筆記中的視圖類里創建的,矩陣被創建之后,負責繪圖的代碼會調用這個函數,把這三個矩陣送進shader。 void ColorShaderClass::SetShaderParameters(D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix) { // Set the world matrix variable inside the shader. m_worldMatrixPtr->SetMatrix((float*)&worldMatrix); ? // Set the view matrix variable inside the shader. m_viewMatrixPtr->SetMatrix((float*)&viewMatrix); ? // Set the projection matrix variable inside the shader. m_projectionMatrixPtr->SetMatrix((float*)&projectionMatrix); ? return; }SetShaderParameters函數執行后,各種參數(這里實際就是那仨矩陣)設置完成,ColorShaderClass類隨后調用RenderShader,RenderShader通過technique指針調用color.fx文件中的shader程序。
RenderShader函數上來先把input layout激活,這樣GPU才能知道vertex buffer里數據的格式。接下來要從shader中獲取technique的描述,這個technique告訴GPU調用哪個vertex shader或pixel shader來繪制vertex buffer里的數據。本例中我們獲取的是color.fx中ColorTechnique的描述,然后通過device調用DrawIndexed函數,循環調用technique中的各個pass來渲染三角形。目前的例子里,shader只有一個pass(pass0)。
void ColorShaderClass::RenderShader(ID3D10Device* device, int indexCount) { D3D10_TECHNIQUE_DESC techniqueDesc; unsigned int i; ? // Set the input layout. device->IASetInputLayout(m_layout); ? // Get the description structure of the technique from inside the shader so it can be used for rendering. m_technique->GetDesc(&techniqueDesc); ? // Go through each pass in the technique (should be just one currently) and render the triangles. for(i=0; i<techniqueDesc.Passes; ++i) { m_technique->GetPassByIndex(i)->Apply(0); device->DrawIndexed(indexCount, 0, 0); } ? return; }到這里,我們搞定了一個HLSL shader,設置了vertex buffer和index buffer,并了解了如何調用shader繪制兩種buffer中的數據。除此之外,還有一些輔助性的工作要做。第一個問題是,我們繪制的那些內容,是相對于哪個視點的?
好吧,所以我們還需要來個鏡頭類:
鏡頭類告訴DX10,鏡頭是從哪里、以及怎樣去觀察場景的。鏡頭類會始終跟蹤鏡頭的位置及其旋轉,使用位置和旋轉信息生成一個視點矩陣,這個視點矩陣會傳進shader,用于渲染。
鏡頭類聲明如下:
#include <d3dx10math.h> class CameraClass { public: CameraClass(); CameraClass(const CameraClass&); ~CameraClass(); ? void SetPosition(float, float, float); void SetRotation(float, float, float); ? D3DXVECTOR3 GetPosition(); D3DXVECTOR3 GetRotation(); ? void Render(); void GetViewMatrix(D3DXMATRIX&); ? private: float m_positionX, m_positionY, m_positionZ; float m_rotationX, m_rotationY, m_rotationZ; D3DXMATRIX m_viewMatrix; };其中,SetPosition和SetRotation函數用來設置鏡頭對象的位置和旋轉。Render函數基于位置和旋轉信息創建視點矩陣。GetViewMatrix用來訪問視點矩陣。
構造函數把位置和旋轉設置為場景的原點:
CameraClass::CameraClass() { m_positionX = 0.0f; m_positionY = 0.0f; m_positionZ = 0.0f; ? m_rotationX = 0.0f; m_rotationY = 0.0f; m_rotationZ = 0.0f; } ? ? CameraClass::CameraClass(const CameraClass& other) { } ? ? CameraClass::~CameraClass() { }兩個set函數:
void CameraClass::SetPosition(float x, float y, float z) { m_positionX = x; m_positionY = y; m_positionZ = z; return; } ? ? void CameraClass::SetRotation(float x, float y, float z) { m_rotationX = x; m_rotationY = y; m_rotationZ = z; return; }兩個get函數:
D3DXVECTOR3 CameraClass::GetPosition() { return D3DXVECTOR3(m_positionX, m_positionY, m_positionZ); } ? ? D3DXVECTOR3 CameraClass::GetRotation() { return D3DXVECTOR3(m_rotationX, m_rotationY, m_rotationZ); }Render函數用位置和旋轉信息構造和更新視點矩陣。這里除了位置和旋轉,還需要指定一個“上”方向和鏡頭朝向。接下來,首先在原點處根據x,y,z的值旋轉鏡頭,旋轉之后再把鏡頭移動到三維空間中的指定位置上。當位置、旋轉、方向“上”和所觀察的位置都確定下來后,就可以用DX10中的D3DXMatrixLookAtLH函數創建視點矩陣了:
void CameraClass::Render() { D3DXVECTOR3 up, position, lookAt; float yaw, pitch, roll; D3DXMATRIX rotationMatrix; ? ? // Setup the vector that points upwards. up.x = 0.0f; up.y = 1.0f; up.z = 0.0f; ? // Setup the position of the camera in the world. position.x = m_positionX; position.y = m_positionY; position.z = m_positionZ; ? // Setup where the camera is looking by default. lookAt.x = 0.0f; lookAt.y = 0.0f; lookAt.z = 1.0f; ? // Set the yaw (Y axis), pitch (X axis), and roll (Z axis) rotations in radians. pitch = m_rotationX * 0.0174532925f; yaw = m_rotationY * 0.0174532925f; roll = m_rotationZ * 0.0174532925f; ? // Create the rotation matrix from the yaw, pitch, and roll values. D3DXMatrixRotationYawPitchRoll(&rotationMatrix, yaw, pitch, roll); ? // Transform the lookAt and up vector by the rotation matrix so the view is correctly rotated at the origin. D3DXVec3TransformCoord(&lookAt, &lookAt, &rotationMatrix); D3DXVec3TransformCoord(&up, &up, &rotationMatrix); ? // Translate the rotated camera position to the location of the viewer. lookAt = position + lookAt; ? // Finally create the view matrix from the three updated vectors. D3DXMatrixLookAtLH(&m_viewMatrix, &position, &lookAt, &up); ? return; } GetViewMatrix: void CameraClass::GetViewMatrix(D3DXMATRIX& viewMatrix) { viewMatrix = m_viewMatrix; return; }至此,我們搞定了負責渲染的shader、負責調用shader的ColorShaderClass、用來存儲模型的ModelClass,以及負責管理視點信息的CamaraClass。
下面的故事是,在MFC的MDI框架中,應該怎樣用這些類?
從功能上看,文檔和視圖分別對應數據和顯示,在這個例子里,模型是數據(ModelClass),ColorShaderClass實現顯示(實際上是shader,color.fx),所以模型嵌入到文檔類,而ColorShaderClass集成進視圖類。
在文檔類中添加ModelClass類指針,這里為了訪問方便,直接設置為public:
#include "ModelClass.h" class CMy02_01Doc : public CDocument { ... public: ModelClass * m_pMesh; ... };目前文檔類要改的有三處:
構造函數:
CMy02_01Doc::CMy02_01Doc() { // TODO: add one-time construction code here m_pMesh = NULL; }在新建文檔時創建模型:
BOOL CMy02_01Doc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; ? // TODO: add reinitialization code here // (SDI documents will reuse this document) m_pMesh = new ModelClass(); return TRUE; }關閉文檔時釋放內存:
CMy02_01Doc::~CMy02_01Doc() { if(m_pMesh) { m_pMesh->Shutdown(); delete m_pMesh; m_pMesh = NULL; } }接下來是顯示相關的內容。鏡頭類和ColorShaderClass類都集成到視圖類中。
#include <CameraClass.h> #include <Colorshaderclass.h> ? class CMy02_01View : public CView { ... CameraClass * m_Camara; ColorShaderClass * m_ColorShader; ... };構造函數:
CMy02_01View::CMy02_01View() { // TODO: add construction code here ... ? m_Camara = NULL; m_ColorShader = NULL; }給視圖類添加一個建立camera、shader對象并進行初始化的函數ShaderInitialize,由于模型是在文檔類里創建和銷毀的,所以這里只要弄一個臨時指針指向模型對象就行了,不需要操心資源管理的事兒:
bool CMy02_01View::ShaderInitialize() { bool result; ? HWND hwnd = GetSafeHwnd(); ? // Create the camera object. m_Camera = new CameraClass(); if(!m_Camera) { return false; } ? // Set the initial position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); ? // Create the model object. ModelClass * pmesh = ((CMy02_01Doc *)GetDocument())->m_pMesh; if(!pmesh) { MessageBox(L"NULL Model!!"); return false; } result = pmesh->Initialize(m_device); if(!result) { MessageBox(L"Could not initialize the model object."); return false; } ? // Create the color shader object. m_ColorShader = new ColorShaderClass(); if(!m_ColorShader) { return false; } ? // Initialize the color shader object. result = m_ColorShader->Initialize(m_device, hwnd); if(!result) { MessageBox(L"Could not initialize the color shader object."); return false; } ? return true; }這個初始化函數在OnInitialUpdate中DX環境初始化后調用:
void CMy02_01View::OnInitialUpdate() { CView::OnInitialUpdate(); ? // TODO: Add your specialized code here and/or call the base class InitDX(); ShaderInitialize(); }再添加一個相應的資源釋放函數,該函數在視圖類的析構函數中調用:
void CMy02_01View::ShutDownShader() { // Release the color shader object. if(m_ColorShader) { m_ColorShader->Shutdown(); delete m_ColorShader; m_ColorShader = NULL; } ? // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } }最后在OnPaint中完成繪制功能:
void CMy02_01View::OnPaint() { D3DXMATRIX viewMatrix, projectionMatrix, worldMatrix; ? CPaintDC dc(this); // device context for painting // TODO: Add your message handler code here // Do not call CView::OnPaint() for painting messages float color[4]; ? ? // Setup the color to clear the buffer to. color[0] = 1.0f; color[1] = 0.0f; color[2] = 0.0f; color[3] = 1.0f; ? // Clear the back buffer. m_device->ClearRenderTargetView(m_renderTargetView, color); ? // Clear the depth buffer. m_device->ClearDepthStencilView(m_depthStencilView, D3D10_CLEAR_DEPTH, 1.0f, 0); ? // Generate the view matrix based on the camera's position. m_Camera->Render(); ? // Get the world, view, and projection matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); GetWorldMatrix(worldMatrix); GetProjectionMatrix(projectionMatrix); ? ModelClass * pmesh = ((CMy02_01Doc *)GetDocument())->m_pMesh; pmesh->Render(m_device); ? // Render the model using the color shader. m_ColorShader->Render(m_device, pmesh->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix); ? if(m_vsync_enabled) { // Lock to screen refresh rate. m_swapChain->Present(1, 0); } else { // Present as fast as possible. m_swapChain->Present(0, 0); } }運行效果,弱爆了T_T
下面理一理頭緒,回憶一下,上面的shader、ColorShaderClass、ModelClass、CameraClass都是咋回事兒來著?
shader:
(1)它是一個擴展名為.fx的文件
(2)它的入口是technique,這玩意好比main
(3)shader里面還為vertex shader和pixel shader分別定義了頂點類型
(4)分別實現了vertex shader和pixel shader函數
(5)vertex buffer里的數據送進GPU后,會先讓vertex shader處理,然后再送進pixel shader
(6)別忘了類型匹配那些事兒
ModelClass:
(1)這是一個模型類,雖然現在這個模型很簡單
(2)模型類創建了vertex buffer(注意頂點順序)和index buffer,并且設置了具體的值(也就是把三角形的各個頂點坐標和顏色值都寫進了buffer里面)
(3)模型類激活了vertex buffer和index buffer,讓GPU知道,這塊數據可以進行繪制了。
(4)模型類告訴GPU,用三角形的方式繪制buffer里的內容
ColorShaderClass:
(1)ColorShaderClass是用來調用shader的
(2)ColorShaderClass要創建并設置與shader中定義的頂點類型匹配的layout,讓GPU知道vertex buffer中數據的格式
(3)ColorShaderClass會獲取shader中那三個全局矩陣的指針,并設置這三個矩陣的值
(4)ColorShaderClass會獲取technique的描述,讓GPU知道調用哪些shader函數去繪制,然后循環調用technique中的各個pass進行繪制
CameraClass:
(1)它會設置鏡頭位置和旋轉角度
(2)它會根據鏡頭位置和旋轉角度生成視點變換矩陣
最后,還有一幅惡心的大圖,描述了程序的整個流程和關鍵數據的傳輸途徑:
PS:英文原文教程地址http://www.rastertek.com/dx10tut04.html,根據自己的需要進行了小小改動。
總結
以上是生活随笔為你收集整理的GPU Shader 编程基础的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 减肥期间能吃饺子吗
- 下一篇: 监督学习和非监督学习