知識0からのUnityShader勉強

知識0からのUnityShader勉強

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

【UnityShader】投影テクスチャシャドウ #110

投影テクスチャシャドウとは

前回行った投影テクスチャマッピングの応用技術となります。

soramamenatan.hatenablog.com


影用のカメラを用意し、光源と同じ方向に設置します。
そして、ターゲットとなるオブジェクトの影だけを描画しカメラのtergetTextureとします。
そうすることにより、影のみ描画することができます。

https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F235561%2Fb99b289a-9d62-0e12-f5e6-720f2dfd1db2.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&w=1400&fit=max&s=20ab981b24df5796741542229c7b6107

投影テクスチャシャドウを実作してみた - Qiita:より引用


実装

Unity上

Scene上に以下のオブジェクトを配置します。

  • 影用のカメラ
  • 影を落とすオブジェクト
  • 影を受けるオブジェクト

影用のカメラのInspector

f:id:soramamenatan:20210717160626p:plain

オブジェクト

影を落とすオブジェクトには、何もしなくて良いです。
影を受けるオブジェクトには、今回制作したマテリアルを刺してください。

f:id:soramamenatan:20210717160616p:plain

Scene

f:id:soramamenatan:20210717160621p:plain

スクリプト

using UnityEngine;

[RequireComponent(typeof(Camera))]
public class ProjectionTextureShadow : MonoBehaviour
{
    [Header ("Setting")]
    [SerializeField]
    private Camera _camera;
    [SerializeField]
    private int _renderTextureSize = 512;
    [SerializeField]
    private Material _material;
    [SerializeField] 
    private Transform _lightTransform;
    
    
    private int _matrixVpId;
    private int _textureId;
    private int _posId;
    private RenderTexture _renderTexture;

    private void Start()
    {
        SetPropertyId();
        CameraSettings();
    }
    
    /// <summary>
    /// PropertyIdの設定
    /// </summary>
    private void SetPropertyId()
    {
        _matrixVpId = Shader.PropertyToID("_ShadowProjectorMatrixVP1");
        _textureId = Shader.PropertyToID("_ShadowProjectorTexture1");
        _posId = Shader.PropertyToID("_ShadowProjectorPos1");
    }

    /// <summary>
    /// カメラの設定
    /// </summary>
    private void CameraSettings()
    {
        // 先にレンダリングさせるために、小さい値に
        _camera.depth = -10000;
        _camera.clearFlags = CameraClearFlags.Color;
        _camera.backgroundColor = Color.white;
        // 点滅を防ぐ
        _camera.allowHDR = false;
    }

    /// <summary>
    /// カメラがシーンのレンダリングを開始する前に呼ばれる
    /// </summary>
    private void OnPreRender()
    {
        if (_renderTexture == null)
        {
            UpdateSettings();
        }
        SetMaterialParam();
    }
    
    /// <summary>
    /// 設定の更新
    /// </summary>
    private void UpdateSettings()
    {
        ReleaseTexture();
        SetLightPosition();
        UpdateRenderTexture();
    }
    
    /// <summary>
    /// テクスチャの解放
    /// </summary>
    private void ReleaseTexture()
    {
        if (_renderTexture == null)
        {
            return;
        }
        _material.SetTexture(_textureId, null);
        RenderTexture.ReleaseTemporary(_renderTexture);
        _renderTexture = null;
        _camera.targetTexture = null;
    }
    
    /// <summary>
    /// 位置の設定
    /// </summary>
    private void SetLightPosition()
    {
        var objTransform = transform;
        _lightTransform.position = objTransform.position;
        _lightTransform.rotation = objTransform.rotation;
    }
    
    /// <summary>
    /// RenderTextureの更新
    /// </summary>
    private void UpdateRenderTexture()
    {
        _renderTexture = RenderTexture.GetTemporary(_renderTextureSize, _renderTextureSize, 16, RenderTextureFormat.ARGB32);
        _camera.targetTexture = _renderTexture;
    }
    
    /// <summary>
    /// マテリアルのパラメータ設定
    /// </summary>
    private void SetMaterialParam()
    {
        var viewMatrix = _camera.worldToCameraMatrix;
        var projectionMatrix = GL.GetGPUProjectionMatrix(_camera.projectionMatrix, true);
        _material.SetMatrix(_matrixVpId, projectionMatrix * viewMatrix);
        _material.SetTexture(_textureId, _renderTexture);
        _material.SetVector(_posId, GetProjectorPos());
    }
    
    /// <summary>
    /// プロジェクターの座標取得
    /// </summary>
    /// <returns></returns>
    private Vector4 GetProjectorPos()
    {
        Vector4 projectorPos;
        // Orthographic
        // _ObjectSpaceLightPosを参考に、wに0が入っていたらOrthographicの前方方向とみなす
        if (_camera.orthographic)
        {
            projectorPos = transform.forward;
            projectorPos.w = 0;
        }
        // Perspective
        else
        {
            projectorPos = transform.position;
            projectorPos.w = 1;
        }
        return projectorPos;
    }
    
    private void OnDestroy()
    {
        ReleaseTexture();
    }
}

シェーダー

Shader "Unlit/ProjectionTextureShadow"
{
    Properties 
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float4 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float4 projectorSpacePos : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float3 worldNormal : TEXCOORD2;
                float2 uv : TEXCOORD3;
            };
            
            sampler2D _ShadowProjectorTexture1;
            float4x4 _ShadowProjectorMatrixVP1;
            float4 _ShadowProjectorPos1;
            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.projectorSpacePos = ComputeScreenPos(mul(mul(_ShadowProjectorMatrixVP1, unity_ObjectToWorld), v.vertex));
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // Projection座標に変換
                // tex2DProj(_ProjectorTexture, i.projectorSpacePos)と同義
                i.projectorSpacePos.xyz /= i.projectorSpacePos.w;
                float4 projectorTex = tex2D(_ShadowProjectorTexture1, i.projectorSpacePos.xy);
                // カメラの範囲外(0 ~ 1)は描画しない
                fixed3 isOut = step((i.projectorSpacePos - 0.5) * sign(i.projectorSpacePos), 0.5);
                float alpha = isOut.x * isOut.y * isOut.z;
                // プロジェクターから見て、裏側の面には描画しない
                // Perspective  : _ShadowProjectorPos.xyz - i.worldPos
                // Orthographic : -_ShadowProjectorPos.xyz
                alpha *= step(-dot(lerp(-_ShadowProjectorPos1.xyz, _ShadowProjectorPos1.xyz - i.worldPos, _ShadowProjectorPos1.w), i.worldNormal), 0);
                fixed4 col = tex2D(_MainTex, i.uv);
                return lerp(1, 1 - projectorTex.a, alpha) * col;
            }
            ENDCG
        }
    }
}


解説

基本は前回のものと同じですので、違う箇所のみ解説します。

CameraSettings()

カメラの初期設定を行っているメソッドになります。

private void CameraSettings()
{
    // 先にレンダリングさせるために、小さい値に
    _camera.depth = -10000;
    _camera.clearFlags = CameraClearFlags.Color;
    _camera.backgroundColor = Color.white;
    // 点滅を防ぐ
    _camera.allowHDR = false;
}

depth

depthは深度という意味ですが、カメラですと描画順で使用されています。
今回複数のカメラがある可能性を考慮して低い値にしています。

allowHDR

HDRを無効にしています。
HDRを有効にしていると、Zファイティングのようなものが起きてしまいます。
原因は調べても不明でしたので、詳しい方がいらっしゃれば、教えてくださると幸いです。

f:id:soramamenatan:20210717162534g:plain

HDRに関しては以下で解説しています。

soramamenatan.hatenablog.com

UpdateRenderTexture()

RenderTextureの更新を行っているメソッドになります。

private void UpdateRenderTexture()
{
    _renderTexture = RenderTexture.GetTemporary(_renderTextureSize, _renderTextureSize, 16, RenderTextureFormat.ARGB32);
    _camera.targetTexture = _renderTexture;
}

今回、シャドウマップとしてRenderTextureを生成するので第三引数のdepthBufferに値を入れています。

フラグメントシェーダー

最後のreturn箇所になります。

return lerp(1, 1 - projectorTex.a, alpha) * col;

alphaはカメラの投影手法によって切り替わるため0or1になります。
ですのでlerpで何もなしかRenderTextureを描画するかを決めています。


結果

影を受けるオブジェクトに、影が落ちているのがわかります。 また、Camera Previewを見るとシャドウマップに書き込まれている内容がわかります。

f:id:soramamenatan:20210717164224p:plain


参考サイト様

light11.hatenadiary.com

qiita.com