知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【UnityShader】ワイプエフェクト #28

前回の成果

画面をモノクロ・セピア調にすることができた。

soramamenatan.hatenablog.com


今回やること

今回は、ワイプエフェクトを制作していきます。
ワイプエフェクトとは、だんだん視野が狭くなっていくエフェクトのことです。

nn-hokuson.hatenablog.com


事前準備

SceneにCameraを配置して、今回制作するScriptをアタッチします。
そして、ScriptのSerializeFieldにMaterialをアタッチしてください。

f:id:soramamenatan:20191111134606p:plain


Shaderのソースコード

Shader "Unlit/wipeEffect" {
    Properties {
        _Radius("Radius", Range(0, 2)) = 2
        _MainTex("MainTex", 2D) = "" {}
    }
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag
            #include "UnityCG.cginc"

            float _Radius;
            sampler2D _MainTex;

            fixed4 frag(v2f_img i) : COLOR {
                fixed4 c = tex2D(_MainTex, i.uv);
                i.uv -= fixed2(0.5f, 0.5f);
                float4 projectionSpaceUpperRight = float4(1, 1, UNITY_NEAR_CLIP_VALUE, _ProjectionParams.y);
                float4 viewSpaceUpperRight = mul(unity_CameraInvProjection, projectionSpaceUpperRight);
                i.uv.x *= viewSpaceUpperRight.x / viewSpaceUpperRight.y;
                if(distance(i.uv, fixed2(0, 0)) < _Radius) {
                    return c;
                }
                return fixed4(0, 0, 0, 1);
            }
            ENDCG
        }
    }
}

この部分が謎です。

float4 projectionSpaceUpperRight = float4(1, 1, UNITY_NEAR_CLIP_VALUE, _ProjectionParams.y);
float4 viewSpaceUpperRight = mul(unity_CameraProjection, projectionSpaceUpperRight);
i.uv.x /= viewSpaceUpperRight.x / viewSpaceUpperRight.y;


Scriptのソースコード

using UnityEngine;
public class PostEffects : MonoBehaviour {
    [SerializeField]
    Material _material;
    void OnRenderImage(RenderTexture source, RenderTexture dest) {
        Graphics.Blit(source, dest, _material);
    }
}

Scriptは他のポストエフェクトと同じとなっているので、解説は省きます。
では、Shaderの解説に移っていきます。


i.uv -= fixed2(0.5f, 0.5f);

uv値の原点が左下にありワイプエフェクトを中央に表示させたいため、u座標とv座標を共に-0.5しています。

i.uv.x *= viewSpaceUpperRight.x / viewSpaceUpperRight.y;

これはアスペクト比を出しています。

アスペクト比を考慮しなかった場合

f:id:soramamenatan:20191111135931p:plain


アスペクト比を考慮した場合

f:id:soramamenatan:20191111140019p:plain

これで何をしているか紐解いていきます。


この計算式でプロジェクション空間での右上を出しています。

float4 projectionSpaceUpperRight = float4(1, 1, UNITY_NEAR_CLIP_VALUE, _ProjectionParams.y);

qiita.com


UNITY_NEAR_CLIP_VALUE

これはnear clipping planeに定義されている値です。
Direct3Dでは0.0、OpenGLでは-1.0が値に入ります。
near clipping planeとは、描画できる最短距離のことです

f:id:soramamenatan:20191115150401p:plain

Fall 2016, CS-116A Lecture: Camera and Clipping Plane:より引用


_ProjectionParams

これはfloat4で、カメラのclippng planeの情報が格納されている変数です。

xyzw 用途
x 反転した射影行列でレンダリングしている場合は-1.0、それ以外は1.0
y カメラのnear plane
z カメラのfar plane
w 1 / far plane

docs.unity3d.com


次に

float4 viewSpaceUpperRight = mul(unity_CameraInvProjection, projectionSpaceUpperRight);

について説明します。


unity_CameraInvProjection

これは、カメラのプロジェクション行列の逆行列になります。

プロジェクション行列とは、近くのものは大きく、遠くのものは小さく描画している行列です。
詳しくはこちらのサイト様を参考にしてください。

marupeke296.com


unity_CameraProjectionとUNITY_MATRIX_Pとの違い

少し話が逸れてしまうので、ここは飛ばしてくださっても構いません。

unity_CameraInvProjectionは、プロジェクション行列の逆行列と説明しました。
unity_CameraProjectionは、プロジェクション行列を取得できるものです。
しかし、UNITY_MATRIX_Pという変数も存在していてこれは現在のプロジェクション行列を表しているものです。

この2つの違いは対象となるものが何かの違いです。
unity_CameraProjectionはスクリーンスペースの描画、UNITY_MATRIX_Pはオブジェクトへの描画となっています。

forums.hololens.com


これで取れる理由

プロジェクション行列は、

f:id:soramamenatan:20191118160940g:plain

その72 ビュー・射影変換行列が持つ情報を抜き出そう:より引用

となっていて、今回使用しているプロジェクション行列の逆行列は、

f:id:soramamenatan:20191118161042g:plain

その72 ビュー・射影変換行列が持つ情報を抜き出そう:より引用

となっています。

各要素の説明

名前 説明
W 画面の幅(ピクセル)
H 画面の高さ(ピクセル)
θ 視野角(field of view)
Zn Near
Zf Far


ここまで来ればあとは行列の計算をするだけです。

{\displaystyle 
    \begin{pmatrix}
      a_{11} & a_{12} & a_{13} & a_{14} \\
      a_{21} & a_{22} & a_{23} & a_{24} \\
      a_{31} & a_{32} & a_{33} & a_{34} \\
     a_{41} & a_{42} & a_{43} & a_{44}
    \end{pmatrix}

    \begin{pmatrix}
      b_1  \\
      b_2 \\
      b_3 \\
     b_4
    \end{pmatrix}

=

    \begin{pmatrix}
      a_{11} b_1 + a_{12} b_2 + a_{13} b_3 + a_{14} b_4 \\
      a_{21} b_1 + a_{22} b_2 + a_{23} b_3 + a_{24} b_4 \\
      a_{31} b_1 + a_{32} b_2 + a_{33} b_3 + a_{34} b_4 \\
     a_{41} b_1 + a_{42} b_2 + a_{43} b_3 + a_{44} b_4
    \end{pmatrix}
}

から、

{\displaystyle 
    \begin{pmatrix}
      \frac{W}{H}tan(\frac{\theta}{2}) & 0 & 0 & 0 \\
      0 & \tan(\frac{\theta}{2}) & 0 & 0 \\
      0 & 0 & 0 & -\frac{Z_{f}-Z_{n}}{Z_{n}Z_{f}} \\
     0 & 0 & 1 & \frac{1}{Z_{n}}
    \end{pmatrix}

    \begin{pmatrix}
      1  \\
      1 \\
      1 \\
     1
    \end{pmatrix}
}

(xとyしか使用しないので、zとwは1とする)となり、

{\displaystyle 
    \begin{pmatrix}
    \frac{W}{H}tan(\frac{\theta}{2}) \\
    \tan(\frac{\theta}{2}) \\
    -\frac{Z_{f}-Z_{n}}{Z_{n}Z_{f}} \\
    \frac{1}{Z_{n}}
    \end{pmatrix}
}

となります。
i.uv.x *= viewSpaceUpperRight.x / viewSpaceUpperRight.y; をしているので、

{\displaystyle 
    \frac{\frac{W}{H}tan(\frac{\theta}{2})}{
    \tan(\frac{\theta}{2})}
}

となるので、解像度を求めることができます。

結果

Radiusの値を変更することで、ワイプエフェクトを実装することができました。

f:id:soramamenatan:20191119105654g:plain

今回はここまでとなります。
ご視聴ありがとうございました。