在OpenGL上绘制图像时,有时候需要控制图像形状,比如圆形,方形,裁处各种形状。
如下效果
最简单的方法就是在上面加个遮罩
其实遮罩也很麻烦,需要对View进行重绘。于是打算从根源出发,控制OpenGL的绘制区域。
矩形剪裁就很简单了,这里不多说了。主要说说圆形,圆形是通过无限细分来逐渐逼近圆形。细分等分越大,线条看起来越光滑。下面是10等分,51等分和100等分的效果图。
OpenGL中,纹理和顶点坐标是对应的。只改变定点坐标的话,得到剪裁后的结果会偏移。所以需要同时改变定点坐标和纹理坐标,来控制绘制区域。
其中,定点坐标的坐标系是以屏幕中心为原点的普通坐标系,最大长度为单位长度1。而纹理坐标有点不同,其原点在屏幕左下角,最大长度为单位长度1。
所以,顶点对应的纹理坐标只需要做相应的坐标转换。
基本关系是:
for(GLuint i = 0; i < num_vertex ; i++)
{
texcoord[0] = (cos(delta_angle * i) + 1.0) * 0.5;
texcoord[1] = (sin(delta_angle * i) + 1.0) * 0.5;
glTexCoord2fv(texcoord);
vertex[0] = cos(delta_angle * i) * radius;
vertex[1] = sin(delta_angle * i) * radius;
vertex[2] = 0.0;
vertex[3] = 1.0;
glVertex4fv(vertex);
}
矩形和圆形的处理如下:
AGLKVertexAttribArrayBuffer *temvertexBuffer;
if (mask == ovglview_mask_none) {
numOfVertext = 4;
drawModel = GL_TRIANGLE_STRIP;
temvertexBuffer = [[AGLKVertexAttribArrayBuffer alloc]
initWithAttribStride:sizeof(SceneVertex)
numberOfVertices:sizeof(vertices) / sizeof(SceneVertex)
bytes:vertices
usage:GL_STATIC_DRAW];
}
else if (mask == ovglview_mask_rectangle){
numOfVertext = 4;
drawModel = GL_TRIANGLE_STRIP;
SceneVertex temVertex[4];
for (int i = 0; i < 4; i++) {
temVertex[i].textureCoords.s = vertices[i].textureCoords.s/scale_1 + (1.0f - 1.0f/scale_1)/2.0;
temVertex[i].textureCoords.t = vertices[i].textureCoords.t/scale_1 + (1.0f - 1.0f/scale_1)/2.0;
temVertex[i].positionCoords.x = vertices[i].positionCoords.x/scale_1;
temVertex[i].positionCoords.y = vertices[i].positionCoords.y/scale_1;
temVertex[i].positionCoords.z = vertices[i].positionCoords.z/scale_1;
}
temvertexBuffer = [[AGLKVertexAttribArrayBuffer alloc]
initWithAttribStride:sizeof(SceneVertex)
numberOfVertices:sizeof(temVertex) / sizeof(SceneVertex)
bytes:temVertex
usage:GL_STATIC_DRAW];
}
else if (mask == ovglview_mask_circle){
SceneVertex circleVertex[100];
GLsizei num = 100;
GLfloat angle = 2*PI/num;
GLfloat r = 1.0/scale_1;
for (GLint i = 0; i < num; i++) {
circleVertex[i].textureCoords.s = 1.0 - (GLfloat)((r * cos(angle * i)) +1.0)*0.5;
circleVertex[i].textureCoords.t = (GLfloat)((r * sin(angle * i)) +1.0)*0.5;
circleVertex[i].positionCoords.x = (GLfloat)(r * cos(angle * i));
circleVertex[i].positionCoords.y = (GLfloat)(r * sin(angle * i));
circleVertex[i].positionCoords.z = 0;
}
temvertexBuffer = [[AGLKVertexAttribArrayBuffer alloc]
initWithAttribStride:sizeof(SceneVertex)
numberOfVertices:sizeof(circleVertex) / sizeof(SceneVertex)
bytes:circleVertex
usage:GL_STATIC_DRAW];
numOfVertext = num;
drawModel = GL_TRIANGLE_FAN;
}
还需要强调的是,顶点的绘制模式也需要随之改变
常见的模式有三种:
#define GL_TRIANGLES 0x0004
#define GL_TRIANGLE_STRIP 0x0005
#define GL_TRIANGLE_FAN 0x0006
OpenGL中绘制的是三角面片(基本单位)图像是由若干三角面片组成的
三种绘制模式如图所示:
GLAPI void APIENTRY glDrawArrays (GLenum mode, GLint first, GLsizei count);
图片来自【OpenGL】理解GL_TRIANGLE_STRIP等绘制三角形序列的三种方式
GL_TRIANGLES
是以每三个顶点绘制一个三角形。第一个三角形使用顶点v0,v1,v2,第二个使用v3,v4,v5,以此类推。如果顶点的个数n不是3的倍数,那么最后的1个或者2个顶点会被忽略。
这是最基本的绘制方式,单纯以三个顶点来绘制三角形,但是比较浪费资源,其顶点不能重用。
GL_TRIANGLE_STRIP
是可以进行顶点重用的,规则有点拗口:
构建当前三角形的顶点的连接顺序依赖于要和前面已经出现过的2个顶点组成三角形的当前顶点的序号的奇偶性(如果从0开始):
如果当前顶点是奇数:
组成三角形的顶点排列顺序:T = [n-1 n-2 n].
如果当前顶点是偶数:
组成三角形的顶点排列顺序:T = [n-2 n-21 n].
以上图为例,第一个三角形,顶点v2序号是2,是偶数,则顶点排列顺序是v0,v1,v2。第二个三角形,顶点v3序号是3,是奇数,则顶点排列顺序是v2,v1,v3,第三个三角形,顶点v4序号是4,是偶数,则顶点排列顺序是v2,v3,v4,以此类推。
这个顺序是为了保证所有的三角形都是按照相同的方向绘制的,使这个三角形串能够正确形成表面的一部分。对于某些操作,维持方向是很重要的,比如剔除。
注意:顶点个数n至少要大于3,否则不能绘制任何三角形。
例如:一个矩形由两个三角形组成,按照GL_TRIANGLES模式需要6个顶点,而按照GL_TRIANGLE_STRIP只需要4个顶点,但是需要注意顶点的顺序
GL_TRIANGLE_FAN
顾名思义,有个fan,是和扇形相关的,方便绘制扇形。可以以某一点为圆心进行绘制
它的三角形的顶点排列顺序是T = [n-1 n-2 n].各三角形形成一个扇形序列。
所以,在改变顶点坐标和纹理坐标的同时,还需要改变绘制模式
个人见解,有误之处还请指正