本篇文章主要講解如何使用GLSL語言來進(jìn)行紋理的繪制。
首先我們需要了解如何編寫GLSL程序飞傀,經(jīng)過之前的學(xué)習(xí)我們知道皇型,我們能操作的只有 頂點(diǎn)著色器 和 片元著色器。所以我們需要?jiǎng)?chuàng)建兩個(gè)空文件分別命名為shaderv.vsh
和 shaderf.fsh
:
當(dāng)然文件名字看自己的習(xí)慣助析,可以修改。
shaderv.vsh
和 shaderf.fsh
分別表示 頂點(diǎn)著色器 和 片元著色器 的程序文件椅您。
GLSL代碼
先來看下 shaderv.vsh
文件的代碼:
attribute vec4 position;
attribute vec2 textureCoordinate;
varying lowp vec2 varyTextCoord
void main()
{
varyTextCoord = textureCoordinate;
gl_Position = position;
}
position
:頂點(diǎn)坐標(biāo)
textureCoordinate
:紋理坐標(biāo)
varyTextCoord
:用來傳遞紋理坐標(biāo)到片元著色器外冀。
gl_Position
:內(nèi)建變量,用來接收頂點(diǎn)坐標(biāo)掀泳。
shaderf.fsh
文件的代碼:
precision highp float;
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;
void main()
{
gl_FragColor = texture2D(colorMap, varyTextCoord);
}
precision highp float;
:設(shè)置默認(rèn)精度雪隧。
varying lowp vec2 varyTextCoord;
:用來接收頂點(diǎn)著色器傳過來的紋理坐標(biāo)。需要跟shaderv.vsh
文件的名字保持一致员舵,完全一樣。
colorMap;
:顏色貼圖(也就是紋理)
gl_FragColor
:內(nèi)建變量
繪制流程
簡單了解了GLSL
程序書寫位置,下面我們介紹下繪制流程护昧。
首先我們需要將view
的layer
替換為CAEAGLLayer
類型的layer
:
+ (Class)layerClass {
return [CAEAGLLayer class];
}
然后在layoutSubviews
方法里進(jìn)行操作繪制:
- (void)layoutSubviews {
//1.設(shè)置圖層
[self setupLayer];
//2.設(shè)置上下文
[self setupContext];
//3.清空緩存區(qū)
[self clearColorBuffer];
//4.設(shè)置RenderBuffer
[self setupRenderBuffer];
//5.設(shè)置FrameBuffer
[self setupFrameBuffer];
//6.渲染圖層
[self renderLayer];
}
該段代碼大概分為6步:1倍啥、設(shè)置圖層 2、設(shè)置上下文 3韭邓、清空緩存區(qū) 4措近、設(shè)置RenderBuffer 5、設(shè)置FrameBuffer 6女淑、渲染圖層瞭郑。這些都是自定義的方法。
1鸭你、設(shè)置圖層
- (void)setupLayer {
self.eaglLayer = (CAEAGLLayer *)self.layer;
[self setContentScaleFactor:[[UIScreen mainScreen] scale]];
self.eaglLayer.drawableProperties = @{
kEAGLDrawablePropertyRetainedBacking:@(0),
kEAGLDrawablePropertyColorFormat:kEAGLColorFormatRGBA8
};
}
drawableProperties
繪制屬性:
kEAGLDrawablePropertyRetainedBacking
表示繪圖表面顯示后屈张,是否保留其內(nèi)容, true
保留擒权, false
不保留。
kEAGLDrawablePropertyColorFormat
可繪制表面的內(nèi)部顏色緩存區(qū)格式阁谆,這個(gè)key對(duì)應(yīng)的值是一個(gè)NSString指定特定顏色緩存區(qū)對(duì)象碳抄。默認(rèn)是kEAGLColorFormatRGBA8
。
2笛厦、設(shè)置上下文
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (![EAGLContext setCurrentContext:context]) {
NSLog(@"context set failed");
return;
}
self.currentContext = context;
3纳鼎、清空緩存區(qū)
glDeleteFramebuffers(1, &_colorFrameBuffer);
self.colorFrameBuffer = 0;
glDeleteRenderbuffers(0, &_colorRenderBuffer);
self.colorRenderBuffer = 0;
4、設(shè)置RenderBuffer
GLuint renderBufferID;
glGenRenderbuffers(1, &renderBufferID);
self.colorRenderBuffer = renderBufferID;
//綁定(將標(biāo)識(shí)符綁定到GL_RENDERBUFFER)
glBindRenderbuffer(GL_RENDERBUFFER, renderBufferID);
//綁定layer到GL_RENDERBUFFER
[self.currentContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.eaglLayer];
5裳凸、設(shè)置FrameBuffer
GLuint frameBufferID;
glGenFramebuffers(1, &frameBufferID);
self.colorFrameBuffer = frameBufferID;
glBindFramebuffer(GL_FRAMEBUFFER, frameBufferID);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.colorRenderBuffer);
生成幀緩存區(qū)之后贱鄙,則需要將renderbuffer
跟framebuffer
進(jìn)行綁定,調(diào)用glFramebufferRenderbuffer
函數(shù)進(jìn)行綁定到對(duì)應(yīng)的附著點(diǎn)上姨谷,后面的繪制才能起作用逗宁。
6、渲染圖層
6.1 基本操作
glClearColor(0.2, 0.6, 0.4, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
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);
這段代碼大家應(yīng)該很熟悉了梦湘,都是些常規(guī)的操作瞎颗。
6.2 加載shader程序
NSString *vPath = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"vsh"];
NSString *fPath = [[NSBundle mainBundle] pathForResource:@"shaderf" ofType:@"fsh"];
NSLog(@"vPath:%@ \n fPath:%@",vPath, fPath);
self.program = [self loadShaderWithVPath:vPath fPath:fPath];
我們重點(diǎn)看下loadShaderWithVPath:fPath:
代碼:
- (GLuint)loadShaderWithVPath:(NSString *)vPath fPath:(NSString *)fPath {
//創(chuàng)建program
GLuint program = glCreateProgram();
//編譯頂點(diǎn)著色程序、片元著色器程序
GLuint vShader, fShader;
[self compileShader:&vShader type:GL_VERTEX_SHADER file:vPath];
[self compileShader:&fShader type:GL_FRAGMENT_SHADER file:fPath];
//將頂點(diǎn)著色程序捌议、片元著色器程序附著在program上
glAttachShader(program, vShader);
glAttachShader(program, fShader);
//使用完之后刪除
glDeleteShader(vShader);
glDeleteShader(fShader);
return program;
}
- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)path {
*shader = glCreateShader(type);
NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
const GLchar *source = (GLchar *)[content UTF8String];
glShaderSource(*shader, 1, &source, NULL);
//著色器源代碼編譯成目標(biāo)代碼
glCompileShader(*shader);
}
-
glShaderSource(*shader, 1, &source, NULL);
將著色器源碼附加到著色器對(duì)象上: -
shader
:要編譯的著色器對(duì)*shader
哼拔。 -
numOfStrings
:傳遞的源碼字符串?dāng)?shù)量 1個(gè) -
strings
:著色器程序的源碼(真正的著色器程序源碼&sourc
) -
lenOfStrings
:長度,具有每個(gè)字符串長度的數(shù)組瓣颅,或NULL
倦逐,這意味著字符串是NULL
終止的.
6.3 鏈接
glLinkProgram(self.program);
GLint linkStatus;// 鏈接的狀態(tài)
glGetProgramiv(self.program, GL_LINK_STATUS, &linkStatus);
if (linkStatus == GL_FALSE) {
GLchar errorMessage[512];
glGetProgramInfoLog(self.program, sizeof(errorMessage), 0, &errorMessage[0]);
NSString *errorString = [NSString stringWithUTF8String:errorMessage];
NSLog(@"Program Link Error:%@", errorString);
return;;
}
NSLog(@"Program Link Success!");
6.4 使用program
glUseProgram(self.program);
6.5 處理頂點(diǎn)數(shù)據(jù)
GLfloat attrArr[] =
{
-0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -1.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -1.0f, 0.0f, 1.0f,
0.5f, 0.5f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -1.0f, 0.0f, 1.0f,
0.5f, -0.5f, -1.0f, 1.0f, 0.0f,
};
GLuint vBuffer;
glGenBuffers(1, &vBuffer);
//將attrBuffer綁定到GL_ARRAY_BUFFER標(biāo)識(shí)符上
glBindBuffer(GL_ARRAY_BUFFER, vBuffer);
//把頂點(diǎn)數(shù)據(jù)從CPU內(nèi)存復(fù)制到GPU上
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
6.6 設(shè)置讀取方式
//頂點(diǎn)數(shù)據(jù)
GLuint position = glGetAttribLocation(self.program, "position");
glEnableVertexAttribArray(position);//打開頂點(diǎn)數(shù)據(jù)讀取通道
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
//紋理數(shù)據(jù)
GLuint textureCoordinate = glGetAttribLocation(self.program, "textureCoordinate");
glEnableVertexAttribArray(textureCoordinate);//打開紋理數(shù)據(jù)讀取通道
glVertexAttribPointer(textureCoordinate, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL + 3);
-
glGetAttribLocation(self.program, "position");
:
從program
讀取頂點(diǎn)數(shù)據(jù)position
,注意這里的"position"一定要跟shaderv.vsh
文件中的position
保持一致宫补。 -
glEnableVertexAttribArray(position);
:
打開頂點(diǎn)數(shù)據(jù)讀取通道檬姥,默認(rèn)是關(guān)閉的。 -
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
設(shè)置讀取方式:
index
:頂點(diǎn)數(shù)據(jù)的索引position
size
:每個(gè)頂點(diǎn)屬性的組件數(shù)量粉怕,1健民、2、3或者4贫贝。默認(rèn)初始值是4秉犹。我們這里是3
。
type
:數(shù)據(jù)中的每個(gè)組件的類型稚晚,常用的有GL_FLOAT,GL_BYTE,GL_SHORT
凤优。默認(rèn)初始值為GL_FLOAT
normalized
:固定點(diǎn)數(shù)據(jù)值是否應(yīng)該歸一化,或者直接轉(zhuǎn)換為固定值蜈彼。(GL_FALSE
)
stride
:連續(xù)頂點(diǎn)屬性之間的偏移量筑辨,默認(rèn)為0。我們這里一組數(shù)據(jù)有5個(gè)幸逆,所以需要乘以5棍辕。
NULL
:指定一個(gè)指針暮现,指向數(shù)組中的第一個(gè)頂點(diǎn)屬性的第一個(gè)組件。默認(rèn)為0楚昭。
6.7 加載紋理圖片
- (GLuint)setupTexture:(NSString *)imageName {
CGImageRef imageRef = [UIImage imageNamed:imageName].CGImage;
if (!imageRef) {
NSLog(@"failed image!");
exit(1);
}
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
//獲取圖片字節(jié)數(shù)
GLubyte *imageData = (GLubyte *)calloc(width * height * 4, sizeof(GLubyte));
//創(chuàng)建上下文
CGContextRef context = CGBitmapContextCreate(imageData, width, height, 8, width * 4, CGImageGetColorSpace(imageRef), kCGImageAlphaPremultipliedLast);
CGRect rect = CGRectMake(0, 0, width, height);
//繪制
CGContextDrawImage(context, rect, imageRef);
CGContextRelease(context);
glBindBuffer(GL_TEXTURE_2D, 0);
//設(shè)置紋理屬性
/*
參數(shù)1:紋理維度
參數(shù)2:線性過濾栖袋、為s,t坐標(biāo)設(shè)置模式
參數(shù)3:wrapMode,環(huán)繞模式
*/
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
float fw = width, fh = height;
//載入紋理2D數(shù)據(jù)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
free(imageData);
return 0;
}
-
calloc(width * height * 4, sizeof(GLubyte));
:
獲取圖片字節(jié)數(shù) 寬高4,因?yàn)槲覀冇玫念伾?RGBA 4分量抚太。 -
CGBitmapContextCreate();
創(chuàng)建上下文:
參數(shù)1:data,指向要渲染的繪制圖像的內(nèi)存地址
參數(shù)2:width,bitmap的寬度塘幅,單位為像素
參數(shù)3:height,bitmap的高度,單位為像素
參數(shù)4:bitPerComponent,內(nèi)存中像素的每個(gè)組件的位數(shù)尿贫,比如32位RGBA电媳,就設(shè)置為8
參數(shù)5:bytesPerRow,bitmap的每一行的內(nèi)存所占的比特?cái)?shù)
參數(shù)6:colorSpace,bitmap上使用的顏色空間
參數(shù)7:kCGImageAlphaPremultipliedLast:RGBA -
CGContextDrawImage()
繪制:
使用的是Core Graphics
框架,坐標(biāo)系與UIKit
不一樣庆亡。UIKit
框架的原點(diǎn)在屏幕的左上角匾乓,Core Graphics框架的原點(diǎn)在屏幕的左下角。 -
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
載入紋理2D數(shù)據(jù):
參數(shù)1:紋理模式又谋,GL_TEXTURE_1D拼缝、GL_TEXTURE_2D、GL_TEXTURE_3D
參數(shù)2:加載的層次彰亥,一般設(shè)置為0
參數(shù)3:紋理的顏色值GL_RGBA
參數(shù)4:寬
參數(shù)5:高
參數(shù)6:border咧七,邊界寬度
參數(shù)7:format
參數(shù)8:type
參數(shù)9:紋理數(shù)據(jù)
6.8 設(shè)置紋理采樣器 sampler2D
GLint colorMap = glGetUniformLocation(self.program, "colorMap");
glUniform1i(colorMap, 0);
"colorMap"需要跟shaderf.fsh
文件中保持一致。
6.9 繪制
glDrawArrays(GL_TRIANGLES, 0, 6);
6.10 從渲染緩存區(qū)顯示到屏幕上
[self.currentContext presentRenderbuffer:GL_RENDERBUFFER];
最后我們看下繪制的效果:
我們發(fā)現(xiàn)繪制的效果是倒過來的任斋,如何讓這張圖正過來继阻,相信大家都有自己的想法,這篇文章就不做介紹了仁卷。