1. 程式人生 > >C# 從零開始寫 SharpDx 應用 畫三角

C# 從零開始寫 SharpDx 應用 畫三角

在當前的畫面都是使用三角形,在開始就告訴大家如何畫三角,本文告訴大家如何用畫素著色器畫

本文是 SharpDX 系列部落格,更多部落格請點選SharpDX 系列

頂點

為了建立三角形,需要使用頂點。頂點就是在 3D 空間的點。通過頂點可以新增資料,很多使用的頂點都使用三個值,就是 xyz 來表示點在三維空間。大家都知道三角形有三個頂點,所以下面來建立三個頂點。

這裡的頂點的範圍是 0-1,所以可以使用下面程式碼創建出頂點

        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 格式,那麼複製下面程式碼放在專案

    <PackageReference Include="SharpDX.Mathematics" Version="3.1.1" />

如果不是就開啟 Nuget 安裝 SharpDX.Mathematics ,安裝之後引用using SharpDX就可以使用這個類

頂點快取

現在的頂點資訊放在了記憶體,因為使用了上面程式碼建立。但是渲染的物件是在顯示卡,需要把記憶體的頂點資訊複製到顯示卡。為了做這個需要使用快取。在 DX ,可以使用快取,dx 會自動複製資訊到顯示卡。

下面使用快取來存放頂點資訊,這樣就會在使用資訊自動複製到顯示卡。先寫一個私有變數,通過這個變數把資訊放在快取,請看下面

        private D3D11.Buffer _triangleVertexBuffer;

寫一個函式用來把 _vertices 轉換 _triangleVertexBuffer ,程式碼很簡單

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

現在使用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();
        }

畫素著色器

為了畫出三角形,需要使用頂點著色器和畫素著色器。

使用這兩個著色器因為頂點著色器負責加工頂點集合,可以用來做變換,如移動旋轉頂點。而畫素著色器負責每個畫素,如何畫出每個畫素和紋理。

定義兩個私有變數,表示兩個著色器

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

可以從程式碼發現使用了兩個檔案,所以接下來就需要建立兩個檔案,這兩個檔案使用的是 hlsl 來寫,關於 hlsl 不屬於本文的內容,所以沒有詳細告訴大家,建議複製一下程式碼。這裡建立了著色器需要使用下面程式碼進行設定

        // 其他被忽略的程式碼
            _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;
        }

這裡還使用 PrimitiveTopology 設定如何畫出來,更多請看Primitive Topologies

因為用到了兩個特殊的檔案,現在右擊專案新增兩個文字。

在這裡插入圖片描述

然後建立一個文字檔案,注意文字的名字,一個是 PixelShader.hlsl 另一個是 VertexShader.hlsl ,需要點選新建項才可以建立文字。為什麼需要使用文字,因為這樣編譯選項就不需要自己選

在這裡插入圖片描述

現在就建立了兩個檔案,請看自己的工程是否存在下面兩個檔案

現在需要右擊兩個檔案 PixelShader.hlslVertexShader.hlsl 屬性,選擇輸出

在這裡插入圖片描述

開啟 VertexShader.hlsl 並且寫入下面程式碼

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

這裡也不解釋程式碼的意思

開啟 KikuSimairme 類,在建構函式新增 InitializeShaders 初始化

        // 其他被忽略的程式碼
        public KikuSimairme()
        {
            _renderForm = new RenderForm();
            _renderForm.ClientSize = new Size(Width, Height);

            InitializeDeviceResources();

            InitializeTriangle();

            InitializeShaders();
        }

而且在清理的時候需要關閉 _vertexShader ,請看程式碼

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

這裡的 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 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();
        }

設定 ViewPort

在開始畫之前需要先設定 ViewPort ,在 DirectX 使用的座標是 Normalized Device Coordinates 左上角是 1,1-1,-1,右下角是 1,11,1 ,建立私有變數用來放 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);
            }
        }

上面程式碼 SetVertexBuffers 是告訴 _d3DDeviceContext 使用頂點快取,第二個引數就是告訴每個頂點的長度

使用 _d3DDeviceContext.Draw 可以從頂點快取畫出,第二個引數就是指定畫出的偏移,從那個頂點開始畫,第一個引數是畫多少個。如輸入 3,2 就是從第2個開始畫三個

執行程式碼

在這裡插入圖片描述

感謝三千提供圖片

我搭建了自己的部落格 https://lindexi.gitee.io/ 歡迎大家訪問,裡面有很多新的部落格。只有在我看到部落格寫成熟之後才會放在csdn或部落格園,但是一旦釋出了就不再更新

如果在部落格看到有任何不懂的,歡迎交流,我搭建了 dotnet 職業技術學院 歡迎大家加入