|
本教程由游戏开发者Linden Reid介绍如何制作着色器实现起雾的窗户效果,主要分为三部分内容:
我们在每一部分的最后会提供可以使用的着色器,你可以从中学习方法,以便在制作其它着色器时重用或改写。
获取实现水雾窗户效果的着色器代码:
https://github.com/lindenreid/Unity-Shader-Tutorials/blob/master/Assets/Materials/Shaders/window.shader
高斯模糊
窗户的起雾效果通过高斯模糊和往上面添加轻微的着色来实现。
我们会通过使用GrabPass标签,获取已经在摄像机渲染的窗户后面的像素,然后对这些像素应用高斯模糊算法。
很多文章讲解过高斯模糊的实现原理,本文使用了《GLSL代码的高斯模糊教程》来编写自定义的着色器:
https://github.com/mattdesl/lwjgl-basics/wiki/ShaderLesson5
使用GrabPass
我们需要获取窗户后面的像素,以便对其进行模糊处理,我们可以使用Unity的GrabPass。
GrabPass将在对象后渲染的像素绘制到着色器可以访问的纹理上。使用该渲染批次时,我们需要使用SubShader代码块中的GrabPass标签。
- SubShader
- {
- //绘制透明窗户也很重要
- Tags
- {
- "Queue" = "Transparent"
- }
- //将对象后的屏幕内容抓取到_BGTex中
- GrabPass
- {
- "_BGTex"
- }
- // … 其它着色器代码…
复制代码
接下来,在CGPROGRAM标签中,确保已经包含Unity.cginc文件,从而使用读取GrabPass的特别函数。
为了能够读取GrabPass纹理,我们需要合适的纹理坐标。
Unity通过ComputeScreenGrabPos函数非常简单的获得坐标,只要将剪辑空间顶点位置作为输入提供,该函数就能够给出合适的纹理坐标来读取GrabPass纹理。我们可以在顶点着色器中进行这项计算。
- vertexOutput output;
- output.pos = UnityObjectToClipPos(input.vertex);
- output.grabPos = ComputeGrabScreenPos(output.pos);
复制代码
应用模糊效果
现在,我们可以在片元着色器Fragment Shader中读取纹理,然后应用模糊效果。
我们通过以下参数编写模糊算法。
- float4 gaussianBlur(
- float2 dir,
- float4 grabPos,
- float res,
- sampler2D tex,
- float radius
- )
- {
- //模糊算法在此编写
- }
复制代码
该算法会获取GrabPass纹理,即“tex”,应用模糊效果,并返回float4类型的像素颜色。
下面介绍每个参数的含义:
float2 dir:模糊效果将应用于两个通道,所以我们需要“dir”即方向参数。效果会在X方向和Y方向各应用一次,因为我们使用(1,0)表示X方向,(0,1)表示Y方向,所以获得的是Float2类型。
float4 grabPos:grabPos变量表示模糊像素的纹理坐标。
float res:res变量表示X轴和Y轴上的纹理分辨率。
sampler2D tex:该变量表示要模糊的纹理。我们需要整个纹理,因为模糊算法会对原始像素附近的像素进行采样。
float radius:该变量表示从原始像素到模糊位置的距离。数值越大,模糊效果越强。
接下来,让我们定义控制模糊效果所需的参数。
我们需要一个浮点数定义模糊强度,我们将其定义为_BlurRadius,并在着色器代码的开始将该变量公开给属性块的材质。
我们还需要GrabPass纹理,该纹理的名称要和GrabPass标签中的名称相同,本示例中为_BGTex。我们可以通过创建_YourTextureName_TexelSize属性来获取需要纹理的大小信息。
我们给模糊效果加入了深蓝色着色,使效果更明显。如果想使用该颜色,请添加颜色到属性中,我们将其命名为_FogColor。
- //属性
- //在材质设置
- uniform float4 _FogColor;
- uniform float _BlurRadius;
- //获取通道
- uniform sampler2D _BGTex;
- uniform float4 _BGTex_TexelSize;
复制代码
现在,我们得到了将模糊效果应用到背景纹理的所需信息。
我们打算将模糊效果应用到两个渲染批次:一个在X方向,另一个在Y方向。通常,我们会让第二个模糊渲染批次处理第一个模糊的结果,而不是处理原始背景图像。但这样需要更多着色器,过程也会更复杂。
所以,我对模糊的处理比较简单,在两个方向模糊了原始图像并添加效果。该方法的缺点是:1、模糊的质量较低。2、模糊部分比原始图像更亮,因为添加了效果。
我们将模糊部分乘以着色颜色。请注意,_TexelSize在.zw属性中包含纹理的xy大小。
- float4 blurX = gaussianBlur(float2(1,0),
- input.grabPos,
- _BGTex_TexelSize.z,
- _BGTex,
- _BlurRadius);
- float4 blurY = gaussianBlur(float2(0,1),
- input.grabPos,
- _BGTex_TexelSize.w,
- _BGTex,
- _BlurRadius);
- return (blurX + blurY) * _FogColor;
复制代码
效果
我们将该着色器应用到材质上,并将其附加到场景的一个平坦表面上,现在着色器实现了基本的模糊效果。
下图是起雾窗户效果的预览。
读写纹理
为了按照鼠标交互改变着色器效果,我们需要将鼠标移动写入纹理,并在着色器读取该纹理。
从着色器读取纹理
首先,我们在着色器中创建名为_MouseMap的sampler2D属性。
- uniform sampler2D _MouseMap;
复制代码
在片元着色器中,绘制该纹理以便调试。
- float4 mouseSample=tex2D(_MouseMap,input.texCoord.xy);
复制代码
以上就是片元着色器的功能,用于实现纹理的读写过程。对_MouseMap属性进行编写前,我们将得到不透明灰色平面,如下所示。
使用C#代码写入纹理
为了写入纹理,我们需要创建C#脚本,并将脚本附加到平面。
我们可以通过C#代码的Material.Set函数,设置着色器属性。只需要让属性的字符串名称对应在着色器的对应名称即可。
- void OnMouseDrag ()
- {
- //从鼠标位置向屏幕创建光线
- //然后测试对纹理的碰撞效果
- Ray ray = cam.ScreenPointToRay(Input.mousePosition);
- RaycastHit hit;
- if(Physics.Raycast(ray, out hit, 100))
- {
- Color color = new Color(1, 0, 0, 1);
- //把纹理坐标转换为像素坐标
- int x = (int)(hit.textureCoord.x * texture.width);
- int y = (int)(hit.textureCoord.y * texture.height);
- //写入被碰到的像素
- texture.SetPixel(x, y, color);
- //写入Radius范围内的相邻像素
- for (int i = 0; i < texture.height; i++)
- {
- for (int j = 0; j < texture.width; j++)
- {
- float dist = Vector2.Distance(new Vector2(i,j),
- new Vector2(x,y)
- );
- if(dist <= Radius)
- texture.SetPixel(i, j, color);
- }
- }
- //应用改动并告知着色器
- texture.Apply();
- destinationRenderer.material.SetTexture("_MouseMap", texture);
- }
- }
复制代码
我们为BlurColor选取了黑色,所以运行时场景效果如下图所示。
写入鼠标位置
现在,我们添加一个OnMouseDrag()函数,当玩家点击划动平面时,在鼠标位置周围绘制圆圈。请将MeshCollider组件附加到平面对象,使它接收OnMouseDrag()事件。
现在运行游戏,我们应该能在纹理上使用鼠标进行绘图了。
根据纹理修改模糊效果
现在,我们可以根据刚创建的鼠标拖动纹理来改变模糊效果。
根据鼠标拖动纹理应用模糊效果
我们返回到着色器部分,根据从纹理读取的数值应用模糊部分。由于我们在鼠标点击的位置绘制了红色,而且纹理默认是黑色,因此我们可以根据红色通道修改模糊和着色量。
我们要进行以下乘法。
- _BlurRadius * (1 - red channel)
复制代码
由于红色通道的数值在0~1之间,因此红色数值越大,模糊的半径越小。这种情况下,红色通道会是0或1,所以它会在红色绘制的位置移除模糊效果。
着色颜色同理,只不过需要在未应用起雾效果的部分定义_ClearColor。
- // r = 1表示鼠标点击
- // r = 0表示没有鼠标操作
- float blurRadius = _BlurRadius * (1-mouseSample.r);
- float4 color = mouseSample.r*_ClearColor + (1.0-mouseSample.r)*_FogColor;
- float4 blurX = gaussianBlur(float2(1,0),
- input.grabPos,
- _BGTex_TexelSize.z,
- _BGTex,
- blurRadius);
- float4 blurY = gaussianBlur(float2(0,1),
- input.grabPos,
- _BGTex_TexelSize.w,
- _BGTex,
- blurRadius);
- return (blurX + blurY) * color;
复制代码
现在,我们可以在窗口进行绘制,点击的位置将消退模糊和着色效果。
我们已经得到了不错的窗户起雾着色器。但是为什么不做的更复杂一些呢?
时间算法
在处理着色器前,请思考一下算法的原理。基本上,我们需要根据点击指定像素的时间,来修改模糊量。像素时间值越小,表示它被点击的时间越近,因此起雾效果较小。
我们还需要最大持续时间来定义像素恢复起雾效果的速度。该值将用于把时间转换为标准化数值,即0~1,用于调整最小值和最大值之间的模糊量。
算法如下所示。
- age = current time - time drawn
- percent max age = age / max age
复制代码
然后,我们将标准化的“percent max age”值应用到模糊半径和着色。像素时间值越小,百分比最大持续时间越小,从而使模糊强度越小。
类似地,我们会根据percent max age值,使用较小的着色颜色量和较大的清晰颜色。
- blur radius = max radius * percent max age
- tint = (1 - percent max age)*(clear color) + (percent max age)*(fog color)
复制代码
应用时间
为了将其应用于着色器,首先我们将像素绘制时间写入鼠标贴图纹理的r通道,而不是只写入1.0。
- Color color = new Color(Time.timeSinceLevelLoad, 0, 0, 1);
复制代码
接下来,在着色器应用之前的算法,获取percent max age值。
- //从鼠标点击纹理获取像素绘制的时间
- float timeDrawn = tex2D(_MouseMap, input.texCoord.xy).r;
- //时间 = 当前时间 - 绘制时间
- float age = clamp(_Time.y - timeDrawn, 0.0001, _Time.y);
- //百分比最大时间 = 时间/最大时间
- float percentMaxAge = saturate(age / _MaxAge);
复制代码
最后,我们将percent max age值应用到模糊半径和着色颜色。
- // 时间越长表示百分比最大时间越大,从而有更大的模糊效果
- float blurRadius = _BlurRadius * percentMaxAge;
- float4 color = (1-percentMaxAge)*_ClearColor + percentMaxAge*_FogColor;
复制代码
现在,模糊效果会根据定义的最大持续时间进行恢复。如下图所示,我们将_MaxAge设为1秒,使模糊效果快速淡化。
结语
本教程介绍了如何将颜色之外的信息编码到纹理中,以及如何利用该方法实现不错的效果。
获取水雾窗户效果的着色器代码:
https://github.com/lindenreid/Unity-Shader-Tutorials/blob/master/Assets/Materials/Shaders/window.shader
作者:Linden Reid
来源:Unity官方平台
原地址:https://mp.weixin.qq.com/s/cLyCGg6h9WGkIk5Xj4KU1w
|
|