蒸汽机车
首发于蒸汽机车

【翻译】Shader · 冰

这回要翻译的是Lindsey Rein女士的文章,很荣幸得到了她的许可。

老样子,我郑重承诺:

搬运和翻译将是非商业化的,并且无广告,不收费。

我将尊重作者文意,不篡改文中内容。

我将始终指向作者的个人网站和Github,不作个人推广。

Lindsey女士的文章简明扼要,Shader简单易学又效果出色,实在是Shader学习、提升自己不可多得的文章。(老干妈脸)

其实这篇文章很简单,但我对Grab Pass的不了解,所以翻译这篇文章,以图与大家技术共进,原文地址:

Ice Shader in Unitylindseyreidblog.wordpress.com

大家好,今天我将展示我是如何在Unity中创建这个冰/水晶化的Shader的,这个效果由三部分组成:

  • 主体透明,边缘呈现白色不透明状。
  • 彩虹状的凹凸贴图
  • 透过冰块后产生的失真效应

这些效果都可以被其他Shader使用,比如你可以将失真效果使用在这样的Water Shader中!

作为参考,这里是Shader的最终代码:

thelindseyreid/Unity-Shader-Tutorialsgithub.com图标

边缘不透明

在另一篇文章中我提到过不透明边缘效果,但是这次我打算重新解释一次,因为这次使用的不透明效果略微有点不同。

首先,为了透明效果,我们先设置一下通道:

Tags {
"Queue" = "Transparent" // important for Unity!!
}
Cull Off // this is optional, I think it looks good with ice
Blend SrcAlpha OneMinusSrcAlpha // standard alpha blending

确保顶点着色器在计算世界空间中的坐标、法线、观察方向之后,将信息传递给片源着色器。

output.pos  = UnityObjectToClipPos(input.vertex);
float4 normal4 = float4(input.normal, 0.0);
output.normal = normalize(mul(normal4, unity_WorldToObject).xyz);
output.viewDir = normalize(_WorldSpaceCameraPos - mul(unity_ObjectToWorld, input.vertex).xyz);

然后让我们来实现片元着色器。

对于每个像素,若要决定它的不透明度,我们需要计算表面法线观察方向的点积。观察方向越垂直于表面,点积越接近0。

// input.viewDir and .normal are defined in the vertex shader
float edgeFactor = abs( dot(input.viewDir, input.normal));

同样的道理,我们可以使用它来计算轮廓的不透明度。

// _Color is a color property
// min() clamps the value to be < 1.0
float opacity = min(1.0, _Color.a / edgeFactor);
// taking the pow(opacity) makes the opacity dropoff happen faster,
// so that only the very edges are opaque
// _EdgeThickness is a float property
opacity = pow(opacity, _EdgeThickness);

另外,我还喜欢改变边缘颜色。因为我打算使用风格化的设计,所以我在Ramp Texture中进行采样来决定轮廓的颜色。下图是我使用的Ramp Texture。如你所示,其左边(x=0.0部分)是深色的,而右边(x=1.0部分)则是白色的。

Ramp Texture

下面是我们使用edgeFactor对Ramp Texture进行采样的过程,当然你也可以用光滑过渡的颜色而不是Ramp Texture来进行此步骤。

// only using oneMinusEdge here because of
// how the ramp texture is set up, lol
float oneMinusEdge = 1.0 - edgeFactor;
// _RampTex is a sampler2D property
// the y-pos doesn't really matter here; keep it constant
float3 rgb = tex2D(_RampTex, float2(oneMinusEdge, 0.5)).rgb;

凹凸贴图

Shader中经常使用法线贴图,用于在不增加面数的情况下令网格看起来富有细节。小冰狗使用的网格并没有细节上的凹凸,而我正要对法线贴图进行采样,使用它里面记录的法线方向来计算光照。这听起来很有意思,我使用的法线贴图(又称凹凸贴图)是一种噪声纹理

使用的法线贴图

首先,保证顶点着色器已经将纹理UV传递到了片元着色器中:

// float3 stored in TEXCOORD0 in the vertex input struct
output.texCoord = input.texCoord;

然后开始编写片元着色器。

先用纹理UV对凹凸贴图采样,并与输入数据中的法线相加。与输入法线相加的目的是要保证即便当小狗旋转的时候,也能改变光照效果。

否则,无论物体如何改变,凹凸效果看上去都会一模一样。

float3 bump = tex2D(_BumpTex, input.texCoord.xy).rgb + input.normal.xyz;

同样我也打算使用Ramp Texutre来渲染这部分效果,因为我觉得卡通效果很酷。所以,我们使用光照方向凹凸贴图法线方向的点积作为依据在Ramp Texture上采样:

// this light provided by Unity- make sure to normalize!
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
// get our sample location
float ramp = clamp( dot(bump, lightDir), 0.001, 1.0);
// sample the ramp texture
// make opacity whatever, we're not using this opacity
float4 lighting = float4(tex2D(_BumpRamp, float2(ramp, 0.5)).rgb, 1.0);

最后,我们第一部分的边缘效果(提供了RGB值和透明度)以及凹凸效果中计算出来的光照结合在一起来取得最后的片源着色器输出。

return float4(rgb, opacity) * lighting;

成了!现在这个Shader已经有了一个很不错的通道,即便现在啥也不做,Shader看起来也有了冰块的样子。我们在下一个通道里添加失真效果。

失真效果

我们需要为失真效果编写一个全新的通道。该通道需要在上述渲染颜色和光照的通道之前执行。

总的来说,我们需要抓取冰块后的背景纹理,然后扭曲其顶点位置,最后再绘制出来。我们将用Grab Pass来抓取背景内容。

为了在Shader中实现这一步,我们需要使用GrabPass标签,写在SubShader后面。

GrabPass
{
  "_BackgroundTexture"
}

然后将背景纹理作为属性添加到列表中,并包含UnityCG.cginc,它为我们接下来要做的事情提供了特殊的函数。

#include "UnityCG.cginc"
sampler2D _BackgroundTexture;
float _DistortStrength;

现在,我们开始编写顶点着色器。首先,我们将顶点坐标转换到世界空间。然后,我们用它获取屏幕空间里的坐标,为片源着色器采样背景纹理做准备。

output.pos = UnityObjectToClipPos(input.vertex);
output.grabPos = ComputeGrabScreenPos(output.pos);

现在我们就可以扭曲采样点了。你可以根据顶点位置和角度或者其他信息,以自己的方式来扭曲它。但我太懒了,我只打算根据纹理坐标对凹凸纹理进行采样。

float3 bump = tex2Dlod(_BumpTex, float4(input.texCoord.xy, 0, 0)).rg;
output.grabPos.xy += bump.xy * _DistortStrength;

最后在片元着色器中,我们用被扭曲过的点对背景纹理进行采样。

return tex2Dproj(_BackgroundTexture, input.grabPos);

把这些代码跟之前的通道组合在一起,大功告成。

结语

WOOOOOOOOOOO!这是我写过的最长的教程(译者:跟Alan的教程比起来,长度上完全不是一个数量级的,比心)我们包含了可以复用的效果。

如果你对编写Shader有啥疑问,我非常乐意与你分享我所知的知识。我不是专家,但我乐意帮助其他独立开发者。

祝好运!

Lindsey Reid @so_good_lin

P.S.这里是 Unity Graphics Setup

编辑于 2018-06-05

文章被以下专栏收录