日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

GPU Shader 编程基础

發(fā)布時(shí)間:2023/12/4 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 GPU Shader 编程基础 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

轉(zhuǎn)載自:http://www.cnblogs.com/youthlion/archive/2012/12/07/2807919.html

幾個(gè)基本概念:

Vertex buffer:存儲頂點(diǎn)的數(shù)組。當(dāng)構(gòu)成模型的所有頂點(diǎn)都放進(jìn)vertex buffer后,就可以把vertex buffer送進(jìn)GPU,然后GPU就可以渲染模型了。

Index buffer:這個(gè)buffer的作用是索引。記錄每個(gè)頂點(diǎn)在vertex buffer中的位置。DX SDK里說使用index buffer可以增加頂點(diǎn)數(shù)據(jù)被緩存在顯存里的概率,所以就效率而言應(yīng)該使用index buffer。

Vertex Shader:vertex shader是一類小程序,主要用來把vertex buffer里的頂點(diǎn)變換到3D空間中去。也可以用vertex shader干點(diǎn)別的,比如算頂點(diǎn)的法向量。對于每個(gè)要處理的頂點(diǎn),GPU都會調(diào)用vertex shader。比如5000個(gè)三角形的網(wǎng)格模型,每一幀就得調(diào)用vertex shader 15000次,每秒60幀的話,vertex shader還真得寫得靠譜點(diǎn)。

Pixel Shader:是一般用來處理多邊形顏色的小程序。對于場景里每個(gè)要畫到屏幕上的可見像素,GPU都會調(diào)用Pixel Shader來處理。像著色、光照,還有大多數(shù)用在多邊形上的其他效果,都要靠Pixel Shader來搞定。沒錯(cuò),這個(gè)東西也得寫的靠譜點(diǎn)。效率,效率啊。要不可能GPU還沒有CPU算得快。

HLSL:這就是用來寫各種shader程序的語言了。HLSL程序包括全局變量、類型定義、vertex shaders,pixel shaders還有g(shù)eometry shaders。

程序的整體結(jié)構(gòu):

這一次接著上一篇筆記的程序繼續(xù)擴(kuò)展。在這篇筆記中,我們會用shader繪制一個(gè)綠色的三角形。這里三角形是要繪制的對象,實(shí)際上是一個(gè)簡單的多邊形模型,也就是數(shù)據(jù),所以把它封裝到一個(gè)模型類(ModelClass)中,并集成到文檔類里去。視圖類負(fù)責(zé)顯示,而顯示的功能是由shader完成的。正如前面說的,shader是一段小程序,上面的圖中集成到視圖類中的ColorShaderClass負(fù)責(zé)調(diào)用shader,也就是讓這段shader小程序運(yùn)行起來。這就是這篇筆記中程序的最宏觀結(jié)構(gòu)。

第一個(gè)shader程序:

在工程中添加一個(gè)名為color.fx的源文件,看來shader程序源文件的擴(kuò)展名是.fx了。這個(gè)shader的目的是繪制一個(gè)綠色的三角形。

這個(gè)shader首先聲明了三個(gè)全局矩陣變量,便于其他類從外部訪問,再傳回shader。

/ // GLOBALS // / matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix;

下面幾行代碼里,使用HLSL中的float4類型創(chuàng)建了一個(gè)位置向量,包括x、y、z、w,以及一個(gè)顏色向量,包括red、green、blue、alpha分量。其中POSITION、COLOR和SV_POSITION是傳遞給GPU的語義信息,讓GPU知道這些變量是干嘛用的。下面的兩個(gè)類型貌似作用是一樣的,但是必須分別創(chuàng)建。因?yàn)閷τ趘ertex shader和pixel shader,需要不同的語義。POSITION是對應(yīng)vertex shader,SV_POSITION適用于pixel shader,COLOR則是兩者通用。如果需要同一類型的多個(gè)成員,就得在類型后面加上個(gè)數(shù)值后綴,像COLOR0、COLOR1這種。

// // TYPEDEFS // // struct VertexInputType { float4 position : POSITION; float4 color : COLOR; }; ? struct PixelInputType { float4 position : SV_POSITION; float4 color : COLOR; };

?

當(dāng)vertex buffer中的數(shù)據(jù)被送進(jìn)GPU進(jìn)行處理的時(shí)候,GPU會調(diào)用vertex shader。下面定義了一個(gè)名為ColorVertexShader的函數(shù),該函數(shù)在處理vertex buffer中每個(gè)頂點(diǎn)的時(shí)候都會被調(diào)用。vertex shader的輸入必須與vertex buffer緩沖區(qū)中的數(shù)據(jù)以及shader源文件中的類型定義相匹配。這里就是VertexInputType。vertex shader的輸出會被送進(jìn)pixel shader,這里輸出類型為上面定義的PixelInputType。

下面代碼中的vertex shader流程是這樣的:他先創(chuàng)建一個(gè)輸出變量,類型為PixelInputType,然后拿到輸入頂點(diǎn)的坐標(biāo),把世界矩陣、視點(diǎn)矩陣、投影矩陣挨個(gè)乘上去,對頂點(diǎn)進(jìn)行變換,最后頂點(diǎn)會被變換到我們視點(diǎn)所觀察的3D空間中的正確位置。然后拿到輸入的顏色值,放進(jìn)輸出變量里,把輸出變量返回,返回的輸出變量接下來會被送進(jìn)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把多邊形上那些要渲染到屏幕上的像素繪制出來。下面的這個(gè)pixel shader以PixelInputType作為輸入,返回一個(gè)float4類型,這個(gè)float4就是最后的像素顏色值。下面這個(gè)pixel shader僅僅是把像素著色為輸入的顏色值。重申,vertex shader的輸出是pixel shader的輸入。 // Pixel Shader float4 ColorPixelShader(PixelInputType input) : SV_Target { return input.color; }

下面幾行代碼里的technique才是真正意義的shader。這個(gè)東西是用來渲染多邊形、調(diào)用vertex shader和pixel shader的,可以把它看做是HLSL的main()函數(shù)。在technique里面可以設(shè)定多個(gè)pass,調(diào)用各種vertex shader和pixel shader來組合出想要的效果。這個(gè)例子里只使用了一個(gè)pass,也只調(diào)用了上面寫好的vertex和pixel shader。geometry shader暫時(shí)不用,這里也沒有調(diào)用。

還有個(gè)值得注意的事兒,代碼里用vs_4_0指定vertex shader的版本為4.0,這是SetVertexShader函數(shù)的第一個(gè)參數(shù)。這樣我們才可以使用DX10 HLSL中vertex shader4.0相應(yīng)的功能。pixel shader也是類似。

// Technique technique10 ColorTechnique { pass pass0 { SetVertexShader(CompileShader(vs_4_0, ColorVertexShader())); SetPixelShader(CompileShader(ps_4_0, ColorPixelShader())); SetGeometryShader(NULL); } }

以上是這個(gè)例子的shader部分。也就是負(fù)責(zé)實(shí)際渲染工作的模塊。那么shader渲染的是神馬?恩,模型。

所以要在工程里再添加個(gè)模型類:

這個(gè)例子里,我們的模型僅僅是個(gè)三角形,暫時(shí)用原教程給的一個(gè)模型類ModelClass,后面如果需要,爭取把這個(gè)模型類用CGAL的Polyhedron替換掉。下面先看一下ModelClass的頭文件:

首先在ModelClass中添加頂點(diǎn)類型的定義。這也是vertex buffer的類型。

struct VertexType { D3DXVECTOR3 position; D3DXVECTOR4 color; };

構(gòu)造和析構(gòu)函數(shù):

ModelClass(); ModelClass(const ModelClass&); ~ModelClass();

下面的幾個(gè)函數(shù)負(fù)責(zé)初始化和釋放模型的vertex和index buffer。Render函數(shù)負(fù)責(zé)把模型的幾何屬性送到顯卡上,準(zhǔn)備讓shader繪制。

bool Initialize(ID3D10Device*); void Shutdown(); void Render(ID3D10Device*); ? int GetIndexCount();

上面的幾個(gè)公有函數(shù)的功能通過調(diào)用下面的幾個(gè)私有函數(shù)實(shí)現(xiàn):

private: bool InitializeBuffers(ID3D10Device*); void ShutdownBuffers(); void RenderBuffers(ID3D10Device*);

添加幾個(gè)私有變量,分別作為vertex buffer和index buffer的指針,另外還有兩個(gè)整型,用來記錄兩塊buffer的大小。注意DX10里buffer一般用通用的ID3D10Buffer類型,這種類型的變量在創(chuàng)建的時(shí)候可以用buffer description進(jìn)行描述。

private: ID3D10Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount;

ModelClass類的實(shí)現(xiàn)部分,先是構(gòu)造和析構(gòu)函數(shù):

ModelClass::ModelClass() { m_vertexBuffer = NULL; m_indexBuffer = NULL; } ? ? ModelClass::ModelClass(const ModelClass& other) { } ? ? ModelClass::~ModelClass() { }

初始化函數(shù):

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函數(shù)實(shí)際是在框架的繪制模塊里調(diào)用的,也就是我第二篇筆記中的視圖類,再具體點(diǎn),應(yīng)該就是在視圖類的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函數(shù)返回index的數(shù)量:

int ModelClass::GetIndexCount() { return m_indexCount; }

接下來是Initialize、ShutDown和Render對應(yīng)的幾個(gè)私有方法的具體實(shí)現(xiàn),首先是InitializeBuffers,這個(gè)函數(shù)負(fù)責(zé)創(chuàng)建vertex buffer和index buffer。在實(shí)際的應(yīng)用里,一般是從數(shù)據(jù)文件里把模型讀進(jìn)來(.off,.obj,.ply等等)然后創(chuàng)建buffer,在這個(gè)例子里,因?yàn)槟P椭皇且粋€(gè)三角形,所以直接在vertex buffer和index buffer里人工設(shè)置了三個(gè)點(diǎn)。

bool ModelClass::InitializeBuffers(ID3D10Device* device) { VertexType* vertices; unsigned long* indices; D3D10_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D10_SUBRESOURCE_DATA vertexData, indexData; HRESULT result;

首先創(chuàng)建兩個(gè)數(shù)組,用來存儲頂點(diǎn)和索引數(shù)據(jù)。后面會用這兩個(gè)數(shù)組去填充最終的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; }

然后分別對頂點(diǎn)屬性和頂點(diǎn)索引賦值。留心,下面的代碼是按照順時(shí)針的順序創(chuàng)建頂點(diǎn)的。如果逆時(shí)針創(chuàng)建的話,程序會認(rèn)為這個(gè)三角形是屁股朝著屏幕。如果恰好又設(shè)置了背面剔除的話,程序就不再繪制這個(gè)三角形了。所以說,被送進(jìn)GPU的頂點(diǎn)順序是有講究的。

// 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數(shù)組和index數(shù)組搞定后,可以用它們來創(chuàng)建vertex buffer和index buffer。兩種buffer的創(chuàng)建方式是一樣的:首先填好buffer的description。在這個(gè)description里面ByteWidth(buffer的大小)和BindFlags(buffer類型)必須得填對。填好description后,還要分別填一個(gè)subresource指針,這量個(gè)指針分別指向前面創(chuàng)建的vertex數(shù)組和index數(shù)組。description和subresource都填好之后,就可以用D3D device調(diào)用CreateBuffer,這個(gè)函數(shù)會返回指向新創(chuàng)建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創(chuàng)建后,就可以卸磨殺驢,干掉vertex數(shù)組和index數(shù)組了:

// 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; }

接下來是負(fù)責(zé)釋放vertex buffer和index buffer的ShutdownBuffers函數(shù):

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; }

下面是對應(yīng)Render函數(shù)的私有函數(shù)RenderBuffers。這個(gè)函數(shù)的作用是把GPU中input assembler上的vertex buffer和index buffer設(shè)置為激活狀態(tài)。一旦有了一塊激活的vertex buffer,GPU就可以用我們寫的HLSL shader去渲染這塊buffer。RenderBuffers函數(shù)還規(guī)定了這些buffer的繪制方式,比如繪制三角形、繪制直線神馬的。這一篇筆記里,我們在input assembler上激活index buffer和vertex buffer,并通過DX10的IASetPrimitiveTopology函數(shù)告訴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類搞定。整理一下宏觀的思路:現(xiàn)在我們有了模型,也有了shader,可以用shader去渲染模型了。

問題在于,shader是怎樣開始運(yùn)行的呢?

我們用下面這個(gè)ColorShaderClass類來調(diào)用shader。

這個(gè)類的Initialize和Shutdown兩個(gè)成員函數(shù)完成對shader的初始化和關(guān)閉,Render成員函數(shù)負(fù)責(zé)設(shè)置shader的參數(shù),然后用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; }; 這個(gè)類和模型類的結(jié)構(gòu)類似。在Initialize函數(shù)里,真正負(fù)責(zé)shader初始化的是InitializeShader,我們要傳給這個(gè)函數(shù)三個(gè)參數(shù):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調(diào)用ShutdownShader關(guān)閉shader:

void ColorShaderClass::Shutdown() { // Shutdown the shader effect. ShutdownShader(); ? return; }

Render函數(shù)里做兩件事:1、設(shè)置shader參數(shù),通過SetShaderParameters完成;2、用shader繪制綠三角,調(diào)用RenderShader完成:

void ColorShaderClass::Render(ID3D10Device* device, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix) { // Set the shader parameters that it will use for rendering. SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix); ? // Now render the prepared buffers with the shader. RenderShader(device, indexCount); ? return; }

在下面的InitializeShader函數(shù)中我們可以看到,shader實(shí)際上是在這里加載的。在這個(gè)函數(shù)里,我們還需要設(shè)置一個(gè)layout,這個(gè)layout需要與模型類及color.fx類中定義的頂點(diǎn)類相匹配:

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函數(shù)中,shader程序被編譯為一個(gè)effect。這個(gè)函數(shù)的幾個(gè)重要參數(shù)包括shader文件名、shader版本(DX10是4.0)、還要制定要把shader編譯到哪個(gè)effect里去(對應(yīng)ColorShaderClass類的m_effect成員)。如果在編譯shader的過程中失敗的話,D3DX10CreateEffectFromFile會把一條錯(cuò)誤消息放到errorMessage里,我們會把這個(gè)字符串塞給另一個(gè)函數(shù)OutputShaderErrorMessage去輸出錯(cuò)誤信息。要是編譯失敗了,卻還沒有錯(cuò)誤信息的話,可能是找不到shader文件,對這種情況我們會彈出一個(gè)對話框作為提示。

// 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; }

當(dāng)shader文件成功編譯為effect后,就可以用這個(gè)effect找到shader里的那個(gè)technique。我們后面用這個(gè)technique進(jìn)行繪制:

// Get a pointer to the technique inside the shader. m_technique = m_effect->GetTechniqueByName("ColorTechnique"); if(!m_technique) { return false; }

下一步,shader所處理的頂點(diǎn),還需要?jiǎng)?chuàng)建并設(shè)置一個(gè)layout。在這一篇筆記里,shader使用了一個(gè)位置向量和一個(gè)顏色向量,所以我們在layout中也要?jiǎng)?chuàng)建對應(yīng)的元素,用來指明位置和顏色信息的內(nèi)存占用情況。首先要填充的是語義信息,這樣shader才能知道這個(gè)layout元素的用途。對于位置信息,我們使用POSITION,顏色信息用COLOR。另一個(gè)重要信息是格式,位置信息我們用DXGI_FORMAT_R32G32B32_FLOAT,顏色信息用DXGI_FORMAT_R32G32B32A32_FLOAT。最后要注意的是AlignedByteOffset,這個(gè)字段指定了buffer中數(shù)據(jù)存儲的起點(diǎn)。對于本例來說,前12個(gè)字節(jié)是位置,隨后的16個(gè)字節(jié)是顏色。這個(gè)字段可以用D3D10_APPEND_ALIGNED_ELEMENT代替,表示DX10會自動計(jì)算。layout的其他字段暫時(shí)不會用到,這里使用默認(rèn)設(shè)置:

// 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數(shù)組設(shè)置好之后,我們計(jì)算一下它包含的元素個(gè)數(shù),然后用device創(chuàng)建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里面那三個(gè)矩陣的指針,這樣以后就能用這三個(gè)指針設(shè)置矩陣的值:

// 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函數(shù)負(fù)責(zé)釋放資源:

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時(shí)若發(fā)生問題,錯(cuò)誤信息由OutputShaderErrorMessage函數(shù)輸出:

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函數(shù)便于我們設(shè)置shader中的全局變量。這個(gè)函數(shù)中的三個(gè)矩陣是在上一篇筆記中的視圖類里創(chuàng)建的,矩陣被創(chuàng)建之后,負(fù)責(zé)繪圖的代碼會調(diào)用這個(gè)函數(shù),把這三個(gè)矩陣送進(jìn)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函數(shù)執(zhí)行后,各種參數(shù)(這里實(shí)際就是那仨矩陣)設(shè)置完成,ColorShaderClass類隨后調(diào)用RenderShader,RenderShader通過technique指針調(diào)用color.fx文件中的shader程序。

RenderShader函數(shù)上來先把input layout激活,這樣GPU才能知道vertex buffer里數(shù)據(jù)的格式。接下來要從shader中獲取technique的描述,這個(gè)technique告訴GPU調(diào)用哪個(gè)vertex shader或pixel shader來繪制vertex buffer里的數(shù)據(jù)。本例中我們獲取的是color.fx中ColorTechnique的描述,然后通過device調(diào)用DrawIndexed函數(shù),循環(huán)調(diào)用technique中的各個(gè)pass來渲染三角形。目前的例子里,shader只有一個(gè)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; }

到這里,我們搞定了一個(gè)HLSL shader,設(shè)置了vertex buffer和index buffer,并了解了如何調(diào)用shader繪制兩種buffer中的數(shù)據(jù)。除此之外,還有一些輔助性的工作要做。第一個(gè)問題是,我們繪制的那些內(nèi)容,是相對于哪個(gè)視點(diǎn)的?

好吧,所以我們還需要來個(gè)鏡頭類:

鏡頭類告訴DX10,鏡頭是從哪里、以及怎樣去觀察場景的。鏡頭類會始終跟蹤鏡頭的位置及其旋轉(zhuǎn),使用位置和旋轉(zhuǎn)信息生成一個(gè)視點(diǎn)矩陣,這個(gè)視點(diǎn)矩陣會傳進(jìn)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函數(shù)用來設(shè)置鏡頭對象的位置和旋轉(zhuǎn)。Render函數(shù)基于位置和旋轉(zhuǎn)信息創(chuàng)建視點(diǎn)矩陣。GetViewMatrix用來訪問視點(diǎn)矩陣。

構(gòu)造函數(shù)把位置和旋轉(zhuǎn)設(shè)置為場景的原點(diǎn):

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() { }

兩個(gè)set函數(shù):

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; }

兩個(gè)get函數(shù):

D3DXVECTOR3 CameraClass::GetPosition() { return D3DXVECTOR3(m_positionX, m_positionY, m_positionZ); } ? ? D3DXVECTOR3 CameraClass::GetRotation() { return D3DXVECTOR3(m_rotationX, m_rotationY, m_rotationZ); }

Render函數(shù)用位置和旋轉(zhuǎn)信息構(gòu)造和更新視點(diǎn)矩陣。這里除了位置和旋轉(zhuǎn),還需要指定一個(gè)“上”方向和鏡頭朝向。接下來,首先在原點(diǎn)處根據(jù)x,y,z的值旋轉(zhuǎn)鏡頭,旋轉(zhuǎn)之后再把鏡頭移動到三維空間中的指定位置上。當(dāng)位置、旋轉(zhuǎn)、方向“上”和所觀察的位置都確定下來后,就可以用DX10中的D3DXMatrixLookAtLH函數(shù)創(chuàng)建視點(diǎn)矩陣了:

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; }

至此,我們搞定了負(fù)責(zé)渲染的shader、負(fù)責(zé)調(diào)用shader的ColorShaderClass、用來存儲模型的ModelClass,以及負(fù)責(zé)管理視點(diǎn)信息的CamaraClass。

下面的故事是,在MFC的MDI框架中,應(yīng)該怎樣用這些類?

從功能上看,文檔和視圖分別對應(yīng)數(shù)據(jù)和顯示,在這個(gè)例子里,模型是數(shù)據(jù)(ModelClass),ColorShaderClass實(shí)現(xiàn)顯示(實(shí)際上是shader,color.fx),所以模型嵌入到文檔類,而ColorShaderClass集成進(jìn)視圖類。

在文檔類中添加ModelClass類指針,這里為了訪問方便,直接設(shè)置為public:

#include "ModelClass.h" class CMy02_01Doc : public CDocument { ... public: ModelClass * m_pMesh; ... };

目前文檔類要改的有三處:

構(gòu)造函數(shù):

CMy02_01Doc::CMy02_01Doc() { // TODO: add one-time construction code here m_pMesh = NULL; }

在新建文檔時(shí)創(chuàng)建模型:

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; }

關(guān)閉文檔時(shí)釋放內(nèi)存:

CMy02_01Doc::~CMy02_01Doc() { if(m_pMesh) { m_pMesh->Shutdown(); delete m_pMesh; m_pMesh = NULL; } }

接下來是顯示相關(guān)的內(nèi)容。鏡頭類和ColorShaderClass類都集成到視圖類中。

#include <CameraClass.h> #include <Colorshaderclass.h> ? class CMy02_01View : public CView { ... CameraClass * m_Camara; ColorShaderClass * m_ColorShader; ... };

構(gòu)造函數(shù):

CMy02_01View::CMy02_01View() { // TODO: add construction code here ... ? m_Camara = NULL; m_ColorShader = NULL; }

給視圖類添加一個(gè)建立camera、shader對象并進(jìn)行初始化的函數(shù)ShaderInitialize,由于模型是在文檔類里創(chuàng)建和銷毀的,所以這里只要弄一個(gè)臨時(shí)指針指向模型對象就行了,不需要操心資源管理的事兒:

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; }

這個(gè)初始化函數(shù)在OnInitialUpdate中DX環(huán)境初始化后調(diào)用:

void CMy02_01View::OnInitialUpdate() { CView::OnInitialUpdate(); ? // TODO: Add your specialized code here and/or call the base class InitDX(); ShaderInitialize(); }

再添加一個(gè)相應(yīng)的資源釋放函數(shù),該函數(shù)在視圖類的析構(gòu)函數(shù)中調(diào)用:

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); } }

運(yùn)行效果,弱爆了T_T

下面理一理頭緒,回憶一下,上面的shader、ColorShaderClass、ModelClass、CameraClass都是咋回事兒來著?

shader:

(1)它是一個(gè)擴(kuò)展名為.fx的文件

(2)它的入口是technique,這玩意好比main

(3)shader里面還為vertex shader和pixel shader分別定義了頂點(diǎn)類型

(4)分別實(shí)現(xiàn)了vertex shader和pixel shader函數(shù)

(5)vertex buffer里的數(shù)據(jù)送進(jìn)GPU后,會先讓vertex shader處理,然后再送進(jìn)pixel shader

(6)別忘了類型匹配那些事兒

ModelClass:

(1)這是一個(gè)模型類,雖然現(xiàn)在這個(gè)模型很簡單

(2)模型類創(chuàng)建了vertex buffer(注意頂點(diǎn)順序)和index buffer,并且設(shè)置了具體的值(也就是把三角形的各個(gè)頂點(diǎn)坐標(biāo)和顏色值都寫進(jìn)了buffer里面)

(3)模型類激活了vertex buffer和index buffer,讓GPU知道,這塊數(shù)據(jù)可以進(jìn)行繪制了。

(4)模型類告訴GPU,用三角形的方式繪制buffer里的內(nèi)容

ColorShaderClass:

(1)ColorShaderClass是用來調(diào)用shader的

(2)ColorShaderClass要?jiǎng)?chuàng)建并設(shè)置與shader中定義的頂點(diǎn)類型匹配的layout,讓GPU知道vertex buffer中數(shù)據(jù)的格式

(3)ColorShaderClass會獲取shader中那三個(gè)全局矩陣的指針,并設(shè)置這三個(gè)矩陣的值

(4)ColorShaderClass會獲取technique的描述,讓GPU知道調(diào)用哪些shader函數(shù)去繪制,然后循環(huán)調(diào)用technique中的各個(gè)pass進(jìn)行繪制

CameraClass:

(1)它會設(shè)置鏡頭位置和旋轉(zhuǎn)角度

(2)它會根據(jù)鏡頭位置和旋轉(zhuǎn)角度生成視點(diǎn)變換矩陣

最后,還有一幅惡心的大圖,描述了程序的整個(gè)流程和關(guān)鍵數(shù)據(jù)的傳輸途徑:

PS:英文原文教程地址http://www.rastertek.com/dx10tut04.html,根據(jù)自己的需要進(jìn)行了小小改動。

總結(jié)

以上是生活随笔為你收集整理的GPU Shader 编程基础的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。