几何着色器&公告牌

几何着色器

QQ图片20221228095854

渲染管线:顶点着色器-细分着色器-几何着色器-片段着色器

作用:由于几何着色器可以在顶点发送到下一着色阶段之前对他们随意变换,所以作用就是将一个图元变换为另一个完全不同的图元,或者修改图元的位置

使用方法:

  • 在几何着色器的顶部,声明从顶点着色器输入的图元类型:layout (类型) in;
  • 可以输入的图元有5类:points,lines,lines_adjacency,triangles,triangles_adjacency
  • 指定几何着色器的输出图元类型:layout (primitive, max_vertices = n) out;
  • 输出图元有三种选项:points,line_strip,triangle_strip
  • 几何着色器还要设置一个他的最大输出顶点数量,如果绘制时超过了这个值,OpenGL将不会绘制多出的顶点

以一个简单的几何着色器为例(顶点和细分着色器都不用修改,但是要注意细分着色器的patch不能为四边形):

注意ES中没有gl_PositionIn内置变量,因此需要从TES中通过out和in传入顶点数据,且进入几何着色器的数据为一个图元所有顶点数据,因此传入的顶点数据为数组。并且对于不确定大小的数据,不能通过变量访问,因此不能使用i循环访问,需要使用0,1,2。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#version 330
#extension GL_ARB_geometry_shader4:enable
layout( triangles ) in;
//layout( line_strip, max_vertices = 3 ) out;的话一个三角形三个顶点用2条线绘制,四边形不能封口
layout( line_strip, max_vertices = 4 ) out;
out vec4 color;//将当前顶点color传给片段着色器的gl_FragColor
in vec4 fragmentColor[];//几何着色器的输入为图元的一组顶点相关数据,必须设置为数组类型
void main(void)
{
int i =0;
//输入图元的一个图元的顶点的个数
for(i=0;i<gl_PositionIn.length();i++)
{
gl_Position=gl_PositionIn[i];
color=fragmentColor[0];//颜色传入一组,为当前顶点选择一个颜色值
EmitVertex();//输出一个顶点
}
//四边形在[0]封口
gl_Position=gl_PositionIn[0];
color=fragmentColor[0];
EmitVertex();
EndPrimitive();//结束一个图元的输出
}

以上几何着色器代码实现的效果同理:

1
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

效果如下:

image-20221228133913472

暂时不太会用几何着色器和细分着色器的情况下添加光照计算

几何着色器绘制公告牌

​ 公告板技术(billboarding)。公告板是一个始终朝向相机的四边形,当相机在场景中转动的时候,公告板也会随着相机转动保证相机方向向量始终垂直于公告板的正面。可以将怪物角色、树木等任何场景中重复性高、数量多的物体以纹理贴图的形式直接贴到四边形公告板上(而不需要复杂的计算和渲染实际的3d模型),始终朝向相机。公告板常常用来创建需要大量树木的森林效果,每一个公告板只需要4个顶点。

让公告板始终朝向相机:

image-20230127114208268

​ 首先把公告板的四边形中心定义在世界中心center(0.0,0.0,0.0,0.0)。为了让四边形始终朝向相机,其四个顶点坐标与相机空间的u和s轴相同。(opengl使用右手坐标系)

几何着色器中先求出 -f 向量,即摄像机朝向。先获取公告板中心坐标,用该坐标-相机位置坐标(也是定义在世界空间),最后将其标准化。

1
2
vec3 Pos = gl_in[0].gl_Position.xyz;
vec3 toCamera = normalize(Pos-gCameraPos);

在选取一个up向量,一般选取(0.0,1.0,0.0),利用up向量和-f向量叉乘可以获得right向量,up向量和-f向量在一个平面,因此right一定垂直于-f。注意叉乘的顺序。

1
2
vec3 up = vec3(0.0, 1.0, 0.0);
vec3 right = normalize(cross(toCamera, up));

最后用rihgt向量和-f向量叉乘得到u向量

1
vec3 u = cross(right, toCamera);

通过中心点,u向量和right向量计算公告板的四个顶点坐标,每计算出一个顶点就输出一次顶点,还根据顶点位置计算UV坐标。顶点着色器只传入一个中心点,几何着色器输入point,输出triangle_strip形成三角形带。但是要注意的是up向量不一定就是u向量,向量仅仅是用于计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
layout( points ) in;
layout( triangle_strip, max_vertices = 4 ) out;

gl_Position = gVP * vec4((Pos - right * 0.5 - u * 0.5),1.0);
TexCoord = vec2(0.0, 0.0);
EmitVertex();
gl_Position = gVP * vec4((Pos - right * 0.5 + u * 0.5),1.0);
TexCoord = vec2(0.0, 1.0);
EmitVertex();
gl_Position = gVP * vec4((Pos + right * 0.5 - u * 0.5),1.0);
TexCoord = vec2(1.0, 0.0);
EmitVertex();
gl_Position = gVP * vec4((Pos + right * 0.5 + u * 0.5),1.0);
TexCoord = vec2(1.0, 1.0);
EmitVertex();
EndPrimitive();

下图是view矩阵的结构,

image-20230127120306288

glm库储存矩阵元素采用的是列优先的储存方式,因此除了在几何着色器中手动计算right,up向量,还可以通过如下代码直接获取right,up向量直接传入着色器,此时就不需要传入相机位置。

1
2
3
4
5
6
7
8
9
10
computeMatricesFromInputs();
glm::mat4 ProjectionMatrix = getProjectionMatrix();
glm::mat4 ViewMatrix = getViewMatrix();
//此处也可以使用glm::lookat函数计算ViewMatrix(参数:相机位置坐标,目标位置坐标,up向量)
glUniform3f(rightID, ViewMatrix[0][0], ViewMatrix[1][0], ViewMatrix[2][0]);
glUniform3f(upID, ViewMatrix[0][1], ViewMatrix[1][1], ViewMatrix[2][1]);
/*
glm::vec3 CameraPosition = glm::vec3(-ViewMatrix[0][2], -ViewMatrix[1][2], -ViewMatrix[2][2]);
glUniform3f(PosID, CameraPosition.x, CameraPosition.y, CameraPosition.z);
*/
Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码

请我喝杯咖啡吧~

支付宝
微信