轉自:http://www.manew.com/thread-98360-1-1.html
一、前言
上一篇有個案例講到了繪制雨滴,沒有看過的童鞋可以在回去看看,這一篇其實思路和繪制雨滴是一樣的。首先,用代碼C#生成頂點和面片,然
后用Shader代碼渲染,最后在用C#代碼控制Shader的參數(shù)使得雪花飄起來。飄動的時候加點噪聲處理,使得雪花的飄落更符合真實。上一篇加班太
晚寫的有點倉促,這一篇爭取寫的具體點,每天加班到很晚真的傷不起,尤其傷腎,真的。
依然廢話不多說先上效果圖,切換不同的貼圖可以得到不同的雪花
二、制作步驟
1、C#代碼生成頂點和面片:首先要了解Unity生成頂點和網(wǎng)格面片,下面這個代碼就是有三個頂點畫一個三角形
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。接下來就是將頂點連成三角形面片,這個三角形內(nèi)的點數(shù)必須是3的倍數(shù)。你可以添
加頂點和三角點的連線得到你想要的網(wǎng)格面片,修改代碼如下會得到如下圖所示的效果圖
[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 };
好了,有了繪制網(wǎng)格的基礎,那么接下來開始進入主題。我們當然不需要讓C#代碼來給我們將雪花的樣子的面片畫出來了,其實只要畫一個向上面
的正方形塊就好了,然后通過Shader將一個雪花的貼圖渲到正方形網(wǎng)格上九OK了。首先,生成頂點和面片,然后在Update函數(shù)里給Shader傳遞運動
參數(shù),完整的代碼如下:
usingUnityEngine;
usingSystem.Collections;
[RequireComponent(typeof(MeshFilter),typeof(MeshRenderer))]
publicclassSnow : MonoBehaviour
{
//Unity可以支持多達64000個頂點,如果一個雪花有4個頂點組成,則最多有16000個雪花
constintSNOW_NUM = 16000;
//頂點
privateVector3[] m_vertices;
//頂點構成的三角面
privateint[] triangles_;
//雪花網(wǎng)格的貼圖
privateVector2[] uvs_;
//雪花的范圍
privatefloatrange;
//雪花范圍的倒數(shù),為了提高計算效率
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實現(xiàn)
[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;
//頂點分布的區(qū)域應該是-_Range到_Range,因此target-mv的范圍應該也是這個,因此此處的trip值的范圍為,0~1,計算的最終目的還是為了讓雪花始終在攝像機的正前方
trip = floor(((target - mv)*_RangeR + 1) * 0.5);
//經(jīng)過前面的坐標系的換算再次將范圍擴大到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;
//頂點分布的區(qū)域應該是-_Range到_Range,因此target-mv的范圍應該也是這個,因此此處的trip值的范圍為,0~1,計算的最終目的還是為了讓雪花始終在攝像機的正前方
trip = floor(((target - mv)*_RangeR + 1) * 0.5);
//經(jīng)過前面的坐標系的換算再次將范圍擴大到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
}
}
}
三、尾語
總結來說這種方式實現(xiàn)的大量粒子性的效果要比直接使用粒子系統(tǒng)在性能上的靠小要少很多,我在調試模式下的運行參數(shù)如圖所示,總之,是一個
實用切有效的Shader案例。
終于,寫完了,快十點啦,哎!生活正他么不容易
為了方便大家學習參考,附上百度工程文件如下