iOS视觉

二十 编译链接自定义的着色器(shader)流程

2020-08-05  本文已影响0人  王俏

1. 流程图步骤

效果图

注意:直接加载图片是倒置的,由于纹理坐标系原点在左下角,屏幕坐标系的原点在左上角
[图片上传失败...(image-2510ff-1596589477801)]

流程

image

2. 步骤详解

1.创建图层 [self setupLayer];

self.myEagLayer = (CAEAGLLayer *)self.layer;
+(Class)layerClass
{
    return [CAEAGLLayer class];
}
[self setContentScaleFactor:[[UIScreen mainScreen]scale]];
self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@false,kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,nil];

2.创建上下文 [self setupContext];

EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
EAGLContext *context = [[EAGLContext alloc]initWithAPI:api];
if (!context) {
     NSLog(@"Create context failed!");
     return;
 }
if (![EAGLContext setCurrentContext:context]) {
        NSLog(@"setCurrentContext failed!");
        return;
    }
self.myContext = context;

3.清空缓存区 [self deleteRenderAndFrameBuffer];

glDeleteBuffers(1, &_myColorRenderBuffer);
self.myColorRenderBuffer = 0;
    
glDeleteBuffers(1, &_myColorFrameBuffer);
self.myColorFrameBuffer = 0;

4.设置RenderBuffer [self 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];

5.设置FrameBuffer [self setupFrameBuffer];

//1) 定义一个缓存区ID 
GLuint buffer;

//2) 申请一个缓存区标志
//glGenRenderbuffers(1, &buffer); 
//glGenFramebuffers(1, &buffer);
glGenBuffers(1, &buffer);

//3) 将帧缓存区变成全局的
self.myColorFrameBuffer = buffer;

//4)绑定
glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);

//5.将渲染缓存区myColorRenderBuffer 
通过glFramebufferRenderbuffer函数绑定到 
GL_COLOR_ATTACHMENT0上。
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);

6.开始绘制 [self renderLayer];

1.设置清屏颜色,清除缓冲区

//设置清屏颜色
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);

//清除缓冲区
glClear(GL_COLOR_BUFFER_BIT);

2.设置视口大小

 //1. 设置视口大小
 GLfloat  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);

3.读顶点着色器程序和片元着色器程序

 //2. 读顶点着色器程序和片元着色器程序
    NSString *vertFile = [[NSBundle mainBundle]pathForResource:@"shaderv" ofType:@"vsh"];
    NSString *fragFile = [[NSBundle mainBundle]pathForResource:@"shaderf" ofType:@"fsh"];
    self.htProgram = [self loadShader:vertFile withFrag:fragFile];

加载shader

//加载shader
-(GLuint)loadShader:(NSString *)vert withFrag:(NSString *)frag
{
    //1. 定义两个着色器对象
    GLuint verShader,fragShader;

    //创建 shader;
    GLuint program = glCreateProgram();

    //2. 编译顶点着色器程序和片元着色器程序
    [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);

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

    return program;

}

编译shader

    //编译shader
- (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
    *shader = glCreateShader(type);

    //3.将着色器源码附加到着色器对象上。
    //参数1:shader,要编译的着色器对象 *shader
    //参数2:numOfStrings,传递的源码字符串数量 1个
    //参数3:strings,着色器程序的源码(真正的着色器程序源码)
    //参数4:lenOfStrings,长度,具有每个字符串长度的数组,或NULL,这意味着字符串是NULL终止的
    glShaderSource(*shader, 1, &source, NULL);

    //4.把着色器源代码编译成目标代码
    glCompileShader(*shader);
}

4.链接,获取链接状态: glGetProgramiv

glLinkProgram(self.htProgram);
 GLint linkStatus;
    glGetProgramiv(self.htProgram, GL_LINK_STATUS, &linkStatus);
    if(linkStatus == GL_FALSE){
        GLchar message[512];
        glGetProgramInfoLog(self.htProgram, sizeof(message), 0, &message[0]);
        NSString *messageString  = [NSString stringWithUTF8String:message];
        NSLog(@"Program Link Error:%@",messageString);
        return;
    }

5.使用program: glUseProgram

glUseProgram(self.htProgram);

6.设置顶点,纹理坐标 attrArr[]

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,
    };

7.处理顶点数据

GLuint attrBuffer;
glGenBuffers(1, &attrBuffer);
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);

8.将顶点数据通过myPrograme中的传递到顶点着色程序的position

GLuint position = glGetAttribLocation(self.htProgram, "position");
glEnableVertexAttribArray(position);
    //通过glVertexAttribPointer传递过去的。
    //参数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.处理纹理数据

 //注意:第二参数字符串必须和shaderv.vsh中的输入变量:textCoordinate保持一致
    GLuint textCoor = glGetAttribLocation(self.htProgram, "textCoordinate");
glEnableVertexAttribArray(textCoor);
        //参数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);

10.加载纹理

//加载纹理
-(GLuint )setupTexture:(NSString *)file
{
    //1.将UIImage 转换为CGImageRef
    CGImageRef spriteImage = [UIImage imageNamed:file].CGImage;

    //判断图片是否获取成功
    if(!spriteImage){
        NSLog(@"Failed to load image: %@",file);
        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上--> 将图片绘制出来
    CGRect rect = CGRectMake(0, 0, width, height);
    CGContextDrawImage(spriteContext, rect, spriteImage);

    //6. 画图完毕就释放上下文
    CGContextRelease(spriteContext);

    //7.绑定纹理到默认的纹理ID
    glBindTexture(GL_TEXTURE_2D, 0);

    //8.设置纹理属性
    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);

    //9.载入纹理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:纹理数据
     */
    float fw = width, fh = height;
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);

    //10. 释放spriteData
    free(spriteData);
    return 0;
}

11.设置纹理采样器 sampler2D

GLint textureLocation = glGetUniformLocation(self.htProgram, "colorMap");
    glUniform1i(textureLocation, 0);

12.绘图

glDrawArrays(GL_TRIANGLES, 0, 6);

13.从渲染缓存区显示到屏幕上

[self.htContext presentRenderbuffer:GL_RENDERBUFFER];

7.常用GLSL API解析

image

8. 顶点着色器程序和片元着色器程序书写

注意:顶点着色器程序和片元着色器程序书写时不要加入中文注释,否则可能有意想不到的错误

顶点着色器代码

attribute vec4 position;
attribute vec2 textCoordinate;
varying lowp vec2 varyTextCoord;

void main()
{
    varyTextCoord = textCoordinate;
    gl_Position = position;
}

片元着色器代码

[图片上传失败...(image-2b9-1596589477801)]

precsion highp float;
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;   

void main(){
    lowp vec4 temp = texture2D(colorMap, varyTextCoord);
    gl_FragColor = temp;
} 

片元着色器,是如何访问纹理对象

如何获取纹理对应像素点的颜色值
使用GLSL内建的texture函数来采样纹理的颜色值.
gl_FragColor = texture(ourTexture, TexCoord);

使用glUniform1i给纹理采样器分配一个位置值,可以在一个片段着色器中设置多个纹理

一个纹理的位置值通常称为一个纹理单元(Texture Unit)。一个纹理的默认纹理单元是0,它是默认的激活纹理单元.

//在绑定纹理之前先激活纹理单元
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);

OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0到GL_TEXTRUE15。它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8
多纹理混合:mix函数接受两个值作为参数,并对它们根据第三个参数进行线性插值

varying vec2 TexCoord;
uniform sampler2D ourTexture1;
uniform sampler2D ourTexture2;

void main()
{
    gl_FragColor = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, TexCoord), 0.2);
}

mix函数:如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。输入0.2则会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色

在纹理传输中代码实现

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glUniform1i(glGetUniformLocation(program, "ourTexture1"), 0);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glUniform1i(glGetUniformLocation(program, "ourTexture2"), 1);

完整代码参考:github

倒置的图片如何翻转参考:OpenGL 2D纹理单元和纹理翻转解决策略

上一篇 下一篇

猜你喜欢

热点阅读