3、OpenGL ES 纹理加载图片图片翻转的几种方式

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_TEXTURE0GL_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要求y0.0坐标是在图片的底部的,但是图片的y0.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是没有对应文件的。通过如下方式创建

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值