Shader案例篇—绘制雪花

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容