美文网首页
005-自定义shader加载图片

005-自定义shader加载图片

作者: 冰三尺 | 来源:发表于2019-10-11 14:57 被阅读0次

OpenGL ES通过CAEAGLLayer类连接到Core Animation,该类是Core Animation层的一种特殊类型,其内容来自OpenGL ES渲染缓冲区。Core Animation将渲染缓冲区的内容与其他图层合成, 并在屏幕上显示结果图像。

Core Animation与OpenGL ES共享渲染缓冲区

要将核心动画层CAEAGLLayer用于OpenGL ES渲染,请执行以下操作:

  1. 创建一个CAEAGLLayer对象并配置其属性
  2. 分配一个OpenGL ES上下文,并使其成为当前上下文
  3. 创建帧缓冲区对象
  4. 创建一个颜色渲染缓冲区
  5. 检索颜色渲染缓冲区的高度和宽度。
  6. 分配并附加一个渲染缓存区。
  7. 测试帧缓冲区的完整性。
  8. 创建着色器加载glsh文件
  9. 链接着色器
  10. 使用着色器
  11. 处理顶点
  12. 开启顶点着色器通道
  13. 加载纹理
  14. 将渲染结果绘制到缓冲区
  15. 通过将CAEAGLLayer将缓冲区数据渲染到屏幕上

shader

着色器创建流程

OpenGL ES通过CAEAGLLayer类连接到Core Animation,该类是Core Animation层的一种特殊类型,其内容来自OpenGL ES渲染缓冲区。Core Animation将渲染缓冲区的内容与其他图层合成, 并在屏幕上显示结果图像。

要将核心动画层用于OpenGL ES渲染,请执行以下操作:
准备属性

#import <OpenGLES/ES2/gl.h>
GLuint textureID;
@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

  1. 创建一个CAEAGLLayer对象并配置其属性。
    OpenGL ES通过CAEAGLLayer类连接到Core Animation,该类是Core Animation层的一种特殊类型,其内容来自OpenGL ES渲染缓冲区。为了获得最佳性能,请将图层opaque属性的值设置为YES。

(可选)通过将值的新字典分配给对象的drawableProperties属性来配置渲染表面的表面属性CAEAGLLayer。您可以为渲染缓冲区指定像素格式,并指定将渲染缓冲区的内容发送到Core Animation后是否将其丢弃。

/*
重写layerClass,将CCView返回的图层从CALayer替换成CAEAGLLayer
*/
+(Class)layerClass
{
    return [CAEAGLLayer class];
}

//1.设置图层
-(void)setupLayer
{
    //1.创建特殊图层
    self.myEagLayer = (CAEAGLLayer *)self.layer;
    
    //2.设置scale
    [self setContentScaleFactor:[[UIScreen mainScreen]scale]];


    //3.设置描述属性,这里设置不维持渲染内容以及颜色格式为RGBA8
    /*
     kEAGLDrawablePropertyRetainedBacking  表示绘图表面显示后,是否保留其内容。
     kEAGLDrawablePropertyColorFormat
         可绘制表面的内部颜色缓存区格式,这个key对应的值是一个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];
  
}
  1. 分配一个OpenGL ES上下文,并使其成为当前上下文。请参阅配置OpenGL ES上下文。
-(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(@"setCurrentContext failed!");
        return;
    }
    //5.将局部context,变成全局的
    self.myContext = context;
    
}
  1. 创建帧缓冲区对象(如上面的创建离屏帧缓冲区对象中所述)。

清空缓存区 默认情况下,您必须假定应用程序呈现渲染缓冲区后,渲染缓冲区的内容将被丢弃。这意味着您的应用每次呈现框架时,都必须在呈现新框架时完全重新创建框架的内容。出于这个原因,上面的代码总是擦除颜色缓冲区。

-(void)deleteRenderAndFrameBuffer
{
    /*
     buffer分为frame buffer 和 render buffer2个大类。
     其中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;
    
}
  1. 创建一个渲染缓冲区,通过调用上下文的renderbufferStorage:fromDrawable:方法并将图层对象作为参数来分配其存储空间。宽度,高度和像素格式取自图层,用于为渲染缓冲区分配存储空间。
GLuint colorRenderbuffer;
glGenRenderbuffers(1, &colorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:myEAGLLayer];
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);

//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 object's  CAEAGLLayer的存储绑定到OpenGL ES renderBuffer对象
    [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
}

注意: 当“核心动画”层的边界或属性发生变化时,您的应用应重新分配渲染缓冲区的存储。如果不重新分配渲染缓冲区,则渲染缓冲区的大小将与图层的大小不匹配;在这种情况下,Core Animation可以缩放图像的内容以适合图层。

  1. 检索颜色渲染缓冲区的高度和宽度。
GLint width;
GLint height;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);

在较早的示例中,显式提供了渲染缓冲区的宽度和高度,以为缓冲区分配存储空间。在这里,代码在分配了其存储空间之后,从颜色渲染缓冲区中检索宽度和高度。您的应用程序之所以这样做是因为彩色渲染缓冲区的实际尺寸是根据图层的边界和比例因子计算的。附加到帧缓冲区的其他渲染缓冲区必须具有相同的尺寸。除了使用高度和宽度来分配深度缓冲区外,还可以使用它们来分配OpenGL ES视口并帮助确定应用程序的纹理和模型所需的细节级别。请参阅支持高分辨率显示器。

  1. 分配帧缓冲区并附加一个渲染缓冲区。
屏幕快照 2019-10-08 下午9.59.32.png
-(void)setupFrameBuffer
{
    //1.定义一个缓存区ID
    GLuint buffer;
    
    //2.申请一个缓存区标志
    glGenFramebuffers(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. 检测帧缓冲区是否正确, 在OpenGL ES中所以的验证状态的函数, 尽可能的在调试时使用, 因为这些函数会占用资源
    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER) ;
    if(status != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"failed to make complete framebuffer object %x", status);
    }
    
}
  1. 测试帧缓冲区的完整性
    检测帧缓冲区是否正确, 在OpenGL ES中所以的验证状态的函数, 尽可能的在调试时使用, 因为这些函数会占用资源

    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER) ;
    if(status != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"failed to make complete framebuffer object %x", status);
    }
  1. 创建着色器

#pragma mark --shader
//加载shader
-(GLuint)loadShaders:(NSString *)vert Withfrag:(NSString *)frag
{
    //1.定义2个零时着色器对象
    GLuint verShader, fragShader;
    
    NSString* content = "着色器文件";
    const GLchar* source = (GLchar *)[content UTF8String];
    
    //2.创建一个shader(根据type类型)编译的类型,GL_VERTEX_SHADER(顶点)、GL_FRAGMENT_SHADER(片元)
    verShader = glCreateShader(type);
    
    //3.将着色器源码附加到着色器对象上。
    //参数1:shader,要编译的着色器对象 *shader
    //参数2:numOfStrings,传递的源码字符串数量 1个
    //参数3:strings,着色器程序的源码(真正的着色器程序源码)
    //参数4:lenOfStrings,长度,具有每个字符串长度的数组,或NULL,这意味着字符串是NULL终止的
    glShaderSource(*shader, 1, &source,NULL);
    
    //4.把着色器源代码编译成目标代码
    glCompileShader(*shader);
}

  1. 链接着色器到program, GLSL编译
    在OpenGL ES中, 每个program对象有且仅有一个Vertex Shader对象和一个Fragment Shader 对象链接它. vsh/fsh着色器 -> program ->GPU
//创建着色器最终的程序
glAttachShader(program, verShader);
glAttachShader(program, fragShader);

//释放不需要的shader
glDeleteShader(verShader);
glDeleteShader(fragShader);

//链接着色器程序
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;
}


  1. GLSL编译 使用着色器program
//使用program
glUseProgram(self.myPrograme);
  1. 处理顶点
//前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,
};

//(1)顶点缓存区
GLuint attrBuffer;
//(2)申请一个缓存区标识符
glGenBuffers(1, &attrBuffer);
//(3)将attrBuffer绑定到GL_ARRAY_BUFFER标识符上
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
//(4)把顶点数据从CPU内存复制到GPU上
/*
 GL_STATIC_DRAW 适用于多次渲染且其内容仅指定一次且永不更改的顶点缓冲区。
 GL_DYNAMIC_DRAW 适用于多次渲染且其内容在渲染循环中更改的顶点缓冲区。
 GL_STREAM_DRAW 用于顶点缓冲区,渲染次数很少,然后丢弃。
 */
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);

  1. 开启顶点着色器输入变量通道
//(1)注意:第二参数字符串必须和shaderv.vsh中的输入变量:position保持一致
GLuint position = glGetAttribLocation(self.myPrograme, "position");

//(2).设置合适的格式从buffer里面读取数据, 被attribute修饰的默认是关闭通道, 需要使用glEnableVertexAttribArray打开, 其他修饰符不需要
glEnableVertexAttribArray(position);

//(3).设置读取方式, 直接从VBO里面读取数据
//参数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.----处理纹理数据-------
//(1).glGetAttribLocation,用来获取vertex attribute的入口的.
//注意:第二参数字符串必须和shaderv.vsh中的输入变量:textCoordinate保持一致
GLuint textCoor = glGetAttribLocation(self.myPrograme, "textCoordinate");

//(2).设置合适的格式从buffer里面读取数据
glEnableVertexAttribArray(textCoor);

//(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(textCoor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL + 3);
  1. 加载纹理
//从图片中加载纹理
- (GLuint)setupTexture:(NSString *)fileName {
    
    UIImage *image = [UIImage imageNamed:fileName];
    GLubyte *spriteData = [self getImageDataWithImage:image];

//    这里为什么不需要申请纹理操作
    //纹理申请的操作写在纹理着色器里面了 texture2D(colorMap, varyTextCoord)
//    glGenTextures(1, &textureID);
    
//    第0个纹理是默认激活的
//    glActiveTexture(GL_TEXTURE0);
    //8、绑定纹理到默认的纹理ID(
    glBindTexture(GL_TEXTURE_2D, textureID);
    
    //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 = image.size.width, fh = image.size.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;
}
glUniform1i(glGetUniformLocation(self.myPrograme, "colorMap"), textureID);

  1. 将渲染结果绘制到缓冲区
glDrawArrays(GL_TRIANGLES, 0, 6);

  1. 通过将CAEAGLLayer将缓冲区数据渲染到屏幕上
/*
 可绘制对象CAEAGLLayer绑定到OpenGL ES renderBuffer对象
 [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
 */
//从绑定的GL_RENDERBUFFER里面拿出数据绘制到EAGLayer上
[self.myContext presentRenderbuffer:GL_RENDERBUFFER];

相关文章

网友评论

      本文标题:005-自定义shader加载图片

      本文链接:https://www.haomeiwen.com/subject/xxdbpctx.html