美文网首页iOS视觉
二十 编译链接自定义的着色器(shader)流程

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

作者: 王俏 | 来源:发表于2020-08-05 20:05 被阅读0次

1. 流程图步骤

效果图

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

流程

image

2. 步骤详解

1.创建图层 [self setupLayer];

  • 1)创建特殊图层 重写layerClass,将HTView返回的图层
    从CALayer替换成CAEAGLLayer
self.myEagLayer = (CAEAGLLayer *)self.layer;
+(Class)layerClass
{
    return [CAEAGLLayer class];
}
  • 2)设置scale
[self setContentScaleFactor:[[UIScreen mainScreen]scale]];
  • 3)设置描述属性
self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@false,kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,nil];

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

    1. 指定OpenGL ES 渲染API版本,我们使用2.0
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
    1. 创建图形上下文
EAGLContext *context = [[EAGLContext alloc]initWithAPI:api];
    1. 判断是否创建成功
if (!context) {
     NSLog(@"Create context failed!");
     return;
 }
    1. 设置当前图形上下文
if (![EAGLContext setCurrentContext:context]) {
        NSLog(@"setCurrentContext failed!");
        return;
    }
    1. 将局部context,变成全局的
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

    1. 定义两个着色器对象
    1. 创建 shader;
    1. 编译顶点着色器程序和片元着色器程序
    1. 创建最终程序
    1. 释放不需要的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

  • 1.读取文件路径
  • 2.创建一个shader
  • 3.将着色器源码附加到着色器对象上
  • 4.把着色器源代码编译成目标代码
    //编译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

  • glGetAttribLocation,用来获取vertex attribute的入口的.
GLuint position = glGetAttribLocation(self.htProgram, "position");
  • 设置合适的格式从buffer里面读取数据
glEnableVertexAttribArray(position);
  • 最后数据是通过glVertexAttribPointer传递过去的。
    //通过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.处理纹理数据

  • glGetAttribLocation,用来获取vertex attribute的入口的.
 //注意:第二参数字符串必须和shaderv.vsh中的输入变量:textCoordinate保持一致
    GLuint textCoor = glGetAttribLocation(self.htProgram, "textCoordinate");
  • 设置合适的格式从buffer里面读取数据
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.加载纹理

  • 1)将UIImage 转换为CGImageRef

    CGImageRef spriteImage = [UIImage imageNamed:file].CGImage;

  • 2)判断图片是否获取成功

  • 3)读取图片的大小,宽和高

    size_t width = CGImageGetWidth(spriteImage);

    size_t height = CGImageGetHeight(spriteImage);

  • 4)获取图片字节数 宽4(RGBA) 开辟存储空间

  • 5)创建上下文

  • 6)在CGContextRef上--> 将图片绘制出来 CGContextDrawImage

  • 7)画图完毕就释放上下文
    CGContextRelease(spriteContext);

  • 8)绑定纹理到默认的纹理ID glBindTexture

  • 9)设置纹理属性 glTexParameteri

  • 10)载入纹理2D数据 glTexImage2D

  • 11)释放spriteData

//加载纹理
-(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. 顶点着色器程序和片元着色器程序书写

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

  • 变量存储限定符:varying、attribute、uniform
  • varying 修饰符:当需要将顶点着色器的数据传递到片元着色器时,两个着色器中一模一样的纹理坐标变量就需要它来修饰
  • attribute:数据只能从客户端中传递到顶点着色器,且只能在顶点着色器中使用,修饰的数据:顶点、纹理、颜色、法线等
  • 纹理坐标,需要顶点着色器间接传递到片元着色器,需要在顶点与片元着色器中定义一个一模一样的纹理坐标,通过这个变量将纹理坐标数据间接传递到片元着色器,varying lowp vec2 varyTextCoord;
    顶点着色器计算之后的顶点结果需要赋值给GLSL的内建变量gl_Position
  • uniform:从app代码传递到vertex、fragment中所用的变量
    在vertex,fragment中一般将uniform当成常量
    uniform可以传的数据:视图矩阵、投影矩阵、投影视图矩阵
    片元着色器中最终颜色,即拿到纹理对应坐标下的纹素。纹素是纹理对应像素点的颜色值,需要通过内建函数texture2D(纹理,纹理坐标)计算,将最终返回的颜色值赋值给内建变量gl_FragColor

顶点着色器代码

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

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

  • 不同维度的纹理类型:sampler1D,sampler2D,sampler3D
  • 声明一个纹理对象: uniform sampler2D,将一个纹理添加片元着色器中.

如何获取纹理对应像素点的颜色值
使用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纹理单元和纹理翻转解决策略

相关文章

网友评论

    本文标题:二十 编译链接自定义的着色器(shader)流程

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