Wednesday, June 20, 2012

Water Shader {

Here's a quick demo of a mobile water shader I wrote for an Ipad game (I'll post it when it is released). I used Unity3D with ShaderLab and CG for the shader writing and C# for the scripts. I will try to package it and upload it to the Unity Asset Store soon so others can use it.


Features:


  • Efficient shader optimized for mobile devices
  • Realtime refraction of anything on opposite side of water plane
  • Sin blend between 2 normal maps for smooth normal animation
  • UV Panning normal maps
  • Reflection Cubemap
  • Renders in Opaque queue with no expensive transparency cost
  • Separate settings for fog over and under water
  • Separate settings for far clip planes over and under water (quality)
  • Controls for Transparency, RefractionFactor, ReflectionFactor, RenderTextureSize, Tiling, FogSettings, etc



Here's some more screenshots and a video: (Sorry for bad quality)










And here's the relevent vertex and surface shaders (in CG):



void vert (inout appdata_full v, out Input o) 
{
 fixed4 epos = mul(UNITY_MATRIX_MV, v.vertex );
 
 o.eyeZ = -epos.z;
 
 o.ref = mul( _TextureMatrix, epos );
 
 o.viewDir = ObjSpaceViewDir( v.vertex );
}


void surf (Input IN, inout SurfaceOutput o) 
{
 //Normal map with uv pan based on time:
 fixed4 speed = _Speed * _Time;
 fixed2 UV_Pan0 = IN.uv_Normal + speed.xy;
 fixed2 UV_Pan1 = IN.uv_Normal + speed.yx;
 fixed4 normA =
fixed4(UnpackNormal( tex2D(_Normal,UV_Pan0.xy)).xyz, 1.0 );
fixed4 normB = fixed4(UnpackNormal( tex2D(_Normal2,UV_Pan1.xy)).xyz, 1.0);
 
 fixed4 norm =
lerp( normA, normB, (sin( _Time.x * _SinNormalSpeed ) * 0.8 + 1) * 0.5 );
 
 norm = normalize( norm );
 
 //Refract uvs of refractionTexture in xy
 fixed4 uv2 = IN.ref;
 fixed2 refractUVs =_RefractionFactor * norm.xy;
 uv2.xy += refractUVs;
 
 //calculate fresnel factor for hiding refraction at sheer angle:
 fixed fresnel = saturate( dot( normalize( IN.viewDir ), norm.xzy ));
 fresnel = 1.0 - pow( fresnel, _FresnelFactor ) + 0.5;
 
 //lookup projected refractionTexture:
 half4 refr =
_Color * tex2Dproj( _RefractionTexture, UNITY_PROJ_COORD(uv2) );
 
//lookup reflected cubemap texture:
 half4 refl =
_ReflectionFactor * texCUBE( _ReflectionCube, WorldReflectionVector( IN, norm));
 
//linear fog falloff
 half fogAmount = (IN.eyeZ - _FogStart) / _FogEnd;

 //lerp between reflection and refraction, using fresnel:
 half4 color = lerp( refl, refr, saturate( _Transparency * fresnel) );
 
//lerp btwn color and fog based on fogamount
 color =
lerp( color, saturate(_SurfaceFogColor + norm.x), saturate(fogAmount) );
 
//unlit so assign to emission
 o.Emission = color;
 o.Albedo = 0.0;
 o.Gloss = 0.0;
 o.Alpha = 0.0;
 o.Specular = 0.0;
}

-K

4 comments:

  1. hi Katlan,
    I tried to find this shader as you said you would upload it on Asset Store, but it is not there yet. I am working on a fish tank based project and I am dying to get the screen-space reflection-refraction working on mobile platforms. I am no CG coder, but I think this is what I exactly needed. Can you please share this with me or at least tell me how I would go about it? I also bought hard surface shaders standard, which has the screen-space reflection-refraction shader, but it is unbelievably slow even on iPhone5. Please help.
    Regards,
    Rohan

    ReplyDelete
    Replies
    1. Hey Rohan, I'm just back to blogging, so I apologize for not responding earlier. I have not posted to the Asset Store (turns out, it's a lot of work!), but will consider posting the files here. I prefer not to, as I like to encourage people to do some work on their own. In this case, all the relevant code is above, so with a bit of work, you can recreate the files yourself.

      Delete
  2. Do you have any ideas on how to prevent refraction of objects above the water ? I am not using reflection or fog.

    ReplyDelete
    Replies
    1. If you set _RefractionFactor to 1 it should turn off Refraction completely. If you want to do it on a per-object basis, it's a bit more complex and you'd lose some performance. You'd have to make the shader a transparent shader, render only what you want refracted into _RefractionTexture, then render the water plane, with all refracted objects off, and all unrefracted objects on.

      Unfortunately, shaders are somewhat single-use. They excel when they're efficient. But with efficiency, you lose flexibility. So my shader would need some tweaking to get per-layer/object refraction.

      Delete

Please be courteous and stay on topic. I will be happy to answer any question I can. And I am always open to discussion and respectful criticism. Thanks for commenting!