概述
本文使用C#生成了水的Mesh,通过Shader的时间宏控制Mesh的顶点移动,利用Geometry着色器重新计算法线。
水与物体接触的边缘是用深度图与当前深度的差值来实现的。
C#代码
LowpolywaterEditor.cs
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(LowpolyWater))]
public class LowpolyWaterEditor : Editor
{
private LowpolyWater _target;
private void Awake()
{
_target = target as LowpolyWater;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (GUILayout.Button("Create Water Plane"))
{
_target.CreateWaterPlane();
}
}
}
Lowpolywater.cs
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
[ExecuteInEditMode]
public class LowpolyWater : MonoBehaviour
{
[SerializeField]
private float _unitSize=1.0f;
[SerializeField]
private Vector2Int _size=new Vector2Int(2,2);
[SerializeField]
private Material _waterMaterial;
[SerializeField]
private GameObject _obj;
private MeshRenderer _meshRenderer;
private MeshFilter _meshFilter;
private Mesh _mesh;
private void Awake()
{
_meshFilter = _obj.GetComponent<MeshFilter>();
//if (_meshFilter == null)
// _meshFilter = _obj.AddComponent<MeshFilter>();
_meshRenderer = _obj.GetComponent<MeshRenderer>();
//if (_meshRenderer == null)
// _meshRenderer = _obj.AddComponent<MeshRenderer>();
_mesh = new Mesh();
}
public void CreateWaterPlane()
{
Vector3 offset = new Vector3(_size.x / 2 * _unitSize, 0, _size.y / 2 * _unitSize);
//创建mesh
_mesh.Clear();
_meshFilter.mesh = _mesh;
_meshRenderer.sharedMaterial = _waterMaterial;
//创建顶点和UV
Vector3[] vertices = new Vector3[_size.x*_size.y];
Vector2[] uv = new Vector2[_size.x * _size.y];
//把uv缩放到0 - 1
Vector2 uvScale = new Vector2(1.0f/(_size.x - 1), 1.0f / (_size.y - 1));
for(int x = 0; x < _size.x; x++)
{
for(int y = 0; y < _size.y; y++)
{
vertices[x * _size.y + y] = new Vector3(x, 0, y)*_unitSize- offset;
uv[x * _size.y + y] = Vector2.Scale(new Vector2(x, y), uvScale);
}
}
_mesh.vertices = vertices;
_mesh.uv = uv;
//三角形index
int[] triangles = new int[(_size.x-1)*(_size.y-1)*6];
int index = 0;
Debug.Log(triangles.Length + " " + (_size.x - 1) * (_size.y - 1) * 6);
for (int x = 0; x < _size.x-1; x++)
{
for (int y = 0; y < _size.y-1; y++)
{
Debug.Log(x+" "+ y+" "+index);
triangles[index++] = (x * _size.y) + y + 1;
triangles[index++] = ((x + 1) * _size.y) + y;
triangles[index++] = (x * _size.y) + y;
triangles[index++] = (x * _size.y) + y + 1;
triangles[index++] = ((x + 1) * _size.y) + y + 1;
triangles[index++] = ((x + 1) * _size.y) + y;
}
}
_mesh.triangles = triangles; //三角面
_mesh.RecalculateNormals(); //计算法线
}
}
##Shader
Lowpolywater.shader
Shader "Custom/LowPolyWater" {
Properties {
_BaseColor ("Base color", COLOR) = ( .54, .95, .99, 0.5)
_SpecColor ("Specular Material Color", Color) = (1,1,1,1)
_Shininess ("Shininess", Float) = 10
_depthOffset("Depth Offset", float) = 1
//[MaterialToggle] _isInnerAlphaBlendOrColor("Fade inner to color or alpha?", Float) = 0
_WavePos("Wave Position",Vector)=(0,0,0,0)
_WaveHeight("Wave Height",Float) = 1
_WaveFrequency("Wave Frequency",Float) = 1
}
CGINCLUDE
#include "UnityCG.cginc"
#include "UnityLightingCommon.cginc" // for _LightColor0
sampler2D_float _CameraDepthTexture;
uniform float4 _BaseColor;
uniform float _Shininess;
fixed _depthOffset;
float4 _WavePos;
float _WaveHeight;
float _WaveFrequency;
struct v2g
{
float4 pos : SV_POSITION;
float3 vertex:TEXCOORD0;
float4 scrPos:TEXCOORD1;
float depth : SV_Depth;
};
struct g2f
{
float4 pos : SV_POSITION;
fixed3 worldNormal : NORMAL;
float3 vertex:TEXCOORD0;
float4 scrPos:TEXCOORD1;
//定义fog贴图坐标
UNITY_FOG_COORDS(2)
float depth:SV_Depth;
};
v2g vert(appdata_full v)
{
v2g o;
//初始化v2g内的数据
UNITY_INITIALIZE_OUTPUT(v2g, o);
//世界坐标和相机的距离向量
//o.viewInterpolator.xyz = worldSpaceVertex - _WorldSpaceCameraPos;
//o.worldPos = mul(unity_ObjectToWorld, (v.vertex)).xyz;
//模型坐标转化为世界坐标
float3 worldSpaceVertex = mul(unity_ObjectToWorld,(v.vertex)).xyz;
//通过世界坐标计算波动发起点的距离
float dis = distance(worldSpaceVertex, _WavePos);
//计算顶点y坐标
v.vertex.y = _WaveHeight * sin((_Time.y* _WaveFrequency + dis)*6.28f);
//顶点坐标转换到裁剪空间
o.pos = UnityObjectToClipPos(v.vertex);
o.vertex = v.vertex;
//ComputeScreenPos COMPUTE_EYEDEPTH 必须在vert内调用
o.scrPos = ComputeScreenPos(o.pos);
//计算当前深度(注意因为ZWrite关闭所以该深度无法在深度图读取)
COMPUTE_EYEDEPTH(o.depth);
//UnityWorldSpaceViewDir 得出从顶点到摄像机的向量 等价_WorldSpaceCameraPos - worldPos
// float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
return o;
}
[maxvertexcount(3)]
void geom(triangle v2g input[3], inout TriangleStream<g2f> outStream)
{
fixed3 normal=normalize(UnityObjectToWorldNormal(cross((input[1].vertex - input[0].vertex),(input[2].vertex-input[0].vertex))));
for (int i = 0; i<3; i++)
{
g2f o = (g2f)0;
o.pos = input[i].pos;
o.vertex = input[i].vertex;
o.depth = input[i].depth;
o.worldNormal = normal;
o.scrPos = input[i].scrPos;
//从顶点数据中输出雾效数据
UNITY_TRANSFER_FOG(o, o.pos);
//-----将一个顶点添加到输出流列表
outStream.Append(o);
}
//outStream.RestartStrip();
}
half4 calculateBaseColor(g2f input)
{
float3 worldPos= mul(unity_ObjectToWorld, (input.vertex)).xyz;
float3 viewDirection = normalize(_WorldSpaceCameraPos - worldPos);
float3 lightDirection;
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(_WorldSpaceLightPos0.xyz);
}
else // point or spot light
{
float3 vertexToLightSource =
_WorldSpaceLightPos0.xyz - worldPos;
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
float3 ambientLighting =
UNITY_LIGHTMODEL_AMBIENT.rgb * _BaseColor.rgb;
float3 diffuseReflection =
attenuation * _LightColor0.rgb * _BaseColor.rgb
* max(0.0, dot(input.worldNormal, lightDirection));
float3 specularReflection;
if (dot(input.worldNormal, lightDirection) < 0.0)
// light source on the wrong side?
{
specularReflection = float3(0.0, 0.0, 0.0);
// no specular reflection
}
else
{
specularReflection = attenuation * _LightColor0.rgb * _SpecColor.rgb
* pow(max(0.0, dot(reflect(-lightDirection, input.worldNormal), viewDirection)), _Shininess);
}
return half4(ambientLighting + diffuseReflection + specularReflection, _BaseColor.a);
}
half4 frag( g2f i ) : SV_Target
{
half4 baseColor = calculateBaseColor(i);
//float4 screenPos = ComputeScreenPos(i.pos);
//求深度
half depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.scrPos));
//线性化深度
depth = LinearEyeDepth(depth);
float diffValue =1-saturate(depth - i.depth);
//应用fog
UNITY_APPLY_FOG(i.fogCoord, baseColor);
//return float4(i.deepth -_deepthOffset, i.deepth -_deepthOffset, i.deepth -_deepthOffset, 1);
//return float4(depth - _deepthOffset, depth - _deepthOffset, depth - _deepthOffset, 1);
//return float4(diffValue - _deepthOffset, diffValue - _deepthOffset, diffValue - _deepthOffset, 1);
//return calculateBaseColor(i);
return baseColor+ diffValue *_depthOffset;
}
ENDCG
Subshader
{
Tags {"RenderType"="Transparent" "Queue"="Transparent"}
Lod 500
ColorMask RGB
//GrabPass { "_RefractionTex" }
Pass {
Blend SrcAlpha OneMinusSrcAlpha
ZTest LEqual
ZWrite Off
Cull Off
CGPROGRAM
#pragma target 3.0
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#pragma multi_compile_fog
//#pragma multi_compile WATER_EDGEBLEND_ON WATER_EDGEBLEND_OFF
ENDCG
}
}
Fallback "Transparent/Diffuse"
}