上周学习shader遇到了一个小问题,希望可以帮助有同样困惑的同学,也对自己的学习进行一个记录。
起因
最近几个月一直在断断续续学习Unity的shader相关知识,希望通过冯乐乐女神的《unityshader入门精要》入个门,不过工作时间比较紧张,只能在周末有一点进度。
上周末学到第十三章的全局雾效的时候,遇到了一个怎么也想不通的问题:
生成全局雾效时需要对每个像素的位置进行世界坐标重建,为了重建世界坐标,又需要获得每个像素相对于摄像机的一条射线,完整公式如下:
worldPos = _worldSpaceCameraPos + linearDpeth * interpolatedRay;
因为_worldSpaceCameraPos
和linearDpeth
都是已知的,所以只需要再求这条射线即可。
求值方法是这样:‘
- 在C#层计算了从摄像机到摄像机的近裁切平面的四个角的射线传入shader;
- 在顶点着色器中对四个角分别进行判断,使后处理的texture的四个顶点分别对应的特定射线,并将这条射线传入片元着色器;
- 在片元着色器中调用上一步传入的射线,即为当前像素所对应的射线。
疑问
我们都知道,顶点着色器是逐顶点计算,片元着色器是逐像素计算,换句话说,我们并没有计算过具体的每个像素的射线,于是我当时就陷入了一个巨大的疑问:
计算的明明是相机到四个顶点的射线,为什么最后变成了到指定像素的射线呢?
答案
其实答案非常简单,但是可能因为我基础不够牢固,对这顶点着色器与片元着色器之间的关系理解不够深刻,导致这个问题卡了我一个周——
顶点着色器里的值被传入片元着色器时会被进行一次插值计算
unity官方文档是这样说的(注意图中标红线的部分,原地址链接):
当然,在《UnityShader入门精要》中,也这样提过一两句,但是比较隐晦:
p277 -
interpolatedRay
来源于对近裁平面的4个角的某个特定向量的插值…
p278 - 因此,我们可以把上面的计算结果传递给顶点着色器,顶点着色器根据当前的位置选择它所对应的向量,然后再将其输出,经插值后传递给片元着色器得到
interpolatedRay
…
测试
既然知道了这个插值的存在,我们当然是要测试一下,我准备了一个非常简单又基础的shader和测试场景。
-
创建一个空白场景,命名为test,在场景中创建一个UGUI的image,此时unity会自动生成Canvas并将创建的image挂在Canvas上。
-
调整image的拉伸模式,将image四个角始终对齐屏幕的四个角。
-
创建一个shader,命名为
VertToFrag
;创建材质,命名为vertMat
,使用前面创建的shader作为此材质的shader。 -
将上一步创建的材质挂载到image的Material上。
-
下面开始shader的编写。这里用到的shader非常简单,不需要任何传入参数。
- 顶点着色器的处理:因为是一张非常简单的图片,只有四个角,也就是四个顶点,所以在顶点着色器中我直接将四个顶点的位置保存在了一个插值器中,变量名为
uv
;因为只有四个角,我们知道,按左上、右上、左下、右下的顺序,四个顶点的uv值分别是:(0,1)、(1,1)、(0,0)、(1,0)。 - 片元着然器的处理:为了可视化显示,我直接将保存的uv作为最终输出的颜色显示。
完整代码如下:
- 顶点着色器的处理:因为是一张非常简单的图片,只有四个角,也就是四个顶点,所以在顶点着色器中我直接将四个顶点的位置保存在了一个插值器中,变量名为
Shader "PFX/VertToFrag"
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = o.vertex;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.uv.xy,0,1);
}
ENDCG
}
}
}
-
最终效果如下,很明显可以看到一个渐变的色彩,因为我们使用的是
fixed4(i.uv.xy,0,1)
这样的输出,所以整个图案横向色彩是红色,纵向色彩是绿色,分别对应r
和g
通道,而最左下角的地方由于两个值都为0,显示为黑色。如果把fixed4(i.uv.xy,0,1)
的b
通道改为1输出,会发现左下角被蓝色占领了。
-
需要完整项目的也可以到这里下载:github链接
全文到此结束。