OpenGL教程系列(第十八课:Billboards)

Number of views 72

广告牌是镶嵌在3D世界中的2D元素。它既不是覆盖在所有内容之上的2D菜单,也不是可以环绕观察的3D平面,而是介于两者之间的存在,就像许多游戏中的血条那样。

广告牌的特殊之处在于:它们被放置在特定位置,它的朝向会自动计算,并始终面向摄像机。

方案1:2D实现方式

这种方法非常简单。

只需计算目标点在屏幕上的位置,然后在该位置显示2D文本或其它2D内容(参见教程11)。

glm::vec4 BillboardPos_worldspace(x,y,z, 1.0f);
glm::vec4 BillboardPos_screenspace = ProjectionMatrix * ViewMatrix * BillboardPos_worldspace;
BillboardPos_screenspace /= BillboardPos_screenspace.w;

if (BillboardPos_screenspace.z < 0.0f){
    // 物体在相机后方,不显示它。
}

这种方法的优点是它非常简单,而且广告牌的大小不会随其与相机距离的变化而变化。但2D文本总是显示在所有内容之上,这可能会干扰渲染,并显示在其他物体之上。

方案2:3D实现方式

这种方法通常更好,而且也并不复杂。

实现的目标是让模型网格始终看向摄像机,即使摄像机移动时也是如此:

我们可以将这个问题看成怎么生成一个合适的 模型矩阵(Model Matrix),不过没有它那么复杂。

其理念在于广告牌的每个角点都位于中心位置,并且通过摄像机的向上(up)和向右(right)矢量进行位移。这意味着,为了确保广告牌始终面向摄像机,我们需要根据摄像机的方向来调整广告牌的位置和方向。具体来说,广告牌的四个角点可以从其中心位置出发,沿着摄像机的“上”方向和“右”方向移动一定的距离来确定。这种方法保证了无论摄像机如何移动或旋转,广告牌都能正确地面向摄像机,从而实现预期的视觉效果。

image1739500029065.png

当然,我们只知道广告牌在世界空间中的中心位置,因此我们还需要摄像机在世界空间中的“上”向量和“右”向量。

在摄像机空间(Camera Space)中,摄像机的“上”向量是 (0,1,0)。要将其转换到世界空间(World Space),只需将其乘以从摄像机空间到世界空间的矩阵,这个矩阵就是视图矩阵(View Matrix)的逆矩阵。

用更简单的方式表达相同的数学运算就是:

CameraRight_worldspace = {ViewMatrix[0][0], ViewMatrix[1][0], ViewMatrix[2][0]}
CameraUp_worldspace = {ViewMatrix[0][1], ViewMatrix[1][1], ViewMatrix[2][1]}

一旦我们有了这些,计算最终顶点的位置就非常简单了:

vec3 vertexPosition_worldspace =
    particleCenter_wordspace
    + CameraRight_worldspace * squareVertices.x * BillboardSize.x
    + CameraUp_worldspace * squareVertices.y * BillboardSize.y;
  • particleCenter_worldspace,顾名思义,是广告牌的中心位置。它通过一个 uniform vec3 指定。
  • squareVertices 是原始网格。对于左侧的顶点,squareVertices.x 为 -0.5,因此它们会向摄像机的左侧移动(因为乘以了 CameraRight_worldspace)。
  • BillboardSize 是广告牌的大小,以世界空间的单位表示,通过另一个 uniform 传递。

很快,结果就出来了。是不是很容易?

为了完整性,下面是四面体的顶点数据:

// The VBO containing the 4 vertices of the particles.
 static const GLfloat g_vertex_buffer_data[] = {
 -0.5f, -0.5f, 0.0f,
 0.5f, -0.5f, 0.0f,
 -0.5f, 0.5f, 0.0f,
 0.5f, 0.5f, 0.0f,
 };

方案3:固定大小的3D方式

正如上述的输出结果显示,广告牌的大小会随着与摄像机距离的变化而变化。在某些情况下,这是预期的结果,但在其他情况下,例如 生命值条(health bars),你可能更希望得到的是固定大小的广告牌。

由于中心点与角点之间的位移在屏幕空间中必须是固定的,因此这就是我们将要做的事情:计算中心点在屏幕空间中的位置,并对其进行偏移。具体来说,为了实现 固定大小的3D方式(The fixed-size 3D way),我们需要确保无论广告牌离摄像机有多远,它在屏幕上显示的大小保持不变。这要求我们在渲染过程中对广告牌的位置和大小进行特殊的处理。

首先,我们需要确定广告牌的世界坐标中心点位置particleCenter_worldspace,然后通过视图投影矩阵将其转换到屏幕空间中:

vertexPosition_worldspace = particleCenter_wordspace;
// 获取粒子中心的屏幕空间位置
gl_Position = VP * vec4(vertexPosition_worldspace, 1.0f);
// 这里我们必须自己做透视除法。
gl_Position /= gl_Position.w;

// 直接在屏幕空间中移动顶点。这里不需要CameraUp/ right_worldspace。
gl_Position.xy += squareVertices.xy * vec2(0.2, 0.05);

我们自己做完透视除法后,顶点处于 归一化设备坐标(Normalized Device Coordinates, NDC)中,因此坐标值在两个轴上都介于 -1 和 1 之间:这并不是以像素为单位的坐标。

如果希望得到一个以像素为单位的大小,那就很简单了:只需使用 (ScreenSizeInPixels / BillboardSizeInPixels) 而不是 BillboardSizeInScreenPercentage。这意味着我们可以根据屏幕的实际像素尺寸和广告牌期望的像素尺寸来计算其在屏幕空间中的比例,从而确保广告牌能够以固定的像素大小显示在屏幕上。

方案4:仅垂直旋转

一些系统将远处的树木和路灯建模为 公告板(billboards)。但是,您绝对不希望您的树看起来是弯曲的:它必须保持垂直。因此,您需要一个混合系统,该系统只围绕固定轴(通常为Y轴)进行旋转。你会怎么做?我就不说明了

0 Answers