opengl 纹理贴到对应的位置_一步步学OpenGL(27) -《公告牌技术与几何着色器》

f61023fe81530a0d275c5db52f18bd33.png

教程 27

公告牌技术与几何着色器

40c85d6617863434beb84a9221a38a3f.png

原文: http://ogldev.atspace.co.uk/www/tutorial27/tutorial27.html

CSDN完整版专栏: https://blog.csdn.net/cordova/article/category/9266966


背景

从最初的一系列教程我们已经应用过了顶点着色器和片段着色器,但事实上我们还忽略了一个非常重要的着色阶段,叫做几何着色器(GS)。几何着色器在微软的DirectX 10之后被引入,之后也加入到了核心的OpenGL 3.2中。顶点着色器中是按照顶点一个一个执行的,片段着色器则是一个像素一个像素执行,而几何着色器是以图元为单位执行,这意味着在我们绘制三角形时,每次调用几何着色器接收到的就是一个三角形,而绘制直线每次调用收到的就是一条直线,等等。这就给几何着色器提供了一个看待模型的独特的角度,开发者可以知道顶点和顶点之间拓扑关系,从而可以基于此开发一些新的技术。

顶点着色器总是以一个顶点作为输入,并对应一个顶点作输出(不可以自行增加或减少顶点),而几何着色器却有着特殊的功能,它可以改变经过它的图元,这种改变包括:

  • 改变新传递进来的图元的拓扑结构。几何着色器可以接收任何拓扑类型的图元,但只能输出顶点列表(point lists)、折线(line strip)和三角带(triangle strips);
  • 几何着色器接受一个图元作为输入,在处理过程中它可以将这个图元全部丢弃或者输出一个或更多的图元(也就是说它可以产生比它得到的更多或更少的顶点)。这个能力被叫做几何增长(growing geometry)。这一章我们将会利用几何着色器的这种能力。

几何着色器是可选择的。如果我们编译程序的时候不使用几何着色器,图元会直接的从顶点着色器进入片元着色器。这就是为什么我们之前并没有使用顶点着色器,却可以直接跳过该阶段正常绘制出图形。

三角形列表中的三角形图元是以每三个顶点一组构建的,例如,0-2前三个顶点构建起第一个三角形,3-5三个顶点构建起第二个三角形,以此类推。为了计算已有顶点所组成的三角形个数,只要用顶点数除以3即可(多余的顶点直接抛弃)。但事实上,使用三角形带构建更有效,不需要每个三角形都用专门的三个顶点构建,而是在使用三个顶点将第一个三角形构建完成后,重复利用其中的两个顶点,然后再添加一个顶点即可构建第二个三角形。例如0-2三个顶点构建起一个三角形后,再添加一个顶点3,1-3三个顶点构成第二个三角形,这样0-3四个顶点即可紧密拼接构建起两个三角形,以此类推,再添加顶点4,2-4三个顶点又构成一个三角形等等。也就是说,在第一个三角形使用三个顶点构建完成后,每天添加一个顶点即可再构成一个三角形。这里有一个例子如下图:

6a6e3ed30501578adba84589c970cfe5.png

可以看到,三角带中7个三角形只用了9个顶点,要是在三角形列表中,9个顶点就只能构建3个三角形。

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

6003b6980c3a8f44e106b122284fea1e.png

了解了几何着色器之后,现在看如何利用几何着色器来实现一种非常有用的热门技术:公告板技术(billboarding)。公告板是一个始终朝向相机的四边形,当相机在场景中转动的时候,公告板也会随着相机转动保证相机方向向量始终垂直于公告板的正面。这个和现实中公路边的公告板类似,公告板的放置方向会让尽可能多路过的开车司机看到。有了这种面向相机的四边形之后,我们就很容易将怪物角色、树木等任何场景中重复性高、数量多的物体以纹理贴图的形式直接贴到四边形公告板上(而不需要复杂的计算和渲染实际的3d模型),始终朝向相机。公告板常常用来创建需要大量树木的森林效果,由于公告板始终朝向相机,玩家会误以为看到的物体是有实际深度的,而事实上单纯就是个平面而已。每一个公告板只需要4个顶点,因此比起使用大量实际的3d模型代价就小得多了。

在这个教程中,我们创建一个顶点缓冲器,并为公告板存放顶点的世界空间坐标,每一个坐标就是一个单独的3维坐标点。我们会将这些顶点坐标传送到几何着色器,然后构建相应的四边形作为公告板。这意味着几何着色器会输入顶点列表,然后输出三角形带。利用三角带的优点我们可以使用四个顶点创建出一个四边形。

37f28362ee7d3f06a63f96b176092f70.png

几何着色器会负责调整四边形始终朝向相机,并为每一个输出的顶点附加纹理坐标,这样片段着色器只需要直接从纹理上采样就可以得到最终的颜色信息。

现在看怎样让公告板总是朝向相机。在下图中黑点表示相机,红点表示公告板的位置。虽然图中位置看上去好像在一个平面上,但实际两个点都在世界空间下,世界空间中任意两个点都是可能的。

9e634507c4baa8cafdf200beda7c79ab.png

这里创建一个从公告板到相机的向量:

f922ca3c278ffdf4b8096c8f938ddf7d.png

然后添加一个向量(0,1,0):

a1ddb35bcae4733771fb741569dbb7ad.png

对这两个向量做叉乘,结果得到一个垂直于这两个向量所在平面的向量,然后就要沿着这个结果向量的方向来扩展创建我们的四边形,保证四边形平面和相机朝向垂直,这样才符合我们想要的结果。同样的场景下我们可以得到下面这样(黄色向量是叉乘的结果向量):

2aef2d2d8719b18bee55d044964f52cf.png

一个容易让开发者疑惑的事情是关于向量做叉积的顺序问题(A叉积B,还是B叉积A?),两种情况会得到两个反向的结果向量。事先知道具体的结果向量是很关键的,因为我们要这样输出顶点,使从相机的视角看时三角形组成的四边形呈顺时针方向。这里要用到左手法则了:

如果站在公告板的位置(红点),食指指向相机,中指指向上方的天空,然后你的大拇指将会沿着“食指”和“中指”叉乘的结果的方向(这里剩下的两个手指保持握紧)。此教程中,我们将叉乘的结果称为“右”向量,因为从相机位置看我们的手,‘右’向量是指向右侧的。反过来,“中指”叉乘“食指”又可以产生一个反向的“左”向量。(这里我们使用左手定则的原因是因为我们使用的是左手坐标系(Z轴指向屏幕内))。在右手坐标系中情况就相反了,那样就该选用右手坐标系了。

源代码详解

(billboard_list.h:27)

class BillboardList
{
public:BillboardList();~BillboardList();bool Init(const std::string& TexFilename);void Render(const Matrix4f& VP, const Vector3f& CameraPos);private:void CreatePositionBuffer();GLuint m_VB;Texture* m_pTexture;BillboardTechnique m_technique;
};

BillboardList类封装了创建公告板需要的所有东西,初始化函数Init()的参数为一个文件名,文件就是那个作为纹理贴图贴到公告板上的图像。Render()渲染函数在主渲染循环中被调用,负责设置状态和渲染公告板。这个函数需要两个参数:一个是视图和投影组合矩阵,一个是相机的世界坐标位置。由于公告板的位置也是定义在世界空间中的,所以我们才直接到了视图和投影阶段,跳过世界空间变换的部分。这个类有三个私有属性:一个存储公告板位置的顶点缓冲器,一个指向公告板纹理贴图的指针,和一个包含相关着色器的BillboardTechnique公告板类。

(billboard_list.cpp:80)

void BillboardList::Render(const Matrix4f& VP, const Vector3f& CameraPos)
{m_technique.Enable();m_technique.SetVP(VP);m_technique.SetCameraPosition(CameraPos);m_pTexture->Bind(COLOR_TEXTURE_UNIT);glEnableVertexAttribArray(0);glBindBuffer(GL_ARRAY_BUFFER, m_VB);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vector3f), 0); // position glDrawArrays(GL_POINTS, 0, NUM_ROWS * NUM_COLUMNS);glDisableVertexAttribArray(0);
}

这个函数启用了BillboardTechnique公告板类,并设置了OpenGL中一些必要的状态,绘制顶点且这些顶点之后会在几何着色器阶段转化成四边形面。在这个Demo中,公告板位置是按照严格的行列顺序排列的,因此我们可以行列数相乘得到顶点的数量。需要注意,我们在绘制的时候使用的绘制模式为点模式(GL_POINTS),在几何着色器中要与其对应。

(billboard_technique.h:24)

class BillboardTechnique : public Technique
{
public:BillboardTechnique();virtual bool Init();void SetVP(const Matrix4f& VP);void SetCameraPosition(const Vector3f& Pos);void SetColorTextureUnit(unsigned int TextureUnit);private:GLuint m_VPLocation;GLuint m_cameraPosLocation;GLuint m_colorMapLocation;
};

这里是BillboardTechnique的类接口,它只需要三个参数来完成下面的任务:视图和投影组合矩阵、相机世界空间位置和与公告板绑定的纹理单元的数量。

(billboard.vs)

#version 330

layout (location = 0) in vec3 Position;void main()
{gl_Position = vec4(Position, 1.0);
}

这是公告板类的顶点着色器,由于主要工作都将在几何着色器中完成,因此在这里顶点着色器就不要太简单了哈哈。顶点缓冲器只包含顶点坐标向量,而且这些坐标已经在世界空间中定义了,所以可以直接将它们传给几何着色器,即可。

(billboard.gs:1)

#version 330layout (points) in;
layout (triangle_strip) out;
layout (max_vertices = 4) out;

公告板技术的核心就在几何着色器了,我们分解开一步步来看。开始我们先使用‘layout’关键字声明一些全局缓冲器。我们要先告诉渲染管线输入来的参数结构是点列表,输出的是三角带,并且说明输出的顶点个数最多为4个。这些关键词也会提示图形驱动器从几何着色器输出顶点的最大个数,提前知道顶点个数上限可以给驱动器机会来优化几何着色器在某些特定情况下的动作。我们知道对于每一个输入的顶点要输出的是一个扩展的四边形,因此我们设置最大顶点数为4。

(billboard.gs:7)

uniform mat4 gVP;
uniform vec3 gCameraPos;out vec2 TexCoord;

几何着色器得到了世界空间坐标,因此他只需要一个视图和投影组合变换矩阵即可。另外还需要知道相机的位置来计算如何让公告板始终朝向它。几何着色器为片段着色器创建出了纹理坐标,因此我们也要声明纹理坐标变量。

(billboard.gs:12)

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

上面一行代码是针对几何着色器独有的。由于在几何着色器中执行的是一个完整的图元,因此事实上我们可以访问组成图元的每一个顶点,这个通过内置的‘gl_in’变量实现。这个变量是一个结构体数组,每个结构体都包含了写入到顶点着色器gl_Position中的位置信息。为了访问顶点信息,我们可以使用要访问的顶点在图元中的索引找到。在这个特定的例子中,参数的输入结构为点列表,所以每个图元只有一个可访问的单独的点,可以使用'gl_in[0]'获取它。如果输入结构是个三角形,我们可能还会使用'gl_in[1]'和'gl_in[2]'来访问其他点。我么只需要使用顶点位置向量的前三个xyz分量,通过本地变量'.xyz'提取。

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

这里我们利用文章开始背景部分结尾的原理实现让公告板朝向相机。我们将当前公告板的位置点到相机位置的向量和垂直向上的方向向量做叉积,得到从相机看公告板视角的‘右’向量,然后我们要使用这个向量围着公告板的位置扩展一个四边形面。

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

顶点缓冲器中的点可以被认为是四边形底边的中点,我们要从中点创建两个面朝相机的正面三角形。开始先用中点减去‘右’向量的一半,从而得到四边形的左下角。然后通过乘以视图和投影组合变换矩阵计算该点在裁剪空间的位置,并设置该点的纹理坐标为(0,0),便于将整个纹理完整贴到这个平面上。为了将新产生的顶点传递到管线的下一个阶段,我们需要调用内置的EmitVertex()函数。这个函数调用后,我们之前写入gl_Position的数据就无效了,因此我们要为其设置新值。和左下角点产生方法类似的,我们继续创建出四边形左上角和右下角的点,这样三个点就构建出了第一个正面三角形。由于几何着色器的输出是三角带,之后我们只需要另外一个顶点即可构建第二个三角形,使用前面三角形的后两个顶点(四边形的对角线)和新顶点构建。第四个新顶点也是最后一个顶点,即四边形的右上角。结束三角带的构建要调用内置的EndPrimitive()函数。

(billboard.fs)

#version 330uniform sampler2D gColorMap;in vec2 TexCoord;
out vec4 FragColor;void main()
{FragColor = texture2D(gColorMap, TexCoord);if (FragColor.r == 0 && FragColor.g == 0 && FragColor.b == 0) {discard;}
}

片段着色器很简单,它的主要工作是使用几何着色器创建的纹理坐标进行纹理采样。这里有一个新特性:内置的关键字'discard'用于在某些情况下将某些像素片元完全丢弃。这个教程中我们用了Doom中地狱骑士的图片,展示黑色背景下的怪物场景,但是直接贴上整张图片会有黑色的背景,就是说公告板内容比怪物要大,怪物背景不是透明的,我们不希望这样,因此我们可以检测文素的颜色,如果是黑色就直接抛弃该像素,这样就会只显示怪物了。可以尝试注释掉'discard'语句看效果有什么不同。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/245610.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python 当前目录_virtualenvwrapper打造多版本Python环境

前言面对多个 Python 开发项目时,需要针对不同的项目创建相应的开发环境。通常情况下,使用 virtualenv 创建一个虚拟的独立 Python 环境,但是 virtualenv 创建的环境相对分散不便于管理。这里推荐使用 virtualenvwrapper 来创建集中的便于管理…

oracle装了客户端怎么登陆账号,分享Oracle 11G Client 客户端安装步骤(图文详解)...

Oracle 11G Client 客户端安装步骤,具体如下:下载地址:http://www.gimoo.net/database/167737.html先将下载下来的ZIP文件解压,并运行setup.exe文件。执行到第四步之后,出现错误,直接点全部忽略就可以了。把…

python与excel互通_【python】python vs Excel ( 与mysql数据库之间的交互)

【python】python vs Excel ( 与mysql数据库之间的交互) 通过python与mysql数据库做交互 到目前为止大部分案例的演示数据都是基于文件进行读取的。那么python如何跟数据库之间做交互才是未来我们真正需要关心的。因为我们的数据最终还是要存储到数据库中去的。 python与数据库…

基于matlab的车牌识别系统程序,基于matlab的车牌识别系统的设计(附程序).doc

基于matlab的车牌识别系统的设计(附程序).doc 1车牌识别系统的设计1.摘要:汽车牌照自动识别系统是制约道路交通智能化的重要因素,包括车牌定位、字符分割和字符识别三个主要部分。本文首先确定车辆牌照在原始图像中的水平位置和垂直位置,从而定位车辆牌照…

python英文词云代码_使用python实现个性化词云的方法

先上图片词云图 需要模板 pip install jieba pip install wordcloud 还需要安装另外两个东西这两个我也不太懂借鉴百度写上去的 pip install scipy pip install matplotlib 因为用ubuntu系统所有没有windows那么麻烦,也没有那么多报错 看到好多人制作自己的词云有没…

linux监测node进程,通过node_exporter监控linux服务器一

前言:node_exporter用于监控*nux系统,使用go编写的收集器prometheus服务器:192.168.199.222监控服务器 192.168.199.221在192.168.199.221下载node_exporterwget https://github.com/prometheus/node_exporter/releases/download/v*/node_exp…

k8s 离线安装_阿里开源 k8s 事件通知服务

背景在 Kubernetes 开源生态中,资源监控有 metrics-server、Prometheus等,但这些监控并不能实时推送 Kubernetes 事件,监控准确性也不足。当 kubernetes 集群中发生 Pod因为 OOM 、拉取不到镜像、健康检查不通过等错误导致重启,集…

kali linux解密栅栏密码,最详细bugku加密小白解法---持续更新!

bugku加密!安排本文持续更新1 摩斯密码2 栅栏密码3 Ook密码4 brain密码5 easycrypto密码6 base647 散乱的密文8 凯撒密码9 一段base6410 !?11 []-12 奇怪的密码--凯撒变式13 托马斯杰斐逊--转轮加密14 伪加密15 告诉你个秘密16 这不是MD517 贝…

linux命令 重定向%3e,linux输出信息调试信息重定向

在运行linux的时候有所有的调试信息可以分为三个部分1、bootloader输出信息U-Boot 1.3.2(Nov 19 2016 - 22:02:08)DRAM: 64 MBFlash: 512 kBNAND: 64 MiBIn: serialOut: serialErr: serialHit any key to stop autoboot: 0[yqliu2410 #] tftpFound DM9000 ID:90000a46 at addre…

360加固一键脱壳工具2020_如何脱壳加固过的Apk并利用其API“走近数据库”

0x00 寻找突破口打开首页,emm就一个登录页面,没了随便写点东西提交看看天生手欠的我一不小心就多输了一个单引号WDNMD,除了数字和字母其他都不行?这叫我怎么测?刚刚要放弃,就在这时,首页的一个二维码吸引了我正是安卓端的软件,眼前一亮,仿佛找到了打开新世界的大门开开心心地…

python 函数递归_Python零基础之三元表达式、函数递归、匿名函数教程!超级详细!...

目录一、三元表达式二、函数递归 递归调用的定义递归分为两个阶段:递归,回溯三、匿名函数 什么是匿名函数?有名字的函数与匿名函数的对比lambda匿名函数的应用四、内置函数 #注意:内置函数id()可以返回一个对象的身份,…

linux 内核空间占用cpu百分比过高,linux下分析java程序占用CPU、内存过高

一、CPU过高分析1)使用TOP命令查看CPU、内存使用状态可以发现CPU占用主要分为两部分,一部分为系统内核空间占用CPU百分比,一部分为用户空间占用CPU百分比。其中CPU状态中标示id的为空闲CPU百分比。当空闲CPU百分比越低,说明CPU占用率越高。2)…

springboot 获取application参数_LOOK ! SpringBoot的外部化配置最全解析

本篇要点介绍各种配置方式的优先级。介绍各种外部化配置方式。介绍yaml的格式及原理。介绍如何绑定并测试类型安全的属性配置。介绍ConfigurationProperties与Value的区别。一、SpringBoot官方文档对于外部化配置的介绍及作用顺序SpringBoot支持多种外部化配置,以便…

spark入门_入门必读 | Spark 论文导读

Resilient Distributed Datasets: A fault-tolerant abstraction for in-Memory cluster computing, 是讲述 Spark RDD 的基础论文,通读论文能给我们带来全景的 Spark 知识面摘要:RDD,全称Resilient Distributed Dataset,可伸缩性数据集。使用…

Qt在linux下无法输入中文,Ubuntu使用集成开发环境QT无法输入中文的解决方法

QT Creator是轻量级集成开发环境,在Ubuntu系统操作中,使用QT时无法输入中文,遇到这种情况要如何处理呢?下面小编就给大家介绍下Ubuntu如何解决QT无法输入中文问题。1 安装搜狗输入法,(如果你想用ubuntu自带的输入法也没…

altium pcb 信号高亮_在PCB设计中高效的放置元件技巧

在印刷电路板设计中,设置电路板轮廓后,将零件(占地面积)调用到工作区。然后将零件重新放置到正确的位置,并在完成后进行接线。组件放置是这项工作的第一步,对于之后的平滑布线工作是非常重要的工作。如果在接线工作期间模块不足&a…

linux升级ssh到6.6版本,CentOS6.5 openssh升级到openssh-7.6版本

CentOS6.5 openssh升级到openssh-7.6版本2018-8-3 foooy升级前保证故障后能现场处理,或者远程卡处理,否则不要这样直接升级yum install -y gcc openssl-devel pam-devel rpm-buildwget http://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-7.6…

python中csv文件通过什么表示字符_python_写入csv文件时候无法进行原样写入(写入字符串中出现逗号,时候,csv文件自动分成两个单元格)...

问题描述: 写入csv文件时候无法进行原样写入(写入字符串中出现逗号","时候,csv文件自动分成两个单元格) with open("test.csv","w") as f: f.write("闲暇时,我会被一段,配乐诗朗诵,所感动,悲伤…

rabbitmq怎样确认是否已经消费了消息_阿里Java研发二面:了解RabbitMQ?说说RabbitMQ可靠性投递...

上期写到高并发下RabbitMq消息中间件你应该介么玩今天给小伙伴说说!有自己看法的也可以在评论区留言探讨,也可以转发关注下我以后会长期分享!目录:确保消息发送到RabbitMQ服务器确保消息被正确的路由确保消息在队列正确地存储确保…

linux 装完yum不能用,【linux】yum 不能安装应用,提示There are no enabled repos Run “yum repolist all”...

回答一般来说著名的linux系统基本上分两大类:1 RedHat系列:Redhat、Centos、Fedora等2 Debian系列:Debian、Ubuntu等RedHat 系列:1 常见的安装包格式 rpm 包,安装rpm包的命令是 “rpm -参数”2 包管理工具 yum3 支持ta…