OpenGL ES 纹理加载图片的几种翻转方式
1、补充前面的渲染
1.1、着色器渲染过程
在渲染过程中,必须存储2中着色器,分别是顶点着色器、片元着色器。顶点着色器是第一个着色器、片元着色器是最后一个。顶点着色器中处理顶点、片元着色器处理像素点颜色。
渲染流程图
- 创建顶点
- 通过顶点着色器渲染
- 通过指定方式连接顶点,组成几何图形
- 光栅化:确定像素点在屏幕上绘制的位置,然后这些片段由片元着色器处理(输入给片元着色器)
- 光栅化阶段生成每个片元执行这个着色器
- 最终呈现出图形
1.2、渲染方式
屏幕渲染式分为两种
1、On-Screen Rendering(当前屏幕渲染)
也就是GPU的渲染操作是在当期那用于显示的屏幕缓冲区中进行的
2、off-Screen Rendering(离屏渲染)
指的是GPU在当前屏幕渲染缓冲区外开辟一个新的内存进行渲染操作
一般情况下,OpenGL ES会将应用提供到渲染服务的动画直接渲染显示(使用基本的渲染流程)。但是对于一些复杂的图像动画的渲染,并不能够直接渲染叠加显示出来。而是需要根据Command Buffer 分通道进行渲染再组合。这个组合过程中,就有些渲染通道是不会直接显示出来的。标记此次渲染需要更多的渲染通道和合并步骤,而这些没有直接渲染显示在屏幕上的通道就是离屏渲染通道。
2、FrameBuffer与RenderBuffer
为什么要用FrameBuffer 和 RenderBuffer?它们是什么关系?
- Color Attachment:存储的是纹理图片颜色值,实质上纹理图片颜色值属于颜色附着点的一种
- Depth Attachment:指向的是深度缓冲区和颜色缓冲区
- Stencil Attachment:指向的是模版缓冲区
- RenderBuffer Objects :渲染缓冲区对象,无论是纹理、图片、颜色、深度缓冲区、模版缓冲区都存在这个对象
- FrameBuffer 上的附着点其实相当于内存地址,它并没有存储实质的内容,只是三个附着点或三个内存地址在FrameBuffer Objects例如color Attachment ,它仅仅是附着在FrameBuffer身上
一个RenderBuffer 对象是通过对应分配的一个2D图像缓冲区。RenderBuffer 能够被用来分配和存储颜色、深度或者模板值。也能够在一个FrameBuffer被用作颜色、深度、模板的附件。一个renderBuffer 是一个类似于屏幕窗口系统提供可绘制的表面。比如pBuffer ,一个RenderBuffer,然后它并不能直接的使用像一个GL纹理。
一个FrameBuffer 对象(通常被称为一个FBO)。是一个收集颜色、深度和模板缓冲区的附着点。描述属性的状态,例如颜色、深度和模板缓冲区的大小和格式,都关联到FBO(Frame Buffer Object)。并且纹理的名字和RenderBuffer 对象也都是关联了FBO。各种各样的2D 图形能够被附着FrameBuffer 对象的颜色附着点。他们包含了RenderBuffer 对象存储的颜色值、一个2D纹理或立方体贴图包含了当时的深度值可以附加到一个FBO 的深度附着点中去。唯一的二维图像,能够附着在FBO 的模板附着点,是一个RenderBuffer 对象存储模板值。
3、纹理加载图片,图片翻转
加载出来的图片问什么是反的?
设备空间转用户空间
解决图片倒置问题
1、创建原始画布spriteContext
//创建上下文
/*
参数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);
2、平移画布
CGContextTranslateCTM(spriteContext,rect.origin.x, rect.origin.y);
3、平移画布
CGContextTranslateCTM(spriteContext, 0, rect.size.height);
4、翻转画布
CGContextScaleCTM(spriteContext, 1.0, -1.0)
5、平移画布
CGContextTranslateCTM(spriteContext, -rect.origin.x, -rect.origin.y);
6、倒置变换
CGContextDrawImage(spriteContext, rect, spriteImage);
上例中
片元着色器调用次数比较多
顶点着色器调用次数与顶点数量相关
片元着色器调用与像素点多少相关
4、OpenGL 2D纹理单元
4.1、片元着色器
varying vec2 TexCoord;
uniform sampler2D ourTexture;
void main()
{
gl_FragColor = texture(ourTexture, TexCoord);
}
TexCoord
纹理坐标,通过顶点着色器传递ourTexture
,纹理采样器
4.2、片元着色器,是如何访问纹理对象
首先,思考我们是如何将纹理对象传递给片元着色器? GLSL 中提供一个共纹理对象使用的内建数据类型,叫做采样器(sampler).
例如,sampler1D
,sampler2D
,sampler3D
表示不同维度的纹理类型.
那么我们在片元着色器是如何获取一个纹理的? 我们简单声明一个纹理对象. uniform sampler2D
,将一个纹理添加片元着色器中.
//声明一个纹理对象
uniform sampler2D ourTexture;
4.3、如何获取纹理对应像素点的颜色值
我们可以使用GLSL
内建的texture
函数来采样纹理的颜色值.
gl_FragColor = texture(ourTexture, TexCoord);
//参数1: 纹理采样器对象
//参数2: 纹理坐标
4.4、纹理单元
你可能会奇怪为什么sampler2D
变量是个uniform
,我们却不用glUniform
给它赋值.
使用glUniform1i
,我们可以给纹理采样器分配一个位置值,这样的话我们能够在一个片段着色器中设置多个纹理。
一个纹理的位置值通常称为一个纹理单元(Texture Unit)
。一个纹理的默认纹理单元是0,它是默认的激活纹理单元.
纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。
通过把纹理单元赋值给采样器,我们可以一次绑定多个纹理,只要我们首先激活对应的纹理单元。就像glBindTexture
一样,我们可以使用glActiveTexture
激活纹理单元,传入我们需要使用的纹理单元:
//在绑定纹理之前先激活纹理单元
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
激活纹理单元之后,接下来的glBindTexture
函数调用会绑定这个纹理到当前激活的纹理单元,纹理单元GL_TEXTURE0
默认总是被激活.
OpenGL
至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0
到GL_TEXTRUE15
。它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8
的方式获得GL_TEXTURE8
,这在当我们需要循环一些纹理单元的时候会很有用。
varying vec2 TexCoord;
uniform sampler2D ourTexture1;
uniform sampler2D ourTexture2;
void main()
{
gl_FragColor = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, TexCoord), 0.2);
}
最终输出颜色是两个纹理的结合。
GLSL
内建的mix
函数需要接受两个值作为参数,并对它们根据第三个参数进行线性插值。
如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。输入0.2则会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色。
为了使用第二个纹理(以及第一个),我们必须改变一点渲染流程,先绑定两个纹理到对应的纹理单元,然后定义哪个uniform
采样器对应哪个纹理单元:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1);
注意,我们使用glUniform1i
设置uniform
采样器的位置值,或者说纹理单元。通过glUniform1i
的设置,我们保证每个uniform
采样器对应着正确的纹理单元
5、纹理翻转解决策略
5.1、关于纹理翻转
OpenGL
要求y
轴0.0
坐标是在图片的底部的,但是图片的y
轴0.0
坐标通常在顶部
- 我们可以改变顶点数据的纹理坐标,翻转
y
值(用1
减去y
坐标)。 - 我们可以编辑顶点着色器来自动翻转y坐标,替换
TexCoord
的值为TexCoord = vec2(texCoord.x, 1.0f - texCoord.y);
。
iOS纹理翻转解决策略
第1种: 旋转矩阵翻转图形,不翻转纹理
- 让图形顶点坐标旋转180°. 而纹理保持原状.
GLuint rotate = glGetUniformLocation(self.myPrograme, "rotateMatrix");
float radians = 180 * 3.14159f / 180.0f;
float s = sin(radians);
float c = cos(radians);
GLfloat zRotation[16] = {
c, -s, 0, 0,
s, c, 0, 0,
0, 0, 1.0, 0,
0.0, 0, 0, 1.0
};
glUniformMatrix4fv(rotate, 1, GL_FALSE, (GLfloat *)&zRotation[0]);
第2种: 解压图片时,将图片源文件翻转
CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
size_t width = CGImageGetWidth(spriteImage);
size_t height = CGImageGetHeight(spriteImage);
GLubyte * spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
CGRect rect = CGRectMake(0, 0, width, height);
CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);
CGContextTranslateCTM(spriteContext, rect.origin.x, rect.origin.y);
CGContextTranslateCTM(spriteContext, 0, rect.size.height);
CGContextScaleCTM(spriteContext, 1.0, -1.0);
CGContextTranslateCTM(spriteContext, -rect.origin.x, -rect.origin.y);
CGContextDrawImage(spriteContext, rect, spriteImage);
CGContextRelease(spriteContext);
glBindTexture(GL_TEXTURE_2D, 0);
第3种: 修改片元着色器,纹理坐标
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;
void main()
{
gl_FragColor = texture2D(colorMap, vec2(varyTextCoord.x,1.0-varyTextCoord.y));
}
第4种: 修改顶点着色器,纹理坐标
attribute vec4 position;
attribute vec2 textCoordinate;
varying lowp vec2 varyTextCoord;
void main()
{
varyTextCoord = vec2(textCoordinate.x,1.0-textCoordinate.y);
gl_Position = position;
}
第5种:直接从源纹理坐标数据修改
GLfloat attrArr[] =
{
0.5f, -0.5f, 0.0f, 1.0f, 1.0f, //右下
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f, // 左上
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // 左下
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, // 右上
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f, // 左上
0.5f, -0.5f, 0.0f, 1.0f, 1.0f, // 右下
};
*/
6、翻转代码实现
Demo地址 -> OpenGL_ESDemos -> 003--test
#import <OpenGLES/ES2/gl.h>
#import "LJLView1.h"
@interface LJLView1()
//在iOS上和tvOS上绘制OpenGL ES内容的图层,继承与CALayer
@property (nonatomic, strong) CAEAGLLayer * myEagLayer;
@property (nonatomic, strong) EAGLContext *myContext;
@property (nonatomic, assign) GLuint myColorRenderBuffer;
@property (nonatomic, assign) GLuint myColorFrameBuffer;
@property (nonatomic, assign) GLuint myPrograme;
@end
@implementation LJLView1
- (void)layoutSubviews
{
// 1、设置图层
[self setupLayer];
// 2、设置图形上下文
[self setupContext];
// 3、清空缓冲区
[self deleteRenderAndFrameBuffer];
// 4、设置RenderBuffer
[self setupRenderBuffer];
// 5、设置FrameBuffer
[self setupFrameBuffer];
// 6、开始绘制
[self renderLayer];
}
用CAEAGLLayer,所需需要从写,不重写的话是 UIKit 的 CALayer
+ (Class)layerClass
{
return [CAEAGLLayer class];
}
1、创建图层,这里需要用的是CAEAGLLayer,也就是上面重写的Layer
// 1、设置图层
- (void)setupLayer
{
// 1、创建特殊图层
// 重写layerClass,将LJLView 返回的图层从CALayer 替换成 CAEAGLLayer
self.myEagLayer = (CAEAGLLayer *)self.layer;
// 2、设置scale
[self setContentScaleFactor:[[UIScreen mainScreen] scale]];
// 设置描述属性,这里设置不维持渲染内容以及颜色格式为RGBA8
/*
kEAGLDrawablePropertyRetainedBacking 表示绘图表面显示后,是否保留其内容
kEAGLDrawablePropertyColorFormat 可绘制表面的内容颜色缓存区格式,这个key对应的值是一个NSString 对应的值是一个NSString 指定特点颜色缓冲区对象。默认是 kEAGLColorFormatRGBA8;
kEAGLColorFormatRGBA8 32位RGBA的颜色,4*8=32位
kEAGLColorFormatRGB565 16位的RGB的颜色
kEAGLColorFormatSRGBA8 sRGB代表了标准的红、绿、蓝,即CRT显示器、LCD显示器、投影机、打印机以及其他设备中色彩再现所使用的三个基本色素。sRGB的色彩空间基于独立的色彩坐标,可以使色彩在不同的设备使用传输中对应域同一个色彩坐标体系,而不受这些设备各自具有不同色彩坐标的影响。
*/
self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@false,kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat, nil];
}
2、设置图形上下文
// 2、设置图形上下文
- (void)setupContext
{
// 1、指定OpenGL ES 渲染API版本,我们使用2.0
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
// 2、创建图形上下文
EAGLContext * context = [[EAGLContext alloc] initWithAPI:api];
// 3、判断是否创建成功
if (!context) {
NSLog(@"Create context failed!");
return;
}
// 4、设置图形上下文
if (![EAGLContext setCurrentContext:context]) {
NSLog(@"setCuttentContex failed!");
return;
}
// 5、将局部context,变成全局的
self.myContext = context;
}
3、清空缓冲区
// 3、清空缓冲区
- (void)deleteRenderAndFrameBuffer
{
/*
buffer 分为frame buffer 和 render buffer 2个大类
其中frame buffer 相当于 render buffer 的管理者
frame buffer object 即成为FBO。
render buffer 则可以分为3类。colorBuffer、depthBuffer、stencilBuffer
*/
glDeleteBuffers(1, &_myColorRenderBuffer);
self.myColorRenderBuffer = 0;
glDeleteBuffers(1, &_myColorFrameBuffer);
self.myColorFrameBuffer = 0;
}
4、设置RenderBuffer
// 4、设置RenderBuffer
- (void)setupRenderBuffer
{
// 1、定义一个缓冲区ID
GLuint buffer;
// 2、申请一个缓冲区标志
glGenRenderbuffers(1, &buffer);
// 3、
self.myColorRenderBuffer = buffer;
// 4、将标识符绑定到GL_RENDERBUFFER
glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
// 5、将可绘制对象drawable objec’s CAEALLayer 的存储绑定到OpenGL ES RenderBuffer 对象
[self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
}
5、设置FrameBuffer
// 5、设置FrameBuffer
- (void)setupFrameBuffer
{
// 1、定义一个缓存区ID
GLuint buffer;
// 2、申请一个缓存区标志
glGenRenderbuffers(1, &buffer);
// 3、
self.myColorFrameBuffer = buffer;
// 4、
glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
/*
生成帧缓冲区之后,则需要将RenderBuffer 跟 FrameBuffer 进行绑定
调用 glFramebufferRenderbuffer 函数进行绑定到对应的附着点上,后面的绘制才能起作用
*/
// 5、将渲染缓冲区 myColorRenderBuffer 通过glFramebufferRenderbuffer 函数绑定到GL_COLOR_ATTACHMENT0上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);
}
6、开始绘制
第1种翻转: 旋转矩阵翻转图形,不翻转纹理
[self rotateTextureImage];
第2种翻转: 解压图片时,将图片源文件翻转
CGContextTranslateCTM(spriteContext, rect.origin.x, rect.origin.y);
CGContextTranslateCTM(spriteContext, 0, rect.size.height);
CGContextScaleCTM(spriteContext, 1.0, -1.0);
CGContextTranslateCTM(spriteContext, -rect.origin.x, -rect.origin.y);
CGContextDrawImage(spriteContext, rect, spriteImage);
第3种: 修改片元着色器,纹理坐标
第4种: 修改顶点着色器,纹理坐标
第5种:直接从源纹理坐标数据修改
// 6、开始绘制
- (void)renderLayer
{
// 设置清屏颜色
glClearColor(0.3f, 0.45f, 0.5f, 1.0f);
// 清除屏幕
glClear(GL_COLOR_BUFFER_BIT);
// 1、设置视口大小
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、读取顶点着色程序、片元着色程序
NSString * vertFilePath = @"shaderv";
NSString * fragFilePath = @"shaderf";
if (self.processingType == ProcessingType_two) {
vertFilePath = @"shaderv2";
fragFilePath = @"shaderf2";
}else if (self.processingType == ProcessingType_three){
vertFilePath = @"shaderv3";
fragFilePath = @"shaderf3";
}else if (self.processingType == ProcessingType_four){
vertFilePath = @"shaderv4";
fragFilePath = @"shaderf4";
}else if (self.processingType == ProcessingType_five){
vertFilePath = @"shaderv5";
fragFilePath = @"shaderf5";
}
NSString * vertFile = [[NSBundle mainBundle] pathForResource:vertFilePath ofType:@"vsh"];
NSString * fragFile = [[NSBundle mainBundle] pathForResource:fragFilePath ofType:@"fsh"];
NSLog(@"\nvertFile:%@\nfragFile:%@\n",vertFile,fragFile);
// 3、加载shader
self.myPrograme = [self loadShaders:vertFile Withfrag:fragFile];
// 4、连接
glLinkProgram(self.myPrograme);
GLint linkStatus;
// 获取连接状态
glGetProgramiv(self.myPrograme, GL_LINK_STATUS, &linkStatus);
if (linkStatus == GL_FALSE) {
GLchar message[512];
glGetProgramInfoLog(self.myPrograme, sizeof(message), 0, &message[0]);
NSString * messageString = [NSString stringWithUTF8String:message];
NSLog(@"Program Link Error:%@",messageString);
return;
}
NSLog(@"Program Link Success!");
// 5、使用program
glUseProgram(self.myPrograme);
// 6、设置顶点、纹理坐标
// 前3个是顶点坐标,后2个是纹理坐标
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,
};
GLfloat attrArr5[] =
{
0.5f, -0.5f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -1.0f, 0.0f, 1.0f,
0.5f, 0.5f, -1.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -1.0f, 1.0f, 1.0f,
};
// 7、处理顶点数据
// (1)顶点缓冲区
GLuint attrBuffer;
// (2)申请一个缓冲区标识符
glGenBuffers(1, &attrBuffer);
// (3)将attreBuffer 绑定到GL_ARRAY_BUFFER 标识符上
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
// (4)把顶点数据从CPU内存复制到GPU上
if (self.processingType == ProcessingType_five) {
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr5), attrArr5, GL_DYNAMIC_DRAW);
}else{
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
}
// 8、将顶点数据通过myPrograme中的传递到顶点着色程序的position
// (1)、glGetAttribLocation,采用获取vertext attribute的入口的
// (2)、告诉OpenGL ES,通过glEnableVertextAttribArray,
// (3)、最后数据是通过glVertexAttribPointer 传递过去的
// 1)、主语:第二个参数字符串必须和shaderv.vsh 中的输入变量:position 保存一致
GLuint position = glGetAttribLocation(self.myPrograme, "position");
// 2)、设置合适的格式从buffer 里面读取数据
glEnableVertexAttribArray(position);
// 3)、设置读取方式
// 参数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
//GLfloat 一定要写对,我就是写成了CGfloat 。。。
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
// 9、处理纹理数据
// (1)glGetAttribLocation,用来获取vertex attribute 的入口的。
// 注意:第二个参数字符串必须和shaderv.vsh 中的输入变量 textCoordinate 保存一致
GLuint textCoor = glGetAttribLocation(self.myPrograme, "textCoordinate");
// (2)、设置合适的格式从buffer 里面读取数据
glEnableVertexAttribArray(textCoor);
// (3)、设置读取方式
// 同上
glVertexAttribPointer(textCoor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL + 3);
// 10、加载纹理
// (图片名称)
[self setupTexture:@"testImage"];
// 11、设置纹理采样器 sampler2D
glUniform1i(glGetUniformLocation(self.myPrograme, "colorMap"), 0);
if (self.processingType == ProcessingType_one) {
// 解决纹理倒置(方法1);
[self rotateTextureImage];
}
// 12、绘图
glDrawArrays(GL_TRIANGLES, 0, 6);
// 13、从渲染缓冲区显示到屏幕上
[self.myContext presentRenderbuffer:GL_RENDERBUFFER];
}
绘制的时候一定要注意,调用着色器程序的参数名一定要写对,否者会不显示。
还有就是设置读取方式的时候
// 3)、设置读取方式
// 参数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
//GLfloat 一定要写对,我就是写成了CGfloat 。。。
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
这里是 GLfloat 不要写错了。我就是写成了CGFloat,结果出现的效果如下。一般情况下,如果图片不显示很大可能是着色器程序调用的时候参数写错了,如果出现下面的情况,那就是读取方式的问题。
方案一的图片翻转
#pragma mark -
//解决图片倒置的问题,这里写错图片会不显示
-(void)rotateTextureImage
{
// 注意,想要获取shader 里面的变量,这里记得要在glLinkProgram 后面。 重点也是坑点顺序不能错
GLuint rotate = glGetUniformLocation(self.myPrograme, "rotateMatrix");
// 2、获取旋转的弧度
float radians = 180 * 3.14159f / 180.0f;
// 3、求得弧度对应的 sin / cos 值
float s = sin(radians);
float c = cos(radians);
// 4、因为在3D 课程中用的是横向量,在OpenGL ES 用的是列向量
// 参考Z轴旋转矩阵
GLfloat zRotation[16] = {
c,-s,0,0,
s,c,0,0,
0,0,1,0,
0,0,0,1
};
// 5、设置旋转矩阵
/*
glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value)
loaction: 对于shader 中 ID
count : 个数
transpose : 转量
value : 指针
*/
glUniformMatrix4fv(rotate, 1, GL_FALSE, zRotation);
}
//从图片中加载纹理
- (GLuint)setupTexture:(NSString *)fileName
{
// 1、将 UIImage 转换为 CGImageRef
CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
// 判断图片是否获取成功
if (!spriteImage) {
NSLog(@"Failed to load image %@",fileName);
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 上 --> 将图片绘制出来
/*
CGContextDrawImage 使用的是Core Graphics 框架,坐标系与UIKit 不一样。UIKit 框架的原点在屏幕的左上角,Core Graphics 框架的原点在屏幕的左下角
CGContextDrawImage
参数1:绘制上下文
参数2:rect 坐标
参数3:绘制的图片
*/
CGRect rect = CGRectMake(0, 0, width, height);
// 6、使用默认方式绘制
CGContextDrawImage(spriteContext, rect, spriteImage);
if (self.processingType == ProcessingType_two) {
CGContextTranslateCTM(spriteContext, rect.origin.x, rect.origin.y);
CGContextTranslateCTM(spriteContext, 0, rect.size.height);
CGContextScaleCTM(spriteContext, 1.0, -1.0);
CGContextTranslateCTM(spriteContext, -rect.origin.x, -rect.origin.y);
CGContextDrawImage(spriteContext, rect, spriteImage);
}
// 7。画图默认方式绘制上下文
CGContextRelease(spriteContext);
// 8、绑定纹理到默认的纹理ID
glBindTexture(GL_TEXTURE_2D, 0);
// 9、设置纹理属性
/*
参数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;
// 10、载入纹理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:纹理数据
*/
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
// 11、释放spriteData
free(spriteData);
return 0;
}
#pragma mark - shader
//加载shader
- (GLuint)loadShaders:(NSString *)vert Withfrag:(NSString *)frag
{
// 1、定义2个临时着色器对象
GLuint verShader, fragShader;
// 创建program
GLint program = glCreateProgram();
// 2、编译顶点着色程序、片元着色程序
// 参数1:编译完存储的底层地址
// 参数2:编译的类型,GL_VERTEX_SHADER(顶点)、GL_FRAGMENT_SHADER(片元)
// 参数3:文件路径
[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);
// 4、释放不需要的shader
glDeleteShader(verShader);
glDeleteShader(fragShader);
return program;
}
-(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(根据type类型)
*shader = glCreateShader(type);
// 3、将着色器源码附加到着色器对象上
// 参数1:shader,要编译的着色器对象,*shader
// 参数2:numOfString,传递的源码字符串数量 1个
// 参数3:strings,着色器程序的源码(真正的着色器程序源码)
// 参数4:lenOfStrings,长度,具有每个字符串长度的数组,或NULL,这意味着字符串是NULL终止的
glShaderSource(*shader, 1, &source, NULL);
// 4、把着色器源代码编译成目标代码
glCompileShader(*shader);
}
@end
具体的着色器程序代码就不再提出来了,可以去下载demo查阅
着色器程序的文件创建,xcode是没有对应文件的。通过如下方式创建