1. 流程图步骤
效果图
注意:直接加载图片是倒置的,由于纹理坐标系原点在左下角,屏幕坐标系的原点在左上角
[图片上传失败...(image-2510ff-1596589477801)]
流程
2. 步骤详解
1.创建图层 [self setupLayer];
- 1)创建特殊图层 重写layerClass,将HTView返回的图层
从CALayer替换成CAEAGLLayer
self.myEagLayer = (CAEAGLLayer *)self.layer;
+(Class)layerClass
{
return [CAEAGLLayer class];
}
- 2)设置scale
[self setContentScaleFactor:[[UIScreen mainScreen]scale]];
- 3)设置描述属性
self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@false,kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,nil];
2.创建上下文 [self setupContext];
-
- 指定OpenGL ES 渲染API版本,我们使用2.0
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
-
- 创建图形上下文
EAGLContext *context = [[EAGLContext alloc]initWithAPI:api];
-
- 判断是否创建成功
if (!context) {
NSLog(@"Create context failed!");
return;
}
-
- 设置当前图形上下文
if (![EAGLContext setCurrentContext:context]) {
NSLog(@"setCurrentContext failed!");
return;
}
-
- 将局部context,变成全局的
self.myContext = context;
3.清空缓存区 [self deleteRenderAndFrameBuffer];
glDeleteBuffers(1, &_myColorRenderBuffer);
self.myColorRenderBuffer = 0;
glDeleteBuffers(1, &_myColorFrameBuffer);
self.myColorFrameBuffer = 0;
4.设置RenderBuffer [self setupRenderBuffer];
//1.定义一个缓存区ID
GLuint buffer;
//2.申请一个缓存区标志);
glGenRenderbuffers(1, &buffer
//3.将渲染缓存区,变成全局的
self.myColorRenderBuffer = buffer;
//4.将标识符绑定到GL_RENDERBUFFER
glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
//5.将可绘制对象drawable object's CAEAGLLayer的存储绑定到OpenGL ES renderBuffer对象
[self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
5.设置FrameBuffer [self setupFrameBuffer];
//1) 定义一个缓存区ID
GLuint buffer;
//2) 申请一个缓存区标志
//glGenRenderbuffers(1, &buffer);
//glGenFramebuffers(1, &buffer);
glGenBuffers(1, &buffer);
//3) 将帧缓存区变成全局的
self.myColorFrameBuffer = buffer;
//4)绑定
glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
//5.将渲染缓存区myColorRenderBuffer
通过glFramebufferRenderbuffer函数绑定到
GL_COLOR_ATTACHMENT0上。
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);
6.开始绘制 [self renderLayer];
1.设置清屏颜色,清除缓冲区
//设置清屏颜色
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
//清除缓冲区
glClear(GL_COLOR_BUFFER_BIT);
2.设置视口大小
//1. 设置视口大小
GLfloat 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);
3.读顶点着色器程序和片元着色器程序
//2. 读顶点着色器程序和片元着色器程序
NSString *vertFile = [[NSBundle mainBundle]pathForResource:@"shaderv" ofType:@"vsh"];
NSString *fragFile = [[NSBundle mainBundle]pathForResource:@"shaderf" ofType:@"fsh"];
self.htProgram = [self loadShader:vertFile withFrag:fragFile];
加载shader
-
- 定义两个着色器对象
-
- 创建 shader;
-
- 编译顶点着色器程序和片元着色器程序
-
- 创建最终程序
-
- 释放不需要的shader
//加载shader
-(GLuint)loadShader:(NSString *)vert withFrag:(NSString *)frag
{
//1. 定义两个着色器对象
GLuint verShader,fragShader;
//创建 shader;
GLuint program = glCreateProgram();
//2. 编译顶点着色器程序和片元着色器程序
[self compileShader:&verShader type:GL_VERTEX_SHADER file:vert];
[self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
//3.创建最终程序
glAttachShader(program, verShader);
glAttachShader(program, fragShader);
//释放不需要的shader
glDeleteShader(verShader);
glDeleteShader(fragShader);
return program;
}
编译shader
- 1.读取文件路径
- 2.创建一个shader
- 3.将着色器源码附加到着色器对象上
- 4.把着色器源代码编译成目标代码
//编译shader
- (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];
//2. 创建一个shader
*shader = glCreateShader(type);
//3.将着色器源码附加到着色器对象上。
//参数1:shader,要编译的着色器对象 *shader
//参数2:numOfStrings,传递的源码字符串数量 1个
//参数3:strings,着色器程序的源码(真正的着色器程序源码)
//参数4:lenOfStrings,长度,具有每个字符串长度的数组,或NULL,这意味着字符串是NULL终止的
glShaderSource(*shader, 1, &source, NULL);
//4.把着色器源代码编译成目标代码
glCompileShader(*shader);
}
4.链接,获取链接状态: glGetProgramiv
glLinkProgram(self.htProgram);
GLint linkStatus;
glGetProgramiv(self.htProgram, GL_LINK_STATUS, &linkStatus);
if(linkStatus == GL_FALSE){
GLchar message[512];
glGetProgramInfoLog(self.htProgram, sizeof(message), 0, &message[0]);
NSString *messageString = [NSString stringWithUTF8String:message];
NSLog(@"Program Link Error:%@",messageString);
return;
}
5.使用program: glUseProgram
glUseProgram(self.htProgram);
6.设置顶点,纹理坐标 attrArr[]
GLfloat attrArr[] =
{
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, 0.0f, 0.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,
};
7.处理顶点数据
GLuint attrBuffer;
glGenBuffers(1, &attrBuffer);
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
8.将顶点数据通过myPrograme中的传递到顶点着色程序的position
- glGetAttribLocation,用来获取vertex attribute的入口的.
GLuint position = glGetAttribLocation(self.htProgram, "position");
- 设置合适的格式从buffer里面读取数据
glEnableVertexAttribArray(position);
- 最后数据是通过glVertexAttribPointer传递过去的。
//通过glVertexAttribPointer传递过去的。
//参数1:index,顶点数据的索引
//参数2:size,每个顶点属性的组件数量,1,2,3,或者4.默认初始值是4.
//参数3:type,数据中的每个组件的类型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默认初始值为GL_FLOAT
//参数4:normalized,固定点数据值是否应该归一化,或者直接转换为固定值。(GL_FALSE)
//参数5:stride,连续顶点属性之间的偏移量,默认为0;
//参数6:指定一个指针,指向数组中的第一个顶点属性的第一个组件。默认为0
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);
9.处理纹理数据
- glGetAttribLocation,用来获取vertex attribute的入口的.
//注意:第二参数字符串必须和shaderv.vsh中的输入变量:textCoordinate保持一致
GLuint textCoor = glGetAttribLocation(self.htProgram, "textCoordinate");
- 设置合适的格式从buffer里面读取数据
glEnableVertexAttribArray(textCoor);
- 设置读取方式
//参数1:index,顶点数据的索引
//参数2:size,每个顶点属性的组件数量,1,2,3,或者4.默认初始值是4.
//参数3:type,数据中的每个组件的类型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默认初始值为GL_FLOAT
//参数4:normalized,固定点数据值是否应该归一化,或者直接转换为固定值。(GL_FALSE)
//参数5:stride,连续顶点属性之间的偏移量,默认为0;
//参数6:指定一个指针,指向数组中的第一个顶点属性的第一个组件。默认为0
glVertexAttribPointer(textCoor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL + 3);
10.加载纹理
-
1)将UIImage 转换为CGImageRef
CGImageRef spriteImage = [UIImage imageNamed:file].CGImage;
2)判断图片是否获取成功
-
3)读取图片的大小,宽和高
size_t width = CGImageGetWidth(spriteImage);
size_t height = CGImageGetHeight(spriteImage);
4)获取图片字节数 宽高4(RGBA) 开辟存储空间
5)创建上下文
6)在CGContextRef上--> 将图片绘制出来 CGContextDrawImage
7)画图完毕就释放上下文
CGContextRelease(spriteContext);8)绑定纹理到默认的纹理ID glBindTexture
9)设置纹理属性 glTexParameteri
10)载入纹理2D数据 glTexImage2D
11)释放spriteData
//加载纹理
-(GLuint )setupTexture:(NSString *)file
{
//1.将UIImage 转换为CGImageRef
CGImageRef spriteImage = [UIImage imageNamed:file].CGImage;
//判断图片是否获取成功
if(!spriteImage){
NSLog(@"Failed to load image: %@",file);
exit(1);
}
//2、读取图片的大小,宽和高
size_t width = CGImageGetWidth(spriteImage);
size_t height = CGImageGetHeight(spriteImage);
//3.获取图片字节数 宽*高*4(RGBA) 开辟存储空间
GLubyte *spriteData = (GLubyte *)calloc(width*height*4,sizeof(GLubyte));
//4.创建上下文
/*
参数1:data,指向要渲染的绘制图像的内存地址
参数2:width,bitmap的宽度,单位为像素
参数3:height,bitmap的高度,单位为像素
参数4:bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8
参数5:bytesPerRow,bitmap的没一行的内存所占的比特数
参数6:colorSpace,bitmap上使用的颜色空间 kCGImageAlphaPremultipliedLast:RGBA
*/
CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
//5、在CGContextRef上--> 将图片绘制出来
CGRect rect = CGRectMake(0, 0, width, height);
CGContextDrawImage(spriteContext, rect, spriteImage);
//6. 画图完毕就释放上下文
CGContextRelease(spriteContext);
//7.绑定纹理到默认的纹理ID
glBindTexture(GL_TEXTURE_2D, 0);
//8.设置纹理属性
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);
//9.载入纹理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:纹理数据
*/
float fw = width, fh = height;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
//10. 释放spriteData
free(spriteData);
return 0;
}
11.设置纹理采样器 sampler2D
GLint textureLocation = glGetUniformLocation(self.htProgram, "colorMap");
glUniform1i(textureLocation, 0);
12.绘图
glDrawArrays(GL_TRIANGLES, 0, 6);
13.从渲染缓存区显示到屏幕上
[self.htContext presentRenderbuffer:GL_RENDERBUFFER];
7.常用GLSL API解析
8. 顶点着色器程序和片元着色器程序书写
注意:顶点着色器程序和片元着色器程序书写时不要加入中文注释,否则可能有意想不到的错误
- 变量存储限定符:varying、attribute、uniform
- varying 修饰符:当需要将顶点着色器的数据传递到片元着色器时,两个着色器中一模一样的纹理坐标变量就需要它来修饰
- attribute:数据只能从客户端中传递到顶点着色器,且只能在顶点着色器中使用,修饰的数据:顶点、纹理、颜色、法线等
- 纹理坐标,需要顶点着色器间接传递到片元着色器,需要在顶点与片元着色器中定义一个一模一样的纹理坐标,通过这个变量将纹理坐标数据间接传递到片元着色器,varying lowp vec2 varyTextCoord;
顶点着色器计算之后的顶点结果需要赋值给GLSL的内建变量gl_Position - uniform:从app代码传递到vertex、fragment中所用的变量
在vertex,fragment中一般将uniform当成常量
uniform可以传的数据:视图矩阵、投影矩阵、投影视图矩阵
片元着色器中最终颜色,即拿到纹理对应坐标下的纹素。纹素是纹理对应像素点的颜色值,需要通过内建函数texture2D(纹理,纹理坐标)计算,将最终返回的颜色值赋值给内建变量gl_FragColor
顶点着色器代码
attribute vec4 position;
attribute vec2 textCoordinate;
varying lowp vec2 varyTextCoord;
void main()
{
varyTextCoord = textCoordinate;
gl_Position = position;
}
片元着色器代码
[图片上传失败...(image-2b9-1596589477801)]
precsion highp float;
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;
void main(){
lowp vec4 temp = texture2D(colorMap, varyTextCoord);
gl_FragColor = temp;
}
片元着色器,是如何访问纹理对象
- 不同维度的纹理类型:sampler1D,sampler2D,sampler3D
- 声明一个纹理对象: uniform sampler2D,将一个纹理添加片元着色器中.
如何获取纹理对应像素点的颜色值
使用GLSL内建的texture函数来采样纹理的颜色值.
gl_FragColor = texture(ourTexture, TexCoord);
使用glUniform1i给纹理采样器分配一个位置值,可以在一个片段着色器中设置多个纹理
一个纹理的位置值通常称为一个纹理单元(Texture Unit)。一个纹理的默认纹理单元是0,它是默认的激活纹理单元.
//在绑定纹理之前先激活纹理单元
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0到GL_TEXTRUE15。它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8
多纹理混合:mix函数接受两个值作为参数,并对它们根据第三个参数进行线性插值
varying vec2 TexCoord;
uniform sampler2D ourTexture1;
uniform sampler2D ourTexture2;
void main()
{
gl_FragColor = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, TexCoord), 0.2);
}
mix函数:如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。输入0.2则会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色
在纹理传输中代码实现
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glUniform1i(glGetUniformLocation(program, "ourTexture1"), 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glUniform1i(glGetUniformLocation(program, "ourTexture2"), 1);