### 二、制作步骤

1、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 () {

}
}

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 };

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);
}
}

//从给定的局部坐标到摄像机坐标进行转换，目的是让顶点始终朝向摄像机
float3 eyeVector = ObjSpaceViewDir(float4(tv0, 0));
//float3 eyeVector = mv;
float3 sideVector = normalize(cross(eyeVector, diff));


mr.material.SetVector("_CamUp", Camera.main.transform.up);

//让顶点始终保持在摄像机的正上方位置
float3 diff = _CamUp * _Size;
float3 finalposition;
float3 tv0 = mv;

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 "Custom/snow" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
}
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
}
}
}


### 三、尾语

http://pan.baidu.com/s/1i44IER7

• 广告
• 抄袭
• 版权
• 政治
• 色情
• 无意义
• 其他

120