可编程管线浅析与 Hello World | Cocos 3D Shader 入门系列03
一、最简单的 Shader
在上一篇《Cocos Shader 入门基础二:初识 Cocos Shader》中我提到说,以后的 Cocos Shader 将在 builtin-unlit.effect 基础上逐步添加内容,渐进式学习。
但有不少开发者反馈:
只是粗浅的介绍了各个部分的含义,Google翻译也能做啊。
能不能讲讲基础知识啊,还是啥也不懂啊。
照顾一下新人呗,麻烦从零开始啦!
于是我做了一个大胆的决定:删除不必要的东西,从最最最最简单的 effect 开始,简单到缺少任何一行代码,就跑不起来的地步。
我把它命名为 cocos-shader-helloworld.effect,它将渲染出下面的效果。

它目前很简单,但随着教程的进行,它将变得越来越「胡里花哨」
为了使大家更好地理解各个细节,麒麟子在 Shader 中加了注释,请看下图:

二、可编程管线浅析
麒麟子一直在想,「3D 可编程管线」到底应该以什么样的方式,什么样的程度讲给大家听。
看到上面的截图时我灵机一动。
我们不如就从上到下,以讲解 Cocos Shader 的角度,对涉及到的 3D 管线知识进行讲解。
2.1、定义部分
在 cocos shader effect 中,可以定义多个 technique,每一个 technique 主要的属性就是 name 和 pass。
在上面的 cocos-shaer-helloworld.effect 中我们定义了一个叫 opaque 的 technique,且这个 technique 只有一个 pass。
如果你不喜欢,可以改掉 technique 的名字,这不会对效果造成任何影响。
2.2、vert 函数(顶点着色器)
在本案例中,顶点着色器的入口函数是 vert()。
在 pass 中,通过 vert:unlit-vs:vert 引用。
unlit-vs 就是 CCProgram 的名字,在一个 effect 文件中,我们可以定义多个 CCProgram,每一个 CCProgram 中可以有多个函数,然后根据配置来决定入口函数。
在本文所示例的 vert 函数中,我们只做了最基本的位置信息输出:顶点坐标信息在经历了世界变换,摄像机变换,投影变换后,作为 vert 函数的返回值。
2.3、顶点坐标从本地到屏幕的经过
很多朋友误以为经过投影变换后的坐标就是屏幕坐标,那是不对的。
vert 函数输出的坐标并不是屏幕坐标,在有些书上把这个叫 裁剪坐标。不管叫什么,大家记住它就是投影后的坐标就行。
这个投影后的坐标,为了适应不同的显示设备,会做一次规范化设备坐标系(NDC)处理。
NDC 处理过后,会进行视口映射。
视口映射结束后,才是显示到窗口上。
麒麟子用拙劣的绘图能力给大家绘制了下面这张变换图。可以清晰的看到顶点坐标需要经历的变换步骤。
注:投影变换之后,坐标信息就不受 Shader 控制了。

2.4、光栅化
顶点着色器之后,并不会直接传递给像素着色器,而是会先把顶点着色器输出的东西进行插值、像素化。
这个过程有一个术语叫:光栅化
如下图所示,三角形经过光栅化后,变成了一个个像素。

除了顶点位置信息,顶点法线、颜色、纹理坐标等都会先经过光栅化,再传递给像素着色器。
由于所有 vert 输出的值都会被光栅化,所以顶点着色器传递到像素着色器的法线向量,在使用的时候,记得先 normalize,否则会有意想不到的效果。
关于光栅化的内容,建议大家多在网上搜索资料看看,有一个更深入的了解。
2.5、frag函数 (像素着色器)
光栅化之后的顶点信息会被传递给像素着色器。
后期的教程中,我们为了实现一些高级效果,其实大部分情况下是对 frag 函数的增强。在本文的示例中,我们为了尽可能减少大家的理解成本,麒麟子连 color 都没有从外部传递过来,直接在代码中定义了颜色。
大家可以修改 frag() 函数中的颜色值来查看像素变化
这里顺便说一下,像素着色器(Pixel Shader)和片元着色器(Fragment Shader)是一个东西。前者来自于 Direct3D 圈,后者来自于 OpenGL 圈。
2.6、像素的一生
像素着色器处理之后,像素还会进行一系列的测试和操作,只有测试都通过的像素,才会被写入到目标缓冲区中。如下图所示:

2.6.1、模板测试(Stencil Test)
模板测试会根据预先设置好的模板测试参数进行工作,并决定是否要丢弃像素。
在本文里,麒麟子不打算进一步讲模板测试的细节。
如果后面的章节有用到模板测试的地方,会进行详细说明。
如果现在就想了解模板测试的朋友,请自行搜索。
2.6.2、深度测试(Depth Test)
「深度测试」需要深度缓冲区的配合,请先查看本文 6.4 小节中的「深度缓冲区」概念。
深度测试提供了 > >= == < <= != 总是,从不等比较运算符,默认是 <=。
当一个像素进入深度测试环节时,会进行如下操作。

2.6.3、融合 (Alpha Blend)
如果一个像素以上所有测试都通过了,则会进入融合处理阶段。
融合就是我们经常说的透明混合。
它会将当前像素的值按照我们设置好的混合方式,与目标颜色缓冲区的值进行融合。
如果没有开启 Alpha Blend 开关,则这阶段自动跳过。
我们常见的 alpha 混合因子如 src_alpha、one_minus_src_alpha 等就是用在这个操作上面的。
关于 alpha blend 参数细节和使用技巧,我们会在后面的讲解中深入解释。
2.6.4、写入帧缓冲区
最终,像素会被写入缓冲区中。帧缓冲区有三个:颜色缓冲区、深度缓冲区、模板缓冲区。
颜色缓冲区(Color Buffer):
颜色缓冲区,故明思意,存储颜色的缓冲区。这个缓冲区就是我们屏幕上能看到的缓冲区。
深度缓冲区(Depth Buffer):
是一个看不见的缓冲区。
深度缓冲区存储的是一个与顶点z值相关的值(这个z值是摄影后的z值,由于它处于摄像机空间,由近即远,所以我们称它为深度)。
这个缓冲区使我们在渲染非透明物体的时候,不用管先后顺序,也能保证结果的正确性,大大提高了绘制效率。
眼尖的朋友就会问,透明物体怎么办呢?
透明物体是需要从远到近进行渲染的,可以搜索“油画家算法”作进一步研究。
当然,Cocos 引擎已经做了这个排序的事情,不必过于担忧。
如果渲染状态 深度写未开启,则不会进行深度缓冲区写入。
模板缓冲区(Stencil Buffer):
这个缓冲区可以理解为一个标记缓冲区,他提供了一些比较运算操作,用于实现一些特殊效果。
如果渲染状态开关 模板缓冲未开启,则不会进行模板缓冲区写入
模板缓冲区的背景知识,建议大家多看其他资料。
这里只是简单介绍了颜色缓冲区、深度缓冲区、模板缓冲区的基本概念,如果要展开来讲各个细节,可能够写好几篇文章了。
想要了解更多细节的朋友,请自行搜索关键字。
三、总结
根据开发者朋友们的反馈,麒麟子在本文中直接将 Cocos Shader 的学习拉回到了原点,真正的从零开始。
虽然本文的 cocos-shader-helloworld 非常简单,但随着教程的进行,它会越来越丰富。
它越是华丽,代表你 Shader 的学习越有进步。
本文也对「3D 可编程管线」做了一个简要的梳理,然而由于篇幅有限,不可能详尽地讲每一个知识点,好在网络上已经有很多相关优质的文章。
希望大家阅读完成之后,以本文为题纲,针对文中提到的各种术语和关键字进行搜索,去拓展自己的相关知识。
编程这个事情,只要下功夫多练,收获不会差的。
借卖油翁的一句话:
我亦无它,但手熟尔!
《Cocos Creator 3D Shader 零基础编程入门》,是由麒麟子开设的系列教程。拥有十五年编程经验,曾参与开发过自用引擎的麒麟子,一直在不断免费输出 Cocos 游戏开发经验和教程。
这个系列将通过基础知识讲解融合原理科谱的方式,带领大家一起学习 Cocos Creator 3D 材质系统,并将结合具体案例进行实操教学。欢迎感兴趣的开发者关注学习!
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
