您的位置:首页 > 编程语言

【图形学与游戏编程】开发笔记-基础篇4:程序方面的补充知识

2016-08-14 11:22 260 查看
(本系列文章由pancy12138编写,转载请注明出处:http://blog.csdn.net/pancy12138)

上一次的教程已经讲到了图形管线的实现过程,由于之前大部分篇幅都在进行理论方面的描述,所以这篇文章主要是补充一些前面教程没有提到过的程序方面的知识介绍,其中包括XNA库,HLSL的语法,以及directx的图形调试。

首先我们先讲解XNA库,这个库主要的作用就是在CPU中进行矩阵和向量的数学运算,也就是说这个库其实就和C语言的<math.h>的用处差不多,但是不同的是这个库里的大部分运算函数都是基于矩阵和向量的,至于原因前面也提到过,因为整个图形学的运算基础就是矩阵和向量。注意我们之前的一篇教程已经用到了这个库的一部分内容,比如根据程序运行的时间求那个几何体的平移,旋转,缩放矩阵就是靠这个库来完成的,还有求投影矩阵也是靠这个库完成的。在之后的求逆啊,做转置啊,做乘法啊这些也少不了使用这个数学库来完成。这一点相对于OpenGL来说是非常便利的,因为OpenGL是没有这种类似的数学库的,所以几乎所有的数学运算都要自己写,虽然其实也用不了多少时间但是总感觉自己写的库不是很放心,如果程序出了bug还得考虑考虑是不是哪个数学运算函数写错了啥的。以往的XNA库是附加在directx库里的,现在已经归属于windowsAPI里面了。所以大家其实只要在程序的开头添加一个#include<directxmath.h>就可以了。接下来我们要讲第二个非常重要的点,也就是XNA库不仅仅负责在CPU上进行数学计算,他还得负责把计算结果每一帧告知GPU以让GPU完成渲染工作。但是这里有一个冲突,那就是内存对齐的问题,由于CPU上的内存对齐是比较复杂的,我记得是取结构体最大的变量的整数倍作为对齐标准。但是GPU上的内存一般是以一个4维向量作为一个对齐标准。也就是说同一种数据结构,很有可能在CPU上的大小和在GPU上的大小是不一样的。因此XNA库提供了两种不同风格的数据结构以消除这种冲突,这也就是为什么大家看到上一节的程序里面,同样是矩阵变量,有些地方使用的是XMMATRIX,有些则是XMFLOAT4X4,也就是说这个XMFLOAT4X4就属于CPU上的变量,可以在结构体中以CPU变量的对齐方式和CPU上的其余变量进行结合。而XMMATRIX则是属于GPU的变量,一把只适用于像GPU传送结果。这两种变量是可以相互转换的,转换的方式如下:

XMFLOAT4X4 mat_need;
XMMATRIX rec_mat = XMLoadFloat4x4(mat_need);
XMStoreFloat4x4(&mat_need, rec_mat);其中XMLoadFloat4x4可以把XMFLOAT4X4转换成XMMATRIX,后面的那个函数的作用就刚好是反过来的,把XMMATRIX转换成XMFLOAT4X4,这也就是说我们在CPU上写程序啊,传变量啊的时候尽量使用XMFLOAT4X4类型的变量,然后在给GPU传外部变量的时候就要尽量使用XMMATRIX类型的变量。当然有人就有疑问了,这种做法看起来好他喵的复杂啊,那我就只用XMMATRIX或者XMFLOAT4X4进行操作难道会出神马异常吗

?当然,如果整个程序里面只用其中的一种来进行设计,也有概率神马异常都不会出现,但是..........这也就是说也有概率出现一些奇葩的错误啦。首先我们来看看只用GPU的类型来进行程序设计,也就是说传参数以及设计结构体的时候都用类似于XMMATRIX啊,XMVECTOR啦这些GPU对齐的变量。这种情况下,出问题的一般就是CPU这边,是否出问题则取决于操作系统和编译器的版本,越新的操作系统和编译器,就越容易出问题。出的问题呢,也很简单,就是直接报内存错误然后程序崩溃........这就是我们玩游戏的时候经常出现的所谓的“系统不兼容”最大的原因。因为windows系统一般来说是系统方面是完全向下兼容的。但是更高级的系统一般要求也更严格,对于一些可能存在的隐患,假如在win7上被忽略的话,那么在win8上可能就直接被报错,这里举一个最简单的例子,以往很多游戏程序员喜欢在窗口重绘消息里面加上drawcall,但是一开始showwindow的时候directx还没有注册好,这个时候调drawcall是不会成功的,那么在win7下,不成功的后果就是第一帧白屏,然后继续正常的跑程序。但是在win8下就不一样了,那就直接报错然后程序崩溃了.......还有一种情况就是很多人不清楚窗口的绘制区域大小和窗口的大小实际上是不一样大的,如果设置成一样大的话在之前的驱动下画面都很正常,但是更新了比较新的N卡驱动之后就会发现直接因为渲染区域大小不匹配而产生白屏。这些都是因为引擎设计者没有考虑这些数据的细节所导致的。并不是因为神马系统不兼容啊,或者驱动不兼容导致的。而如果大家把那些GPU上对齐的变量在CPU上一路用到底的话,有可能就会出现在你的机子上跑的挺正常然后换了机子直接就炸的情况。然后我们再来说只使用CPU上的变量的情况,这个时候有小伙伴就说啦,这个乱用莫不是会让GPU直接崩溃的节奏??当然,这肯定是不会的,如果只使用CPU上的变量的话,是不会出现崩溃的问题的,但是会出现所谓的隐藏bug问题。这里举个例子,比如说你的外部变量是float3+float2这种类型的,那么你在给他们赋值的时候一定要注意按照四个四个的格式把它们补齐,也就是最好改成float4+float4这种的外部变量,如果没补齐的话,就会出现数据错位或者数据丢失的bug......这种bug在过去不能调试GPU上的代码的时候,那是相当的坑爹的,因为除非是有经验的程序员,不然看不出来程序上有什么问题,而且渲染结果就是不对或者干脆连结果都没有,让人灰常的头疼。不过这部分的bug主要是对于程序员来说影响比较大,在玩游戏的人那基本上不会因为这个bug出现程序崩溃的情况。
以上就是XNA库的一些补充知识,由于XNA库大家以后几乎每个程序都会用到所以这里我把大部分可能出现的问题和bug先提前说一些,这样大家以后写的程序如果出现bug了也有调试的方向。接下来我们来讲HLSL的一些语法,HLSL的语法大部分和C语言是相同的,也就是说像循环啊,分支啊,变量定义啊这些都是沿用c语言的规则。我们这里主要就讲讲他和C语言不一样的地方,首先是外部变量,这个之前提到过,它属于定义之后由CPU对齐进行赋值的一个变量,当然有人就有疑问了,如果CPU每一帧给几千个线程都赋值这么一个变量的话,会不会影响速度。这里我们讲一下,由于GPU是SIMD架构的,也就是单指令多数据流,CPU只会给GPU赋值一次变量,然后所有线程只是顶点数据不同,而这个外部变量都是共享的。所以并不会在很大程度上影响速度。其次为了方便外部变量的快速赋值,我们会把统一访问频率的变量归为一类,比如每个几何体都要变更的,每帧都要变更的等等,这样就可以让GPU进行数据的合并以加快更新速度:

cbuffer perframe
{
pancy_light_dir dir_light_need[10]; //方向光源
pancy_light_point point_light_need[5]; //点光源
pancy_light_spot spot_light_need[15]; //聚光灯光源
float3 position_view; //视点位置
int num_dir;
int num_point;
int num_spot;
};
cbuffer perobject
{
pancy_material material_need; //材质
float4x4 world_matrix; //世界变换
float4x4 normal_matrix; //法线变换
float4x4 final_matrix; //总变换
float4x4 shadowmap_matrix; //阴影贴图变换
float4x4 ssao_matrix; //ssao变换
};
如上图所示,我们使用cbuffer标志来对不同访问频率的外部便利呢进行归类。接下来我们要说的一点不一样的就是对于基础变量(向量,矩阵)的访问,这里HLSL是可以非常灵活的访问到所有打包基础变量的内部数据的,比如color.rg就可以得到一个代表color向量的红绿分量的二维的变量。通过这种方法我们可以轻易地对一个向量或者矩阵进行内部数据的更改或者调用。最后我们要提到的就是GPU的每个线程的运算速度以及集成度相比于CPU可以说是差之甚远的,所以大家在写程序的时候,能写的简练就写得简练,能不用循环就不用循环,能用内置函数解决的问题尽量用内置函数解决。这样才能写出最高效的程序,要知道你的程序复杂度多一点,所造成的影响就是几千个线程在执行这个程序的时候都会变得很慢。像归一化啦,求反射啦,求对数啦这些运算,尽量都用那些内置的函数来解决。这样既写的简练,也能够提升程序的运行速度。
最后我们来讲一下directx的图形调试机制,这也是相比于OpenGL而言,directx的又一优势所在(不得不说,微软的大腿很粗

........)。接下来我们来看对于上一片博客的程序,我们怎么才能够分段进行调试:



如上图所示,在vs界面下,点击调试->图形->启动诊断就可以进入图形调试界面:



在上述界面中每按一次截屏就可以得到一帧的存储数据,关闭程序后,点击刚才截屏得到的帧就可以开始这一帧的调试:



上述就是调试的界面,对于一般的问题,大家可以在下面直接点击vertexshader或者pixelshader的运行来进行分步调试。如果是需要针对某个像素的话可以在图上点击某个像素之后,调试它的的所有渲染过程:



这个调试工具极大的方便了我们来寻找GPU上程序的错误位置,大家以后再写程序的时候如果遇到了一些渲染效果的错误就不需要再去猜测HLSL的错误了,直接在这个界面进行调试就可以很快地寻找到shader的一些错误的地方。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐