GPGPU 概念 2:內(nèi)核(Kernels) = 著色器(shaders)
在這一章節(jié)中,我們來討論GPU和CPU兩大運(yùn)算模塊最基本的區(qū)別,以及理清一些算法和思想。一但我們弄清楚了GPU是如何進(jìn)行數(shù)據(jù)并行運(yùn)算的,那我們要編寫一個(gè)自已的著色程序,還是比較容易的。
面向循環(huán)的CPU運(yùn)算 vs. 面向內(nèi)核的GPU數(shù)據(jù)并行運(yùn)算
讓我們來回憶一下我們所想要解決的問題:y = y + alpha* x; 在CPU上,通常我們會(huì)使用一個(gè)循環(huán)來遍歷數(shù)組中的每個(gè)元素。如下:
[cpp] view plaincopyfor (int i=0; i《N; i++)
dataY[i] = dataY[i] + alpha * dataX[i];
每一次的循環(huán),都會(huì)有兩個(gè)層次的運(yùn)算在同時(shí)運(yùn)作:在循環(huán)這外,有一個(gè)循環(huán)計(jì)數(shù)器在不斷遞增,并與我們的數(shù)組的長(zhǎng)度值作比較。而在循環(huán)的內(nèi)部,我們利用循環(huán)計(jì)數(shù)器來確定數(shù)組的一個(gè)固定位置,并對(duì)數(shù)組該位置的數(shù)據(jù)進(jìn)行訪問,在分別得到兩個(gè)數(shù)組該位置的值之后,我們便可以實(shí)現(xiàn)我們所想要的運(yùn)算:兩個(gè)數(shù)組的每個(gè)元素相加了。這個(gè)運(yùn)算有一個(gè)非常重要的特點(diǎn):那就是我們所要訪問和計(jì)算的每個(gè)數(shù)組元數(shù),它們之間是相互獨(dú)立的。這句話的意思是:不管是輸入的數(shù)組,還是輸出結(jié)果的數(shù)組,對(duì)于同一個(gè)數(shù)組內(nèi)的各個(gè)元素是都是相互獨(dú)立的,我們可以不按順序從第一個(gè)算到最后一個(gè),可先算最后一個(gè),再算第一個(gè),或在中間任意位置選一個(gè)先算,它得到的最終結(jié)果是不變的。如果我們有一個(gè)數(shù)組運(yùn)算器,或者我們有N個(gè)CPU的話,我們便可以同一時(shí)間把整個(gè)數(shù)組給算出來,這樣就根本不需要一個(gè)外部的循環(huán)。我們把這樣的示例叫做SIMD(single instruction multiple data)?,F(xiàn)在有一種技術(shù)叫做“partial loop unrolling”就是讓允許編譯器對(duì)代碼進(jìn)行優(yōu)化,讓程序在一些支持最新特性(如:SSE , SSE2)的CPU上能得到更高效的并行運(yùn)行。
在我們這個(gè)例子中,輸入數(shù)數(shù)組的索引與輸出數(shù)組的索引是一樣,更準(zhǔn)確地說,是所有輸入數(shù)組下標(biāo),都與輸出數(shù)組的下標(biāo)是相同的,另外,在對(duì)于兩個(gè)數(shù)組,也沒有下標(biāo)的錯(cuò)位訪問或一對(duì)多的訪問現(xiàn)像,如:y[i] = -x[i-1] + 2*x[[i] - x[i+1] 。這個(gè)公式可以用一句不太專業(yè)的語(yǔ)言來描術(shù):“組數(shù)Y中每個(gè)元素的值等于數(shù)組X中對(duì)應(yīng)下標(biāo)元素的值的兩倍,再減去該下標(biāo)位置左右兩邊元素的值?!?/p>
在這里,我們打算使用來實(shí)現(xiàn)我們所要的運(yùn)算的GPU可編程模塊,叫做片段管線(fragment pipeline),它是由多個(gè)并行處理單元組成的,在GeFore7800GTX中,并行處理單元的個(gè)數(shù)多達(dá)24個(gè)。在硬件和驅(qū)動(dòng)邏輯中,每個(gè)數(shù)據(jù)項(xiàng)會(huì)被自動(dòng)分配到不同的渲染線管線中去處理,到底是如何分配,則是沒法編程控制的。從概念觀點(diǎn)上看,所有對(duì)每個(gè)數(shù)據(jù)頂?shù)倪\(yùn)算工作都是相互獨(dú)立的,也就是說不同片段在通過管線被處理的過程中,是不相互影響的。在前面的章節(jié)中我們?cè)懻撨^,如何實(shí)現(xiàn)用一個(gè)紋理來作為渲染目標(biāo),以及如何把我們的數(shù)組保存到一個(gè)紋理上。因此這里我們分析一下這種運(yùn)算方式:片段管線就像是一個(gè)數(shù)組處理器,它有能力一次處理一張紋理大小的數(shù)據(jù)。雖然在內(nèi)部運(yùn)算過程中,數(shù)據(jù)會(huì)被分割開來然后分配到不同的片段處理器中去,但是我們沒辦法控制片段被處理的先后順序,我們所能知道的就是“地址”,也就是保存運(yùn)算最終結(jié)果的那張紋理的紋理坐標(biāo)。我們可能想像為所有工作都是并行的,沒有任何的數(shù)據(jù)相互依賴性。這就是我們通常所說的數(shù)據(jù)并行運(yùn)算(data-paralel computing)。
現(xiàn)在,我們已經(jīng)知道了解決問題的核心算法,我們可以開始討論如何用可編程片段管線來編程實(shí)現(xiàn)了。內(nèi)核,在GPU中被叫做著色器。所以,我們要做的就是寫一個(gè)可能解決問題的著色器,然后把它包含在我們的程序中。在本教程程中,我們會(huì)分別討論如何用CG著色語(yǔ)言及GLSL著色語(yǔ)言來實(shí)現(xiàn),接下來兩個(gè)小節(jié)就是對(duì)兩種語(yǔ)言實(shí)現(xiàn)方法的討論,我們只要學(xué)會(huì)其中一種方法就可以了,兩種語(yǔ)言各有它自已的優(yōu)缺點(diǎn),至于哪個(gè)更好一點(diǎn),則不是本教程所要討論的范圍。
用CG著色語(yǔ)言來編寫一個(gè)著色器
為了用CG語(yǔ)言來著色渲染,我們首先要來區(qū)分一下CG著色語(yǔ)言和CG運(yùn)行時(shí)函數(shù),前者是一門新的編程語(yǔ)言,所寫的程序經(jīng)編譯后可以在GPU上運(yùn)行,后者是C語(yǔ)言所寫的一系列函數(shù),在CPU上運(yùn)算,主要是用來初始化環(huán)境,把數(shù)據(jù)傳送給GPU等。在GPU中,有兩種不同的著色,對(duì)應(yīng)顯卡渲染流水線的兩個(gè)不同的階段,也就是頂點(diǎn)著色和片段著色。本教程中,頂點(diǎn)著色階段,我們采用固定渲染管線。只在片段著色階段進(jìn)行編程。在這里,使用片段管線能更容易解決我們的問題,當(dāng)然,頂點(diǎn)著色也會(huì)有它的高級(jí)用途,但本文不作介紹。另外,從傳統(tǒng)上講,片段著色管線提供更強(qiáng)大的運(yùn)算能力。
讓我們從一段寫好了的CG著色代碼開始。回憶一下CPU內(nèi)核中包含的一些算法:在兩個(gè)包含有浮點(diǎn)數(shù)據(jù)的數(shù)組中查找對(duì)應(yīng)的值。我們知道在GPU中紋理就等同于CPU的數(shù)組,因此在這里我們使用紋理查找到代替數(shù)組查找。在圖形運(yùn)算中,我們通過給定的紋理坐標(biāo)來對(duì)紋理進(jìn)行采樣。這里有一個(gè)問題,就是如何利用硬件自動(dòng)計(jì)算生成正確的紋理坐標(biāo)。我們把這個(gè)問題壓后到下面的章節(jié)來討論。為了處理一些浮點(diǎn)的常量,我們有兩種處理的方法可選:我們可以把這些常量包含在著色代碼代中,但是如果要該變這些常量的值的話,我們就得把著色代碼重新編譯一次。另一種方法更高效一點(diǎn),就是把常量的值作為一個(gè)uniform參數(shù)傳遞給GPU。uniform參數(shù)的意思就是:在整個(gè)渲染過程中值不會(huì)被改變的。以下代碼就是采用較高較的方法寫的。
[cpp] view plaincopyfloat saxpy (
float2 coords : TEXCOORD0,
uniform sampler2D textureY,
uniform sampler2D textureX,
uniform float alpha ) : COLOR
{
float result;
float yval=y_old[i];
float y = tex2D(textureY,coords);
float xval=x[i];
float x = tex2D(textureX,coords);
y_new[i]=yval+alpha*xval;
result = y + alpha * x;
return result;
}
從概念上講,一個(gè)片段著色器,就是像上像這樣的一段小程序,這段代碼在顯卡上會(huì)對(duì)每個(gè)片段運(yùn)行一編。在我們的代碼中,程序被命名為saxpy。它會(huì)接收幾個(gè)輸入?yún)?shù),并返回一個(gè)浮點(diǎn)值。用作變量復(fù)制的語(yǔ)法叫做語(yǔ)義綁定(semantics binding):輸入輸出參數(shù)名稱是各種不同的片段靜態(tài)變量的標(biāo)識(shí),在前面的章節(jié)中我們把這個(gè)叫“地址”。片段著色器的輸出參數(shù)必須綁定為COLOR語(yǔ)義,雖然這個(gè)語(yǔ)義不是很直觀,因?yàn)槲覀兊妮敵鰠?shù)并不是傳統(tǒng)作用上顏色,但是我們還是必須這樣做。綁定一個(gè)二分量的浮點(diǎn)元組(tuple ,float2)到TEXCOORD0語(yǔ)義上,這樣便可以在運(yùn)行時(shí)為每個(gè)像素指定一對(duì)紋理坐標(biāo)。對(duì)于如何在參數(shù)中定義一個(gè)紋理樣本以及采用哪一個(gè)紋理采樣函數(shù),這就要看我們種用了哪一種紋理對(duì)像,參考下表:
如果我們使用的是四通道的紋理而不是LUMINANCE格式的紋理,那們只須把上面代碼中的用來保存紋理查詢結(jié)果的浮點(diǎn)型變量改為四分量的浮點(diǎn)變量(float4 )就可以了。由于GPU具有并行運(yùn)算四分量數(shù)的能力,因此對(duì)于使用了rectangle為對(duì)像的RGBA格式紋理,我們可以采用以下代碼:
?。踓pp] view plaincopyfloat4 saxpy (
float2 coords : TEXCOORD0,
uniform samplerRECT textureY,
uniform samplerRECT textureX,
uniform float alpha ) : COLOR
{
float4 result;
float4 y = texRECT(textureY,coords);
float4 x = texRECT(textureX,coords);
result = y + alpha*x;
// equivalent: result.rgba=y.rgba+alpha*x.rgba
// or: result.r=y.r+alpha*x.y; result.g=。。。
return result;
}
我們可以把著色代碼保存在字符數(shù)組或文本文件中,然后通過OpenGL的CG運(yùn)行時(shí)函數(shù)來訪問它們。
建立CG運(yùn)行環(huán)境
在這一小節(jié),中描術(shù)了如何在OpenGL應(yīng)用程序中建立Cg運(yùn)行環(huán)境。首先,我們要包含CG的頭文件(#include 《cg/cggl.h》),并且把CG的庫(kù)函數(shù)指定到編譯連接選項(xiàng)中,然后聲明一些變量。
[cpp] view plaincopy// Cg vars
CGcontext cgContext;
CGprofile fragmentProfile;
CGprogram fragmentProgram;
CGparameter yParam, xParam, alphaParam;
char* program_source = “float saxpy( [。。。。] return result; } ”;
CGcontext 是一個(gè)指向CG運(yùn)行時(shí)組件的入口指針,由于我們打算對(duì)片段管線進(jìn)行編程,因此我們要一個(gè)fragment profile,以及一個(gè)程序container。為了簡(jiǎn)單起見,我們還聲明了三個(gè)句柄,分別對(duì)應(yīng)了著色程序中的三個(gè)沒有語(yǔ)義的入口參數(shù)。我們用一個(gè)全局的字符串變量來保存前面所寫好的著色代碼?,F(xiàn)在就把所有的CG初始化工作放在一個(gè)函數(shù)中完成。這里只作了最簡(jiǎn)單的介紹,詳細(xì)的內(nèi)容可以查看CG手冊(cè),或者到Cg Toolkit page.網(wǎng)頁(yè)上學(xué)習(xí)一下。
譯注:對(duì)于CG入門,可以看一下《CG編程入門》這篇文章:http://www.physdev.com/phpbb/cms_view_article.php?aid=7
?。踓pp] view plaincopyvoid initCG(void) {
// set up Cg
cgContext = cgCreateContext();
fragmentProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);
cgGLSetOptimalOptions(fragmentProfile);
// create fragment program
fragmentProgram = cgCreateProgram (
cgContext,CG_SOURCE,program_source,
fragmentProfile,“saxpy”,NULL);
// load program
cgGLLoadProgram (fragmentProgram);
// and get parameter handles by name
yParam = cgGetNamedParameter (fragmentProgram,“textureY”);
xParam = cgGetNamedParameter (fragmentProgram,“textureX”);
alphaParam = cgGetNamedParameter (fragmentProgram,“alpha”);
}
用OpenGL著色語(yǔ)言來編寫一個(gè)著色器
使用OpenGL的高級(jí)著色語(yǔ)言,我們不需要另外引入任何的頭文件或庫(kù)文件,因因它們?cè)诎惭b驅(qū)動(dòng)程序的時(shí)候就一起被建立好了。三個(gè)OpenGL的擴(kuò)展:(ARB_shader_objects,ARB_vertex_shader 和ARB_fragment_shader)定義了相關(guān)的接口函數(shù)。它的說明書(specification )中對(duì)語(yǔ)言本身作了定義。兩者,API和GLSL語(yǔ)言,現(xiàn)在都是OpenGL2.0內(nèi)核的一個(gè)重要組成部份。但是如果我們用的是OpenGL的老版本,就要用到擴(kuò)展。
我們?yōu)槌绦驅(qū)ο穸x了一系列的全局變量,包括著色器對(duì)像及數(shù)據(jù)變量的句柄,通過使用這些句柄,我們可以訪問著色程序中的變量。前面兩個(gè)對(duì)像是簡(jiǎn)單的數(shù)據(jù)容器,由OpenGL進(jìn)行管理。一個(gè)完整的著色程序是由頂點(diǎn)著色和片段著色兩大部份組成的,每部分又可以由多個(gè)著色程序組成。
[cpp] view plaincopy// GLSL vars
GLhandleARB programObject;
GLhandleARB shaderObject;
GLint yParam, xParam, alphaParam;
編寫著色程序和使用Cg語(yǔ)言是相似的,下面提供了兩個(gè)GLSL的例子,兩個(gè)主程序的不同之處在于我們所采用的紋理格式。變量的類型入關(guān)鍵字與CG有很大的不同,一定要按照OpenGL的定義來寫。
?。踓pp] view plaincopy// shader for luminance data | // shader for RGBA data
// and texture rectangles | // and texture2D
|
uniform samplerRect textureY; | uniform sampler2D textureY;
uniform samplerRect textureX; | uniform sampler2D textureX;
uniform float alpha; | uniform float alpha;
|
void main(void) { | void main(void) {
float y = textureRect( | vec4 y = texture2D(
textureY, | textureY,
gl_TexCoord[0].st).x; | gl_TexCoord[0].st);
float x = textureRect( | vec4 x = texture2D(
textureX, | textureX
gl_TexCoord[0].st).x; | gl_TexCoord[0].st);
gl_FragColor.x = | gl_FragColor =
y + alpha*x; | y + alpha*x;
} | }
下面代碼就是把所有對(duì)GLSL的初始化工作放在一個(gè)函數(shù)中實(shí)現(xiàn),GLSL API是被設(shè)計(jì)成可以模擬傳統(tǒng)的編譯及連接過程,更多的細(xì)節(jié),請(qǐng)參考橙皮書(Orange Book),或者查找一些GLSL的教程來學(xué)習(xí)一下,推薦到Lighthouse 3D’s GLSL tutorial 網(wǎng)站上看一下
?。踓pp] view plaincopyvoid initGLSL(void) {
// create program object
programObject = glCreateProgramObjectARB();
// create shader object (fragment shader) and attach to program
shaderObject = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
glAttachObjectARB (programObject, shaderObject);
// set source to shader object
glShaderSourceARB(shaderObject, 1, &program_source, NULL);
// compile
glCompileShaderARB(shaderObject);
// link program object together
glLinkProgramARB(programObject);
// Get location of the texture samplers for future use
yParam = glGetUniformLocationARB(programObject, “textureY”);
xParam = glGetUniformLocationARB(programObject, “textureX”);
alphaParam = glGetUniformLocationARB(programObject, “alpha”);
}
Back to top
GPGPU 概念3:運(yùn)算 = 繪圖
在這一章節(jié)里,我們來討論一下如何把本教程前面所學(xué)到的知識(shí)拼湊起來,以及如何使用這些知識(shí)來解決前面所提出的加權(quán)數(shù)組相加問題:y_new =y_old +alpha *x 。關(guān)于執(zhí)行運(yùn)算的部份,我們把所有運(yùn)算都放在performComputation()這個(gè)函數(shù)中實(shí)現(xiàn)。一共有四個(gè)步驟:首先是激活內(nèi)核,然后用著色函數(shù)來分配輸入輸出數(shù)組的空間,接著是通過渲染一個(gè)適當(dāng)?shù)膸缀螆D形來觸發(fā)GPU的運(yùn)算,最后一步是簡(jiǎn)單驗(yàn)證一下我們前面所列出的所有的基本理論。
準(zhǔn)備好運(yùn)算內(nèi)核
使用CG運(yùn)行時(shí)函數(shù)來激活運(yùn)算內(nèi)核就是顯卡著色程序。首先用enable函數(shù)來激活一個(gè)片段profile,然后把前面所寫的著色代碼傳送到顯卡上并綁定好。按規(guī)定,在同一時(shí)間內(nèi)只能有一個(gè)著色器是活動(dòng)的,更準(zhǔn)確的說,是同一時(shí)間內(nèi),只能分別激活一個(gè)頂點(diǎn)著色程序和一個(gè)片段著色程序。由于本教程中采用了固定的頂點(diǎn)渲染管線,所以我們只關(guān)注片段著色就行了,只需要下面兩行代碼便可以了。
[cpp] view plaincopy// enable fragment profile
cgGLEnableProfile(fragmentProfile);
// bind saxpy program
cgGLBindProgram(fragmentProgram);
如果使用的是GLSL著色語(yǔ)言,這一步就更容易實(shí)現(xiàn)了,如果我們的著色代碼已以被成功地編譯連接,那么剩下我們所需要做的就只是把程序作為渲染管線的一部分安裝好,代碼如下:
glUseProgramObjectARB(programObject);
建立用于輸入的數(shù)組和紋理
在CG環(huán)境中,我們先要把紋理的標(biāo)識(shí)與對(duì)應(yīng)的一個(gè)uniform樣本值關(guān)聯(lián)起來,然后激活該樣本。這樣該紋理樣本便可以在CG中被直接使用了。
[cpp] view plaincopy// enable texture y_old (read-only)
cgGLSetTextureParameter(yParam, y_oldTexID);
cgGLEnableTextureParameter(yParam);
// enable texture x (read-only)
cgGLSetTextureParameter(xParam, xTexID);
cgGLEnableTextureParameter(xParam);
// enable scalar alpha
cgSetParameter1f(alphaParam, alpha);
但在GLSL中,我們必須把紋理與不同的紋理單元綁定在一起(在CG中,這部分由程序自動(dòng)完成),然后把這些紋理單元傳遞給我們的uniform參數(shù)。
?。踓pp] view plaincopy// enable texture y_old (read-only)
glActiveTexture(GL_TEXTURE0);
glBindTexture(textureParameters.texTarget,yTexID[readTex]);
glUniform1iARB(yParam,0); // texunit 0
// enable texture x (read-only)
glActiveTexture(GL_TEXTURE1);
glBindTexture(textureParameters.texTarget,xTexID);
glUniform1iARB(xParam, 1); // texunit 1
// enable scalar alpha
glUniform1fARB(alphaParam,alpha);
建立用于輸出的紋理及數(shù)組
定義用于輸出的紋理,從本質(zhì)上講,這和把數(shù)據(jù)傳輸?shù)揭粋€(gè)FBO紋理上的操作是一樣的,我們只需要指定OpenGL函數(shù)參數(shù)的特定意義就可以了。這里我們只是簡(jiǎn)單地改變輸出的方向,也就是,把目標(biāo)紋理與我們的FBO綁定在一起,然后使用標(biāo)準(zhǔn)的GL擴(kuò)展函數(shù)來把該FBO指為渲染的輸出目標(biāo)。
?。踓pp] view plaincopy// attach target texture to first attachment point
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT,
texture_target, y_newTexID, 0);
// set the texture as render target
glDrawBuffer (GL_COLOR_ATTACHMENT0_EXT);
準(zhǔn)備運(yùn)算
讓們暫時(shí)先來回顧一下到目前為止,我們所做過了的工作:我們實(shí)現(xiàn)了目標(biāo)像素、紋理坐標(biāo)、要繪制的圖形三者元素一一對(duì)應(yīng)的關(guān)系。我們還寫好了一個(gè)片段著色器,用來讓每個(gè)片段渲染的時(shí)候都可以運(yùn)行一次?,F(xiàn)在剩下來還要做的工作就是:繪制一個(gè)“合適的幾何圖形” ,這個(gè)合適的幾何圖形,必須保證保存在目標(biāo)紋理中的數(shù)據(jù)每個(gè)元素就會(huì)去執(zhí)行一次我們的片段著色程序。換句話來說,我們必須保證紋理中的每個(gè)數(shù)據(jù)頂在片段著色中只會(huì)被訪一次。只要指定好我們的投影及視口的設(shè)置,其它的工作就非常容易:我們所需要的就只是一個(gè)剛好能覆蓋整個(gè)視口的填充四邊形。我們定義一個(gè)這樣的四邊形,并調(diào)用標(biāo)準(zhǔn)的OpenGL函數(shù)來對(duì)其進(jìn)行渲染。這就意味著我們要直接指定四邊形四個(gè)角的頂點(diǎn)坐標(biāo),同樣地我們還要為每個(gè)頂點(diǎn)指定好正確的紋理坐標(biāo)。由于我們沒有對(duì)頂點(diǎn)著色進(jìn)行編程,程序會(huì)把四個(gè)頂點(diǎn)通過固定的渲染管線傳輸?shù)狡聊豢臻g中去。光冊(cè)處理器(一個(gè)位于頂點(diǎn)著色與片段著色之間的固定圖形處理單元)會(huì)在四個(gè)頂點(diǎn)之間進(jìn)行插值處理,生成新的頂點(diǎn)來把整個(gè)四邊形填滿。插值操作除了生成每個(gè)插值點(diǎn)的位置之外,還會(huì)自動(dòng)計(jì)算出每個(gè)新頂點(diǎn)的紋理坐標(biāo)。它會(huì)為四邊形中每個(gè)像素生成一個(gè)片段。由于我們?cè)趯懫沃髦薪壎讼嚓P(guān)的語(yǔ)義,因此插值后的片段會(huì)被自動(dòng)發(fā)送到我們的片段著色程序中去進(jìn)行處理。換句話說,我們渲染的這個(gè)簡(jiǎn)單的四邊形,就可以看作是片段著色程序的數(shù)據(jù)流生成器。由于目標(biāo)像素、紋理坐標(biāo)、要繪制的圖形三者元素都是一一對(duì)應(yīng)的,從而我們便可以實(shí)現(xiàn):為數(shù)組每個(gè)輸出位置觸發(fā)一次片段著色程序的運(yùn)行。也就是說通過渲染一個(gè)帶有紋理的四邊形,我們便可以觸發(fā)著色內(nèi)核的運(yùn)算行,著色內(nèi)核會(huì)為紋理或數(shù)組中的每個(gè)數(shù)據(jù)項(xiàng)運(yùn)行一次。
使用 texture rectangles 紋理坐標(biāo)是與像素坐標(biāo)相同的,我樣使用下面一小段代碼便可以實(shí)現(xiàn)了。
?。踓pp] view plaincopy// make quad filled to hit every pixel/texel
glPolygonMode(GL_FRONT,GL_FILL);
// and render quad
glBegin(GL_QUADS);
glTexCoord2f(0.0, 0.0);
glVertex2f(0.0, 0.0);
glTexCoord2f(texSize, 0.0);
glVertex2f(texSize, 0.0);
glTexCoord2f(texSize, texSize);
glVertex2f(texSize, texSize);
glTexCoord2f(0.0, texSize);
glVertex2f(0.0, texSize);
glEnd();
如果使用 texture2D ,就必須單位化所有的紋理坐標(biāo),等價(jià)的代碼如下:
[cpp] view plaincopy// make quad filled to hit every pixel/texel
glPolygonMode(GL_FRONT,GL_FILL);
// and render quad
glBegin(GL_QUADS);
glTexCoord2f(0.0, 0.0);
glVertex2f(0.0, 0.0);
glTexCoord2f(1.0, 0.0);
glVertex2f(texSize, 0.0);
glTexCoord2f(1.0, 1.0);
glVertex2f(texSize, texSize);
glTexCoord2f(0.0, 1.0);
glVertex2f(0.0, texSize);
glEnd();
這里提示一下那些做高級(jí)應(yīng)用的程序員:在我們的著色程序中,只用到了一組紋理坐標(biāo),但是我們也可以為每個(gè)頂點(diǎn)定義多組不同的紋理坐標(biāo),相關(guān)的更多細(xì)節(jié),可以查看一下glMultiTexCoord()函數(shù)的使用。
Back to top
評(píng)論