0012 -- GLSL语言自定义着色器加载图片
前言
接下来通过一个例子,研究OpenGL
是着色器是如何编译和链接提供使用.
自定义顶点着色器
attribute vec4 position;
attribute vec2 textCoordinate;
varying lowp vec2 varyTextCoord;
void main()
{
varyTextCoord = textCoordinate;
gl_Position = position;
}
gl_Position
:顶点着色器计算完之后的结果
textCoordinate
:纹理坐标
varyTextCoord
:等于attribute
传递进来的textCoordinate
,为了拿到纹理坐标的值
自定义片断着色器
precision highp float; //高精度
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;
void main()
{
gl_FragColor = texture2D(colorMap, varyTextCoord);
}
uniform
传递方式sampler2D
类型colorMap
变量名
varyTextCoord
:纹理坐标 和 顶点着色器对应,必须一样
colorMap
:纹理
varyTextCoord
:纹理坐标
texture2D
内建函数,返回值是颜色值
编译 链接 绑定
不采用
GLKBaseEffect
,使用编译链接自定义的着色器(shader
)。用简单的glsl
语言来实现顶点、片元着色器,并图形进行简单的变换。
基本思路
- 1.创建图层
- 2.创建上下文
- 3.清空缓存区
- 4.设置
RenderBuffer
- 5.设置
FrameBuffer
- 6.开始绘制
准备工作
首先,修改
Storyboard
里面View
的父类为MyView
.
其次,导入#import <OpenGLES/ES2/gl.h>
,声明一些属性.
//在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;
着色器程序对象
将顶点着色器
和片段着色器
与它绑定
到一起.
设置图层
- (void)setUpLayer{
//创建特殊图层
self.myEagLayer = (CAEAGLLayer *)self.layer;
//设置scale
[self setContentScaleFactor:[[UIScreen mainScreen] scale]];
// 设置描述属性
NSDictionary *pDcit = @{
kEAGLDrawablePropertyRetainedBacking:@false,
kEAGLDrawablePropertyColorFormat:kEAGLColorFormatRGBA8
};
self.myEagLayer.drawableProperties = pDcit;
}
+(Class)layerClass
{
return [CAEAGLLayer class];
}
- 重写
layerClass
,将MyView
返回的图层从CALayer
替换成CAEAGLLayer
. - 设置
scale
,直接选择屏幕的宽高比 - 设置描述属性,这里设置不维持渲染内容以及颜色格式为
RGBA8
kEAGLDrawablePropertyRetainedBacking
:表示绘图表面显示后,是否保留其内容。
kEAGLDrawablePropertyColorFormat
:可绘制表面的内部颜色缓存区格式,这个key
对应的值是一个NSString
指定特定颜色缓存区对象。默认是kEAGLColorFormatRGBA8
;
kEAGLColorFormatRGBA8
:32
位RGBA
的颜色,4*8=32
位
kEAGLColorFormatRGB565
:16
位RGB
的颜色,
kEAGLColorFormatSRGBA8
:sRGB
代表了标准的红、绿、蓝,即CRT
显示器、LCD
显示器、投影机、打印机以及其他设备中色彩再现所使用的三个基本色素。sRGB
的色彩空间基于独立的色彩坐标,可以使色彩在不同的设备使用传输中对应于同一个色彩坐标体系,而不受这些设备各自具有的不同色彩坐标的影响。
设置图形上下文
- (void)setUpContext{
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
EAGLContext *context = [[EAGLContext alloc] initWithAPI:api];
if (!context){
NSLog(@"context create error ");
return;
}
if (![EAGLContext setCurrentContext:context]) {
NSLog(@"setCurrentContext failed!");
return;
}
self.myContext = context;
}
- 指定
OpenGL ES
渲染API
版本,我们使用2.0
- 创建图形上下文
- 判断是否创建成功
- 设置图形上下文
- 将局部context,变成全局的
清空缓存区
- (void)deleteRenderBufferAndFrameBuffer{
glDeleteBuffers(1, &_myColorRenderBuffer);
self.myColorRenderBuffer = 0;
glDeleteBuffers(1, &_myColorFrameBuffer);
self.myColorFrameBuffer = 0;
}
buffer
分为frame buffer
和render buffer
2个大类。
其中frame buffer
相当于render buffer
的管理者。
frame buffer object
即称FBO
。
render buffer
则又可分为3
类。colorBuffer、depthBuffer、stencilBuffer
。
设置renderBuffer
- (void)setUpRenderBuffer{
GLuint buffer;
glGenRenderbuffers(1, &buffer);
self.myColorRenderBuffer = buffer;
glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
[self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
}
- 定义一个缓存区
ID
- 申请一个缓存区标志
- 拿到
buffer
传给myColorRenderBuffer
- 将标识符绑定到
GL_RENDERBUFFER
- 将可绘制对象
drawable object's CAEAGLLayer
的存储绑定到OpenGL ES renderBuffer
对象
设置framebuffer
- (void)setUpFrameBuffer{
GLuint buffer;
glGenFramebuffers(1, &buffer);
self.myColorFrameBuffer = buffer;
glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);
}
- 定义一个缓存区
ID
- 申请一个缓存区标志
- 拿到
buffer
传给myColorFrameBuffer
- 绑定
生成帧缓存区之后,则需要将
renderbuffer
跟framebuffer
进行绑定,
调用glFramebufferRenderbuffer
函数进行绑定到对应的附着点上,后面的绘制才能起作用
- 将渲染缓存区
myColorRenderBuffer
通过glFramebufferRenderbuffer
函数绑定到 GL_COLOR_ATTACHMENT0上。
开始绘制
- (void)renderLayer{
glClearColor(0.3f, 0.45f, 0.5f, 1.0f);
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);
NSString *verfilePath = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"vsh"];
NSString *fragfilePath = [[NSBundle mainBundle] pathForResource:@"shaderf" ofType:@"fsh"];
self.myPrograme = [self LoadShaders:verfilePath withfrag:fragfilePath];
glLinkProgram(self.myPrograme);
GLint linkStatus;
glGetProgramiv(self.myPrograme, GL_LINK_STATUS, &linkStatus);
if (linkStatus == GL_FALSE){
GLchar message[1024];
glGetProgramInfoLog(self.myPrograme, sizeof(message), 0, &message[0]);
NSString *messageString = [NSString stringWithUTF8String:message];
NSLog(@"Program Link Error:%@",messageString);
return;
}
NSLog(@"Program Link Success!");
glUseProgram(self.myPrograme);
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,
};
GLuint attrBuffer;
glGenBuffers(1, &attrBuffer);
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
//(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
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GL_FLOAT) * 5, NULL);
//(1).glGetAttribLocation,用来获取vertex attribute的入口的.
//注意:第二参数字符串必须和shaderv.vsh中的输入变量:textCoordinate保持一致
GLuint textColor = glGetAttribLocation(self.myPrograme, "textCoordinate");
//(2).设置合适的格式从buffer里面读取数据
glEnableVertexAttribArray(textColor);
//(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
glVertexAttribPointer(textColor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (float*)NULL + 3);
[self setupTexture:@"iu.jpg"];
glUniform1i(glGetUniformLocation(self.myPrograme, "colorMap"), 0);
glDrawArrays(GL_TRIANGLES, 0, 6);
[self.myContext presentRenderbuffer:GL_RENDERBUFFER];
}
-
设置清屏颜色
-
清除屏幕
-
设置视口大小
-
读取顶点着色程序、片元着色程序
-
loadShaders
加载shader
-
glLinkProgram,glGetProgramiv
链接,获取链接状态 -
glUseProgram
使用program -
attrBuffer
设置顶点、纹理坐标 -
position
处理顶点数据- 顶点缓存区
- 申请一个缓存区标识符
- 将
attrBuffer
绑定到GL_ARRAY_BUFFER
标识符上
- 将
- 把顶点数据从
CPU
内存复制到GPU
上
- 把顶点数据从
-
将顶点数据通过
myPrograme
中的传递到顶点着色程序的position
- 1.
glGetAttribLocation
,用来获取vertex attribute
的入口的. - 2.告诉
OpenGL ES
,通过glEnableVertexAttribArray
, - 3.最后数据是通过
glVertexAttribPointer
传递过去的。
- 1.
-
textCoor
处理纹理数据 -
setupTexture
加载纹理 -
glUniform1i
设置纹理采样器sampler2D
-
glDrawArrays
绘图 -
presentRenderbuffer
从渲染缓存区显示到屏幕上
从图片中加载纹理
//从图片中加载纹理
- (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.使用默认方式绘制
CGContextSetTextMatrix(spriteContext, CGAffineTransformIdentity);
CGContextTranslateCTM(spriteContext, 0, rect.size.height);
CGContextScaleCTM(spriteContext, 1.0, -1.0);
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;
}
图片旋转
image.png
CGContextDrawImage
使用的是Core Graphics
框架,坐标系与UIKit
不一样。UIKit
框架的原点在屏幕的左上角,Core Graphics
框架的原点在屏幕的左下角.画出来的图片是倒立的.
压入一个单元矩阵
将画图向上平移屏幕高度,在绕x轴选择180°.
CGContextSetTextMatrix(spriteContext, CGAffineTransformIdentity);
CGContextTranslateCTM(spriteContext, 0, rect.size.height);
CGContextScaleCTM(spriteContext, 1.0, -1.0);
image.png
效果图
加载着色器
根据文件路径加载着色器,进行编译并连接到一起
最后返回program
着色器`程序对象
#pragma mark --shader
- (GLuint)LoadShaders:(NSString *)vert withfrag:(NSString *)frag{
//1,定义两个着色器对象
GLuint VerShader,fragShader;
//创建program
GLuint program = glCreateProgram();
//2.编译顶点着色程序、片元着色器程序
//参数1:编译完存储的底层地址
//参数2:编译的类型,GL_VERTEX_SHADER(顶点)、GL_FRAGMENT_SHADER(片元)
//参数3:文件路径
[self ClangCompileShader:&VerShader type:GL_VERTEX_SHADER file:vert];
[self ClangCompileShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
//3.创建最终的程序
glAttachShader(program, VerShader);
glAttachShader(program, fragShader);
//4.释放不需要的shader
glDeleteShader(VerShader);
glDeleteShader(fragShader);
return program;
}
- (void)ClangCompileShader:(GLuint *)shader type:(GLenum )type file:(NSString *)file{
//1.读取文件路径字符串
NSString *content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
const GLchar* sourse = (GLchar *)[content UTF8String];
//2.创建一个shader(根据type类型)
*shader = glCreateShader(type);
//3.将着色器源码附加到着色器对象上。
//参数1:shader,要编译的着色器对象 *shader
//参数2:numOfStrings,传递的源码字符串数量 1个
//参数3:strings,着色器程序的源码(真正的着色器程序源码)
//参数4:lenOfStrings,长度,具有每个字符串长度的数组,或NULL,这意味着字符串是NULL终止的
glShaderSource(*shader, 1, &sourse, NULL);
// 4.把着色器源代码编译成目标代码
glCompileShader(*shader);
}
```
[demo](https://github.com/zedlv/glslDemo1.git)