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

扫一扫,访问微社区

开发者专栏

关注:1876

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

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

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

[zhang273162308] Unity&Shader案例篇—绘制雪花

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

108

主题

525

帖子

6213

积分

Rank: 9Rank: 9Rank: 9

UID
3579
好友
87
蛮牛币
2682
威望
0
注册时间
2013-9-10
在线时间
1283 小时
最后登录
2017-8-31

专栏作家社区QQ达人活力之星游戏蛮牛QQ群会员蛮牛哥

发表于 2016-12-15 21:42:07 | 显示全部楼层 |阅读模式

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

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

x
本帖最后由 zhang273162308 于 2016-12-15 21:43 编辑

一、前言     

上一篇有个案例讲到了绘制雨滴,没有看过的童鞋可以在回去看看,这一篇其实思路和绘制雨滴是一样的。首先,用代码C#生成顶点和面片,然

后用Shader代码渲染,最后在用C#代码控制Shader的参数使得雪花飘起来。飘动的时候加点噪声处理,使得雪花的飘落更符合真实。上一篇加班太


写的有点仓促,这一篇争取写的具体点,每天加班到很晚真的伤不起,尤其伤肾,真的。

依然废话不多说先上效果图,切换不同的贴图可以得到不同的雪花


fffff.gif ffffff.gif



二、制作步骤

1、C#代码生成顶点和面片:首先要了解Unity生成顶点和网格面片,下面这个代码就是有三个顶点画一个三角形
[C#] 纯文本查看 复制代码
using UnityEngine;
using System.Collections;

[RequireComponent(typeof(MeshFilter),typeof(MeshRenderer))]
public class MeshTriangle : MonoBehaviour {

    private Mesh mesh;
        // Use this for initialization
        void Start () {

        mesh = GetComponent<MeshFilter>().mesh;
        mesh.Clear();
        mesh.vertices = new Vector3[] { Vector3.zero, new Vector3(0, 1, 0), new Vector3(1, 1, 0) };
        mesh.uv = new Vector2[] { Vector2.zero, Vector2.zero, Vector2.zero };
        mesh.triangles = new int[] { 0, 1, 2};


        
        
        }
        
        // Update is called once per frame
        void Update () {
        
        }
}




将这个代码随便赋给一个空的GameObject就可以得到如图所示的三角形,代码其实很简单,就是在空间中先定义三个顶点,然后贴图部分因为这里
QQ截图20161215211452.png

没有使用贴图,所以坐标就无所谓,但是坐标点的范围是0~1。接下来就是将顶点连成三角形面片,这个三角形内的点数必须是3的倍数。你可以添



加顶点和三角点的连线得到你想要的网格面片,修改代码如下会得到如下图所示的效果图



[C#] 纯文本查看 复制代码
mesh.vertices = new Vector3[] { Vector3.zero, new Vector3(0, 1, 0), new Vector3(1, 1, 0),new Vector3(1,0,0) };
        mesh.uv = new Vector2[] { Vector2.zero, Vector2.zero, Vector2.zero, Vector2.zero };
        mesh.triangles = new int[] { 0, 1, 2,0,2,3 };






QQ截图20161215211915.png

好了,有了绘制网格的基础,那么接下来开始进入主题。我们当然不需要让C#代码来给我们将雪花的样子的面片画出来了,其实只要画一个向上面



的正方形块就好了,然后通过Shader将一个雪花的贴图渲到正方形网格上九OK了。首先,生成顶点和面片,然后在Update函数里给Shader传递运动



参数,完整的代码如下:

[C#] 纯文本查看 复制代码
using UnityEngine;
using System.Collections;

[RequireComponent(typeof(MeshFilter),typeof(MeshRenderer))]
public class Snow : MonoBehaviour
{
    //Unity可以支持多达64000个顶点,如果一个雪花有4个顶点组成,则最多有16000个雪花
        const int SNOW_NUM = 16000;
    //顶点
        private Vector3[] m_vertices;
    //顶点构成的三角面
        private int[] triangles_;
    //雪花网格的贴图
        private Vector2[] uvs_;
    //雪花的范围
        private float range;
    //雪花范围的倒数,为了提高计算效率
        private float rangeR_;
        private Vector3 move_ = Vector3.zero;

        void Start ()
        {
                range = 16f;
                rangeR_ = 1.0f/range;
                m_vertices = new Vector3[SNOW_NUM*4];
                for (var i = 0; i < SNOW_NUM; ++i) {
                        float x = Random.Range (-range, range);
                        float y = Random.Range (-range, range);
                        float z = Random.Range (-range, range);
            var point = new Vector3(x, y, z);
                        m_vertices [i*4+0] = point;
                        m_vertices [i*4+1] = point;
                        m_vertices [i*4+2] = point;
                        m_vertices [i*4+3] = point;
                }

                triangles_ = new int[SNOW_NUM * 6];
                for (int i = 0; i < SNOW_NUM; ++i) {
                        triangles_[i*6+0] = i*4+0;
                        triangles_[i*6+1] = i*4+1;
                        triangles_[i*6+2] = i*4+2;
                        triangles_[i*6+3] = i*4+2;
                        triangles_[i*6+4] = i*4+1;
                        triangles_[i*6+5] = i*4+3;
                }

                uvs_ = new Vector2[SNOW_NUM*4];
                for (var i = 0; i < SNOW_NUM; ++i) {
                        uvs_ [i*4+0] = new Vector2 (0f, 0f);
                        uvs_ [i*4+1] = new Vector2 (1f, 0f);
                        uvs_ [i*4+2] = new Vector2 (0f, 1f);
                        uvs_ [i*4+3] = new Vector2 (1f, 1f);
                }
                Mesh mesh = new Mesh ();
                mesh.name = "MeshSnowFlakes";
                mesh.vertices = m_vertices;
                mesh.triangles = triangles_;
                mesh.uv = uvs_;
                mesh.bounds = new Bounds(Vector3.zero, Vector3.one * 99999999);
                var mf = GetComponent<MeshFilter> ();
                mf.sharedMesh = mesh;
        }
        
        void LateUpdate ()
        {
                var target_position = Camera.main.transform.TransformPoint(Vector3.forward * range);
                var mr = GetComponent<Renderer> ();
                mr.material.SetFloat("_Range", range);
                mr.material.SetFloat("_RangeR", rangeR_);
                mr.material.SetFloat("_Size", 0.1f);
                mr.material.SetVector("_MoveTotal", move_);
                mr.material.SetVector("_CamUp", Camera.main.transform.up);
                mr.material.SetVector("_TargetPosition", target_position);
                float x = (Mathf.PerlinNoise(0f, Time.time*0.1f)-0.5f) * 10f;
                float y = -2f;
                float z = (Mathf.PerlinNoise(Time.time*0.1f, 0f)-0.5f) * 10f;
                move_ += new Vector3(x, y, z) * Time.deltaTime;
                move_.x = Mathf.Repeat(move_.x, range * 2f);
                move_.y = Mathf.Repeat(move_.y, range * 2f);
                move_.z = Mathf.Repeat(move_.z, range * 2f);
        }
}


2、Shader部分:这一部分做的工作不仅仅是将顶点生成的面片用贴图去渲染,其实还包括让雪花始终朝着摄像机方向



[C#] 纯文本查看 复制代码
[/size][/align]
[align=left][size=4]//从给定的局部坐标到摄像机坐标进行转换,目的是让顶点始终朝向摄像机
                                float3 eyeVector = ObjSpaceViewDir(float4(tv0, 0));
                                //float3 eyeVector = mv;
                                float3 sideVector = normalize(cross(eyeVector, diff));



雪花的生成范围始终是从摄像机的正上方落下,这个在C#代码部分通过将摄像机的正方向传递给Shader实现



[C#] 纯文本查看 复制代码
mr.material.SetVector("_CamUp", Camera.main.transform.up);




Shader代码部分:



[C#] 纯文本查看 复制代码
[/size][/align]
[align=left][size=4]//让顶点始终保持在摄像机的正上方位置
                                float3 diff = _CamUp * _Size;
                                float3 finalposition;
                                float3 tv0 = mv;




当然还有运动部分:



[C#] 纯文本查看 复制代码
        float3 mv = v.vertex.xyz;
                                mv += _MoveTotal;
                                //顶点分布的区域应该是-_Range到_Range,因此target-mv的范围应该也是这个,因此此处的trip值的范围为,0~1,计算的最终目的还是为了让雪花始终在摄像机的正前方
                                trip = floor(((target - mv)*_RangeR + 1) * 0.5);
                                //经过前面的坐标系的换算再次将范围扩大到2个_Range范围
                                trip *= (_Range * 2);
                                mv += trip;



完整的Shader代码如下:



[C#] 纯文本查看 复制代码
Shader "Custom/snow" {
    Properties {
                _MainTex ("Base (RGB)", 2D) = "white" {}
    }
        SubShader {
                   Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
                ZWrite Off
                Cull Off
                // alpha blending
                //float4 result = fragment_output.aaaa * fragment_output + (float4(1.0, 1.0, 1.0, 1.0) - fragment_output.aaaa) * pixel_color;
                //用前一个队列的输出的Alpha通道作为不透明度
                Blend SrcAlpha OneMinusSrcAlpha 
                
        Pass {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
                         #pragma target 3.0
                         
                         #include "UnityCG.cginc"

            uniform sampler2D _MainTex;

                         struct appdata_custom {
                                float4 vertex : POSITION;
                                float2 texcoord : TEXCOORD0;
                        };

                         struct v2f {
                                 float4 pos:SV_POSITION;
                                float2 uv:TEXCOORD0;
                         };
                         
                         float4x4 _PrevInvMatrix;
                        float3   _TargetPosition;
                        float    _Range;
                        float    _RangeR;
                        float    _Size;
                        float3   _MoveTotal;
                        float3   _CamUp;
   
                        v2f vert(appdata_custom v)
                        {
                                //摄像机正前方距离为Range的位置
                                float3 target = _TargetPosition;
                                float3 trip;
                                float3 mv = v.vertex.xyz;
                                mv += _MoveTotal;
                                //顶点分布的区域应该是-_Range到_Range,因此target-mv的范围应该也是这个,因此此处的trip值的范围为,0~1,计算的最终目的还是为了让雪花始终在摄像机的正前方
                                trip = floor(((target - mv)*_RangeR + 1) * 0.5);
                                //经过前面的坐标系的换算再次将范围扩大到2个_Range范围
                                trip *= (_Range * 2);
                                mv += trip;

                                //让顶点始终保持在摄像机的正上方位置
                                float3 diff = _CamUp * _Size;
                                float3 finalposition;
                                float3 tv0 = mv;
                                
                                //tv0.x += sin(mv.x*0.2) * sin(mv.y*0.3) * sin(mv.x*0.9) * sin(mv.y*0.8);
                                //tv0.z += sin(mv.x*0.1) * sin(mv.y*0.2) * sin(mv.x*0.8) * sin(mv.y*1.2);
                                
                                //从给定的局部坐标到摄像机坐标进行转换,目的是让顶点始终朝向摄像机
                                float3 eyeVector = ObjSpaceViewDir(float4(tv0, 0));
                                //float3 eyeVector = mv;
                                float3 sideVector = normalize(cross(eyeVector, diff));

                                //最终的计算
                                tv0 += (v.texcoord.x - 0.5f)*sideVector * _Size;
                                tv0 += (v.texcoord.y - 0.5f)*diff;
                                finalposition = tv0;
                                
                                //将其最终转换到屏幕上
                                v2f o;
                                o.pos = mul(UNITY_MATRIX_MVP, float4(finalposition, 1));
                                o.uv = MultiplyUV(UNITY_MATRIX_TEXTURE0, v.texcoord);
                                return o;
                        }

            fixed4 frag(v2f i) : SV_Target
            {
                                return tex2D(_MainTex, i.uv);
            }

            ENDCG
        }
    }
}




三、尾语



总结来说这种方式实现的大量粒子性的效果要比直接使用粒子系统在性能上的靠小要少很多,我在调试模式下的运行参数如图所示,总之,是一个



QQ截图20161215213805.png

实用切有效的Shader案例。



终于,写完了,快十点啦,哎!生活正他么不容易



了方便大家学习参考,附上百度工程文件如下



游客,如果您要查看本帖隐藏内容请回复








评分

参与人数 3蛮牛币 +5 鲜花 +13 收起 理由
iam小磊 + 5 很给力!
winning11jc + 8 很给力!
z7232124 + 5 很给力!

查看全部评分


回复

使用道具 举报

排名
24979
昨日变化
7

0

主题

8

帖子

44

积分

Rank: 1

UID
177751
好友
1
蛮牛币
110
威望
0
注册时间
2016-10-23
在线时间
26 小时
最后登录
2017-4-29
发表于 2016-12-15 22:49:42 | 显示全部楼层
太bang了啊啊啊

回复

使用道具 举报

5熟悉之中
655/1000
排名
6547
昨日变化
2

0

主题

415

帖子

655

积分

Rank: 5Rank: 5

UID
126622
好友
0
蛮牛币
89
威望
0
注册时间
2015-10-24
在线时间
92 小时
最后登录
2017-8-31
发表于 2016-12-15 23:41:47 | 显示全部楼层
太bang了啊啊啊

回复

使用道具 举报

4四处流浪
331/500
排名
10996
昨日变化
5

0

主题

222

帖子

331

积分

Rank: 4

UID
181594
好友
0
蛮牛币
133
威望
0
注册时间
2016-11-7
在线时间
47 小时
最后登录
2017-8-2
发表于 2016-12-16 06:37:42 | 显示全部楼层
感谢楼主分享

回复

使用道具 举报

4四处流浪
337/500
排名
6616
昨日变化
2

1

主题

83

帖子

337

积分

Rank: 4

UID
144935
好友
1
蛮牛币
477
威望
0
注册时间
2016-4-9
在线时间
109 小时
最后登录
2017-5-10
发表于 2016-12-16 08:21:01 | 显示全部楼层
拿下来看看

回复

使用道具 举报

3偶尔光临
174/300
排名
9225
昨日变化
2

5

主题

27

帖子

174

积分

Rank: 3Rank: 3Rank: 3

UID
192252
好友
1
蛮牛币
77
威望
0
注册时间
2016-12-14
在线时间
56 小时
最后登录
2017-5-19
发表于 2016-12-16 08:22:12 | 显示全部楼层
感谢楼主分享

回复

使用道具 举报

6蛮牛粉丝
1172/1500
排名
1239
昨日变化

2

主题

74

帖子

1172

积分

Rank: 6Rank: 6Rank: 6

UID
133503
好友
2
蛮牛币
3144
威望
0
注册时间
2016-1-5
在线时间
316 小时
最后登录
2017-9-23
QQ
发表于 2016-12-16 08:26:58 | 显示全部楼层
感谢楼主分享

回复

使用道具 举报

6蛮牛粉丝
1483/1500
排名
1026
昨日变化
2

1

主题

84

帖子

1483

积分

Rank: 6Rank: 6Rank: 6

UID
102587
好友
0
蛮牛币
3091
威望
0
注册时间
2015-5-23
在线时间
542 小时
最后登录
2017-9-25
发表于 2016-12-16 08:40:46 | 显示全部楼层
额。看一看效果

回复

使用道具 举报

5熟悉之中
564/1000
排名
5130
昨日变化

5

主题

199

帖子

564

积分

Rank: 5Rank: 5

UID
187870
好友
0
蛮牛币
898
威望
0
注册时间
2016-11-29
在线时间
154 小时
最后登录
2017-9-21
发表于 2016-12-16 08:56:47 | 显示全部楼层
感谢楼主分享
[发帖际遇]: 老猴子 发帖时在路边捡到 1 蛮牛币,偷偷放进了口袋. 幸运榜 / 衰神榜

回复

使用道具 举报

排名
650
昨日变化
1

1996

主题

2981

帖子

1万

积分

Rank: 16

UID
101536
好友
17
蛮牛币
47388
威望
0
注册时间
2015-5-19
在线时间
3300 小时
最后登录
2017-9-25

七夕浪漫情人蛮牛妹

发表于 2016-12-16 08:58:59 | 显示全部楼层
1.png

喜欢他,感谢他,就支持他吧~为他投票!

http://www.manew.com/tools/expert

回帖是美德~是一种行动认可和支持~希望更多蛮牛小伙伴参与到支持行动中~
回复 支持 1 反对 0

使用道具 举报

7日久生情
2110/5000
排名
869
昨日变化

0

主题

158

帖子

2110

积分

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

UID
44768
好友
1
蛮牛币
1853
威望
0
注册时间
2014-9-13
在线时间
1014 小时
最后登录
2017-9-22

活力之星

发表于 2016-12-16 09:03:23 | 显示全部楼层
感謝分享

回复

使用道具 举报

4四处流浪
447/500
排名
3671
昨日变化

0

主题

21

帖子

447

积分

Rank: 4

UID
159672
好友
0
蛮牛币
947
威望
0
注册时间
2016-7-30
在线时间
124 小时
最后登录
2017-3-2
发表于 2016-12-16 09:19:09 | 显示全部楼层
如果您要查看本帖隐藏

回复 支持 反对

使用道具 举报

5熟悉之中
816/1000
排名
3285
昨日变化
1

4

主题

164

帖子

816

积分

Rank: 5Rank: 5

UID
182321
好友
0
蛮牛币
-3
威望
0
注册时间
2016-11-9
在线时间
306 小时
最后登录
2017-8-31
发表于 2016-12-16 09:26:35 | 显示全部楼层
DuangDuangDuang,是特效的演技,是演技的特效。
[发帖际遇]: 一个袋子砸在了 连山归藏 头上,连山归藏 赚了 1 蛮牛币. 幸运榜 / 衰神榜

回复 支持 反对

使用道具 举报

7日久生情
1578/5000
排名
2112
昨日变化
3

4

主题

448

帖子

1578

积分

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

UID
112674
好友
4
蛮牛币
958
威望
0
注册时间
2015-7-8
在线时间
610 小时
最后登录
2017-9-22
QQ
发表于 2016-12-16 09:28:50 | 显示全部楼层

太bang了啊啊啊

回复

使用道具 举报

排名
328
昨日变化

43

主题

358

帖子

4222

积分

Rank: 9Rank: 9Rank: 9

UID
42814
好友
32
蛮牛币
462
威望
0
注册时间
2014-8-31
在线时间
1311 小时
最后登录
2017-9-25

专栏作家

QQ
发表于 2016-12-16 09:44:52 | 显示全部楼层
Unity&Shader案例篇—绘制雪花

回复 支持 反对

使用道具 举报

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

本版积分规则

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