Unityシェーダーチュートリアル
ガラスの表現手法いろいろ
シェーダー
こんなステージで作っていきたいと思います。
完全なBaked Lightmap、リアルタイムライトは無し。
反射
ガラスに反射は必須なので Reflection Probe で反射を入れることになりますが、
よりガラスっぽく見せるためのコツがあります。
それは、反射素材に強烈な光源を入れる事です。
背面とのブレンド方法を乗算にする
赤いガラスを作ってみたいと思います。まず単純に Surface Shader の色を赤に変更すると。。。
全く変化がありません。
これは、部屋のライティングが理由です。
冒頭で述べましたが、この部屋は完全にライトマップ化されており、
リアルタイムライトが1つもありません。
Surface Shader はライトに影響するシェーダーなので、
ライトが無い = 真っ暗 となり、いくら色を変更したところで画に影響が出ないのです。
そこでポイントライトを1つ置いてみます。
ポイントライトで照らされている部分は赤くなりました。。。
ですが、これは求めている画ではありません。
学生の頃を思い出してみてください。洗脳の時代です。
英単語を暗記するために赤い透明シートを誰もが使っていたでしょう。
シート越しに景色を見ると、どんな色であっても全て赤くなっていたはずです。
色付きガラスは、背景に対してその色が乗算されていなければならないのです。
まさにあの赤い暗記シートです。
これを実現するためにはシェーダーを Vertex / Fragment で記述する必要があります。
Shader "Custom/Shader_Glass01" { | |
Properties { | |
_Color ("Color" , Color ) = (1, 1, 1, 1) | |
_Smoothness ("Smoothness", Range(0, 1)) = 1 | |
_Alpha ("Alpha" , Range(0, 1)) = 0 | |
} | |
SubShader { | |
Tags { | |
"Queue" = "Transparent" | |
"RenderType" = "Transparent" | |
} | |
// 背景とのブレンド法を「乗算」に指定 | |
Blend DstColor Zero | |
Pass { | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment frag | |
#include "UnityCG.cginc" | |
half3 _Color; | |
half _Alpha; | |
struct appdata { | |
float4 vertex : POSITION; | |
}; | |
struct v2f { | |
float4 vertex : SV_POSITION; | |
}; | |
v2f vert (appdata v) { | |
v2f o; | |
o.vertex = UnityObjectToClipPos(v.vertex); | |
return o; | |
} | |
fixed4 frag (v2f i) : SV_Target { | |
return fixed4(lerp(_Color, 0, _Alpha), 1); | |
} | |
ENDCG | |
} | |
// V/FシェーダーはReflection Probeに反応しないので | |
// 反射だけを描画するSurface Shaderを追記する | |
CGPROGRAM | |
#pragma target 3.0 | |
#pragma surface surf Standard alpha | |
half _Smoothness; | |
struct Input { | |
fixed null; | |
}; | |
void surf (Input IN, inout SurfaceOutputStandard o) { | |
o.Smoothness = _Smoothness; | |
} | |
ENDCG | |
} | |
FallBack "Standard" | |
} |
両面を描画する
両面を描画する事によって、厚みがより分かりやすくなります。
内側の反射には色が乗り、強度を調整できるようにします。
Shader "Custom/Shader_Glass02" { | |
Properties { | |
_Color ("Color" , Color ) = (1, 1, 1, 1) | |
_Smoothness ("Smoothness" , Range(0, 1)) = 1 | |
_Alpha ("Alpha" , Range(0, 1)) = 0 | |
_InRefl ("Inner Reflectivity", Range(0, 1)) = 1 | |
} | |
SubShader { | |
Tags { | |
"Queue" = "Transparent" | |
"RenderType" = "Transparent" | |
} | |
// 背景とのブレンド法を「乗算」に指定 | |
Blend DstColor Zero | |
Cull Front | |
Pass { | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment frag | |
#include "UnityCG.cginc" | |
half3 _Color; | |
half _Alpha; | |
struct appdata { | |
float4 vertex : POSITION; | |
}; | |
struct v2f { | |
float4 vertex : SV_POSITION; | |
}; | |
v2f vert (appdata v) { | |
v2f o; | |
o.vertex = UnityObjectToClipPos(v.vertex); | |
return o; | |
} | |
fixed4 frag (v2f i) : SV_Target { | |
return fixed4(lerp(_Color, 0, _Alpha), 1); | |
} | |
ENDCG | |
} | |
// V/FシェーダーはReflection Probeに反応しないので | |
// 反射だけを描画するSurface Shaderを追記する | |
CGPROGRAM | |
#pragma target 3.0 | |
#pragma surface surf Standard alpha | |
half _Smoothness; | |
half _InRefl; | |
struct Input { | |
fixed null; | |
}; | |
void surf (Input IN, inout SurfaceOutputStandard o) { | |
o.Smoothness = _Smoothness; | |
// 反射強度は o.Occlusion で調整できる | |
o.Occlusion = _InRefl; | |
} | |
ENDCG | |
Cull Back | |
Pass { | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment frag | |
#include "UnityCG.cginc" | |
half3 _Color; | |
half _Alpha; | |
struct appdata { | |
float4 vertex : POSITION; | |
}; | |
struct v2f { | |
float4 vertex : SV_POSITION; | |
}; | |
v2f vert (appdata v) { | |
v2f o; | |
o.vertex = UnityObjectToClipPos(v.vertex); | |
return o; | |
} | |
fixed4 frag (v2f i) : SV_Target { | |
return fixed4(lerp(_Color, 0, _Alpha), 1); | |
} | |
ENDCG | |
} | |
// V/FシェーダーはReflection Probeに反応しないので | |
// 反射だけを描画するSurface Shaderを追記する | |
CGPROGRAM | |
#pragma target 3.0 | |
#pragma surface surf Standard alpha | |
half _Smoothness; | |
struct Input { | |
fixed null; | |
}; | |
void surf (Input IN, inout SurfaceOutputStandard o) { | |
o.Smoothness = _Smoothness; | |
} | |
ENDCG | |
} | |
FallBack "Standard" | |
} |
カット面と角の透明度を下げる
カット面と角の透明度を下げることによって、
さらに厚み感や、ガラスとしての存在感が増します。
テクスチャでの描き分けはUV展開の必要があり面倒なので
頂点カラーをマスクとして利用します。以下の記事が参考になります。
Shader "Custom/Shader_Glass03" { | |
Properties { | |
_Color ("Color" , Color ) = (1, 1, 1, 1) | |
_Smoothness ("Smoothness" , Range(0, 1)) = 1 | |
_AlphaF ("Alpha (Face)" , Range(0, 1)) = 0 | |
_AlphaE ("Alpha (Edge)" , Range(0, 1)) = 0 | |
_InRefl ("Inner Reflectivity", Range(0, 1)) = 1 | |
} | |
SubShader { | |
Tags { | |
"Queue" = "Transparent" | |
"RenderType" = "Transparent" | |
} | |
// 背景とのブレンド法を「乗算」に指定 | |
Blend DstColor Zero | |
Cull Front | |
Pass { | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment frag | |
#include "UnityCG.cginc" | |
half3 _Color; | |
half _AlphaF; | |
half _AlphaE; | |
struct appdata { | |
float4 vertex : POSITION; | |
half color : COLOR; | |
}; | |
struct v2f { | |
float4 vertex : SV_POSITION; | |
half color : COLOR; | |
}; | |
v2f vert (appdata v) { | |
v2f o; | |
o.vertex = UnityObjectToClipPos(v.vertex); | |
o.color = v.color; | |
return o; | |
} | |
fixed4 frag (v2f i) : SV_Target { | |
return fixed4(lerp(lerp(_Color, 0, _AlphaF), lerp(_Color, 0, _AlphaE), i.color), 1); | |
} | |
ENDCG | |
} | |
// V/FシェーダーはReflection Probeに反応しないので | |
// 反射だけを描画するSurface Shaderを追記する | |
CGPROGRAM | |
#pragma target 3.0 | |
#pragma surface surf Standard alpha | |
half _Smoothness; | |
half _InRefl; | |
struct Input { | |
fixed null; | |
}; | |
void surf (Input IN, inout SurfaceOutputStandard o) { | |
o.Smoothness = _Smoothness; | |
// 反射強度は o.Occlusion で調整できる | |
o.Occlusion = _InRefl; | |
} | |
ENDCG | |
Cull Back | |
Pass { | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment frag | |
#include "UnityCG.cginc" | |
half3 _Color; | |
half _AlphaF; | |
half _AlphaE; | |
struct appdata { | |
float4 vertex : POSITION; | |
half color : COLOR; | |
}; | |
struct v2f { | |
float4 vertex : SV_POSITION; | |
half color : COLOR; | |
}; | |
v2f vert (appdata v) { | |
v2f o; | |
o.vertex = UnityObjectToClipPos(v.vertex); | |
o.color = v.color; | |
return o; | |
} | |
fixed4 frag (v2f i) : SV_Target { | |
return fixed4(lerp(lerp(_Color, 0, _AlphaF), lerp(_Color, 0, _AlphaE), i.color), 1); | |
} | |
ENDCG | |
} | |
// V/FシェーダーはReflection Probeに反応しないので | |
// 反射だけを描画するSurface Shaderを追記する | |
CGPROGRAM | |
#pragma target 3.0 | |
#pragma surface surf Standard alpha | |
half _Smoothness; | |
struct Input { | |
fixed null; | |
}; | |
void surf (Input IN, inout SurfaceOutputStandard o) { | |
o.Smoothness = _Smoothness; | |
} | |
ENDCG | |
} | |
FallBack "Standard" | |
} |
側面の透明度を下げる
光が目に届くまでにガラス内を通る距離や、屈折による影響等で、
面がカメラに正対していない箇所が暗くなることがります。
物理的に正しい計算をさせるのは無理ですが、
擬似的に表現してあげることで、よりガラスらしくなります。
Shader "Custom/Shader_Glass04" { | |
Properties { | |
_Color ("Color" , Color ) = (1, 1, 1, 1) | |
_Smoothness ("Smoothness" , Range(0, 1)) = 1 | |
_AlphaF ("Alpha (Face)" , Range(0, 1)) = 0 | |
_AlphaE ("Alpha (Edge)" , Range(0, 1)) = 0 | |
_AlphaR ("Alpha (Rim)" , Range(0, 1)) = 0 | |
_InRefl ("Inner Reflectivity", Range(0, 1)) = 1 | |
} | |
SubShader { | |
Tags { | |
"Queue" = "Transparent" | |
"RenderType" = "Transparent" | |
} | |
// 背景とのブレンド法を「乗算」に指定 | |
Blend DstColor Zero | |
Cull Front | |
Pass { | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment frag | |
#include "UnityCG.cginc" | |
half3 _Color; | |
half _AlphaF; | |
half _AlphaE; | |
struct appdata { | |
float4 vertex : POSITION; | |
half color : COLOR; | |
}; | |
struct v2f { | |
float4 vertex : SV_POSITION; | |
half color : COLOR; | |
}; | |
v2f vert (appdata v) { | |
v2f o; | |
o.vertex = UnityObjectToClipPos(v.vertex); | |
o.color = v.color; | |
return o; | |
} | |
fixed4 frag (v2f i) : SV_Target { | |
return fixed4(lerp(lerp(_Color, 0, _AlphaF), lerp(_Color, 0, _AlphaE), i.color), 1); | |
} | |
ENDCG | |
} | |
// V/FシェーダーはReflection Probeに反応しないので | |
// 反射だけを描画するSurface Shaderを追記する | |
CGPROGRAM | |
#pragma target 3.0 | |
#pragma surface surf Standard alpha | |
half _Smoothness; | |
half _InRefl; | |
struct Input { | |
fixed null; | |
}; | |
void surf (Input IN, inout SurfaceOutputStandard o) { | |
o.Smoothness = _Smoothness; | |
// 反射強度は o.Occlusion で調整できる | |
o.Occlusion = _InRefl; | |
} | |
ENDCG | |
Cull Back | |
Pass { | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment frag | |
#include "UnityCG.cginc" | |
half3 _Color; | |
half _AlphaF; | |
half _AlphaE; | |
half _AlphaR; | |
struct appdata { | |
float4 vertex : POSITION; | |
half color : COLOR; | |
float3 normal : NORMAL; | |
}; | |
struct v2f { | |
float4 vertex : SV_POSITION; | |
half color : COLOR; | |
float3 normal : NORMAL; | |
float3 viewDir : TEXCOORD0; | |
}; | |
v2f vert (appdata v) { | |
v2f o; | |
o.vertex = UnityObjectToClipPos(v.vertex); | |
o.color = v.color; | |
o.normal = v.normal; | |
o.viewDir = normalize(ObjSpaceViewDir(v.vertex)); | |
return o; | |
} | |
fixed4 frag (v2f i) : SV_Target { | |
float rimFallOff = lerp(1, dot(i.viewDir, i.normal), _AlphaR); | |
return fixed4(lerp(lerp(_Color, 0, _AlphaF), lerp(_Color, 0, _AlphaE), i.color) * rimFallOff, 1); | |
} | |
ENDCG | |
} | |
// V/FシェーダーはReflection Probeに反応しないので | |
// 反射だけを描画するSurface Shaderを追記する | |
CGPROGRAM | |
#pragma target 3.0 | |
#pragma surface surf Standard alpha | |
half _Smoothness; | |
struct Input { | |
fixed null; | |
}; | |
void surf (Input IN, inout SurfaceOutputStandard o) { | |
o.Smoothness = _Smoothness; | |
} | |
ENDCG | |
} | |
FallBack "Standard" | |
} |
カメラ空間法線による屈折
塊状のガラスは屈折を入れることでよりガラスらしさが出ます。
上記記事ではノーマルマップを使って擬似屈折させていますが、
今回はカメラ空間法線を使って擬似屈折させてみます。次はこんなシーンでやっていきます。
カメラ空間法線とは・・・これです。
面がカメラに正対していれば(R:0.5, G:0.5, B:1.0)となり、
横にズレていれば、R が 0 ~ 1
縦にズレていれば、G が 0 ~ 1
の値をとります。
このズレている箇所、つまりカメラに正対していない部分に
擬似屈折を入れることで、平面的な塊ガラスの厚み感を表現します。
角部分はさらに屈折を大きくします。
Shader "Custom/Shader_Glass05" { | |
Properties { | |
_Color ("Color" , Color ) = (1, 1, 1, 1) | |
_Smoothness ("Smoothness" , Range(0, 1)) = 1 | |
_AlphaF ("Alpha (Face)" , Range(0, 1)) = 0 | |
_AlphaE ("Alpha (Edge)" , Range(0, 1)) = 0 | |
_AlphaR ("Alpha (Rim)" , Range(0, 1)) = 0 | |
_DistortionF ("Distortion (Face)" , Range(0, 1)) = 0 | |
_DistortionE ("Distortion (Edge)" , Range(0, 1)) = 0 | |
} | |
SubShader { | |
Tags { | |
"Queue" = "Transparent" | |
"RenderType" = "Transparent" | |
} | |
// 屈折を入れる場合はシェーダー内で乗算させるので「通常の重ね設定」 | |
Blend SrcAlpha OneMinusSrcAlpha | |
// 背景画をテクスチャとして取得 | |
GrabPass{ "" } | |
Cull Front | |
Pass { | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment frag | |
#include "UnityCG.cginc" | |
sampler2D _GrabTexture; | |
half3 _Color; | |
half _AlphaF; | |
half _AlphaE; | |
half _DistortionF; | |
half _DistortionE; | |
struct appdata { | |
float4 vertex : POSITION; | |
half color : COLOR; | |
float3 normal : NORMAL; | |
}; | |
struct v2f { | |
float4 vertex : SV_POSITION; | |
half color : COLOR; | |
float3 VSnormal : TEXCOORD0; | |
float4 screenPos : TEXCOORD1; | |
float distance : TEXCOORD2; | |
}; | |
v2f vert (appdata v) { | |
v2f o; | |
o.vertex = UnityObjectToClipPos(v.vertex); | |
o.color = v.color; | |
o.VSnormal = COMPUTE_VIEW_NORMAL; | |
o.screenPos = ComputeScreenPos(o.vertex); | |
o.distance = distance(v.vertex, _WorldSpaceCameraPos); | |
return o; | |
} | |
fixed4 frag (v2f i) : SV_Target { | |
// 擬似屈折を入れる | |
half3 offset = i.VSnormal * (lerp(_DistortionF, _DistortionE, i.color) * (1 / i.distance)); | |
half3 grab = tex2D(_GrabTexture, (i.screenPos.xy / i.screenPos.w) + offset); | |
return fixed4(grab * lerp(lerp(_Color, 0, _AlphaF), lerp(_Color, 0, _AlphaE), i.color), 1); | |
} | |
ENDCG | |
} | |
Cull Back | |
Pass { | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment frag | |
#include "UnityCG.cginc" | |
half3 _Color; | |
half _AlphaF; | |
half _AlphaE; | |
struct appdata { | |
float4 vertex : POSITION; | |
half color : COLOR; | |
}; | |
struct v2f { | |
float4 vertex : SV_POSITION; | |
half color : COLOR; | |
}; | |
v2f vert (appdata v) { | |
v2f o; | |
o.vertex = UnityObjectToClipPos(v.vertex); | |
o.color = v.color; | |
return o; | |
} | |
fixed4 frag (v2f i) : SV_Target { | |
// 透明度だけ描画 | |
// 方向の違いによる厚みに大きな差が無いので側面不透明度はいらない | |
return fixed4(0, 0, 0, lerp(_AlphaF, _AlphaE, i.color)); | |
} | |
ENDCG | |
} | |
// 擬似屈折で内面が見えないので、表面のみ描画 | |
Cull Back | |
// V/FシェーダーはReflection Probeに反応しないので | |
// 反射だけを描画するSurface Shaderを追記する | |
CGPROGRAM | |
#pragma target 3.0 | |
#pragma surface surf Standard alpha | |
half _Smoothness; | |
half _AlphaR; | |
struct Input { | |
float3 viewDir; | |
}; | |
void surf (Input IN, inout SurfaceOutputStandard o) { | |
o.Smoothness = _Smoothness; | |
o.Alpha = 0; | |
} | |
ENDCG | |
} | |
FallBack "Standard" | |
} |
物理的には屈折や全反射によって景色が反転して見えたりもするので、
GrabPassで取得した背景を左右反転させてみるのも良いかもしれない。
あくまでも擬似的な方法なので実際の塊ガラスの見た目とはかなり違いますね。
ただ、雰囲気としては良いのではないでしょうか。