在當前的畫面都是使用三角形,在開始就告訴你們如何畫三角,本文告訴你們如何用像素著色器畫php
本文是 SharpDX 系列博客,更多博客請點擊SharpDX 系列css
在 C# 從零開始寫 SharpDx 應用 初始化dx修改顏色 建立了資源,在這個博客的代碼繼續寫html
爲了建立三角形,須要使用頂點。頂點就是在 3D 空間的點。經過頂點能夠添加數據,不少使用的頂點都使用三個值,就是 xyz 來表示點在三維空間。你們都知道三角形有三個頂點,因此下面來建立三個頂點。git
這裏的頂點的範圍是 0-1,因此可使用下面代碼建立出頂點github
private Vector3[] _vertices = new Vector3[] {new Vector3(-0.5f, 0.5f, 0.0f), new Vector3(0.5f, 0.5f, 0.0f), new Vector3(0.0f, -0.5f, 0.0f)};
private Vector3[] _vertices = new Vector3[] {new Vector3(-0.5f, 0.5f, 0.0f), new Vector3(0.5f, 0.5f, 0.0f), new Vector3(0.0f, -0.5f, 0.0f)};
這時會發現 Vector3 沒有定義,由於沒有安裝SharpDX.Mathematics
,若是使用的是 VisualStudio 2017 格式,那麼複製下面代碼放在項目web
<PackageReference Include="SharpDX.Mathematics" Version="3.1.1" />
<PackageReference Include="SharpDX.Mathematics" Version="3.1.1" />
若是不是就打開 Nuget 安裝 SharpDX.Mathematics ,安裝以後引用using SharpDX
就可使用這個類windows
如今的頂點信息放在了內存,由於使用了上面代碼建立。可是渲染的對象是在顯卡,須要把內存的頂點信息複製到顯卡。爲了作這個須要使用緩存。在 DX ,可使用緩存,dx 會自動複製信息到顯卡。緩存
下面使用緩存來存放頂點信息,這樣就會在使用信息自動複製到顯卡。先寫一個私有變量,經過這個變量把信息放在緩存,請看下面markdown
private D3D11.Buffer _triangleVertexBuffer;
private D3D11.Buffer _triangleVertexBuffer;
寫一個函數用來把 _vertices
轉換 _triangleVertexBuffer
,代碼很簡單app
private void InitializeTriangle() { _triangleVertexBuffer = D3D11.Buffer.Create<Vector3>(_d3DDevice, D3D11.BindFlags.VertexBuffer, _vertices); }
private void InitializeTriangle() { _triangleVertexBuffer = D3D11.Buffer.Create<Vector3>(_d3DDevice, D3D11.BindFlags.VertexBuffer, _vertices); }
這個函數須要在構造使用
// 其餘被忽略的代碼 public KikuSimairme() { _renderForm = new RenderForm(); _renderForm.ClientSize = new Size(Width, Height); InitializeDeviceResources(); InitializeTriangle(); }
// 其餘被忽略的代碼 public KikuSimairme() { _renderForm = new RenderForm(); _renderForm.ClientSize = new Size(Width, Height); InitializeDeviceResources(); InitializeTriangle(); }
如今使用D3D.Buffer.Create
建立新的緩存,這裏的Vector3
實際能夠不須要傳。第一個參數 Direct3D 設備就是建立資源的設備,表示緩存會在哪一個設備使用。第二個參數就是但願建立的類型,這裏寫的是頂點緩存,這裏用的是 VertexBuffer ,除了這個還有 Constant buffer 和 IndexBuffer 。constant代表了constant buffer中的數據,在一次draw call的執行過程當中都是不變的,用來從 CPU 傳數據到 GPU。而IndexBuffer是保存索引編號的緩衝區。關於 Constant Buffer 請看Constant Buffer的高效使用,讓你碼出質量
注意緩存是須要去掉
// 其餘被忽略的代碼 public void Dispose() { _renderTargetView.Dispose(); _swapChain.Dispose(); _d3DDevice.Dispose(); _d3DDeviceContext.Dispose(); _renderForm?.Dispose(); _triangleVertexBuffer.Dispose(); }
// 其餘被忽略的代碼 public void Dispose() { _renderTargetView.Dispose(); _swapChain.Dispose(); _d3DDevice.Dispose(); _d3DDeviceContext.Dispose(); _renderForm?.Dispose(); _triangleVertexBuffer.Dispose(); }
爲了畫出三角形,須要使用頂點着色器和像素着色器。
使用這兩個着色器由於頂點着色器負責加工頂點集合,能夠用來作變換,如移動旋轉頂點。而像素着色器負責每一個像素,如何畫出每一個像素和紋理。
定義兩個私有變量,表示兩個着色器
private D3D11.VertexShader _vertexShader; private D3D11.PixelShader _pixelShader;
private D3D11.VertexShader _vertexShader; private D3D11.PixelShader _pixelShader;
建立的着色器須要使用 D3DCompiler 編譯着色器文件,編譯文件的速度很快
using SharpDX.D3DCompiler; // 其餘被忽略的代碼 private void InitializeShaders() { using (var vertexShaderByteCode = ShaderBytecode.CompileFromFile("VertexShader.hlsl", "main", "vs_4_0", ShaderFlags.Debug)) { _vertexShader = new D3D11.VertexShader(_d3DDevice, vertexShaderByteCode); } using (var pixelShaderByteCode = ShaderBytecode.CompileFromFile("PixelShader.hlsl", "main", "ps_4_0", ShaderFlags.Debug)) { _pixelShader = new D3D11.PixelShader(_d3DDevice, pixelShaderByteCode); } }
using SharpDX.D3DCompiler; // 其餘被忽略的代碼 private void InitializeShaders() { using (var vertexShaderByteCode = ShaderBytecode.CompileFromFile("VertexShader.hlsl", "main", "vs_4_0", ShaderFlags.Debug)) { _vertexShader = new D3D11.VertexShader(_d3DDevice, vertexShaderByteCode); } using (var pixelShaderByteCode = ShaderBytecode.CompileFromFile("PixelShader.hlsl", "main", "ps_4_0", ShaderFlags.Debug)) { _pixelShader = new D3D11.PixelShader(_d3DDevice, pixelShaderByteCode); } }
能夠從代碼發現使用了兩個文件,因此接下來就須要建立兩個文件,這兩個文件使用的是 hlsl 來寫,關於 hlsl 不屬於本文的內容,因此沒有詳細告訴你們,建議複製一下代碼。這裏建立了着色器須要使用下面代碼進行設置
// 其餘被忽略的代碼 _d3DDeviceContext.VertexShader.Set(_vertexShader); _d3DDeviceContext.PixelShader.Set(_pixelShader); _d3DDeviceContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
// 其餘被忽略的代碼 _d3DDeviceContext.VertexShader.Set(_vertexShader); _d3DDeviceContext.PixelShader.Set(_pixelShader); _d3DDeviceContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
如今的 InitializeShaders 方法看起來就是以下
private void InitializeShaders() { using (var vertexShaderByteCode = ShaderBytecode.CompileFromFile("VertexShader.hlsl", "main", "vs_4_0", ShaderFlags.Debug)) { _vertexShader = new D3D11.VertexShader(_d3DDevice, vertexShaderByteCode); } using (var pixelShaderByteCode = ShaderBytecode.CompileFromFile("PixelShader.hlsl", "main", "ps_4_0", ShaderFlags.Debug)) { _pixelShader = new D3D11.PixelShader(_d3DDevice, pixelShaderByteCode); } _d3DDeviceContext.VertexShader.Set(_vertexShader); _d3DDeviceContext.PixelShader.Set(_pixelShader); _d3DDeviceContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList; }
private void InitializeShaders() { using (var vertexShaderByteCode = ShaderBytecode.CompileFromFile("VertexShader.hlsl", "main", "vs_4_0", ShaderFlags.Debug)) { _vertexShader = new D3D11.VertexShader(_d3DDevice, vertexShaderByteCode); } using (var pixelShaderByteCode = ShaderBytecode.CompileFromFile("PixelShader.hlsl", "main", "ps_4_0", ShaderFlags.Debug)) { _pixelShader = new D3D11.PixelShader(_d3DDevice, pixelShaderByteCode); } _d3DDeviceContext.VertexShader.Set(_vertexShader); _d3DDeviceContext.PixelShader.Set(_pixelShader); _d3DDeviceContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList; }
這裏還使用 PrimitiveTopology
設置如何畫出來,更多請看Primitive Topologies
由於用到了兩個特殊的文件,如今右擊項目添加兩個文本。
而後建立一個文本文件,注意文本的名字,一個是 PixelShader.hlsl 另外一個是 VertexShader.hlsl ,須要點擊新建項才能夠建立文本。爲何須要使用文本,由於這樣編譯選項就不須要本身選
如今就建立了兩個文件,請看本身的工程是否存在下面兩個文件
如今須要右擊兩個文件 PixelShader.hlsl
和 VertexShader.hlsl
屬性,選擇輸出
打開 VertexShader.hlsl
而且寫入下面代碼
float4 main(float4 position : POSITION) : SV_POSITION { return position; }
float4 main(float4 position : POSITION) : SV_POSITION { return position; }
上面代碼就是建立一個 main 函數,寫法和 C 差很少,具體的意思在這裏不會告訴你們,由於關於這個的寫法是很複雜,這裏複製就好
打開 PixelShader.hlsl
輸入下面代碼
float4 main(float4 position : SV_POSITION) : SV_TARGET { return float4(1.0, 0.0, 0.0, 1.0); }
float4 main(float4 position : SV_POSITION) : SV_TARGET { return float4(1.0, 0.0, 0.0, 1.0); }
這裏也不解釋代碼的意思
打開 KikuSimairme 類,在構造函數添加 InitializeShaders 初始化
// 其餘被忽略的代碼 public KikuSimairme() { _renderForm = new RenderForm(); _renderForm.ClientSize = new Size(Width, Height); InitializeDeviceResources(); InitializeTriangle(); InitializeShaders(); }
// 其餘被忽略的代碼 public KikuSimairme() { _renderForm = new RenderForm(); _renderForm.ClientSize = new Size(Width, Height); InitializeDeviceResources(); InitializeTriangle(); InitializeShaders(); }
並且在清理的時候須要關閉 _vertexShader
,請看代碼
public void Dispose() { // 其餘被忽略的代碼 _vertexShader.Dispose(); _pixelShader.Dispose(); }
public void Dispose() { // 其餘被忽略的代碼 _vertexShader.Dispose(); _pixelShader.Dispose(); }
若是在var pixelShaderByteCode = ShaderBytecode.CompileFromFile("PixelShader.hlsl", "main", "ps_4_0", ShaderFlags.Debug)
出現 System.IO.FileNotFoundException
,那麼就是 PixelShader.hlsl
右擊屬性沒有輸出到和 exe 相同的文件夾
如今已經有了頂點緩存和頂點數據。可是 DirectX 一樣須要知道數據的結構和每一個頂點類型,因此須要使用輸入層。建立輸入層須要兩步,首先須要描述每一個頂點,而後從頂點建立輸入層。
由於這裏就使用一個頂點集合,因此只須要建立一個輸入元素集合
private D3D11.InputElement[] _inputElements = new D3D11.InputElement[] { new D3D11.InputElement("POSITION", 0, Format.R32G32B32_Float, 0) };
private D3D11.InputElement[] _inputElements = new D3D11.InputElement[] { new D3D11.InputElement("POSITION", 0, Format.R32G32B32_Float, 0) };
這裏的 POSITION
能夠在 shader 的代碼被識別,這個字符串就是語義,用於匹配輸入的材質的簽名。第二個參數 0 就是語義槽的使用,表示使用哪一個,在有多個POSITION
語義的例子才使用。第三個參數就是數據的類型,使用的元素是包括三個浮點數,因此使用 Float
,還記得爲何是三個浮點數?緣由在三維的空間使用三個浮點數能夠表示一個點。
在剛纔的初始化函數獲取簽名,經過編譯的代碼
// 其餘被忽略的代碼 private void InitializeShaders() { ShaderSignature inputSignature; using (var vertexShaderByteCode = ShaderBytecode.CompileFromFile("VertexShader.hlsl", "main", "vs_4_0", ShaderFlags.Debug)) { inputSignature = ShaderSignature.GetInputSignature(vertexShaderByteCode); // 其餘被忽略的代碼 } // 其餘被忽略的代碼 }
// 其餘被忽略的代碼 private void InitializeShaders() { ShaderSignature inputSignature; using (var vertexShaderByteCode = ShaderBytecode.CompileFromFile("VertexShader.hlsl", "main", "vs_4_0", ShaderFlags.Debug)) { inputSignature = ShaderSignature.GetInputSignature(vertexShaderByteCode); // 其餘被忽略的代碼 } // 其餘被忽略的代碼 }
建立輸入層的私有變量,建立輸入層須要輸入簽名和輸入元素
private D3D11.InputLayout _inputLayout; private ShaderSignature _inputSignature; private void InitializeShaders() { using (var vertexShaderByteCode = ShaderBytecode.CompileFromFile("VertexShader.hlsl", "main", "vs_4_0", ShaderFlags.Debug)) { _inputSignature = ShaderSignature.GetInputSignature(vertexShaderByteCode); _vertexShader = new D3D11.VertexShader(_d3DDevice, vertexShaderByteCode); } _inputLayout = new D3D11.InputLayout(_d3DDevice, _inputSignature, _inputElements); _d3DDeviceContext.InputAssembler.InputLayout = _inputLayout; // 其餘被忽略的代碼 }
private D3D11.InputLayout _inputLayout; private ShaderSignature _inputSignature; private void InitializeShaders() { using (var vertexShaderByteCode = ShaderBytecode.CompileFromFile("VertexShader.hlsl", "main", "vs_4_0", ShaderFlags.Debug)) { _inputSignature = ShaderSignature.GetInputSignature(vertexShaderByteCode); _vertexShader = new D3D11.VertexShader(_d3DDevice, vertexShaderByteCode); } _inputLayout = new D3D11.InputLayout(_d3DDevice, _inputSignature, _inputElements); _d3DDeviceContext.InputAssembler.InputLayout = _inputLayout; // 其餘被忽略的代碼 }
建立的代碼第一個參數就是剛纔使用的 D3D 設備,第二個就是剛纔的輸入簽名,最後一個就是輸入元素。
這裏建立了一個私有變量,最後仍是須要去掉他
public void Dispose() { // 其餘被忽略的代碼 _inputLayout.Dispose(); _inputSignature.Dispose(); }
public void Dispose() { // 其餘被忽略的代碼 _inputLayout.Dispose(); _inputSignature.Dispose(); }
在開始畫以前須要先設置 ViewPort ,在 DirectX 使用的座標是 Normalized Device Coordinates 左上角是 ,右下角是 ,建立私有變量用來放 ViewPort 代碼
private Viewport _viewport; private void InitializeDeviceResources() { // 其餘被忽略的代碼 _viewport = new Viewport(0, 0, Width, Height); _d3DDeviceContext.Rasterizer.SetViewport(_viewport); }
private Viewport _viewport; private void InitializeDeviceResources() { // 其餘被忽略的代碼 _viewport = new Viewport(0, 0, Width, Height); _d3DDeviceContext.Rasterizer.SetViewport(_viewport); }
在 Draw 畫出頂點
private void Draw() { _d3DDeviceContext.OutputMerger.SetRenderTargets(_renderTargetView); _d3DDeviceContext.ClearRenderTargetView(_renderTargetView, ColorToRaw4(Color.Coral)); _d3DDeviceContext.InputAssembler.SetVertexBuffers(0, new D3D11.VertexBufferBinding(_triangleVertexBuffer, Utilities.SizeOf<Vector3>(), 0)); _d3DDeviceContext.Draw(_vertices.Length, 0); _swapChain.Present(1, PresentFlags.None); RawColor4 ColorToRaw4(Color color) { const float n = 255f; return new RawColor4(color.R / n, color.G / n, color.B / n, color.A / n); } }
private void Draw() { _d3DDeviceContext.OutputMerger.SetRenderTargets(_renderTargetView); _d3DDeviceContext.ClearRenderTargetView(_renderTargetView, ColorToRaw4(Color.Coral)); _d3DDeviceContext.InputAssembler.SetVertexBuffers(0, new D3D11.VertexBufferBinding(_triangleVertexBuffer, Utilities.SizeOf<Vector3>(), 0)); _d3DDeviceContext.Draw(_vertices.Length, 0); _swapChain.Present(1, PresentFlags.None); RawColor4 ColorToRaw4(Color color) { const float n = 255f; return new RawColor4(color.R / n, color.G / n, color.B / n, color.A / n); } }
上面代碼 SetVertexBuffers 是告訴 _d3DDeviceContext
使用頂點緩存,第二個參數就是告訴每一個頂點的長度
使用 _d3DDeviceContext.Draw
能夠從頂點緩存畫出,第二個參數就是指定畫出的偏移,從那個頂點開始畫,第一個參數是畫多少個。如輸入 3,2
就是從第2個開始畫三個
運行代碼
參見:SharpDX Beginners Tutorial Part 4: Drawing a triangle - Johan Falk
更多博客請看 SharpDX 系列
感謝三千提供圖片
我搭建了本身的博客 https://lindexi.gitee.io/ 歡迎你們訪問,裏面有不少新的博客。只有在我看到博客寫成熟以後纔會放在csdn或博客園,可是一旦發佈了就再也不更新
若是在博客看到有任何不懂的,歡迎交流,我搭建了 dotnet 職業技術學院 歡迎你們加入
本做品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、從新發布,但務必保留文章署名林德熙(包含連接:http://blog.csdn.net/lindexi_gd ),不得用於商業目的,基於本文修改後的做品務必以相同的許可發佈。若有任何疑問,請與我聯繫。