我小时候想成为任何人,除了我自己。现在我爱上了你,然而你心里却只有上帝。-《兰陵王的骑士》
不可否认,OpenGL是一个只有成绩好的学生才学的明白的课程。这也就注定了这是个小众知识的原因。但我们程序员本身这个职业又何尝不是呢。其实仔细思考一下,确实如此,比如我们经常讨论的问题如下:
多目标渲染
老丝~~,为什么我运行同一个程序,在Win10上和Win7上的显示结果不一样啊,是不是有什么BUG呀?
实际上Win10上出问题,多半是因为集成显卡的缘故,特别是笔记本,为了省电,在有双显卡的情况下,默认会用集成显卡。集成显卡我们知道是会出各种问题的,但是我们依旧可以通过一些手段去规避这些问题。比如当我们无法使用多目标渲染的时候,可以按照我们的课程中多通道渲染的方式解决,也就是渲染多次。
实际上你可以通过一些查询API去获取当前你运行的显卡可以渲染到多少个ColorBuffer。查询办法请参考我们给大家准备的QuickReferenceCard,你不妨把那个卡片通读一遍,自然会发现很多有用的东西,我们给大家准备的课后资料不是随便乱准备的。没有权威性的资料肯定不会给。
主题
理智的人没有品位?
实际上这是个错误的命题,艺术界不存在高低贵贱,谁能说什么样的品位算是高的,什么样的品位算是低的?实际上没几个人真正能说得清楚。智力水平和见识不一样,自然品位不一样,又何必去在乎大部分臭味相投的人的眼光。
程序员的品位
世界上最强大的语言是PHP,其他的都是垃圾。Python都是些外行学的,他们不懂什么是真正的爱情。
OpenGL程序员的品位
写程序根本不在于你用什么语言,真正的乐趣在于你可以把你学到的数学、物理甚至是柏拉图式的爱情都用在程序中。你没学懂OpenGL程序的时候觉得他巨繁琐,当你学懂了它的时候,你甚至连思考普通程序的架构都变得不一样的,你总会让所有可并行的东西并行。
实际上,学懂一门手艺,才能真正体会设计者的思想,这样会反过来强化你本身的所有编程技能。实际上这些图形API的设计者的编程思想是学完OpenGL后最大的收获,而不仅仅是那几行C++代码。
Compute Shader
一切都是可并行的!!!!!
与其他的Shader有明显的不一样,Compute Shader是个疯子,其他种类的Shader都是为了完成渲染管线中特定的部分而存在。而Compute Shader完全没有什么特定的任务,所以比较符合我们中国人不按规矩办事的风格。
Compute Shader中既没有什么标准的输入,也没有必须规定的输出。它的输入可以是一对顶点,也可以是来自一张图片。所以完全可以按照设计师的想法来。
如果你是学过Cuda程序设计的,那么你会明白,Compute Shader中,你设置的那些执行用的参数并不一定可以让你的算法更快更高效,你必须设计一个合适的值,这个值取决于你的程序在什么样的GPU上运行。
那么刚才设置的这些值是指什么呢?是下面这些参数
总数据块的大小
每个Group的大小
每次运行的数据块的大小
总数据块的大小,这个也有讲究的吗?答案是肯定的。做过Cuda程序设计的人肯定知道,并不是说你调用的时候说请每次处理5亿的数据,那么Cuda程序就真的每次处理5亿个的。你需要让你的GPU满负荷运转,这才能让你的程序最快运行。
那么为什么参数的设置可以让GPU变得不是满负荷运转呢?这就要考虑我们的GPU中的浮点数运算单元了。学完进阶课的学员肯定是知道,我们的GPU是一个并行运算的芯片。我们假设,我现在有一个GPU,总共有256x256个核心,我假设每个核心每个GPU时钟周期只能做一次浮点运算。那么我们为了使得我们GPU满负荷运转,我们需要让我们的GPU的执行的数据块大小为256x256的,这样才能保证GPU的核心不会发生空转。
如果你设置的数据块大小太大,那么会被分批次执行,那么总会有那么几次,部分GPU核心是空转的。
如果设置的数据块太小,那么肯定是有GPU在空转的。
但是总体来说,使用GPU加速的程序,一般提速都在50~200倍之间。
因此,我们可以看出在做GPU程序设计的时候,数据块的设计本身也是一门技术。
总数据
比如我的总数据块设计成为1000x1000,对于上面的GPU,肯定会产生空转。
但如果我的总数据块设计成为1024x1024,对于上面的GPU,就可以在此基础上设计出一套让GPU满负荷运转的调度。(因为1000x1000肯定不是256x256的整数倍,而1024x1024可以被切割成为整数个256x256大小的数据块)
当然这都是在极限编程的情况下。
分组
那么Group是哪里设置的呢?就是下面这个OpenGL的API
glDispatchCompute(x,y,z)
这个API就是设置的总数据被分为多少个Group,这里定义了Group的大小之后,系统就会自动对总数据进行分组了,分成若干个组(Group)。
总的数据被分组后,这些分组数据被处理的顺序是不可预测的,也就是说,你不知道哪一个分组的数据会被先处理,哪一个后被处理。这里Group之间是并行的,所以你不能让分组的数据之间产生跨组织的依赖性。
每次执行多少数据
这里设置的就是,每一个Group内,每次处理多少个数据。那么这个是哪里设置的呢?是在Shader中设置的,大家可以去翻开我们的Compute Shader课程中的shader代码看看
layout(local_size_x=256,local_size_y=256,local_size_z=1) in;
这就是设置每次我要处理多少个数据,上面的设置是,我每次要处理256x256这么大的数据块。如果我的Group是512x512大小的,那么要处理完毕这个Group的所有数据,需要使用Compute Shader对Group内的数据迭代4次,每次处理其中一块256x256大小的数据。
关于TransformFeedbackObject
有很多人一看到这个玩意就一头雾水,不知道这是干嘛的。
前不久有一个同学跟我们讲,骨骼动画的计算非常耗时,怎么办?我们给他的答案就是使用TransformFeedbackObject。实际上我们光这么讲你肯定又不觉得这靠谱。
但是你们熟悉的游戏引擎Unity中就使用了这个东西对骨骼动画进行处理的。不管怎么说,这种方式是被Unity验证过的,效果马马虎虎还过得去吧。
这个技术是OpenGLES3.0以后的版本就有了的,站在现在这个时间节点,覆盖率是100%。所以不要害怕自己用了特殊的技术,会不被支持,至少在主流的Android和iOS上是100%被支持的。
最后,我们在这里要指出一点的就是,如果有人去百度《兰陵王的骑士》这本书了的话,你就彻底的输了。因为这本书还没有出版。