本篇文章主要讲解如何使用GLSL语言来进行纹理的绘制。
首先我们需要了解如何编写GLSL程序,经过之前的学习我们知道,我们能操作的只有 顶点着色器 和 片元着色器。所以我们需要创建两个空文件分别命名为shaderv.vsh
和 shaderf.fsh
:
当然文件名字看自己的习惯,可以修改。
shaderv.vsh
和 shaderf.fsh
分别表示 顶点着色器 和 片元着色器 的程序文件。
GLSL代码
先来看下 shaderv.vsh
文件的代码:
attribute vec4 position;
attribute vec2 textureCoordinate;
varying lowp vec2 varyTextCoord
void main()
{
varyTextCoord = textureCoordinate;
gl_Position = position;
}
position
:顶点坐标
textureCoordinate
:纹理坐标
varyTextCoord
:用来传递纹理坐标到片元着色器。
gl_Position
:内建变量,用来接收顶点坐标。
shaderf.fsh
文件的代码:
precision highp float;
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;
void main()
{
gl_FragColor = texture2D(colorMap, varyTextCoord);
}
precision highp float;
:设置默认精度。
varying lowp vec2 varyTextCoord;
:用来接收顶点着色器传过来的纹理坐标。需要跟shaderv.vsh
文件的名字保持一致,完全一样。
colorMap;
:颜色贴图(也就是纹理)
gl_FragColor
:内建变量
绘制流程
简单了解了GLSL
程序书写位置,下面我们介绍下绘制流程。
首先我们需要将view
的layer
替换为CAEAGLLayer
类型的layer
:
+ (Class)layerClass {
return [CAEAGLLayer class];
}
然后在layoutSubviews
方法里进行操作绘制:
- (void)layoutSubviews {
//1.设置图层
[self setupLayer];
//2.设置上下文
[self setupContext];
//3.清空缓存区
[self clearColorBuffer];
//4.设置RenderBuffer
[self setupRenderBuffer];
//5.设置FrameBuffer
[self setupFrameBuffer];
//6.渲染图层
[self renderLayer];
}
该段代码大概分为6步:1、设置图层 2、设置上下文 3、清空缓存区 4、设置RenderBuffer 5、设置FrameBuffer 6、渲染图层。这些都是自定义的方法。
1、设置图层
- (void)setupLayer {
self.eaglLayer = (CAEAGLLayer *)self.layer;
[self setContentScaleFactor:[[UIScreen mainScreen] scale]];
self.eaglLayer.drawableProperties = @{
kEAGLDrawablePropertyRetainedBacking:@(0),
kEAGLDrawablePropertyColorFormat:kEAGLColorFormatRGBA8
};
}
drawableProperties
绘制属性:
kEAGLDrawablePropertyRetainedBacking
表示绘图表面显示后,是否保留其内容, true
保留, false
不保留。
kEAGLDrawablePropertyColorFormat
可绘制表面的内部颜色缓存区格式,这个key对应的值是一个NSString指定特定颜色缓存区对象。默认是kEAGLColorFormatRGBA8
。
2、设置上下文
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (![EAGLContext setCurrentContext:context]) {
NSLog(@"context set failed");
return;
}
self.currentContext = context;
3、清空缓存区
glDeleteFramebuffers(1, &_colorFrameBuffer);
self.colorFrameBuffer = 0;
glDeleteRenderbuffers(0, &_colorRenderBuffer);
self.colorRenderBuffer = 0;
4、设置RenderBuffer
GLuint renderBufferID;
glGenRenderbuffers(1, &renderBufferID);
self.colorRenderBuffer = renderBufferID;
//绑定(将标识符绑定到GL_RENDERBUFFER)
glBindRenderbuffer(GL_RENDERBUFFER, renderBufferID);
//绑定layer到GL_RENDERBUFFER
[self.currentContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.eaglLayer];
5、设置FrameBuffer
GLuint frameBufferID;
glGenFramebuffers(1, &frameBufferID);
self.colorFrameBuffer = frameBufferID;
glBindFramebuffer(GL_FRAMEBUFFER, frameBufferID);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.colorRenderBuffer);
生成帧缓存区之后,则需要将renderbuffer
跟framebuffer
进行绑定,调用glFramebufferRenderbuffer
函数进行绑定到对应的附着点上,后面的绘制才能起作用。
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);
这段代码大家应该很熟悉了,都是些常规的操作。
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];
我们重点看下loadShaderWithVPath:fPath:
代码:
- (GLuint)loadShaderWithVPath:(NSString *)vPath fPath:(NSString *)fPath {
//创建program
GLuint program = glCreateProgram();
//编译顶点着色程序、片元着色器程序
GLuint vShader, fShader;
[self compileShader:&vShader type:GL_VERTEX_SHADER file:vPath];
[self compileShader:&fShader type:GL_FRAGMENT_SHADER file:fPath];
//将顶点着色程序、片元着色器程序附着在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);
//着色器源代码编译成目标代码
glCompileShader(*shader);
}
-
glShaderSource(*shader, 1, &source, NULL);
将着色器源码附加到着色器对象上: -
shader
:要编译的着色器对*shader
。 -
numOfStrings
:传递的源码字符串数量 1个 -
strings
:着色器程序的源码(真正的着色器程序源码&sourc
) -
lenOfStrings
:长度,具有每个字符串长度的数组,或NULL
,这意味着字符串是NULL
终止的.
6.3 链接
glLinkProgram(self.program);
GLint linkStatus;// 链接的状态
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 处理顶点数据
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标识符上
glBindBuffer(GL_ARRAY_BUFFER, vBuffer);
//把顶点数据从CPU内存复制到GPU上
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
6.6 设置读取方式
//顶点数据
GLuint position = glGetAttribLocation(self.program, "position");
glEnableVertexAttribArray(position);//打开顶点数据读取通道
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
//纹理数据
GLuint textureCoordinate = glGetAttribLocation(self.program, "textureCoordinate");
glEnableVertexAttribArray(textureCoordinate);//打开纹理数据读取通道
glVertexAttribPointer(textureCoordinate, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL + 3);
-
glGetAttribLocation(self.program, "position");
:
从program
读取顶点数据position
,注意这里的"position"一定要跟shaderv.vsh
文件中的position
保持一致。 -
glEnableVertexAttribArray(position);
:
打开顶点数据读取通道,默认是关闭的。 -
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
设置读取方式:
index
:顶点数据的索引position
size
:每个顶点属性的组件数量,1、2、3或者4。默认初始值是4。我们这里是3
。
type
:数据中的每个组件的类型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT
。默认初始值为GL_FLOAT
normalized
:固定点数据值是否应该归一化,或者直接转换为固定值。(GL_FALSE
)
stride
:连续顶点属性之间的偏移量,默认为0。我们这里一组数据有5个,所以需要乘以5。
NULL
:指定一个指针,指向数组中的第一个顶点属性的第一个组件。默认为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);
//获取图片字节数
GLubyte *imageData = (GLubyte *)calloc(width * height * 4, sizeof(GLubyte));
//创建上下文
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);
//设置纹理属性
/*
参数1:纹理维度
参数2:线性过滤、为s,t坐标设置模式
参数3:wrapMode,环绕模式
*/
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数据
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));
:
获取图片字节数 宽高4,因为我们用的颜色是 RGBA 4分量。 -
CGBitmapContextCreate();
创建上下文:
参数1:data,指向要渲染的绘制图像的内存地址
参数2:width,bitmap的宽度,单位为像素
参数3:height,bitmap的高度,单位为像素
参数4:bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8
参数5:bytesPerRow,bitmap的每一行的内存所占的比特数
参数6:colorSpace,bitmap上使用的颜色空间
参数7:kCGImageAlphaPremultipliedLast:RGBA -
CGContextDrawImage()
绘制:
使用的是Core Graphics
框架,坐标系与UIKit
不一样。UIKit
框架的原点在屏幕的左上角,Core Graphics框架的原点在屏幕的左下角。 -
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
载入纹理2D数据:
参数1:纹理模式,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
参数2:加载的层次,一般设置为0
参数3:纹理的颜色值GL_RGBA
参数4:宽
参数5:高
参数6:border,边界宽度
参数7:format
参数8:type
参数9:纹理数据
6.8 设置纹理采样器 sampler2D
GLint colorMap = glGetUniformLocation(self.program, "colorMap");
glUniform1i(colorMap, 0);
"colorMap"需要跟shaderf.fsh
文件中保持一致。
6.9 绘制
glDrawArrays(GL_TRIANGLES, 0, 6);
6.10 从渲染缓存区显示到屏幕上
[self.currentContext presentRenderbuffer:GL_RENDERBUFFER];
最后我们看下绘制的效果:
我们发现绘制的效果是倒过来的,如何让这张图正过来,相信大家都有自己的想法,这篇文章就不做介绍了。
网友评论