[Unity]カメラ/プレイヤー間のオブジェクトの半透明化

キャラクターが壁で隠れてしまう問題

 カメラワークによって、カメラ/プレイヤー間にオブジェクトがあるとプレイヤーがオブジェクトによって見えなくなってしまいます。
 今回カメラ/プレイヤー間のオブジェクトを半透明にする方法を解説します。

参考ブログ

【C#スクリプト】
1.[Unity] カメラに被った建物を半透明化する(@susasaki(佐々木 昴))
2.[Unity] MaterialPropertyBlockを用いて、オブジェクトを半透明化にする(@susasaki(佐々木 昴)
 →大体1,2でほぼ解決
【シェーダー】
3.Unity 半透明描画するサーフェースシェーダー(@beinteractive(Yoshihiro Shindo))
 →1,2で透明にできるシェーダーを指定して、シェーダーを変更していたが、そのシェーダーがなかったため、3を参考にシェーダーを作成。

処理内容

1.前フレームで透明にしているオブジェクトを配列へ保存
2.カメラ→プレイヤーの足元へrayを飛ばす
3.下記の手順を取得したオブジェクト分行う

(1)rayで取得したオブジェクトが以下の条件に合うか判定
・タグが"FadeObj"かどうか
・rayが当たったポイント-カメラ間の距離<カメラ-プレイヤーの足元間の距離
(2)上記を満たしている場合、color.aの値を1以下にし、シェーダーをStandardから透明のシェーダーに変更する。その後透明にしているオブジェクトリストへ追加。
4.カメラ→プレイヤーの頭頂部へrayを飛ばす
5.3と同様
6.前フレームで透明にしているオブジェクトと現フレームで透明にしているオブジェクトを比較、差分を不透明に戻す

詳細

1.カメラ側にアタッチするスクリプト(C100002b)

参照ブログ1.を参考にして作成してます。
(クラス名は個人的な管理手法に基づいたものなので気にしないでください。)
このスクリプトでカメラからrayを発射。条件にあったオブジェクトアタッチされたC100002aのメソッドを呼び出して半透明にしてます。

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class C100002b : MonoBehaviour
{
    [SerializeField] private GameObject player;     //インスペクター上でプレイヤーを接続しておく
    Vector3 tergetPosition;
    float tergetOffsetYFoot = 0.1f; //rayを飛ばす方向のオフセット(足元の方)
    float tergetOffsetYHead = 1.6f; //rayを飛ばす方向のオフセット(頭頂部の方)

    public GameObject[] prevRaycast;
    public List<GameObject> raycastHitsList_ = new List<GameObject>();

    void Update()
    {
        //カメラ→足元間のオブジェクトを半透明化している
        prevRaycast = raycastHitsList_.ToArray();   //前フレームで透明にしているオブジェクト(リスト)を配列prevRayCastに出力
        raycastHitsList_.Clear();                   //前フレームで透明にしているオブジェクト(リスト)を初期化?消去?
        tergetPosition = player.transform.position; //tergetPositionにPlayerのpositionを格納
        tergetPosition.y += tergetOffsetYFoot;      //tergetPositionのy軸(高さ方向)にオフセットを反映。ここでは足元の高さに合わせている。(足元の値をそのままいれると真下の床が透明になることがあったためオフセットした。)
        Vector3 _difference = (tergetPosition - this.transform.position);   //カメラ位置→tergetPositionへのベクトルを取得
        RayCastHit(_difference);                    //↓のメソッドを参照。rayを飛ばして条件に合うものを半透明にして、raycastHitListに追加している。

        //カメラ→頭頂部間のオブジェクトを半透明化している
        tergetPosition.y += tergetOffsetYHead;      //tergetPositionのy軸(高さ方向)にオフセットを反映。ここでは頭の高さに合わせている。
        _difference = (tergetPosition - this.transform.position);   //カメラ位置→tergetPositionへのベクトルを取得
        RayCastHit(_difference);

        //ヒットしたGameObjectの差分を求めて、今回衝突しなかったオブジェクトを不透明に戻す
        foreach (GameObject _gameObject in prevRaycast.Except<GameObject>(raycastHitsList_))    //prevRaycastとraycastHitList_との差分を抽出してる。
        {
            C100002a noSampleMaterial = _gameObject.GetComponent<C100002a>();
            if (_gameObject != null)
            {
                noSampleMaterial.NotClearMaterialInvoke();
            }

        }
    }

    //rayを飛ばして条件に合うものを半透明にして、raycastHitListに追加している。
    public void RayCastHit(Vector3 __difference)
    {
        Vector3 _direction = __difference.normalized;           //カメラ-ターゲット間のベクトルの正規ベクトルを抽出

        Ray _ray = new Ray(this.transform.position, _direction);//Rayを発射
        RaycastHit[] rayCastHits = Physics.RaycastAll(_ray);    //Rayにあたったオブジェクトをすべて取得

        foreach (RaycastHit hit in rayCastHits)
        {
            float distance = Vector3.Distance(hit.point, transform.position);       //カメラ-rayがあたった場所間の距離を取得
            if (distance < __difference.magnitude)      //カメラ-rayがあたった場所間の距離とカメラ-ターゲット間の距離を比較。(この比較を行わないとPlayerの奥側のオブジェクトも透明になる。)
            {
                C100002a c100002a = hit.collider.GetComponent<C100002a>();
                if (
                hit.collider.tag == "FadeObj")          //タグを確認
                {
                    c100002a.ClearMaterialInvoke();                 //透明にするメソッドを呼び出す。
                    raycastHitsList_.Add(hit.collider.gameObject);  //hitしたgameobjectを追加する
                }
            }
        }
    }
}

2.透明にするオブジェクト側にアタッチするスクリプト(C100002a)

参照ブログ2.を参考にして作成してます。
ほぼ参照ブログ同様。私が作成したunityプロジェクトにTransparent/Diffuse ZWriteシェーダーがなぜか存在しなかったので、自作のシェーダーを指定してます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class C100002a : MonoBehaviour
{

    private Color color = Color.white;      //colorはwhiteをベースに使用します。

    //親 子オブジェクトを格納。
    private MeshRenderer[] meshRenderers;
    private MaterialPropertyBlock m_mpb;

    public MaterialPropertyBlock mpb
    {
        get { return m_mpb ?? (m_mpb = new MaterialPropertyBlock()); }
    }

    void Awake()
    {
        //子オブジェクトと親オブジェクトのmeshrendererを取得
        meshRenderers = this.GetComponentsInChildren<MeshRenderer>();
    }
    public void ClearMaterialInvoke()
    {
        color.a = 0.25f;

        mpb.SetColor(Shader.PropertyToID("_Color"), color);
        for (int i = 0; i < meshRenderers.Length; i++)
        {
            meshRenderers[i].GetComponent<Renderer>().material.shader = Shader.Find("Custom/C100002");
            meshRenderers[i].SetPropertyBlock(mpb);
        }
    }
    public void NotClearMaterialInvoke()
    {
        color.a = 1f;
        mpb.SetColor(Shader.PropertyToID("_Color"), color);
        for (int i = 0; i < meshRenderers.Length; i++)
        {
            meshRenderers[i].GetComponent<Renderer>().material.shader = Shader.Find("Standard");
            meshRenderers[i].SetPropertyBlock(mpb);
        }
    }
}

ちなみにシェーダーをわざわざ変えているのですが、これには理由があります。
最初からStandardシェーダーでRendaring ModeをTransparentにしておけばシェーダーを変えなくてもオブジェクトを透明にすることはできました。
しかし(原因はよくわかっていないのですが、)透明なオブジェクト同士が重なっているときに、奥側のオブジェクトが手前に表示されてしまうことがあったので、不透明オブジェクトのシェーダーは透明にしないシェーダー(Standard/Rendaring Mode:Opaque)にしました。

すべてのシェーダーをStandard(Rendaring Mode:Transparent)の場合
画面左側の壁は不透明の設定だが、奥側のオブジェクトが手前に描画されてしまっている。

3.シェーダー

参照ブログ3.を参考に作成。
作成>シェーダー>標準サーフェスシェーダーで作成したものから、タグの追記と#pragmaを書き換えました。

Shader "Custom/C100002"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "RenderType"="Transparent"
        }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma  surface surf Standard fullforwardshadows alpha

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma  target 3.0

        //#pragma surface surf Lambert alpha

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma  instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

以上です。

以下完成例。


この記事が気に入ったらサポートをしてみませんか?