【unity】何とかしてリアルな影を軽量化したい


影というものは、簡単に言うとキャラクターを二回描画しているようなもの。しかもライトを一つや二つ増やすだけで結構大変なことになる。まして輪郭がボケたりもさせるなんて、少なくともPS4以下の端末では重すぎて動かないのではないだろうか?


画像1

(ぱくたそ様より拝借)

影は通常、影の母体と投影先が遠くなればなるほどボケていく。上記画像のように、影の足元の方はくっきりとしているが、頭のほうは若干ブラーがかかっている。勿論光源にもよる。

リアルタイムレンダリングでもゴリゴリに計算すれば技術的にできないということはないだろう。だがしかし、ブラーをかけるのに複数回描画しなくてはならない(はず、確か)ので、ぼかせばぼかすほど画面が重くなる。

画像2

例えばこちらはFF7リメイクのとあるワンシーン。やはりFF7Rほどのハイクオリティの作品でも影がボケないのは致し方ない。

画像3

こちらはAPEX。画質が悪いがこれもぼかしは入っていない。ハイスピードで動き回るゲームではそこまでのリアリティは必要ないと言えばない。


だがしかし、何とかここに挑戦できないだろうか?


そこで思いついたのがライティングをせず、影用のテクスチャをいじる方法。

画像4

照明の位置に合わせて、影が出来る範囲をカバーできる大きさにポリゴンを用意し、あらかじめキャラクタ―の影を書いたテクスチャを貼り、あとはキャラのポジションに合わせてUVをずらすという方法。

お気付きかとは思うが、これではモーションに合わせて影の形が変わらないので、そこはまだ工夫しきれていない。

しかしこれの良い所は、複数の影を同時に生成しても全く重くないという点である。


試しにやってみた。

まずは影を作成

画像5

ちょっとわざとらしくぼかしてある。

用意したシェーダーはこちら

Shader "Unlit/ScrollShader"
{
   Properties
   {
       [NoScaleOffset]_MainTex ("Texture", 2D) = "white" {}
	  [HideInInspector]_OffsetX ("OffsetX", float) = 0
     [HideInInspector]_OffsetY ("OffsetY", float) = 0
       _Shadow ("Shadow Intensity", Range(0,1)) = 1
       _Scale ("Scale", float) = 1
   }
   SubShader
   {
       Tags { "RenderType"="Opaque" }
       Blend Zero SrcColor
       LOD 100

       Pass
       {
           CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
           #include "UnityCG.cginc"

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

           struct v2f
           {
               float2 uv : TEXCOORD0;
               float4 vertex : SV_POSITION;
           };

           sampler2D _MainTex;
           float4 _MainTex_ST;
		   float _OffsetX;
		  float _OffsetY;
           float _Shadow;
           float _Scale;

           v2f vert (appdata v)
           {
               v2f o;
               o.vertex = UnityObjectToClipPos(v.vertex);
               o.uv = TRANSFORM_TEX(v.uv, _MainTex);
               return o;
           }

           fixed4 frag (v2f i) : SV_Target
           {
			  float2 scroll = float2(_OffsetX, _OffsetY) * _Scale;
               fixed4 col = tex2D(_MainTex, i.uv + scroll) + _Shadow;
               return col;
           }
           ENDCG
       }
   }
}

オフセットのX、Yにはそれぞれキャラクターのポジションの値が入る。スケールは、ポリゴンの大きさとキャラのポジション値の比率を各々計算して入れてほしい。影の強度もいじれるようにした。(0と1が逆だがとりあえず気にしないこととする)

これだけだと影がスクロールしないので、影オブジェクトに下記のスクリプトを当てる。

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

public class PositionSender : MonoBehaviour
{
   private Renderer rend;
   public GameObject Character;
   Vector3 CharaPos;

   void Start ()
   {
       rend = GetComponent<Renderer>();
   }

   void Update()
   {
       Transform CharaTransform = Character.gameObject.GetComponent<Transform>();
       CharaPos = CharaTransform.position;
       rend.material.SetFloat("_OffsetX", CharaPos.x);
       rend.material.SetFloat("_OffsetY", CharaPos.z);
   }
}

中身は至って単純、使用しているマテリアルにポジション値を渡しているだけだ。つまり、当然上記シェーダーを使用しているマテリアルを割り当てていないと機能しない。

画像7

それからテクスチャをクランプにするのを忘れないようにしよう。スクロールすると無限に影が出てきてしまう。

描画結果はこちら

画像6

静止状態ではまあそりゃいい見た目だよなという感じ。
キャラを動かすとこの影のままついてくるし、キャラがアイドリングモーションをしているのにすら追従しないから違和感しかない。
しかし個人的には可能性を感じた。気になる方は是非試してみてほしい。そしてより良い方法があればぜひとも共有していただけると嬉しい。

今回はここまで。私も引き続きこれをさらにバージョンアップさせていく。
それでは。

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