本文介绍通过编译链接自定义的Shader着色器(GLSL)绘制图片,并通过旋转矩阵将图片进行旋转操作。
GLView.m
#import "GLView.h"
#import <OpenGLES/ES2/gl.h>
@interface GLView()
@property (nonatomic,strong) EAGLContext *context;
@property (nonatomic,strong) CAEAGLLayer *eaglLayer;
@property (nonatomic,assign) GLuint program;//着色器程序
@property (nonatomic,assign) GLuint colorRenderBuffer;
@property (nonatomic,assign) GLuint colorFrameBuffer;
@end
@implementation GLView
+ (Class)layerClass {
return [CAEAGLLayer class];
}
- (void)layoutSubviews {
//创建CAEAGLLayer
[self setupLayer];
//创建EAGLContext
[self setupContext];
// //清除Buffers
// [self destoryBuffers];
//创建颜色缓冲区
[self setupRenderBuffer];
//创建帧缓冲区
[self setupFrameBuffer];
//render
[self render];
}
#pragma mark - Setup
//创建CAEAGLLayer
- (void)setupLayer {
self.eaglLayer = (CAEAGLLayer *)self.layer;
//设置放大倍数
[self setContentScaleFactor:[[UIScreen mainScreen] scale]];
//设置不透明(CALayer默认是透明的,必须将它设为不透明才能让其可见)
self.eaglLayer.opaque = YES;
//设置描绘属性(在这里设置不维持渲染内容,以及颜色格式为RGBA8)
self.eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
//不维持渲染内容,因此在下一次呈现时,应用程序必须完全重绘一次
[NSNumber numberWithBool:NO],kEAGLDrawablePropertyRetainedBacking,
//颜色格式为RGBA8
kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,
nil];
}
//创建EAGLContext
- (void)setupContext {
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];//在这里我们使用OpenGLES2.0版本
if (!context) {
NSLog(@"Failed to initialize OpenGLES 2.0 context");
exit(1);
}
//设置为当前上下文
if (![EAGLContext setCurrentContext:context]) {
NSLog(@"Failed to set current OpenGL context");
exit(1);
}
self.context = context;
}
//清除Buffers
- (void)destoryBuffers {
glDeleteBuffers(1, &_colorFrameBuffer);
self.colorFrameBuffer = 0;
glDeleteBuffers(1, &_colorRenderBuffer);
self.colorRenderBuffer = 0;
}
//创建颜色缓冲区
- (void)setupRenderBuffer {
GLuint buffer;
glGenRenderbuffers(1, &buffer);
self.colorRenderBuffer = buffer;
glBindRenderbuffer(GL_RENDERBUFFER, self.colorRenderBuffer);//绑定为当前的renderBuffer
//为颜色缓冲区分配存储空间
[self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.eaglLayer];
}
//创建帧缓冲区
- (void)setupFrameBuffer {
GLuint buffer;
glGenFramebuffers(1, &buffer);
self.colorFrameBuffer = buffer;
glBindFramebuffer(GL_FRAMEBUFFER, self.colorFrameBuffer);//绑定为当前的frameBuffer
//将self.colorRenderBuffer绑定到GL_COLOR_ATTACHMENT0这个装配点上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.colorRenderBuffer);
}
#pragma mark - Render
//render
- (void)render {
//清除颜色缓冲区
// glClearColor(0, 1, 0, 1);
// glClear(GL_COLOR_BUFFER_BIT);
//获取视图放大倍数,可以把scale设置为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);
//读取shader文件路径
NSString *vertFile = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"vsh"];
NSString *fragFile = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"fsh"];
//加载shader
self.program = [self loadShaders:vertFile frag:fragFile];
//链接着色器程序
glLinkProgram(self.program);
GLint linkSuccess;
glGetProgramiv(self.program, GL_LINK_STATUS, &linkSuccess);//获取链接情况
if (linkSuccess==GL_FALSE) {//连接错误
GLchar messages[256];
glGetProgramInfoLog(self.program, sizeof(messages), 0, &messages[0]);
NSString *messageString = [NSString stringWithUTF8String:messages];
NSLog(@"error,%@", messageString);
return;
}
else {
NSLog(@"link ok");
glUseProgram(self.program);//成功便使用,避免由于未使用导致的bug
}
//前三个是顶点坐标,后面两个是纹理坐标
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,//右下
//手机显示的纹理坐标(0,0)是左上角,(1,1)是右下角
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,//右下
};
GLuint attrBuffer;
glGenBuffers(1, &attrBuffer);//生成新缓存对象
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);//绑定缓存对象
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);//将顶点数据拷贝到缓存对象中
GLuint position = glGetAttribLocation(self.program, "position");
glVertexAttribPointer(position,
3,
GL_FLOAT,
GL_FALSE,
sizeof(GLfloat)*5,
NULL);
glEnableVertexAttribArray(position);//顶点数据缓存
GLuint textCoor = glGetAttribLocation(self.program, "textCoordinate");
glVertexAttribPointer(textCoor,
2,
GL_FLOAT,
GL_FALSE,
sizeof(GLfloat)*5,
(float *)NULL+3);
glEnableVertexAttribArray(textCoor);//纹理数据缓存
//加载纹理
[self setupTexture:@"for_test"];
/*
对于一个图形进行旋转变换,相当于对每个顶点乘以一个旋转变换矩阵
矩阵如下:
(x'y'z'1)=(x y z 1)[cosr sinr 0 0
-sinr cosr 0 0
0 0 1 0
0 0 0 1]
对于顶点的变换,我们可以放在OC代码里面来实现,把顶点变换完成后,把顶点输入到OpenGLES;
也可以在glsl代码实现,把顶点变换交给gpu来完成。这里我们采用的是后者。
*/
//获取shader里面的变量,这里记得要在glLinkProgram()后面
GLuint rotate = glGetUniformLocation(self.program, "rotateMatrix");
// float radians = 10*M_PI/180.0f;
float radians = M_PI/4;//逆时针45度
float s = sin(radians);
float c = cos(radians);
//z轴旋转矩阵
/*
这里的z轴旋转矩阵和上面给出来的旋转矩阵并不一致。
究其原因就是OpenGLES是列主序矩阵,对于一个一维数组表示的二维矩阵,会先填满每一列(a[0][0]、a[1][0]、a[2][0]、a[3][0])。
把矩阵赋值给glsl对应的变量,然后就可以在glsl里面计算出旋转后的矩阵。
*/
GLfloat zRotation[16] =
{
// c,s,0,0.2,
// -s,c,0,0,
c,-s,0,0,
s,c,0,0,
0,0,1,0,
0,0,0,1,
};
//设置旋转矩阵
glUniformMatrix4fv(rotate, 1, GL_FALSE, (GLfloat *)&zRotation[0]);
//绘制
glDrawArrays(GL_TRIANGLES, 0, 6);
//提交渲染
[self.context presentRenderbuffer:GL_RENDERBUFFER];
}
/**
* c语言编译流程:预编译、编译、汇编、链接
* glsl的编译过程主要有glCompileShader、glAttachShader、glLinkProgram三步;
* @param vert 顶点着色器
* @param frag 片元着色器
*
* @return 编译成功的shaders
*/
- (GLuint)loadShaders:(NSString *)vert frag:(NSString *)frag {
GLuint verShader;
GLuint fragShader;
GLuint program = glCreateProgram();
//编译
[self complieShader:&verShader type:GL_VERTEX_SHADER file:vert];
[self complieShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
glAttachShader(program, verShader);
glAttachShader(program, fragShader);
//释放不需要的shader
glDeleteShader(verShader);
glDeleteShader(fragShader);
return program;
}
- (void)complieShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file {
//读取字符串
NSString *content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
const GLchar *source = (GLchar *)[content UTF8String];
*shader = glCreateShader(type);
glShaderSource(*shader, 1, &source, NULL);
glCompileShader(*shader);
}
- (GLuint)setupTexture:(NSString *)fileName {
//1.获取图片的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);
GLubyte *spriteData = (GLubyte *)calloc(width*height*4, sizeof(GLubyte));//rgba共4个byte
//创建位图上下文
CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
//3.在CGContextRef上绘图
CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);
CGContextRelease(spriteContext);
//4.绑定纹理到默认的纹理ID(这里只有一张图片,故而相当于默认于片段着色器里面的colorMap,如果有多张图不可以这么做)
glBindTexture(GL_TEXTURE_2D, 0);
/*
图像从纹理图像空间映射到帧缓冲图像空间(映射需要重新构造纹理图像,这样就会造成应用到多边形上的图像失真),这时就可用glTexParmeteri()函数来确定如何把纹理像素映射成像素。
GL_TEXTURE_2D:操作2D纹理
GL_TEXTURE_MIN_FILTER:缩小过滤
GL_TEXTURE_MAG_FILTER:放大过滤
GL_LINEAR:线性过滤,使用距离当前渲染像素中心最近的4个纹素加权平均值
GL_TEXTURE_WRAP_S:S方向上的贴图模式
GL_TEXTURE_WRAP_T:T方向上的贴图模式
GL_CLAMP_TO_EDGE:将纹理坐标限制在0.0,1.0的范围之内,如果超出了会如何呢,不会错误,只是会边缘拉伸填充
*/
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);
//生成2D纹理
float fw = width;
float fh = height;
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA,
fw,
fh,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
spriteData);
glBindTexture(GL_TEXTURE_2D, 0);
free(spriteData);
return 0;
}
@end
Shader.vsh
/*
attribute:应用程序传给顶点着色器用的,attribute限定符标记的是一种全局变量,该变量在顶点着色器中是只读(read-only)的,该变量被用作从OpenGL应用程序向顶点着色器中传递参数,因此该限定符仅能用于顶点着色器。
uniform:一般是应用程序用于设定顶点着色器和片断着色器相关初始化值,uniform限定符标记的是一种全局变量,该变量对于一个图元(primitive)来说是不可更改的,它可以从OpenGL应用程序中接收传递来的参数。
varying:用于传递顶点着色器的值给片断着色器,它提供了从顶点着色器向片段着色器传递数据的方法,varying限定符可以在顶点着色器中定义变量,然后再传递给光栅化器,光栅化器对数据插值后,再将每个片段的值交给片段着色器。
lowp:精度(精度有 highp mediump lowp)
vec4:包含4个浮点数的矢量
vec2:包含2个浮点数的矢量
mat4:4维浮点型矩阵
*/
attribute vec4 position;//顶点
attribute vec2 textCoordinate;//纹理
uniform mat4 rotateMatrix;//mvp矩阵(模型矩阵(model)、观察矩阵(view)、投影矩阵(Projection))
varying lowp vec2 varyTextCoord;
void main()
{
varyTextCoord = textCoordinate;
vec4 vPos = position;//从顶点矩阵传入的顶点的原始数据
vPos = vPos*rotateMatrix;//旋转
gl_Position = vPos;//设置顶点位置
}
Shader.fsh
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;//sampler2D:访问一个二维纹理
void main()
{
gl_FragColor = texture2D(colorMap,varyTextCoord);//设置片段着色器中的颜色
}
ViewController.m
#import "ViewController.h"
#import "GLView.h"
@interface ViewController ()
@property (nonatomic,strong)GLView *v;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.v = (GLView *)self.view;
}
@end
网友评论