Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question Refraction on textured drinking glasses

Discussion in 'General Graphics' started by TicciTacca, Oct 13, 2020.

  1. TicciTacca

    TicciTacca

    Joined:
    Jun 11, 2018
    Posts:
    3
    Hello! I have a problem with my project, hope you can help me. I already tried finding a solution for it on the internet, but at this point i'm just clueless if what I want to achieve is even possible.

    I made drinking glasses with textures, where the textures are not just subtle scratches or the like, but also include a painted pattern (painted glass basically). The transparency, reflections and the textures work just fine, but I'm having real trouble adding the refraction.

    I tried different approaches:
    • Writing my own shader while using the standard render pipeline.
    • Using an HDRP lit shader with "Double-sided" and "Depth Write" enabled, and then adding refraction inside the shader settings.
    • Creating a shader with the shader graph while using HDRP.
    With every approach I always ran into the same problems:
    • the opposite side of the glass doesn't show the textures anymore (even with "Double-sided" or "Cull Off")
    • sometimes even the opposite side of the glass isn't visible anymore
    • the textures are not refracted as well, only the glass, which is a problem since the textures are quite obvious and so it looks very unrealistic if they are not refracted
    I guess those issues occur because of the way that the refraction is added: the only two methods that i found are grabbing the background texture with a grab pass (and of course, the textures on the glass are not included in this background texture) or using the scene color texture (where the textures on the glasses should be included in the texture, but it still looks wrong).

    So how can I make it so that my glasses have (1) realistic refraction (next to transparency/reflections/textures) and also (2) the textures are refracted as well?

    Thanks in advance!
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,309
    Refraction isn't really possible without raytracing.

    Refraction shaders aren't really doing any refracting. They're screen space distortion shaders which are only very basically approximating refraction.

    With the built in rendering pipeline, you can use a two pass shader that renders the back faces first, then does a grab pass, then renders the front faces using that grab pass as the "transparency". If you use a double sided shader with Cull Off, then the grab pass is done before the object has rendered at all, and thus you don't see the glass in the refraction. If you want to have liquid inside the glass, that makes it all even more complicated as you have to split up the object into glass back faces as its own shader & material, liquid as its own material, and then the glass front face as it's own shader & material with the grab pass, and make sure it's always rendered in that order. Basically it's a huge pain.

    With the HDRP pipeline ... it's just not possible.* The HDRP doesn't support grab passes, only the scene color which is more specifically the "_OpaqueCameraTexture", which as it sounds is a copy of the scene after all opaque objects have rendered. You can kind of fake stuff like the paint on the glass if it's opaque by rendering that separately as opaque. Then it'll be in the opaque texture to be seen by the refraction. But the other layers of the glass will not be seen through the parts that are refracting!

    * However if you have an RTX GPU, you can enable ray tracing for the HDRP, and that does support refraction.
    https://docs.unity3d.com/Packages/c...1/manual/Ray-Tracing-Recursive-Rendering.html
     
    KiddUniverse likes this.
  3. TicciTacca

    TicciTacca

    Joined:
    Jun 11, 2018
    Posts:
    3
    Thanks for your reply!

    Yes, I wanted to add liquids as well, but after reading this, I think this is too complicated for me. I'm still a beginner when it comes to shaders.

    I'll try it with the two pass shader again (I tried that before, but I guess the order was wrong), and see how it goes. What do you mean by "using that grab pass as the "transparency"" ?
     
  4. TicciTacca

    TicciTacca

    Joined:
    Jun 11, 2018
    Posts:
    3
    So I managed to basically get what I want, a glass shader that also "refracts" the textures. Yay! But there are still some issues:
    • The biggest problem is that I can see the glass inside the glass, especially when looking from above.
    • Also the refraction effect is too strong, setting the IOR to 1.03 is already too much, at least from several angles. I'd like to change that, but I don't really know how.
    Maybe the calculation for the refraction is not the most suitable in my case, but it's the closest one I've found. :(

    Here is the code:
    Code (CSharp):
    1. Shader "Custom/Refraction"
    2. {  
    3.     Properties
    4.     {
    5.         //_Tint ("Tint", Color) = (0, 0, 0, 1)
    6.         [NoScaleOffset] _MainTex ("Albedo/Transparency", 2D) = "white" {}
    7.         [NoScaleOffset] _Metallic ("Metallic/Smoothness", 2D) = "white" {}
    8.         [Normal][NoScaleOffset] _Normal ("Normal Map", 2D) = "bump" {}
    9.         _RefractionIndex("Refraction Index", Range(1.0, 3.0)) = 1.33
    10.         _Distance("Distance", Float) = 10.0
    11.              
    12.     }
    13.  
    14.     SubShader
    15.     {
    16.         Tags{ "RenderType"="Transparent" "Queue"="Transparent"}
    17.         LOD 200
    18.  
    19.         Cull Front  
    20.                
    21.         CGPROGRAM
    22.         #pragma surface surf Standard fullforwardshadows alpha:premul
    23.         #pragma target 3.0
    24.  
    25.         sampler2D _MainTex;
    26.         sampler2D _Metallic;
    27.         sampler2D _Normal;
    28.         //fixed4 _Tint;
    29.  
    30.         struct Input
    31.         {
    32.             float2 uv_MainTex;  
    33.             float2 uv_Metallic;
    34.             float2 uv_Normal;
    35.                  
    36.         };
    37.  
    38.         void surf (Input IN, inout SurfaceOutputStandard o)
    39.         {
    40.             fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
    41.             //fixed Alpha = c.a;
    42.             //c *= _Tint;
    43.             o.Albedo = c.rgb;
    44.             o.Alpha = c.a;          
    45.             //o.Albedo = lerp(_Tint.rgb, c.rgb, Alpha);
    46.             //o.Alpha = lerp(_Tint.a, 1.0, Alpha);
    47.             fixed4 m = tex2D (_Metallic, IN.uv_Metallic);
    48.             o.Metallic = m.rgb;
    49.             o.Smoothness = m.a;
    50.             //o.Normal = UnpackNormal (tex2D (_Normal, IN.uv_Normal));
    51.             o.Normal = half3(0,0,-1);      
    52.                    
    53.         }
    54.  
    55.         ENDCG
    56.      
    57.         GrabPass
    58.         {
    59.             //"_BackgroundTexture"
    60.         }
    61.      
    62.         Pass
    63.         {
    64.             Blend One Zero
    65.  
    66.             ZWrite Off
    67.  
    68.             CGPROGRAM
    69.             #pragma vertex vert
    70.             #pragma fragment frag
    71.          
    72.             #include "UnityCG.cginc"
    73.             #include "Lighting.cginc"
    74.  
    75.             struct appdata
    76.             {
    77.                 float4 pos : POSITION;
    78.                 float3 normal : NORMAL;
    79.             };
    80.  
    81.             struct v2f
    82.             {
    83.                 float4 pos : SV_POSITION;
    84.                 float3 worldPos : TEXCOORD0;
    85.                 float3 normal : TEXCOORD1;
    86.             };
    87.  
    88.             float _RefractionIndex;
    89.             float _Distance;
    90.             //sampler2D _BackgroundTexture;          
    91.             sampler2D _GrabTexture;
    92.  
    93.             v2f vert (appdata v)
    94.             {
    95.                 v2f o;
    96.                 o.pos = UnityObjectToClipPos(v.pos);
    97.                 o.worldPos = mul(unity_ObjectToWorld, v.pos).xyz;
    98.                 o.normal = UnityObjectToWorldNormal(v.normal);
    99.                 return o;
    100.             }
    101.  
    102.             float schlickFresnel(float cosine) {
    103.                 float r0 = (1 - _RefractionIndex) / (1 + _RefractionIndex);
    104.                 r0 = r0 * r0;
    105.                 return r0 + (1 - r0) * pow(1 - cosine, 5);
    106.             }
    107.  
    108.             fixed4 frag (v2f i) : SV_Target
    109.             {
    110.                 float3 normal = normalize(i.normal);
    111.                 float3 viewDir = -normalize(i.worldPos - _WorldSpaceCameraPos.xyz);
    112.  
    113.                 float3 reflectDir = reflect(viewDir, normal);
    114.                 float3 reflectCol = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, reflectDir);
    115.  
    116.                 float3 refractDir = refract(viewDir, normal, 1.0 / _RefractionIndex);
    117.                 float3 refractPos = i.worldPos + refractDir * _Distance;
    118.                 float4 refractScreenPos = mul(UNITY_MATRIX_VP, float4(refractPos, 1.0));
    119.                 float2 screenUv = (refractScreenPos.xy / refractScreenPos.w) * 0.5 + 0.5;
    120.                 #if UNITY_UV_STARTS_AT_TOP
    121.                 screenUv.y = 1.0 - screenUv.y;
    122.                 #endif
    123.  
    124.                 float3 refractCol = tex2D(_GrabTexture, screenUv).xyz;
    125.                 float3 spec = pow(max(0.0, dot(normalize(_WorldSpaceLightPos0.xyz), reflectDir)), 10) * _LightColor0;
    126.  
    127.                 float f = schlickFresnel(max(0.0, dot(-viewDir, normal)));
    128.  
    129.                 float3 c = (1.0 - f) * refractCol + f * (reflectCol + spec);
    130.              
    131.                 return float4(c, 1.0);
    132.             }
    133.             ENDCG
    134.         }
    135.      
    136.         Cull Back  
    137.      
    138.         CGPROGRAM
    139.         #pragma surface surf Standard fullforwardshadows alpha:premul
    140.         #pragma target 3.0
    141.  
    142.         sampler2D _MainTex;
    143.         sampler2D _Metallic;
    144.         sampler2D _Normal;
    145.         //fixed4 _Tint;
    146.  
    147.         struct Input
    148.         {
    149.             float2 uv_MainTex;  
    150.             float2 uv_Metallic;
    151.             float2 uv_Normal;
    152.                  
    153.         };
    154.  
    155.         void surf (Input IN, inout SurfaceOutputStandard o)
    156.         {
    157.             fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
    158.             //fixed Alpha = c.a;
    159.             //c *= _Tint;
    160.             o.Albedo = c.rgb;
    161.             o.Alpha = c.a;
    162.             //o.Albedo = lerp(_Tint.rgb, c.rgb, Alpha);
    163.             //o.Alpha = lerp(_Tint.a, 1.0, Alpha);
    164.             fixed4 m = tex2D (_Metallic, IN.uv_Metallic);
    165.             o.Metallic = m.rgb;
    166.             o.Smoothness = m.a;
    167.             o.Normal = UnpackNormal (tex2D (_Normal, IN.uv_Normal));
    168.          
    169.         }
    170.         ENDCG      
    171.              
    172.     }
    173.     FallBack "Diffuse"
    174. }
    Any further help would be much appreciated. :)
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,309
    Again, this isn't refraction, it's a very bad approximation. Note, I'm not saying the above shader is bad, I'm saying the approximation of using a refraction direction as a positional offset for the UVs is never going to get you anything close to what actual an refraction would produce. It's all just a giant hack to distort the background a little to give the impression of refraction. It's also what quite literally everyone does as it's "good enough" most of the time.

    The solution for what you're seeing is essentially just to play with the numbers enough for it to not look too terrible and leave it at that. There really isn't anything else you can do.