Unity シェーダーチュートリアル 屈折表現
シェーダー
水面やワイングラスなどを作る時、
透明シェーダを当ててリフレクションプローブで反射を出しますよね。
しかし、それだけでは全くそれらしく見えません。
厚みのないガラス窓ならそれで十分ですが、厚みを感じさせたい場合は
屈折表現を入れる事でグッとクオリティがアップします。
背景の画を取得する
ゲームエンジンにおける屈折は、プリレンダーのレイトレースのように、
まともに計算をして表現するものではありません。
背景の画をオフセットさせることで、屈折しているかのように見せるのです。
Unity のシェーダーではオブジェクトの背景の画を、テクスチャとして簡単に取得することができます。
SubShader {
Tags {
"Queue" = "Transparent"
"RenderType" = "Transparent"
}
GrabPass {}
CGPROGRAM
sampler2D _GrabTexture;
ENDCG
}
SubShader 内で GrabPass {} と記述するだけで、
以降の CGPROGRAM~ENDCG 内で Sampler2D _GrabTexture; で取得できます。
ここで注意が必要なのは、Tags の Queue です。
他のオブジェクトの描画がすべて終わった後で画を取得をしないと意味がないので、
Transparent の設定になっています。
左が "Queue" = "Transparent" で、右が "Queue" = "Geometry" です。
他のオブジェクトが描画しきっていない状態で取得しているので、大部分が黒くなっています。
そして、取得した背景画をシェーディングなしで張り付ければ、屈折の準備完了です。
_GrabTexture を貼り付ける方法
以下が、_GrabTexture を張り付けるミニマムなシェーダーコードです。
入力構造体の ScreenPos
_GrabTexture を展開する時は、他のテクスチャと同様に tex2D を使いますが、
UV の指定の仕方が違います。
struct Input {
float4 ScreenPos;
};
void surf (Input IN, inout SurfaceOutputStandard o) {
float2 grabUV = (IN.screenPos.xy / IN.screenPos.w);
fixed3 grab = tex2D(_GrabTexture, grabUV).rgb;
}
入力構造体で、float4 型の ScreenPos を取得し、
tex2D の UV として使うときに、xy を w で割って指定しています。
これは難しく考えずに、「こういう風にやれば良いんだ」と思って流してください。
そしてこのシェーダーを当てて見たのが下図です。
おかしいです。
シーンビューでは問題ないのに、ゲームビューでは上下が反転しているようです。
この様に、様々な要因によって上下が反転する事があるので、
その場合は UV の上下を反転させます。
void surf (Input IN, inout SurfaceOutputStandard o) {
float2 grabUV = (IN.screenPos.xy / IN.screenPos.w);
grabUV.y = grabUV.y * -1 + 1;
fixed3 grab = tex2D(_GrabTexture, grabUV).rgb;
}
これでゲームビューの反転がなくなりました。
(ただしシーンビューでは反転しています。)
Vertex シェーダーを組み合わせれば、こんなややこしい反転は起きないのですが、
それは今後説明するとして、今はこれでいきます。
シェーディングしない
Surface Shader はライトの影響を受けるので、
_GrabTexture を o.Albedo に繋げてしまっては、陰影が出てしまいます。
なので、o.Emisssion に繋げて、ライトの影響が表面化しないようにします。
本来の意味として、o.Emission は発光に使用されるものなので、何か変な感じがするかもしれませんが、
そこは気にせずに流してください。
そもそもライトの影響を受けないように、Vertex/Fragment シェーダーで記述する手もありますが、
(Standard Assets の GrassRefraction はそうなってる)
それだと、ライトによるスペキュラ(光源の反射)や、
リフレクションプローブによる反射も出なくなり、質感が分からなくなってしまうので、
今回は敢えて Surface Shader でやっています。
ノーマルマップで歪ませる
「背景画をオフセットさせて、屈折を表現する」
というのはつまり、「画が歪んでいれば屈折に見える」と言うことです。
GrabPass{} で取得した背景画のテクスチャを、ノーマルマップを使って歪ませます。
今回は例として球体でやっているので、
上下反転している方がそれっぽいかなと思い、あえてそのままにしています。
ノーマルマップを使って法線を変移させているわけではありません。
ではなぜ、ノーマルマップを使うのか? 都合が良いからです。
ノーマルマップの中を除いて見ると、簡単に言えば、
Rチャンネルの 0 ~ 1 で横方向、Gチャンネルの 0 ~ 1 で縦方向の変移を表します。
そして、シェーダー内で 0 ~ 1 の情報を -1 ~ 1 に変換して法線を変移させます。
(0 では法線が変移しない)
つまり、「形が歪んでるところは、背景も屈折して歪むんじゃね?」という事で、
-1 ~ 1 に変換されたRGチャンネルを使って grabUV をオフセットします。
(-1 は 左に移動、0 はそのまま、1 は右に移動)
float2 grabUV = (IN.screenPos.xy / IN.screenPos.w);
fixed2 normalTex = UnpackNormal(tex2D(_NormalTex, IN.uv_NormalTex)).rg;
grabUV += normalTex * _Distortion;
_Distortion によって歪みの強さを調整できるようにしています。
おわりに
上記が基本的な屈折のやり方です。これに色々な味付けをして見てください。
ノーマルマップの数を増やし、UVをクスロールさせて波を表現してみたり、
本来の形状の法線を利用して屈折させてみたり、
表現したい目的の画によって、色々な手法を組みあわせて屈折させてみましょう。
[…] 屈折表現 | Tsumiki Tech Times […]
[…] 屈折表現 | Tsumiki Tech Times […]