OpenGL深入探索——广告牌(Billboard)和几何着色器

举报
ShaderJoy 发表于 2021/12/29 23:39:57 2021/12/29
【摘要】 转载自:第二十七课 广告牌和几何着色器 背景 在前面的几章内容里,我们已经认识了顶点着色器和片元着色器,但事实上,我们遗漏了一个很重要的着色器——几何着色器(GS)。微软在 DirectX10 中使用了这个着色器,后来被集成到了 OpenGL3.2 的内核中。VS 会对每一个顶点执行一次, FS 会对每一个片元执行一次,GS 则会对每...

转载自:第二十七课 广告牌和几何着色器

背景



特殊的能力

  1. 改变新传递进来的图元的拓扑结构。GS 可以接收任何拓扑类型的图元,但是只能输出点列表、折线(line strip)和三角带(triangle strips);
  2. GS 需要一个图元作为输入,在处理过程中他可以将这个图元整个丢弃或者输出一个或更多的图元(也就是说它可以产生比它得到的更多或更少的顶点)。这个能力被叫做几何增长(growing geometry)。这一章我们将会看看它有哪些优势。


三角形列表中每三个顶点构成一组。比如顶点 0-2 形成第一个三角形,顶点 3-5 形成第二个三角形,依次进行。我们可以简单地通过将顶点数除以 3(舍去余数)来计算三角形的数量。三角带的绘制更加高效,因为这个过程不是每添加 3 个顶点才绘制一个新三角形的,大多数时候我们只需要添加一个顶点就可以绘制出一个新三角形。构建第一个三角形,我们用三个顶点( 0-2 )。当我们添加第四个顶点的时候,就得到了第二个三角形,这个三角形是由顶点 1-3 构成的。当你添加第五个顶点的时候,你就会得到第三个三角形,这个三角形是由顶点 2-4 构成的。以此类推。因此,从第二个三角形开始,每添加一个新的顶点,就可以和先前的两个顶点一起构建一个新的三角形,例如:


如你所见,9 个顶点就可以创建出 7 个三角形。如果这是三角形列表,我们只能创建 3 个三角形。

三角带有一条关于三角形内部环绕顺序的重要性质——奇数三角形的环绕顺序是反向的。这就意味着如下的顺序:[0,1,2],[1,3,2], [2,3,4], [3,5,4],以此类推。下面的图片显示了这个顺序:



这下我们就理解了 GS 的主要原理了,让我们来看一下它如何帮助我们实现一个非常有用并且广受欢迎的技术—— billboard 。Billboard 是一个始终朝向相机的四边形。当相机在场景中发生运动时,Billboard 也随之转动,因此,从 billboard 到相机的矢量一直垂直于 billboard 的表面。这和我们真实世界中高速公路上的广告牌是一个道理,总是尽可能地面朝行驶的车辆。一旦我们得到面向相机的四边形,就很容易将怪兽、树等等的图片贴在这个四边形之上了,并且创建大量朝相机的场景物体。森林需要大量的树,我们常使用 Billboard 来创建森林,营造森林的效果。由于 billboard 上的纹理总是面朝相机,所以玩家误以为所有物体都有真实的深度,而实际上呢,这些物体都仅仅是一个面而已。每一个 billboard 只需要四个顶点,因此它比实实在在制作出来的模型占用的资源会少很多。

这一课中我们创建一个顶点缓冲区,并且在这个顶点缓存中存放 billboard 在世界坐标系中的位置,每一个位置仅仅是一个单独的点( 3D 矢量)。我们将这个点的信息传递给几何着色器,并用这些位置坐标组建出一个四边形。也就是说,输入给 GS 的拓扑结构是点列表,而输出的拓扑结构是三角带。利用三角带的原理,我们用 4 个顶点创建一个四边形:



GS 负责对四边形进行处理,使之始终朝向相机,同时他会为每个顶点生成合适的纹理坐标。片元着色器只需要从纹理上取样就能得到最后的颜色信息。

我们来看一下如何使 billboard 一直朝向相机。在下面的这张图片中,黑点代表相机,红点代表 billboard 的位置。每一个点都在世界坐标系下,虽然看起来就好像它们像是位于平行于 XZ 平面的一个平面上(当然也没必要如此),实际上任意两个点都行。



接下来我们创建一个从 billboard 指向相机的矢量:



接下来我们添加(0,1,0)矢量:



现在对这两个矢量做一个叉乘,结果是一个垂直于这两个矢量创建的平面的矢量,我们可以通过这个矢量对这个点进行扩展并创建出四边形。这个四边形将会垂直于从 billboard 到相机的矢量,这就是我们想要的。由上面的情况可知,我们能得到以下内容(黄色的矢量就是叉乘的结果):



有一件事情经常困扰开发人员,就是计算叉乘时的顺序(A X B还是 B X A)。这两种方法产生的向量是相向的。我们有必要提前知道向量的结果,因为我们需要输出顶点,这样从相机的方位来看,这两个三角形生成的正方形才会是顺时针的。这里我们使用左手定则——如果你站在 billboard 的位置,你的食指指向相机的方位,中指指向天空,然后你的大拇指将会沿着“食指”和“中指”叉乘的结果的方向(两个手指保持夹紧)。这一节,我们将叉乘的结果称为“右”向量,因为从相机方位看我们手,是指向右侧的。“中指”叉乘“食指”会产生一个“左”向量。 (我们使用左手定则,因为我们使用的是左手坐标系(Z轴指向屏幕里)。左手坐标系非常合适)。


  
  1. (billboard_list.h:27)
  2. class BillboardList
  3. {
  4. public:
  5. BillboardList();
  6. ~BillboardList();
  7. bool Init(const std::string& TexFilename);
  8. void Render(const Matrix4f& VP, const Vector3f& CameraPos);
  9. private:
  10. void CreatePositionBuffer();
  11. GLuint m_VB;
  12. Texture* m_pTexture;
  13. BillboardTechnique m_technique;
  14. };




  
  1. (billboard_list.cpp:80)
  2. void BillboardList::Render(const Matrix4f& VP, const Vector3f& CameraPos)
  3. {
  4. m_technique.Enable();
  5. m_technique.SetVP(VP);
  6. m_technique.SetCameraPosition(CameraPos);
  7. m_pTexture->Bind(COLOR_TEXTURE_UNIT);
  8. glEnableVertexAttribArray(0);
  9. glBindBuffer(GL_ARRAY_BUFFER, m_VB);
  10. glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vector3f), 0); // position
  11. glDrawArrays(GL_POINTS, 0, NUM_ROWS * NUM_COLUMNS);
  12. glDisableVertexAttribArray(0);
  13. }





  
  1. (billboard_technique.h:24)
  2. class BillboardTechnique : public Technique
  3. {
  4. public:
  5. BillboardTechnique();
  6. virtual bool Init();
  7. void SetVP(const Matrix4f& VP);
  8. void SetCameraPosition(const Vector3f& Pos);
  9. void SetColorTextureUnit(unsigned int TextureUnit);
  10. private:
  11. GLuint m_VPLocation;
  12. GLuint m_cameraPosLocation;
  13. GLuint m_colorMapLocation;
  14. };





  
  1. (billboard.vs)
  2. #version 330
  3. layout (location = 0) in vec3 Position;
  4. void main()
  5. {
  6. gl_Position = vec4(Position, 1.0);
  7. }





  
  1. (billboard.gs:1)
  2. #version 330
  3. layout (points) in;
  4. layout (triangle_strip) out;
  5. layout (max_vertices = 4) out;


我们告诉管线输入的图元拓扑结构是点列表,输出图元的拓扑是三角带。我们还告诉管线生成的顶点数不会多于四个顶点。这个关键词是用来告诉驱动程序 GS 中会产生的顶点的最大数目。


  
  1. (billboard.gs:7)
  2. uniform mat4 gVP;
  3. uniform vec3 gCameraPos;
  4. out vec2 TexCoord;





  
  1. void main()
  2. {
  3. vec3 Pos = gl_in[0].gl_Position.xyz;


由于 GS 的执行时针对一个完整的图元的,所以我们可以访问这个图元的所有顶点,这是通过内置变量 “gl_in” 做到的 这个变量是一个结构体数组(数组中每个元素都是一个结构体),数组中每个元素都包含了写入到 gl_position 中的位置信息和其他一些从 VS 中输出的数据。


  
  1. vec3 toCamera = normalize(gCameraPos - Pos);
  2. vec3 up = vec3(0.0, 1.0, 0.0);
  3. vec3 right = cross(toCamera, up);





  
  1. Pos -= (right * 0.5);
  2. gl_Position = gVP * vec4(Pos, 1.0);
  3. TexCoord = vec2(0.0, 0.0);
  4. EmitVertex();
  5. Pos.y += 1.0;
  6. gl_Position = gVP * vec4(Pos, 1.0);
  7. TexCoord = vec2(0.0, 1.0);
  8. EmitVertex();
  9. Pos.y -= 1.0;
  10. Pos += right;
  11. gl_Position = gVP * vec4(Pos, 1.0);
  12. TexCoord = vec2(1.0, 0.0);
  13. EmitVertex();
  14. Pos.y += 1.0;
  15. gl_Position = gVP * vec4(Pos, 1.0);
  16. TexCoord = vec2(1.0, 1.0);
  17. EmitVertex();
  18. EndPrimitive();
  19. }


为了将新产生的顶点传递到管线的下一阶段,我们调用内置函数 EmitVertex()。一旦我们调用了这个函数 gl_Position 变量中的数据就无效了,所以我们需要给其传一个新值。 为了终止三角带,我们调用内置函数 EndPrimitive()。


  
  1. (billboard.fs)
  2. #version 330
  3. uniform sampler2D gColorMap;
  4. in vec2 TexCoord;
  5. out vec4 FragColor;
  6. void main()
  7. {
  8. FragColor = texture2D(gColorMap, TexCoord);
  9. if (FragColor.r == 0 && FragColor.g == 0 && FragColor.b == 0)
  10. {
  11. discard;
  12. }
  13. }


内置关键字 “discard”

操作结果:



OpenGL Video Tutorial: Geometry Shader


文章来源: panda1234lee.blog.csdn.net,作者:panda1234lee,版权归原作者所有,如需转载,请联系作者。

原文链接:panda1234lee.blog.csdn.net/article/details/51737102

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。