ご質問者さんの懸念はもっともだと思います。不透明オブジェクト用のシェーダーを全部透明用に作り替えるのはかなり手間がかかりそうですし、前後関係を正確に表現できなくなって「半透明になるオブジェクトの描画順がぐちゃぐちゃになる問題の解決法 - プログラミング関連の覚え書き集 」の冒頭にある図のような不具合を起こしてしまうかもしれません。
最初から透明キューで描画されるように意図されたMeshRenderer
(描画モードがFadeに設定されているものなど)であればテラシュールブログさんのSortingLayer
を使うのがいいかと思います。
一方、今回のキューブのように不透明なオブジェクトの場合はソーティングレイヤーを設定しても効果を発揮しないため、SortingLayer
は削除してかまわないでしょう(残しておいても動作に影響はないだろうとは思いますが...)。
その上で、追記依頼欄で申し上げた「スプライトやTextMesh Proのマテリアルに手を加えることになる気配がします」とはちょっと異なるアプローチになってしまいすみませんが、以下のような作戦を考えてみました。
まず、特に対策を行っていない状態でご質問者さんのシーンをまねて下図のようにシーンをセットアップしました。カメラから見て遠くから順にTextMesh Pro、星のスプライト、キューブが並んでいます。
キューブのマテリアルはDefault-Materialのままですので、通常どおり不透明キューで描画されます。TextMesh Proのソーティングレイヤーは前面描画用の「Front」に変更していますが、ご質問者さんがお示しいただいた図と同様に星よりも前面、キューブよりも背面に描画されています。
Frame Debuggerで描画過程を確認してみますと、まず不透明キューでキューブが描画され、引き続き透明キューでのソーティングレイヤーの設定に従い先に星、次にテキストが描画されています。
キューブより後に描画される透明キューオブジェクトがキューブの背後に隠れることができるのはデプステストのおかげですが、今回のような強制的に前面に描画させたいケースでは逆に邪魔になってしまうでしょう。追記依頼欄の「マテリアルに手を加える」というのは、スプライトやTextMesh Proのマテリアルをデプステストを行わないタイプに改造することを想定していたのですが、それもわりと面倒そうだと思い直しまして、代わりに「不透明オブジェクトより前面に出したいオブジェクトの描画に入る前に、デプスバッファを最遠点で塗りつぶす」という方針に変更しました。
まず下記のようなデプス塗りつぶしシェーダーを作成しました。ファークリップ面いっぱいに四角形を描きますが、「デプステストは常に通過」「デプスを書き込む」「カラーバッファへの書き込みは行わない」と設定することで、目には見えませんがデプスを最深値に書き換える挙動をさせています。
なお、シェーダーがビルド時に除外されないようにするにはAlways Included Shaders のリストに追加しておく必要があるかもしれません。
ShaderLab
1 Shader "Effect/DepthEraser"
2 {
3 Properties
4 {
5 }
6 SubShader
7 {
8 Tags { "Queue" = "Transparent" "RenderType" = "Transparent" }
9
10 Cull Off
11 ZWrite On
12 ZTest Always
13 ColorMask 0
14
15 Pass
16 {
17 CGPROGRAM
18 #pragma vertex vert
19 #pragma fragment frag
20
21 #include "UnityCG.cginc"
22
23 float4 vert(float4 vertex : POSITION) : SV_POSITION
24 {
25 return float4(vertex.xy, 1.0 - saturate(UNITY_NEAR_CLIP_VALUE), 1.0);
26 }
27
28 fixed4 frag() : SV_Target
29 {
30 return 0.0;
31 }
32 ENDCG
33 }
34 }
35 }
そして塗りつぶしオブジェクト用スクリプトを用意しました。
C#
1 using UnityEngine ;
2 # if UNITY_EDITOR
3 using UnityEditor ;
4 # endif
5
6 [ ExecuteAlways ]
7 public class DepthEraser : MonoBehaviour
8 {
9 private const string DepthEraserShaderName = "Effect/DepthEraser" ;
10 private static Shader depthEraserShader ;
11
12 // レイヤー名の選択にはテラシュールブログさんのSortingLayerAttributeを使いました
13 // Set Sorting Layer
14 // Copyright (c) 2014 Tatsuhiko Yamamura
15 // Released under the MIT license
16 // http://opensource.org/licenses/mit-license.php
17 [ SortingLayer ] public string sortingLayer = "Default" ;
18 public int orderInLayer ;
19
20 private Material material ;
21 private Mesh mesh ;
22 private MeshFilter meshFilter ;
23 private MeshRenderer meshRenderer ;
24 private int previousOrderInLayer = int . MaxValue ;
25 private string previousSortingLayer ;
26
27 private void Update ( )
28 {
29 if ( depthEraserShader == null )
30 {
31 depthEraserShader = Shader . Find ( DepthEraserShaderName ) ;
32 if ( depthEraserShader == null )
33 {
34 Debug . LogError ( $ "{DepthEraserShaderName} not found." ) ;
35 this . enabled = false ;
36 return ;
37 }
38 }
39
40 if ( this . material == null )
41 {
42 this . material = new Material ( depthEraserShader )
43 {
44 name = "DepthEraser" ,
45 hideFlags = HideFlags . HideAndDontSave
46 } ;
47 }
48
49 if ( this . mesh == null )
50 {
51 this . mesh = new Mesh
52 {
53 name = "DepthEraser" ,
54 hideFlags = HideFlags . HideAndDontSave ,
55 vertices = new [ ]
56 {
57 new Vector3 ( - 1.0f , - 1.0f , 0.0f ) ,
58 new Vector3 ( - 1.0f , 1.0f , 0.0f ) ,
59 new Vector3 ( 1.0f , - 1.0f , 0.0f ) ,
60 new Vector3 ( 1.0f , 1.0f , 0.0f )
61 } ,
62 triangles = new [ ]
63 {
64 0 , 1 , 2 ,
65 3 , 2 , 1
66 }
67 } ;
68 }
69
70 if ( this . meshFilter == null )
71 {
72 this . meshFilter = this . gameObject . AddComponent < MeshFilter > ( ) ;
73 this . meshFilter ! . hideFlags = HideFlags . HideAndDontSave ;
74 this . meshFilter ! . sharedMesh = this . mesh ;
75 }
76
77 if ( this . meshRenderer == null )
78 {
79 this . meshRenderer = this . gameObject . AddComponent < MeshRenderer > ( ) ;
80 this . meshRenderer ! . hideFlags = HideFlags . HideAndDontSave ;
81 this . meshRenderer ! . sharedMaterial = this . material ;
82 }
83
84 this . meshRenderer ! . bounds = new Bounds (
85 Vector3 . zero ,
86 new Vector3 ( Mathf . Infinity , Mathf . Infinity , Mathf . Infinity ) ) ;
87 if ( this . orderInLayer != this . previousOrderInLayer )
88 {
89 this . previousOrderInLayer = this . orderInLayer ;
90 this . meshRenderer . sortingOrder = this . orderInLayer ;
91 }
92
93 if ( this . sortingLayer != this . previousSortingLayer )
94 {
95 this . previousSortingLayer = this . sortingLayer ;
96 this . meshRenderer . sortingLayerName = this . sortingLayer ;
97 }
98 }
99
100 private void OnDestroy ( )
101 {
102 if ( Application . isPlaying )
103 {
104 Destroy ( this . material ) ;
105 Destroy ( this . mesh ) ;
106 }
107 else
108 {
109 DestroyImmediate ( this . material ) ;
110 DestroyImmediate ( this . mesh ) ;
111 }
112 }
113
114 # if UNITY_EDITOR
115 [ MenuItem ( "GameObject/Depth Eraser" , false , 11 ) ]
116 public static void Create ( )
117 {
118 var eraserObject = new GameObject ( "Depth Eraser" ) ;
119 Undo . RegisterCreatedObjectUndo ( eraserObject , "Create Depth Eraser" ) ;
120 Undo . AddComponent < DepthEraser > ( eraserObject ) ;
121 }
122 # endif
123 }
「GameObject」→「Depth Eraser」でオブジェクトを作成し、Sorting LayerとOrder In Layerを最前面にしたいオブジェクトより手前になるようにします。さしあたりSorting LayerはFront、Order In Layerは-1としました。
その結果、TextMesh Proの描画前にデプス塗りつぶし用の見えないオブジェクトが描画され、TextMesh Proがキューブに遮られなくなりました。