思维导图
初始化View
1 | -(id)initWithCoder:(NSCoder *)aDecoder |
layoutSubviews
1 | -(void)layoutSubviews |
若没有进行过初始化
initialized 绘制”加油!”
initGL
1 | -(BOOL)initGL |
手写着色程序 vsh&fsh
vsh : 顶点着色器
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//顶点
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;
}fsh : 片元着色器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//获取纹理
uniform sampler2D texture;
/*
sampler2D,中的2D,表示这是一个2D纹理。我们也可以使用1D\3D或者其他类型的采样器。我们总是
把这个值设置为0。来指示纹理单元0.
*/
//获取从顶点程序传递过来的颜色
//lowp,精度
varying lowp vec4 color;
void main()
{
//将颜色和纹理组合 是相乘!!!!
gl_FragColor = color * texture2D(texture, gl_PointCoord);
}
加载shader
即加载vsh和fsh1
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- (void)setupShaders
{
for (int i = 0; i < NUM_PROGRAMS; i++) {
//读取顶点着色程序
char *vsrc = readFile(pathForResource(program[i].vert));
char *fsrc = readFile(pathForResource(program[i].frag));
//将char->NSString 对象
NSString *vsrcStr = [[NSString alloc]initWithBytes:vsrc length:strlen(vsrc)-1 encoding:NSUTF8StringEncoding];
NSString *fsrcStr = [[NSString alloc]initWithBytes:fsrc length:strlen(fsrc)-1 encoding:NSUTF8StringEncoding];
//打印着色程序中的代码
NSLog(@"vsrc:%@",vsrcStr);
NSLog(@"fsrc:%@",fsrcStr);
//attribute
GLsizei attribCt = 0;
//创建字符串数组【1】
GLchar *attribUsed[NUM_ATTRIBS];
//
GLint attrib[NUM_ATTRIBS];
//attribute 变量名称-inVertex(point.vsh)
GLchar *attribName[NUM_ATTRIBS] = {
"inVertex",
};
//uniform变量名称 "MVP", "pointSize", "vertexColor", "texture",
const GLchar *uniformName[NUM_UNIFORMS] = {
"MVP", "pointSize", "vertexColor", "texture",
};
//遍历attribute
for (int j = 0; j < NUM_ATTRIBS; j++)
{
//strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL。
//判断,attribute 变量,是否存在顶点着色器程序中。point.vsh
if (strstr(vsrc, attribName[j]))
{
//attribute个数
attrib[attribCt] = j;
//使用的attribute的名称
attribUsed[attribCt++] = attribName[j];
}
}
//利用shaderUtil.c封装好的方法对programe 进行创建、链接、生成Programe
/*
参数1:vsrc,顶点着色器程序
参数2:fsrc,片元着色器程序
参数3:attribute变量个数
参数4:attribute变量名称
参数5:当前attribute位置
参数6:uniform名字
参数7:program的uniform地址
参数8:program程序地址
*/
glueCreateProgram(vsrc, fsrc,
attribCt, (const GLchar **)&attribUsed[0], attrib,
NUM_UNIFORMS, &uniformName[0], program[i].uniform,
&program[i].id);
//释放vsrc,fsrc指针
free(vsrc);
free(fsrc);
// 设置常数、初始化Uniform
//当前的i == 0
if (i == PROGRAM_POINT)
{
//使用proram program[0].id 等价,以往课程例子中的GLuint program;
glUseProgram(program[PROGRAM_POINT].id);
//为当前程序对象指定uniform变量值
/*
为当前程序对象指定uniform变量MVP赋值
void glUniform1f(GLint location, GLfloat v0);
参数1:location,指明要更改的uniform变量的位置 MVP
参数2:v0,指明在指定的uniform变量中要使用的新值
program[0].uniform[3] = 0
等价于,vsh顶点着色器程序中的uniform变量,MVP = 0;
其实简单理解就是做了一次初始化,清空这个mat4矩阵
*/
glUniform1i(program[PROGRAM_POINT].uniform[UNIFORM_TEXTURE], 0);
// 投影矩阵
/*
投影分为正射投影和透视投影,我们可以通过它来设置投影矩阵来设置视域,在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的数组指针
*/
glUniformMatrix4fv(program[PROGRAM_POINT].uniform[UNIFORM_MVP], 1, GL_FALSE, MVPMatrix.m);
//点的大小 pointSize
/*
为当前程序对象指定uniform变量pointSize赋值
program[0].uniform[pointSize] = 纹理宽度/画笔比例
*/
glUniform1f(program[PROGRAM_POINT].uniform[UNIFORM_POINT_SIZE], brushTexture.width / kBrushScale);
//笔刷颜色
/*
为当前程序对象指定uniform变量vertexColor赋值
program[0].uniform[vertexColor] = 画笔颜色
void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
功能:为当前程序对象指定uniform变量值
参数1:location 指明要更改的uniform变量的位置 vertexColor
参数2:count 指定将要被修改的4分量的数量
参数3:value ,指向将要用于更新uniform变量vertexColor的值
*/
glUniform4fv(program[PROGRAM_POINT].uniform[UNIFORM_VERTEX_COLOR], 1, brushColor);
}
}
glError();
}
加载画笔纹理
1 | // 创建一个纹理图片 |
绘制已存储顶点数据的”加油”
1 | -(void)paint |
在两点之间绘制线条
1 | //在用户触摸的地方绘制屏幕上的线条 |
已经进行过初始化
调整图层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//调整图层
- (BOOL)resizeFromLayer:(CAEAGLLayer *)layer
{
//根据当前图层大小分配颜色缓冲区
glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);
//绑定一个Drawable对象存储到一个OpenGL ES渲染缓存对象。
/*
创建一个渲染,可以呈现到屏幕上,你将渲染然后分配共享存储通过调用此方法。这个方法的调用替换通常给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的数组指针
*/
glUniformMatrix4fv(program[PROGRAM_POINT].uniform[UNIFORM_MVP], 1, GL_FALSE, MVPMatrix.m);
//更新视口
glViewport(0, 0, backingWidth, backingHeight);
return YES;
}
修改画笔颜色
1 | - (void)setBrushColorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue |
清理屏幕
1 | //清空屏幕 |
开始绘制
绘制任意图形,线条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
//点击屏幕开始
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//获取绘制的bounds
CGRect bounds = [self bounds];
//获取当前的点击touch
UITouch* touch = [[event touchesForView:self] anyObject];
//设置为firstTouch -> yes
firstTouch = YES;
//获取当前点击的位置信息,x,y
_location = [touch locationInView:self];
//y = height - y
_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
firstTouch = NO;
//_previousLocation = 获取上一个顶点
_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;
}
//获取_previousLocation 和 _location 2个顶点,绘制成线条
[self renderLineFromPoint:_previousLocation toPoint:_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 toPoint:_location];
}
}
-(void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"Touch Cancelled");
}
-(BOOL)canBecomeFirstResponder
{
return YES;
}