转自:http://www.manew.com/thread-98360-1-1.html
一、前言
上一篇有个案例讲到了绘制雨滴,没有看过的童鞋可以在回去看看,这一篇其实思路和绘制雨滴是一样的。首先,用代码C#生成顶点和面片,然
后用Shader代码渲染,最后在用C#代码控制Shader的参数使得雪花飘起来。飘动的时候加点噪声处理,使得雪花的飘落更符合真实。上一篇加班太
晚写的有点仓促,这一篇争取写的具体点,每天加班到很晚真的伤不起,尤其伤肾,真的。
依然废话不多说先上效果图,切换不同的贴图可以得到不同的雪花
二、制作步骤
1、C#代码生成顶点和面片:首先要了解Unity生成顶点和网格面片,下面这个代码就是有三个顶点画一个三角形
usingUnityEngine;
usingSystem.Collections;
[RequireComponent(typeof(MeshFilter),typeof(MeshRenderer))]
publicclassMeshTriangle : MonoBehaviour {
privateMesh mesh;
// Use this for initialization
voidStart () {
mesh = GetComponent().mesh;
mesh.Clear();
mesh.vertices =newVector3[] { Vector3.zero,newVector3(0, 1, 0),newVector3(1, 1, 0) };
mesh.uv =newVector2[] { Vector2.zero, Vector2.zero, Vector2.zero };
mesh.triangles =newint[] { 0, 1, 2};
}
// Update is called once per frame
voidUpdate () {
}
}
将这个代码随便赋给一个空的GameObject就可以得到如图所示的三角形,代码其实很简单,就是在空间中先定义三个顶点,然后贴图部分因为这里
没有使用贴图,所以坐标就无所谓,但是坐标点的范围是0~1。接下来就是将顶点连成三角形面片,这个三角形内的点数必须是3的倍数。你可以添
加顶点和三角点的连线得到你想要的网格面片,修改代码如下会得到如下图所示的效果图
[C#]纯文本查看复制代码
1
2mesh.vertices =newVector3[] { Vector3.zero,newVector3(0, 1, 0),newVector3(1, 1, 0),newVector3(1,0,0) };
mesh.uv =newVector2[] { Vector2.zero, Vector2.zero, Vector2.zero, Vector2.zero };
mesh.triangles =newint[] { 0, 1, 2,0,2,3 };
好了,有了绘制网格的基础,那么接下来开始进入主题。我们当然不需要让C#代码来给我们将雪花的样子的面片画出来了,其实只要画一个向上面
的正方形块就好了,然后通过Shader将一个雪花的贴图渲到正方形网格上九OK了。首先,生成顶点和面片,然后在Update函数里给Shader传递运动
参数,完整的代码如下:
usingUnityEngine;
usingSystem.Collections;
[RequireComponent(typeof(MeshFilter),typeof(MeshRenderer))]
publicclassSnow : MonoBehaviour
{
//Unity可以支持多达64000个顶点,如果一个雪花有4个顶点组成,则最多有16000个雪花
constintSNOW_NUM = 16000;
//顶点
privateVector3[] m_vertices;
//顶点构成的三角面
privateint[] triangles_;
//雪花网格的贴图
privateVector2[] uvs_;
//雪花的范围
privatefloatrange;
//雪花范围的倒数,为了提高计算效率
privatefloatrangeR_;
privateVector3 move_ = Vector3.zero;
voidStart ()
{
range = 16f;
rangeR_ = 1.0f/range;
m_vertices =newVector3[SNOW_NUM*4];
for(var i = 0; i < SNOW_NUM; ++i) {
floatx = Random.Range (-range, range);
floaty = Random.Range (-range, range);
floatz = Random.Range (-range, range);
var point =newVector3(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_ =newint[SNOW_NUM * 6];
for(inti = 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_ =newVector2[SNOW_NUM*4];
for(var i = 0; i < SNOW_NUM; ++i) {
uvs_ [i*4+0] =newVector2 (0f, 0f);
uvs_ [i*4+1] =newVector2 (1f, 0f);
uvs_ [i*4+2] =newVector2 (0f, 1f);
uvs_ [i*4+3] =newVector2 (1f, 1f);
}
Mesh mesh =newMesh ();
mesh.name ="MeshSnowFlakes";
mesh.vertices = m_vertices;
mesh.triangles = triangles_;
mesh.uv = uvs_;
mesh.bounds =newBounds(Vector3.zero, Vector3.one * 99999999);
var mf = GetComponent ();
mf.sharedMesh = mesh;
}
voidLateUpdate ()
{
var target_position = Camera.main.transform.TransformPoint(Vector3.forward * range);
var mr = GetComponent ();
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);
floatx = (Mathf.PerlinNoise(0f, Time.time*0.1f)-0.5f) * 10f;
floaty = -2f;
floatz = (Mathf.PerlinNoise(Time.time*0.1f, 0f)-0.5f) * 10f;
move_ +=newVector3(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#]纯文本查看复制代码
1
2
3
4[/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#]纯文本查看复制代码
1
2
3
4[/size][/align]
[align=left][size=4]//让顶点始终保持在摄像机的正上方位置
float3 diff = _CamUp * _Size;
float3 finalposition;
float3 tv0 = mv;
当然还有运动部分:
[C#]纯文本查看复制代码
1
2
3
4
5
6float3 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;
structappdata_custom {
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
structv2f {
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);
returno;
}
fixed4 frag(v2f i) : SV_Target
{
returntex2D(_MainTex, i.uv);
}
ENDCG
}
}
}
三、尾语
总结来说这种方式实现的大量粒子性的效果要比直接使用粒子系统在性能上的靠小要少很多,我在调试模式下的运行参数如图所示,总之,是一个
实用切有效的Shader案例。
终于,写完了,快十点啦,哎!生活正他么不容易
为了方便大家学习参考,附上百度工程文件如下