July 19, 2017

Unity Shader 2D 水面反射

更新一篇小文,在研究Unity Shader的时候,看到一段youtube视频https://www.youtube.com/watch?v=iBWwNHEHo3I

觉得这个做的非常好,于是想仿照做一个简单的。

于是就有了下面这个

2D水面反射

研究了过后发现,其实这是个挺简单的shader效果。

原理与实现

原理就是在场景中放置一个摄像机对准湖面上面的部分,然后通过脚本把摄像机捕捉到的传递到shader中去,再加上一个噪声材质和时间的混合就能够表现出水面流动的效果。

首先在场景中加入一些GameObject,如下图。

场景的Hierarchy

Main Camera就是主相机,LandscapePhoto就是岸上的背景图,Water水面则是一个带SpriteRenderer的矩形

然后编写一个脚本,把摄像机的大小,比例,捕获图像渲染的目标材质和shader使用的材质都设置好,挂在Water上面。

[ExecuteInEditMode]
public class WaterReflection : MonoBehaviour {
  public Renderer water;
  public Camera camera;

  private RenderTexture offscreenTexture;

  void Start() {
    offscreenTexture = new RenderTexture (1024, 1024, 16);
    Vector3 size = water.bounds.size;
    float aspect = size.x / size.y;
    camera.aspect = aspect;
    camera.orthographicSize = size.y / 2f;
    camera.targetTexture = offscreenTexture;
  }
}

然后是shader部分,它需要完成下面的工作。

  1. 对噪声材质的采样
  2. 翻转当前点的y轴,加入噪声
  3. 对摄像机传递过来的材质采样
  4. 返回当前节点像素值

shader的输入属性设置如下

_TintColor ("Tint Color", Color) = (1,1,1,1)
_NoiseTex ("Noise Texture", 2D) = "bump" {}
_ReflectionTex ("Reflection Texture", 2D) = "white" {}
_Magnitude ("Magnitude", Range(0, 1)) = 0.02
_Speed ("Speed", Range(0, 10)) = 0.15

然后是顶点和片元着色器的输入结构体定义,通过TEXCOORDn来定义材质。

struct appdata
{
  float4 vertex : POSITION;
  float2 uvnoise : TEXCOORD0;
  float2 uvrefl : TEXCOORD1;
};

struct v2f
{
  float2 uvnoise : TEXCOORD0;
  float2 uvrefl : TEXCOORD1;
  float4 vertex : SV_POSITION;
};

顶点着色器非常简单,主要都是完成顶点的变换

v2f vert (appdata v)
{
  v2f o;
  o.vertex = UnityObjectToClipPos(v.vertex);
  o.uvnoise = TRANSFORM_TEX(v.uvnoise, _NoiseTex);
  o.uvrefl = TRANSFORM_TEX(v.uvrefl, _ReflectionTex);
  return o;
}

在片元着色器中,首先要对噪声材质进行采样,如果直接使用i.uvnoise进行采样,得到的噪声将是固定点的噪声,水面将会静止不动。要让它动起来可以在x分量上加上当前时间速度乘积,相当于噪声点在噪声材质上水平移动。(这里有另外一种实现方法,就是在shader中直接使用i.uvnoise进行采样,在脚本的Update中不断增大噪声材质offset的x分量)然后就可以用UnpackNormal方法提取出噪声。这里要注意在使用的噪声材质要把材质类型选择成Normal map。

fixed4 frag (v2f i) : SV_Target
{
  fixed4 noise = tex2D(_NoiseTex, i.uvnoise + fixed2(_Speed * _Time.y, 0));
  fixed2 distortion = UnpackNormal(noise).rg;

下面则是翻转摄像机材质加上噪声分量,最后对摄像机材质进行采样,返回当前片元的颜色。

  // reverse uvrefl
  fixed2 p = fixed2(i.uvrefl.x, 1 - i.uvrefl.y) + distortion * _Magnitude;
  fixed4 refl = tex2D(_ReflectionTex, p);
  return refl * _TintColor;
}

完整的片元着色器代码如下:

fixed4 frag (v2f i) : SV_Target
{
  fixed4 noise = tex2D(_NoiseTex, i.uvnoise + fixed2(_Speed * _Time.y, 0));
  fixed2 distortion = UnpackNormal(noise).rg;
  // reverse uvrefl
  fixed2 p = fixed2(i.uvrefl.x, 1 - i.uvrefl.y) + distortion * _Magnitude;
  fixed4 refl = tex2D(_ReflectionTex, p);
  return refl * _TintColor;
}