上一節的筆記自己寫的十分糟糕,那個程序也寫的十分糟糕。。。。。。。。。如果真的有人看的話,說聲抱歉。
這一節主要是記錄一個旋轉的正方形的制作過程,先說好:以下所有內容請配合上傳了的代碼食用。。。。。。。。。。如果真的有人看的話。
首先,先大概介紹一下繪制一個圖形的基本流程:
一.創建基本的D3D對象:
1.使用D3D11CreateDeviceAndSwapChain創建D3D設備對象與交換鏈。
2.使用CreateRenderTargetView創建后一個繪制緩沖區。
3.如果需要,創建模板與景深緩沖區。
4.使用RSSetViewports設置視窗。
二.編譯,鏈接到effect:
1.組建,加載頂點着色器。
2.初始化着色器的輸入布局。
3.組建,加載像素着色器。
三.創建繪制圖形:
1.創建,定義頂點坐標,創建相應的緩沖區,用已定義的頂點初始化,再將創建好的緩沖區綁到設備輸入槽上去。
2.創建頂點索引,創建相應的緩沖區,用以創建的索引初始化。
3.設置好投影空間的位置,中心坐標,z軸坐標(下面再進行詳細介紹)。
4.HLSL相關。
關於如何創建D3D的一些基本對象就不再敘述了。直接從第二個開始:
首先我們需要一個.fx的文件,關於文件如何組成的之后再說明,我們只要知道現在這個文件給予了程序2個接口:頂點着色器與像素着色器。
關於頂點,我們的圖像在程序中都是由一個個三角形組成的,參見書中的各種圖形,假設這里我們需要繪制一個方形,那么我們至少需要2個三角形,4個頂點去描述這個方形。
頂點着色器通過我們輸入的頂點與它相應的屬性去描述各個三角形通過各種坐標變換,最后組成了我們的圖像。
像素着色器,圖形的顏色是由一個個像素點組成的,像素着色器告訴GPU哪些像素需要着色,需要什么顏色。
(以上是我現在的理解,可能會有許多錯誤)
代碼之前,先對書中內容進行一些討論:由於D3DX系列的函數在我現在的環境下無法使用,所以書中的代碼是無法正常通過編譯的,主要是書中使用了D3DX11CompileFromFile去編譯fx文件,這對我的學習產生了極大的困擾。
關於這個問題的解決方案,看一下stackoverflow的這個回答。
http://stackoverflow.com/questions/30579016/d3d11-d3dx11createeffectfrommemory-returns-e-noiterface
以及
http://stackoverflow.com/questions/12549472/using-directx-effect11-with-visual-studio-2012
總之,雖然我下載了D3DX的開源代碼,並且編譯成功了,但是還是沒有成功搭建出使用D3DX11CompileFromFile以及之類的函數的環境,所以選擇使用其他的函數。
我的代碼
HRESULT DrawFunction::CompileTheShader(void) { HRESULT hr = S_OK;//ret ID3DBlob* pVSBlob = nullptr; ID3DBlob* pErrorBlob = nullptr; //Compiler vertex shader //組建加載頂點着色器 hr = D3DCompileFromFile(L"Squance.fx", nullptr, nullptr, "VS", "vs_5_0", D3DCOMPILE_ENABLE_STRICTNESS, 0, &pVSBlob, &pErrorBlob); if (FAILED(hr)) { #ifdef _DEBUG printf("Can not find FX File .\nPlease run this executable from the directory that contains the FX file\n Error HRESULT:%ld\n\n", hr); return hr; #else MessageBox( nullptr, L"The FX file cannot be compiled. Please run this executable from the directory that contains the FX file.", L"Error", MB_OK ); return hr; #endif } //Create VERTEX shader hr = dev->CreateVertexShader(pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), nullptr, &pVertexShader); if (FAILED(hr)) { pVSBlob->Release(); return hr; } // Define the input layout //定義輸入布局 D3D11_INPUT_ELEMENT_DESC layout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 }, }; UINT numElements = ARRAYSIZE(layout); // Create the input layout hr = dev->CreateInputLayout(layout, numElements, pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), &pVertexLayout); if (FAILED(hr)) return hr; // Set the input layout devContext->IASetInputLayout(pVertexLayout); // Compile the pixel shader //組建加載像素着色器 ID3DBlob* pPSBlob = nullptr; hr = D3DCompileFromFile(L"Squance.fx", nullptr, nullptr, "PS", "ps_5_0", D3DCOMPILE_ENABLE_STRICTNESS, 0, &pPSBlob, &pErrorBlob); if (FAILED(hr)) { #ifdef _DEBUG printf("Can not find FX File .\nPlease run this executable from the directory that contains the FX file\n Error HRESULT:%ld\n\n", hr); return hr; #else MessageBox(nullptr, L"The FX file cannot be compiled. Please run this executable from the directory that contains the FX file.", L"Error", MB_OK); return hr; #endif } // Create the pixel shader hr = dev->CreatePixelShader(pPSBlob->GetBufferPointer(), pPSBlob->GetBufferSize(), nullptr, &pPixelShader); if (FAILED(hr)) return hr; //release resource pVSBlob->Release(); pPSBlob->Release(); return hr; }
關於輸入布局:
對於頂點着色器,我們需要知道輸入的頂點包含了哪些結構與元素,這里我就只包含了一個頂點的坐標與顏色(頂點的顏色。。。。好奇怪。。。)
創建繪制的圖像:
上面提到了三角形與圖像的關系,還是用方形為例,一個方形需要4個頂點,所以就定義出來
這里我定義了2個方形,因為在繪圖的時候會出現背面消影的問題,如果我需要一個旋轉的,二面的方形,就需要把它的正反面都定義出來。
struct SimpleVertex { DirectX::XMFLOAT3 Pos; DirectX::XMFLOAT4 Color; }; SimpleVertex vertices[] = { //正面 { DirectX::XMFLOAT3(+1.0f, +1.0f, +0.0f), DirectX::XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },//紅 { DirectX::XMFLOAT3(+1.0f, -1.0f, +0.0f), DirectX::XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f) },//黃 { DirectX::XMFLOAT3(-1.0f, -1.0f, +0.0f), DirectX::XMFLOAT4(0.0f, 0.5f, 1.0f, 1.0f) },//藍 { DirectX::XMFLOAT3(-1.0f, +1.0f, +0.0f), DirectX::XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },//綠 //反面 { DirectX::XMFLOAT3(+1.0f, +1.0f, -0.1f), DirectX::XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) }, { DirectX::XMFLOAT3(+1.0f, -1.0f, -0.1f), DirectX::XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f) }, { DirectX::XMFLOAT3(-1.0f, -1.0f, -0.1f), DirectX::XMFLOAT4(0.0f, 0.5f, 1.0f, 1.0f) }, { DirectX::XMFLOAT3(-1.0f, +1.0f, -0.1f), DirectX::XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) } };
定義頂點索引:
頂點索引的原理在書中已經說明了,目的就是為了不對頂點進行復刻,一個頂點只需要定義一次,減少內存與處理器的負擔。
WORD indices[] = { 0, 2, 1, 0, 3, 2, 4, 5, 6, 4, 6, 7 };
這是一個以上面的8個頂點為例的索引,可以看見創建了4個三角形。
然后需要注意一點:背面消隱,具體看書中5.10.2的部分,簡而言之,一個三角形只有一面是“有顏色”去顯示的,而三角形的正反面是由你定義三角形時的頂點順序有關的
投影的視角,坐標:
與其叫投影,我更喜歡將他比喻成相機或眼睛。
想象一下,我們繪制好的圖形需要一個攝像機去把它放到我們的屏幕上,需要以下幾個參數:
1.攝像機的位置,相對於世界空間,攝像機的擺放在哪個坐標。
2.攝像機的朝向,也就是鏡頭的方向,鏡頭向哪里拍攝。
3.垂直方向,在上面2項決定好的情況下,還需要一個固定鏡頭垂直的方向,來防止鏡頭隨意的旋轉,來告訴鏡頭Z軸。
DirectX::XMVECTOR Eye = DirectX::XMVectorSet(0.0f, 0.0f, 4.0f, 0.0f);; //眼睛坐標 DirectX::XMVECTOR Point = DirectX::XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); //看向的位置 DirectX::XMVECTOR Eye_Up = DirectX::XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);//定義眼睛"向上"的方向 View_View = DirectX::XMMatrixLookAtLH(Eye, Point, Eye_Up); View_Projection = DirectX::XMMatrixPerspectiveFovLH(DirectX::XM_PIDIV2, 800 / (FLOAT)600, 0.01f, 100.0f);
上面的代碼中,我將鏡頭設置在(0,0,4)的位置,看向原點(0,0,0),鏡頭的垂直方向是(0,1,0)就是Y軸。
HLSL語言:
使用時,需要設置FX文件的屬性為不參與生成,否則會報一個沒有main入口的錯誤。
着色器的編寫需要HLSL這種語言,好在他與C++極其相似。聽說以前的着色器語言是用匯編寫的。。。這真是太糟糕了。
對於語法什么的也沒啥好講的,主要是1個功能:
常量緩沖
C++代碼中有:
//結構定義 struct ConstantBuffer //用於描述視角坐標 { DirectX::XMMATRIX mWorld; DirectX::XMMATRIX mView; DirectX::XMMATRIX mProjection; }; //創建 bd.Usage = D3D11_USAGE_DEFAULT; bd.ByteWidth = sizeof(ConstantBuffer); bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; bd.CPUAccessFlags = 0; hr = dev->CreateBuffer(&bd, nullptr, &pConstantBuffer); if (FAILED(hr)) { #ifdef _DEBUG printf("Can not create the constant buffer\n Error HRESULT:%ld\n\n", hr); #endif return hr; }
fx文件中有:
cbuffer ConstantBuffer : register(b0) { matrix World; matrix View; matrix Projection; } VS_OUTPUT VS(float4 Pos : POSITION, float4 Color : COLOR) { VS_OUTPUT output = (VS_OUTPUT)0; output.Pos = mul(Pos, World); output.Pos = mul(output.Pos, View); output.Pos = mul(output.Pos, Projection); output.Color = Color; return output; }
可以看作着色器與程序共享了一塊內存,運行時,着色器不能改變常量緩沖,我們通過C++代碼改變常量緩沖的內容,加以該變着色器的顯示。
最后,也是最重要的,數學變換原理:
1.局部坐標,世界坐標相互轉換:
上面的的內容說了,我們在世界坐標的某點有一個攝像機,然而,對於程序而言,繪圖就是繪制一個個像素點,坐標分析的步驟需要我們去完成:
在世界坐標內有一個坐標點,那么在我們一相機為原點的坐標系內它的坐標是什么呢?
部分代碼:
View_World = DirectX::XMMatrixIdentity();//XMMatrixIdentity構建單位矩陣 // Initialize the view matrix DirectX::XMVECTOR Eye = DirectX::XMVectorSet(0.0f, 0.0f, 4.0f, 0.0f);; //眼睛坐標 DirectX::XMVECTOR Point = DirectX::XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); //看向的位置 DirectX::XMVECTOR Eye_Up = DirectX::XMVectorSet(0.0f, 1.0f, 100.0f, 0.0f);//定義眼睛"向上"的方向 View_View = DirectX::XMMatrixLookAtLH(Eye, Point, Eye_Up); //XMMatrixLookAtLH函數返回的是世界->視圖變換矩陣
代碼中定義了一個單位矩陣作為原世界坐標,在(0,0,4)這點定義了相機,然后看一個API:XMMatrixLookAtLH,它的作用是產生一個變換矩陣,用於將世界坐標轉換為局部坐標,也就是一相機為原點的坐標。描述一下它的實現原理:
將固定好的相機方向單位化,計算出從局部坐標轉換成世界坐標的矩陣,然后求這個矩陣的逆矩陣,就是從世界坐標轉換為局部坐標的轉換矩陣。
我們可以直接在書中3.4.5節找到直接求逆的公式,完整的用代碼表示就是:
XMMATRIX XM_CALLCONV XMMatrixLookAtLH ( FXMVECTOR Eye FXMVECTOR At FXMVECTOR Up ) zaxis = normal(At - Eye) xaxis = normal(cross(Up, zaxis)) yaxis = cross(zaxis, xaxis)
對應矩陣:
xaxis.x yaxis.x zaxis.x 0 xaxis.y yaxis.y zaxis.y 0 xaxis.z yaxis.z zaxis.z 0 -dot(xaxis, eye) -dot(yaxis, eye) -dot(zaxis, eye) 1
這里normal指求單位向量,cross指向量叉乘,dot指向量點乘
2.投影空間
現在,我們有了一個自己的相機坐標,與相對於相機坐標的通過世界——》局部矩陣轉換后的點。
那么,我們需要一個投影方式,將他push到我們的屏幕上。
投影空間可以看作一個4棱錐的投射。在書中,我們需要以下4個變量表示這個錐形:
1.垂直視角域
2.縱橫比
3.近剪裁面
4.遠剪裁面
示例:我們現在以自己的眼睛為例,眼睛看向的方向為z軸,頭的向上的方向為y軸,肩膀的方向為x軸。
很明顯,眼睛向上的視角是有限的,你當然不可能看到額頭上有什么,這個視角,就是垂直視角域。那么,水平視角域怎么處理呢?它使用了一個縱橫比,就是你水平可以看見的長度的極限除以你垂直可以看見長度 的極限。至於遠,近裁剪面,定義了你的視野最遠與最近可以看見的東西。
代碼:
View_Projection = DirectX::XMMatrixPerspectiveFovLH(DirectX::XM_PIDIV2, 800 / (FLOAT)600, 0.01f, 100.0f);
這里,我定義了垂直視角為90度,這里指的是總視角0到180度的,橫縱比為800/600,最近顯示0.01,最遠顯示100。
好,看一下我定義的邊長為2的正方形:
{ DirectX::XMFLOAT3(+1.0f, +1.0f, +0.0f), DirectX::XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },//紅 { DirectX::XMFLOAT3(+1.0f, -1.0f, +0.0f), DirectX::XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f) },//黃 { DirectX::XMFLOAT3(-1.0f, -1.0f, +0.0f), DirectX::XMFLOAT4(0.0f, 0.5f, 1.0f, 1.0f) },//藍 { DirectX::XMFLOAT3(-1.0f, +1.0f, +0.0f), DirectX::XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },//綠
以及攝像機的位置:
DirectX::XMVECTOR Eye = DirectX::XMVectorSet(0.0f, 0.0f, 4.0f, 0.0f);; //眼睛坐標 DirectX::XMVECTOR Point = DirectX::XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); //看向的位置 DirectX::XMVECTOR Eye_Up = DirectX::XMVectorSet(0.0f, 1.0f, 100.0f, 0.0f);//定義眼睛"向上"的方向
攝像機在(0,0,4)看向(0,0,0)
那么可以計算出這個正方形的高度占顯示總高度的大小,為4分之1,就是說,我們的如果程序窗口的高度為600像素(假設D3D占滿整個屏幕),那么顯示的時候這個正方形邊長為150像素。
其它:
我還是沒搞懂HLSL中mul的詳細作用,以及投影詳細的數學推導,什么時候看懂了再寫。