本文來告訴大家如何在上一篇博客創建的窗口裏面使用 Sharpdx 初始化,然後設置窗口顏色
本文是 SharpDX 系列博客,更多博客請點擊SharpDX 系列
在C# 控制檯創建 Sharpdx 窗口已經創建了一個窗口,現在需要在這個窗口初始化。因爲是從零開始寫,所以需要非常多細節,我覺得一篇文章是很難全部告訴大家,所以分爲了系列的文章。從零開始寫有利於大家瞭解一個渲染框架是如何做出來,並且從底層優化渲染,當然這個方法就是學習的時間會比較長。我會在文章去掉很多細節放在後面的博客講,讓大家先知道總體是如何做的。
創建資源
第一步是需要添加一個方法 InitializeDeviceResources
用來初始化資源,這裏初始化的就是設備的資源。在 dx 的渲染是需要緊密綁定設備。
這個方法就是寫在KikuSimairme
類裏,關於這個類的代碼在C# 控制檯創建 Sharpdx 窗口
// 其他被忽略的代碼
private void InitializeDeviceResources()
{
}
創建一個可以畫出來的類需要先創建顯示模式描述,通過顯示描述創建交換鏈描述,交換鏈描述創建設備和交換鏈,通過交換鏈和設備可以創建可以畫出來的類,在這個類就可以畫出無聊的圖形,按照創建的順序,我將文章分爲多個部分,下面先來窗口模式描述
模式描述
首先需要創建一個描述顯示模式,模式描述使用的是 ModeDescription
類,可以使用 new 的方式創建。在 dx 裏很多的類都只能通過工廠創建,可以通過 new 創建的類一般都是描述的類。爲什麼需要描述的類?因爲如果直接創建一個類需要傳入大量的參數,那麼這個寫法將會很難,而且存在很多屬性,只可以在構造的時候進行設置,不能在構造之後設置。爲了方便開發,所以就將多個參數分爲不同的類,這些類就是描述類。下面創建的是模式顯示描述
using SharpDX.Direct3D;
using SharpDX.DXGI;
// 其他忽略的代碼
class KikuSimairme : IDisposable
// 其他忽略的代碼
private void InitializeDeviceResources()
{
ModeDescription backBufferDesc =
new ModeDescription(Width, Height, new Rational(60, 1), Format.R8G8B8A8_UNorm);
}
通過 ModeDescription 就可以描述創建的模式是什麼,前兩個參數是表示緩存的大小,在很多的情況,這個值都和顯示的大小相同。
第三個參數就是表示刷新率,這裏使用的就是 1/60
也就是 60hz
最後一個參數設置的是像素格式,這裏使用 8 位的 RGBA 格式,使用一個無符號的 32 位整數表示,在設置格式是很重要,請仔細看自己的設置,因爲我就告訴了一位小夥伴看着他將模式的格式寫錯了。如果有安裝 Resharper 就可以按下 RGBA 快速找到這個屬性
更多關於 ModeDescription 請看 DXGI_MODE_DESC
這裏的 backBufferDesc 是在描述後臺緩衝區,後臺緩衝區的作用是防止動畫中出現閃爍,可以這樣看,在用戶看到一個白紙的時候,一邊有個人在這個白紙上畫東西,這時用戶就會看到畫東西的閃爍。如果有兩張紙,給用戶看一張紙,在另一張紙上畫,畫完就把這張紙給用戶,把剛纔的紙拿來畫下一個動畫。通過這個方式就可以減少用戶看到閃爍。這個不給用戶看到的紙就是後臺緩衝區(back buffer)。
交換鏈描述
下面可以來創建交換鏈的描述,交換鏈就是用來交換後臺緩衝和顯示的類,創建這個類需要先創建描述類,創建的方法是使用 new 的方式創建SwapChainDescription
類,同樣是爲了減少創建交換鏈的輸入參數,所以將很多參數放在這個類,創建描述類的時候就需要將上面創建的模式描述類傳進來,請使用這個代碼
// 其他忽略的代碼
class KikuSimairme : IDisposable
// 其他忽略的代碼
private void InitializeDeviceResources()
// 其他忽略的代碼
SwapChainDescription swapChainDesc = new SwapChainDescription()
{
ModeDescription = backBufferDesc,
SampleDescription = new SampleDescription(1, 0),
Usage = Usage.RenderTargetOutput,
BufferCount = 1,
OutputHandle = _renderForm.Handle,
IsWindowed = true
};
先來解釋一下參數。
交換鏈的 ModeDescription 就是上面定義的 backBufferDesc
多重採用 SampleDescription 用來優化圖片,是一種用於採樣和平衡渲染像素的創建亮麗色彩變化之間的平滑過渡的一種技術,這裏設置等級 1 也就是關閉多重採樣,需要傳入兩個參數一個是Count 指定每個像素的採樣數量,一個是Quality指定希望得到的質量級別,參見DXGI_SAMPLE_DESC structure,在這裏質量級別越高,佔用的系統資源就越多。
Usage 設置 CPU 訪問緩衝的權限,這裏設置可以訪問 RenderTarget 輸出,請看 DXGI_USAGE
後緩衝數量 BufferCount 建議設置一個,設置一個就是雙緩衝。兩個緩衝區已經足夠用了。
OutputHandle 獲取渲染窗口句柄
IsWindowed 這個值設置是否希望是全屏,如果是 true 就是窗口。現在軟件還沒寫好,如果這時全屏可能就無法退出,建議先設置這個值爲 true 不然難以直接退出。但是我還是設置了全屏,原因是本金魚有兩個屏幕,所以可以讓軟件退出
現在已經創建交換鏈,但是我裏面很多設置沒有告訴大家還有哪些可以設置
這裏有很多都需要在微軟官方纔可以看到,因爲本文是簡單的博客,不會在本文介紹。
爲什麼需要設置交換鏈?因爲在剛纔已經說了防止用戶看到閃爍需要使用兩個緩衝,如何把前臺緩衝區和後臺緩衝區交換就需要用到交換鏈。
私有變量
剛纔是在 InitializeDeviceResources
方法裏創建描述,但是創建了描述之後是需要創建出一些具體的類,這些類不能只放在 InitializeDeviceResources
方法,需要將這些類放在私有變量,這樣在這個類的其他地方纔可以拿到,請看代碼
using D3D11 = SharpDX.Direct3D11;
// 其他忽略的代碼
class KikuSimairme : IDisposable
// 其他忽略的代碼
private D3D11.DeviceContext _d3DDeviceContext;
private SwapChain _swapChain;
private D3D11.RenderTargetView _renderTargetView;
這裏使用了 using 定義了 D3D11 ,這樣可以區分一些類,如果有看到我之前的博客,會看到我在很多博客裏都使用這個方式
渲染上下文 d3DDeviceContext 是一種描述設備如何繪製的渲染設備上下文。
創建的 RenderTargetView 是 渲染目標視圖,在CPU把如何渲染寫入到渲染目標視圖中,它是一個2D紋理,寫入 RenderTargetView 不會立刻渲染到屏幕,而是到管線的輸出混合階段,最後纔到屏幕。
創建交換鏈
準備的代碼已經寫好,可以創建設備,創建了設備纔可以畫出
using SharpDX.Direct3D;
// 其他忽略的代碼
class KikuSimairme : IDisposable
// 其他忽略的代碼
private void InitializeDeviceResources()
// 其他忽略的代碼
D3D11.Device.CreateWithSwapChain(DriverType.Hardware, D3D11.DeviceCreationFlags.None, swapChainDesc,
out _d3DDevice, out _swapChain);
_d3DDeviceContext = _d3DDevice.ImmediateContext;
第一個參數 DriverType.Hardware 表示希望使用 GPU 渲染,設置 驅動設備類型 可以設置硬件設備(hardware device)、參考設備(reference device)、軟件驅動設備(software driver device)
硬件設備(hardware device)是一個運行在顯卡上的D3D設備,在所有設備中運行速度是最快的
軟件驅動設備(software driverdevice)是開發人員自己編寫的用於Direct3D的渲染驅動軟件
參考設備(reference device)是用於沒有可用的硬件支持時在CPU上進行渲染的設備
WARP設備(WARPdevice)是一種高效的CPU渲染設備,可以模擬現階段所有的Direct3D特性
第二個參數選不使用特殊的方法,參見 D3D11_CREATE_DEVICE_FLAG enumeration
第三個參數是輸入上面的交換鏈描述
最後的參數是輸出設備和交換鏈,關於輸出設備請看Direct3D設備。
交換鏈在Direct3D中爲一個設備渲染目標的集合。每一個設備都有至少一個交換鏈,而多個交換鏈能夠被多個設備所創建。
有了交換鏈和設備可以在緩衝區畫出圖形,畫圖形需要使用RenderTargetView
,爲了在其他函數可以使用,這裏需要把這個類寫在私有變量
private D3D11.RenderTargetView _renderTargetView;
然後在 InitializeDeviceResources 使用下面代碼,創建緩衝和使用緩衝和設置創建渲染目標視圖。因爲渲染目標視圖可以認爲是一張紙,這就是紋理,紋理是比較複雜的,將紋理理解爲一副圖像就行了。
// 其他忽略的代碼
class KikuSimairme : IDisposable
// 其他忽略的代碼
private void InitializeDeviceResources()
// 其他忽略的代碼
using (D3D11.Texture2D backBuffer = _swapChain.GetBackBuffer<D3D11.Texture2D>(0))
{
_renderTargetView = new D3D11.RenderTargetView(_d3DDevice, backBuffer);
}
總的創建過程很多,所以我使用了思維導圖讓大家知道每個步驟需要創建的
修改顏色
如果已經看過了之前的博客,那麼知道已經有可以畫的類,就可以開始畫出。
本文沒有告訴大家如何畫出線和畫出圓形,只是告訴大家初始資源,所以到這裏本文就結束了。
但是大家可以看到這時的界面和之前一樣,會說我的程序是不是寫錯了。所以我就簡單修改一下界面,創建一個函數 Draw 在這個函數寫代碼
// 其他忽略的代碼
class KikuSimairme : IDisposable
// 其他忽略的代碼
private void Draw()
{
_d3DDeviceContext.OutputMerger.SetRenderTargets(_renderTargetView);
_d3DDeviceContext.ClearRenderTargetView(_renderTargetView, ColorToRaw4(Color.Coral));
_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);
}
}
這裏爲了畫出顏色,使用 ColorToRaw4 的類,因爲 RawColor4 是傳入顏色是 [0,1],但是很多代碼使用的是[0,255],爲了讓顏色比較容易寫,我就寫了這個類。
在_d3DDeviceContext.OutputMerger.SetRenderTargets(_renderTargetView);
設置了剛纔創建的_renderTargetView
激活,在每次我們想渲染一個特定的渲染目標的時候,必須在所有的繪製的函數調用之前對它進行設置。
第二句代碼_d3DDeviceContext.ClearRenderTargetView(_renderTargetView, ColorToRaw4(Color.Coral));
清理_renderTargetView
設置顏色,把他放在第一個緩衝。在 dx 有兩個緩衝,一個是看不見的,一個是顯示的。第一個緩衝就是顯示的,第二個就是在第一個顯示的時候畫出來,於是不停交換,讓用戶看到一個畫好的緩衝。通過這個方法用戶可以看到動畫
調用交換鏈的Present函數在屏幕上顯示渲染緩衝區的內容 _swapChain.Present(1, PresentFlags.None);
是等待垂直同步,在刷新完成在完成這個方法,第一個參數是同步間隔,第二個參數是演示的標誌。
創建資源和顏色設置代碼已經寫好,現在需要調用方法
// 其他忽略的代碼
class KikuSimairme : IDisposable
{
public KikuSimairme()
{
_renderForm = new RenderForm();
_renderForm.ClientSize = new Size(Width, Height);
InitializeDeviceResources();
}
// 其他忽略的代碼
}
如果有看過之前的博客,會發現有一個方法是空的,現在可以在RenderCallback
添加代碼
// 其他忽略的代碼
class KikuSimairme : IDisposable
{
private void RenderCallback()
{
Draw();
}
// 其他忽略的代碼
}
在完成了上面的代碼之後,還需要做清理,在 dx 使用的資源都需要手動釋放。需要注意釋放的順序,本文在這裏不告訴大家釋放的順序是如何確定,所以希望先複製下面代碼進行釋放。
public void Dispose()
{
renderTargetView.Dispose();
swapChain.Dispose();
d3dDevice.Dispose();
d3dDeviceContext.Dispose();
renderForm.Dispose();
}
現在按一下 F5 就可以運行,看到一個綠色的窗口。本文也就告訴了大家如何初始化窗口,在初始化窗口之後離在窗口畫東西還需要一步,那就是創建 ViewPort 視口,詳細請點擊C# 從零開始寫 SharpDx 應用 畫三角
所有代碼
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpDX.Direct3D;
using SharpDX.DXGI;
using SharpDX.Mathematics.Interop;
using SharpDX.Windows;
using D3D11 = SharpDX.Direct3D11;
namespace NawbemcemXadre
{
class KikuSimairme : IDisposable
{
/// <inheritdoc />
public KikuSimairme()
{
_renderForm = new RenderForm();
_renderForm.ClientSize = new Size(Width, Height);
InitializeDeviceResources();
}
private const int Width = 1280;
private const int Height = 720;
public void Run()
{
RenderLoop.Run(_renderForm, RenderCallback);
}
private RenderForm _renderForm;
private D3D11.Device _d3DDevice;
private D3D11.DeviceContext _d3DDeviceContext;
private SwapChain _swapChain;
private D3D11.RenderTargetView _renderTargetView;
private void RenderCallback()
{
Draw();
}
private void InitializeDeviceResources()
{
ModeDescription backBufferDesc =
new ModeDescription(Width, Height, new Rational(60, 1), Format.R8G8B8A8_UNorm);
SwapChainDescription swapChainDesc = new SwapChainDescription()
{
ModeDescription = backBufferDesc,
SampleDescription = new SampleDescription(1, 0),
Usage = Usage.RenderTargetOutput,
BufferCount = 1,
OutputHandle = _renderForm.Handle,
IsWindowed = true
};
D3D11.Device.CreateWithSwapChain(DriverType.Hardware, D3D11.DeviceCreationFlags.None, swapChainDesc,
out _d3DDevice, out _swapChain);
_d3DDeviceContext = _d3DDevice.ImmediateContext;
using (D3D11.Texture2D backBuffer = _swapChain.GetBackBuffer<D3D11.Texture2D>(0))
{
_renderTargetView = new D3D11.RenderTargetView(_d3DDevice, backBuffer);
}
}
private void Draw()
{
_d3DDeviceContext.OutputMerger.SetRenderTargets(_renderTargetView);
_d3DDeviceContext.ClearRenderTargetView(_renderTargetView, ColorToRaw4(Color.Coral));
_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);
}
}
/// <inheritdoc />
public void Dispose()
{
_renderTargetView.Dispose();
_swapChain.Dispose();
_d3DDevice.Dispose();
_d3DDeviceContext.Dispose();
_renderForm?.Dispose();
}
}
}
參見:SharpDX Beginners Tutorial Part 3: Initializing DirectX - Johan Falk
D3D11_CREATE_DEVICE_FLAG enumeration
上一篇 C# 從零開始寫 SharpDx 應用 控制檯創建 Sharpdx 窗口
下一篇 C# 從零開始寫 SharpDx 應用 畫三角 將會告訴大家如何創建 視口 ViewPort,視口定義了我們渲染到屏幕上的面積。
我搭建了自己的博客 https://lindexi.gitee.io/ 歡迎大家訪問,裏面有很多新的博客。只有在我看到博客寫成熟之後纔會放在csdn或博客園,但是一旦發佈了就不再更新
如果在博客看到有任何不懂的,歡迎交流,我搭建了 dotnet 職業技術學院 歡迎大家加入
本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名林德熙(包含鏈接:http://blog.csdn.net/lindexi_gd ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。如有任何疑問,請與我聯繫。