百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Unity&Shader案例篇—绘制雪花

bigegpt 2024-08-20 10:55 2 浏览

一、前言

上一篇有个案例讲到了绘制雨滴,没有看过的童鞋可以在回去看看,这一篇其实思路和绘制雨滴是一样的。首先,用代码C#生成顶点和面片,然

后用Shader代码渲染,最后在用C#代码控制Shader的参数使得雪花飘起来。飘动的时候加点噪声处理,使得雪花的飘落更符合真实。上一篇加班太

晚写的有点仓促,这一篇争取写的具体点,每天加班到很晚真的伤不起,尤其伤肾,真的。

依然废话不多说先上效果图,切换不同的贴图可以得到不同的雪花

二、制作步骤

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

}

}

将这个代码随便赋给一个空的GameObject就可以得到如图所示的三角形,代码其实很简单,就是在空间中先定义三个顶点,然后贴图部分因为这里

没有使用贴图,所以坐标就无所谓,但是坐标点的范围是0~1。接下来就是将顶点连成三角形面片,这个三角形内的点数必须是3的倍数。你可以添

加顶点和三角点的连线得到你想要的网格面片,修改代码如下会得到如下图所示的效果图

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

好了,有了绘制网格的基础,那么接下来开始进入主题。我们当然不需要让C#代码来给我们将雪花的样子的面片画出来了,其实只要画一个向上面

的正方形块就好了,然后通过Shader将一个雪花的贴图渲到正方形网格上九OK了。首先,生成顶点和面片,然后在Update函数里给Shader传递运动

参数,完整的代码如下:

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

}

}

2、Shader部分:这一部分做的工作不仅仅是将顶点生成的面片用贴图去渲染,其实还包括让雪花始终朝着摄像机方向

//从给定的局部坐标到摄像机坐标进行转换,目的是让顶点始终朝向摄像机

float3 eyeVector = ObjSpaceViewDir(float4(tv0, 0));

//float3 eyeVector = mv;

float3 sideVector = normalize(cross(eyeVector, diff));

雪花的生成范围始终是从摄像机的正上方落下,这个在C#代码部分通过将摄像机的正方向传递给Shader实现

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

Shader代码部分:

//让顶点始终保持在摄像机的正上方位置

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代码如下:

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;

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

}

}

}

三、尾语

总结来说这种方式实现的大量粒子性的效果要比直接使用粒子系统在性能上的靠小要少很多,我在调试模式下的运行参数如图所示,总之,是一个

实用切有效的Shader案例。

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

为了方便大家学习参考,附上工程文件:http://www.manew.com/thread-98360-1-1.html 。

相关推荐

悠悠万事,吃饭为大(悠悠万事吃饭为大,什么意思)

新媒体编辑:杜岷赵蕾初审:程秀娟审核:汤小俊审签:周星...

高铁扒门事件升级版!婚宴上‘冲喜’老人团:我们抢的是社会资源

凌晨两点改方案时,突然收到婚庆团队发来的视频——胶东某酒店宴会厅,三个穿大红棉袄的中年妇女跟敢死队似的往前冲,眼瞅着就要扑到新娘的高额钻石项链上。要不是门口小伙及时阻拦,这婚礼造型团队熬了三个月的方案...

微服务架构实战:商家管理后台与sso设计,SSO客户端设计

SSO客户端设计下面通过模块merchant-security对SSO客户端安全认证部分的实现进行封装,以便各个接入SSO的客户端应用进行引用。安全认证的项目管理配置SSO客户端安全认证的项目管理使...

还在为 Spring Boot 配置类加载机制困惑?一文为你彻底解惑

在当今微服务架构盛行、项目复杂度不断攀升的开发环境下,SpringBoot作为Java后端开发的主流框架,无疑是我们手中的得力武器。然而,当我们在享受其自动配置带来的便捷时,是否曾被配置类加载...

Seata源码—6.Seata AT模式的数据源代理二

大纲1.Seata的Resource资源接口源码2.Seata数据源连接池代理的实现源码3.Client向Server发起注册RM的源码4.Client向Server注册RM时的交互源码5.数据源连接...

30分钟了解K8S(30分钟了解微积分)

微服务演进方向o面向分布式设计(Distribution):容器、微服务、API驱动的开发;o面向配置设计(Configuration):一个镜像,多个环境配置;o面向韧性设计(Resista...

SpringBoot条件化配置(@Conditional)全面解析与实战指南

一、条件化配置基础概念1.1什么是条件化配置条件化配置是Spring框架提供的一种基于特定条件来决定是否注册Bean或加载配置的机制。在SpringBoot中,这一机制通过@Conditional...

一招解决所有依赖冲突(克服依赖)

背景介绍最近遇到了这样一个问题,我们有一个jar包common-tool,作为基础工具包,被各个项目在引用。突然某一天发现日志很多报错。一看是NoSuchMethodError,意思是Dis...

你读过Mybatis的源码?说说它用到了几种设计模式

学习设计模式时,很多人都有类似的困扰——明明概念背得滚瓜烂熟,一到写代码就完全想不起来怎么用。就像学了一堆游泳技巧,却从没下过水实践,很难真正掌握。其实理解一个知识点,就像看立体模型,单角度观察总...

golang对接阿里云私有Bucket上传图片、授权访问图片

1、为什么要设置私有bucket公共读写:互联网上任何用户都可以对该Bucket内的文件进行访问,并且向该Bucket写入数据。这有可能造成您数据的外泄以及费用激增,若被人恶意写入违法信息还可...

spring中的资源的加载(spring加载原理)

最近在网上看到有人问@ContextConfiguration("classpath:/bean.xml")中除了classpath这种还有其他的写法么,看他的意思是想从本地文件...

Android资源使用(android资源文件)

Android资源管理机制在Android的开发中,需要使用到各式各样的资源,这些资源往往是一些静态资源,比如位图,颜色,布局定义,用户界面使用到的字符串,动画等。这些资源统统放在项目的res/独立子...

如何深度理解mybatis?(如何深度理解康乐服务质量管理的5个维度)

深度自定义mybatis回顾mybatis的操作的核心步骤编写核心类SqlSessionFacotryBuild进行解析配置文件深度分析解析SqlSessionFacotryBuild干的核心工作编写...

@Autowired与@Resource原理知识点详解

springIOCAOP的不多做赘述了,说下IOC:SpringIOC解决的是对象管理和对象依赖的问题,IOC容器可以理解为一个对象工厂,我们都把该对象交给工厂,工厂管理这些对象的创建以及依赖关系...

java的redis连接工具篇(java redis client)

在Java里,有不少用于连接Redis的工具,下面为你介绍一些主流的工具及其特点:JedisJedis是Redis官方推荐的Java连接工具,它提供了全面的Redis命令支持,且...