Unityでカメラ前のオブジェクトを半透明にして主人公を見えるようにする

今回はUnityで操作キャラクターを写すカメラの前に他のキャラクターや障害物等が入った時に半透明にして操作キャラクターを見えるような機能を作っていきます。

通常であればカメラの前に他のキャラクター等がいると以下のように操作キャラクターが見えなくなってしまいます。

他のキャラクターがかぶって操作キャラクターが見えなくなる

今回の機能を取り付けると以下のようにカメラ前の他のキャラクター等が半透明になって操作キャラクターが見えるようになります。

今回の機能ではUnityのShaderGraphを使用しますので、プロジェクトはURPやHDRP等のスクリタブルレンダーパイプラインである必要があります。

今回使用しているUnityのバージョンは2021.3.7f1でShaderGraphのバージョンは12.1.7を使用しています。

ShaderGraphに関しては以下の記事も参照してください。

Unityのシェーダーグラフについて
Unityのシェーダーグラフの使い方と個々のノードについて見ていきます。
スポンサーリンク

他のキャラクター用のシェーダーグラフの作成

まず最初に他のキャラクター用のシェーダーグラフを作成していきます。

今回操作キャラクター、他のキャラクター共にスタンダードアセットのEthanを使用しますが、他のキャラクターでも大丈夫です。

Assetsフォルダ内で右クリックからCreate→Shader Graph→URP→Lit Shader Graphを選択し、名前をOtherCharacterとします。

OtherCharacterシェーダーグラフの作成

わたくしの場合はURPを使用していますのでURPのLit Shader Graphを選択していますが、HDRPの場合は適宜変更してください。

OtherCharacterをダブルクリックしてシェーダーグラフのウインドウを開きます。

Blackboardの+を押し、Texture 2Dを選択し、名前をMainTexture、Floatを選択し、名前をAlphaとします。

MainTextureとAlphaプロパティの作成

以下のようにプロパティが出来ました。

MainTextureとAlphaプロパティが出来た

MainTextureプロパティをシェーダーグラフウインドウの何もない所にドラッグ&ドロップし、MainTextureのポート部分を右にドラッグ&ドロップして出てきたウインドウの検索窓にsampleと入力し、Sample Texture 2Dノードを選択します。

MainTextureプロパティをSample Texture2Dノードに接続する

Sample Texture 2DのRGBAポートをFragmentのBase Colorに接続します。

Sample Texture 2DのRGBAポートをBase Colorに接続

ここまできたらSave Assetボタンを押して保存します。

Save Assetボタンを押して保存する

他のキャラクター用のマテリアルの作成

他のキャラクター用のマテリアルを作成し、先ほど作ったシェーダーグラフを設定し、キャラクターに設定してみます。

Assetsフォルダ内で右クリックからCreate→Materialを選択し、名前をOtherCharacterとします。

OtherCharacterマテリアルの作成

OtherCharacterマテリアルを選択し、インスペクタのShaderからShader Graphs→OtherCharacterを選択します(もしくはOtherCharacterシェーダーグラフをOtherCharacterマテリアルにドラッグ&ドロップ)。

シェーダーをOtherCharacterに変更する

OtherCharacterマテリアルのSurface InputsのMain TextureにEthanOcclusionを設定します(キャラクターのテクスチャ)。

MainTextureにEthanOcclusionを設定する

ヒエラルキーにEthanを配置し、名前をOtherCharacterとし、子のEthanBodyとEthanGlassesのSkinnedMeshRendererコンポーネントのMaterialsのElement0にOtherCharacterマテリアルを設定します。

EthanBodyとEthanGlassesのマテリアルにOtherCharacterマテリアルを設定する

これでキャラクターが通常どおり表示されていればOKです。

実際にはFragmentのNormalにEthanNormalマップを接続していないので通常とは異なっています。

ノーマルマップも設定したい場合は以下のようにノードを接続します。

EthanNormalマップを接続する

SampleTexture2DのTypeをNormalにし、TextureにはEthanNormalを設定し、FragmentのNormalに接続します。

今回は指定するTextureをプロパティにしていませんが、プロパティにしておくとインスペクタで設定出来ます。

新しく作ったマテリアルでキャラクターが通常通り表示された

これでとりあえずOtherCharacterシェーダーを設定したOtherCharacterマテリアルをキャラクターに設定し表示出来る事を確認出来ました。

キャラクターを半透明にする仕組みをシェーダーグラフに追加

キャラクターを通常通り表示出来たので、次は半透明にする仕組みを追加していきます。

OtherCharacterシェーダーグラフウインドウを開きFragmentを選択して、Graph InspectorでAlpha Clippingにチェックを入れます。

Graph InspectorでAlpha Clippingにチェックを入れる

チェックを入れるとFragmentにAlphaとAlpha Clip Thresholdが追加されます。

Alphaは透明度で、Alpha Clip Thresholdは指定したアルファ値のしきい値より下の場合は表示しない設定になります。

BlackboardのAlphaプロパティを何もない所にドラッグ&ドロップし、Alphaに接続します。

AlphaプロパティをAlphaに接続する

Alphaプロパティの値を直接Alphaに反映できるようにしました。

Save Assetボタンを押して保存したら、AlphaとAlpha Clip Thresholdの確認をしてみます。

ヒエラルキーのEthanBodyを選択し、インスペクタのマテリアルのSurface InputsのAlphaを0.49にしてみます。

Alphaプロパティを0.49にしたのでシェーダーグラフのAlphaの値はそのまま0.49になりAlpha Clip Thresholdの0.5より値が低くなります。

つまりクリップされてキャラクターが表示されなくなります。

試しにAlphaを0.49にし試してみる

キャラクターが完全に見えなくなると困るのでAlpha Clip Thresholdの値を変更します。

OtherCharacterシェーダーグラフの何もない所でSpaceキーを押して出てきたウインドウの検索窓にDitherと入力し選択します(DitherでなくNoise系でも出来る)。

DitherのInの値を1にし、OutをAlpha Clip Thresholdに接続します。

DitherをAlpha Clip Thresholdに接続する

以前は0.5の値をしきい値としていましたが、Ditherを使う事で所々白~黒の範囲になり、しきい値が変わります。

切り取られた部分があちこちにあるので実際のキャラクターでも所々切り取られ半透明になったかのように見えます(実際には透明ではなく間が切り取られていてそう見える)。

試しにEthanBodyのマテリアルのSurface InputsのAlphaを0.3にして確認してみると以下のように半透明のように見えます。

キャラクターが半透明になったように見える

操作キャラクターの配置

OtherCharacterが出来たので次は自分で操作するキャラクターを配置します。

キャラクターは移動が出来るようにし、カメラはCinemachineのFree Lock Cameraを使って視点の変更が出来るようにしておきます。

キャラクターの移動に関しては以下の記事を参照してください。

Unityでキャラクターを移動する為の準備と概要(スクリプトを書く前に)
Unityでキャラクターを移動させるスクリプトを書く前に準備しておく事と、移動させる為に必要な事を考えていきます。
Unityでキャラクターの移動をプログラミングしてみる
Unityで3Dキャラクターモデルを配置し、キャラクターをCharacterControllerの機能を使って移動させるようなプログラミングをしてみます。

Cinemachineに関しては以下の記事を参照してください。

UnityのCineMachineを使ってカメラの動きを作成してみる
UnityのCineMachineを使うと通常のゲームシーンのカメラとしてだけでなく、イベントシーンのカメラワークの作成も簡単に作成出来ます。そんなCineMachineを使っていくつかのカメラワークを作成してみます。
UnityのCinemachineの仮想カメラが優秀過ぎる
UnityのCinemachineの仮想カメラを使うとキャラクターを追従させるのも簡単に出来ます。

とりあえずキャラクターの移動が出来て、カメラの動きがあれば問題ないです。

とりあえず移動とカメラ操作が出来る状態

ヒエラルキーの操作キャラクターを選択し、インスペクタでTagをPlayerに設定しておきます。

操作キャラクターのTagをPlayerにする

他のキャラクターがカメラに近づいた時にAlpha値を操作する

操作キャラクターが出来たので次にカメラがOtherCharacterキャラクターに近づいた時にOtherCharacterシェーダーグラフのAlphaプロパティの値を操作して半透明にするという処理を作っていきます。

シェーダーグラフ内でカメラとオブジェクトの距離を計算することも出来ますが、今回はC#スクリプトを使って操作することにします。

EraseTheCharacterスクリプトを作成し、ヒエラルキーのOtherCharacterゲームオブジェクトにドラッグ&ドロップして設定します。

characterTransformは操作キャラクターのTransformです。

renderersは自身の子の全てのレンダラーを入れます。

materialsは自身の子の全てのマテリアルを入れます。

cameraDistanceは自身がカメラとどのくらいの距離に入ったら半透明にするかです。

alphaOffsetはアルファ値に関するオフセット値です。

mainCameraはメインカメラです。

distanceはカメラ位置と自身の距離。

distanceBetweenCharacterAndCameraは操作キャラクターとカメラの距離。

StartメソッドでFindWithTagを使ってPlayerタグを設定された操作キャラクターのTransformを取得しcharacterTransformに入れています。

mainCameraにはCamera.mainを使ってMainCameraを設定されたカメラを取得し入れています。

GetComponentsInChildrenを使って自身の子のRenderer(今回の場合はSkinnedMeshRenderer)を全て取得し、その各マテリアルをmaterialsに入れています。

ここではEthanBodyとEthanGlassesに設定しているマテリアルが同じなので、ループを使って処理をせず、ひとつのマテリアルにRendererの.sharedMaterialプロパティを使って取得し変更するだけで出来ますが、sharedMaterialを使った場合はUnity実行後もマテリアルに加えた変化が反映されてしまうので今回は各materialを使ってmaterialsにそれぞれ入れています。

Updateメソッドでは自身とカメラとの距離を計算しdistanceに入れ、操作キャラクターとカメラの距離をdistanceBetweenCharacterAndCameraに入れています。

自身とカメラの距離が操作キャラクターとカメラの距離よりも短い時は操作キャラクターとカメラの間に自身がいるので半透明にする必要があります。

さらに自身とカメラの距離が指定したcameraDistanceよりも短いという条件も加えます。

条件に合致していたら半透明にする必要があるので各マテリアルの_Alphaの値を設定します。

注意が必要なのが、プロパティの操作にはAlphaプロパティの参照を使用する必要があります。

プロパティの参照を使ってスクリプトから操作する

設定する値はMathf.InverseLerpを使ってdistanceが0~cameraDistance+alphaOffsetの範囲のどこになるかを計算し設定しています。

Mathf.InverseLerpはdistanceが0~cameraDistance+alphaOffsetのどの範囲にあるかを0~1の値で返しますが、範囲外の場合はその限界値の値を返します。

自身とカメラの距離が指定した距離より長い場合は自身を半透明にする必要がないので_Alphaを1にしてクリッピングをしないようにします。

常に計算するのを避ける

先ほどのスクリプトだと毎フレームUpdateメソッドが呼ばれカメラ位置と自身の距離、操作キャラクターとカメラの距離を計算しています。

スクリプトを取り付けたゲームオブジェクトが増えると処理が遅くなる可能性もある為、オブジェクトのレンダラーがカメラの範囲内にいる時だけ距離の計算をするように変更します。

isVisibleがカメラの範囲内にレンダラーを持つオブジェクトがいるかいないかを入れます。

UpdateメソッドではそのisVisibleがfalseの時(自身がカメラの範囲外にいる)はreturnでそれ以降の処理をしないようにします。

カメラの範囲内かそうでないかを判断しているのはMonoBehaviourのメソッドのOnBecameVisible(カメラの範囲内にある時に実行される)とOnBecameInVisible(カメラの範囲内を出ると実行される)でしています。

ただオブジェクトがカメラに映っていなくてもOnBecameVisibleが呼ばれることもあります。

ここら辺は以下の記事も参照してみてください。

Unityでカメラの範囲内にゲームオブジェクトがある時だけ何らかの処理をしたい
Unityでカメラの範囲内にゲームオブジェクトがある時だけ時間の計測をする処理を作っていきます。

改変したスクリプトはレンダラーを持つゲームオブジェクト個々に設定するようにする必要があるので(OnBecameVisibleとOnBecameInVisibleはスクリプトが取り付けられたレンダラーで呼び出される為)、今回の場合はOtherCharacterのEraseTheCharacterスクリプトを削除し、子のEthanBodyとEthanGlassesの2つにEraseScriptを取り付けて使用する必要があります。

オブジェクトのレンダラーがカメラの範囲内かどうかで判断する他に、コライダを使って操作キャラクターがコライダの範囲内に入っているかどうかで計算処理をするということも出来ます。

機能の確認

これで機能が出来ました。

今回は他のキャラクター用のマテリアルとしてOtherCharacterを作りましたが、確認の為にヒエラルキーにCubeを配置しOtherCharacterマテリアルを設定し、EraseTheCharacterスクリプト(もしくはEraseScript)を取り付けて障害物も同じように出来る事を確認します。

確認の為にCubeゲームオブジェクトを配置し設定する

OtherCharacterの子のEthanBodyを選択し、マテリアルのSurface InputsのAlphaを1に戻しておきます。

終わりに

オブジェクトを半透明にすると影もクリッピングされてしまいます。

これに関しては以下の動画の4:05辺りにあるカスタム関数を使用してAlphaを設定すると影の変更はされません。

https://www.youtube.com/watch?v=qk6nrQihcOQ

今回の機能はPS4のニーアレプリカントにあったので作ってみました。

タイトルとURLをコピーしました