GLSL
GLSL語(yǔ)言就是編寫自定義著色器的語(yǔ)言,Xcode并不支持編譯和鏈接,需要開發(fā)者手動(dòng)去設(shè)置。GLSL語(yǔ)言編寫的著色器其本質(zhì)就是一個(gè)字符串,一般用.vsh和.fsh做后綴區(qū)分頂點(diǎn)著色器和片元著色器。
*在著色器中,盡可能不要用中文注釋,否則可能會(huì)報(bào)錯(cuò)
GLSL語(yǔ)言常用的API
- 向量數(shù)據(jù)類型(vec2,vec3,vec4比較常用)
類型 | 描述 |
---|---|
vec2,vec3,vec4 | 2分量、3分量、4分量浮點(diǎn)向量 |
ivec2,ivec3,ivec4 | 2分量、3分量、4分量整型向量 |
uvec2,uvec3,uvec4 | 2分量、3分量、4分量無(wú)符號(hào)整型向量 |
bvec2,bvec3,bvec4 | 2分量、3分量、4分量bool型向量 |
- 矩陣數(shù)據(jù)類型
類型(mat列×行) | 描述 |
---|---|
mat2,mat2x2 | 兩?兩列 |
mat3,mat3x3 | 三行三列 |
mat4,mat4x4 | 四行四列 |
mat2x3 | 三行兩列 |
mat2x4 | 四行兩列 |
mat3x2 | 兩行三列 |
mat3x4 | 四行三列 |
mat4x2 | 兩行四列 |
mat4x3 | 三行四列 |
- 變量存儲(chǔ)限定符
varying:當(dāng)片元著色器中用到頂點(diǎn)著色器中的某個(gè)變量時(shí),只需要在片元著色器中定義一樣的變量名然后用該限定符修飾即可。
attribute:該修飾符主要用于客戶端想頂點(diǎn)著色器傳遞頂點(diǎn)、紋理、顏色、法線等數(shù)據(jù)。
uniform:在著色器中該修飾符常用來(lái)修飾常量,在著色器中該修飾符修飾的屬性一般不建議修改,在客戶端傳入數(shù)據(jù)的時(shí)候修改。(例如視圖矩陣,投影矩陣,投影視圖矩陣等)
常用限定符
限定符 | 描述 |
---|---|
<none> | 只是普通的本地變量,外部不見,外部不可訪問(wèn) |
const | ?個(gè)編譯常量,或者說(shuō)是?個(gè)對(duì)函數(shù)來(lái)說(shuō)為只讀的參數(shù) |
in/varying | 從以前階段傳遞過(guò)來(lái)的變量 |
in/varying centroid | ?個(gè)從以前的階段傳遞過(guò)來(lái)的變量,使?質(zhì)?插值 |
out/attribute | 傳遞到下?個(gè)處理階段或者在?個(gè)函數(shù)中指定?個(gè)返回值 |
out/attribute centroid | 傳遞到下?個(gè)處理階段,質(zhì)心插值 |
uniform | ?個(gè)從客戶端代碼傳遞過(guò)來(lái)的變量,在頂點(diǎn)之間不做改變 |
自定義著色器創(chuàng)建及使用流程
一、創(chuàng)建
頂點(diǎn)著色器
- 創(chuàng)建頂點(diǎn)坐標(biāo)和紋理坐標(biāo)的變量,用attribute修飾
- 創(chuàng)建一個(gè)用varying修飾的紋理坐標(biāo),使得片元著色器能夠從頂點(diǎn)著色器獲取紋理坐標(biāo)
- 創(chuàng)建內(nèi)建變量gl_Position(用于存放頂點(diǎn)著色器處理后的結(jié)構(gòu))
- 創(chuàng)建main函數(shù),用于頂點(diǎn)坐標(biāo)的轉(zhuǎn)換處理及處理結(jié)果的賦值
//頂點(diǎn)坐標(biāo)
attribute vec4 position;
紋理坐標(biāo)
attribute vec2 textCoordinate;
//紋理坐標(biāo)
varying lowp vec2 varyTextCoord;
void main(){
//通過(guò)????varying 修飾的??????varyTextCoord,??????????????????????????將紋理坐標(biāo)傳遞到片元著色器
varyTextCoord = textCoordinate;
//給內(nèi)江變量gl_Position賦值
gl_Position = position;
}
片元著色器
- 設(shè)置片元著色器float類型數(shù)據(jù)的默認(rèn)精度
- 定義紋理坐標(biāo)變量,用varying修飾,變量名必須跟頂點(diǎn)著色器中的紋理坐標(biāo)變量名相同,否則獲取不到數(shù)據(jù)。
- 定義紋理采樣器,用unifom修飾,用于獲取紋理坐標(biāo)每個(gè)像素點(diǎn)的紋素(紋理坐標(biāo)點(diǎn)的像素)
- 創(chuàng)建內(nèi)建變量gl_FragColor(用于存放頂點(diǎn)著色器處理后的結(jié)構(gòu))
- 創(chuàng)建main函數(shù),用于處理紋理的填充,并將處理后的顏色值賦值
//指定float的默認(rèn)精度
precision highp float;
//紋理坐標(biāo)
varying lowp vec2 varyTextCoord;
//紋理采樣器(獲取對(duì)應(yīng)的紋理ID)
uniform sampler2D colorMap;
void main(){
//texture2D(紋理采樣器,紋理坐標(biāo)),獲取對(duì)應(yīng)坐標(biāo)紋素
//紋理坐標(biāo)添加到對(duì)應(yīng)像素點(diǎn)上,即將讀取的紋素賦值給內(nèi)建變量 gl_FragColor
gl_FragColor = texture2D(colorMap, varyTextCoord);
}
二、初始化
1、 創(chuàng)建圖層setupLayer
//1、創(chuàng)建圖層
- (void)setupLayer{
// 1、創(chuàng)建特殊圖層
self.myEagLayer = (CAEAGLLayer*)self.layer;
// 2、設(shè)置scale
[self setContentScaleFactor:[[UIScreen mainScreen] scale]];
// 3、設(shè)置描述屬性
self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@false, kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
}
+ (Class)layerClass{
return [CAEAGLLayer class];
}
參數(shù)說(shuō)明
屬性 | 說(shuō)明 | 默認(rèn)值 |
---|---|---|
kEAGLDrawablePropertyRetainedBacking | 表示繪圖表面顯示后,是否保留其內(nèi)容 | false |
kEAGLDrawablePropertyColorFormat | 可繪制表面的內(nèi)部顏色緩存區(qū)格式 | kEAGLColorFormatRGBA8 |
2、 創(chuàng)建上下文setupContext
上下文主要是用于保存OpenGL ES中的狀態(tài),是一個(gè)狀態(tài)機(jī),不論是GLKIt還是GLSL,都是需要context的
//2、創(chuàng)建上下文
- (void)setupContext{
// 1、指定OpenGL ES 渲染API版本
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
// 2、創(chuàng)建圖形上下文
EAGLContext *context = [[EAGLContext alloc] initWithAPI:api];
// 3、判斷是否創(chuàng)建成功
if (!context) {
NSLog(@"Create context failed!");
return;
}
// 4、設(shè)置圖形上下文
if (![EAGLContext setCurrentContext:context]) {
NSLog(@"setCurrentContext failed");
return;
}
// 5、將局部變量賦值給全局變量
self.myContext = context;
}
3、 清理緩沖區(qū)deleteRenderAndFrameBuffer
//3、清空緩存區(qū)
- (void)deleteRenderAndFrameBuffer{
// 清空渲染緩存區(qū)
glDeleteBuffers(1, &_myColorRenderBuffer);
self.myColorRenderBuffer = 0;
// 清空幀緩存區(qū)
glDeleteBuffers(1, &_myColorFrameBuffer);
self.myColorFrameBuffer = 0;
}
4、 申請(qǐng)標(biāo)識(shí)符并綁定到緩沖區(qū)
- RenderBuffer:是一個(gè)通過(guò)應(yīng)用分配的2D圖像緩沖區(qū),需要附著在FrameBuffer上
- FrameBuffer:是一個(gè)收集顏色、深度和模板緩存區(qū)的附著點(diǎn),簡(jiǎn)稱FBO,即是一個(gè)管理者,用來(lái)管理RenderBuffer,且FrameBuffer沒有實(shí)際的存儲(chǔ)功能,真正實(shí)現(xiàn)存儲(chǔ)的是RenderBuffer
FrameBuffer有3個(gè)附著點(diǎn)
- 顏色附著點(diǎn)(Color Attachment):管理紋理、顏色緩沖區(qū)
- 深度附著點(diǎn)(depth Attachment):會(huì)影響顏色緩沖區(qū),管理深度緩沖區(qū)(Depth Buffer)
- 模板附著點(diǎn)(Stencil Attachment):管理模板緩沖區(qū)(Stencil Buffer)
RenderBuffer有3種緩存區(qū) - 深度緩存區(qū)(Depth Buffer):存儲(chǔ)深度值等
- 紋理緩存區(qū):存儲(chǔ)紋理坐標(biāo)中對(duì)應(yīng)的紋素、顏色值等
- 模板緩存區(qū)(Stencil Buffer):存儲(chǔ)模板
設(shè)置RenderBuffer
- (void)setupRenderBuffer{
// 1、定義一個(gè)緩存區(qū)ID
GLuint buffer;
// 2、申請(qǐng)一個(gè)緩存區(qū)標(biāo)識(shí)符
glGenRenderbuffers(1, &buffer);
// 3、賦值給全局變量
self.myColorRenderBuffer = buffer;
// 4、將標(biāo)識(shí)符綁定到GL_RENDERBUFFER
glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
// 5、將可繪制對(duì)象drawable object的CAEAGLLayer的存儲(chǔ)綁定到OpenGL ES renderBuffer對(duì)象
[self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
}
設(shè)置FrameBuffer,將RenderBuffer通過(guò)glFramebufferRenderbuffer函數(shù)綁定到FrameBuffer中的GL_COLOR_ATTACHMENT0附著點(diǎn)上,通過(guò)FrameBuffer來(lái)管理RenderBuffer,RenderBuffer存儲(chǔ)相關(guān)數(shù)據(jù)到相應(yīng)緩存區(qū)
//5、設(shè)置FrameBuffer
- (void)setupFrameBuffer{
// 1、定義一個(gè)ID
GLuint buffer;
// 2、申請(qǐng)一個(gè)緩存區(qū)標(biāo)識(shí)符
glGenBuffers(1, &buffer);
// 3、賦值給全局變量
self.myColorFrameBuffer = buffer;
// 4、將標(biāo)識(shí)符綁定到GL_FRAMEBUFFER
glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
// 5、將渲染緩存區(qū)的myColorRenderBuffer 通過(guò) glFramebufferRenderbuffer函數(shù)綁定到GL_COLOR_ATTACHMENT0上
/*
glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
參數(shù)1:綁定到的目標(biāo)
參數(shù)2:FrameBuffer的附著點(diǎn)
參數(shù)3:需要綁定的渲染緩沖區(qū)目標(biāo)
參數(shù)4:渲染緩沖區(qū)
*/
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);
}
二、著色器的鏈接
1、初始化
初始化背景顏色,清理緩存,需要將視口大小設(shè)置為屏幕大小
// 設(shè)置清屏顏色 & 清除屏幕
glClearColor(0.3f, 0.45f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 1、設(shè)置視口大小
CGFloat scale = [[UIScreen mainScreen] scale];
glViewport(self.frame.origin.x * scale, self.frame.origin.y * scale, self.frame.size.width * scale, self.frame.size.height * scale);
2、加載自定義著色器
- 讀取自定義著色器文件的前提是需要獲得文件的路徑,將其傳入loadShaders函數(shù)進(jìn)行加載
NSString *vertFile = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"vsh"];
NSString *fragFile = [[NSBundle mainBundle] pathForResource:@"shaderf" ofType:@"fsh"];
3、編譯shader
- 根據(jù)文件路徑讀取著色器文件中的源碼字符串,并將其轉(zhuǎn)換為c中的字符串,類型為GLchar
- 根據(jù)傳入的著色器類型type,調(diào)用glCreateShader函數(shù)創(chuàng)建一個(gè)帶有唯一標(biāo)識(shí)ID的著色器,此時(shí)著色器中并沒有附加相對(duì)應(yīng)的源碼
- 將讀取的著色器源碼通過(guò)glShaderSource函數(shù)附加到創(chuàng)建的shader上,并將shader的ID返回給loadShaders函數(shù)中shader,以ID來(lái)獲取并使用對(duì)應(yīng)的著色器
- 通過(guò)glCompileShader函數(shù)將shader上附加的源碼編譯成目標(biāo)代碼
- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file{
//1.讀取文件路徑字符串
NSString* content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
const GLchar* source = (GLchar *)[content UTF8String];
NSLog(@"content %@, source %s", content, source);
//2.創(chuàng)建一個(gè)shader(根據(jù)type類型)
*shader = glCreateShader(type);
//3.將著色器源碼附加到著色器對(duì)象上。
//參數(shù)1:shader,要編譯的著色器對(duì)象 *shader
//參數(shù)2:numOfStrings,傳遞的源碼字符串?dāng)?shù)量 1個(gè)
//參數(shù)3:strings,著色器程序的源碼(真正的著色器程序源碼)
//參數(shù)4:lenOfStrings,長(zhǎng)度,具有每個(gè)字符串長(zhǎng)度的數(shù)組,或NULL,這意味著字符串是NULL終止的
glShaderSource(*shader, 1, &source,NULL);
NSLog(@"shader %d", *shader);
//4.把著色器源代碼編譯成目標(biāo)代碼
glCompileShader(*shader);
}
4、加載著色器
分別將頂點(diǎn)著色器和片元著色器編譯完成后,并返回著色器對(duì)應(yīng)的ID,然后通過(guò)glAttachShader函數(shù)將頂點(diǎn)和片元的shader分別附著到program上,然后釋放不再使用的shader,并賦值給全局的program。
-(GLuint)loadShaders:(NSString *)vert Withfrag:(NSString *)frag
{
//1.定義2個(gè)零時(shí)著色器對(duì)象
GLuint verShader, fragShader;
//創(chuàng)建program
GLint program = glCreateProgram();
//2.編譯頂點(diǎn)著色程序、片元著色器程序
//參數(shù)1:編譯完存儲(chǔ)的底層地址
//參數(shù)2:編譯的類型,GL_VERTEX_SHADER(頂點(diǎn))、GL_FRAGMENT_SHADER(片元)
//參數(shù)3:文件路徑
[self compileShader:&verShader type:GL_VERTEX_SHADER file:vert];
[self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
NSLog(@"verShader %d, fragShader: %d", verShader, fragShader);
//3.創(chuàng)建最終的程序
glAttachShader(program, verShader);
glAttachShader(program, fragShader);
//4.釋放不需要的shader
glDeleteShader(verShader);
glDeleteShader(fragShader);
return program;
}
5、鏈接program
- 通過(guò)glLinkProgram函數(shù)鏈接program
- 可以通過(guò)glGetProgramiv函數(shù)通過(guò)制定值GL_LINK_STATUS獲取鏈接的狀態(tài),判斷鏈接是成功還是失敗
- 如果鏈接失敗,可以通過(guò)glGetProgramIngoLog函數(shù)獲取錯(cuò)誤信息日志,根據(jù)錯(cuò)誤信息一致去排查問(wèn)題
6、使用鏈接成功的program
glUseProgram(self.myPrograme);
三、設(shè)置參數(shù)
1、處理頂點(diǎn)數(shù)據(jù)
- 設(shè)置頂點(diǎn)數(shù)據(jù):主要是初始化頂點(diǎn)坐標(biāo)和紋理坐標(biāo)
- 開辟頂點(diǎn)緩存區(qū):用于將頂點(diǎn)數(shù)據(jù)從CPU拷貝至GPU
- 打開頂點(diǎn)/片元的通道
2、開辟頂點(diǎn)緩沖區(qū) - 通過(guò)GLuint定義一個(gè)頂點(diǎn)緩存區(qū)ID
- 通過(guò)glGenBuffers函數(shù),申請(qǐng)一個(gè)頂點(diǎn)緩存區(qū)標(biāo)識(shí)符
- 通過(guò)glBindBuffers函數(shù),將緩存區(qū)的標(biāo)識(shí)符綁定到GL_ARRAY_BUFFER
- 通過(guò)glBufferData函數(shù),將頂點(diǎn)數(shù)據(jù)copy到GPU中
GLuint attrBuffer;
glGenBuffers(1, &attrBuffer);
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
3、打開頂點(diǎn)、片元通道
- 通過(guò)glGetAttribLocation函數(shù),獲取vertex attribute的入口,需要傳入兩個(gè)參數(shù),一個(gè)是program,一個(gè)是自定義著色器文件中變量名字符串,這里著重強(qiáng)調(diào)下!??!第二個(gè)參數(shù)的字符串必須與著色器文件中對(duì)應(yīng)的變量名保持一致!
- 通過(guò)glEnableVertexAttribArray函數(shù),設(shè)置合適的格式從buffer里讀取數(shù)據(jù),即設(shè)置讀取入口
- 通過(guò)glVertexAttribPointer函數(shù),設(shè)置讀取方式
// (1)注意:第二參數(shù)字符串必須和shaderv.vsh中的輸入變量:position保持一致
GLuint position = glGetAttribLocation(self.myPrograme, "position");
// (2).設(shè)置合適的格式從buffer里面讀取數(shù)據(jù)
glEnableVertexAttribArray(position);
// (3).設(shè)置讀取方式
// 參數(shù)1:index,頂點(diǎn)數(shù)據(jù)的索引
// 參數(shù)2:size,每個(gè)頂點(diǎn)屬性的組件數(shù)量,1,2,3,或者4.默認(rèn)初始值是4.
// 參數(shù)3:type,數(shù)據(jù)中的每個(gè)組件的類型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默認(rèn)初始值為GL_FLOAT
// 參數(shù)4:normalized,固定點(diǎn)數(shù)據(jù)值是否應(yīng)該歸一化,或者直接轉(zhuǎn)換為固定值。(GL_FALSE)
// 參數(shù)5:stride,連續(xù)頂點(diǎn)屬性之間的偏移量,默認(rèn)為0;
// 參數(shù)6:指定一個(gè)指針,指向數(shù)組中的第一個(gè)頂點(diǎn)屬性的第一個(gè)組件。默認(rèn)為0
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);
// 9、處理紋理數(shù)據(jù)
// (1).glGetAttribLocation,用來(lái)獲取vertex attribute的入口的.
// 注意:第二參數(shù)字符串必須和shaderv.vsh中的輸入變量:textCoordinate保持一致
// (2).設(shè)置合適的格式從buffer里面讀取數(shù)據(jù)
// (3).設(shè)置讀取方式
GLuint textColor = glGetAttribLocation(self.myPrograme, "textCoordinate");
glEnableVertexAttribArray(textColor);
4、加載紋理
將png/jpg圖片解壓成位圖,并通過(guò)自定義著色器讀取紋理每個(gè)像素點(diǎn)的紋素
- 紋理解壓縮:將UIImage轉(zhuǎn)換為CGImageRef
- 圖片重繪:使用CGContextRef常見的上下文,調(diào)用CGContextDrawImage函數(shù)使用默認(rèn)方式進(jìn)行繪制,再繪制之前,需要獲取圖片的大小、寬、高等數(shù)據(jù),因?yàn)槔L制時(shí)需要使用這些數(shù)據(jù)
- 綁定紋理:通過(guò)glBindTexture函數(shù)綁定,當(dāng)只有一個(gè)紋理的時(shí)候,默認(rèn)的紋理ID是0,且0一直是激活狀態(tài),因此是可以省略glGenTexture的
- 設(shè)置紋理屬性:通過(guò)glTexParameteri函數(shù)分別設(shè)置 放大/縮小的過(guò)濾方式 和 S/T的環(huán)繞模式
- 載入紋理:通過(guò)glTexImage2D函數(shù)載入紋理,載入完成后,釋放指向紋理數(shù)據(jù)的指針
5、設(shè)置紋理采樣器
主要是獲取紋理中對(duì)應(yīng)像素點(diǎn)的的顏色值,即紋素 - 通過(guò)glGetUniformLocation函數(shù),獲取fragment uniform的入口,需要傳入兩個(gè)參數(shù),一個(gè)是program,一個(gè)是自定義片元著色器文件中變量名字符串colorMap,這里著重強(qiáng)調(diào)下?。?!第二個(gè)參數(shù)的字符串必須與著色器文件中對(duì)應(yīng)的變量名保持一致!
- 通過(guò)glUniform1i函數(shù)獲取紋素,有兩個(gè)參數(shù),第一個(gè)參數(shù)是 fragment uniform的入口,本質(zhì)也是一個(gè)ID,第二個(gè)參數(shù)是紋理的ID,使用的是默認(rèn)的ID 0
四、繪制
- 調(diào)用glDrawArrays函數(shù)指定圖元連接方式進(jìn)行繪制
- context調(diào)用presentRenderbuffer函數(shù)將繪制好的圖片渲染到屏幕上進(jìn)行顯示