項目場景需求一個遍及鮮花的小島,因爲運行在手機上,因此對效率有必定的要求。html
環境unity2017.3.f1,使用simpleLOD這個插件,方便作mesh合併,以及LOD處理app
先放1張最終的效果圖。dom
1.shader編寫ide
先找來一個花的模型,貼圖模型大體以下:函數
shaderVS階段作一個頂點運動。大體思路是花越靠近地面的,搖晃幅度越小,反之幅度越大。這個高度能夠用頂點座標來作,不過要兼容靜態烘焙,或者地面不平等狀況,沒法獲取準確高度。我這裏採用UV的思路(美術保證草的根在貼圖底部)。post
代碼以下this
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.簡單的筆刷spa
可以在場景裏面編輯。可以調整大小,草密度,隨機大小,繞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)code
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 } } }