【重写了】Cocos Shader入门基础三:可编程管线浅析与Hello World

Tony is Coding

一、写在前面


你没看错,这篇文章重新发了。

因为C姐说,之前的版本没头没尾。

现在想想,确实有点突兀了。

可编程管线基础在后面的学习中至关重要,所以我觉得有必要认认真真优化一次。

昨天朋友跟我说:好汉不提当年勇,何况当初那么菜鸡,你写公众号里干啥?

我回答说:菜鸡怎么了?菜鸡就不是鸡了?

每当写这个系列文章的时候,脑海中就有一种奇特的画面。

但具体是什么,我抓不住,一种似曾相识却又恍如隔世的感觉。

我总是忍不住要把自己当初学Shader时候的窘迫分享出来。

目的是为了激励大家:没有人生来就会,但不去学,就永远都不会。

二、最简单的shader


在上一篇《Cocos Shader入门基础二:初识Cocos Shader》中我提到说,以后的Cocos Shader将在builtin-unlit.effect基础上逐步添加内容,渐进式学习。

但有不少C友反馈:

麒麟子,这就是你说的教程?只是粗浅的介绍了各个部分的含义,Google翻译也能做啊。

麒麟子,能不能讲讲基础知识啊,你这么一讲,还是啥也不懂啊。

麒麟子,照顾一下新人呗,麻烦从零开始啦。

于是我做了一个大胆的决定,删除不必要的东西,从最最最最简单的effect开始,简单到缺少任何一行代码,就跑不起来的地步。

我把它命名为cocos-shader-helloworld.effect,它将渲染出下面的效果。

image-20201012220010555

它目前很简单,但随着教程的进行,它将变得越来越胡里花哨。

为了使大家更好地理解各个细节,麒麟子在shader中加了注释,请看下图:

三、可编程管线浅析


麒麟子一直在想,3D可编程管线到底应该以什么样的方式,什么样的程度讲给大家听。

看到上面的截图时我灵机一动。

我们不如就从上到下,以讲解Cocos Shader的角度,对涉及到的3D管线知识进行讲解。

3.1、定义部分

在cocos shader effect中,可以定义多个technique,每一个technique主要的属性就是name和pass。

在上面的cocos-shaer-helloworld.effect中我们定义了一个叫opaque的technique,且这个technique只有一个pass。

如果你不喜欢,可以改掉technique的名字,这不会对效果造成任何影响。

3.2、vert函数(顶点着色器)

在本案例中,顶点着色器的入口函数是 vert()。

在pass中,通过 vert:unlit-vs:vert 引用。

unlit-vs 就是CCProgram的名字,在一个effect文件中,我们可以定义多个CCProgram,每一个CCProgram中可以有多个函数,然后根据配置来决定入口函数。

在本文所示例的vert函数中,我们只做了最基本的位置信息输出:顶点坐标信息在经历了世界变换,摄像机变换,投影变换后,作为vert函数的返回值。

3.3、顶点坐标从本地到屏幕的经过

很多朋友误以为经过投影变换后的坐标就是屏幕坐标,那是不对的。

vert函数输出的坐标并不是屏幕坐标,在有些书上把这个叫 裁剪坐标。不管叫什么,大家记住它就是投影后的坐标就行。

这个投影后的坐标,为了适应不同的显示设备,会做一次规范化设备坐标系(NDC)处理。

NDC处理过后,会进行视口映射。

视口映射结束后,才是显示到窗口上。

麒麟子用拙劣的绘图能力给大家绘制了下面这张变换图。可以清晰的看到顶点坐标需要经历的变换步骤。

注:投影变换之后,坐标信息就不受Shader控制了。

3.4、光栅化

顶点着色器之后,并不会直接传递给像素着色器,而是会先把顶点着色器输出的东西进行插值、像素化。

这个过程有一个术语叫:光栅化

如下图所示,三角形经过光栅化后,变成了一个个像素。

image-20201010002530740

除了顶点位置信息,顶点法线、颜色、纹理坐标等都会先经过光栅化,再传递给像素着色器。

由于所有vert输出的值都会被光栅化,所以顶点着色器传递到像素着色器的法线向量,在使用的时候,记得先normalize,否则会有意想不到的效果。

关于光栅化的内容,建议大家多在网上搜索资料看看,有一个更深入的了解。

3.5、frag函数 (像素着色器)

光栅化之后的顶点信息会被传递给像素着色器。

后期的教程中,我们为了实现一些高级效果,其实大部分情况下是对frag函数的增强。在本文的示例中,我们为了尽可能减少大家的理解成本,麒麟子连color都没有从外部传递过来,直接在代码中定义了颜色。

大家可以修改frag()函数中的颜色值来查看像素变化

这里顺便说一下,像素着色器(Pixel Shader)和片元着色器(Fragment Shader)是一个东西。前者来自于Direct3D圈,后者来自于OpenGL圈。

3.6、像素的一生

像素着色器处理之后,像素还会进行一系列的测试和操作,只有测试都通过的像素,才会被写入到目标缓冲区中。如下图所示:

image-20201010004035477

3.6.1、模板测试(Stencil Test)

模板测试会根据预先设置好的模板测试参数进行工作,并决定是否要丢弃像素。

在本文里,麒麟子不打算进一步讲模板测试的细节。

如果后面的章节有用到模板测试的地方,会进行详细说明。

如果现在就想了解模板测试的朋友,请自行搜索。

3.6.2、深度测试(Depth Test)

深度测试需要深度缓冲区的配合,请先查看本文6.4小节中的深度缓冲区概念。

深度测试提供了 > >= == < <= != 总是,从不等比较运算符,默认是 <=。

当一个像素进入深度测试环节时,会进行如下操作。

3.6.3、融合 (Alpha Blend)

如果一个像素以上所有测试都通过了,则会进入融合处理阶段。

融合就是我们经常说的透明混合。

它会将当前像素的值按照我们设置好的混合方式,与目标颜色缓冲区的值进行融合。

如果没有开启Alpha Blend开关,则这阶段自动跳过。

我们常见的alpha混合因子如src_alpha、one_minus_src_alpha等就是用在这个操作上面的。

关于alpha blend参数细节和使用技巧,我们会在后面的讲解中深入解释。

3.6.4、写入帧缓冲区

最终,像素会被写入缓冲区中。帧缓冲区有三个:颜色缓冲区、深度缓冲区、模板缓冲区。

颜色缓冲区(Color Buffer):

颜色缓冲区,故明思意,存储颜色的缓冲区。这个缓冲区就是我们屏幕上能看到的缓冲区。

深度缓冲区(Depth Buffer):

是一个看不见的缓冲区。

深度缓冲区存储的是一个与顶点z值相关的值(这个z值是摄影后的z值,由于它处于摄像机空间,由近即远,所以我们称它为深度)。

这个缓冲区使我们在渲染非透明物体的时候,不用管先后顺序,也能保证结果的正确性。

大大提高了绘制效率。

眼尖的朋友就会问,透明物体怎么办呢?

透明物体是需要从远到近进行渲染的,可以搜索“油画家算法”作进一步研究。

当然,cocos引擎已经做了这个排序的事情,不必过于担忧。

如果渲染状态 深度写未开启,则不会进行深度缓冲区写入。

模板缓冲区(Stencil Buffer):

这个缓冲区可以理解为一个标记缓冲区,他提供了一些比较运算操作,用于实现一些特殊效果。

如果渲染状态开关 模板缓冲未开启,则不会进行模板缓冲区写入

模板缓冲区的背景知识,建议大家多看其他资料。

这里只是简单介绍了颜色缓冲区、深度缓冲区、模板缓冲区的基本概念,如果要展开来讲各个细节,可能够写好几篇文章了。

想要了解更多细节的朋友,请自行搜索关键字。

四、总结


根据C友们的反馈,麒麟子在本文中直接将Cocos Shader的学习拉回到了原点,真正的从零开始。

虽然本文的cocos-shader-helloworld非常简单,但随着教程的进行,它会越来越丰富。

它越是华丽,代表你Shader的学习越有进步。

本文也对3D可编程管线做了一个简要的梳理。

然而由于篇幅有限,不可能详尽地讲每一个知识点。

好在网络上已经有很多相关优质的文章。

希望大家下来后,以本文为题纲,针对文中提到的各种术语和关键字进行搜索,去拓展自己的相关知识。

编程这个事情,只要下功夫多练,收获不会差的。

借卖油翁的一句话:

我亦无它,但手熟尔!

五、预告


下一篇,我们将在cocos-shader-helloworld的基础上做如下操作

  1. 为shader添加外部变量

  2. 外部变量在Inspector面板上的控制

  3. 外部变量在代码中如何控制

敬请关注!

image-20201012222236914

六、更多Cocos Shader相关文章推荐


38赞

mark zc

重写了一个版本

mark mark

mark :eyes:

可以收藏,为啥要mark

马克~~~~~~~~~

我要在这神圣的地方留下我来过的痕迹

marklll

1赞

cocos模板测试设置了不起作用

Mark!

理解又加深了

shader 好难呀

mark111

mark :smiley:

mark :grinning:

先插入,后观看。