【UnityShader】氷のようなShader #7
前回の成果
#pragmaが理解できた。
今回やること
おもちゃラボ様を参考に氷のようなシェーダーを作ります。
もうシェーダーらしさ出てますね!
ソースコード
Shader "Custom/ice" { SubShader { Tags { "Queue" = "Transparent"} LOD 200 CGPROGRAM #pragma surface surf Standard alpha:fade #pragma target 3.0 struct Input { float3 worldNormal; float3 viewDir; }; void surf(Input IN, inout SurfaceOutputStandard o) { o.Albedo = fixed4(1,1,1,1); float alpha = 1 - (abs(dot(IN.viewDir, IN.worldNormal))); o.Alpha = alpha * 1.5f; } ENDCG } FallBack "Diffuse" }
こちらが今回のソースコードになります。
Tags { "Queue" = "Transparent"}
や
#pragma surface surf Standard alpha:fade
から透過処理を行いたいことが理解できると思います。
しかし、
void surf(Input IN, inout SurfaceOutputStandard o) { o.Albedo = fixed4(1,1,1,1); float alpha = 1 - (abs(dot(IN.viewDir, IN.worldNormal))); o.Alpha = alpha * 1.5f; }
ここを理解するのが今回の目的となります。
abs?? dot??
アルファ値の値を変更しようとしてるのはわかるけど...。
absとdotとは何者なんだ、となったと思います。
abs
absoluteのことです、つまり絶対値です。
使用方はいたって簡単で、
// xには絶対値を取得したい数字を入れる
abs(x);
以上です。
簡単に言えば数値のマイナスを取って返してくれるもので、例を挙げると
int x = 10; float y = -15.5; // 10が返ってくる abs(x); // 15.5が返ってくる abs(y);
このような感じです。
dot
内積のことです。
内積を使用することによって、worldNomal(1ピクセル毎の法線)とviewDir(カメラからの視線)の2つのベクトルが交わる角度を取得することができます。
この画像をみると一目瞭然ですね。
dotを使用すると返ってくる値はかっこ内の数値です。
2ベクトル間の角度 | 返ってくる値 |
---|---|
0° | 1 |
90° | 0 |
180° | -1 |
この返ってくる値をアルファ値に代入して、透明感を出そうという考え方となります。
今回の成果
これで氷シェーダーの完成です!
ソースコードにコメント
Shader "Custom/ice" { SubShader { // 半透明オブジェクトを一番最後に描画する Tags { "Queue" = "Transparent"} // あとで理解する LOD 200 // あとで理解する CGPROGRAM // 透過させる #pragma surface surf Standard alpha:fade // あとで理解する #pragma target 3.0 // input構造体 struct Input { // 1ピクセル毎の法線 float3 worldNormal; // カメラからの視線 float3 viewDir; }; // surf関数 void surf(Input IN, inout SurfaceOutputStandard o) { // ベースカラーの変更 o.Albedo = fixed4(1,1,1,1); // アルファ値を法線と視線の2つのベクトルから取得 float alpha = 1 - (abs(dot(IN.viewDir, IN.worldNormal))); // 見た目の調整で*1.5 o.Alpha = alpha * 1.5f; } // あとで理解する ENDCG } // あとで理解する FallBack "Diffuse" }
今回は以上となります!
次回もおもちゃラボ様を参考にシェーダーを書いていこうと思います!
おまけ
あとで理解するものが多くなってきたので少しずつ紹介していきます。
LOD
LODとはLevel of Detailの略で、LOD値がこちらで決めたしきい値以下の場合に、そのシェーダーを使用するものです。
例えば、iPhone5とiPhoneXRがあるとします。
2機間でスペックが違うので、iPhone5では描画コストが低いシェーダーを、iPhoneXRでは描画コストが高いシェーダーを描画させたいときにLODを使用することで、シェーダーを切り替えることができます。
やってみる
このサイト様を参考に実装していこうと思います。
まずはシェーダー側のソースコードです。
// LOD値の高い順からSubShaderを記述する必要がある Shader "Custom/LOD" { Properties{} SubShader { Tags { "RenderType"="Opaque" } LOD 10 Pass { CGPROGRAM #pragma vertex vert_img #pragma fragment frag #include "UnityCG.cginc" fixed4 frag (v2f_img i) : SV_Target { return fixed4(0, 0, 1, 1); } ENDCG } } SubShader { Tags { "RenderType"="Opaque" } LOD 5 Pass { CGPROGRAM #pragma vertex vert_img #pragma fragment frag #include "UnityCG.cginc" fixed4 frag (v2f_img i) : SV_Target { return fixed4(1, 0, 0, 1); } ENDCG } } SubShader { Tags { "RenderType"="Opaque" } LOD 3 Pass { CGPROGRAM #pragma vertex vert_img #pragma fragment frag #include "UnityCG.cginc" fixed4 frag (v2f_img i) : SV_Target { return fixed4(0, 1, 1, 1); } ENDCG } } SubShader { Tags { "RenderType"="Opaque" } LOD 1 Pass { CGPROGRAM #pragma vertex vert_img #pragma fragment frag #include "UnityCG.cginc" fixed4 frag (v2f_img i) : SV_Target { return fixed4(0, 1, 0, 1); } ENDCG } } }
このシェーダーで注目してほしいのは、LODの値によって、色が変化しているところです。
次に、Script側のソースコードです。
using UnityEngine; public class LODTest : MonoBehaviour { // 色を変更させるオブジェクトのレンダラー [SerializeField] private Renderer objRenderer; // カメラの位置 [SerializeField] private Transform _cameraTransform; void Update() { // シェーダにLOD値を代入 objRenderer.sharedMaterial.shader.maximumLOD = (int) Mathf.Abs(_cameraTransform.localPosition.z); } }
このLODTest.csを空のGameObjectにアタッチしてあげてください。
そして、
objRendererにはオブジェクトを、
cameraTransformにはカメラをアタッチしてください。
Unityを実行してカメラのz座標を変更すると・・・
色が変わっていますね!
z座標が1未満になると1未満のLODがないので、描画されなくなってしまいます。
おまけ終わり。