主页 Learning OpenGLES Texture Rendering from Scratch
Post
Cancel

Learning OpenGLES Texture Rendering from Scratch

Preface

It’s been a long time since I used OpenGL. This post records my notes on learning GLSL code and its implementation.

Objective-C Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
#import "ViewController.h"
#import <GLKit/GLKit.h>

//vertex struct type
typedef struct {
    GLKVector3 positionCoord; // (x,y,z)
    GLKMatrix2 textureCoord; // (u, v)
    
} SenceVertex;


@interface ViewController ()

@property (nonatomic, assign) SenceVertex *vertices; //顶点数组
@property (nonatomic, strong) EAGLContext *context;

@end

@implementation ViewController

#pragma mark -
#pragma mark - override methods
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    [self commonInit];
}

#pragma mark -
#pragma mark - private methods
- (void)commonInit {
    //create the context, using version 2.0
    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    [EAGLContext setCurrentContext:self.context];
    
    //create the vertex array
    self.vertices = malloc(sizeof(SenceVertex) * 4); //4个顶点
    
    self.vertices[0] = (SenceVertex){{-1, 1, 0},{ 0, 1 }}; //左上角
    self.vertices[1] = (SenceVertex){{-1, -1, 0},{0 ,0}}; //左下角
    self.vertices[2] = (SenceVertex){{1, 1, 0},{1, 1}}; //右上角
    self.vertices[3] = (SenceVertex){{1, -1, 0},{1, 0}}; //右下角
    
    //create a layer for displaying the texture
    
    CAEAGLLayer *layer = [CAEAGLLayer layer];
    layer.frame = CGRectMake(0, 100, self.view.frame.size.width, self.view.frame.size.width);
    layer.contentsScale = [[UIScreen mainScreen] scale]; //设置缩放比例,不设置的话,纹理会失真
    
    [self.view.layer addSublayer:layer];
    
    //bind the texture to the output layer
    [self bindRenderLayer:layer];
    
    //load the texture
    NSString *imagePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"logo.avif"];
    UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
    GLuint textureID = [self createTextureWithImage:image];
    
    
    //set the viewport size
    
    glViewport(0, 0, self.drawableWidth, self.drawableHeight);
    
    //compile and link the shaders
    GLuint program = [self programWithShaderName:@"glsl"];
    glUseProgram(program);
    
    //get the parameters from the shader and pass data into them
    GLuint positionSlot = glGetAttribLocation(program, "Position"); //获取顶点着色器的位置
    GLuint textureCoordsSlot = glGetAttribLocation(program, "TextureCoords"); //获取顶点着色器中的纹理坐标

    GLuint textureSlot = glGetUniformLocation(program, "Texture"); //获取片元着色器纹理变量

    //pass the texture ID to the shader program
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textureID);
    glUniform1i(textureSlot, 0); // 将textureSlot 赋值为 0, 而 0 与 GL_TEXTURE0 对应,这里如果写1,就是GL_TEXTURE1
    
    //create the vertex buffer
    GLuint vertexBuffer;
    glGenBuffers(1, &vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    GLsizeiptr bufferSizeBytes = sizeof(SenceVertex) * 4;
    glBufferData(GL_ARRAY_BUFFER, bufferSizeBytes, self.vertices, GL_STATIC_DRAW);
    
    //set the vertex data
    glEnableVertexAttribArray(positionSlot);
    glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, positionCoord));
    
    //set the texture data
    glEnableVertexAttribArray(textureCoordsSlot);
    glVertexAttribPointer(textureCoordsSlot, 2, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, textureCoord));
    
    //start drawing
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    
    //present the renderbuffer
    [self.context presentRenderbuffer:GL_RENDERBUFFER];
    
    //delete the vertex buffer
    glDeleteBuffers(1, &vertexBuffer);
    vertexBuffer = 0;
    
}

//bind the layer where the image will be rendered
- (void)bindRenderLayer:(CALayer <EAGLDrawable> *)layer {
    GLuint frameBuffer; //帧缓冲
    GLuint renderBuffer; //渲染缓冲

    //bind the renderbuffer to the output layer
    glGenRenderbuffers(1, &renderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
    [self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];
    
    //attach the renderbuffer to the framebuffer
    glGenFramebuffers(1, &frameBuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
}

//create a texture from an image
- (GLuint)createTextureWithImage:(UIImage *)image {
    //convert UIImage to CGImageRef
    CGImageRef cgImageRef = [image CGImage];
    GLuint width = (GLuint)CGImageGetWidth(cgImageRef);
    GLuint height = (GLuint)CGImageGetHeight(cgImageRef);
    CGRect rect = CGRectMake(0, 0, width, height);
    
    //draw the image
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    void *imageData = malloc(width * height * 4);
    CGContextRef context = CGBitmapContextCreate(imageData, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGContextTranslateCTM(context, 0, height);
    CGContextScaleCTM(context, 1.0f, -1.0f);
    CGColorSpaceRelease(colorSpace);
    CGContextClearRect(context, rect);
    CGContextDrawImage(context, rect, cgImageRef);

    //generate the texture
    GLuint textureID;
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_2D, textureID);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData); // 将图片数据写入纹理缓存
    
    //set how texels are mapped to pixels
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
    //unbind
    glBindTexture(GL_TEXTURE_2D, 0);
    
    //release memory
    CGContextRelease(context);
    free(imageData);
    
    return textureID;
}

//attach a vertex shader and a fragment shader to a shader program and return the program id
- (GLuint)programWithShaderName:(NSString *)shaderName {
    //compile the two shaders
    GLuint vertexShader = [self compileShaderWithName:shaderName type:GL_VERTEX_SHADER];
    GLuint fragmentShader = [self compileShaderWithName:shaderName type:GL_FRAGMENT_SHADER];
    
    //attach the shaders to the program
    GLuint program = glCreateProgram();
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    
    //link the program
    glLinkProgram(program);
    
    //check whether the link succeeded
    GLint linkSuccess;
    glGetProgramiv(program, GL_LINK_STATUS, &linkSuccess);
    if (linkSuccess == GL_FALSE) {
        GLchar messages[256];
        glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]);
        NSString *messageString = [NSString stringWithUTF8String:messages];
        NSAssert(NO, @"program链接失败:%@", messageString);
        exit(1);
    }
    return program;
}

//compile a shader and return the shader id
- (GLuint)compileShaderWithName:(NSString *)name type:(GLenum)shaderType {
    //find the shader file
    NSString *shaderPath = [[NSBundle mainBundle] pathForResource:name ofType:shaderType == GL_VERTEX_SHADER ? @"vsh" : @"fsh"]; // 根据不同的类型确定后缀名
    NSError *error;
    NSString *shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:&error];
    if (!shaderString) {
        NSAssert(NO, @"读取shader失败");
        exit(1);
    }
    
    //create a shader object
    GLuint shader = glCreateShader(shaderType);
    
    //get the shader content
    const char *shaderStringUTF8 = [shaderString UTF8String];
    int shaderStringLength = (int)[shaderString length];
    glShaderSource(shader, 1, &shaderStringUTF8, &shaderStringLength);
    
    //compile the shader
    glCompileShader(shader);
    
    //check whether the shader compiled successfully
    GLint compileSuccess;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compileSuccess);
    if (compileSuccess == GL_FALSE) {
        GLchar messages[256];
        glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]);
        NSString *messageString = [NSString stringWithUTF8String:messages];
        NSAssert(NO, @"shader编译失败:%@", messageString);
        exit(1);
    }
    
    return shader;
}


#pragma mark -
#pragma mark - public methods



#pragma mark -
#pragma mark - getters and setters
//get the renderbuffer width
- (GLint)drawableWidth {
    GLint backingWidth;
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
    
    return backingWidth;
}

//get the renderbuffer height
- (GLint)drawableHeight {
    GLint backingHeight;
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);
    
    return backingHeight;
}

#pragma mark -
#pragma mark - life cycle
- (void)dealloc {
    if ([EAGLContext currentContext] == self.context) {
        [EAGLContext setCurrentContext:nil];
    }
    //free the array memory in the struct, needs manual free
    if (_vertices) {
        free(_vertices);
        _vertices = nil;
    }
}

@end

Vertex Shader

attribute vec4 Position;
attribute vec2 TextureCoords;
varying vec2 TextureCoordsVarying;

void main (void) {
    gl_Position = Position;
    TextureCoordsVarying = TextureCoords;
}

Fragment Shader

precision mediump float;

uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;

void main (void) {
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    gl_FragColor = vec4(mask.rgb, 1.0);
}

Summary

Whenever I have time, I try to study graphics and image related technologies. This post carries a strong personal flavor — if it makes you uncomfortable, please close it quickly. This article is only for personal study notes, but you’re welcome to repost or share it within the scope of the license agreement. Please respect the copyright and keep the original link. Thank you for your understanding and cooperation. If you find this site helpful, you can subscribe via RSS. Thanks for your support!

Demo

Study reference

该博客文章由作者通过 CC BY 4.0 进行授权。