文章目录
原文: OpenGL-Turorial - Billboards
(2020.03.24 23:16 这里吐槽一下:我的天,优快云更新后,写文章系统一堆BUG,写着写着,光标不会换行,即使按下多少次回车键都一样,然后保存草稿后,在刷新页面,发现之前写的一部分内容不见了。。。这。。。无语,多浪费心血啊。。。)
Billboards
Billboard 是3D世界中的2D元素。它不是一个在所有其他东西顶上显示的2D菜单,也不是一个可以让你旋转的3D平面;但介于他们之间的东西,如多数游戏中的:血条。
而Billboard区别在于他们的位置是可以指定的,但它们的朝向会一直的朝向镜头。
Solution #1 : The 2D way
解决方案1:2D的方法
这个方法超级简单。
仅仅计算在屏幕中的位置,并在这个位置上显示一个2D tex文本(查看教程11有)。
// Everything here is explained in Tutorial 3 ! There's nothing new.
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){
// Object is behind the camera, don't display it.
}
噔噔!
从好的方面来说,这是非常简单的方法,并且billboard的大小不管离镜头距离多少都是一样的。但是2D tex文本本身是显示与其他东西顶层的,而且这种处理有可能还会影响到其他的渲染,并显示在这些对象顶上。
Solution #2 : The 3D way
解决方案2:3D的方式
这种方式一般来说是更好的,同时也不会太复杂。
处理目标是会保持对齐于镜头的,就算镜头移动:
你可以将这种方式认为是生成一个模型矩阵的方式,但其实更简单。
这个思路是:billboard的每一个角落掉都位于中心点,然后通过镜头的up(顶部)方向与right(右边)方向的向量来位置这些顶点:
当然,billboard的世界坐标我们是知道的,所以我们还需要镜头相对世界空间的up/right方向向量。
在相机空间下,相机的up向量就是(0,1,0)。要获取它的相对世界空间的向量,仅仅用它乘以一个Camera Space to World Space(相机空间到世界空间)的矩阵即可,就是View矩阵的逆矩阵。
有一个简便的方式同样可以获取:
CameraRight_worldspace = {ViewMatrix[0][0], ViewMatrix[1][0], ViewMatrix[2][0]}
CameraUp_worldspace = {ViewMatrix[0][1], ViewMatrix[1][1], ViewMatrix[2][1]}
(译者jave.lin:这里我要说明一下,ViewMatrix是视图矩阵,不是视图矩阵的逆矩阵,但其实使用其逆矩阵的方式获取会更加简便,只不过会需要更多性能,只要获取ViewMatrix的逆矩阵即可,而ViewMatrix是个正交基矩阵,所以其逆矩阵就是其转置矩阵,所以right = transpose(ViewMatrix)[0], up = traspose(ViewMatrix)[1]
)
一旦获取这些数据后,就可以非常简单的计算出最终的顶点位置:
vec3 vertexPosition_worldspace =
particleCenter_wordspace
+ CameraRight_worldspace * squareVertices.x * BillboardSize.x
+ CameraUp_worldspace * squareVertices.y * BillboardSize.y;
particleCenter_worldspace
正如其名字描述的,就是billboard的世界坐标中心点位置。它是指定为uniform vec3
。squareVertices
是网格的原点。squareVertices.x
为-0.5
就是左边的顶点,因此根据他来位移就是相对镜头左边方向移动(因为是乘以CameraRight_worldspace
的)- BillboardSize 是大小,使用世界单位,也是个uniform。
OK,到这里就完成了,不是很简单吗?
为了记录用,这里贴出squreVertices
是怎么来的:
// The VBO containing the 4 vertices of the particles.
// VBO包含了粒子的四个顶点
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,
};
Solution #3 : The fixed-size 3D way
解决方案3:固定大小的3D方式
正如你上述所看到的,billboard的大小会相对相机的距离而改变。在某些情况下可以适用,但其他的一些情况,如:血条栏,你可能想要它是一个固定大小的。
也就是说,必须是在屏幕空间下移动他们的中心点或是角落点,这正是我们处理的:计算屏幕空间的中心点位置,再偏移它。
vertexPosition_worldspace = particleCenter_wordspace;
// Get the screen-space position of the particle's center
// 获取屏幕空间的粒子的中心点位置
gl_Position = VP * vec4(vertexPosition_worldspace, 1.0f);
// Here we have to do the perspective division ourselves.
// 这里我们自己处理透视除法
gl_Position /= gl_Position.w;
// Move the vertex in directly screen space. No need for CameraUp/Right_worlspace here.
// 直接在屏幕空间中移动顶点。这里就不需要CameraUp/Right_worldspace。
gl_Position.xy += squareVertices.xy * vec2(0.2, 0.05);
要记得这在渲染流水线里,是处于Normalized Device Coordinates(译者jave.lin:NDC可以参考:OpenGL Transformation 几何变换的顺序概要(MVP,NDC,Window坐标变换过程),所以它们轴上的值是在:-1~1之间的,另外注意这不是像素坐标。
如果你想要的是一个像素的大小,也简单:就使用(ScreenSizeInPixels / BillboardSizeInPixels
,就是:屏幕的像素大小 / Billboard的像素大小
) BillboardSizeInScreenPercentage
(Billboard在屏幕的大小比例
)来替代。
Solution #4 : Vertical rotation only
在某些系统中的模型,如很远的树和灯都用Billboard来处理。但你不想树歪了:它必须是垂直于地面的。所以你需要一个仅仅只旋转某个轴的系统。
好了,这个功能就留给读者当作练习!
(关于现实方式,我都再后面一篇文章都有描述,代码实现:Unity Shader - Billboard 广告板/广告牌)