現代OpenGL是怎麼繪製曲面的?

glEnable(GL_MAP2_VERTEX_3);
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 6, 0, 1, 18, 5, Points[0][0][0]);
glMapGrid2f(12.0, 0.0, 1.0, 12.0, 0.0, 1.0);
glEvalMesh2(GL_FILL, 0, 12.0, 0, 12.0);

還是用這些函數嗎?

之前學了下現代OpenGL,但對繪製曲面絲毫沒有思路,求大神指點一二?


  要畫出一個曲面,首先要有曲面的表達方式。對於OpenGL渲染而言,個人覺得曲面可以分「參數曲面」跟「非參數曲面」兩種

  參數曲面的通常由一組控制點(稱為Patch,可以理解為一個很粗糙的曲面定義,用一個正方形或者單位等邊三角形作為參數空間(稱為Domain)在Domain上通過一定精細度採樣多個參數點,最後把這些Domain上的採樣點映射到Patch所在空間上的三維點坐標,構成比Patch更加精細的曲面。

  非參數化的曲面不像參數曲面那樣直接算出曲面點,它是通過某種細分演算法逐步把Patch細分的,例如Catmull-Clark【1】曲面是通過遞歸細分得到的,這種方法沒辦法通過GPU硬體直接支持,但可以通過參數曲面近似模擬之【2】。

  現代OpenGL支持參數化曲面,包括Bezier曲面,PN-triangle, B-Spline等等,主要是考慮減少CPU-GPU的帶寬以及Vertex Shader的計算。理論上應用可以在pipeline外邊用software自行根據細分規則,通過參數也好,通過遞歸也好,生成光滑精細的Mesh,然後把曲面上的三角形逐個送入GPU去rendering,但這種方法明顯性能不好。

  新的OpenGL引入Tessellation Shader來渲染曲面,包含三大塊:Control Shader, Primitive Generator,Evaluation Shader。Tessellation Shader的輸入僅僅是Control point而非Mesh。

(1)Control Shader是可編程的,負責對patch做變換,比如我們旋轉patch,那麼後面生成的整個精細曲面會被旋轉作用:便於更輕量地實現動畫:

在Control Shader可以改變控制點的數量,做任意自由的變換。如有必要,它還可以計算一些整個Patch共享的常量(Patch Constant)給Evaluation Shader使用,這個就不展開了。

(2)Primitive Generator是個不可編程但可配置的模塊,負責在一個Domain上按應用指定的精細程度採樣Domain點,在Domain空間上實現了細分,最終要Evaluate Shader將Domain坐標變成三維坐標。

OpenGL支持的Domain包括Quad,Triangle,Isoline三種:

(3)Evaluation Shader也是可編程模塊,Control Shader會把轉化後的新Patch送給Evaluation Shader,Primitive Generator所有採樣點送給Evaluation Shader,然後Evaluation Shader根據應用寫的Shader(evalute公式)把每個採樣點轉化為三維曲面三維坐標點,眾多的曲面點就能構成了更精細的曲面了。

一個例子

以最簡單的Bezier曲線為例,它的Domain只有一維t(假設我們用Isoline domain並忽略第二維),配置細分參數Generator在[0 1]間採樣了若干個採樣點t0,t1...tk。

一階Bezier曲線有兩個控制點P0和P1,Control Shader將每兩個頂點作為一個Patch執行三維變換並交給Evaluate Shader,一階Bezier曲線的evaluate公式為:

B(t)=(1-t)P_0+tP_1quad 0leq tleq 1

其實就是P0和P1之間的線段,二階Bezier為:

egin{align*}mathbf{B}^{2}(t)=(1-t)[(1-t)mathbf{P}_{0}+tmathbf{P}_{1}]+t[(1-t)mathbf{P}_{1}+tmathbf{P}_{2}]\=(1-t)^{2}mathbf{P}_{0}+2(1-t)tmathbf{P}_{1}+t^{2}mathbf{P}_{2}quad 0leqslant tleqslant 1end{align*}

例如t=0.25在evaluate後得到以下的曲線點B

n階Bezier曲線有n+1個控制點P0,P1...Pn,evaluate公式為:

mathbf{C}^{n}(t)=sum_{i=0}^{n}B_{i}^{n}(t)mathbf{P}_{i}quad0leqslant tleqslant1B_{i}^{n}(u)=frac{n!}{i!(n-i)!}u^{i}(1-u)^{n-i}

例如n=3,t=0.25在evaluate後得到以下的曲線點B

曲面跟曲線的細分是同一個道理,只不過曲面的Domain是二維(Quad, Isoline)或三維重心坐標(Triangle),具體的Evaluate方法就看選擇何種曲面形式(比如Bezier或B-Spline)了。

建議看下《OpenGL Programming Guide》書中Tessellation Shader那一章,試下把Utah Teapot細分渲染。

【1】Recursively generated B-spline surfaces on arbitrary topological meshes

【2】exact evaluation of catmull-clark subdivision surfaces at arbitrary parameter values


首先,你還在使用即時API。理論上講,這套API在調用的時候就立即把頂點數據上傳,效率會比較低,現在實際上已經淘汰。

不管是DirectX還是OpenGL,現在的大致用法都是:

  • 建立一塊設備端緩存,將緩存視為為頂點緩存;
  • 建立設備端的shader對象,建立設備端渲染程序;
  • 在宿主端生成你的頂點數據;
  • 當需要改變頂點位置時,將頂點數據從宿主端傳到設備端;
  • 使用設備端渲染程序執行渲染。

然後對於你的問題,首先要了解顯卡管線的結構。傳統上的顯卡管線就是個很簡明、固定的東西,大致上就是這樣:

  • 將三角面片的頂點進行空間變換,並進行裁剪等微小的工作;
  • 柵格化,根據貼圖顏色、深度等信息,設定像素的顏色。

可編程管線只是允許你自定義頂點變換、柵格化時候的具體方式(vertex shader、fragment shader),但整個管線還是固定的。可以發現這裡面只是在畫三角形而已,沒有什麼地方允許你從少量的頂點數據生成新的頂點數據。所以你得在宿主端親自將參數化曲面「渲染」為三角面片。

在前些年,顯卡開始引入了tessellation,在頂點步驟前面加了一步,允許你寫一個額外的shader程序從少量頂點數據生成更多的頂點。這個就是用來做參數化曲面生成的。問題是,並不是所有的平台都支持這個,比如OpenGL ES就完全不支持。


可能你最好先從畫個三角形開始。可編程流水線的開發模式跟你這種實在太不一樣了。

等你搞定三角形了,再看tessellation應該也就好懂了。


我想到三條路線

1 參考displacemapping,矢量渲染等演算法, 利用discard指令自己完成邊緣剪切,注意手動抗鋸齒,這些gpugems2,3兩卷就有。

2 tessellation 或幾何Shader。

3 用computeshader自己光柵化或者寫剪裁的輔助信息。


OpenGL從2.0開始已經全面進入可編程管線的階段了,你用的還是最早的OpenGL 1.0時代的API


推薦閱讀:

OpenGL ES2 對象樹繪製與 VBO組織問題?
請問OpenGL的的頂點數據中position必須與color一一對應嗎?
如何正確理解 opengl 的 vao ?
一直說要對不同渲染狀態的對象進行排序 但是排序的優先順序應該是什麼呢?

TAG:OpenGL | OpenGLES | C | 計算機圖形學 |