Search Unity

  1. Unity 2020.1 has been released.
    Dismiss Notice
  2. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Colored Glass Shader ? Like a bottle of wine ?

Discussion in 'Shaders' started by Noors84, Jul 28, 2020.

  1. Noors84

    Noors84

    Joined:
    Jul 12, 2016
    Posts:
    14
    Hello. I'm really struggling with this, and i dont find any example online. Can you confirm to me that there no way to make colored glass with the standard shader (built-in) that acts decently ? To me the color (e.g green) should be in multiply blend mode, then lighting/reflections, should be additive on top. Would it be the correct way to do it ? Thanks !
     
    Last edited: Jul 29, 2020
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    8,979
    Correct. There is no built in shader that can accurately replicate the look of colored glass.

    But basically there are three major parts to glass. Specular reflection, diffuse scattering / diffusion, and transmission. The Standard shader only does specular reflections and opaque diffuse shading, not diffuse scattering or transmission. This means it does a poor job or replicating any surface that requires the scattering and / or transmission.

    Ignoring the refraction and diffuse scattering, it's not that hard to replicate the multiplicative tinting. But part of the problem is the way GPUs work is it's not possible to do the multiplicative coloration, and the additive specular reflections on top in a single pass*. There's no blend mode that's capable of both. So usually you need a two pass shader, or more commonly you need a grab pass (built in rendering paths) or opaque scene color texture (URP/HDRP) so you can also do some form of faked refraction / diffusion (blurring). There are some excellent approximations abusing reflection probes too if you want something more like frosted glass. There's also fun stuff like Beer's Law that's difficult to take into account properly since getting accurate thickness information is complicated. But that can be mostly ignored for something like a glass bottle if you're okay assuming a constant thickness and some hackish approximations with a Fresnel.
    * Apple's mobile devices are capable of this when using Metal, but it's uncommon elsewhere.

    The short version is you'll need a custom Surface Shader with an extra pass at the start to do the multiplication. Here's a basic one.

    upload_2020-7-28_14-9-35.png
    Code (CSharp):
    1. Shader "Custom/ColoredGlass"
    2. {
    3.     Properties
    4.     {
    5.         _Color ("Color", Color) = (1,1,1,1)
    6.         _ThickColor ("Thick Glass Color", Color) = (0,0,0,1)
    7.         _FakeThickness ("Fake Wall Thickness", Range(0,1)) = 0.5
    8.         _MainTex ("Label Albedo & Alpha", 2D) = "black" {}
    9.         _Glossiness ("Glass Smoothness", Range(0,1)) = 0.5
    10.         _Metallic ("Glass Metallic", Range(0,1)) = 0.0
    11.         _LabelGlossiness ("Label Smoothness", Range(0,1)) = 0.5
    12.         _LabelMetallic ("Label Metallic", Range(0,1)) = 0.0
    13.     }
    14.     SubShader
    15.     {
    16.         Tags { "Queue"="Transparent" "RenderType"="Transparent" }
    17.         LOD 200
    18.  
    19.         // back face of glass & label
    20.         Cull Front
    21.         CGPROGRAM
    22.         #pragma surface surf Standard fullforwardshadows alpha:premul
    23.         #pragma target 3.0
    24.  
    25.         sampler2D _MainTex;
    26.  
    27.         struct Input
    28.         {
    29.             float2 uv_MainTex;
    30.         };
    31.  
    32.         half _Glossiness, _LabelGlossiness;
    33.         half _Metallic, _LabelMetallic;
    34.         fixed4 _Color;
    35.  
    36.         void surf (Input IN, inout SurfaceOutputStandard o)
    37.         {
    38.             // Albedo comes from a texture tinted by color
    39.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
    40.             fixed labelAlpha = c.a;
    41.  
    42.             o.Albedo = lerp(_Color.rgb, c.rgb, labelAlpha);
    43.             o.Metallic = lerp(_Metallic, _LabelMetallic, labelAlpha);
    44.             o.Smoothness = lerp(_Glossiness, _LabelGlossiness, labelAlpha);
    45.             o.Alpha = lerp(_Color.a, 1.0, labelAlpha);
    46.             o.Normal = half3(0,0,-1);
    47.         }
    48.         ENDCG
    49.  
    50.         // color tint pass
    51.         Pass {
    52.             Blend Zero SrcColor
    53.             Cull Back
    54.  
    55.             CGPROGRAM
    56.             #pragma vertex vert
    57.             #pragma fragment frag
    58.  
    59.             #include "UnityCG.cginc"
    60.  
    61.             struct v2f
    62.             {
    63.                 float4 pos : SV_POSITION;
    64.                 float3 worldNormal : TEXCOORD0;
    65.                 float3 worldPos : TEXCOORD1;
    66.             };
    67.  
    68.             v2f vert (appdata_full v)
    69.             {
    70.                 v2f o;
    71.                 o.pos = UnityObjectToClipPos(v.vertex);
    72.                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
    73.                 o.worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0));
    74.                 return o;
    75.             }
    76.  
    77.             half4 _Color, _ThickColor;
    78.             half _FakeThickness;
    79.  
    80.             half4 frag (v2f i) : SV_Target
    81.             {
    82.                 float3 viewDir = -normalize(i.worldPos - _WorldSpaceCameraPos.xyz);
    83.                 float ndotv = dot(normalize(i.worldNormal), viewDir);
    84.  
    85.                 float fresnel = pow(1.0 - saturate(ndotv), 2.0);
    86.  
    87.                 // return fresnel;
    88.  
    89.                 return lerp(_Color, _ThickColor, saturate(fresnel + _FakeThickness));
    90.             }
    91.             ENDCG
    92.         }
    93.  
    94.         // front face of glass & label
    95.         Cull Back
    96.         CGPROGRAM
    97.         #pragma surface surf Standard fullforwardshadows alpha:premul
    98.         #pragma target 3.0
    99.  
    100.         sampler2D _MainTex;
    101.  
    102.         struct Input
    103.         {
    104.             float2 uv_MainTex;
    105.         };
    106.  
    107.         half _Glossiness, _LabelGlossiness;
    108.         half _Metallic, _LabelMetallic;
    109.         fixed4 _Color;
    110.  
    111.         void surf (Input IN, inout SurfaceOutputStandard o)
    112.         {
    113.             // Albedo comes from a texture tinted by color
    114.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
    115.             fixed labelAlpha = c.a;
    116.  
    117.             o.Albedo = lerp(_Color.rgb, c.rgb, labelAlpha);
    118.             o.Metallic = lerp(_Metallic, _LabelMetallic, labelAlpha);
    119.             o.Smoothness = lerp(_Glossiness, _LabelGlossiness, labelAlpha);
    120.             o.Alpha = lerp(_Color.a, 1.0, labelAlpha);
    121.         }
    122.         ENDCG
    123.     }
    124.     FallBack "Diffuse"
    125. }
    upload_2020-7-28_14-12-37.png

    This does a transparent back face, multiply pass, and transparent front face with support for a label texture. The shadow is opaque still, and it doesn't do any kind of fake refraction or any attempt at faking diffuse scattering, but it's probably good enough. If you want to have normal maps and the like, that's up to you.
     
    Noors84 likes this.
  3. Noors84

    Noors84

    Joined:
    Jul 12, 2016
    Posts:
    14
    Thank you very much for the detailed explanations. Interesting to use the fresnel to fake the thickness. I'd probably have the interior mesh tho. I might want to use a grabpass for a fake refraction, depending of the target platform.
     
unityunity