JHHK

欢迎来到我的个人网站
行者常至 为者常成

13、GLSL图片加载(下)

目录

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

编译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(根据type类型)
    *shader = glCreateShader(type);
    
    //3.将着色器源码附加到着色器对象上。
    //参数1:shader,要编译的着色器对象 *shader
    //参数2:numOfStrings,传递的源码字符串数量 1个
    //参数3:strings,着色器程序的源码(真正的着色器程序源码)
    //参数4:lenOfStrings,长度,具有每个字符串长度的数组,或NULL,这意味着字符串是NULL终止的
    glShaderSource(*shader, 1, &source,NULL);
    
    //4.把着色器源代码编译成目标代码
    glCompileShader(*shader);
}

从图片加载纹理

//从图片中加载纹理
- (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);
   
    //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;
}

纹理翻转

由于纹理坐标的原点在左下角,屏幕坐标的原点在左上角,所以当纹理渲染到屏幕上时是倒置的。

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

映射关系如下图:

我们将如下图片渲染到屏幕上看下效果

渲染到屏幕上的效果

我们可以从不同的角度切入解决该问题。

一、改变映射关系

最直接的方式是将顶点坐标与纹理坐标的映射关系进行调整。

纹理坐标的 S 值不变,T = 1.0 - T;

GLfloat attrArr[] =
{
    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,
};

二、从着色器入手

1、从着色器入手,纹理坐标最终是要传给片元着色器的,那么我们可以在片元着色器中对纹理坐标的T值进行统一处理。

修改shaderf.fsh文件

varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;

void main(){
    
    //gl_FragColor = texture2D(colorMap,varyTextCoord);

    gl_FragColor = texture2D(colorMap,vec2(varyTextCoord.x,1.0-varyTextCoord.y));
}

2、片元着色器的纹理坐标是通过顶点着色器传递过去的,所以我们也可以在顶点着色器内进行处理

修改shaderv.vsh文件

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

void main(){
    
    //varyTextCoord = textCoordinate;

    varyTextCoord = vec2(textCoordinate.x,1.0-textCoordinate.y);
    gl_Position = position;
}

注意1:该方法与改变映射关系的原理是相同的,只不过处理的位置不同。另外需要注意的是,第一种方法的计算在CPU,该方法的计算在GPU

注意2:修改片元着色器和修改顶点着色器这里还是有区别的,因为顶点着色器程序在该案例内会执行6次,片元着色器程序会执行很多次(有多少个像素点就执行多少次)

三、旋转矩阵

在绘制之前通过旋转矩阵对所有的顶点坐标进行变换改变映射关系。

shaderv.vsh文件

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

uniform mat4 rotateMatrix;

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


//    varyTextCoord = vec2(textCoordinate.x,1.0-textCoordinate.y);
//    gl_Position = position;
    
    
    
    vec4 vPos = position;
    vPos = vPos * rotateMatrix;
    gl_Position = vPos;

}

旋转矩阵赋值

-(void)rotateTextureImage
{
    //注意,想要获取shader里面的变量,这里记得要在glLinkProgram后面,后面,后面!
    //1. rotate等于shaderv.vsh中的uniform属性,rotateMatrix
    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,0,-s,0,
        0,1,0,0,
        s,0,c,0,
        0,0,0,1
    };
    
    
    
//    GLfloat zRotation[16] = {
//        1,0,0,0,
//        0,1,0,0,
//        0,0,1,0,
//        0,0,0,1
//    };
    
//    GLfloat zRotation[16] = {
//        1,0,0,0,
//        0,c,s,0,
//        0,-s,c,0,
//        0,0,0,1
//    };

    
    //5.设置旋转矩阵
    /*
     glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)
     location : 对于shader 中的ID
     count : 个数
     transpose : 转置
     value : 指针
     */
    glUniformMatrix4fv(rotate, 1, GL_FALSE, zRotation);
}

四、图片重绘时进行变化

CGContextTranslateCTM(spriteContext, 0, rect.size.height);
CGContextScaleCTM(spriteContext, 1.0, -1.0);

精度限定符

lowp、mediump、highp

它是用来限定数据类型精度的,比如:int, float

一、限定float的取值范围

highp (-2^62, 2^62);

mediump (-2^14, 2^14);

lowp (-2,2);

二、限定int的取值范围

highp (-2^16, 2^16);

mediump (-2^10, 2^10);

lowp (-2^8, 2^8);

注意:字符常量和布尔类型没有精度限定符.

三、用法

即可单独限定某一数据类型,也可放在Vertex和Fragment shader 源码开始处统一限定数据类型.

1、单独限定某一数据类型

highp vec4 position;
varying lowp vec4 color;
mediump float specularExp;

2、统一限定

//precision 可以用来确定默认精度修饰符
//precision-qualifier可以是lowp、mediump、highp。任何其他类型和修饰符都会引起错误
precision precision-qualifier type;

如果type是float类型,那么该精度(precision-qualifier)将适用于所有五精度修饰符的浮点数声明(标量,向量,矩阵)

如果type是int类型,那么该精度(precision-qualifier)将适用于所有无精度修饰符的整型数声明(标量,向量)

包括全局变量声明,函数返回值声明,函数参数声明,和本地变量声明等。没有声明精度修饰符的变量将使用和它最近的precision语句中的精度。

precision highp float;
precision mediump int;

在 Vertex Shader 中,如果没有默认精度,则float和int精度都为highp;

在Fragment Shader中,float没有默认精度,所以必须在Fragment Shader中为float指定一个默认精度或为每个float变量指定精度。

3、预定义精度

在顶点语言中有如下预定义的全局默认精度语句

precision highp float;

precision highp int;

precision lowp sampler2D;

precision lowp samplerCube;

在片元语言中有如下预定义的全局默认精度语句

precision mediump int;

precision lowp sampler2D;

precision lowp samplerCube;

片元语言没有默认的浮点数精度修饰符。因此,对于浮点数,浮点数向量和矩阵变量声明,要么声明必须包含一个精度修饰符,要么默认的精度修饰符在之前已经被声明过了。


行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.