【UnityShader】投影テクスチャシャドウ #110
投影テクスチャシャドウとは
前回行った投影テクスチャマッピングの応用技術となります。
影用のカメラを用意し、光源と同じ方向に設置します。
そして、ターゲットとなるオブジェクトの影だけを描画しカメラのtergetTextureとします。
そうすることにより、影のみ描画することができます。
実装
Unity上
Scene上に以下のオブジェクトを配置します。
- 影用のカメラ
- 影を落とすオブジェクト
- 影を受けるオブジェクト
影用のカメラのInspector
オブジェクト
影を落とすオブジェクトには、何もしなくて良いです。
影を受けるオブジェクトには、今回制作したマテリアルを刺してください。
Scene
スクリプト
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ファイティングのようなものが起きてしまいます。
原因は調べても不明でしたので、詳しい方がいらっしゃれば、教えてくださると幸いです。
HDRに関しては以下で解説しています。
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を見るとシャドウマップに書き込まれている内容がわかります。