想用C++實現一個軟體渲染器,類似DX和OpenGL,除了《3D遊戲編程大師技巧》,或者什麼網站推薦?

如何開始用 C++ 寫一個光柵化渲染器? - Yong He 的回答

這個我看了,不過貌似沒有明確的方向,求指教


Muli3D - Browse /muli3d/0.8 at SourceForge.net

不知道你們有沒有讀過這個項目。

代碼質量很不錯,是一個很好的借鑒。而且還有相當多的例子。

當年我寫salvia的時候,受到它一些啟發。

我的salvia也可以讀一讀,它的特性和性能在軟體渲染器中已經不算差了。

雖然有一些優化,但是整體結構仍然是清晰的。

wuye9036/SalviaRenderer · GitHub

其中salviar是渲染的核心部分,sasl是shader編譯器,salviax是一些輔助庫,類似於d3dx


@韋易笑

我在韋前輩的軟渲染器基礎上開發,參照《3D遊戲編程大師技巧》陸陸續續添加了一些其他功能。包括:

  • 將原來的Gouraud著色改為phong著色
  • 加入一個平行光和4個點光源,物體材質
  • 背面剔除
  • 利用libpng讀取外部紋理
  • 實現雙線性紋理濾波和mipmap
  • 完整實現3D裁剪演算法
  • 簡單的投影
  • 利用sdl2實現了mac、ios、windows多平台支持
  • 3D漫遊和object簡單封裝
  • obj模型讀取
  • 各種貼圖支持,法線貼圖,漫反射,鏡面高光貼圖等
  • shadow map實現陰影效果
  • 頂點著色器和片元著色器

下面是效果截圖:

在我的iphone上運行效果:

項目地址:sdlwlxf1/tinyEngine

經過這一番折騰,總算是搞明白了渲染背後的原理。非常感謝韋易笑老師提供如此簡潔易懂的代碼,讓我可以有更多的精力關注底層的實現。令人的欣慰的是,我的渲染器所有代碼也只有2000行左右,整個項目只有tiny3D.h和main.c兩個文件。雖然沒有做優化,運行幀率還不錯(目測有40fps)。

目前項目中注釋較少,如果有需要的話,我會抽時間將一些核心代碼演算法以及引用書籍補充完整。

希望我的努力可以幫助更多對圖形學感興趣的同學們~~

——————————————————————————————

下面是重點!!

想要通過該工程學習光柵化渲染的同學,請務必備好以下兩本書籍:

  • 《3D數學基礎:圖形與遊戲開發》(建議認真看完)
  • 《3D遊戲編程大師技巧》上下冊

還有其他一些其他資料可以輔助理解

《[計算機圖形學].(美國)Peter.Shirley》

learnopengl-cn一個學習opengl的網站,講解光影部分挺好

代碼簡介

tiny3D.h

核心的數學演算法和光柵化渲染實現,掌握了它可以說你就徹底弄明白了光柵化渲染

數學部分,全部是重點,特別是矩陣操作等,透視矩陣和旋轉矩陣,是重中之重。

光柵化部分,為了實現phong光照加入了重心坐標計算,可能還有更好的實現方法,有能力的同學可以重構之。

main.c

非核心代碼,包括sdl和紋理處理,有能力的同學可以自己實現並移植到任何平台

關於學習方法

因為我也是通過參考大神韋易笑的代碼來學習圖形學的。這裡我可以提供一些我的學習經驗

首先要端正自己的學習態度,不要認為軟渲染有多難,看整個實現也不過1000行左右代碼,當然也不要被這微小的代碼量所迷惑,量少就意味每一行代碼每一個函數都潛藏著巨大的知識量。如果有不明白的,一定要查閱上面提到的兩本書,比如透視和旋轉的演算法公式,最好都要弄明白推導過程。然後自己實現一遍代碼,通過實踐加深對原理的理解。

數學部分在於理解,光柵化部分在於實現

請認真實現自己的光柵化部分,每個人的實現方法都不同,也許你的實現會更高效

個人認為軟渲染是學習圖形學的一個關鍵點。

等你也實現了自己的軟渲染引擎,再看unity3D和shader,就會有一種醍醐灌頂的感覺,甚至可以自行腦補其實現方法


實現個簡單的固定渲染管線軟渲染器不算複雜,大家都說了那麼多了,我來個更短的吧,差不多700行代碼就可以搞定了。之所以很多人用 D3D用的很熟,寫軟渲染卻坑坑窪窪,主要是現在大部分講圖形的書,講到透視投影時就是分析一下透視變換矩陣如何生成,頂點如何計算就跳到其他講模型或者光照的部分了。

因為今天基本上是直接用 D3D 或者 OGL,真正光柵化的部分不了解也不影響使用,所以大部分教材都直接跳過了一大段,攝像機坐標系如何轉換?三角形如何生成?CVV邊緣如何檢測?四維坐標如何裁剪?邊緣及步長如何計算?掃描線該如何繪製?透視紋理映射具體代碼該怎麼寫?framebuffer zbuffer 到底該怎麼用?z-test 到底是該 test z 還是 w 還是 1/z 還是 1/w ?這些都沒講。

早年培訓學生時候,我花兩天時間寫的一個 DEMO,今天拿出來重新調整注釋一下,性能和功能當然比不過高大上的 Salviar。但一般來講,工程類項目代碼不容易閱讀,太多邊界情況和太多細節優化容易讓初學者迷失,這個 mini3d 的項目不做任何優化,主要目的就是為了突出主幹:

源代碼:skywind3000/mini3d · GitHub

可執行:http://www.skywind.me/mw/images/c/c8/Mini3d.7z

操作方式:左右鍵旋轉,前後鍵前進後退,空格鍵切換模式,ESC退出。

特性介紹:

  • 單個文件:源代碼只有一個 mini3d.c,單個文件實現所有內容,易閱讀。

  • 獨立編譯:沒有任何第三方庫依賴,沒有複雜的工程目錄。

  • 模型標準:標準 D3D 坐標模型,左手系 + WORLD/VIEW/PROJECTION 三矩陣

  • 簡單裁剪:簡單 CVV 裁剪

  • 紋理支持:最大支持 1024 x 1024 的紋理

  • 深度緩存:使用深度緩存判斷圖像前後
  • 透視貼圖:透視紋理映射以及透視色彩填充

  • 實現精簡:渲染部分只有 700行, 模塊清晰,主幹突出。

  • 詳細注釋:主要代碼詳細注釋

截圖效果:

透視紋理映射

色彩填充

線框圖

閱讀要求:

  • 看過並了解 D3D / OGL的矩陣變換。

  • 用 D3D / OGL 完成過簡單程序。

實現說明:

  • transform:實現坐標變換,和書本手冊同

  • vertex: 如何定義頂點?如何定義邊?如何定義掃描線?如何定義渲染主體(trapezoid)?

  • device: 設備,如何 projection,如何裁剪和歸一化,如何切分三角形,如何頂點排序?

  • trapezoid:如何生成 trape,如何生成邊,如何計算步長,如何計算掃描線

  • scanline:如何繪製掃描線,如何透視糾正,如何使用深度緩存,如何繪製

基礎練習:先前給學生的作業

  • 增加背面剔除
  • 增加簡單光照
  • 提供更多渲染模式
  • 實現二次線性差值的紋理讀取

擴展練習:給有餘力的學生

  • 推導並證明程序中用到的所有幾何知識
  • 優化頂點計算性能

  • 優化 draw_scanline 性能
  • 從 BMP/TGA 文件載入紋理

  • 載入 BSP 場景並實現漫遊

其他內容

當年還用不了 D3D 和 OGL ,開發遊戲,做圖形實現軟體渲染是必備技能,當年機型差,連浮點數都用不了,要用定點數來計算,矩陣稍不注意就越界了。計算透視糾正還是一個比較昂貴的工作,更多遊戲使用仿射紋理繪製,只是把離屏幕近的多邊形切割成更小的三角形,讓人看起來沒有那麼明顯。即便到了 Quake 年代,計算 1/z 的除法也只是四個點才算一次(經過精確計算CPU周期,繪製四個點時下一個點的 1/z剛好算完),Quake 的四個點內也還是仿射紋理繪製。。。。

那時顯卡沒普及,光軟體渲染器的優化就是一個無底洞,今天有了 OGL/D3D和顯卡,人的精力才能充分集中在更高層次的場景組織、層次細節、動態光照等功能上。然而有空的時候,花個一周時間坐下來了解一下這部分的大概原理,推導所用到的數學模型,也能幫助大家更好的理解底層運行機制,寫出更加優化的代碼來。

PS:光線跟蹤版本的軟體渲染,考慮光照的話,簡單實現起來差不多 500 行,比這個要簡單一些。各位有興趣也可以嘗試一下,就是簡單渲染個立方體足夠了。

----------

更新:8.12 有朋友跟我說他加上了光照(支持同時N個平行光源)和二次線性插值,效果還行:


我寫了一個 Python 版的,仿照韋易笑前輩做的,也是單個文件,基於 numpy 和 pyglet。pyglet 用於管理窗口,numpy 進行數學運算。大概500多行。支持簡單地紋理映射、簡單裁剪、透視變換、深度緩衝、背向面剔除以及漫反射平行光。

不過由於 Python 自身的性能問題有一些功能比如更進一步的光照之類的實在是加不上去了,而且幀數有點低。但是作為練手足夠了。

GitHub - coldfog/simpleRender

截圖:


只能說一點點大致的方向,說多細節,算是出賣公司機密啦。

cpu光柵化渲染器的輸入是一個已經投影到clipping space三角形,輸出是每個三角形所覆蓋的可見sample,sample和pixel的對應關係取決於是否開msaa和何種aa,和每個pixel的barycentric coordinates用於插值vertex attributes。

定義完輸入輸出,基本的渲染器就做這麼幾件事。為了簡單期間,我們只考慮1xaa的做法

1. 計算三角形的平面方程,方程建立後就可以通過每個pixel的屏幕x和y得到每個pixel的z

2 從三角形最上面一個頂點開始,從上往下從左往右掃描每個sample,計算每個pixel的z,與當前的depth buffer中的比較,然後選擇保留還是丟棄。

3. 計算保留的sample的barycentric coordinates

其中有很多優化,比如先做一遍粗力度的raster,例如8x8pixels,判斷這樣的一個大block是否在三角形內,不在的話就整個丟棄。


GitHub - wuye9036/SalviaRenderer: SALVIA is the rasterizer based software renderer. The goal of SALVIA is capacity of Direct3D 10+.

看看 @空明流轉的Silvia,完美兼容DX10介面和所有功能


這本書已經可以在京東和亞馬遜上購買了。


那麼你應該看我專欄裡面的文章(系列)

渲染器 1 —— 基本繪圖 - 煉瓜研究所 - 知乎專欄

貼幾張截圖

跟完我的再看任何書實現上面的名詞,一切都會變得 最 容易

如果你覺得好還可以給我捐款,因為它實在是小小地改變了你的人生


首推這個項目guaxiao/renderer.gua · GitHub

可以配合專欄看,渲染器 1 —— 基本繪圖 - 煉瓜研究所 - 知乎專欄

@蕭井陌 寫的這個項目對新人非常友好。

回歸編程本質,talk is cheap, show me your code.

我是鼓勵編程"速成"的,當然這個速成是在掌握一定基礎知識後,閱讀並且跟著做這麼樣一個項目,就會產生巨大的興趣,深入學習起來思路和動力都非常巨大,之後再靜下心來穩紮穩打的學習就好,如果一直停在所謂的理論,其實很多東西看不懂學不透。

2015年8月9日

先自我介紹下,我基本沒有圖形學基礎,不會C++,只在本科上過相關課程,能畫畫線、圓、多邊形,裁剪直線、多邊形等基本演算法,作業用的Java湊出來的,C++只懂輸入輸出循環函數引用這些內容,最多講講Big Three,代碼全靠複製粘貼的水平,總之水平十分糟糕。

半夜才開始看的這個項目,只是漫遊了一遍,先粗糙的解釋下這個項目,效果好的話,我儘可能的寫詳細些,包括演算法等等。

2015年8月10日

高估自己的效率了,今天還沒辦法深入講解演算法,把整個代碼結構捋清楚了一些。

好了切入正題,這個項目好在哪裡呢:

1. 首先定位明確簡單,就是為了讓人看懂渲染器的製作過程,所以代碼也是力求簡潔。

2. 使用SDL跨平台,寫好了各種執行的腳本,基本上你會修改環境變數跑這些例子完全沒問題,管你用windows / osx / linux。

3. 最讓我感動的是,代碼寫的很有規整,提交非常有目的性,我實在是沒有這種好習慣。

那麼讀我這篇文章需要什麼要求呢,上面其實已經寫了一部分了,總結下就是:

1. 要會用Git。

2. 了解C++語法,知道圖形學是個什麼東西,知道我們要寫的渲染器是個什麼東西。

(不懂SDL完全沒關係,見文知意就好)

1.代碼目錄

工程目錄 projects [vs2013 / vs2015 / xcode6]

跨平台庫多媒體開發庫 SDL-1.2.15

不同平台的入口文件 platform

模型文件 illidan.gua / illidan.guaimage

核心代碼目錄 src

編譯腳本 auto_make.py / build_win_mingw.bat / install_mac_sdl.sh / makefile

我們的目的是閱讀源碼並學習該項目,故重點內容放在核心代碼目錄模型文件上。

詳細解釋,如下圖(僅供參考):

2.代碼詳解

第一步(可跳過):

這裡我們藉助git log來查看作者的提交記錄,由簡至繁,畢竟不是自己的代碼,看起來總是不那麼爽快。

進入項目根目錄,打開git shell輸入git指令

git log --stat &> log.txt

使用這個指令可以查看提交記錄,為方便查看,存到了當前目錄下的log文件中。

git log -p &> detail.txt

可以查看詳細的提交記錄,但是內容結構複雜,不便閱讀,不鼓勵使用,想查看詳細記錄建議使用GUI或者Github網站下查看。

接下來我們開始查看log.txt

我挑選了一次提交歷史來解釋如何閱讀這份log.txt,如下圖。

我們快速瀏覽下代碼提交記錄,我會標註行號參考閱讀,記住,花大概3-5分鐘過一遍就好。

line30中提交了文件window.h,主要內容是測試了SDL的按鍵響應。

和vs等配置相關的內容可以跳過,提交量較小的入hotfix等也跳過。

陸續新建了canvas / guaimage / texture / color等文件。

line287完成了version 1.0是一次比較大的提交。

如下圖,其中illidan.gua3d數量巨大主要是因為它是模型文件,裡面存在各種數據內容。

line1027做了一些補充提交,如下圖。

截圖提到的這些文件是我們需要重要關注的,其他的還有很多平台調試,以及SDL組件的添加,可以無視掉。

第二步:

核心代碼文件都放在了src中,如何閱讀他們呢,先看文件名吧。

canvas.cpp / canvas.h 畫布

color.cpp / color.h 顏色

guafoundation.h 瓜の基礎函數

guaimage.cpp / guaimage.h 瓜の影像函數

guamath.h 瓜の數學函數

matrix.h 矩陣

mesh.cpp / mesh.h 網格

texture.cpp / texture.h 紋理[貼圖]

vector.cpp / vector.h 向量[描述點、向量]

vertex.cpp / vertex.h 頂點

window.cpp / window.h 窗口

看到這裡,對這些文件有個模糊的認識,最終程序要通過窗體展示給我們,所以這一部分應該均在window中完成。

而window.h也是最早提交的文件,我們先閱讀window.h來理清這些文件的邏輯關係。

渲染器 0 —— 準備工作 - 煉瓜研究所 - 知乎專欄

專欄里也提到,程序的主要繪製邏輯在windows.h中,主循環在run函數中,所以我們應該先行閱讀這部分。

其中有一個Window的類,其私有變數有

SDL_Surface *_screen; // SDL定義好的

Canvas *_canvas; // 來自canvas.h

Mesh

*_mesh; // 來自mesh.h

我們依然僅能從名字大概推斷其作用,往下看。

構造函數使用SDL的介面負責窗體的初始化,這裡載入了兩個數據文件

const char *modelPath = "illidan.gua3d"; // 這個是模型
const char *texturePath = "illidan.guaimage"; // 這個是紋理

那麼什麼是模型什麼是紋理呢,模型就是你的骨架,紋理就是你的皮膚與衣服。

再往下看這個run函數,while(_running)當然就是保持程序運行啦。

中間有裡面有SDL_LockSurface / updateInput / update / clear / draw / show六個函數的調用。見名知意,鎖住表面 / 更新輸入 / 更新 / 清空 / 繪製 / 顯示。

在不查詢手冊的情況下,第一個還不清楚具體意義,作用大概是保持當前視圖,第二個是交互相關,後面三個是視圖計算繪製相關。

看不懂的地方記下先往下看,除了SDL_LockSurface外其他函數在windows.h中均有聲明與定義。

先是update,負責更新mesh實例的rotation和position,rotation是旋轉position是定位的意思,看來mesh和模型關聯度比較大的,並且可以旋轉與上下左右移動。

怎麼動呢,根據按鍵,一次修改0.05的值,如果改大了transform這個值,生成的項目在操作的時候會有比較大的位移與角度變換。

draw中,通知canvas重新繪製mesh,mesh與canvas關係密切。

在show中,存在UnlockSurface函數,這個與前面的LockSurface相呼應,這裡應該是先計算再更新,過程中將計算結果保存到了畫布中,然後更新畫布,這就是常見的雙緩衝技術

至於後面的onKeyEnvent沒什麼特別的內容,都是寫SDL封裝好的東西,很容易理解,ipdateInput配合HandleEvent處理事件響應,通俗講就是我們的滑鼠鍵盤操作。

通過讀window.h我們發現,canvas和mesh關係密切並且較為重要,而vertex / vector /

guamath / color等雖然也包含在了window.h中,但是並未影響我們簡單的閱讀。

劇情跌宕起伏的小說,往往也少不了複雜的人物關係,在讀完這些後我會畫一個模塊關係圖,來說明這些內容。

我們先來看canvas.h / canvas.cpp

Canvas中持有一個uint32_t類型的變數pixels,表徵像素,以左上角為原點,pixels 數組長度:窗口寬 * 窗口高,坐標(x, y)對應的像素值:y * 窗口寬 + x。

_depthBuffer,搜一下就會出現一個深度緩衝的概念,總之遇到不會的查找就好了。簡單的說,較近的物體遮擋較遠的物體,讓像素有了深度的概念,還有一個名字是z-buffer。

putPixel(x, y, z, color)

// 放置像素點

drawScanline(point1,

point2, texture) //掃描線著色

project(point, matrix) // 投影,得到一個投影后的點[返回了一個Vertex]

drawPoint(point, color) //

繪點並填色

drawLine (point1, point2)

// 繪線

drawTriangle(point1,

point2, point3, texture) // 繪製三角形添加紋理

drawMesh(mesh) // 繪製網格

大概確定了幾個事情,Canvas是一個像素點集成的畫布,Vertex代表點,texture叫紋理、貼紙、表面塗層都可以,color是角度,matrix是一個投影用到的矩陣。

mesh還不清楚是什麼,先給出結論。

A mesh is a collection of vertices, edges, and faces that describe the
shape of a 3D object。

mesh就是將前面的點 / 邊(線) / 面集合起來,用來描述立體對象的東西。

在mesh的構造函數中看到了熟悉的東西

const char

*path, const char *texturePath

也就是前面的

const char *modelPath =

"illidan.gua3d"; // 這個是模型

const char *texturePath =

"illidan.guaimage"; // 這個是紋理

這是作者自己寫的格式,先不用關注格式細節,只需要知道裡面存儲的是vertices和triangles的數據就夠了。

vertices是頂點,triangles是三角形,mesh要把這些數據讀取並集合起來,描述立體對象,這些都還只是骨架,通過貼圖也就是texture,而在texture中,包含了構造函數和sample函數,還有u / v,這兩個變數代表x / y。

在texture.cpp中看到構造函數的實現,構建了Guaimage,將貼圖中的像素信息賦值給int32型的pixels上,然後賦值給了_pixels,然而我們並沒有看到這個文件中有_pixels,它在哪裡呢,在texture.h。如果你寫Java或者一些腳本語言比較多,這樣的物理分離可能不是很適應,習慣就好了。

而sample函數,看了半天沒看懂,最後發現參數是u / v,返回值是Color,豁然開朗,根據坐標得到對應點的紋理。

接著看mesh.h,其中的變數

Vector position; // 坐標位置

Vector rotation; //角度

Vector scale; // 大小比例

std::vector&

vertices; // 存儲頂點的信息,每個頂點都是一個Vertex

std::vector&

indices; // 存儲線[三角形],按照a / b / c順序

Texture *texture; // 上面已經解釋過了

上面的vertices向量存儲了Vertex,那麼Vertex是什麼呢,進去源碼看看。

vertex裡面有一個構造函數,一個返回vertex的interpolate函數。

interpolate是什麼,是插入意思[其實就是插值,可以搜到相關的知識],而vertex是頂點的意思,其實我們都知道,點連成線,線連成面,面連成體。

這裡構造函數需要關注一下,我們是如何描述這麼一個點的,需要使用兩個Vector變數,u / v,還有一個Color,然後看mesh中的調用,傳的color居然是Color::randomColor(),可能這裡會比較凌亂。

先看下color.h / color.cpp好了,定義了random方法,還有構造函數,已經重載的+-*/操作符,沒什麼特別的難點。

這裡存在兩個問題,首先是Vertex,不清楚其私有變數的具體含義,其次是Color為什麼要使用randomColor。

我們先列出Vertex的變數,方便看一個類型的寫在了一起。

Vector position, normal;
float u, v;
Color color;

我們稍微回憶一下如何來到Vertex,從window

&>&> canvas / mesh &>&> vertices &>&> Vertex,那麼找關於Vertex相關的內容在這條鏈上找就可以了。

很容易在canvas中找到,邏輯強的話,應該只能直接反映到和繪製有關,因為我們在第一次見到canvas和mesh的時候,他們倆就看起來關係密切:)

在canvas.cpp中可以看到,position就是位置,而normal在project中用到,也就是投射中用到。

而u / v 在掃描著色還有投影中均用到。

而randomColor,其實這裡是這樣的,因為我們貼圖了,所以這裡顏色並不是什麼關鍵,在drawPixels和drawLine中都出現了Color,線和點的顏色並不會有什麼影響。

此處發一下作者原話:
因為我就隨便設置了一個顏色啊,我沒用這個屬性,不貼圖就用顏色了,貼圖沒必要混合顏色。

3. 軟體渲染器知識補充

什麼是渲染器,為什麼要做渲染器?

渲染器的主要任務,是完成3D物體的繪製,渲染器有硬體渲染和軟體渲染,做渲染器實際我們在做的是軟體渲染器,做軟體渲染器可以更深刻的理解DX / openGL等硬體渲染器。

什麼是光柵化?

可以通俗的理解為像素化,將圖轉化為一個個柵格組成的圖像,通過把頂點數據轉換成片元的過程,讓片元每個元素對應幀緩緩從去中的一個像素。

其實這裡有四個概念需要補充一下,頂點(vertexs)圖元(primitives)片元(fragments)像素(pixels),圖元有幾何頂點組合而成,圖元進行裁剪後,顏色和紋理數據也相應作出必要的調整,相關的坐標被轉換為窗口坐標,經過光柵化的處理就成為了片元,二維圖象上每個點都包含了顏色、深度和紋理數據。將該點和相關信息叫做一個片元(fragment)

詳細可以閱讀頂點(vertexs) 圖元(primitives) 片元(fragments片斷) 像素(pixels)

還需要哪些哪些東西?

這個render已經實現的東西有,光柵化2D點,光柵化2D直線,光柵化2D三角形,將頂點從三維空間變換至二維空間,光柵化模型,深度緩衝,空間插值與紋理映射。

還缺少一個常見的東西,光照。


推薦鬼火引擎,裡面GL,DX,軟渲染三套全有。


OpenGL 有開源實現 http://mesa3d.org 你可以參考一下。


從計算機圖形學的書(看傳統的計算機圖形學書,不是基於DX或者OpenGL的那種)開始看,從在屏幕上畫點,到在屏幕上畫線,到在屏幕上畫三角形,到加入變換,到支持紋理,基本的計算機圖形學的書應該都有講。

但是。

我不推薦你寫這種東西,

以前因為我想做遊戲,但是自己懶不想學DX,所以我決定從畫點開始自己寫,我花了半年時間,但是由於軟體API無法利用GPU,所以我寫出來的API速度非常慢,根本不能拿來做遊戲,所以後來還是學了DX。

你如果寫軟體渲染器,那就一步到位寫一個光線跟蹤的好了。


你想說的是離線渲染器嗎?我看pbrt就很好。知乎上也有大v自己在寫這種工具。

如果只要基本功能的話,我建議實現畫三角形就可以了,把三個頂點轉化到二維坐標然後做掃描線填充。你可以先試試逐三角形的繪製。

------------------------之前用手機回答的,現在在電腦上再補充一些-------

1.首先我們考慮最簡單的情況,所有面片都是同一個顏色,比如白色,而且面片都是三角形。

空間中的三角形,投影到屏幕上之後,依然還是三角形(或者退化成線段)。

那麼我們需要實現一個如下的函數

void drawTriangle(vec2 p1, vec2d p2, vec2 p3, int color);

這裡vec2是三個二維坐標,也就是三維坐標投影到屏幕之後的坐標,至於XY軸的正負方向可以隨你自己喜好(比如opencv是Y軸向下,opengl是Y軸向上)。color是顏色(顏色用什麼格式看自己喜好了),因為這裡我用的最簡單的情況,這樣三角形填充起來只需要每個像素寫同樣的顏色就可以了。

填充三角形時用掃描線演算法,請看這個鏈接【圖像處理】邊相關掃描線填充演算法

注意:這個鏈接里的多邊形是整數坐標,實際處理時時小數坐標,端點問題一定要仔細摳。

2.我們把問題描述描述的稍微複雜一點,那就是三角形的三個頂點不是同一個顏色,

那麼需要實現如下的函數

void drawTriangle(vec2 p1, int color1, vec2 p2, int color2, vec2 p3, int color3);

與前面那個函數相比,這裡每個頂點都賦予了顏色,填充哪些像素,依然由掃描線演算法來決定,但是每個像素的顏色是線性插值出來的,簡單的理解線性插值的原理請看這個鏈接三角形內部線性插值方法

注意:直接這麼實現效率肯定很低,你多找找網上的資料應該有很多更快的方法

3.三角形的顏色,頂點法向,紋理坐標,處理起來都可以用上一部分的線性插值來實現。至於你要多少屬性,看個人喜好。

4.從三維到二維,是圖形學理論的核心。

主要由兩個矩陣組成,

modelview矩陣:坐標從模型的局部坐標系變換到視點坐標系

projection矩陣:坐標從視點坐標系變換到屏幕坐標

至於多個坐標系之間的相對變換矩陣,最後連乘到一起,就會組成modelview矩陣,比如物體局部坐標系變換到世界坐標系,然後世界坐標系變換到視點坐標系,

modelview_matrix = view_matrix * world_matrix.

DX裡面是把view_matrix和world_matrix分開的,而opengl是只有一個modelview_matrix,

但是這都不是重點,總之,他們是把坐標從一個三維直角坐標系變換到另一個三維直角坐標系裡面,不管中間有多少個相對位置,都能用一個矩陣辦到。

從局部坐標系變換到視點坐標系我建議你的基礎函數這麼寫

void coordinate_from_local_to_view(Matrix modelview, vec3 local_pos, vec3 view_pos);

如果你用齊次坐標,就改成vec4,第四個分量為1

5.從視點坐標系的三維坐標變換到屏幕坐標系。

投影的原理你可以看這個鏈接透視投影的原理和實現,opengl的做這個變換的時候還是用的矩陣乘向量做的(其實我一直覺得這個做法有零點幾個像素的誤差,也許是我沒有完全理解,也許是opengl為了快速計算)。其實原理很簡單,而且如果以光線追蹤的角度來看這個問題

(這個圖是我隨手畫的)

一般在圖形學裡面焦距都是用像素為單位的,物理意義是鏡頭的夾角,封裝之後都用變數fovy來描述,是指視線在Y軸上的夾角。如果上下對稱的話(也就是說光心在屏幕中心),根據fovy可以計算出焦距的長度(像素為單位), focallen = window_height/2.0/tan(fovy/2.0)。

如果直接給出焦距(其實拍的照片還分為fx,fy,主要是每個像素不是絕對的正方形),給出光心位置(cx,cy)。每個像素對應一個射線(道理應該能想明白吧),同意,視點與三維坐標點的連線會與成像平面有個交點,這個交點根據焦距長度可以算出來屏幕上的坐標。對於在視點後方的你根據視點坐標系下的三維坐標的符號來判斷就可以了,而投影到屏幕上的坐標如果超出了範圍, 你也可以選擇裁剪(裁剪就是不用繼續做什麼掃描線填充了,是三個點都在屏幕外時,還是有一個在屏幕外時?)。6.深度測試與alpha混合

在前面做投影時,視點坐標系下的那個Z坐標也不是完全無用的,它可以判斷在填充同一個像素時,是用以前的那個值還是新算出來的值。至於半透明的效果,我覺得有點複雜,一般按照深度順序挨個繪製才比較好,如果是亂序就太困難了,你做的是底層的函數,我覺得提供alpha混合這個功能就好了,混合模式就參照opengl裡面就好了,無非就是「現在得到一個值,本來存在一個值,兩者以多大權重求加權和」的問題。


照著《3d大師》擼了個

http://www.cocoachina.com/bbs/3g/read.php?tid=325470


可以推薦點書,國內大神 毛星雲寫的 《逐夢旅程:Windows遊戲編程之從零開始》適合沒接觸過 DX的新萌。或者看他博客上的文章。現在好像在講DX11. 還有想要了解低層調用DX或opengl就純用C++實現 圖形渲染。可以看 在CSDN上一個大神出的視頻 三維遊戲引擎開發-圖形理論基礎 http://edu.csdn.net/course/detail/864 這是鏈接視頻是付費的 不過才20軟妹幣。還有其他的視頻都很不錯。根據個人需要。 還有就是《3D遊戲編程大師技巧》算然太老了,但是還是值得一看(打下自己的臉(疼))。最好理解原理最好


實現了一個簡單的軟體3D渲染 https://github.com/7heaven/SHSoftwareRasterizer, 目前只有透視紋理,z-buffer,Gouraud光照。主要的參考有《3D遊戲:實時渲染與軟體技術》、wikipedia上的gouraud和z-buffer頁面、Chris Hecker的Perspective correct texture mapping文章。


推薦一本書P. Shirley, S. Marschner, et. al. Fundamentals of Computer Graphics (Third Edition)

這裡面應當有你想要的所有內容

我自己寫過一個基本的光線追蹤器,雖然不是動態渲染的好方案,但是也是一個辦法。光線追蹤的基本演算法都在這本書里也是有的。

這一幅圖就是用我自己的軟體渲染的,在我的超級本上運行了一個半小時。


如果有精力的話,通讀一遍PBRT吧,從理論到代碼,講得非常好。

對了,馬上要出第三版了。


這個我還真搗鼓過.我前幾年搞的是功能機(總內存8M以內,你可以使用的內存1M以內那種破機器)程序開發的.

當時老闆想在上面搞3d. 我就下載了這個,各種改.

http://alleg.sourceforge.net/old.html

allegro 裡面是個寫的很精緻的軟體渲染器,他的早期版本應該都是純的軟體渲染

後期版本我記得弄成了gl內核.

想起來了

Coco3D - Generic 3D Software Renderer

coco3d 是個c++樣式的(Allegro是純C格式的) 這個寫的好是好,但是當年mtk給的C編譯器 對c++支持不好. 所以沒用這個coco3d.

還有:

trenki

http://www.trenki.net/content/view/18/38/

這個沒怎麼看介紹.有興趣你自己研究著玩吧.


《計算機圖形學與幾何造型導論》

已經推薦過無數遍的書


說白了先把固定管線那一套搞明白,少看點應用,多看點原理的,圖形學基礎的內容先看看,磨刀不誤砍柴工么。從簡單的Bresenham、dda畫直線開始,到笛卡爾坐標系這些最基本的內容一步步深入。到三維模型文件解析、紋理映射實現、投影變換等等(啟蒙的圖形學教材里一般都會有吧)。等固定管線搞定了,就開始模仿glsl那一套搞個可編程的管線,一步步來就差不多了吧……


klayge引擎 - http://www.klayge.org

感覺設計思想特別好,讓自己大開眼界

@叛逆者


推薦閱讀:

現階段應該怎麼學習計算機圖形學呢?
計算機圖形學是否已經進入瓶頸期?
為什麼高解析度屏幕在使用低解析度時無法做到點對點映射?
紋理坐標三角形內插值問題,我搞不定了。?
為什麼一些採用 Source 引擎的遊戲,會有「看畫風知引擎」的感覺?

TAG:遊戲開發 | C | 計算機圖形學 | 遊戲編程 |