Shader案例篇—繪制雪花

轉自: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案例。

終于,寫完了,快十點啦,哎!生活正他么不容易

為了方便大家學習參考,附上百度工程文件如下

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內(nèi)容