本来这一节应该讲的是裁剪,但是裁剪的时候,根据不同的图元类型处理裁剪的方式也是不一样的,所以在裁剪之前先来说一下图元装配。
1、图元装配
图元装配发生在顶点着色之后,根据我们在调用 glDrawArrays 或者 glDrawElements 时设置的图元类型,将顶点着色输出的顶点组合成指定的图元形状,OpenGL ES 2.0 一共支持 7 种不同的图元类型,经过图元装配之后,图元的类型就只有三种了,分别是点、线、三角形。
2、图元类型
图元枚举值和对应图元类型如下表所示
枚举值 | 图元类型 |
GL_POINT | 点 |
GL_LINES | 线 |
GL_LINE_STRIP | 条带线 |
GL_LINE_LOOP | 循环线 |
GL_TRIANGLES | 三角形 |
GL_TRIANGL_STRIP | 条带三角形 |
GL_TRIANGLE_FAN | 扇面三角形 |
点
点可以通过单一的顶点来表示,一个点也就是一个四维的其次坐标值。默认的点大小是 1.0,因此当我们渲染点的时候,每个顶点实际上都是屏幕上的一个像素,如果点的大小增加,那么每个点的顶点都会占据超过 1 个像素的值。
点精灵
当点大小是 1 的时候,看起来就是屏幕上的一个像素,当点大小超过 1 的时候,比如设置为 4,那么就是屏幕上的 4x4 大小的一个块,在这个 4x4 块里面的像素除了正常的像素坐标以外,还有一个点精灵坐标,它的范围是 [0, 1],左上角的坐标是 (0, 0),右下角的坐标是 (1, 1),OpenGL 在片段着色器里面提供了一个内置变量来表示这个坐标,它叫做 gl_PointCoord,它的值只对点渲染有效,我们可以使用这个坐标将一个方形的点绘制成一个圆形的点,也可以将 gl_PointCoord 作为输入的纹理坐标使用,那么就可以使用纹理颜色来替代方块的颜色。
使用点精灵绘制圆形的点(片段着色器)
precision mediump float;
void main() {
vec2 temp = gl_PointCoord - vec2(0.5);
float dis = dot(temp, temp);
if(dis > 0.25) {
discard;
}
gl_FragColor = vec4(1.0, 0.9, 0.3, 1.0);
}
绘制效果
使用点精灵作为纹理坐标(片段着色器)
precision mediump float;
uniform sampler2D vTexture;
void main() {
gl_FragColor = texture2D(vTexture, gl_PointCoord);
}
绘制效果
线、条带线、循环线
线 | 两个点表示一条线 |
条带线 | 每两个点表示一条线,但是下一条线段的起点是上一条线的结束点 |
循环线 | 和条带类似,但是最后一条线段是尾和首相连,形成一个闭环 |
三角形、条带三角形、扇面三角形
三角形 | 三个点构成一个三角形 |
条带三角形 | 和线的条带有点类似,区别是一个三角形前两个点是上一个三角形的后两个点 |
扇面三角形 | 当绘制扇面的时候,第一个顶点会作为一个共享点存在,也是后面每一个三角形的第一个点,然后每一个三角形的最后一个点作为下一个三角形的第二个点存在 |
这里要特别注意的是 GL_TRIANGL_STRIP 也就是条带三角形,比如我们输入的顶点顺序是 {1,2,3,4,5,6},然后设置图元类型为 GL_TRIANGL_STRIP,那么它的组合方式如下
三角形编号 | 顶点组成 |
1 | 1,2,3 |
2 | 3,2,4 |
3 | 3,4,5 |
4 | 5,4,6 |
这里需要注意的是第二个和第四个三角形的顶点顺序,顶点顺序不一样计算出来的前后面也是不同的,那么这里就可以写成下面这样
n 为奇数三角形顺序为 n,n+1,n+2 n 为偶数三角形顺序为 n+1,n,n+2
那么对应的代码实现就像下面这样
struct Point {
float x;
float y;
};
Point traingles[3];
int index = 0;
bool flip = true;
// 顶点是按照顺序下发的,不是一次性所有的顶点都会到图元装配模块
void PushData(Point point) {
traingles[index++] = point;
if (index == 3) {
std::cout << traingles[0] << " " << traingles[1] << " " << traingles[2] << std::endl;
if (flip) {
traingles[0] = traingles[2];
} else {
traingles[1] = traingles[2];
}
flip = !flip;
index = 2;
}
}
3、C++ 代码实现
下面使用一段完整的 C++ 代码来实现图元装配的功能
#define GL_POINTS 0x0000
#define GL_LINES 0x0001
#define GL_LINE_LOOP 0x0002
#define GL_LINE_STRIP 0x0003
#define GL_TRIANGLES 0x0004
#define GL_TRIANGLE_STRIP 0x0005
#define GL_TRIANGLE_FAN 0x0006
struct Point {
float x{};
float y{};
Point(float x = 0, float y = 0) {
this->x = x;
this->y = y;
}
};
class PrimitiveAssembly {
public:
PrimitiveAssembly() {}
~PrimitiveAssembly() {}
void SetPrimitiveType(int type) {
if (primitive_type_ != type) {
primitive_type_ = type;
}
}
void PushData(const Point& point, bool is_end) {
if (primitive_type_ == GL_POINTS) {
std::cout << point.x << " " << point.y << std::endl;
return;
}
recv_point_count_++;
points_[recv_point_count_ - 1] = point;
if (primitive_type_ == GL_LINES || primitive_type_ == GL_LINE_STRIP || primitive_type_ == GL_LINE_LOOP) {
if (recv_point_count_ == 2) {
HandleLine(is_end);
}
} else {
if (recv_point_count_ == 3) {
HandleTriangle(is_end);
}
}
if (is_end) {
recv_point_count_ = 0;
triangle_strip_flip_ = true;
}
}
void HandleLine(bool is_end) {
for (int i = 0; i < 2; ++i) {
std::cout << points_[i].x << " " << points_[i].y << std::endl;
}
if (primitive_type_ == GL_LINES) {
recv_point_count_ = 0;
return;
}
if (primitive_type_ == GL_LINE_LOOP) {
if (is_end && !line_loop_first_) {
std::cout << points_[1].x << " " << points_[1].y << std::endl;
std::cout << tmp_point_.x << " " << tmp_point_.y << std::endl;
line_loop_first_ = true;
}
if (line_loop_first_) {
tmp_point_ = points_[0];
line_loop_first_ = false;
}
}
points_[0] = points_[1];
recv_point_count_ = 1;
}
void HandleTriangle(bool is_end) {
for (int i = 0; i < 3; ++i) {
std::cout << points_[i].x << " " << points_[i].y << std::endl;
}
if (primitive_type_ == GL_TRIANGLES) {
recv_point_count_ = 0;
return;
}
if (primitive_type_ == GL_TRIANGLE_STRIP) {
if (triangle_strip_flip_) {
points_[0] = points_[2];
} else {
points_[1] = points_[2];
}
triangle_strip_flip_ = !triangle_strip_flip_;
} else {
points_[1] = points_[2];
}
recv_point_count_ = 2;
}
private:
int primitive_type_{};
int recv_point_count_{};
Point points_[3];
Point tmp_point_{};
bool triangle_strip_flip_{ true };
bool line_loop_first_{ true };
};