OpenGL ES案例学习-画板

发布时间:2024年01月01日
#import "PaintView.h"
#import <QuartzCore/QuartzCore.h>
#import <GLKit/GLKit.h>
#import <OpenGLES/EAGLDrawable.h>
#import "debug.h"
#import "shaderUtil.h"
#import "fileUtil.h"
//画笔透明度
#define kBrushOpacity        (1.0 / 2.0)
//画笔每一笔,有几个点!
#define kBrushPixelStep        2
//画笔的比例
#define kBrushScale            2

enum {
    PROGRAM_POINT, //0,
    NUM_PROGRAMS   //1,有几个程序
};


enum {
    UNIFORM_MVP,         //0
    UNIFORM_POINT_SIZE,  //1
    UNIFORM_VERTEX_COLOR,//2
    UNIFORM_TEXTURE,     //3
    NUM_UNIFORMS         //4
};

enum {
    ATTRIB_VERTEX, //0
    NUM_ATTRIBS//1
};

//定义一个结构体
typedef struct {
    //vert,frag 指向顶点、片元着色器程序文件
    char *vert, *frag;
    //创建uniform数组,4个元素,数量由你的着色器程序文件中uniform对象个数
    GLint uniform[NUM_UNIFORMS];
    
    GLuint infoId;
} programInfo_t;

//注意数据结构
/*
  programInfo_t 结构体,相当于数据类型
  program 数组名,相当于变量名
  NUM_PROGRAMS 1,数组元素个数

  "point.vsh"和"point.fsh";2个着色器程序文件名是作为program[0]变量中
  vert,frag2个字符指针的值。
  uniform 和 id 是置空的。
 
 */

programInfo_t program[NUM_PROGRAMS] = {
    { "pointv.vsh",   "pointf.fsh" },
};

//纹理
typedef struct {
    GLuint textureId;
    GLsizei width, height;
} textureInfo_t;

@implementation DrawPoint
- (instancetype)initWithCgpoint:(CGPoint)point {
    if (self = [super init]) {
        self.mX = [NSNumber numberWithFloat:point.x];
        self.mY = [NSNumber numberWithFloat:point.y];
    }
    return self;
}
@end

@interface PaintView()
{
    //后备缓冲区的像素尺寸
    GLint backingWidth;
    GLint backingHeight;
    
    EAGLContext *context;
    
    //缓存区frameBuffer\renderBuffer
    GLuint viewRenderBuffer,viewFrameBuffer;
    
    
    //画笔纹理,画笔颜色
    textureInfo_t brushTexture;
    GLfloat brushColor[4];
    
    //是否第一次点击
    Boolean firstTouch;
    //是否需要清屏
    Boolean needsErase;
    
    //shader object 顶点Shader、片元Shader、Program
    GLuint vertexShader;
    GLuint fragmentShader;
    GLuint shaderProgram;
    
    //VBO 顶点Buffer
    GLuint vboId;
    
    //是否初始化
    BOOL initialized;
    
    //点
    NSMutableArray <DrawPoint *>* pointArr;
    
    
}

@end

@implementation PaintView
- (instancetype) initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame: frame]) {
        CAEAGLLayer *eagLayer = (CAEAGLLayer *)self.layer;
        eagLayer.opaque = YES;
        eagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO],kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat, nil];
        context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
        if (context == nil || ![EAGLContext setCurrentContext:context]) {
            return nil;
        }
        self.contentScaleFactor =  [[UIScreen mainScreen] scale];
        needsErase = YES;
    }
    return  self;
}
- (void)layoutSubviews {
    [EAGLContext setCurrentContext:context];
    if (!initialized) {
        initialized = [self initGL];
    } else {
        [self resizeFromLayer:(CAEAGLLayer *)self.layer];
    }
    if (needsErase) {
        [self clearScren];
        needsErase = NO;
    }
}

-(BOOL)initGL {
    //1.
    glGenRenderbuffers(1, &viewRenderBuffer);
    glGenFramebuffers(1, &viewFrameBuffer);
    
    //绑定
    glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, viewFrameBuffer);
    //
    [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(id<EAGLDrawable>) self.layer];
    //attach
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, viewRenderBuffer);
    // 获取绘制缓存区的宽和高
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);
    
    //检查framebuffer 状态
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"make complete framebuffer failed");
        return NO;
    }
    glViewport(0, 0, backingWidth, backingHeight);
    
    /// 顶点缓冲区
    glGenBuffers(1, &vboId);
    // 加载画笔
    brushTexture = [self textureFromName:@"Particle.png"];

    // 加载shader
    [self setUpShader];
    //点模糊
    glEnable(GL_BLEND);
    /*
     GL_ONE_MINUS_CONSTANT_ALPHA
     在 OpenGL中,GL_ONE_MINUS_CONSTANT_ALPHA是一个混合因子,它的值为1-const ant的alpha分量。
     在混合方程中,GL_ONE_MINUS_CONSTANT_ALPHA等因子允许在混合过程中引入一个常量混合颜色。这些因子可以用于设置不同通道的颜色混合选项,从而实现丰富的颜色效果。

     
     */
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    ///回放路径
    NSString *path = [[NSBundle mainBundle] pathForResource:@"abc" ofType:@"string"];
    NSString *str = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    pointArr = [[NSMutableArray alloc] init];
    NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil];
    
    //便利json
    for (NSDictionary *dict in jsonArray) {
        DrawPoint *point = [[DrawPoint alloc] initWithCgpoint:CGPointMake([[dict objectForKey:@"mX"] floatValue], [[dict objectForKey:@"mY"] floatValue])];
        [pointArr addObject:point];
    }
    [self performSelector:@selector(paint) withObject:nil afterDelay:0.5];
    return  YES;
}
-(void)paint {
    
    //
    for (int i = 0; i < pointArr.count - 1; i+=2) {
        DrawPoint *p1 = pointArr[i];
        DrawPoint *p2 = pointArr[i+1];
        
        CGPoint p3,p4;
        p3.x = p1.mX.floatValue;
        p3.y = p1.mY.floatValue;
        p4.x = p2.mX.floatValue;
        p4.y = p2.mY.floatValue;
        //将亮点链接成线
        [self renderLineFromPoint:p3 end:p4];
    }
}
-(void)renderLineFromPoint:(CGPoint)start  end:(CGPoint)end {
    // 顶点缓冲区
    static GLfloat *vertexBuffer = NULL;
    // 暂时顶点数
    static NSUInteger vertexMax = 64;
    
    //顶点个数
    NSUInteger vertexCount = 0, count;
    // 普通点-<像素点
    CGFloat scale = self.contentScaleFactor;
    start.x *= scale;
    start.y *= scale;
    end.x *= scale;
    end.y *= scale;
    if (vertexBuffer == NULL) {
        // 开辟顶点缓冲区
        vertexBuffer = malloc(vertexMax * 2  * sizeof(GLfloat));
        
    }
    
    /// 亮点之间的距离 statt end
    float seq = sqrtf((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y));
    //kBrushPixelStep 值越大笔触越细腻
    NSInteger pointCount = ceilf(seq/kBrushPixelStep);
    /// 取最大点数
    count = MAX(pointCount, 1);
    // 遍历顶点
    for (int i = 0; i < count; i++) {
        /// 当前订单个数大于开辟的空间就需要扩容
        if (vertexCount >= vertexMax) {
            vertexMax = 2 * vertexMax;
            vertexBuffer = realloc(vertexBuffer, vertexMax * 2 * sizeof(GLfloat));
        }
        // start end 距离之间 计算出count个数 并存储在vertexBuffer 中
        vertexBuffer[2*vertexCount+0] = start.x + (end.x - start.x) * ((GLfloat)i/ (GLfloat)count);
        vertexBuffer[2*vertexCount+1] = start.y + (end.y - start.y) * ((GLfloat)i/ (GLfloat)count);
        vertexCount++;
    }
    
    glBindBuffer(GL_ARRAY_BUFFER, vboId);
    glBufferData(GL_ARRAY_BUFFER, vertexCount * 2 * sizeof(GLfloat), vertexBuffer, GL_DYNAMIC_DRAW);
    
    //gl
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0);
    glUseProgram(program[PROGRAM_POINT].infoId);
    glDrawArrays(GL_POINTS, 0, (int)vertexCount);
    ///显示
    glBindBuffer(GL_RENDERBUFFER, viewRenderBuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER];
}


-(textureInfo_t)textureFromName:(NSString *)name {
    textureInfo_t texture;
    CGContextRef brushContext;
    GLubyte    *brushData;
    size_t width,height;
    CGImageRef brushImage;
    GLuint textId;
    brushImage = [UIImage imageNamed:name].CGImage;
    width = CGImageGetWidth(brushImage);
    height = CGImageGetHeight(brushImage);
    
    brushData = (GLubyte *)calloc(width * height * 4, sizeof(GLubyte));
    
    brushContext = CGBitmapContextCreate(brushData, width, height, 8, width * 4, CGImageGetColorSpace(brushImage), kCGImageAlphaPremultipliedLast);
    
    CGContextDrawImage(brushContext, CGRectMake(0.0, 0.0f, (CGFloat)width, (CGFloat)height), brushImage);

    CGContextRelease(brushContext);
    
    glGenTextures(1, &textId);
    glBindTexture(GL_TEXTURE_2D, textId);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    /// 获取纹理
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)width,(int)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, brushData);
    free(brushData);
    texture.textureId = textId;
    texture.width = (int)width;
    texture.height =  (int)height;
    return texture;
}
-(BOOL)resizeFromLayer:(CAEAGLLayer *)layer {
    
    //根据当前图层大小分配颜色缓冲区
    //绑定一个Drawable对象存储到一个OpenGL ES渲染缓存对象。
    glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);
    /*
     创建一个渲染,可以呈现到屏幕上,你将渲染然后分配共享存储通过调用此方法。这个方法的调用替换通常给glrenderbufferstorage。缓存的存储分配了这个方法以后可以显示一个回调presentrenderbuffer:
     - (BOOL)renderbufferStorage:(NSUInteger)target fromDrawable:(id<EAGLDrawable>)drawable;
     为绘制缓冲区分配存储区,此处将CAEAGLLayer的绘制存储区作为绘制缓冲区的存储区
     参数1:OpenGL ES的结合点为当前绑定的渲染。这个参数的值必须gl_renderbuffer(或gl_renderbuffer_oes在OpenGL ES 1.1语境)
     参数2:对象管理数据存储区中的渲染。在iOS中,这个参数的值必须是一个CAEAGLLayer对象
     
     */
    [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];
    获取绘制缓存区的像素宽度 --将绘制缓存区像素宽度存储在backingWidth
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
    获取绘制缓存区的像素高度--将绘制缓存区像素高度存储在backingHeight
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);
    //检查GL_FRAMEBUFFER缓存区状态
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"Make compelete framebuffer object failed!%x",glCheckFramebufferStatus(GL_FRAMEBUFFER));
        return NO;
    }
    //更新投影矩阵、模型视图矩阵
    // 投影矩阵
    /*
     投影分为正射投影和透视投影,我们可以通过它来设置投影矩阵来设置视域,在OpenGL中,默认的投影矩阵是一个立方体,即x y z 分别是-1.0~1.0的距离,如果超出该区域,将不会被显示
     
     正射投影(orthographic projection):GLKMatrix4MakeOrtho(float left, float righ, float bottom, float top, float nearZ, float farZ),该函数返回一个正射投影的矩阵,它定义了一个由 left、right、bottom、top、near、far 所界定的一个矩形视域。此时,视点与每个位置之间的距离对于投影将毫无影响。
     
     透视投影(perspective projection):GLKMatrix4MakeFrustum(float left, float right,float bottom, float top, float nearZ, float farZ),该函数返回一个透视投影的矩阵,它定义了一个由 left、right、bottom、top、near、far 所界定的一个平截头体(椎体切去顶端之后的形状)视域。此时,视点与每个位置之间的距离越远,对象越小。
     
     在平面上绘制,只需要使正投影就可以了!!
     */
    GLKMatrix4 projectionMatrix = GLKMatrix4MakeOrtho(0, backingWidth, 0, backingHeight, -1, 1);
    // //模型矩阵,比如你要平移、旋转、缩放,就可以设置在模型矩阵上
    //这里不需要这些变换,则使用单元矩阵即可,相当于1 * ? = ?
    GLKMatrix4 modelViewMatrix = GLKMatrix4Identity;
    //矩阵相乘,就2个矩阵的结果交给MVPMatrix
    GLKMatrix4 MVPMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix);
    /*
     void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
     功能:为当前程序对象指定uniform变量值
     参数1:location 指明要更改的uniform变量的位置 MVP
     参数2:count 指定将要被修改的矩阵的数量
     参数3:transpose 矩阵的值被载入变量时,是否要对矩阵进行变换,比如转置!
     参数4:value ,指向将要用于更新uniform变量MVP的数组指针
     */
    /// 传递到vsh
    glUniformMatrix4fv(program[PROGRAM_POINT].uniform[UNIFORM_MVP], 1, GL_FALSE, MVPMatrix.m);

    //更新视口
    glViewport(0, 0, backingWidth, backingHeight);

    return YES;
}
-(void)setUpShader{
    for (int i = 0; i < NUM_PROGRAMS; i++) {
        char *vsrc = readFile(pathForResource(program[i].vert));
        char *fsrc = readFile(pathForResource(program[i].frag));
        
//        NSString *vsrcString = [[NSString alloc] initWithBytes:vsrc length:strlen(vsrc) - 1 encoding:NSUTF8StringEncoding];
//        NSString *fsrcString = [[NSString alloc] initWithBytes:fsrc length:strlen(fsrc) - 1 encoding:NSUTF8StringEncoding];
        GLsizei attribCt = 0;
        GLchar *attribUsed[NUM_ATTRIBS];
        GLint attrib[NUM_ATTRIBS];
        GLchar *attribName[NUM_ATTRIBS] = {"inVertex"};
        GLchar *uniformName[NUM_UNIFORMS] = {"MVP","pointSize","vertexColor","texture"};
        for (int j = 0; j < NUM_ATTRIBS; j++) {
            if (strstr(vsrc, attribName[j])) {
                attrib[attribCt] = j;
                attribUsed[attribCt++] = attribName[j];
            }
        }
        glueCreateProgram(vsrc, fsrc, attribCt, (const GLchar **) &attribUsed[0], attrib, NUM_UNIFORMS, (const GLchar **)&uniformName[0], program[i].uniform, &program[i].infoId);
        
        free(vsrc);
        free(fsrc);
        if (PROGRAM_POINT == i) {
            glUseProgram(program[PROGRAM_POINT].infoId);
            glUniform1i(program[PROGRAM_POINT].uniform[UNIFORM_TEXTURE], 0);
            /// 更新投影/模型视图矩阵 --正投影
            GLKMatrix4 projectionMatrix = GLKMatrix4MakeOrtho(0, backingWidth, 0, backingHeight, -1, 1);
            GLKMatrix4 modelviewMatrix = GLKMatrix4Identity;
            //
            GLKMatrix4 MVPMatrix = GLKMatrix4Multiply(projectionMatrix,modelviewMatrix);
            glUniformMatrix4fv(program[PROGRAM_POINT].uniform[UNIFORM_MVP], 1, GL_FALSE, MVPMatrix.m);
            ///点大小
            glUniform1f(program[PROGRAM_POINT].uniform[UNIFORM_POINT_SIZE], brushTexture.width/kBrushScale);
            
            /// 笔刷颜色
            glUniform4fv(program[PROGRAM_POINT].uniform[UNIFORM_VERTEX_COLOR], 1, brushColor);
        }
    }
}

- (void)setBrushColorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue {
    
    // 获取需要更新的颜色
    brushColor[0] = red * kBrushOpacity;
    brushColor[1] = green * kBrushOpacity;
    brushColor[2] = blue * kBrushOpacity;
    brushColor[3] = kBrushOpacity;
    
    // 判断是否初始化图层
    if (initialized) {
        glUseProgram(program[PROGRAM_POINT].infoId);
        // 将颜色通过uniform传递到顶点着色器
        glUniform4fv(program[PROGRAM_POINT].uniform[UNIFORM_VERTEX_COLOR], 1, brushColor);
    }

}
- (void)clearScren {
    //1.清空viewFrameBuffer
    glBindFramebuffer(GL_FRAMEBUFFER, viewFrameBuffer);
    glClearColor(0, 0, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    
    //2. viewRenderBuffer 重新渲染
    glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER];
    
    
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    CGRect bounds = [self bounds];
    UITouch *touch = [[event touchesForView:self] anyObject];
    firstTouch = YES;
    _location = [touch locationInView:self];
    
    _location.y = bounds.size.height - _location.y;
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    CGRect bounds = [self bounds];
    UITouch *touch = [[event touchesForView:self] anyObject];
    if (firstTouch) {
        firstTouch = NO;
        /// 获取上一次点击的位置
        _previousLocation = [touch previousLocationInView:self];
        _previousLocation.y = bounds.size.height - _previousLocation.y;
    } else {
        _location = [touch locationInView:self];
        _location.y = bounds.size.height - _location.y;
        
        _previousLocation = [touch previousLocationInView:self];
        _previousLocation.y = bounds.size.height - _previousLocation.y;

    }
    
    //p1 p2
    [self renderLineFromPoint:_previousLocation end:_location];
    
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    CGRect bounds = [self bounds];
    UITouch *touch = [[event touchesForView:self] anyObject];
    if (firstTouch) {
        firstTouch = NO;
        /// 获取上一次点击的位置
        _previousLocation = [touch previousLocationInView:self];
        _previousLocation.y = bounds.size.height - _previousLocation.y;
        [self renderLineFromPoint:_previousLocation end:_location];

    }
}

+(Class)layerClass
{
    return [CAEAGLLayer class];
}
-(void)dealloc
{
    //安全释放viewFrameBuffer、viewRenderBuffer、brushTexture、vboId、context
    if (viewFrameBuffer) {
        glDeleteFramebuffers(1, &viewFrameBuffer);
        viewFrameBuffer = 0;
    }
    
    if (viewRenderBuffer) {
        glDeleteRenderbuffers(1, &viewRenderBuffer);
        viewRenderBuffer = 0;
    }
    
    
    if (brushTexture.textureId) {
        glDeleteTextures(1, &brushTexture.textureId);
        brushTexture.textureId = 0;
    }
    if (vboId) {
        glDeleteBuffers(1, &vboId);
        vboId = 0;
    }
    
    if ([EAGLContext currentContext] == context) {
        [EAGLContext setCurrentContext:nil];
    }
}

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
}
*/

@end

初始化上下文

- (instancetype) initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame: frame]) {
        CAEAGLLayer *eagLayer = (CAEAGLLayer *)self.layer;
        eagLayer.opaque = YES;
        eagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO],kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat, nil];
        context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
        if (context == nil || ![EAGLContext setCurrentContext:context]) {
            return nil;
        }
        self.contentScaleFactor =  [[UIScreen mainScreen] scale];
        needsErase = YES;
    }
    return  self;
}

顶点着色器

//顶点
attribute vec4 inVertex;
//矩阵
uniform mat4 MVP;
//点的大小
uniform float pointSize;
//点的颜色
uniform lowp vec4 vertexColor;
//输出颜色
varying lowp vec4 color;
void main() {
    //顶点计算 = 矩阵 * 顶点
    gl_Position = MVP * inVertex;
    //修改顶点大小
    gl_PointSize = pointSize;
    //    1 * 3.0;
    
    //将通过uniform 传递进来的颜色,从顶点着色器程序传递到片元着色器
    color = vertexColor;
}

片元着色器

//获取纹理
uniform sampler2D texture;
/*
 sampler2D,中的2D,表示这是一个2D纹理。我们也可以使用1D\3D或者其他类型的采样器。我们总是
 把这个值设置为0。来指示纹理单元0.
 */
//获取从顶点程序传递过来的颜色
//lowp,精度
varying lowp vec4 color;
void main() {
    //将颜色和纹理组合 是相乘!!!!
    gl_FragColor = color * texture2D(texture,gl_PointCoord);
}

文章来源:https://blog.csdn.net/weixin_38432324/article/details/135321655
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。