OpenGL索引绘图(GLSL实现金字塔纯色填充和纹理+颜色混合填充)

471 阅读7分钟

GLSL实现

效果图

纯色填充

纹理+颜色混合填充

流程

0. 准备工作

  • 头文件引入
#import "GLESMath.h"
#import "GLESUtils.h"
#import <OpenGLES/ES2/gl.h>
  • 扩展属性
@interface HTView()

//CAEAGLLayer
@property(nonatomic,strong)CAEAGLLayer *htEagLayer;

//上下文
@property(nonatomic,strong)EAGLContext *htContext;

//渲染缓冲区id
@property(nonatomic,assign)GLuint htColorRenderBuffer;

//帧缓冲区id
@property(nonatomic,assign)GLuint htColorFrameBuffer;

//着色器程序id
@property(nonatomic,assign)GLuint htProgram;

//顶点缓存区id
@property (nonatomic , assign) GLuint  htVertices;

  • 内部属性
@implementation HTView
{
    //x轴方向的旋转弧度
    float xDegree;

    //y轴方向的旋转弧度
    float yDegree;

    //z轴方向的旋转弧度
    float zDegree;

    //是否围绕x轴旋转
    BOOL bX;

    //是否围绕y轴旋转
    BOOL bY;

    //是否围绕z轴旋转
    BOOL bZ;

    //定时器
    NSTimer* htTimer;
}

流程1-5参考: 编译链接自定义的着色器(shader)流程

6.开始绘制 [self renderLayer]

  • 6.1.设置清屏颜色,清除缓冲区
	    //1.设置清屏颜色
    glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
        //清除缓冲区
    glClear(GL_COLOR_BUFFER_BIT);
  • 6.2.设置视口大小
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);
  • 6.3.读顶点着色器程序和片元着色器程序
NSString *vertFile = [[NSBundle mainBundle]pathForResource:@"shaderv" ofType:@"vsh"];
NSString *fragFile = [[NSBundle mainBundle]pathForResource:@"shaderf" ofType:@"fsh"];
  • 6.4.判断self.htProgram是否存在,存在则清空其文件
//4.判断self.htProgram是否存在,存在则清空其文件
  if(self.htProgram){
        glDeleteProgram(self.htProgram);
        self.htProgram = 0;
    }
  • 6.5.加载程序到htProgram中来
self.htProgram = [self loadShader:vertFile withFrag:fragFile];
  • 6.6.链接
glLinkProgram(self.htProgram);
  • 6.7.获取链接状态
	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;
    }

    NSLog(@"Programe Link Success!");
  • 6.8.使用program
glUseProgram(self.htProgram);
  • 6.9.创建顶点数组 和 索引数组
//(1)顶点数组 前3顶点值(x,y,z),后3位颜色值(RGB)
    GLfloat attrArr[] =
    {
    -0.5f, 0.5f, 0.0f,      1.0f, 0.0f, 1.0f,      //左上0
    0.5f, 0.5f, 0.0f,       1.0f, 0.0f, 1.0f,      //右上1
    0.5f, -0.5f, 0.0f,      1.0f, 1.0f, 1.0f,     //右下2
    -0.5f, -0.5f, 0.0f,     1.0f, 1.0f, 1.0f,     //左下3
    0.0f, 0.0f, 1.0f,       0.0f, 1.0f, 0.0f,     //顶点4
    };

// 索引数组
    GLuint indices[] =
    {
    0, 1, 2,
    0, 2, 3,
    0, 4, 1,
    1, 4, 2,
    2, 4, 3,
    3, 4, 0,
    };
  • 6.10.处理顶点数据
 //.判断顶点缓存区是否为空,如果为空则申请一个缓存区标识符
    if (self.htVertices == 0) {
        glGenBuffers(1, &_htVertices);
    }
    glBindBuffer(GL_ARRAY_BUFFER, _htVertices);
    glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
  • 6.11.将顶点数据通过htPrograme传递到顶点着色程序的position
//1).glGetAttribLocation,用来获取vertex attribute的入口的.
	GLuint position = glGetAttribLocation(self.htProgram, "position");
//2).设置合适的格式从buffer里面读取数据
	glEnableVertexAttribArray(position);
//3).最后数据是通过glVertexAttribPointer传递过去的。
	glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*6, NULL);
  • 6.12.处理顶点颜色值
//(1).glGetAttribLocation,用来获取vertex attribute的入口的.
//注意:第二参数字符串必须和shaderv.glsl中的输入变量positionColor保持一致
    GLuint positionColor = glGetAttribLocation(self.htProgram, "positionColor");
//(2).设置合适的格式从buffer里面读取数据
	glEnableVertexAttribArray(positionColor);
//(3).设置读取方式
	glVertexAttribPointer(positionColor, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*6, NULL);
  • 6.13.找到htProgram中的projectionMatrix和 modelViewMatrix
GLuint projectionMatrixID = glGetUniformLocation(self.htProgram, "projectionMatrix");
GLuint modelViewMatrixID = glGetUniformLocation(self.htProgram, "modelViewMatrix");
  • 6.14.设置投影矩阵
//创建4 * 4投影矩阵
	KSMatrix4 _projectionMatrix
//获取单元矩阵
	ksMatrixLoadIdentity(&_projectionMatrix);
//计算纵横比
	float width = self.frame.size.width;
    float height = self.frame.size.height;
    float aspect = width / height; //长宽比
//获取透视矩阵
	ksPerspective(&_projectionMatrix, 30.0, aspect, 5.0f, 20.0f);
//将投影矩阵传递到顶点着色器
	glUniformMatrix4fv(projectionMatrixID, 1, GL_FALSE, (GLfloat*)&_projectionMatrix.m[0][0]);
  • 6.15. 设置模型视图矩阵
//(1)创建一个4 * 4 矩阵,模型视图矩阵,获取单元矩阵
	KSMatrix4 _modelViewMatrix;
//获取单元矩阵
    ksMatrixLoadIdentity(&_modelViewMatrix);
//(2)平移,z轴负方向平移10
	ksTranslate(&_modelViewMatrix, 0.0, 0.0, -10.0);
//(3)创建一个4 * 4 矩阵,旋转矩阵,初始化为单元矩阵
	KSMatrix4 _rotationMatrix;
//初始化为单元矩阵
    ksMatrixLoadIdentity(&_rotationMatrix);
//(4)旋转
	ksRotate(&_rotationMatrix, xDegree, 1.0, 0.0, 0.0); //绕X轴
    ksRotate(&_rotationMatrix, yDegree, 0.0, 1.0, 0.0); //绕Y轴
    ksRotate(&_rotationMatrix, zDegree, 0.0, 0.0, 1.0);
//(5)把变换矩阵相乘
	ksMatrixMultiply(&_modelViewMatrix, &_rotationMatrix, &_modelViewMatrix);
//(6)将模型视图矩阵传递到顶点着色器
	glUniformMatrix4fv(modelViewMatrixID, 1, GL_FALSE, (GLfloat*)&_modelViewMatrix.m[0][0]);
  • 6.16.开启正背面剔除
glEnable(GL_CULL_FACE);
  • 6.17.使用索引绘图
glDrawElements(GL_TRIANGLES, sizeof(indices) / sizeof(indices[0]), GL_UNSIGNED_INT, indices);
  • 6.18.提交渲染到本地窗口系统显示
[self.htContext presentRenderbuffer:GL_RENDERBUFFER];
  • 6.19 shaderv.vsh

attribute vec4 position;
attribute vec4 positionColor;

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

varying lowp vec4 varyColor;

void main()
{
    varyColor = positionColor;

    vec4 vPos;

        //4*4 * 4*4 * 4*1
    vPos = projectionMatrix * modelViewMatrix * position;

        //ERROR
        //vPos = position * modelViewMatrix  * projectionMatrix ;
    gl_Position = vPos;
}

  • 6.20 shaderf.fsh
varying lowp vec4 varyColor;
void main()
{
    gl_FragColor = varyColor;
}

点击事件和定时器控制旋转

#pragma mark - XYClick
- (IBAction)XClick:(id)sender {

        //开启定时器
    if (!htTimer) {
        htTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(reDegree) userInfo:nil repeats:YES];
    }
        //更新的是X还是Y
    bX = !bX;

}
- (IBAction)YClick:(id)sender {

        //开启定时器
    if (!htTimer) {
        htTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(reDegree) userInfo:nil repeats:YES];
    }
        //更新的是X还是Y
    bY = !bY;
}
- (IBAction)ZClick:(id)sender {

        //开启定时器
    if (!htTimer) {
        htTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(reDegree) userInfo:nil repeats:YES];
    }
        //更新的是X还是Y
    bZ = !bZ;
}

-(void)reDegree
{
        //如果停止X轴旋转,X = 0则度数就停留在暂停前的度数.
        //更新度数
    xDegree += bX * 5;
    yDegree += bY * 5;
    zDegree += bZ * 5;
        //重新渲染
    [self renderLayer];

}

纹理+颜色混合填充逻辑

在纯色填充上修改shaderf.fsh和shaderv.vsh ->绘制函数中需要增加 设置纹理坐标映射+ 载入纹理

shaderv.vsh


attribute vec4 position;
attribute vec4 positionColor;
attribute vec2 textCoord;



uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

varying lowp vec4 varyColor;
varying lowp vec2 varyTextCoord;

void main()
{
    varyColor = positionColor;
    varyTextCoord = textCoord;

    vec4 vPos;

        //4*4 * 4*4 * 4*1
    vPos = projectionMatrix * modelViewMatrix * position;

        //ERROR
        //vPos = position * modelViewMatrix  * projectionMatrix ;
    gl_Position = vPos;
}

shaderf.fsh

precision highp float;

varying lowp vec4 varyColor;
varying lowp vec2 varyTextCoord;

uniform sampler2D colorMap;

void main ()
{
    vec4 weakMask = texture2D(colorMap, varyTextCoord);
    vec4 mask = varyColor;
    float alpha = 0.8;

    vec4 tempColor = mask * (1.0 - alpha) + weakMask * alpha;
    gl_FragColor = tempColor;
}

绘制流程修改

  • 9.创建顶点数组 和 索引数组 (顶点数组中加入了纹理坐标映射,注意映射的纹理映射的正反面)
	GLfloat attrArr[] =
    {
    -0.5f, 0.5f, 0.0f,      1.0f, 0.0f, 1.0f,   0.0f, 1.0f,        //左上0
    0.5f, 0.5f, 0.0f,       1.0f, 0.0f, 1.0f,   1.0f, 1.0f,        //右上1
    0.5f, -0.5f, 0.0f,      1.0f, 1.0f, 1.0f,   1.0f, 0.0f,        //右下2
    -0.5f, -0.5f, 0.0f,     1.0f, 1.0f, 1.0f,   0.0f, 0.0f,        //左下3
    0.0f, 0.0f, 1.0f,       0.0f, 1.0f, 0.0f,   0.5f, 0.5f,        //顶点4
    };

        // 索引数组
    GLuint indices[] =
    {
    0, 1, 2,
    0, 2, 3,
    0, 4, 1,
    1, 4, 2,
    2, 4, 3,
    3, 4, 0,
    };

11.将顶点数据通过htPrograme传递到顶点着色程序的position(坐标读取时两次连续读取之间的间隔改为8)

	//1).glGetAttribLocation,用来获取vertex attribute的入口的.
		 GLuint position = glGetAttribLocation(self.htProgram, "position");
	//2).设置合适的格式从buffer里面读取数据
		glEnableVertexAttribArray(position);
	//3).最后数据是通过glVertexAttribPointer传递过去的。
		glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*8, NULL);
  • 11.2 处理纹理数据
	//1).glGetAttribLocation,用来获取vertex attribute的入口的.
		 //注意:第二参数字符串必须和shaderv.vsh中的输入变量:textCoord保持一致
    GLuint textCoor = glGetAttribLocation(self.htProgram, "textCoord");
	//2).设置合适的格式从buffer里面读取数据
		glEnableVertexAttribArray(position);
	//3).最后数据是通过glVertexAttribPointer传递过去的。
		glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*8, NULL);
  • 12.处理顶点颜色值(坐标读取时两次连续读取之间的间隔改为8)
	//(1).glGetAttribLocation,用来获取vertex attribute的入口的.
		//注意:第二参数字符串必须和shaderv.glsl中的输入变量:positionColor保持一致
    GLuint positionColor = glGetAttribLocation(self.htProgram, "positionColor");
	//(2).设置合适的格式从buffer里面读取数据
		glEnableVertexAttribArray(textCoor);

	//(3).设置读取方式
		glVertexAttribPointer(textCoor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*8, (float *)NULL + 6);
  • 12.2 加载纹理和设置 纹理采样器 sampler2D
    • 加载纹理 [self setupTexture:@"stone"];
//加载纹理
-(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;
}
* 设置纹理采样器 sampler2D
glUniform1f(glGetAttribLocation(self.htProgram, "colorMap"), 0);
  • 开启混合
glEnable(GL_BLEND);

完整代码实现:

纯色填充

纹理+颜色混合填充