output怎么用_如何用 C++ 写一个可编程软件渲染器?
今天你想用最新的 D3D12 畫一個三角形,少說也要上千行代碼了,對于初學者來講,這個門檻是非常高的,太多干擾了,而一千多行代碼,已經足夠你重頭實現一個簡易版 D3D 了,為什么不呢?比起從圖形 API 入門,不如從畫點開始,同樣一千行代碼,卻能讓你對 GPU 的工作原理有一個直觀的了解。
因此,為了讓希望學習渲染的人更快入門,我開源了一個 C++ 實現可編程渲染管線的教程:
skywind3000/RenderHelp
那么網上軟件渲染器其實不少,這個 RenderHelp 和他們有什么區別么?區別有三:
- 實現精簡,沒依賴,就是一個 RenderHelp.h 文件,單獨 include 它就能編譯了,不用復雜的工程,導入一堆源文件,vim/vscode 里設置個 gcc 命令行,F9 編譯單文件即可。
- 模型標準,計算精確,網上很多軟渲染器實現有很多大大小小的問題:比如紋理不是透視正確的,比如鄰接三角形的邊沒有處理正確,比如 Edge Equation 其實沒用對,比如完全沒有裁剪,比如到屏幕坐標的計算有誤差,應該以像素點方框的中心對齊,結果他們對齊到左上角去了,導致模型動起來三角形邊緣會有跳變的感覺,太多問題了,對于強迫癥,畫錯個點都是難接受的,RenderHelp 采用標準模型,不畫錯一個點,不算錯一處坐標。
- 可讀性高,全中文注釋,一千多行代碼 1/3 是注釋,網上很多同類項目,屬于作者自己的習作,重在實現,做完了事,注釋量不足 5%,一串矩陣套矩陣的操作過去,連行說明都沒有,你想搜索下相關概念,連個關鍵字都不知道。RenderHelp.h 是面向可讀性編寫的,雖然也比較小巧,但重點計算全部展開,每一處計算都有解釋。某些代碼其實可以提到外層運行更快些,但為了可讀性,還是寫到了相關位置上,便于理解。
渲染效果圖片:
使用很簡單,include 項目內的 RenderHelp.h 即可,VS 和 PS 之間傳參,主要使用一個 ShaderContext 的結構體,里面都是一堆各種類型的 varying。
// 著色器上下文,由 VS 設置,再由渲染器按像素逐點插值后,供 PS 讀取 struct ShaderContext {std::map<int, float> varying_float; // 浮點數 varying 列表std::map<int, Vec2f> varying_vec2f; // 二維矢量 varying 列表std::map<int, Vec3f> varying_vec3f; // 三維矢量 varying 列表std::map<int, Vec4f> varying_vec4f; // 四維矢量 varying 列表 };外層需要提供給渲染器 VS 的函數指針,并在渲染器的 DrawPrimitive 函數進行頂點初始化時對三角形的三個頂點依次調用:
// 頂點著色器:因為是 C++ 編寫,無需傳遞 attribute,傳個 0-2 的頂點序號 // 著色器函數直接在外層根據序號讀取響應數據即可,最后需要返回一個坐標 pos // 各項 varying 設置到 output 里,由渲染器插值后傳遞給 PS typedef std::function<Vec4f(int index, ShaderContext &output)> VertexShader;每次調用時,渲染器會依次將三個頂點的編號 0, 1, 2 通過 index 字段傳遞給 VS 程序,方便從外部讀取頂點數據。
渲染器對三角形內每個需要填充的點調用像素著色器:
// 像素著色器:輸入 ShaderContext,需要返回 Vec4f 類型的顏色 // 三角形內每個點的 input 具體值會根據前面三個頂點的 output 插值得到 typedef std::function<Vec4f(ShaderContext &input)> PixelShader;像素著色程序返回的顏色會被繪制到 Frame Buffer 的對應位置。
完整例子很簡單,只需要下面幾行代碼就能工作了:
#include "RenderHelp.h"int main(void) {// 初始化渲染器和幀緩存大小RenderHelp rh(800, 600);const int VARYING_COLOR = 0; // 定義一個 varying 的 key// 頂點數據,由 VS 讀取,如有多個三角形,可每次更新 vs_input 再繪制struct { Vec4f pos; Vec4f color; } vs_input[3] = {{ { 0.0, 0.7, 0.90, 1}, {1, 0, 0, 1} },{ { -0.6, -0.2, 0.01, 1}, {0, 1, 0, 1} },{ { +0.6, -0.2, 0.01, 1}, {0, 0, 1, 1} },};// 頂點著色器,初始化 varying 并返回坐標,// 參數 index 是渲染器傳入的頂點序號,范圍 [0, 2] 用于讀取頂點數據rh.SetVertexShader([&] (int index, ShaderContext& output) -> Vec4f {output.varying_vec4f[VARYING_COLOR] = vs_input[index].color;return vs_input[index].pos; // 直接返回坐標});// 像素著色器,返回顏色rh.SetPixelShader([&] (ShaderContext& input) -> Vec4f {return input.varying_vec4f[VARYING_COLOR];});// 渲染并保存rh.DrawPrimitive();rh.SaveFile("output.bmp");return 0; }運行后,生成一張 output.bmp 圖片:
由于 VS/PS 全部 C++ 編寫,因此開發和調試都較方便,無需分開單獨編譯,能直接訪問各種全局變量,能 printf 信息,還能斷點觀察問題。如果 Windows 的話,最后你可以加一行:
system("mspaint output.bmp");這樣每次運行后就能打開畫板程序查看最新的渲染效果。
有了上面繪制三角形的代碼,我們可以繼續改寫載入個模型繪制下:
直接顯示模型紋理,光禿禿的太丑,加個高洛德著色:
稍微能看一點,像十多年前的網游,再加個法向貼圖,讓細節更豐富些:
看著還行,再加層高光:
看起來不錯,主要用于驗證渲染器,有興趣你可以繼續折騰 PBR/BRDF 等高級渲染技巧。
渲染器的主要實現原理很簡單,我在下面這篇文章介紹過:
OpenGL 和 DirectX 是如何在只知道頂點的情況下得出像素位置的??www.zhihu.com兩種光柵化的實現方式,基于 Edge Walking 和掃描線繪制的適合 CPU 的傳統實時軟渲染方法,和現在這個基于 Edge Equation 的適合 GPU 的方法。前者復雜但是速度快,適合 CPU 實時渲染;后者簡單,但是運算量大,適合 GPU 并行處理。
歡迎參考我十多年前做的另外一個軟件渲染器:
- mini3d:700 行 C 語言實現實時軟件渲染
走的是傳統 CPU 實時渲染方法,其他參考:
- 計算機底層是如何訪問顯卡的?
- 256字節3D程序是如何實現3D引擎的呢?
- 如何進行三角形在齊次空間內的裁剪?
--
收藏比點贊多一倍,怎么回事?點個贊那么難么?
總結
以上是生活随笔為你收集整理的output怎么用_如何用 C++ 写一个可编程软件渲染器?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中信银行信用卡怎么查询额度 中信信用卡如
- 下一篇: 多晶硅概念利好哪些股票,多晶硅行业十大龙