Edit 2: Check out my newest version: Unipolywater shader
Edit: I’ve added a link to a downloadable version of the shader! Copying the code from this post does crazy stuff to the symbols..
The side tracking specialist strikes again.
I’ve been working on another project lately and ended up in need of low poly flat shaded animated water.
I tried editing the mesh at runtime by script. Using the method mentioned by joni.giuro here: http://forum.unity3d.com/threads/lowpolywaterhelp.276685/
But when using the standard plane with this script, basically 200 triangles and when you’ve separeted all vertices (600), FPS drops down from 1500 without script to 190 with script applied. If you add more planes or models with more triangles you quickly get to a point where you get 1 FPS. So I needed something else.
That’s when I figured I’d try my luck with shaders.
Tried googling for solutions, but nothing was to be found so I needed to build my own. With no experience in shader programming this proved to be harder than I expected. But fast forward through some horrible looking results to today. Now I’ve gotten to a point where I can take any plane (not having to worry about splitting verts) applying my shader and getting this:
Almost no hit on FPS with fully animated low poly flat shaded water with specular color. And with this solution:
I can add looots of planes with this shader with almost no hit on FPS. Here I’ve added got 30 standard Unity planes with 200 tris each, all animating off into the distance.
Here’s a video of it in motion:
So how is this done, you say?
Code below.
It’s not perfect but it works! F.ex. I’ve left the UV coords in there but don’t need them for this. And I’m not entirely happy with the waves just yet.. But here you go.
1 
Shader "Trolltunga/LowPolyWaves 2.0"
{
Properties
{
_Color("Color", Color) = (1,0,0,1)
_SpecColor("Specular Material Color", Color) = (1,1,1,1)
_Shininess("Shininess", Float) = 1.0
_WaveLength("Wave length", Float) = 0.5
_WaveHeight("Wave height", Float) = 0.5
_WaveSpeed("Wave speed", Float) = 1.0
_RandomHeight("Random height", Float) = 0.5
_RandomSpeed("Random Speed", Float) = 0.5
}
SubShader
{
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
float rand(float3 co)
{
return frac(sin(dot(co.xyz ,float3(12.9898,78.233,45.5432))) * 43758.5453);
}
float rand2(float3 co)
{
return frac(sin(dot(co.xyz ,float3(19.9128,75.2,34.5122))) * 12765.5213);
}
float _WaveLength;
float _WaveHeight;
float _WaveSpeed;
float _RandomHeight;
float _RandomSpeed;
uniform float4 _LightColor0;
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
struct v2g
{
float4 pos : SV_POSITION;
float3 norm : NORMAL;
float2 uv : TEXCOORD0;
};
struct g2f
{
float4 pos : SV_POSITION;
float3 norm : NORMAL;
float2 uv : TEXCOORD0;
float3 diffuseColor : TEXCOORD1;
float3 specularColor : TEXCOORD2;
};
v2g vert(appdata_full v)
{
float3 v0 = mul(_Object2World, v.vertex).xyz;
float phase0 = (_WaveHeight)* sin((_Time[1] * _WaveSpeed) + (v0.x * _WaveLength) + (v0.z * _WaveLength) + rand2(v0.xzz));
float phase0_1 = (_RandomHeight)*sin(cos(rand(v0.xzz) * _RandomHeight * cos(_Time[1] * _RandomSpeed * sin(rand(v0.xxz)))));
v0.y += phase0 + phase0_1;
v.vertex.xyz = mul((float3x3)_World2Object, v0);
v2g OUT;
OUT.pos = v.vertex;
OUT.norm = v.normal;
OUT.uv = v.texcoord;
return OUT;
}
[maxvertexcount(3)]
void geom(triangle v2g IN[3], inout TriangleStream triStream)
{
float3 v0 = IN[0].pos.xyz;
float3 v1 = IN[1].pos.xyz;
float3 v2 = IN[2].pos.xyz;
float3 centerPos = (v0 + v1 + v2) / 3.0;
float3 vn = normalize(cross(v1  v0, v2  v0));
float4x4 modelMatrix = _Object2World;
float4x4 modelMatrixInverse = _World2Object;
float3 normalDirection = normalize(
mul(float4(vn, 0.0), modelMatrixInverse).xyz);
float3 viewDirection = normalize(_WorldSpaceCameraPos
 mul(modelMatrix, float4(centerPos, 0.0)).xyz);
float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
float attenuation = 1.0;
float3 ambientLighting =
UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;
float3 diffuseReflection =
attenuation * _LightColor0.rgb * _Color.rgb
* max(0.0, dot(normalDirection, lightDirection));
float3 specularReflection;
if (dot(normalDirection, lightDirection) < 0.0)
// light source on the wrong side?
{
specularReflection = float3(0.0, 0.0, 0.0);
// no specular reflection
}
else // light source on the right side
{
specularReflection = attenuation * _LightColor0.rgb
* _SpecColor.rgb * pow(max(0.0, dot(
reflect(lightDirection, normalDirection),
viewDirection)), _Shininess);
}
g2f OUT;
OUT.pos = mul(UNITY_MATRIX_MVP, IN[0].pos);
OUT.norm = vn;
OUT.uv = IN[0].uv;
OUT.diffuseColor = ambientLighting + diffuseReflection;
OUT.specularColor = specularReflection;
triStream.Append(OUT);
OUT.pos = mul(UNITY_MATRIX_MVP, IN[1].pos);
OUT.norm = vn;
OUT.uv = IN[1].uv;
OUT.diffuseColor = ambientLighting + diffuseReflection;
OUT.specularColor = specularReflection;
triStream.Append(OUT);
OUT.pos = mul(UNITY_MATRIX_MVP, IN[2].pos);
OUT.norm = vn;
OUT.uv = IN[2].uv;
OUT.diffuseColor = ambientLighting + diffuseReflection;
OUT.specularColor = specularReflection;
triStream.Append(OUT);
}
half4 frag(g2f IN) : COLOR
{
return float4(IN.specularColor +
IN.diffuseColor, 1.0);
}
ENDCG
}
}
}
