开启辅助访问
 找回密码
 注册帐号

扫一扫,访问微社区

开发者专栏

关注:1810

当前位置:游戏蛮牛 技术专区 开发者专栏

__________________________________________________________________________________
开发者干货区版块规则:

  1、文章必须是图文形式。(至少2幅图)
      2、文章字数必须保持在1500字节以上。(编辑器右下角有字数检查)
      3、本版块只支持在游戏蛮牛原创首发,不支持转载。
      4、本版块回复不得无意义,如:顶、呵呵、不错......【真的会扣分的哦】
      5、......
__________________________________________________________________________________
查看: 1333|回复: 35

[蛮牛干货] Unity着色器教程 | 积雪效果

[复制链接]  [移动端链接]
排名
3
昨日变化

5312

主题

5761

帖子

2万

积分

Rank: 16

UID
1231
好友
183
蛮牛币
1437
威望
30
注册时间
2013-7-29
在线时间
2524 小时
最后登录
2017-8-17

社区QQ达人活力之星原创精华达人突出贡献奖财富之证游戏蛮牛QQ群会员蛮牛妹VIP

发表于 2017-8-7 10:55:41 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
  为游戏中的所有纹理都加上雪花可能需要花费大量时间。本文将展示在Unity中如何创建Image Effect(屏幕空间着色器)来快速改变场景的季节。
  使用前后效果图对比如下:
1.jpg

2.jpg
  工作原理
  上面两张图显示的是同一个场景。它们之间唯一的区别就是第二张图启用了相机上的雪花特效。实现该效果无需对场景的贴图做任何更改。这是什么原理呢?
  其实原理非常简单。就是假定所有法线朝上的像素点(如:草,地板等)都需要覆盖雪花。同样,法线朝着其它方向的像素点(如:松树,墙),则需要在雪花纹理和原始纹理之间进行平缓过渡。
  获取所需数据
  实现上面的雪花效果有以下准备事项:
  1、将渲染路径设置为Deferred(延迟渲染)
  2、将Camera.depthTextureMode设置为DepthNormals
  由于第二项可以很方便地由屏幕特效脚本进行设置,所以如果游戏已经使用了前向渲染路径(Forward Rendering Path)时,第一项很容易出问题
  将Camera.depthTextureMode设置为DepthNormals后可以读取屏幕深度(像素与相机之间的距离)和法线(所朝的方向)。
  创建一个屏幕特效(Image Effect)由至少一个脚本和一个着色器构成。通常这个着色器不是用来渲染3D物体的,而是根据给定的输入数据渲染一个全屏的图像。在本文的例子中,输入数据就是一张相机渲染的结果图片以及一些用户设置的属性。
[C#] 纯文本查看 复制代码
using UnityEngine;
using System.Collections;
 
[ExecuteInEditMode]
public class ScreenSpaceSnow : MonoBehaviour
{
 
    public Texture2D SnowTexture;
 
    public Color SnowColor = Color.white;
 
    public float SnowTextureScale = 0.1f;
 
    [Range(0, 1)]
    public float BottomThreshold = 0f;
    [Range(0, 1)]
    public float TopThreshold = 1f;
 
    private Material _material;
 
    void OnEnable()
    {
        // dynamically create a material that will use our shader
        _material = new Material(Shader.Find("TKoU/ScreenSpaceSnow"));
 
        // tell the camera to render depth and normals
        GetComponent<Camera>().depthTextureMode |= DepthTextureMode.DepthNormals;
    }
 
    void OnRenderImage(RenderTexture src, RenderTexture dest) 
    {
        // set shader properties
        _material.SetMatrix("_CamToWorld", GetComponent<Camera>().cameraToWorldMatrix);
        _material.SetColor("_SnowColor", SnowColor);
        _material.SetFloat("_BottomThreshold", BottomThreshold);
        _material.SetFloat("_TopThreshold", TopThreshold);
        _material.SetTexture("_SnowTex", SnowTexture);
        _material.SetFloat("_SnowTexScale", SnowTextureScale);
 
        // execute the shader on input texture (src) and write to output (dest)
        Graphics.Blit(src, dest, _material);
    }
}


  这里只是基础的设置,还不能生成雪花。有趣的事情还在后面。
  着色器
  雪花着色器是无光照着色器(unlit shader),因为屏幕空间是没有光照的,所以也不会用到任何光照信息。基础模板如下:
[C#] 纯文本查看 复制代码
Shader "TKoU/ScreenSpaceSnow"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always
 
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
             
            #include "UnityCG.cginc"
 
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
 
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };
 
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.uv;
                return o;
            }
             
            fixed4 frag (v2f i) : SV_Target
            {
                // the magic happens here
            }
            ENDCG
        }
    }
}


  注意,直接新建无光照着色器(Create->Shader->Unlit Shader) 就会看到几乎同样的代码。
  现在看看重要部分:片段着色器。首先,需要通过ScreenSpaceSnow脚本来获取所有数据:
[C#] 纯文本查看 复制代码
sampler2D _MainTex;
sampler2D _CameraDepthNormalsTexture;
float4x4 _CamToWorld;
 
sampler2D _SnowTex;
float _SnowTexScale;
 
half4 _SnowColor;
 
fixed _BottomThreshold;
fixed _TopThreshold;
 
 
half4 frag (v2f i) : SV_Target
{
     
}


  找出需要下雪的地方
  正如之前所说,所有法线朝上的表面都将覆盖雪。相机已经设置了生成深度法线贴图,所以现在直接获取即可。代码如下:
[C#] 纯文本查看 复制代码
sampler2D _CameraDepthNormalsTexture;


  查看Unity官方文档可以了解该命名的意义:
  ”深度贴图可以作为一个着色器的全局着色器属性进行采样。通过声明名为_CameraDepthTexture的采样器,就能够采样相机的主深度纹理。
  _CameraDepthTexture总是引用相机的主深度贴图。”
现在开始获取法线:
[C#] 纯文本查看 复制代码
half4 frag (v2f i) : SV_Target
{
    half3 normal;
    float depth;
 
    DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, i.uv), depth, normal);
    normal = mul( (float3x3)_CamToWorld, normal);
 
    return half4(normal, 1);
}


  Unity文档解释深度和法线的数据都打包为16位。这里需要像代码那样调用DecodeDepthNormal方法进行解包。
  这个方法检索的是相机空间的法线。也就是说,如果旋转屏幕相机,那么法线朝向也会改变。脚本中将法线乘以_CamToWorld 矩阵就是为了避免这种情况。它会将法线从相机空间转换为世界空间,这样就不再依赖于相机的透视。
  为了让着色器正确编译就必须返回一些东西,所以上面的代码设置了返回语句。这样也便于预览结果以确认计算是否正确。


3.jpg

  暂时渲染为RGB图像。在Unity中,Y轴是默认向上的。图中绿色部分表示Y坐标轴的值。目前为止结果不错!
  现在将其转换为雪量的因子。
[C#] 纯文本查看 复制代码
half4 frag (v2f i) : SV_Target
{
    half3 normal;
    float depth;
 
    DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, i.uv), depth, normal);
    normal = mul( (float3x3)_CamToWorld, normal);
 
    half snowAmount = normal.g;
    half scale = (_BottomThreshold + 1 - _TopThreshold) / 1 + 1;
    snowAmount = saturate( (snowAmount - _BottomThreshold) * scale);
 
    return half4(snowAmount, snowAmount, snowAmount, 1);
}


  这里会用到绿色分量。接下来配置积雪覆盖区域顶部和底部的阀值,以便于调整场景的积雪量。


4.jpg


  雪纹理
  如果没有纹理,雪看起来会不真实。最难的部分就是将2D纹理(屏幕空间)应用到3D物体上。一种方法是获取像素的世界坐标,然后将世界坐标的X和Z值作为纹理坐标。
[C#] 纯文本查看 复制代码
half4 frag (v2f i) : SV_Target
{
    half3 normal;
    float depth;
 
    DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, i.uv), depth, normal);
    normal = mul( (float3x3)_CamToWorld, normal);
 
    // find out snow amount
    half snowAmount = normal.g;
    half scale = (_BottomThreshold + 1 - _TopThreshold) / 1 + 1;
    snowAmount = saturate( (snowAmount - _BottomThreshold) * scale);
 
    // find out snow color
    float2 p11_22 = float2(unity_CameraProjection._11, unity_CameraProjection._22);
    float3 vpos = float3( (i.uv * 2 - 1) / p11_22, -1) * depth;
    float4 wpos = mul(_CamToWorld, float4(vpos, 1));
    wpos += float4(_WorldSpaceCameraPos, 0) / _ProjectionParams.z;
 
    half3 snowColor = tex2D(_SnowTex, wpos.xz * _SnowTexScale * _ProjectionParams.z) * _SnowColor;
 
    return half4(snowColor, 1);
}


  这里涉及到一些数学知识,您只需知道vpos是视口坐标,wpos是由视口坐标与_CamToWorld矩阵相乘而得到的世界坐标,并且它通过除以远平面的位置(_ProjectionParams.z)来转换为有效的世界坐标。最后使用XZ坐标乘以可配置参数_SnowTexScale和远平面,来计算雪的颜色并获取适当的值。


5.jpg

  合并
  下面将积雪与场景进行合并。
[C#] 纯文本查看 复制代码
half4 frag (v2f i) : SV_Target
{
    half3 normal;
    float depth;
 
    DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, i.uv), depth, normal);
    normal = mul( (float3x3)_CamToWorld, normal);
 
    // find out snow amount
    half snowAmount = normal.g;
    half scale = (_BottomThreshold + 1 - _TopThreshold) / 1 + 1;
    snowAmount = saturate( (snowAmount - _BottomThreshold) * scale);
 
    // find out snow color
    float2 p11_22 = float2(unity_CameraProjection._11, unity_CameraProjection._22);
    float3 vpos = float3( (i.uv * 2 - 1) / p11_22, -1) * depth;
    float4 wpos = mul(_CamToWorld, float4(vpos, 1));
    wpos += float4(_WorldSpaceCameraPos, 0) / _ProjectionParams.z;
 
    wpos *= _SnowTexScale * _ProjectionParams.z;
    half3 snowColor = tex2D(_SnowTex, wpos.xz) * _SnowColor;
 
    // get color and lerp to snow texture
    half4 col = tex2D(_MainTex, i.uv);
    return lerp(col, snowColor, snowAmount);
}


  上述代码获取原始颜色,并使用snowAmount进行插值渐变为snowColor 。


6.jpg

  最后一步:将_TopThreshold设为0.6:
7.jpg
  完成!
  结论

  全屏效果见下图。看起来不错吧?


8.jpg
9.jpg



来源:Unity官方中文社区

评分

参与人数 2鲜花 +10 收起 理由
kaifeng0301 + 5 很给力!
yang512423 + 5 很给力!

查看全部评分


跟我念“站长妹纸萌萌哒!”我说站长,你说YO!爱你们么么哒~
回复

使用道具 举报

7日久生情
1703/5000
排名
1374
昨日变化
2

5

主题

573

帖子

1703

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
143831
好友
2
蛮牛币
3079
威望
0
注册时间
2016-3-29
在线时间
435 小时
最后登录
2017-8-17

蛮牛译员活力之星七夕浪漫情人

发表于 2017-8-7 11:16:50 | 显示全部楼层
666666666666666666

回复 支持 反对

使用道具 举报

排名
16553
昨日变化
15

2

主题

13

帖子

57

积分

Rank: 2Rank: 2

UID
191223
好友
0
蛮牛币
403
威望
0
注册时间
2016-12-11
在线时间
19 小时
最后登录
2017-8-15
发表于 2017-8-7 11:23:07 | 显示全部楼层
感谢楼主无私分享,不错的

回复 支持 反对

使用道具 举报

6蛮牛粉丝
1326/1500
排名
886
昨日变化
4

0

主题

33

帖子

1326

积分

Rank: 6Rank: 6Rank: 6

UID
125642
好友
0
蛮牛币
2649
威望
0
注册时间
2015-10-15
在线时间
383 小时
最后登录
2017-8-17
发表于 2017-8-7 11:25:21 | 显示全部楼层
666666666666666666

回复 支持 反对

使用道具 举报

6蛮牛粉丝
1164/1500
排名
1693
昨日变化
1

0

主题

204

帖子

1164

积分

Rank: 6Rank: 6Rank: 6

UID
78535
好友
5
蛮牛币
1495
威望
0
注册时间
2015-3-10
在线时间
358 小时
最后登录
2017-8-17
发表于 2017-8-7 11:33:00 | 显示全部楼层
恩,不错,谢谢楼主分享了

回复 支持 反对

使用道具 举报

2初来乍到
142/150
排名
13509
昨日变化
334

0

主题

69

帖子

142

积分

Rank: 2Rank: 2

UID
224879
好友
0
蛮牛币
225
威望
0
注册时间
2017-6-2
在线时间
33 小时
最后登录
2017-8-17
发表于 2017-8-7 11:42:39 | 显示全部楼层
谢谢分享。。。。。。。。。。。。。。。。

回复

使用道具 举报

7日久生情
2381/5000
排名
584
昨日变化
1

13

主题

161

帖子

2381

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
85410
好友
1
蛮牛币
4374
威望
0
注册时间
2015-3-28
在线时间
986 小时
最后登录
2017-8-17

蛮牛译员

发表于 2017-8-7 11:55:05 | 显示全部楼层
666666666666
犀利

回复 支持 反对

使用道具 举报

2初来乍到
115/150
排名
11100
昨日变化
201

0

主题

20

帖子

115

积分

Rank: 2Rank: 2

UID
130351
好友
0
蛮牛币
234
威望
0
注册时间
2015-11-29
在线时间
39 小时
最后登录
2017-8-16
发表于 2017-8-7 11:56:52 | 显示全部楼层
谢谢分享。。。。。。。。。。。。。。。。

回复

使用道具 举报

排名
849
昨日变化
1

24

主题

427

帖子

2212

积分

Rank: 9Rank: 9Rank: 9

UID
1261
好友
31
蛮牛币
569
威望
0
注册时间
2013-7-31
在线时间
485 小时
最后登录
2017-8-17

专栏作家社区QQ达人认证开发者

发表于 2017-8-7 12:17:15 | 显示全部楼层
有提供工程的话就更赞了

回复 支持 反对

使用道具 举报

5熟悉之中
909/1000
排名
2633
昨日变化

0

主题

306

帖子

909

积分

Rank: 5Rank: 5

UID
6218
好友
0
蛮牛币
1794
威望
0
注册时间
2013-10-23
在线时间
193 小时
最后登录
2017-8-14

社区QQ达人

发表于 2017-8-7 13:04:16 | 显示全部楼层
感谢楼主无私分享

回复 支持 反对

使用道具 举报

5熟悉之中
872/1000
排名
9626
昨日变化
141

4

主题

690

帖子

872

积分

Rank: 5Rank: 5

UID
209186
好友
0
蛮牛币
1456
威望
0
注册时间
2017-3-1
在线时间
100 小时
最后登录
2017-8-17
发表于 2017-8-7 14:16:48 | 显示全部楼层
Unity着色器教程 | 积雪效果

回复 支持 反对

使用道具 举报

3偶尔光临
197/300
排名
15999
昨日变化
539

1

主题

144

帖子

197

积分

Rank: 3Rank: 3Rank: 3

UID
233607
好友
0
蛮牛币
38
威望
0
注册时间
2017-7-22
在线时间
24 小时
最后登录
2017-8-17

一贫如洗

发表于 2017-8-7 14:40:09 | 显示全部楼层
不错哦

回复

使用道具 举报

2初来乍到
106/150
排名
16553
昨日变化
622

1

主题

27

帖子

106

积分

Rank: 2Rank: 2

UID
173093
好友
0
蛮牛币
69
威望
0
注册时间
2016-9-30
在线时间
56 小时
最后登录
2017-8-17
发表于 2017-8-7 14:41:06 | 显示全部楼层
楼主好强啊 感谢分享

回复 支持 反对

使用道具 举报

排名
49357
昨日变化
196

0

主题

5

帖子

8

积分

Rank: 1

UID
227785
好友
0
蛮牛币
6
威望
0
注册时间
2017-8-7
在线时间
1 小时
最后登录
2017-8-7
发表于 2017-8-7 17:41:35 | 显示全部楼层

感谢楼主无私分享,不错的

回复 支持 反对

使用道具 举报

6蛮牛粉丝
1362/1500
排名
1128
昨日变化
5

0

主题

122

帖子

1362

积分

Rank: 6Rank: 6Rank: 6

UID
78167
好友
0
蛮牛币
3638
威望
0
注册时间
2015-3-9
在线时间
454 小时
最后登录
2017-8-17
QQ
发表于 2017-8-7 17:52:10 | 显示全部楼层
66666666666666666666666666666

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

快速回复 返回顶部 返回列表