知乎视频​www.zhihu.com

https://medium.com/@andresgomezjr89/rain-snow-with-geometry-shaders-in-unity-83a757b767c1

接下来我们尝试添加天气系统。主要是雨滴和雪花下落。

我们可以使用几何着色器来批量创建粒子,可以减少从CPU送往GPU顶点数据的瓶颈问题,让大多数的工作在GPU中完成。我们可以在一个绘制命令中批处理所有的雨滴和雪花。

我们首先编写一个移动摄像机的脚本来帮助我们观察场景:

 using UnityEngine;​public class FreeCamera : MonoBehaviour{public float moveSpeed = 5;public float moveSpeedUp = 20;public float turnSpeed = 75;float GetAxis (KeyCode pos, KeyCode neg) {float r = 0;if (Input.GetKey(pos)) r += 1;if (Input.GetKey(neg)) r -= 1;return r;}​float ClampAngle(float angle, float min, float max) {// remap from [0, 360] to [-180, 180]return Mathf.Clamp(((angle + 180f) % 360f - 180f), min, max);}​void Update () {// handle rotationfloat tSpeed = turnSpeed * Time.deltaTime;Vector3 angles = transform.rotation.eulerAngles;// clamp up down look, so we cant do somersaultsangles.x = ClampAngle(angles.x + GetAxis (KeyCode.UpArrow, KeyCode.DownArrow) * tSpeed, -89, 89);angles.y += GetAxis (KeyCode.RightArrow, KeyCode.LeftArrow) * tSpeed;angles.z = 0;transform.rotation = Quaternion.Euler(angles);// handle movmeentVector3 side = transform.right * GetAxis (KeyCode.D, KeyCode.A);Vector3 upDown = transform.up * GetAxis (KeyCode.E, KeyCode.Q);Vector3 fwd = transform.forward * GetAxis (KeyCode.W, KeyCode.S);float mSpeed = (Input.GetKey(KeyCode.LeftShift) ? moveSpeedUp : moveSpeed) * Time.deltaTime;transform.position += (side + upDown + fwd) * mSpeed;}}

将脚本附在摄像机上就可以随意移动。

接下来实现网格系统。为了节省性能消耗,我们可以只渲染摄像机一定范围内的效果。这样的话随摄像机移动,效果也会随之移动渲染。但这会有问题,当摄像机移动时,我们可能会在某些位置看到雨雪分界线。

我们可以在世界空间完成一个网格,其中只有网格坐标内的区域会渲染雨滴:

不过在玩家到达边界时会发生问题:

此时玩家会看到明显的雨雪分界线。

为了修复这一点我们可以向每个方向扩大一个网格,在下面的​网格渲染效果,这样的话就不会得到边界效果:

这样在世界空间中,我们会为摄像机的活动范围设置一个​的网格立方体。我们可以定义一个脚本来显示这一网格范围,帮助我们观察:

 using UnityEngine;using System;​[ExecuteInEditMode] public class GridHandler : MonoBehaviour{[Tooltip("How large (in meters) one grid block side is")]public float gridSize = 10f;[Tooltip("The player's transform to track")]public Transform playerTransform;// a callback to subscribe to when the player grid changespublic event Action<Vector3Int> onPlayerGridChange;Vector3Int lastPlayerGrid = new Vector3Int(-99999,-99999,-99999);// Update runs once per frame.void Update () {if (playerTransform == null) {Debug.LogWarning("Grid Handler Has No Player Transform!");return;}// calculate the grid coordinate where the player currently isVector3 playerPos = playerTransform.position;Vector3Int playerGrid = new Vector3Int(Mathf.FloorToInt(playerPos.x / gridSize),Mathf.FloorToInt(playerPos.y / gridSize),Mathf.FloorToInt(playerPos.z / gridSize));// check if the player changed grid coordinates since the last checkif (playerGrid != lastPlayerGrid) {// if it has, then broadcast the new grid coordinates// to whoever subscribed to the callbackif (onPlayerGridChange != null)onPlayerGridChange(playerGrid);lastPlayerGrid = playerGrid;}}// calculate the center position of a certain grid coordinatepublic Vector3 GetGridCenter(Vector3Int grid) {float halfGrid = gridSize * .5f;return new Vector3(grid.x * gridSize + halfGrid,grid.y * gridSize + halfGrid,grid.z * gridSize + halfGrid);}// draw gizmo cubes around teh grids where the player is// so we can see it in the scene viewvoid OnDrawGizmos () {// loop in a 3 x 3 x 3 gridfor (int x = -1; x <= 1; x++) {for (int y = -1; y <= 1; y++) {for (int z = -1; z <= 1; z++) {bool isCenter = x == 0 && y == 0 && z == 0;Vector3 gridCenter = GetGridCenter(lastPlayerGrid + new Vector3Int(x, y, z));// make the center one green and slightly smaller so it stands out visuallyGizmos.color = isCenter ? Color.green : Color.red;Gizmos.DrawWireCube(gridCenter, Vector3.one * (gridSize * (isCenter ? .95f : 1.0f)));}}}}}

创建一个空物体PrecipitationSystem,并将脚本赋予它,将摄像机赋予该脚本组件。

接下来我们创建要渲染的网格:

 using System.Collections.Generic;using UnityEngine;using UnityEditor;​[ExecuteInEditMode] public class PrecipitationManager : MonoBehaviour {// 65536 (256 x 256) vertices is the max per mesh[Range(2, 256)] public int meshSubdivisions = 200;​GridHandler gridHandler;Mesh meshToDraw;​void OnEnable () {gridHandler = GetComponent<GridHandler>();gridHandler.onPlayerGridChange += OnPlayerGridChange;}​void OnDisable() {gridHandler.onPlayerGridChange -= OnPlayerGridChange;}void OnPlayerGridChange(Vector3Int playerGrid) {}​void Update() {// update the mesh automatically if it doesnt existif (meshToDraw == null)RebuildPrecipitationMesh();}​// the mesh created has a // center at [0,0], // min at [-.5, -.5] // max at [.5, .5]public void RebuildPrecipitationMesh() {Mesh mesh = new Mesh ();List<int> indicies = new List<int>();List<Vector3> vertices = new List<Vector3>();List<Vector3> uvs = new List<Vector3>();// use 0 - 100 range instead of 0 to 1// to avoid precision errors when subdivisions// are to highfloat f = 100f / meshSubdivisions;int i  = 0;for (float x = 0.0f; x <= 100f; x += f) {for (float y = 0.0f; y <= 100f; y += f) {// normalize x and y to a value between 0 and 1float x01 = x / 100.0f;float y01 = y / 100.0f;vertices.Add(new Vector3(x01 - .5f, 0, y01 - .5f));uvs.Add(new Vector3(x01, y01, 0.0f));indicies.Add(i++);}    }mesh.SetVertices(vertices);mesh.SetUVs(0,uvs);mesh.SetIndices(indicies.ToArray(), MeshTopology.Points, 0);// give a large bounds so it's always visible, we'll handle culling manuallymesh.bounds = new Bounds(Vector3.zero, new Vector3(500, 500, 500));// dont save as an assetmesh.hideFlags = HideFlags.HideAndDontSave;meshToDraw = mesh;} }​#if UNITY_EDITOR// create a custom editor with a button// to trigger rebuilding of the render mesh[CustomEditor(typeof(PrecipitationManager))] public class PrecipitationManagerEditor : Editor {​public override void OnInspectorGUI() {base.OnInspectorGUI();if (GUILayout.Button("Rebuild Precipitation Mesh")) {(target as PrecipitationManager).RebuildPrecipitationMesh();// set dirty to make sure the editor updatesEditorUtility.SetDirty(target);}}}

该脚本可以创建网格,在更新时可以使用按钮控制网格的生成。

注意UV是三维,第三个值用于其它元素的存储。

接下来我们进行渲染,雨雪的shader很简单:

 Shader "Snow" {Properties { }SubShader{Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "True" }CULL FRONTBlend SrcAlpha OneMinusSrcAlphaZWrite OffPass {CGPROGRAM#pragma multi_compile_instancing#pragma fragmentoption ARB_precision_hint_fastest#pragma vertex vert#pragma fragment frag#pragma geometry geom#pragma target 4.0#include "Precipitation.cginc"  ENDCG}}}Shader "Rain" {Properties { }SubShader{Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "True" }CULL OFFBlend SrcAlpha OneMinusSrcAlphaZWrite OffPass {CGPROGRAM#pragma multi_compile_instancing        #pragma fragmentoption ARB_precision_hint_fastest#pragma vertex vert#pragma fragment frag#pragma geometry geom#pragma target 4.0#define RAIN#include "Precipitation.cginc"       ENDCG}}}

我们把相关结构体和着色器放于头文件中:

 #include "UnityCG.cginc"​float _GridSize;​struct MeshData {float4 vertex : POSITION;float4 uv : TEXCOORD0;uint instanceID : SV_InstanceID;};​// vertex shader, just pass along the mesh data to the geometry functionMeshData vert(MeshData meshData) {return meshData; }​// structure that goes from the geometry shader to the fragment shaderstruct g2f {UNITY_POSITION(pos);float4 uv : TEXCOORD0; // uv.xy, opacity, color variation amountUNITY_VERTEX_OUTPUT_STEREO};​void AddVertex (inout TriangleStream<g2f> stream, float3 vertex, float2 uv, float colorVariation, float opacity) {      // initialize the struct with information that will go// form the vertex to the fragment shaderg2f OUT;​// unity specificUNITY_INITIALIZE_OUTPUT(g2f, OUT);UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);OUT.pos = UnityObjectToClipPos(vertex);   ​// transfer the uv coordinatesOUT.uv.xy = uv;    ​// we put `opacity` and `colorVariation` in the unused uv vector elements// this limits the amount of attributes we need going between the vertex// and fragment shaders, which is good for performanceOUT.uv.z = opacity;OUT.uv.w = colorVariation;​stream.Append(OUT);}​void CreateQuad (inout TriangleStream<g2f> stream, float3 bottomMiddle, float3 topMiddle, float3 perpDir, float colorVariation, float opacity) {    AddVertex (stream, bottomMiddle - perpDir, float2(0, 0), colorVariation, opacity);AddVertex (stream, bottomMiddle + perpDir, float2(1, 0), colorVariation, opacity);AddVertex (stream, topMiddle - perpDir, float2(0, 1), colorVariation, opacity);AddVertex (stream, topMiddle + perpDir, float2(1, 1), colorVariation, opacity);stream.RestartStrip();}​/*this geom function actually builds the quad from each vertex in themesh. so this function runs once for each "rain drop" or "snowflake"*/#if defined(RAIN)[maxvertexcount(8)] // rain draws 2 quads#else[maxvertexcount(4)] // snow draws one quad that's billboarded towards the camera#endifvoid geom(point MeshData IN[1], inout TriangleStream<g2f> stream) {    ​MeshData meshData = IN[0];​UNITY_SETUP_INSTANCE_ID(meshData);​// the position of the snowflake / raindropfloat3 pos = meshData.vertex.xyz;​// make sure the position is spread out across the entire grid, the original vertex position// is normalized to a plane in the -.5 to .5 rangepos.xz *= _GridSize;​// make sure the position originates from the top of the local gridpos.y += _GridSize * .5;​float opacity = 1.0;​// temporary valuesfloat colorVariation = 0;float2 quadSize = float2(.05, .05);​float3 quadUpDirection = float3(0,0,1);float3 topMiddle = pos + quadUpDirection * quadSize.y;float3 rightDirection = float3(.5 * quadSize.x, 0, 0);​CreateQuad (stream, pos, topMiddle, rightDirection, colorVariation, opacity);}​float4 frag(g2f IN) : SV_Target {float4 color = float4(IN.uv.xy, 0, 1);​return color;}​

接下来修改PrecipitationManager脚本来可视化创建的网格:

 using System.Collections.Generic;using UnityEngine;using UnityEditor;​// NEW =================================================using UnityEngine.Rendering;// NEW =================================================​[ExecuteInEditMode] public class PrecipitationManager : MonoBehaviour {[Range(2, 256)] public int meshSubdivisions = 200;GridHandler gridHandler;Mesh meshToDraw;// NEW =================================================Matrix4x4[] renderMatrices = new Matrix4x4[3 * 3 * 3];​Material rainMaterial, snowMaterial;// automatic material creationstatic Material CreateMaterialIfNull(string shaderName, ref Material reference) {if (reference == null) {reference = new Material(Shader.Find(shaderName));reference.hideFlags = HideFlags.HideAndDontSave;reference.renderQueue = 3000;reference.enableInstancing = true;}return reference;}// NEW =================================================​void OnEnable () {// [ UNCHANGED ]}void OnDisable() {// [ UNCHANGED ]}// NEW =================================================/*set all our render matrices to be positionedin a 3x3x3 grid around the player*/void OnPlayerGridChange(Vector3Int playerGrid) {​// index for each individual matrixint i = 0;​// loop in a 3 x 3 x 3 gridfor (int x = -1; x <= 1; x++) {for (int y = -1; y <= 1; y++) {for (int z = -1; z <= 1; z++) {​Vector3Int neighborOffset = new Vector3Int(x, y, z);// adjust the rendering position matrix, leaving rotation and scale alonerenderMatrices[i++].SetTRS(gridHandler.GetGridCenter(playerGrid + neighborOffset), Quaternion.identity, Vector3.one);}}}}// NEW =================================================​void Update(){if (meshToDraw == null)RebuildPrecipitationMesh();​// NEW =================================================// render the rain and snowRenderEnvironmentParticles(CreateMaterialIfNull("Hidden/Environment/Rain", ref rainMaterial));RenderEnvironmentParticles(CreateMaterialIfNull("Hidden/Environment/Snow", ref snowMaterial));// NEW =================================================}// NEW =================================================void RenderEnvironmentParticles(Material material) {material.SetFloat("_GridSize", gridHandler.gridSize);Graphics.DrawMeshInstanced(meshToDraw, 0, material, renderMatrices, renderMatrices.Length, null, ShadowCastingMode.Off, true, 0, null, LightProbeUsage.Off);}// NEW =================================================​public void RebuildPrecipitationMesh() {// [ UNCHANGED ]} }​#if UNITY_EDITOR//[ CUSTOM EDITOR UNCHANGED ]

我们实现OnPlayerGridChange函数,创建一个简单的函数来从着色器创建材质,并创建一个方法来渲染环境粒子(雨雪),在Update函数中使用Graphics.DrawMeshInstanced来绘制实例。

效果如下:

我们不需要每时每刻渲染所有的四边形,因此我们可以基于粒子的数量、距摄像机的距离和是否在摄像机后来设置剔除。

首先更新脚本,包含一个内置设置类:

 using System.Collections.Generic;using UnityEngine;using UnityEditor;using UnityEngine.Rendering;​[ExecuteInEditMode] public class PrecipitationManager : MonoBehaviour {// NEW =================================================[System.Serializable] public class EnvironmentParticlesSettings{[Range(0, 1)] public float amount = 1.0f;public Color color = Color.white;​[Tooltip("Alpha = variation amount")]public Color colorVariation = Color.white;public float fallSpeed;public Vector2 cameraRange; public Vector2 flutterFrequency;public Vector2 flutterSpeed;public Vector2 flutterMagnitude;public Vector2 sizeRange;public EnvironmentParticlesSettings (Color color, Color colorVariation, float fallSpeed, Vector2 cameraRange, Vector2 flutterFrequency, Vector2 flutterSpeed, Vector2 flutterMagnitude, Vector2 sizeRange) {this.color = color;this.colorVariation = colorVariation;this.fallSpeed = fallSpeed;this.cameraRange = cameraRange;this.flutterFrequency = flutterFrequency;this.flutterSpeed = flutterSpeed;this.flutterMagnitude = flutterMagnitude;this.sizeRange = sizeRange;}}// NEW =================================================[Range(2, 256)] public int meshSubdivisions = 200;​// NEW =================================================// populate the settings with some initial valuespublic EnvironmentParticlesSettings rain = new EnvironmentParticlesSettings(Color.white, Color.white, 3,  // color, colorVariation, fall speednew Vector2(0,15), //camera rangenew Vector2(0.988f, 1.234f), //flutter frequencynew Vector2(.01f, .01f), //flutter speednew Vector2(.35f, .25f), //flutter magnitudenew Vector2(.5f, 1f)//, //size range );public EnvironmentParticlesSettings snow = new EnvironmentParticlesSettings(    Color.white, Color.white, .25f,  // color, colorVariation, fall speednew Vector2(0,10), //camera rangenew Vector2(0.988f, 1.234f), //flutter frequencynew Vector2(1f, .5f), //flutter speednew Vector2(.35f, .25f), //flutter magnitudenew Vector2(.05f, .025f)//, //size range );// NEW =================================================GridHandler gridHandler;Matrix4x4[] renderMatrices = new Matrix4x4[3 * 3 * 3];Mesh meshToDraw;Material rainMaterial, snowMaterial;static Material CreateMaterialIfNull(string shaderName, ref Material reference) {// [ UNCHANGED ]}void OnEnable () {// [ UNCHANGED ]}void OnDisable() {// [ UNCHANGED ]}void OnPlayerGridChange(Vector3Int playerGrid) {// [ UNCHANGED ]}​void Update() {if (meshToDraw == null)RebuildPrecipitationMesh();​// NEW =================================================// render the rain and snowRenderEnvironmentParticles(rain, CreateMaterialIfNull("Hidden/Environment/Rain", ref rainMaterial));RenderEnvironmentParticles(snow, CreateMaterialIfNull("Hidden/Environment/Snow", ref snowMaterial));// NEW =================================================  }​void RenderEnvironmentParticles(EnvironmentParticlesSettings settings, Material material) {// NEW =================================================// if the amount is 0, dont render anythingif (settings.amount <= 0)return;// NEW =================================================​material.SetFloat("_GridSize", gridHandler.gridSize);// NEW =================================================material.SetFloat("_Amount", settings.amount);// send teh other variables which we'll use latermaterial.SetColor("_Color", settings.color);material.SetColor("_ColorVariation", settings.colorVariation);material.SetFloat("_FallSpeed", settings.fallSpeed);material.SetVector("_FlutterFrequency", settings.flutterFrequency);material.SetVector("_FlutterSpeed", settings.flutterSpeed);material.SetVector("_FlutterMagnitude", settings.flutterMagnitude);material.SetVector("_CameraRange", settings.cameraRange);material.SetVector("_SizeRange", settings.sizeRange);// NEW =================================================Graphics.DrawMeshInstanced(meshToDraw, 0, material, renderMatrices, renderMatrices.Length, null, ShadowCastingMode.Off, true, 0, null, LightProbeUsage.Off);}​public void RebuildPrecipitationMesh() {// [ UNCHANGED ]} }​#if UNITY_EDITOR

我们还改变了RenderEnvironmentParticles函数,增加一个设置对象,将数量值送往着色器,以及其它设置值。

我们可能希望基于数量变量来修改整体效果的透明度,但因为所有的雨滴会是渐变的,这样会看起来很奇怪。我们希望可以大雨滂沱,也可以小雨淅淅,为了实现这一点,我们可以在构建网格时,为每个顶点设置一个阈值,如果数量低于阈值,那么该顶点就不会渲染。阈值的计算基于网格中的顶点位置。

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEngine.Rendering;[ExecuteInEditMode] public class PrecipitationManager : MonoBehaviour
{[System.Serializable] public class EnvironmentParticlesSettings {// [ UNCHANGED ]}[Range(2, 256)] public int meshSubdivisions = 200;public EnvironmentParticlesSettings rain = new EnvironmentParticlesSettings(// [ UNCHANGED ] );public EnvironmentParticlesSettings snow = new EnvironmentParticlesSettings( // [ UNCHANGED ] );GridHandler gridHandler;Matrix4x4[] renderMatrices = new Matrix4x4[3 * 3 * 3];Mesh meshToDraw;Material rainMaterial, snowMaterial;static Material CreateMaterialIfNull(string shaderName, ref Material reference) {// [ UNCHANGED ]}void OnEnable () {// [ UNCHANGED ]}void OnDisable() {// [ UNCHANGED ]}void OnPlayerGridChange(Vector3Int playerGrid) {// [ UNCHANGED ]}void Update() {// [ UNCHANGED ]}void RenderEnvironmentParticles(EnvironmentParticlesSettings settings, Material material) {// [ UNCHANGED ]    }public void RebuildPrecipitationMesh() {Mesh mesh = new Mesh ();List<int> indicies = new List<int>();List<Vector3> vertices = new List<Vector3>();List<Vector3> uvs = new List<Vector3>();float f = 100f / meshSubdivisions;int i  = 0;for (float x = 0.0f; x <= 100f; x += f) {for (float y = 0.0f; y <= 100f; y += f) {float x01 = x / 100.0f;float y01 = y / 100.0f;vertices.Add(new Vector3(x01 - .5f, 0, y01 - .5f));// NEW =================================================// calcualte the threshold for this vertex// to recreate the 'thinning out' effectfloat vertexIntensityThreshold = Mathf.Max((float)((x / f) % 4.0f) / 4.0f, (float)((y / f) % 4.0f) / 4.0f);// store the `vertexIntensityThreshold` value as the z component in the uv'suvs.Add(new Vector3(x01, y01, vertexIntensityThreshold));// NEW =================================================indicies.Add(i++);}    }mesh.SetVertices(vertices);mesh.SetUVs(0,uvs);mesh.SetIndices(indicies.ToArray(), MeshTopology.Points, 0);mesh.bounds = new Bounds(Vector3.zero, new Vector3(500, 500, 500));mesh.hideFlags = HideFlags.HideAndDontSave;meshToDraw = mesh;}
}#if UNITY_EDITOR
// [ UNCHANGED ]
#endif

我们可以在着色器代码中的几何体构建方法里利用阈值剔除顶点:

#include "UnityCG.cginc"float _GridSize;// NEW =================================================
float _Amount;
// NEW =================================================struct MeshData {// [ UNCHANGED ]
};
MeshData vert(MeshData meshData) {// [ UNCHANGED ]
}
struct g2f {// [ UNCHANGED ]
};
void AddVertex (inout TriangleStream<g2f> stream, float3 vertex, float2 uv, float colorVariation, float opacity) {      // [ UNCHANGED ]
}
void CreateQuad (inout TriangleStream<g2f> stream, float3 bottomMiddle, float3 topMiddle, float3 perpDir, float colorVariation, float opacity) {    // [ UNCHANGED ]
}#if defined(RAIN)
[maxvertexcount(8)] // rain draws 2 quads
#else
[maxvertexcount(4)] // snow draws one quad that's billboarded towards the camera
#endif
void geom(point MeshData IN[1], inout TriangleStream<g2f> stream)
{    MeshData meshData = IN[0];UNITY_SETUP_INSTANCE_ID(meshData);float3 pos = meshData.vertex.xyz;pos.xz *= _GridSize;// NEW =================================================// mesh vertices cull rendering based on a pattern// and the particles `amount` to simulate 'thinning out'float vertexAmountThreshold = meshData.uv.z;if (vertexAmountThreshold > _Amount)return;// NEW =================================================pos.y += _GridSize * .5;float opacity = 1.0;float colorVariation = 0;float2 quadSize = float2(.05, .05);float3 quadUpDirection = float3(0,0,1);float3 topMiddle = pos + quadUpDirection * quadSize.y;float3 rightDirection = float3(.5 * quadSize.x, 0, 0);CreateQuad (stream, pos, topMiddle, rightDirection, colorVariation, opacity);
}float4 frag(g2f IN) : SV_Target {// [ UNCHANGED ]
}

回到Unity中,如果我们设置数量,就会发现网格的密度发生变化。

为了更加自然,我们可以在阈值上添加噪声,到达阈值时修改每个四边形的透明度进行渐变。

在脚本中,我们添加一个纹理对象,然后通过材质传入着色器:

 using System.Collections.Generic;using UnityEngine;using UnityEditor;using UnityEngine.Rendering;​[ExecuteInEditMode] public class PrecipitationManager : MonoBehaviour {[System.Serializable] public class EnvironmentParticlesSettings {// [ UNCHANGED ]}// NEW =================================================public Texture2D mainTexture;public Texture2D noiseTexture;// NEW =================================================[Range(2, 256)] public int meshSubdivisions = 200;public EnvironmentParticlesSettings rain = new EnvironmentParticlesSettings(// [ UNCHANGED ] );public EnvironmentParticlesSettings snow = new EnvironmentParticlesSettings(    // [ UNCHANGED ] );GridHandler gridHandler;Matrix4x4[] renderMatrices = new Matrix4x4[3 * 3 * 3];Mesh meshToDraw;Material rainMaterial, snowMaterial;static Material CreateMaterialIfNull(string shaderName, ref Material reference) {// [ UNCHANGED ]}void OnEnable () {// [ UNCHANGED ]}void OnDisable() {// [ UNCHANGED ]}void OnPlayerGridChange(Vector3Int playerGrid) {// [ UNCHANGED ]}void Update() {// [ UNCHANGED ]    }​void RenderEnvironmentParticles(EnvironmentParticlesSettings settings, Material material) {​if (settings.amount <= 0)return;​// NEW =================================================material.SetTexture("_MainTex", mainTexture);material.SetTexture("_NoiseTex", noiseTexture);  // NEW =================================================​material.SetFloat("_GridSize", gridHandler.gridSize);material.SetFloat("_Amount", settings.amount);material.SetColor("_Color", settings.color);material.SetColor("_ColorVariation", settings.colorVariation);material.SetFloat("_FallSpeed", settings.fallSpeed);material.SetVector("_FlutterFrequency", settings.flutterFrequency);material.SetVector("_FlutterSpeed", settings.flutterSpeed);material.SetVector("_FlutterMagnitude", settings.flutterMagnitude);material.SetVector("_CameraRange", settings.cameraRange);material.SetVector("_SizeRange", settings.sizeRange);​Graphics.DrawMeshInstanced(meshToDraw, 0, material, renderMatrices, renderMatrices.Length, null, ShadowCastingMode.Off, true, 0, null, LightProbeUsage.Off);}​public void RebuildPrecipitationMesh() {// [ UNCHANGED ]} }​#if UNITY_EDITOR// [ UNCHANGED ]#endif

在shader头文件中,我们使用噪声纹理来变化定点与之,基于该阈值的数量来计算透明度:

#include "UnityCG.cginc"// NEW =================================================
sampler2D _NoiseTex;
// NEW =================================================float _GridSize;
float _Amount;struct MeshData {// [ UNCHANGED ]
};
MeshData vert(MeshData meshData) {// [ UNCHANGED ]
}
struct g2f {// [ UNCHANGED ]
};
void AddVertex (inout TriangleStream<g2f> stream, float3 vertex, float2 uv, float colorVariation, float opacity) {      // [ UNCHANGED ]
}
void CreateQuad (inout TriangleStream<g2f> stream, float3 bottomMiddle, float3 topMiddle, float3 perpDir, float colorVariation, float opacity) {    // [ UNCHANGED ]
}#if defined(RAIN)
[maxvertexcount(8)] // rain draws 2 quads
#else
[maxvertexcount(4)] // snow draws one quad that's billboarded towards the camera
#endif
void geom(point MeshData IN[1], inout TriangleStream<g2f> stream)
{    MeshData meshData = IN[0];UNITY_SETUP_INSTANCE_ID(meshData);float3 pos = meshData.vertex.xyz;pos.xz *= _GridSize;// NEW =================================================// samples 2 seperate noise values so we get some variationfloat2 noise = float2(frac(tex2Dlod(_NoiseTex, float4(meshData.uv.xy    , 0, 0)).r + (pos.x + pos.z)), frac(tex2Dlod(_NoiseTex, float4(meshData.uv.yx * 2, 0, 0)).r + (pos.x * pos.z)));// NEW =================================================float vertexAmountThreshold = meshData.uv.z;// NEW =================================================// add some noise to the vertex thresholdvertexAmountThreshold *= noise.y;// NEW =================================================if (vertexAmountThreshold > _Amount)return;pos.y += _GridSize * .5;float opacity = 1.0;// NEW =================================================// fade out as the amount reaches the limit for this vertex threshold#define VERTEX_THRESHOLD_LEVELS 4float vertexAmountThresholdFade = min((_Amount - vertexAmountThreshold) * VERTEX_THRESHOLD_LEVELS, 1);opacity *= vertexAmountThresholdFade;if (opacity <= 0)return;// NEW =================================================// temporary valuesfloat colorVariation = 0;float2 quadSize = float2(.05, .05);float3 quadUpDirection = float3(0,0,1);float3 topMiddle = pos + quadUpDirection * quadSize.y;float3 rightDirection = float3(.5 * quadSize.x, 0, 0);CreateQuad (stream, pos, topMiddle, rightDirection, colorVariation, opacity);
}float4 frag(g2f IN) : SV_Target {float4 color = float4(IN.uv.xy, 0, 1);// NEW =================================================// apply opacitycolor.a *= IN.uv.z;// NEW =================================================return color;
}

现在的话,雨雪网格的变化就会比较自然。

现在我们基于摄像机的距离和朝向来进行剔除。我们可以在着色器中进行:

 #include "UnityCG.cginc"​sampler2D _NoiseTex;float _GridSize;float _Amount;​// NEW =================================================float2 _CameraRange;// NEW =================================================​struct MeshData {// [ UNCHANGED ]};MeshData vert(MeshData meshData) {// [ UNCHANGED ]}struct g2f {// [ UNCHANGED ]};void AddVertex (inout TriangleStream<g2f> stream, float3 vertex, float2 uv, float colorVariation, float opacity) {      // [ UNCHANGED ]}void CreateQuad (inout TriangleStream<g2f> stream, float3 bottomMiddle, float3 topMiddle, float3 perpDir, float colorVariation, float opacity) {    // [ UNCHANGED ]}​#if defined(RAIN)[maxvertexcount(8)] // rain draws 2 quads#else[maxvertexcount(4)] // snow draws one quad that's billboarded towards the camera#endifvoid geom(point MeshData IN[1], inout TriangleStream<g2f> stream){    MeshData meshData = IN[0];UNITY_SETUP_INSTANCE_ID(meshData);float3 pos = meshData.vertex.xyz;pos.xz *= _GridSize;float2 noise = float2(frac(tex2Dlod(_NoiseTex, float4(meshData.uv.xy    , 0, 0)).r + (pos.x + pos.z)), frac(tex2Dlod(_NoiseTex, float4(meshData.uv.yx * 2, 0, 0)).r + (pos.x * pos.z)));float vertexAmountThreshold = meshData.uv.z;vertexAmountThreshold *= noise.y;​if (vertexAmountThreshold > _Amount)return;​pos.y += _GridSize * .5;​// NEW =================================================// calculate the world space position of the particlesfloat3 worldPos = pos + float3(unity_ObjectToWorld[0].w, unity_ObjectToWorld[1].w, unity_ObjectToWorld[2].w);​// the direction from the position to the camerafloat3 pos2Camera = worldPos - _WorldSpaceCameraPos;float distanceToCamera = length(pos2Camera);// normalize pos2Camera directionpos2Camera /= distanceToCamera;​// calculate the camera's forward directionfloat3 camForward = normalize(mul((float3x3)unity_CameraToWorld, float3(0,0,1)));​// if the angle between the direction to camera and it's forward are too large// then the camera is facign away, so don't drawif (dot(camForward, pos2Camera) < 0.5)return;// NEW =================================================​​float opacity = 1.0;​// NEW =================================================// produces a value between 0 and 1 corresponding to where the distance to camera is within// the Camera Distance range (1 when at or below minimum, 0 when at or above maximum)// this way the particle fades out as it get's too far, and doesnt just pop out of existencefloat camDistanceInterpolation = 1.0 - min(max(distanceToCamera - _CameraRange.x, 0) / (_CameraRange.y - _CameraRange.x), 1);opacity *= camDistanceInterpolation;// NEW =================================================​#define VERTEX_THRESHOLD_LEVELS 4float vertexAmountThresholdFade = min((_Amount - vertexAmountThreshold) * VERTEX_THRESHOLD_LEVELS, 1);opacity *= vertexAmountThresholdFade;if (opacity <= 0)return;​float colorVariation = 0;float2 quadSize = float2(.05, .05);​float3 quadUpDirection = float3(0,0,1);float3 topMiddle = pos + quadUpDirection * quadSize.y;float3 rightDirection = float3(.5 * quadSize.x, 0, 0);CreateQuad (stream, pos, topMiddle, rightDirection, colorVariation, opacity);}​float4 frag(g2f IN) : SV_Target {// [ UNCHANGED ]}

现在移动摄像机的话就会发现网格会随距离渐变。

现在我们来实现雨雪下落效果。我们需要在着色器中为顶点的Y坐标制作动画。在这之前,我们需要确定雨雪会在哪里停止。在其到达网格y轴坐标时执行。在脚本中,我们将_MaxTravelDistance的着色器变量传入网格大小:

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEngine.Rendering;[ExecuteInEditMode] public class PrecipitationManager : MonoBehaviour
{[System.Serializable] public class EnvironmentParticlesSettings {// [ UNCHANGED ]}public Texture2D mainTexture;public Texture2D noiseTexture;[Range(2, 256)] public int meshSubdivisions = 200;public EnvironmentParticlesSettings rain = new EnvironmentParticlesSettings(// [ UNCHANGED ]);public EnvironmentParticlesSettings snow = new EnvironmentParticlesSettings(   // [ UNCHANGED ]);GridHandler gridHandler;Matrix4x4[] renderMatrices = new Matrix4x4[3 * 3 * 3];Mesh meshToDraw;Material rainMaterial, snowMaterial;static Material CreateMaterialIfNull(string shaderName, ref Material reference) {// [ UNCHANGED ]}void OnEnable () {// [ UNCHANGED ]}void OnDisable() {// [ UNCHANGED ]}void OnPlayerGridChange(Vector3Int playerGrid) {// [ UNCHANGED ]}void Update() {if (meshToDraw == null)RebuildPrecipitationMesh();// NEW =================================================float maxTravelDistance = gridHandler.gridSize;// render the rain and snowRenderEnvironmentParticles(rain, CreateMaterialIfNull("Hidden/Environment/Rain", ref rainMaterial), maxTravelDistance);RenderEnvironmentParticles(snow, CreateMaterialIfNull("Hidden/Environment/Snow", ref snowMaterial), maxTravelDistance);// NEW =================================================}void RenderEnvironmentParticles(EnvironmentParticlesSettings settings, Material material, float maxTravelDistance) {if (settings.amount <= 0)return;material.SetTexture("_MainTex", mainTexture);material.SetTexture("_NoiseTex", noiseTexture);  material.SetFloat("_GridSize", gridHandler.gridSize);material.SetFloat("_Amount", settings.amount);material.SetColor("_Color", settings.color);material.SetColor("_ColorVariation", settings.colorVariation);material.SetFloat("_FallSpeed", settings.fallSpeed);material.SetVector("_FlutterFrequency", settings.flutterFrequency);material.SetVector("_FlutterSpeed", settings.flutterSpeed);material.SetVector("_FlutterMagnitude", settings.flutterMagnitude);material.SetVector("_CameraRange", settings.cameraRange);material.SetVector("_SizeRange", settings.sizeRange);// NEW =================================================material.SetFloat("_MaxTravelDistance", maxTravelDistance);// NEW =================================================Graphics.DrawMeshInstanced(meshToDraw, 0, material, renderMatrices, renderMatrices.Length, null, ShadowCastingMode.Off, true, 0, null, LightProbeUsage.Off);}public void RebuildPrecipitationMesh() {// [ UNCHANGED ]}
}#if UNITY_EDITOR
// [ UNCHANGED ]
#endif

着色器中,我们随时间变换顶点的Y轴坐标,确保其会在到达_MaxTravelDistance后会重新循环。循环点和下落速度可以通过噪声值修改,因此雨雪的下落会看起来比较自然:

 #include "UnityCG.cginc"​sampler2D _NoiseTex;float _GridSize;float _Amount;float2 _CameraRange;​// NEW =================================================float _FallSpeed;float _MaxTravelDistance;// NEW =================================================​struct MeshData {// [ UNCHANGED ]};MeshData vert(MeshData meshData) {// [ UNCHANGED ] }struct g2f {// [ UNCHANGED ]};void AddVertex (inout TriangleStream<g2f> stream, float3 vertex, float2 uv, float colorVariation, float opacity) {      // [ UNCHANGED ]}void CreateQuad (inout TriangleStream<g2f> stream, float3 bottomMiddle, float3 topMiddle, float3 perpDir, float colorVariation, float opacity) {    // [ UNCHANGED ]}​#if defined(RAIN)[maxvertexcount(8)] // rain draws 2 quads#else[maxvertexcount(4)] // snow draws one quad that's billboarded towards the camera#endifvoid geom(point MeshData IN[1], inout TriangleStream<g2f> stream){    MeshData meshData = IN[0];UNITY_SETUP_INSTANCE_ID(meshData);float3 pos = meshData.vertex.xyz;pos.xz *= _GridSize;float2 noise = float2(frac(tex2Dlod(_NoiseTex, float4(meshData.uv.xy    , 0, 0)).r + (pos.x + pos.z)), frac(tex2Dlod(_NoiseTex, float4(meshData.uv.yx * 2, 0, 0)).r + (pos.x * pos.z)));float vertexAmountThreshold = meshData.uv.z;vertexAmountThreshold *= noise.y;if (vertexAmountThreshold > _Amount)return;// NEW =================================================// "falling down" movement// add 10000 to the time variable so it starts out `prebaked`// modify the movespeed by a random factor as wellpos.y -= (_Time.y + 10000) * (_FallSpeed + (_FallSpeed * noise.y));​// make sure the particles "loops" around back to the top once it reaches the// max travel distance (+ some noise for randomness)pos.y = fmod(pos.y, -_MaxTravelDistance) + noise.x;// NEW =================================================pos.y += _GridSize * .5;float3 worldPos = pos + float3(unity_ObjectToWorld[0].w, unity_ObjectToWorld[1].w, unity_ObjectToWorld[2].w);​float3 pos2Camera = worldPos - _WorldSpaceCameraPos;float distanceToCamera = length(pos2Camera);pos2Camera /= distanceToCamera;float3 camForward = normalize(mul((float3x3)unity_CameraToWorld, float3(0,0,1)));if (dot(camForward, pos2Camera) < 0.5)return;​float opacity = 1.0;float camDistanceInterpolation = 1.0 - min(max(distanceToCamera - _CameraRange.x, 0) / (_CameraRange.y - _CameraRange.x), 1);opacity *= camDistanceInterpolation;#define VERTEX_THRESHOLD_LEVELS 4float vertexAmountThresholdFade = min((_Amount - vertexAmountThreshold) * VERTEX_THRESHOLD_LEVELS, 1);opacity *= vertexAmountThresholdFade;if (opacity <= 0)return;​float colorVariation = 0;float2 quadSize = float2(.05, .05);​// change the quadUpDirection so the quad is upright for nowfloat3 quadUpDirection = float3(0, 1, 0);float3 topMiddle = pos + quadUpDirection * quadSize.y;float3 rightDirection = float3(.5 * quadSize.x, 0, 0);CreateQuad (stream, pos, topMiddle, rightDirection, colorVariation, opacity);}​float4 frag(g2f IN) : SV_Target {// [ UNCHANGED ]}

对于雪花,我们需要在X、Z轴进行偏移来体现其的轻柔。我们在几何着色器中使用_Flutter变量来完成:

#include "UnityCG.cginc"sampler2D _NoiseTex;
float _GridSize;
float _Amount;
float2 _CameraRange;
float _FallSpeed;
float _MaxTravelDistance;// NEW =================================================
float2 _FlutterFrequency;
float2 _FlutterSpeed;
float2 _FlutterMagnitude;
// NEW =================================================struct MeshData {// [ UNCHANGED ]
};
MeshData vert(MeshData meshData) {// [ UNCHANGED ]
}
struct g2f {// [ UNCHANGED ]
};
void AddVertex (inout TriangleStream<g2f> stream, float3 vertex, float2 uv, float colorVariation, float opacity) {      // [ UNCHANGED ]
}
void CreateQuad (inout TriangleStream<g2f> stream, float3 bottomMiddle, float3 topMiddle, float3 perpDir, float colorVariation, float opacity) {    // [ UNCHANGED ]
}#if defined(RAIN)
[maxvertexcount(8)] // rain draws 2 quads
#else
[maxvertexcount(4)] // snow draws one quad that's billboarded towards the camera
#endif
void geom(point MeshData IN[1], inout TriangleStream<g2f> stream)
{    MeshData meshData = IN[0];UNITY_SETUP_INSTANCE_ID(meshData);float3 pos = meshData.vertex.xyz;pos.xz *= _GridSize;float2 noise = float2(frac(tex2Dlod(_NoiseTex, float4(meshData.uv.xy    , 0, 0)).r + (pos.x + pos.z)), frac(tex2Dlod(_NoiseTex, float4(meshData.uv.yx * 2, 0, 0)).r + (pos.x * pos.z)));float vertexAmountThreshold = meshData.uv.z;vertexAmountThreshold *= noise.y;if (vertexAmountThreshold > _Amount)return;pos.y -= (_Time.y + 10000) * (_FallSpeed + (_FallSpeed * noise.y));// NEW =================================================// Add random noise while travelling based on time, some randomness, and "distance travelled"float2 inside = pos.y * noise.yx * _FlutterFrequency + ((_FlutterSpeed + (_FlutterSpeed * noise)) * _Time.y);float2 flutter = float2(sin(inside.x), cos(inside.y)) * _FlutterMagnitude;pos.xz += flutter;// NEW =================================================pos.y = fmod(pos.y, -_MaxTravelDistance) + noise.x;pos.y += _GridSize * .5;float3 worldPos = pos + float3(unity_ObjectToWorld[0].w, unity_ObjectToWorld[1].w, unity_ObjectToWorld[2].w);float3 pos2Camera = worldPos - _WorldSpaceCameraPos;float distanceToCamera = length(pos2Camera);pos2Camera /= distanceToCamera;float3 camForward = normalize(mul((float3x3)unity_CameraToWorld, float3(0,0,1)));if (dot(camForward, pos2Camera) < 0.5)return;float opacity = 1.0;float camDistanceInterpolation = 1.0 - min(max(distanceToCamera - _CameraRange.x, 0) / (_CameraRange.y - _CameraRange.x), 1);opacity *= camDistanceInterpolation;#define VERTEX_THRESHOLD_LEVELS 4float vertexAmountThresholdFade = min((_Amount - vertexAmountThreshold) * VERTEX_THRESHOLD_LEVELS, 1);opacity *= vertexAmountThresholdFade;if (opacity <= 0)return;float colorVariation = 0;float2 quadSize = float2(.05, .05);float3 quadUpDirection = float3(0, 1, 0);float3 topMiddle = pos + quadUpDirection * quadSize.y;float3 rightDirection = float3(.5 * quadSize.x, 0, 0);CreateQuad (stream, pos, topMiddle, rightDirection, colorVariation, opacity);
}float4 frag(g2f IN) : SV_Target {// [ UNCHANGED ]
}

之后我们应用纹理,并修改一些细节。

unity天气系统_天气系统(一)相关推荐

  1. Unity插件之天气系统UniStorm

    一.前言 1.官方简介描述: 现在支持URP.HDRP支持目前正在开发中. UniStorm是AAA动态天空.天气.云阴影和程序体积云的最终解决方案.UniStorm有超过100个可定制的选项,以帮助 ...

  2. 【小沐学Unity3d】Unity插件之天气系统UniStorm

    文章目录 1.简介 1.1 描述 1.2 兼容性 1.3 价格 1.4 特点 1.5 示例 3.安装 3.1 新建Unity项目 3.2 安装插件UniStorm 3.3 介绍UniStorm工具栏 ...

  3. 2d shader unity 阴影_【Unity Shader】平面阴影(Planar Shadow)

    来介绍一种适用于移动平台的高性能实时阴影解决方案--平面阴影(Planar Shadow). 由于Unity内置的实时阴影实现方式是屏幕空间阴影贴图(Screen Space Shadow Map)非 ...

  4. unity 开枪_史诗般的游戏可能在两脚都开枪

    unity 开枪 You've surely heard about the gaming industry's biggest story by now: Epic Games is suing A ...

  5. unity镜像_通过镜像学习Unity Multiplayer Basics

    unity镜像 Unity is one of the most well-known and established engines for game development, and Mirror ...

  6. unity 灯笼_如何创建将自己拼成文字的漂亮灯笼

    unity 灯笼 In this tutorial, we will go through how to create a group of festival lanterns that arrang ...

  7. tcp unity 图片_用 Unity 做个游戏(七) - TCP Socket 客户端

    前言 这真的是最后一篇有关基础框架的文章了! 写到这里已经第七篇了orz之前的其实还是挺枯燥的,都是些基础方面的东西,并看不到什么有趣的内容 可能是我把事情想的太复杂了吧,所有东西都想做到能力范围内的 ...

  8. unity 克隆_使用Unity开发Portal游戏克隆

    unity 克隆 Learn game development principles by coding a Portal-like game using Unity and C#. The prin ...

  9. audio unity 加速_浅谈Unity中Android、iOS音频延迟

    在Unity上面做音游,当在移动端实机运行起来,会发现,音频的发出会有一定的延迟,无论是长音效还是短音效,Unity内置的Audio内部使用的是FMOD,有以下手段改善 通过设置稍微改善其延迟的问题 ...

  10. max unity 方向_在2D游戏中实现方向光照

    老实讲,这个需求是老板提的. 游戏嘛,很多东西都可以做,但是做不做往往不是做的人可以决定的.这个效果虽然没见过有游戏实现过(一般实现的都是无方向的边缘光),但是在一些2D动画里是有的--比如一款叫轮舞 ...

最新文章

  1. 损失函数约束类间不一致性
  2. RPM包搜索下载网站
  3. VB 宏+mysql解决EXCEL表格实现自动化处理
  4. codeforce训练2总结
  5. Singleton Pattern(单例模式)
  6. IAR FOR ARM 各版本,需要的大家可以收藏了
  7. c++11多线程之packaged_task<>介绍与实例
  8. mongodb如何写入图片_CTO之瞳-数据库-MongoDB
  9. 小程序 遮照 mask 背景透明,里面内容不透明
  10. springside 4 web-init 启动
  11. 假设前置数据法|全网唯一
  12. 百度正用谷歌AlphaGo,解决一个比围棋更难的问题 | 300块GPU在燃烧
  13. 开源linux 二进制工具,又一款开源好物:逐字节分析的二进制数据分析工具bitinsight...
  14. 霍邱一中2021高考成绩查询入口,2021六安高考成绩查询系统
  15. c语言分数等级switch,用switch输出分数等级
  16. PLSQL Developer 12 破解注册码
  17. 全国2013年最新电子地图矢量数据超图格SGD MAPINFO GST SMW SHP格式等
  18. 小米商城静态页面制做
  19. 将心比心,你对别人好,别人才会对你好,是真的吗?
  20. 基于STM32根据DL/T 645-2007通讯协议利用RS485进行抄表并将电压等数据利用HC-05蓝牙实时传输至上位机显示

热门文章

  1. 连连看.NET v1.2版下载
  2. java 获取当前文件的路径+文件全名
  3. EnglishWords——星期与月份
  4. 数据结构 http://www.cnblogs.com/sun-haiyu/p/7704654.html
  5. 拥2180亿美元收入 苹果成全球最大IT企业
  6. FUCKED-BUG之python子进程的键盘中断
  7. 总线、设备和驱动的关系
  8. IO负载高的来源定位
  9. linux内核与用户之间的通信方式——虚拟文件系统、ioctl以及netlink .
  10. OpenCV 利用MFC的Picture控件显示和处理图像