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 !
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.
Shader "Custom/ColoredGlass"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_ThickColor ("Thick Glass Color", Color) = (0,0,0,1)
_FakeThickness ("Fake Wall Thickness", Range(0,1)) = 0.5
_MainTex ("Label Albedo & Alpha", 2D) = "black" {}
_Glossiness ("Glass Smoothness", Range(0,1)) = 0.5
_Metallic ("Glass Metallic", Range(0,1)) = 0.0
_LabelGlossiness ("Label Smoothness", Range(0,1)) = 0.5
_LabelMetallic ("Label Metallic", Range(0,1)) = 0.0
}
SubShader
{
Tags { "Queue"="Transparent" "RenderType"="Transparent" }
LOD 200
// back face of glass & label
Cull Front
CGPROGRAM
#pragma surface surf Standard fullforwardshadows alpha:premul
#pragma target 3.0
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
};
half _Glossiness, _LabelGlossiness;
half _Metallic, _LabelMetallic;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutputStandard o)
{
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
fixed labelAlpha = c.a;
o.Albedo = lerp(_Color.rgb, c.rgb, labelAlpha);
o.Metallic = lerp(_Metallic, _LabelMetallic, labelAlpha);
o.Smoothness = lerp(_Glossiness, _LabelGlossiness, labelAlpha);
o.Alpha = lerp(_Color.a, 1.0, labelAlpha);
o.Normal = half3(0,0,-1);
}
ENDCG
// color tint pass
Pass {
Blend Zero SrcColor
Cull Back
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert (appdata_full v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0));
return o;
}
half4 _Color, _ThickColor;
half _FakeThickness;
half4 frag (v2f i) : SV_Target
{
float3 viewDir = -normalize(i.worldPos - _WorldSpaceCameraPos.xyz);
float ndotv = dot(normalize(i.worldNormal), viewDir);
float fresnel = pow(1.0 - saturate(ndotv), 2.0);
// return fresnel;
return lerp(_Color, _ThickColor, saturate(fresnel + _FakeThickness));
}
ENDCG
}
// front face of glass & label
Cull Back
CGPROGRAM
#pragma surface surf Standard fullforwardshadows alpha:premul
#pragma target 3.0
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
};
half _Glossiness, _LabelGlossiness;
half _Metallic, _LabelMetallic;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutputStandard o)
{
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
fixed labelAlpha = c.a;
o.Albedo = lerp(_Color.rgb, c.rgb, labelAlpha);
o.Metallic = lerp(_Metallic, _LabelMetallic, labelAlpha);
o.Smoothness = lerp(_Glossiness, _LabelGlossiness, labelAlpha);
o.Alpha = lerp(_Color.a, 1.0, labelAlpha);
}
ENDCG
}
FallBack "Diffuse"
}
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.
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.
that shader turned to pink!