OpenGL ES学习笔记2: 使用shader(着色器)展现图

2017-02-22  本文已影响465人  南华coder

<h5>1、先上效果图</h5>

效果图.png

<h5> 背景知识</h5>

<h5>2、实现思路</h5>

1)设置CAEAGLLayer图层
2)创建OpenGL ES上下文

  1. 设置渲染和帧缓存区
    4)编译顶点着色器和片段着色器,链接成着色器程序
    5)为着色器中的输入参数赋值
    6)加载纹理
    7)渲染

<h5>3、主要代码</h5>

- (instancetype)initWithFrame:(CGRect)frame{

    self = [super initWithFrame:frame];
    if (self) {
        [self setupLayer];
        [self setupContext];
        [self setupRenderBuffer];
        [self setupFrameBuffer];
        GLuint shader = [self compileShader];
        [self setupValueForShader:shader];
        [self setupTexture:@"ic_dog.jpeg"];
        [self render];
    }
    return self;
}

<h5>4、主要代码分析</h5>

<h5>4-1、主要代码分析之 编译着色器,链接成着色器程序 </h5>

1) 顶点着色器

attribute vec4 position; //输入参数1 (位置坐标)
attribute vec2 textCoordinate;  //输入参数2 (纹理坐标)
uniform mat4 rotateMatrix;  //全局参数
varying lowp vec2 varyTextCoord; //纹理坐标

void main()
{
    varyTextCoord = vec2(textCoordinate.x,1.0 - textCoordinate.y); //解决纹理上下颠倒的问题
    gl_Position = position * rotateMatrix;
}

2)片元着色器

varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap; //2d纹理采样器,默认的激活纹理单元(0),没有分配值
void main()
{
    gl_FragColor = texture2D(colorMap, varyTextCoord); //采样纹理的颜色,第一个参数是纹理采样器,第二个参数是对应的纹理坐标
}

着色器代码说明

3)着色器编译和链接部分的代码

/**
 *  glsl的编译过程主要有glCompileShader、glAttachShader、glLinkProgram三步;
 *
 *  @return 编译成功的shaders
 */
- (GLuint)compileShader{

    GLuint verShader, fragShader;
    GLint program = glCreateProgram();

    //1、编译shader
    NSString* vertFilePath = [[NSBundle mainBundle] pathForResource:kVertexFileName ofType:@"glsl"];
    [self compileShader:&verShader type:GL_VERTEX_SHADER file:vertFilePath];

    NSString* fragFilePath = [[NSBundle mainBundle] pathForResource:kFragmentFileName ofType:@"glsl"];
    [self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragFilePath];

    glAttachShader(program, verShader);
    glAttachShader(program, fragShader);

    //2、链接
    glLinkProgram(program);
    GLint linkSuccess;
    glGetProgramiv(program, GL_LINK_STATUS, &linkSuccess);
    if (linkSuccess == GL_FALSE) { //连接错误
        GLchar messages[256];
        glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]);
        NSString *messageString = [NSString stringWithUTF8String:messages];
        NSLog(@"error%@", messageString);
        return 0;
    }
    else {
        NSLog(@"link ok");
        glUseProgram(program); //激活着色器,成功便使用,避免由于未使用导致的的bug
    }

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

    return program;
}

 /**
  编译shader功能函数
 */
- (void)compileShader:(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);

    GLint compileSuccess;
    glGetShaderiv(*shader, GL_COMPILE_STATUS, &compileSuccess);
    if (compileSuccess == GL_FALSE) {
        GLchar messages[256];
        glGetShaderInfoLog(*shader, sizeof(messages), 0, &messages[0]);
        NSString *messageString = [NSString stringWithUTF8String:messages];
        NSLog(@"%@", messageString);
        exit(1);
    }
}

<h5>4-2、主要代码分析之 加载纹理 </h5>

1) 对齐纹理和顶点,以便GPU知道每个片元的颜色由哪些纹理决定。这种对齐又叫映射(mapping)。扩展每个顶点的数据来实现:x,y,z坐标之外增加是s,t坐标。x,y,z坐标取值[-1,1],s,t坐标取值[0,1]。如果s,t超出[0,1],就需要设置纹理环绕

//前三个是顶点坐标(x,y,z), 后面两个是纹理坐标(s,t)
static GLfloat vertices[] =
{
      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,  //右下
};

2) 使用CoreGraphics把图像转换成bitmap data

// 获取图片的CGImageRef
CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
if (!spriteImage) {
    NSLog(@"Failed to load image %@", fileName);
    exit(1);
}

// 读取图片的大小
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);
//在CGContextRef上绘图
CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);
CGContextRelease(spriteContext);

3) 纹理采样(设置纹理环绕和过滤方式)

GLuint texture;
glGenBuffers(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);

//纹理环绕
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);   //超出s轴的部分,会重复纹理坐标的边缘
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);   //超出t轴的部分,会重复纹理坐标的边缘

//纹理过滤
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );   //纹理缩小,采用邻近过滤(GL_NEAREST)
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );   //纹理放大,采用线性过滤(GL_LINEAR)

<h5>重要函数说明</h5>

设置把纹素映射成像素的方式.
//参数1:  指定了纹理目标;我们使用的是2D纹理,因此纹理目标是GL_TEXTURE_2D
//参数2:  纹理参数名称,可以是GL_TEXTURE_MAG_FILTER(放大过滤) 、GL_TEXTURE_MIN_FILTER(缩小过滤) 、GL_TEXTURE_WRAP_S((环绕)纹理S轴)、GL_TEXTURE_WRAP_T((环绕)纹理T轴)     
//参数3:纹理过滤方式或环绕方式              

a) 环绕方式说明

纹理坐标的范围通常是从(0, 0)到(1, 1),如果我们把纹理坐标设置在范围之外,OpenGL ES默认的行为是重复这个纹理图像(GL_REPEAT),
这种行为成为环绕方式。支持的环绕方式如下:

TextureWrapMode 描述
GL_REPEAT 对纹理的默认行为。重复纹理图像。
GL_MIRRORED_REPEAT 和GL_REPEAT一样,但每次重复图片是镜像放置的。
GL_CLAMP_TO_EDGE 纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。

b) 环绕过滤说明

环绕过滤是用来处理纹素的数量与需要被着色的片元数量之间不匹配的情况,过滤方式有:

TextureMagFilter 描述
GL_NEAREST(Nearest Neighbor Filtering) 邻近过滤,默认的纹理过滤方式,OpenGL会选择中心点最接近纹理坐标的那个纹素的颜色。
GL_LINEAR(linear Filtering) 线性过滤,OpenGL会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。

总之
为GL_TEXTURE_MIN_FILTER设置GL_NEAREST时候,表明当多个纹素对应一个片元时候,从多纹素个中取色,选择中心点最接近纹理坐标的纹素的颜色作为片元的颜色;
为GL_TEXTURE_MAG_FILTER设置GL_LINEAR时候,表明没有足够纹素映射到片元的时候,混合附近纹素的颜色来计算片元的颜色。

4) 加载图片形成纹理

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);

<h5>重要函数说明</h5>

具体代码请看:QSOpenGLES002

参考
iOS开发-OpenGL ES入门教程2
(译)OpenGL ES2.0 – Iphone开发指引
OpenGL纹理上下颠倒翻转的三种解决办法

上一篇下一篇

猜你喜欢

热点阅读