Unity 大面积花草风吹动效果以及编辑

项目场景需求一个遍布鲜花的小岛,由于运行在手机上,所以对效率有一定的要求。

环境unity2017.3.f1,使用simpleLOD这个插件,方便做mesh合并,以及LOD处理

先放1张最终的效果图。

1.shader编写

    先找来一个花的模型,贴图模型大致如下:

shaderVS阶段做一个顶点运动。大致思路是花越靠近地面的,摇晃幅度越小,反之幅度越大。这个高度可以用顶点坐标来做,不过要兼容静态烘焙,或者地面不平等情况,无法获取准确高度。我这里采用UV的思路(美术保证草的根在贴图底部)。

代码如下

Shader "custom/2-sided_grass"
{
    Properties 
    {
        _Color ("Main Color", Color) = (1,1,1,1)
        _SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 0)
        _Shininess ("Shininess", Range (0.01, 10)) = 0.078125
        _MainTex ("Base (RGB) TransGloss (A)", 2D) = "white" {}
        _BumpMap ("Normalmap", 2D) = "bump" {}
        _Cutoff ("Alpha cutoff", Range(0,1)) = 0.5

        _Direction("Direction",Vector) =(0,0,0,0) //运动的方向
        _TimeScale("TimeScale",float) = 1        //时间
        _TimeDelay("TimeDelay",float) = 1     //延迟
    }

    SubShader 
    {
        Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
        LOD 400
        Cull Off
    
        CGPROGRAM
        #pragma surface surf BlinnPhong alphatest:_Cutoff vertex:vert
        #pragma target 3.0
    
        sampler2D _MainTex;
        sampler2D _BumpMap;
        fixed4 _Color;
        half _Shininess;
        fixed4 _Direction;
        half _TimeScale;
        half _TimeDelay;

        struct Input 
        {
            float2 uv_MainTex;
            float2 uv_BumpMap;
        };
    
        void vert(inout appdata_full v) 
        {
            fixed4 worldPos =  mul(unity_ObjectToWorld,v.vertex);
            half dis =  v.texcoord.y; //这里采用UV的高度来做。也可以用v.vertext.y
            half time = (_Time.y + _TimeDelay) * _TimeScale;
            v.vertex.xyz += dis * (sin(time + worldPos.x) * cos(time * 2 / 3) + 0.3)* _Direction.xyz;    //核心,动态顶点变换
        } 

        void surf (Input IN, inout SurfaceOutput o) 
        {
            fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
            o.Albedo = tex.rgb * _Color.rgb;
            o.Gloss = tex.rgb * _Color.rgb;
            o.Alpha = tex.a * _Color.a;
            o.Specular = _Shininess;
            o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
        }
        ENDCG 
    }    

    FallBack "Transparent/Cutout/VertexLit"
}

 

2.简单的笔刷

 能够在场景里面编辑。能够调整大小,草密度,随机大小,绕Y轴旋转等,我们写一个简单的管理脚本,也方便做Mesh合并以及LOD。GrassGroup代码如下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GrassGroup : MonoBehaviour
{
    [Tooltip("是否打开编辑")]
    public bool editorMode = false;
    [Tooltip("预制体")]
    public GameObject grassPrefab = null;
    [Tooltip("地形")]
    public Terrain terrain = null;
    [Tooltip("随机朝向")]
    public bool roodomRotationY = true;
    [Tooltip("随时缩放最小值")]
    public float minScale = 1;
    [Tooltip("随时缩放最小值")]
    public float maxScale = 1;
    [Tooltip("半径")]
    [HideInInspector]
    public float radius = 1;
    [Tooltip("数量")]
    [HideInInspector]
    public int count = 1;

    // Use this for initialization
    void Start ()
    {
        editorMode = false;
    }
    /// <summary>
    /// 生成子草
    /// </summary>
    /// <param name="postion"></param>
    public void AddGrassNode(Vector3 postion)
    {
        if (grassPrefab == null)
        {
            Debug.LogError("草预制件不能为空!!!!!");
            return;
        }

        if (terrain == null)
        {
            Debug.LogError("地形不能为空!!!!!");
            return;
        }

        for (int i = 0;i<count; i++)
        {
            GameObject go = GameObject.Instantiate(grassPrefab);
            go.transform.SetParent(transform);
            Vector2 p = Random.insideUnitCircle * radius;//将位置设置为一个半径为radius中心点在原点的圆圈内的某个点X.  
            Vector2 pos2 = p.normalized * (p.magnitude);
            Vector3 pos3 = new Vector3(pos2.x, 0, pos2.y) + postion;
            float y = terrain.SampleHeight(pos3);
            Vector3 pos = new Vector3(pos3.x ,y, pos3.z);
            go.transform.position = pos;
            if (roodomRotationY)
                go.transform.Rotate(new Vector3(0, 0, 1),Random.Range(0,360) );
            float scale = Random.Range(minScale, maxScale);
            go.transform.localScale = new Vector3(scale, scale, scale);
            go.name = "grass_" + transform.childCount.ToString();
        }
    }
}

Editor代码,简单写了下(注:HeGizmosCircle 是一个画圆的代码,稍微修改了下,网上找的https://www.cnblogs.com/TravelingLight/archive/2013/08/27/3286242.html

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(GrassGroup))]
public class GrassGroup_Inspector : Editor
{
    private GrassGroup grassGroup = null;
    private float m_Theta = 0.1f; // 值越低圆环越平滑  
    private Color m_Color = Color.blue; // 线框颜色  
    private HeGizmosCircle heGizmosCircle = null;
    void OnEnable()
    {
        grassGroup = target as GrassGroup;
        if (heGizmosCircle == null)
            heGizmosCircle = GameObject.FindWithTag("HeGizmosCircle").GetComponent<HeGizmosCircle>();
    }
    void OnDisable()
    {
        if (heGizmosCircle != null)
        {
            heGizmosCircle.SetEnable(grassGroup.editorMode);
        }         
    }
    public override void OnInspectorGUI()
    {

        base.OnInspectorGUI();
        GUILayout.BeginHorizontal();
        GUILayout.Label("radius(半径):" + grassGroup.radius.ToString());
        grassGroup.radius = GUILayout.HorizontalSlider(grassGroup.radius, 0, 10, null);
        if (heGizmosCircle != null)
            heGizmosCircle.m_Radius = grassGroup.radius;
        GUILayout.EndHorizontal();
        GUILayout.BeginHorizontal();
        GUILayout.Label("count(数量):" + grassGroup.count.ToString());
        grassGroup.count = System.Convert.ToInt32(GUILayout.HorizontalSlider(grassGroup.count, 1, 100, null));
        GUILayout.EndHorizontal();
    }

    [MenuItem("地图编辑/创建GrassGroup")]
    static void CreateGrassGroup()
    {
        GameObject go = new GameObject("GrassGroup");
        GrassGroup group = go.AddComponent<GrassGroup>();
        go.transform.position = Vector3.zero;
    }

    public void OnSceneGUI()
    {
        if (grassGroup ==null || !grassGroup.editorMode)
            return;

        if (grassGroup.editorMode)
        {
            Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
            RaycastHit hitInfo;
            if (Physics.Raycast(ray, out hitInfo, 1 << 8))
            {
                heGizmosCircle.transform.position = hitInfo.point + new Vector3(0,0.2f,0);
                if (Event.current.type == EventType.MouseDown)
                {
                    grassGroup.AddGrassNode(hitInfo.point);
                }           
            }
        }
    }
}

3.地图编辑以及合并mesh.

代码基本写完了,大致如下

勾上EditorMode即可在场景中编辑

使用SimpleLOD合并mesh,分组做LOD(我这里没做LOD,面有点多)。

看看最后shader参数,以及DrawCall等效率

这张去掉地形,只有天空盒子,花

面有点高,可以分组做LOD

补充一下,由于光照效果理想,修改了光照函数,改成贴图颜色了。

Shader "custom/2-sided_grass"
{
	Properties 
	{
		_Color ("Main Color", Color) = (1,1,1,1)
		_SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 0)
		_Shininess ("Shininess", Range (0.01, 10)) = 0.078125
		_MainTex ("Base (RGB) TransGloss (A)", 2D) = "white" {}
		_BumpMap ("Normalmap", 2D) = "bump" {}
		_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5

		_Direction("Direction",Vector) =(0,0,0,0)
		_TimeScale("TimeScale",float) = 1
		_TimeDelay("TimeDelay",float) = 1
	}

	SubShader 
	{
		Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
		LOD 400
		Lighting Off
		Cull Off
	
		CGPROGRAM
		#pragma surface surf myLightModel alphatest:_Cutoff vertex:vert
		#pragma target 3.0
	
		sampler2D _MainTex;
		sampler2D _BumpMap;
		fixed4 _Color;
		half _Shininess;
		fixed4 _Direction;
		half _TimeScale;
		half _TimeDelay;

		struct Input 
		{
			float2 uv_MainTex;
			float2 uv_BumpMap;
		};
	    //修改为主要贴图的颜色 
        //lightDir :点到光源的单位向量   viewDir:点到摄像机的单位向量   atten:衰减系数   
        float4 LightingmyLightModel(SurfaceOutput s, float3 lightDir,half3 viewDir, half atten)   
        {   
            float4 c ;   
            c.rgb =  s.Albedo;  
            c.a = s.Alpha;   
            return c;   
        }  

		void vert(inout appdata_full v) 
		{
			fixed4 worldPos =  mul(unity_ObjectToWorld,v.vertex);
			half dis =  v.texcoord.y;
			half time = (_Time.y + _TimeDelay) * _TimeScale;
			v.vertex.xyz += dis * (sin(time + worldPos.x) * cos(time * 2 / 3) + 0.3)* _Direction.xyz;	//核心,动态顶点变换
		} 

		void surf (Input IN, inout SurfaceOutput o) 
		{
			fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
			o.Albedo = tex.rgb * _Color.rgb;
			o.Gloss = tex.rgb * _Color.rgb;
			o.Alpha = tex.a * _Color.a;
			o.Specular = _Shininess;
			o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
		}
		ENDCG
	}	
	FallBack "Transparent/Cutout/VertexLit"
}

游戏中效果

修改了一个顶点着色器shader

Shader "custom/TwoSideGrass"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_Color ("Color", Color) = (1, 1, 1, 1)
		_Cutoff ("Alpha cutoff", Range(0, 1)) = 0.5
		_Direction("Direction", Vector) = (0, 0, 0, 0)
		_TimeScale("TimeScale", Float) = 1
		_TimeDelay("TimeDelay", Float) = 1
	}
	SubShader
	{
		Tags { "RenderType"="TransparentCutout" "Queue"="AlphaTest" "IgnoreProjector"="True" }
		LOD 400
		Cull Off

		Pass
		{
//			Tags { "LightMode"="ForwardBase" }

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 3.0
			// make fog work
			#pragma multi_compile_fog
			#pragma fragmentoption ARB_precision_hint_fastest
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				UNITY_FOG_COORDS(1)
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			float _TimeScale, _TimeDelay;
			float4 _Direction;
			
			v2f vert (appdata v)
			{
				v2f o;

				float4 worldPos =  mul(unity_ObjectToWorld,v.vertex);
				float dis =  v.uv.y;
				float time = (_Time.y + _TimeDelay) * _TimeScale;
				v.vertex.xyz += dis * (sin(time + worldPos.x) * cos(time * 0.667) + 0.3) * _Direction.xyz;

				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				UNITY_TRANSFER_FOG(o,o.vertex);

				return o;
			}

			fixed _Cutoff;
			fixed4 _Color;
			fixed4 frag (v2f i) : SV_Target
			{
				// sample the texture
				fixed4 col = tex2D(_MainTex, i.uv) * _Color;
				clip(col.a - _Cutoff);
				// apply fog
				UNITY_APPLY_FOG(i.fogCoord, col);
				return col*2;
			}
			ENDCG
		}
	}
}
  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Unity是一款强大的游戏引擎,可以通过其内置的粒子系统轻松地实现各种特效,包括模拟效果。 通过Unity的粒子系统,我们可以制作出非常逼真的效果。首先,我们需要创建一个空的GameObject来作为的发射器。然后,在该对象上创建一个粒子系统组件。 在粒子系统组件的Inspector面板中,我们可以设置粒子的外观、速度、发射方向和颜色等属性。为了制作出模拟效果,我们可以将发射速度和数量调整为较大值,以便让粒子能够产生一个向外扩散的效果。 接下来,我们可以通过设置粒子的大小、形状和颜色等属性来使其更加逼真。可以尝试调整粒子的大小为较小的值,使其看起来更像细小的气流。 为了让看起来更加逼真,我们可以添加额外的效果。例如,可以在粒子系统上添加一个向的Force Field,以模拟的流动。可以设置Force Field的大小、位置和强度,使其在特定区域内产生一个向的力场。 最后,我们可以将制作好的效果应用到游戏场景中。可以将的发射器放置在需要出现的位置上,并根据需要调整其大小和方向。 通过以上步骤,我们可以在Unity中使用粒子系统轻松地制作出逼真的效果。在游戏开发中,我们可以根据需要调整粒子的属性和效果,以实现各种各样的效果,为游戏增添更多的视觉效果和交互性。 ### 回答2: 使用Unity制作的粒子效果是非常简单的。下面是一个简单的步骤: 1. 在Unity中创建一个新的粒子系统。在层次结构中单击右键,选择“Effects” -> “Particle System”来创建一个新的粒子系统。 2. 调整粒子系统的属性。在粒子系统的检查器窗口中,你可以调整各种属性来创建不同的效果。例如,你可以调整粒子的速度,大小,颜色等等。 3. 添加强度。在粒子系统的检查器窗口中,你可以找到一个名为“Force over Lifetime”的属性。通过调整该属性的曲线,你可以模拟效果。例如,你可以创建一个向某个方向推动粒子的曲线,以模拟向。 4. 添加特效。如果你想要让效果更加真实,你可以添加一些的特效。例如,你可以添加一个旋转的特效来模拟的旋转。你可以在粒子系统的检查器窗口中找到各种特效选项。 5. 调整其他参数。根据你想要的效果,你还可以调整粒子系统的其他参数。例如,你可以调整发射速率,粒子的生命周期等等。 通过以上步骤,你可以在Unity中制作出逼真的的粒子效果。你可以根据你的需求和创意进行更多的调整和改进。希望这个回答对你有帮助! ### 回答3: 使用Unity制作效果可以使用粒子系统来实现。以下是一个简单的步骤: 1. 创建粒子系统:在Unity编辑器中,打开场景或者创建一个新的场景。然后在“Hierarchy”面板中,右键单击并选择“Effects”>“ParticleSystem”来创建一个新的粒子系统。 2. 配置粒子系统属性:在Inspector面板中,可以调整粒子系统的各种属性以实现所需的效果。例如,可以通过调整“Start Speed”,“Start Size”和“Lifetime”等属性来控制的强度和持续时间。 3. 设置粒子贴图:可以为粒子系统使用相关的贴图,例如云朵或者烟的贴图。在Inspector面板中,可以将所需的贴图拖放到“Renderer”组件中的“Material”字段上。 4. 调整粒子系统形状:可以通过调整粒子系统的形状来使其更加符合效果。在Inspector面板中,可以选择不同的形状类型,并调整其大小、旋转和位置等属性。 5. 添加其他特效:如果想要进一步增强效果,可以添加其他特效。例如,在粒子系统上添加一个“WindZone”组件,以模拟的力度和方向。可以在Inspector面板中调整该组件的属性,如“Wind Main”和“Wind Turbulence”。 6. 调整摄像机和光照效果:为了使效果更加真实,可能需要调整场景中的摄像机和光照效果。可以调整摄像机的视角和位置,以及灯光的类型、强度和颜色等属性,以适应的环境。 通过使用Unity的粒子系统,并按照上述步骤进行配置,就可以制作出逼真的效果。可以根据需求进一步调整各种参数和添加其他特效,以创造出更加独特和个性化的效果

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值