【連載】現代OpenGL介紹(四)

發(fā)布時(shí)間:2014-7-22 11:22    發(fā)布者:eechina
關(guān)鍵詞: OpenGL
緩沖和紋理包含了OpenGL程序所需要的原材料,但是沒(méi)有著(zhù)色器,它們只是無(wú)效的字節塊。如果你還記得我們概要中的繪圖管線(xiàn),渲染需要一個(gè)頂點(diǎn)著(zhù)色器將我們的頂點(diǎn)映射到屏幕空間,還需要一個(gè)片元著(zhù)色器,對生成的三角形的光柵化片元進(jìn)行著(zhù)色。OpenGL中的著(zhù)色器是使用一種叫作GLSL(GL Shading Language)的語(yǔ)言寫(xiě)的,它看起來(lái)跟C語(yǔ)言很像。在這篇文章中,我們將展示我們的"hello world"程序的著(zhù)色器代碼,然后寫(xiě)C代碼來(lái)加載,編譯并將它鏈接到OpenGL。

頂點(diǎn)著(zhù)色器

這個(gè)是我們的頂點(diǎn)著(zhù)色器的GLSL代碼,在hello-gl.v.glsl中:

#version 110
attribute vec2 position;
varying vec2 texcoord;
void main()
{
gl_Position = vec4(position, 0.0, 1.0);
texcoord = position * vec2(0.5) + vec2(0.5);
}

我先總結這個(gè)著(zhù)色器做什么事情,然后再給出關(guān)于GLSL更多的一些細節。這個(gè)著(zhù)色器首先將頂點(diǎn)的屏幕坐標賦值到gl_Position,它是GLSL提供的一個(gè)預定義變量。在屏幕空間中,坐標(-1,-1)和(1,1)分別代表framebuffer的左下角和右上角;由于我們的頂點(diǎn)數組也是這樣的矩形,我們可以直接拷貝每個(gè)頂點(diǎn)position值的x和y。gl_Position的另外兩個(gè)向量組成是用于深度測試和透視投影(譯者:perspective projection是專(zhuān)業(yè)術(shù)語(yǔ)吧?如何翻譯);我們將在下一節用于3D數學(xué)的時(shí)候好好看一下它們,F在,我們僅僅是將它們的值設為0和1。著(zhù)色器然后做了一些數學(xué)計算來(lái)將我們的屏幕空間點(diǎn)positions從屏幕空間(-1到1)映射到紋理空間(0到1)并將結果賦值給頂點(diǎn)的texcoord。



跟C很相似,GLSL著(zhù)色器從main函數開(kāi)始執行,在GLSL中main函數不接受參數并返回void。GLSL借用了C的預處理關(guān)鍵字用于它自己的指令。#version指令表明下面源代碼的GLSL版本;我們的#version聲明了我們使用GLSL版本1.10(GLSL版本跟OpenGL版本綁定得很緊;1.10是對應于OpenGL 2.0)。GLSL去掉了指針和大多數的C中的各種大小的數值類(lèi)型,只保留了常用的bool,int和float類(lèi)型,但是它添加了一系列的向量和矩陣類(lèi)型,長(cháng)度最多為4個(gè)單元大小。這里你看到的vec2和vec4類(lèi)型分別是兩元素和四元素的float向量。類(lèi)型名也可以作為這些類(lèi)型的構造函數使用;你可以使用單值構造一個(gè)向量,構成的向量的每個(gè)元素都將是這個(gè)值,或者從向量和單值的混合構造,它們會(huì )綁到一起成為一個(gè)更大的向量。GLSL的數學(xué)操作和一些內置函數是定義在這些向量類(lèi)型之上的,可以執行元素級的計算。除了數值類(lèi)型,GLSL還提供特殊的sampler數據類(lèi)型用于紋理取樣,在下面片元著(zhù)色器中我們將會(huì )看到。這些基本類(lèi)型可以集合成數組和用戶(hù)自定義的struct類(lèi)型。

頂點(diǎn)著(zhù)色器使用GLSL程序中特殊定義的全局變量和繪圖管線(xiàn)環(huán)境進(jìn)行通信。它的輸入來(lái)自于uniform變量以及attribute變量,分別提供狀態(tài)值和頂點(diǎn)數組的每個(gè)頂點(diǎn)屬性。著(zhù)色器將它的每個(gè)頂點(diǎn)輸出賦值到varying變量。GLSL預定義了一些varying變量來(lái)接收繪圖管線(xiàn)中使用的特殊的輸出,包括這里我們使用的gl_Position變量。

片元著(zhù)色器

現在讓我們看一下片元著(zhù)色器源代碼,在hello-gl.f.glsl中:

#version 110
uniform float fade_factor;
uniform sampler2D textures[2];

varying vec2 texcoord;
void main()
{
gl_FragColor = mix(
texture2D(textures[0], texcoord),
texture2D(textures[1], texcoord),
fade_factor
);
}

在片元著(zhù)色器中,有些輕微的變化。varying變量成了這里的輸入:每個(gè)片元著(zhù)色器中的varying變量是跟頂點(diǎn)著(zhù)色器中的同名變量鏈接在一起的,并且對這個(gè)變量,每個(gè)片元著(zhù)色器調用都接收到一個(gè)光柵化的頂點(diǎn)著(zhù)色器的輸出。片元著(zhù)色器也給出了一系列不同的gl*預定義變量。glFragColor是其中最重要的,著(zhù)色器將會(huì )給它一個(gè)vec4的RGBA顏色值。片元著(zhù)色器可以訪(fǎng)問(wèn)到跟頂點(diǎn)著(zhù)色器同樣的uniform系列,但是不能訪(fǎng)問(wèn)到attribute變量。



我們的片元著(zhù)色器使用GLSL內置的texture2D函數來(lái)對兩個(gè)紋理從texcoord的uniform狀態(tài)進(jìn)行取樣。然后它調用內置的mix函數基于當前的fade_factor值對兩個(gè)紋理值進(jìn)行組合:0會(huì )輸出只有第一個(gè)紋理的取樣,1只會(huì )輸出第二個(gè)紋理的取樣,而中間的值會(huì )給出兩者的一個(gè)混色。

既然我們已經(jīng)察看了GLSL著(zhù)色器代碼,讓我們回到C并加載著(zhù)色器到OpenGL。

存儲我們的著(zhù)色器對象

static struct {
/* ... fields for buffer and texture objects */
GLuint vertex_shader, fragment_shader, program;

struct {
GLint fade_factor;
GLint textures[2];
} uniforms;

struct {
GLint position;
} attributes;

GLfloat fade_factor;
}
g_resources;

首先,讓我們添加一些域到我們的gresources結構體中,存儲我們的著(zhù)色器對象名字和創(chuàng )建后的程序對象。類(lèi)似緩沖和紋理對象,著(zhù)色器和程序對象也是用GLuint句柄命名。我們還添加了一些域來(lái)存放整型變量,我們需要在我們的著(zhù)色器的uniform和attribute變量引用它們。最后,我們添加了一個(gè)域來(lái)存浮點(diǎn)數值,我們將在每一幀把fadefactor賦值給它。

編譯著(zhù)色器對象


static GLuint make_shader(GLenum type, const char *filename)
{
GLint length;
GLchar *source = file_contents(filename, &length);
GLuint shader;
GLint shader_ok;

if (!source)
return 0;

OpenGL從GLSL源代碼編譯著(zhù)色器對象并保存生成的GPU機器碼。沒(méi)有一個(gè)標準的方式來(lái)將GLSL程序預編譯成一個(gè)二進(jìn)制--你必須每次都從源代碼編譯著(zhù)色器。這里我們在一個(gè)單獨的文件中寫(xiě)著(zhù)色器代碼,這樣每次我們改變著(zhù)色器代碼時(shí)就不用重編譯我們的C代碼。

shader = glCreateShader(type);
glShaderSource(shader, 1, (const GLchar**)&source, &length);
free(source);
glCompileShader(shader);

著(zhù)色器和程序對象脫離了緩沖和紋理所使用的那套glGen和glBind協(xié)議。不像緩沖和紋理函數,操作著(zhù)色器和程序的函數直接使用對象的整數名作為參數。對象不需要綁定到任何目標。這里,我們對過(guò)調用glCreateShader創(chuàng )建一個(gè)著(zhù)色器對象,著(zhù)色器參數可以是GLVERTEXSHADER或者GLFRAGMENTSHADER。然后我們提供一個(gè)源代碼的字符串指針給glShaderSource,并告訴OpenGL去使用glCompileShader編譯著(zhù)色器。這一步跟C的編譯處理過(guò)程很類(lèi)型;編譯的著(zhù)色器對象也是類(lèi)型一個(gè).o或者.obj文件。正如C項目中一樣,任意多的頂點(diǎn)著(zhù)色器和片元著(zhù)色器可以被鏈接到一起形成一個(gè)工作的程序,每個(gè)著(zhù)色器對象引用到其它同類(lèi)型著(zhù)色器對象中定義的函數,只要被引用函數全部可以被解析并且頂點(diǎn)著(zhù)色器和片元著(zhù)色器的main函數都提供了。

glGetShaderiv(shader, GL_COMPILE_STATUS, &shader_ok);
if (!shader_ok) {
fprintf(stderr, "Failed to compile %s:\n", filename);
show_info_log(shader, glGetShaderiv, glGetShaderInfoLog);
glDeleteShader(shader);
return 0;
}
return shader;
}

同樣正如C程序,一個(gè)著(zhù)色器的代碼塊可能會(huì )由于語(yǔ)法錯誤,引用不存在的函數,或者類(lèi)型不匹配而鏈接失敗。OpenGL對每個(gè)著(zhù)色器對象維護一個(gè)由GLSL編譯器發(fā)出的錯誤或警告信息記錄。在編譯著(zhù)色器之后,我們需要使用glGetShaderiv檢查它的GLCOMPILESTATUS。如果編譯失敗了,我們使用showinfolog函數顯示信息記錄并放棄。下面是showinfolog函數:

static void show_info_log(
GLuint object,
PFNGLGETSHADERIVPROC glGet__iv,
PFNGLGETSHADERINFOLOGPROC glGet__InfoLog
)
{
GLint log_length;
char *log;

glGet__iv(object, GL_INFO_LOG_LENGTH, &log_length);
log = malloc(log_length);
glGet__InfoLog(object, log_length, NULL, log);
fprintf(stderr, "%s", log);
free(log);
}

我們將glGetShaderiv和glGetShaderInfoLog函數作為參數傳給showinfolog,這樣我們可以在后面對程序對象重用函數(那些PFNGL*函數指針名是由GLEW提供的)。我們使用GLINFOLOG_LENGTH參數調用glGetShaderiv來(lái)得到信息記錄的長(cháng)度,分配緩沖來(lái)存放它,并使用glGetShaderInfoLog來(lái)得到它的內容。

鏈接程序對象

static GLuint make_program(GLuint vertex_shader, GLuint fragment_shader)
{
GLint program_ok;

GLuint program = glCreateProgram();
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
glLinkProgram(program);

如果著(zhù)色器對象是GLSL編譯過(guò)程的對象文件,那么程序對象在完成時(shí)是可執行的。我們使用glCreateProgram創(chuàng )建一個(gè)程序對象,使用glAttachShader附上著(zhù)色器對象跟它進(jìn)行鏈接,最后使用glLinkProgram調用鏈接過(guò)程。

glGetProgramiv(program, GL_LINK_STATUS, &program_ok);
if (!program_ok) {
fprintf(stderr, "Failed to link shader program:\n");
show_info_log(program, glGetProgramiv, glGetProgramInfoLog);
glDeleteProgram(program);
return 0;
}
return program;
}

當然,鏈接也可能會(huì )失敗,由于被引用函數未定義,缺少main函數,片元著(zhù)色器使用了非頂點(diǎn)著(zhù)色器提供的varying輸入,以及其它一些類(lèi)似C程序鏈接失敗的原因。我們檢查程序的GLLINKSTATUS并將它的日志信息使用showinfolog導出,這次使用用于program的glGetProgramiv和glGetProgramInfoLog函數。

現在我們將make_resources用來(lái)編譯和鏈接我們著(zhù)色器的最后一部分填上:

static int make_resources(void)
{
/* make buffers and textures ... */
g_resources.vertex_shader = make_shader(
GL_VERTEX_SHADER,
"hello-gl.v.glsl"
);
if (g_resources.vertex_shader == 0)
return 0;

g_resources.fragment_shader = make_shader(
GL_FRAGMENT_SHADER,
"hello-gl.f.glsl"
);
if (g_resources.fragment_shader == 0)
return 0;

g_resources.program = make_program(
g_resources.vertex_shader,
g_resources.fragment_shader
);
if (g_resources.program == 0)
return 0;

查找著(zhù)色器變量位置


g_resources.uniforms.fade_factor
= glGetUniformLocation(g_resources.program, "fade_factor");
g_resources.uniforms.textures[0]
= glGetUniformLocation(g_resources.program, "textures[0]");
g_resources.uniforms.textures[1]
= glGetUniformLocation(g_resources.program, "textures[1]");

g_resources.attributes.position
= glGetAttribLocation(g_resources.program, "position");
return 1;
}

GLSL鏈接器將一個(gè)GLint位置賦值到每個(gè)uniform變量和頂點(diǎn)的attribute。uniforms或者attributes的結構體和數組會(huì )被繼續分解,每個(gè)域都會(huì )對它的位置賦值。當我們使用程序進(jìn)行渲染時(shí),將變量賦值到uniform變量以及映射頂點(diǎn)數組的屬性,我們將需要使用這些整數位置。這里,我們使用函數glGetUniformLocation和glGetAttribLocation來(lái)查找這些位置,以字符串形式給它們變量名,結構體域名,或者數組元素名字。我們然后在我們程序的g_resource結構體中記錄這些位置。程序鏈接在一起,并且記錄中有了uniform和attribute位置,我們可以準備好了使用程序進(jìn)行渲染。

下次,渲染

我知道我在吊你胃口,最后部分還沒(méi)完成,還沒(méi)有一個(gè)完整的可以運行的程序。我將在在下次,也就是本章最后一部分,修復它,到時(shí)我會(huì )寫(xiě)代碼讓繪圖管線(xiàn)運作起來(lái)渲染我們的場(chǎng)景。
本文地址:http://selenalain.com/thread-131049-1-1.html     【打印本頁(yè)】

本站部分文章為轉載或網(wǎng)友發(fā)布,目的在于傳遞和分享信息,并不代表本網(wǎng)贊同其觀(guān)點(diǎn)和對其真實(shí)性負責;文章版權歸原作者及原出處所有,如涉及作品內容、版權和其它問(wèn)題,我們將根據著(zhù)作權人的要求,第一時(shí)間更正或刪除。
您需要登錄后才可以發(fā)表評論 登錄 | 立即注冊

關(guān)于我們  -  服務(wù)條款  -  使用指南  -  站點(diǎn)地圖  -  友情鏈接  -  聯(lián)系我們
電子工程網(wǎng) © 版權所有   京ICP備16069177號 | 京公網(wǎng)安備11010502021702
快速回復 返回頂部 返回列表
午夜高清国产拍精品福利|亚洲色精品88色婷婷七月丁香|91久久精品无码一区|99久久国语露脸精品|动漫卡通亚洲综合专区48页