UnityMol  0.9.6-875
UnityMol viewer / In developement
Water.cs
Go to the documentation of this file.
1 using UnityEngine;
2 using System.Collections;
3 
4 
5 [ExecuteInEditMode] // Make water live-update even when not in play mode
6 public class Water : MonoBehaviour
7 {
8  public enum WaterMode {
9  Simple = 0,
10  Reflective = 1,
11  Refractive = 2,
12  };
13  public WaterMode m_WaterMode = WaterMode.Refractive;
14  public bool m_DisablePixelLights = true;
15  public int m_TextureSize = 256;
16  public float m_ClipPlaneOffset = 0.07f;
17 
18  public LayerMask m_ReflectLayers = -1;
19  public LayerMask m_RefractLayers = -1;
20 
21  private Hashtable m_ReflectionCameras = new Hashtable(); // Camera -> Camera table
22  private Hashtable m_RefractionCameras = new Hashtable(); // Camera -> Camera table
23 
24  private RenderTexture m_ReflectionTexture = null;
25  private RenderTexture m_RefractionTexture = null;
27  private int m_OldReflectionTextureSize = 0;
28  private int m_OldRefractionTextureSize = 0;
29 
30  private static bool s_InsideWater = false;
31 
32  // This is called when it's known that the object will be rendered by some
33  // camera. We render reflections / refractions and do other updates here.
34  // Because the script executes in edit mode, reflections for the scene view
35  // camera will just work!
36  public void OnWillRenderObject()
37  {
38  if( !enabled || !GetComponent<Renderer>() || !GetComponent<Renderer>().sharedMaterial || !GetComponent<Renderer>().enabled )
39  return;
40 
41  Camera cam = Camera.current;
42  if( !cam )
43  return;
44 
45  // Safeguard from recursive water reflections.
46  if( s_InsideWater )
47  return;
48  s_InsideWater = true;
49 
50  // Actual water rendering mode depends on both the current setting AND
51  // the hardware support. There's no point in rendering refraction textures
52  // if they won't be visible in the end.
53  m_HardwareWaterSupport = FindHardwareWaterSupport();
54  WaterMode mode = GetWaterMode();
55 
56  Camera reflectionCamera, refractionCamera;
57  CreateWaterObjects( cam, out reflectionCamera, out refractionCamera );
58 
59  // find out the reflection plane: position and normal in world space
60  Vector3 pos = transform.position;
61  Vector3 normal = transform.up;
62 
63  // Optionally disable pixel lights for reflection/refraction
64  int oldPixelLightCount = QualitySettings.pixelLightCount;
65  if( m_DisablePixelLights )
66  QualitySettings.pixelLightCount = 0;
67 
68  UpdateCameraModes( cam, reflectionCamera );
69  UpdateCameraModes( cam, refractionCamera );
70 
71  // Render reflection if needed
72  if( mode >= WaterMode.Reflective )
73  {
74  // Reflect camera around reflection plane
75  float d = -Vector3.Dot (normal, pos) - m_ClipPlaneOffset;
76  Vector4 reflectionPlane = new Vector4 (normal.x, normal.y, normal.z, d);
77 
78  Matrix4x4 reflection = Matrix4x4.zero;
79  CalculateReflectionMatrix (ref reflection, reflectionPlane);
80  Vector3 oldpos = cam.transform.position;
81  Vector3 newpos = reflection.MultiplyPoint( oldpos );
82  reflectionCamera.worldToCameraMatrix = cam.worldToCameraMatrix * reflection;
83 
84  // Setup oblique projection matrix so that near plane is our reflection
85  // plane. This way we clip everything below/above it for free.
86  Vector4 clipPlane = CameraSpacePlane( reflectionCamera, pos, normal, 1.0f );
87  Matrix4x4 projection = cam.projectionMatrix;
88  CalculateObliqueMatrix (ref projection, clipPlane);
89  reflectionCamera.projectionMatrix = projection;
90 
91  reflectionCamera.cullingMask = ~(1<<4) & m_ReflectLayers.value; // never render water layer
92  reflectionCamera.targetTexture = m_ReflectionTexture;
93  GL.invertCulling = true;
94  reflectionCamera.transform.position = newpos;
95  Vector3 euler = cam.transform.eulerAngles;
96  reflectionCamera.transform.eulerAngles = new Vector3(0, euler.y, euler.z);
97  reflectionCamera.Render();
98  reflectionCamera.transform.position = oldpos;
99  GL.invertCulling = false;
100  GetComponent<Renderer>().sharedMaterial.SetTexture( "_ReflectionTex", m_ReflectionTexture );
101  }
102 
103  // Render refraction
104  if( mode >= WaterMode.Refractive )
105  {
106  refractionCamera.worldToCameraMatrix = cam.worldToCameraMatrix;
107 
108  // Setup oblique projection matrix so that near plane is our reflection
109  // plane. This way we clip everything below/above it for free.
110  Vector4 clipPlane = CameraSpacePlane( refractionCamera, pos, normal, -1.0f );
111  Matrix4x4 projection = cam.projectionMatrix;
112  CalculateObliqueMatrix (ref projection, clipPlane);
113  refractionCamera.projectionMatrix = projection;
114 
115  refractionCamera.cullingMask = ~(1<<4) & m_RefractLayers.value; // never render water layer
116  refractionCamera.targetTexture = m_RefractionTexture;
117  refractionCamera.transform.position = cam.transform.position;
118  refractionCamera.transform.rotation = cam.transform.rotation;
119  refractionCamera.Render();
120  GetComponent<Renderer>().sharedMaterial.SetTexture( "_RefractionTex", m_RefractionTexture );
121  }
122 
123  // Restore pixel light count
124  if( m_DisablePixelLights )
125  QualitySettings.pixelLightCount = oldPixelLightCount;
126 
127  // Setup shader keywords based on water mode
128  switch( mode )
129  {
130  case WaterMode.Simple:
131  Shader.EnableKeyword( "WATER_SIMPLE" );
132  Shader.DisableKeyword( "WATER_REFLECTIVE" );
133  Shader.DisableKeyword( "WATER_REFRACTIVE" );
134  break;
135  case WaterMode.Reflective:
136  Shader.DisableKeyword( "WATER_SIMPLE" );
137  Shader.EnableKeyword( "WATER_REFLECTIVE" );
138  Shader.DisableKeyword( "WATER_REFRACTIVE" );
139  break;
140  case WaterMode.Refractive:
141  Shader.DisableKeyword( "WATER_SIMPLE" );
142  Shader.DisableKeyword( "WATER_REFLECTIVE" );
143  Shader.EnableKeyword( "WATER_REFRACTIVE" );
144  break;
145  }
146 
147  s_InsideWater = false;
148  }
149 
150 
151  // Cleanup all the objects we possibly have created
152  void OnDisable()
153  {
154  if( m_ReflectionTexture ) {
155  DestroyImmediate( m_ReflectionTexture );
156  m_ReflectionTexture = null;
157  }
158  if( m_RefractionTexture ) {
159  DestroyImmediate( m_RefractionTexture );
160  m_RefractionTexture = null;
161  }
162  foreach( DictionaryEntry kvp in m_ReflectionCameras )
163  DestroyImmediate( ((Camera)kvp.Value).gameObject );
164  m_ReflectionCameras.Clear();
165  foreach( DictionaryEntry kvp in m_RefractionCameras )
166  DestroyImmediate( ((Camera)kvp.Value).gameObject );
167  m_RefractionCameras.Clear();
168  }
169 
170 
171  // This just sets up some matrices in the material; for really
172  // old cards to make water texture scroll.
173  void Update()
174  {
175  if( !GetComponent<Renderer>() )
176  return;
177  Material mat = GetComponent<Renderer>().sharedMaterial;
178  if( !mat )
179  return;
180 
181  Vector4 waveSpeed = mat.GetVector( "WaveSpeed" );
182  float waveScale = mat.GetFloat( "_WaveScale" );
183  float t = Time.time / 40.0f;
184 
185  Vector3 offset = new Vector3( t * waveSpeed.x, t * waveSpeed.y, 0 );
186  Vector3 scale = new Vector3( 1.0f/waveScale, 1.0f/waveScale, 1 );
187  Matrix4x4 scrollMatrix = Matrix4x4.TRS( offset, Quaternion.identity, scale );
188  mat.SetMatrix( "_WaveMatrix", scrollMatrix );
189 
190  offset = new Vector3( t * waveSpeed.z, t * waveSpeed.w, 0 );
191  scrollMatrix = Matrix4x4.TRS( offset, Quaternion.identity, scale * 0.45f );
192  mat.SetMatrix( "_WaveMatrix2", scrollMatrix );
193  }
194 
195  private void UpdateCameraModes( Camera src, Camera dest )
196  {
197  if( dest == null )
198  return;
199  // set water camera to clear the same way as current camera
200  dest.clearFlags = src.clearFlags;
201  dest.backgroundColor = src.backgroundColor;
202  if( src.clearFlags == CameraClearFlags.Skybox )
203  {
204  Skybox sky = src.GetComponent(typeof(Skybox)) as Skybox;
205  Skybox mysky = dest.GetComponent(typeof(Skybox)) as Skybox;
206  if( !sky || !sky.material )
207  {
208  mysky.enabled = false;
209  }
210  else
211  {
212  mysky.enabled = true;
213  mysky.material = sky.material;
214  }
215  }
216  // update other values to match current camera.
217  // even if we are supplying custom camera&projection matrices,
218  // some of values are used elsewhere (e.g. skybox uses far plane)
219  dest.farClipPlane = src.farClipPlane;
220  dest.nearClipPlane = src.nearClipPlane;
221  dest.orthographic = src.orthographic;
222  dest.fieldOfView = src.fieldOfView;
223  dest.aspect = src.aspect;
224  dest.orthographicSize = src.orthographicSize;
225  }
226 
227  // On-demand create any objects we need for water
228  private void CreateWaterObjects( Camera currentCamera, out Camera reflectionCamera, out Camera refractionCamera )
229  {
230  WaterMode mode = GetWaterMode();
231 
232  reflectionCamera = null;
233  refractionCamera = null;
234 
235  if( mode >= WaterMode.Reflective )
236  {
237  // Reflection render texture
238  if( !m_ReflectionTexture || m_OldReflectionTextureSize != m_TextureSize )
239  {
240  if( m_ReflectionTexture )
241  DestroyImmediate( m_ReflectionTexture );
242  m_ReflectionTexture = new RenderTexture( m_TextureSize, m_TextureSize, 16 );
243  m_ReflectionTexture.name = "__WaterReflection" + GetInstanceID();
244  m_ReflectionTexture.isPowerOfTwo = true;
245  m_ReflectionTexture.hideFlags = HideFlags.DontSave;
246  m_OldReflectionTextureSize = m_TextureSize;
247  }
248 
249  // Camera for reflection
250  reflectionCamera = m_ReflectionCameras[currentCamera] as Camera;
251  if( !reflectionCamera ) // catch both not-in-dictionary and in-dictionary-but-deleted-GO
252  {
253  GameObject go = new GameObject( "Water Refl Camera id" + GetInstanceID() + " for " + currentCamera.GetInstanceID(), typeof(Camera), typeof(Skybox) );
254  reflectionCamera = go.GetComponent<Camera>();
255  reflectionCamera.enabled = false;
256  reflectionCamera.transform.position = transform.position;
257  reflectionCamera.transform.rotation = transform.rotation;
258  reflectionCamera.gameObject.AddComponent<FlareLayer>();
259  go.hideFlags = HideFlags.HideAndDontSave;
260  m_ReflectionCameras[currentCamera] = reflectionCamera;
261  }
262  }
263 
264  if( mode >= WaterMode.Refractive )
265  {
266  // Refraction render texture
267  if( !m_RefractionTexture || m_OldRefractionTextureSize != m_TextureSize )
268  {
269  if( m_RefractionTexture )
270  DestroyImmediate( m_RefractionTexture );
271  m_RefractionTexture = new RenderTexture( m_TextureSize, m_TextureSize, 16 );
272  m_RefractionTexture.name = "__WaterRefraction" + GetInstanceID();
273  m_RefractionTexture.isPowerOfTwo = true;
274  m_RefractionTexture.hideFlags = HideFlags.DontSave;
275  m_OldRefractionTextureSize = m_TextureSize;
276  }
277 
278  // Camera for refraction
279  refractionCamera = m_RefractionCameras[currentCamera] as Camera;
280  if( !refractionCamera ) // catch both not-in-dictionary and in-dictionary-but-deleted-GO
281  {
282  GameObject go = new GameObject( "Water Refr Camera id" + GetInstanceID() + " for " + currentCamera.GetInstanceID(), typeof(Camera), typeof(Skybox) );
283  refractionCamera = go.GetComponent<Camera>();
284  refractionCamera.enabled = false;
285  refractionCamera.transform.position = transform.position;
286  refractionCamera.transform.rotation = transform.rotation;
287  refractionCamera.gameObject.AddComponent<FlareLayer>();
288  go.hideFlags = HideFlags.HideAndDontSave;
289  m_RefractionCameras[currentCamera] = refractionCamera;
290  }
291  }
292  }
293 
295  {
296  if( m_HardwareWaterSupport < m_WaterMode )
297  return m_HardwareWaterSupport;
298  else
299  return m_WaterMode;
300  }
301 
303  {
304  if( !SystemInfo.supportsRenderTextures || !GetComponent<Renderer>() )
305  return WaterMode.Simple;
306 
307  Material mat = GetComponent<Renderer>().sharedMaterial;
308  if( !mat )
309  return WaterMode.Simple;
310 
311  string mode = mat.GetTag("WATERMODE", false);
312  if( mode == "Refractive" )
313  return WaterMode.Refractive;
314  if( mode == "Reflective" )
315  return WaterMode.Reflective;
316 
317  return WaterMode.Simple;
318  }
319 
320  // Extended sign: returns -1, 0 or 1 based on sign of a
321  private static float sgn(float a)
322  {
323  if (a > 0.0f) return 1.0f;
324  if (a < 0.0f) return -1.0f;
325  return 0.0f;
326  }
327 
328  // Given position/normal of the plane, calculates plane in camera space.
329  private Vector4 CameraSpacePlane (Camera cam, Vector3 pos, Vector3 normal, float sideSign)
330  {
331  Vector3 offsetPos = pos + normal * m_ClipPlaneOffset;
332  Matrix4x4 m = cam.worldToCameraMatrix;
333  Vector3 cpos = m.MultiplyPoint( offsetPos );
334  Vector3 cnormal = m.MultiplyVector( normal ).normalized * sideSign;
335  return new Vector4( cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos,cnormal) );
336  }
337 
338  // Adjusts the given projection matrix so that near plane is the given clipPlane
339  // clipPlane is given in camera space. See article in Game Programming Gems 5 and
340  // http://aras-p.info/texts/obliqueortho.html
341  private static void CalculateObliqueMatrix (ref Matrix4x4 projection, Vector4 clipPlane)
342  {
343  Vector4 q = projection.inverse * new Vector4(
344  sgn(clipPlane.x),
345  sgn(clipPlane.y),
346  1.0f,
347  1.0f
348  );
349  Vector4 c = clipPlane * (2.0F / (Vector4.Dot (clipPlane, q)));
350  // third row = clip plane - fourth row
351  projection[2] = c.x - projection[3];
352  projection[6] = c.y - projection[7];
353  projection[10] = c.z - projection[11];
354  projection[14] = c.w - projection[15];
355  }
356 
357  // Calculates reflection matrix around the given plane
358  private static void CalculateReflectionMatrix (ref Matrix4x4 reflectionMat, Vector4 plane)
359  {
360  reflectionMat.m00 = (1F - 2F*plane[0]*plane[0]);
361  reflectionMat.m01 = ( - 2F*plane[0]*plane[1]);
362  reflectionMat.m02 = ( - 2F*plane[0]*plane[2]);
363  reflectionMat.m03 = ( - 2F*plane[3]*plane[0]);
364 
365  reflectionMat.m10 = ( - 2F*plane[1]*plane[0]);
366  reflectionMat.m11 = (1F - 2F*plane[1]*plane[1]);
367  reflectionMat.m12 = ( - 2F*plane[1]*plane[2]);
368  reflectionMat.m13 = ( - 2F*plane[3]*plane[1]);
369 
370  reflectionMat.m20 = ( - 2F*plane[2]*plane[0]);
371  reflectionMat.m21 = ( - 2F*plane[2]*plane[1]);
372  reflectionMat.m22 = (1F - 2F*plane[2]*plane[2]);
373  reflectionMat.m23 = ( - 2F*plane[3]*plane[2]);
374 
375  reflectionMat.m30 = 0F;
376  reflectionMat.m31 = 0F;
377  reflectionMat.m32 = 0F;
378  reflectionMat.m33 = 1F;
379  }
380 }
static void CalculateObliqueMatrix(ref Matrix4x4 projection, Vector4 clipPlane)
Definition: Water.cs:341
void CreateWaterObjects(Camera currentCamera, out Camera reflectionCamera, out Camera refractionCamera)
Definition: Water.cs:228
LayerMask m_RefractLayers
Definition: Water.cs:19
Vector4 CameraSpacePlane(Camera cam, Vector3 pos, Vector3 normal, float sideSign)
Definition: Water.cs:329
static bool s_InsideWater
Definition: Water.cs:30
WaterMode m_HardwareWaterSupport
Definition: Water.cs:26
LayerMask m_ReflectLayers
Definition: Water.cs:18
void UpdateCameraModes(Camera src, Camera dest)
Definition: Water.cs:195
Definition: Water.cs:6
void Update()
Definition: Water.cs:173
static void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, Vector4 plane)
Definition: Water.cs:358
WaterMode
Definition: Water.cs:8
Hashtable m_ReflectionCameras
Definition: Water.cs:21
bool m_DisablePixelLights
Definition: Water.cs:14
float m_ClipPlaneOffset
Definition: Water.cs:16
WaterMode FindHardwareWaterSupport()
Definition: Water.cs:302
int m_TextureSize
Definition: Water.cs:15
RenderTexture m_ReflectionTexture
Definition: Water.cs:24
int m_OldRefractionTextureSize
Definition: Water.cs:28
static float sgn(float a)
Definition: Water.cs:321
WaterMode m_WaterMode
Definition: Water.cs:13
Hashtable m_RefractionCameras
Definition: Water.cs:22
WaterMode GetWaterMode()
Definition: Water.cs:294
void OnDisable()
Definition: Water.cs:152
RenderTexture m_RefractionTexture
Definition: Water.cs:25
int m_OldReflectionTextureSize
Definition: Water.cs:27
void OnWillRenderObject()
Definition: Water.cs:36