Unity Shader 高级纹理
立方体纹理实现环境映射,渲染纹理以及程序纹理。
立方体纹理:
定义:
立方体纹理是环境映射的一种实现方式。环境映射可以模拟物体周围的环境,使用了环境映射的物体可以看起来像是镀了金属一样反射出周围的环境。
采样方式
正方体纹理一共包含了6张图像,这些图像对应了一个立方体的6个面。立方体的每个面表示沿着世界空间下的轴向观察所得来的图像。对立方体纹理采样需要我们提供一个三维的纹理坐标,这个三维纹理坐标表示了我们在世界空间下的一个3D方向。这个方向矢量从立方体的中心出发,当它向外部延伸时就会和立方体的6个纹理之一发生相交,而采样得到的结果就是由该交点计算而来的。
优点与缺点:
- 优点:实现简单快速,得到的效果比较好。
- 缺点:
- 当场景中引入了新的物体、光源,或者物体发生移动时,我们就需要重新生成立方体纹理。
- 立方体纹理只能反射环境,不能反射使用了该立方体纹理的物体本身。它不能模拟多次反射的结果,故应尽量对凸面体而不是凹面体使用立方体纹理。
天空盒子(Skybox):
天空盒子是在所有不透明物体之后渲染的,而其背后使用的网格是一个立方体或一个细分后的球体。
创建用于环境映射的立方体纹理:
- 直接由一些特殊布局的纹理创建:需要一张具有特殊布局的纹理,然后将其Texture Type设置成Cubemap即可。
- 手动创建一个Cubemap资源:创建一个Cubemap,将六张纹理拖拽到它的面板中。
- 由脚本生成:使用Camera.RenderToCubemap函数实现——从任意位置观察到的场景图像存储到6张图像中,从而创建出该位置上对应的正方体纹理。
反射效果:
通过入射光线的方向和表面法线方向来计算反射方向,再利用反射方向对立方体纹理采样。
首先,声明需要用到的属性:
在顶点着色器中计算该顶点处的反射方向,根据CG的reflect函数来实现:
物体反射到摄像机中的光线方向,可以由光路可逆的原则来反向求得。可以计算视角方向关于顶点法线的反射方向来求得入射光线的方向。
(TRANSFER_SHADOW参见以前的博客:(此处应有链接))
在片元着色器中,利用反射方向来对立方体纹理采样:
对立方体纹理的采样需要使用CG的texCUBE函数,用于采样的参数仅仅是作为方向变量传递给texCUBE函数的,没有必要进行归一化。
然后使用_ReflectAmount来混合漫反射颜色和反射颜色,并和环境光照相加后返回。
- 可以在片元着色器中计算反射方向,这样效果更好,但是差别不大;处于性能考虑,我们选择在顶点着色器中计算反射方向。
效果:
折射效果:
当光线从一种介质斜射入另一种介质时,传播方向一般会发生改变。当给定入射角时,可以使用斯涅尔定律来计算反射角。
$n_1sinθ_1 = n_2sinθ_2$
其中,n1和n2分别是两个介质的折射率。
一般对一个透明物体,准确的模拟需要计算两次折射——一次入射与一次出射。但是这样模拟很复杂,所以初学在此仅模拟一次折射。
声明属性:
使用CG的refract函数来计算折射方向。
第一个参数是入射光线的方向,必须是归一化后的矢量;第二个参数是表面法线,法线方向同样需要是归一化的;第三个参数是入射光线所在介质的折射率与折射光线所在介质的折射率之间的比值。返回计算而得的折射方向,它的模则等于入射光线的模。
在片元着色器中使用折射方向对立方体纹理进行采样:
最后使用_RefractAmount来混合漫反射和折射颜色,并和环境光照相加后返回。
效果:
菲涅尔反射:
当光线照射到物体表面上时,一部分发生反射,一部分进入物体内部,发生折射或散射。被反射的光和入射光之间存在一定的比率关系,这个比例关系可以通过菲涅尔等式进行计算。
菲涅尔等式:
- Schlick菲涅尔近似等式:
$F_{Schlick}(v,n) = F_0 + (1 - F_0)(1 - v·n)^5$
$F_0$是反射系数,用于控制菲涅尔反射的强度,v是视角方向,n是表面法线。
Empricial菲涅尔近似等式:
$F_{Empricial}(v,n) = max(0, min(1,bias + scale * (1 - v·n)^{power})$
Bias、scale和power是控制项。
定义属性:_FresnelScale属性用于调整菲涅尔反射
在顶点着色器中计算世界空间下的法线方向、视角方向和反射方向:
在片元着色器中计算菲涅尔反射,并使用结果值混合漫反射光照和反射光照:
效果:
当_FresnelScale为1时:
为0.5时:
为0时:
渲染纹理:
镜子效果的实现:
玻璃效果的实现:
我们可以使用GrabPass来完成获取屏幕图像的目的。在Shader中定义了一个GrabPass后,Unity会把当前屏幕的图像绘制在一张纹理中,以便在后续的Pass中访问。与简单的透明混合不同,使用GrabPass可以让我们对物体后面的图像进行更复杂的处理,例如使用法线来模拟折射效果,而不是简单的与原屏幕颜色进行混合。
使用GrabPass时,要小心物体的渲染队列设置,往往需要把物体的渲染队列设置成透明队列(“Queue ” = “Transparent”),来保证当渲染该物体的时候,所有的不透明物体已经被绘制在屏幕上,获得正确的屏幕图像。
整体思路:
首先使用一张法线纹理来修改模型的法线信息,然后使用反射方法,通过一个Cubemap来模拟玻璃的反射,而在模拟折射时,使用了GrabPass获取玻璃后面的屏幕图像,并使用切线空间下的法线对屏幕纹理坐标偏移后,再对屏幕图像进行采样来模拟近似的折射效果。
声明属性:
定义相应的渲染队列
定义变量:
定义顶点着色器阶段:
- 先进行必要的顶点坐标变换。
- 调用内置的ComputeGrabScreenPos函数来得到对应被抓取的屏幕图像的采样坐标。
- 计算
_MainTex
和_BumpMap
的采样坐标,并存储在一个float4类型变量中。 - 接着计算顶点对应的从切线空间到世界空间的变换矩阵,并把该矩阵的每一行分别存储在三个TtoWx的xyz分量中。这样做是为了在片元着色器中把法线方向从切线空间转换到世界空间下,以便对CubeMap进行采样。
- 计算得到的分量被按列组成一个变换矩阵,w轴被用来存储世界空间下的顶点坐标。
定义片元着色器:
- 通过世界坐标得到该片元对应的视角方向。
- 对法线纹理进行采样,得到切线空间下的法线方向。
- 使用切线空间下的法线方向进行偏移(因为该空间下的法线可以反映顶点局部空间下的法线方向)
- 随后对scrPos透视除法得到真正的屏幕坐标,再使用该坐标对抓取的屏幕图像
_RefractionTex
进行采样,得到模拟的折射颜色。 - 下一步把法线方向从切线空间变换到了世界空间下,并据此得到视角方向相对于法线方向的反射方向。
- 使用反射方向对Cubemap进行采样,并把结果和主纹理颜色相乘后得到反射颜色。
- 最后使用
_RefractAmount
属性对反射和折射颜色进行混合,作为最终的输出颜色。
效果:
- 为什么要在Pass中使用一个字符串指明被抓取的屏幕图像存储名称?
- 直接使用GrabPass{}性能消耗较大,因为这样对于每个使用它的物体,Unity会为它单独进行一次昂贵的屏幕抓取工作。不过这样可以使不同的物体得到不同的屏幕图像。
- 指明字符串后,可以在后续的Pass中通过名称来访问屏幕图像。这样效果更高效,但是所有物体都会使用同一张屏幕图像。
程序纹理:
可以使用程序纹理来创建程序材质。