Search Unity

Quake 1 liquid shader

Discussion in 'Shaders' started by Zebbi, Apr 22, 2020.

  1. Zebbi

    Zebbi

    Joined:
    Jan 17, 2017
    Posts:
    521
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Zebbi likes this.
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    jqe7h3B2ml.gif
    Code (CSharp):
    1. Shader "Unlit/QuakeLiquid"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.         _WaveFrequency ("Wave Frequency", Float) = 0.5
    7.         _WaveScale ("Wave Scale", Float) = 0.8
    8.         _WaveAmplitude ("Wave Amplitude", Float) = 0.15
    9.     }
    10.     SubShader
    11.     {
    12.         Tags { "RenderType"="Opaque" }
    13.         LOD 100
    14.  
    15.         Pass
    16.         {
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.             // make fog work
    21.             #pragma multi_compile_fog
    22.  
    23.             #include "UnityCG.cginc"
    24.  
    25.             struct appdata
    26.             {
    27.                 float4 vertex : POSITION;
    28.                 float2 uv : TEXCOORD0;
    29.             };
    30.  
    31.             struct v2f
    32.             {
    33.                 float2 uv : TEXCOORD0;
    34.                 UNITY_FOG_COORDS(1)
    35.                 float4 vertex : SV_POSITION;
    36.             };
    37.  
    38.             sampler2D _MainTex;
    39.             float4 _MainTex_ST;
    40.             float _WaveScale, _WaveAmplitude, _WaveFrequency;
    41.  
    42.             v2f vert (appdata v)
    43.             {
    44.                 v2f o;
    45.                 o.vertex = UnityObjectToClipPos(v.vertex);
    46.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    47.                 UNITY_TRANSFER_FOG(o,o.vertex);
    48.                 return o;
    49.             }
    50.  
    51.             fixed4 frag (v2f i) : SV_Target
    52.             {
    53.                 float2 sinTime = (i.uv.yx * _WaveScale + _Time.y * _WaveFrequency) * UNITY_PI;
    54.                 float2 uv = i.uv + float2(sin(sinTime.x), sin(sinTime.y)) * _WaveAmplitude;
    55.                 fixed4 col = tex2D(_MainTex, uv);
    56.                 UNITY_APPLY_FOG(i.fogCoord, col);
    57.                 return col;
    58.             }
    59.             ENDCG
    60.         }
    61.     }
    62. }
     
    dogs_ and Invertex like this.
  4. Zebbi

    Zebbi

    Joined:
    Jan 17, 2017
    Posts:
    521
    Wow, that's perfect, thankyou @bgolus ! I was interested about this part:

    I'm interested, what specifically is supposedly so difficult to replicate with modern shaders?
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    The author is wrong. The above shader almost exactly replicates Quake’s liquid (though the parameterization is different).

    It was difficult (actually impossible for many GPUs still being used) when most of the Quake ports to hardware renderers were written, 15 years ago... Today it’s trivial unless you’re trying to run it on 10 year old mobile phones.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    I guess it could be argued it’s still difficult to implement Quake’s liquid exactly the same way as Quake did on modern GPUs ... but there’s no need to use the same implementation if the results are identical.
     
  7. Zebbi

    Zebbi

    Joined:
    Jan 17, 2017
    Posts:
    521
    Thanks, that's very interesting!
     
  8. Zebbi

    Zebbi

    Joined:
    Jan 17, 2017
    Posts:
    521
    I've been trying the shader as a fullscreen effect but I'm getting some edge distortion/fringing:


    Does the shader need to overshoot or would it be an issue with blitting?
     
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    A quick look at the original Quake software renderer, when underwater it appears they expand the rendered image slightly to account for the distortion. Not necessary when applied on a surface because it presumably has a repeating texture. But for a post process you'd need to scale the screen space UV prior to warping.

    You probably also want to correct for the aspect ratio in the wave amplitude.

    Code (csharp):
    1. // correct wave amplitude to match the aspect ratio
    2. float2 waveAmplitude = float2(_ScreenParams.y / _ScreenParams.x * _WaveAmplitude, _WaveAmplitude);
    3.  
    4. // scale the uvs to account for wave amplitude
    5. float2 uv = (i.uv - 0.5) / (1 + abs(waveAmplitude)) + 0.5;
    6. uv += float2(sin(sinTime.x), sin(sinTime.y)) * waveAmplitude;
     
    Zebbi likes this.
  10. Zebbi

    Zebbi

    Joined:
    Jan 17, 2017
    Posts:
    521
    Thanks! I did try making the rendertexture slightly bigger than the screen width and height, but it didn't work. Is changing the screen space UV different to the size of the rendertexture (I'm using graphics.blit)?
     
  11. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    The problem is you were distorting the uvs so you were seeing outside the 0.0 to 1.0 range of the texture. Making the render texture a higher resolution doesn't fix anything since the uv range is normalized to the extents of the texture. The idea with the above code is scaling the UV so that before distortion some amount of the render texture is off screen so that after distortion nothing outside the 0.0 to 1.0 range is visible.

    I also might have the math wrong. Might need to do
    / (1 + abs(waveAmplitude) * 2.0)
    , or do
    * (1 - abs(waveAmplitude) * 2.0)
    . Brain isn't totally working today... actually pretty sure it's that last one and not what's above.
     
  12. Zebbi

    Zebbi

    Joined:
    Jan 17, 2017
    Posts:
    521
    Thanks, I tried both modifications and the waves are no longer working with either (although the scene shows okay), am I doing this right?
    Code (CSharp):
    1. Shader "Unlit/QuakeLiquid"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex("Texture", 2D) = "white" {}
    6.         _WaveFrequency("Wave Frequency", Float) = 0.5
    7.         _WaveScale("Wave Scale", Float) = 0.8
    8.         _WaveAmplitude("Wave Amplitude", Float) = 0.15
    9.     }
    10.         SubShader
    11.         {
    12.             Tags { "RenderType" = "Opaque" }
    13.             LOD 100
    14.  
    15.             Pass
    16.             {
    17.                 CGPROGRAM
    18.                 #pragma vertex vert
    19.                 #pragma fragment frag
    20.                 // make fog work
    21.                 #pragma multi_compile_fog
    22.  
    23.                 #include "UnityCG.cginc"
    24.  
    25.                 struct appdata
    26.                 {
    27.                     float4 vertex : POSITION;
    28.                     float2 uv : TEXCOORD0;
    29.                 };
    30.  
    31.                 struct v2f
    32.                 {
    33.                     float2 uv : TEXCOORD0;
    34.                     UNITY_FOG_COORDS(1)
    35.                     float4 vertex : SV_POSITION;
    36.                 };
    37.  
    38.                 sampler2D _MainTex;
    39.                 float4 _MainTex_ST;
    40.                 float _WaveScale, _WaveAmplitude, _WaveFrequency;
    41.  
    42.                 v2f vert(appdata v)
    43.                 {
    44.                     v2f o;
    45.                     o.vertex = UnityObjectToClipPos(v.vertex);
    46.                     o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    47.                     UNITY_TRANSFER_FOG(o,o.vertex);
    48.                     return o;
    49.                 }
    50.  
    51.                 fixed4 frag(v2f i) : SV_Target
    52.                 {
    53.                     float2 waveAmplitude = float2(_ScreenParams.y / _ScreenParams.x * _WaveAmplitude, _WaveAmplitude);
    54.                     float2 sinTime = (i.uv.yx * _WaveScale + _Time.y * _WaveFrequency) * UNITY_PI;
    55.                     float2 uv = (i.uv - 0.5) * (1 - abs(waveAmplitude) * 2.0) + 0.5;
    56.                     fixed4 col = tex2D(_MainTex, uv);
    57.                     UNITY_APPLY_FOG(i.fogCoord, col);
    58.                     return col;
    59.                 }
    60.                 ENDCG
    61.             }
    62.         }
    63. }
     
  13. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Here's a version of the shader fixed up for use as an image effect. Makes one minor change from the original in that the amplitude is scaled by the wave scale so you can more easily modify the wave scale without the distortion going crazy or fading too much. Also corrects the UV used for the sine wave phase offset so that you get that "wiggle" look from the original instead of just looking like scrolling sine waves.
    Code (CSharp):
    1. Shader "Hidden/QuakeUnderWater"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.         _WaveFrequency ("Wave Frequency", Float) = 0.5
    7.         _WaveScale ("Wave Scale", Float) = 0.8
    8.         _WaveAmplitude ("Wave Amplitude", Range(0,0.33)) = 0.15
    9.     }
    10.     SubShader
    11.     {
    12.         // No culling or depth
    13.         Cull Off ZWrite Off ZTest Always
    14.  
    15.         Pass
    16.         {
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.  
    21.             #include "UnityCG.cginc"
    22.  
    23.             struct appdata
    24.             {
    25.                 float4 vertex : POSITION;
    26.                 float2 uv : TEXCOORD0;
    27.             };
    28.  
    29.             struct v2f
    30.             {
    31.                 float2 uv : TEXCOORD0;
    32.                 float4 vertex : SV_POSITION;
    33.             };
    34.  
    35.             v2f vert (appdata v)
    36.             {
    37.                 v2f o;
    38.                 o.vertex = UnityObjectToClipPos(v.vertex);
    39.                 o.uv = v.uv;
    40.                 return o;
    41.             }
    42.  
    43.             sampler2D _MainTex;
    44.             float _WaveScale, _WaveAmplitude, _WaveFrequency;
    45.  
    46.             fixed4 frag (v2f i) : SV_Target
    47.             {
    48.                 float aspect = _ScreenParams.y / _ScreenParams.x;
    49.                 float2 waveAmplitude = float2(aspect * _WaveAmplitude, _WaveAmplitude) / _WaveScale;
    50.  
    51.                 float2 uv = (i.uv - 0.5) * (1.0 - abs(waveAmplitude) * 2.0) + 0.5;
    52.  
    53.                 float2 sinTime = (float2((uv.y - 0.5) * aspect, uv.x - 0.5) * _WaveScale + _Time.y * _WaveFrequency) * UNITY_PI;
    54.  
    55.                 uv += float2(sin(sinTime.x), sin(sinTime.y)) * waveAmplitude;
    56.  
    57.                 fixed4 col = tex2D(_MainTex, uv);
    58.                 return col;
    59.             }
    60.             ENDCG
    61.         }
    62.     }
    63. }
     
    Zebbi likes this.
  14. Zebbi

    Zebbi

    Joined:
    Jan 17, 2017
    Posts:
    521
    Thank you SO much, this works perfectly! As a side note, would you recommend adding a color tint to the shader or doing it via a canvas? If the shader was given an RGBA value, the alpha could presumably be used for the amount of tint and the color for the hue, would this is be possible in the same shader?
     
  15. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Yeah, I'd do that in the same shader. I'll let you figure out how to do that though.
     
    Zebbi likes this.
  16. Zebbi

    Zebbi

    Joined:
    Jan 17, 2017
    Posts:
    521
    Well, I'm kinda proud I did this all by myself and nothing broke, but I don't think the alphas working properly :(

    Code (CSharp):
    1. Shader "Hidden/QuakeUnderWater"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex("Texture", 2D) = "white" {}
    6.         _WaveFrequency("Wave Frequency", Float) = 0.5
    7.         _WaveScale("Wave Scale", Float) = 0.8
    8.         _WaveAmplitude("Wave Amplitude", Range(0,0.33)) = 0.15
    9.         _Color("Color Tint", Color) = (1,1,1,1)
    10.     }
    11.         SubShader
    12.         {
    13.             // No culling or depth
    14.             Cull Off ZWrite Off ZTest Always
    15.  
    16.             Pass
    17.             {
    18.                 CGPROGRAM
    19.                 #pragma vertex vert
    20.                 #pragma fragment frag
    21.  
    22.                 #include "UnityCG.cginc"
    23.  
    24.                 struct appdata
    25.                 {
    26.                     float4 vertex : POSITION;
    27.                     float2 uv : TEXCOORD0;
    28.                 };
    29.  
    30.                 struct v2f
    31.                 {
    32.                     float2 uv : TEXCOORD0;
    33.                     float4 vertex : SV_POSITION;
    34.                 };
    35.  
    36.                 v2f vert(appdata v)
    37.                 {
    38.                     v2f o;
    39.                     o.vertex = UnityObjectToClipPos(v.vertex);
    40.                     o.uv = v.uv;
    41.                     return o;
    42.                 }
    43.  
    44.                 sampler2D _MainTex;
    45.                 float _WaveScale, _WaveAmplitude, _WaveFrequency;
    46.                 float4 _Color;
    47.  
    48.                 fixed4 frag(v2f i) : SV_Target
    49.                 {
    50.                     float aspect = _ScreenParams.y / _ScreenParams.x;
    51.                     float2 waveAmplitude = float2(aspect * _WaveAmplitude, _WaveAmplitude) / _WaveScale;
    52.  
    53.                     float2 uv = (i.uv - 0.5) * (1.0 - abs(waveAmplitude) * 2.0) + 0.5;
    54.  
    55.                     float2 sinTime = (float2((uv.y - 0.5) * aspect, uv.x - 0.5) * _WaveScale + _Time.y * _WaveFrequency) * UNITY_PI;
    56.  
    57.                     uv += float2(sin(sinTime.x), sin(sinTime.y)) * waveAmplitude;
    58.  
    59.                     fixed4 col = tex2D(_MainTex, uv) * _Color;
    60.                     return col;
    61.                 }
    62.                 ENDCG
    63.             }
    64.         }
    65. }
     
  17. a4chteam

    a4chteam

    Joined:
    Dec 22, 2020
    Posts:
    22
    Sorry, I tried to interpret your shader with the additions of additional color and lighting effects. But alas, nothing works, could you please see what the error is.

    Code (CSharp):
    1. Shader "Custom/Water"
    2. {
    3.     //Based on bgolus QuakeLiquid shader;
    4.     Properties
    5.     {
    6.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    7.         _Color("Color", Color) = (1,1,1,1)
    8.         _Amplitude("Amplitude", Float) = 0.12
    9.         _Scale("Scale", Float) = 0.7
    10.         _Frequency("Frequency", Float) = 0.2
    11.     }
    12.     SubShader
    13.     {
    14.         Tags { "RenderType"="Opaque" }
    15.         LOD 200
    16.  
    17.         CGPROGRAM
    18. // Upgrade NOTE: excluded shader from DX11; has structs without semantics (struct appdata members normal)
    19. #pragma exclude_renderers d3d11
    20.         #pragma surface surf Lambert
    21.         #pragma vertex vert
    22.  
    23.         #include "UnityCG.cginc"
    24.  
    25.         sampler2D _MainTex;
    26.         float4 _MainTex_ST;
    27.  
    28.         struct Input
    29.         {
    30.             float4 vertex : POSITION;
    31.             float2 uv : TEXCOORD0;
    32.         };
    33.  
    34.         struct appdata
    35.         {
    36.             float4 vertex : POSITION;
    37.             float2 uv : TEXCOORD0;
    38.             float3 normal;
    39.         };
    40.  
    41.         fixed4 _Color;
    42.         float _Amplitude;
    43.         float _Scale;
    44.         float _Frequency;
    45.  
    46.         Input vert(inout appdata v)
    47.         {
    48.             Input o;
    49.             o.vertex = UnityObjectToClipPos(v.vertex);
    50.             o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    51.             return o;
    52.         }
    53.  
    54.         void surf (Input IN, inout SurfaceOutput o)
    55.         {
    56.             float2 DeltaTime = (IN.uv.yx * _Scale + _Time.y * _Frequency) * UNITY_PI;
    57.             float2 uv = IN.uv + float2(sin(DeltaTime.x), sin(DeltaTime.y)) * _Amplitude;
    58.             // Albedo comes from a texture tinted by color
    59.             fixed4 c = tex2D (_MainTex, uv) * _Color;
    60.             o.Albedo = c.rgb;
    61.         }
    62.         ENDCG
    63.     }
    64.     FallBack "Diffuse"
    65. }
     
  18. imaethan

    imaethan

    Joined:
    Dec 13, 2012
    Posts:
    7

    Did you ever get this working