A Simple OpenGL Shader Example
eryar@163.com
Abstract. OpenGL Shading Language, the high-level programming language defined to allow application writers to write programs that execute on the programmable processors defined within OpenGL. Informally the language is sometimes referred to as GLSL. The GLSL has been made part of the OpenGL standard as of OpenGL2.0. The paper focus on a simple example of OpenGL Shader, which can be used as a guide of GLSL.
Key Words. OpenGL, OpenGL Shading Language, GLSL, Shader, Qt
1. Introduction
很早之前,從網上下載到這么一本書《OpenGL Shading Language》,翻看了幾遍,終不得要領。后來看到一本由仇德元編寫的《GPGPU編程技術—從GLSL、CUDA到OpenCL》,對GPU的了新的認識,對其性能刮目相看。書中給出的一個簡單例子,也便于對Shader的入門。
Figure 1.1 OpenGL Shading Language and GPGPU
從OpenGL Shading Language的出現可以發現程序員的一個特點,那就是不喜歡一成不變的東西,希望自己有更多的控制權,有個性,向往自由。如果要有個性,那就要引入這個新的東西GLSL了,增加了學習成本。不過從OpenGL2.0之后,Shader已經成了OpenGL標準的一部分,新版的OpenGL的書籍中都少不了shader的內容。Shader成了真實感圖形、高性能計算中不可或缺的技術,學習掌握新的工具是為了生活更輕松。
為了讓例子簡單,本文在Qt中實現了一個簡單的Shader示例,通過這個例子入門后,再結合《OpenGL Shading Language》去實現更炫的效果。再者就是去理解OpenCASCADE中的shader應用。
2.OpenGL operation pipeline
原來有個問題一直困擾著我,那就是三維的模型怎么在二維的計算機屏幕上顯示的。現在明白了這個就其實是顯卡的主要工作,并按一定的流水線來實現的。圖形流水線(pipeline)是GPU工作的通用模型,它以某種形式表示的三維場景為輸入,輸出二維的光柵圖像(raster images),也就是位圖。這樣三維的模型就可以在二維的屏幕上進行顯示了。下面依次解釋流水線中的關鍵步驟:
v 圖形流水線的起點是一個三維模型。這個三維模型可以是用軟件設計出的三維游戲人物,也可以是逆向工程(Reverse Engineering)中用激光掃描儀(Laser Scanner)設備采集的頂點,也可以是幾何造型內核(如OpenCASCADE)將模型網格化生成的頂點等。不論何種模型,在計算機處理之前都一定要經過采樣而得到有限的離散頂點,每個頂點都可以被一個向量描述為一個三維坐標系里的點。這些可用來描述三維頂點組成了點云(Point Cloud)。如果采樣頻率足夠高,得到的頂點就可以細致地描述模型的表面。點云中的點可以由一個列表表示,列表中每一項是某點的三維坐標值。同時,列表中每一點都帶有該點的顏色信息。這個頂點列表即是流水線的輸入數據,從起點進入流水線。
v 頂點可以用來形成多邊形,從而擬合出近似的表面。由頂點形成多邊形最常用的一種方法是三角化(triangulation),即每相鄰的三個點組成一個三角形。接下來每個頂點要經過一系列的逐頂點操作(per-vertex operation),比如計算每個頂點的光照、坐標變換等。
v 由于顯示輸出的需要,用戶會定義一個視口(viewport),即觀察模型的位置和角度。然后,模型被投影到與視口觀察方向垂直的平面上。這個投影變換也是硬件加速的。根據視口的大小,投影結果有可能被裁剪(clipping)掉一部分。
v 接受模型投影的平面是一個幀緩存(frame buffer),它是一個由像素(pixels)定義的光柵化平面。光柵化(rasterization)的過程,實際上就是決定幀緩存上的哪些像素該取怎么樣的值。通過采樣和插值,光柵化器(rasterizer)會決定一幅最接近原投影圖像的位圖。
v 這些像素或者由像素連成的片段還須經歷一些逐片段操作(per-fragment operation),也就是說它們的顏色也可以根據算法改變。另外紋理映射(texturing or texture mapping)在這一階段也會覆蓋某些像素的值。對于投影和光柵化的結果,還要判斷片段的可見性,也就是遮擋探測(occlusion detection)。
v 最后幀緩存里面的結果被刷新到顯示器上。該過程以較高的頻率重復,因為人的視頻延遲,感覺到的就是連續的。
Figure 2.1 A simplified version of the OpenGL pipeline
Figure 2.2 Overview of OpenGL operation
可以將上述OpenGL的渲染管線想成有兩個機器來完成主要的工作:一個機器處理頂點;一個處理片段。對于早期的OpenGL而言,只是兩個機器是內置的,程序員不能改變他們的工作方式。
Figure 2.3 The OpenGL Fixed-Function Pipeline[3]
然而可編程的Shader(programmable shader)則是可以對這兩個機器的工作進行干預。
3.Using GLSL Shaders
當你想在程序中使用一個頂點Shader或片段Shader時,需要按如下步驟進行:
v 創建一個Shader對象;
v 將Shader的源文件編譯到這個對象;
v 驗證源文件是否編譯成功;
然后將這些shader鏈接到一個Shader程序:
v 創建一個Shader程序;
v 將創建的Shader對象綁定到這個Shader程序;
v 鏈接Shader程序;
v 驗證鏈接是否成功;
v 將shader對象應用到頂點及片段的處理;
如下圖所示:
Figure 3.1 Shader Creation Flowchart
4.The simplest Shader Example
本來想用glut來寫個簡單的例子的,但是識別不了glCreateShader這種函數,發現要下載一些第三方庫才可以,如glew。看到Qt中已經有封裝好的QGLShader和QGLShaderProgram,所以還是決定用Qt來寫個簡單的例子,從而來對OpenGL的shader有個感性的認識。其中關于Shader部分的主要是這個函數:
void ShaderWidget::setShader()
{
if (!isValid())
{
return;
}
const QGLContext* aGlContext = context();
QGLShaderProgram* aShaderProgram = new QGLShaderProgram(aGlContext);
aShaderProgram->addShaderFromSourceFile(QGLShader::Vertex, "vertex.vert");
aShaderProgram->addShaderFromSourceFile(QGLShader::Fragment, "fragment.frag");
aShaderProgram->link();
aShaderProgram->bind();
QString aLog = aShaderProgram->log();
}
先看一下沒有使用Shader之前的程序的效果圖:
Figure 4.1 A teapot model without shader
其中添加兩個shader,一個是vertex shader: vertex.vert,一個是fragment shader:fragment.frag。在vertex shader中對頂點的坐標進行變換,代碼如下所示:
void main()
{
vec4 a = gl_ModelViewProjectionMatrix * gl_Vertex;
gl_Position.x = 0.4 * a.x;
gl_Position.y = 0.1 * a.y;
}
OpenGL內置變量gl_Position保存了當前頂點的位置信息,上面的頂點著色器修改了每個頂點的X坐標和Y坐標,使得輸出了一個扭曲的teapot。
片段著色器當前片段的顏色改成紫色,片段著色器代碼如下:
void main()
{
gl_FragColor = vec4(0.627, 0.125, 0.941, 1.0);
}
為了驗證程序是使用了著色器,運行程序得到如下圖所示:
Figure 4.2 A Teapot with shader
在不重新編譯程序的情況下,只修改片段著色器中的代碼,改成如下所示內容:
void main()
{
gl_FragColor = vec4(0.627, 0.125, 0.0, 1.0);
}
保存片段著色器代碼后直接運行程序,可得到如下圖所示的紅色teapot:
Figure 4.3 Change Fragment Shader
可以看到shader的確是起了作用。本文最后將給出程序的完整代碼及shader的代碼,便于測試。
5.Conclusion
OpenGL的Shader給了程序員對OpenGL的更多的控制權,可對其頂點處理和片段處理進行更個性化的配置以達到炫酷的效果。
Shader的使用步驟是先創建shader對象,再將源碼編譯到shader對象。最后通過shader程序,將shader添加并編譯、鏈接和使用。
最后在Qt中以一個簡單的例子來驗證了shader的效果,入門之后便于理解GLSL更詳細的功能,以使自己的可視化程序具有更高的性能,更酷的效果。
6. References
1. 仇德元. GPGPU編程技術—從GLSL、CUDA到OpenCL. 機械工業出版社. 2012
2. Randi J. Rost. OpenGL Shading Language. Addison Wesley. 2006
3. Dave Shreiner. OpenGL Programming Guide(7th). Addison-Wesley. 2009