在3.1节中的程序中,我们在RendererApplication::OnInitialize()中看到有下面一段代码,这段代码创建了一个转换到摄像机空间的矩阵和转换到投影空间的矩阵,并将他们传递给了渲染器。到目前为止,我们还没有创建摄像机类,因为对渲染器来讲,摄像机对象最终的主要作用就是将这两个矩阵数据传递给着色器,因此在还没涉及到复杂的场景管理之前,我们先介绍这两个矩阵的原理。
1 ...
2 Matrix4x4f vMat, proMat;
3 vMat.BuildCameraLookAtMatrix(Vector3f(0, 0, 0), Vector3f(0, 0, -1), Vector3f(0, 1, 0));
4 proMat.BuildProjectionMatrixPerspectiveFovRH(PI / 6.f, 1.0f * GetWindowHeight() / GetWindowWidth(), 1.0f, 1000.f);
5 Renderer->SetGlobalUniform("mvpMat", (proMat * vMat).m, sizeof(Matrix4x4f));
6 ...
上面代码第三行创建了由世界坐标空间转换到摄像机空间的矩阵,这个矩阵的计算添加到了Matrix4x4这个类中,其代码如下:
1 template <typename T>
2 Matrix4x4<T> &Matrix4x4<T>::BuildCameraLookAtMatrix(const Vector3<T> &position, const Vector3<T> &dir, const Vector3<T> &upVector)
3 {
4 Vector3<T> zaxis = -dir;
5 zaxis.Normalize();
6
7 Vector3<T> xaxis = upVector.CrossProduct(zaxis);
8 xaxis.Normalize();
9
10 Vector3<T> yaxis = zaxis.CrossProduct(xaxis);
11 yaxis.Normalize();
12
13 m00 = (T)xaxis.x;
14 m01 = (T)xaxis.y;
15 m02 = (T)xaxis.z;
16 m03 = (T)-xaxis.DotProduct(position);
17
18 m10 = (T)yaxis.x;
19 m11 = (T)yaxis.y;
20 m12 = (T)yaxis.z;
21 m13 = (T)-yaxis.DotProduct(position);
22
23 m20 = (T)zaxis.x;
24 m21 = (T)zaxis.y;
25 m22 = (T)zaxis.z;
26 m23 = (T)-zaxis.DotProduct(position);
27
28 m30 = 0;
29 m31 = 0;
30 m32 = 0;
31 m33 = 1;
32
33 return *this;
34 }
在2.2矩阵章节我们已经知道,3D空间中的点表示为基向量空间的线性组合,于是点要由世界空间转换到摄像机空间,首先要计算摄像机空间坐标系的三个基向量,通常我们通过摄像机的位置、方向和向上向量就可以计算这个矩阵了。我们定义摄像机的方向为z轴的负方向(使用右手坐标系),那么负的摄像机的方向就是摄像机空间坐标系的z轴方向,通过向上方向和z轴的叉乘,我们可以计算出x轴方向,再次进行z轴和x轴的叉乘,最后得到了y轴的方向(上面代码4~11行),我们将计算到的基向量赋值给矩阵的每一行,这就是得到了由世界到摄像机空间的旋转矩阵。但摄像机还有位置信息,这个位置就是摄像机坐标系在世界坐标系中旋转后进行的位移,而位移值就是位置在摄像机坐标系各轴上的投影大小,因此在计算最终摄相机空间的坐标时,需要减去这个位移值,在构造矩阵中代码为上面的第16、21、26行。下图为2D空间中的例子,由xOy先旋转到x'Oy',再位移到x''O''y'',p为空间中的一点。

在空间中的点变换到相机空间后,就可以进行投影操作,摄像机的投影分为正交投影和透视投影,我们以透视投影为例,如果屏幕的宽度/高度为aspect,那么我们在这个步骤的目的是将相机空间中的xyz坐标映射到[-1, 1](OpenGL使用的z映射值为[-1,1],DX为[0, 1],这里以[-1,1]说明),只有落到这些范围内的坐标点才会进行最终的渲染计算, 先看代码,然后我们来讲解矩阵中的这些值是怎么计算来的:
1 template <typename T>
2 Matrix4x4<T> &Matrix4x4<T>::BuildProjectionMatrixPerspectiveFovRH(T fieldOfViewRadians, T aspectRatio, T zNear, T zFar)
3 {
4 const T h = static_cast<T>(1.0 / tan(fieldOfViewRadians * 0.5f));
5 assert(aspectRatio != 0.f);
6 const T w = static_cast<T>(h / aspectRatio);
7 assert(zNear != zFar);
8
9 m[0] = w;
10 m[1] = 0;
11 m[2] = 0;
12 m[3] = 0;
13
14 m[4] = 0;
15 m[5] = h;
16 m[6] = 0;
17 m[7] = 0;
18
19 m[8] = 0;
20 m[9] = 0;
21 m[10] = ((zNear + zFar) / (zNear - zFar));
22 m[11] = (T)(2.0f * zNear * zFar / (zNear - zFar));
23
24 m[12] = 0;
25 m[13] = 0;
26 m[14] = (T)-1;
27 m[15] = 0;
28
29 return *this;
30 }
上面函数是计算透视投影矩阵的代码,函数传入的参数为摄像机的fov、屏幕的宽高比、近裁剪面和远裁剪面的距离,如下图,摄像机的fov为θ,点P在垂直方向投影到近裁剪面上一点Q,近裁剪面为我们的目的投影面,因此利用三角函数可以计算出投影平面上的点

为:


而这个值要映射到[-1,1] ,由于宽高比的存在,宽度方向需要除以aspect,最终P'x为

正常来讲,图形计算出了x和y的裁剪坐标,就可以转到屏幕空间坐标进行绘制了,但是在光栅化时,我们还需要用到z值。这里z值主要有两个作用:
1. 深度测试,光栅化阶段会对每个像素位置进行深度测试,以保证最前面的图形被正确绘制。
2. 计算顶点属性的插值。
第二点需要特殊说明,顶点属性除了位置之外还有颜色、纹理坐标等信息,光栅化阶段,需要对三角形内的这些信息进行插值计算出每个片元像素的顶点属性,下图为一条线段向平面投影的示意图,在投影平面上均匀采样,但实际线段上采样点的间隔却是随着位置离投影平面的距离增大而增大,这表明投影后顶点属性的插值计算不是线性的。

上图中的线段我们可用方程表示为:

由相似三角形知道:

解关于x的方程,上式代入直线方程得到:

将直线方程写成1/z的形式:

对于投影平面上的一点p3,令:

则有:

由上式知道,三角形面投影后z值的倒数是线性插值。对于一个顶点属性b,光栅化时需要根据b1和b2计算插值属于b3,这两个顶点的z值为z1和z2,则有:

而z3的值为:

最终可以解得:

由上面的公式,我们就可以在光栅化阶段计算出正确的属性插值结果,这个过程叫做透视校正。
通过上面我们知道,透视校正计算插值时需要乃至1/z的值,因此最终需要求得的z'值可以表示为下面的线性关系:

其中右手坐标系中z的范围为(-n,-f),z'的范围为(-1,1),代入上式有


解上面方程得到:


于是有投影后的z'值为:

这里点P进行投影后的点值都进行了除以z值的操作,因此投影后的Q点的齐次坐标为:

用一个矩阵来表示这个计算为:

到目前为止,我们计算出了ViewMatrix和ProjectMatrix,这是后面用到摄像机的核心内容,此外还有正交投影矩阵计算,其计算过程与上述类似,正交投影的代码如下:
1 template <typename T>
2 Matrix4x4<T> &Matrix4x4<T>::BuildProjectionMatrixOrthoRH(T widthOfViewVolume, T heightOfViewVolume, T zNear, T zFar)
3 {
4 assert(widthOfViewVolume != 0.f);
5 assert(heightOfViewVolume != 0.f);
6 assert(zNear != zFar);
7
8 m[0] = (T)(2 / widthOfViewVolume);
9 m[1] = 0;
10 m[2] = 0;
11 m[3] = 0;
12
13 m[4] = 0;
14 m[5] = (T)(2 / heightOfViewVolume);
15 m[6] = 0;
16 m[7] = 0;
17
18 m[8] = 0;
19 m[9] = 0;
20 m[10] = (T)(2 / (zNear - zFar));
21 m[11] = (T)((zNear + zFar) / (zNear - zFar));
22
23 m[12] = 0;
24 m[13] = 0;
25 m[14] = 0;
26 m[15] = 1;
27
28 return *this;
29 }
通过矩阵计算获取了裁剪空间的坐标之后,需要将坐标转换到屏幕空间才能进行光栅化操作,以Windows窗口为例左上角为屏幕空间的(0,0)点,向中为x正方向,向下为y正方向,则对每个顶点位置进行转换的计算过程为:


到此,我们已经为光栅化所需要的数据做好了准备,下一步就是进行光栅化操作了。
我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现