##キューブの各面に落ちる影を描かせるための対策
まずターゲットのキューブ(ご質問者さんの場合はライトの真下にある赤いキューブでしょうか)に下記スクリプトをアタッチし、6つの面がUV空間上で重ならないようにしました。
C#
1 using UnityEngine ;
2
3 [ RequireComponent ( typeof ( MeshFilter ) ) ]
4 public class CubeMeshUnwrapper : MonoBehaviour
5 {
6 private Mesh cubeMesh ;
7
8 private void Awake ( )
9 {
10 this . cubeMesh = this . GetComponent < MeshFilter > ( ) . mesh ;
11 var normals = this . cubeMesh . normals ;
12 var uv = this . cubeMesh . uv ;
13 var scale = new Vector2 ( 0.25f , 0.5f ) ;
14 for ( var vertexIndex = 0 ; vertexIndex < normals . Length ; vertexIndex ++ )
15 {
16 var normal = normals [ vertexIndex ] ;
17 var absNormalYz = new Vector2 ( Mathf . Abs ( normal . y ) , Mathf . Abs ( normal . z ) ) ;
18 var x = ( int ) Vector2 . Dot ( absNormalYz , new Vector2 ( 1 , 2 ) ) ;
19 var y = ( int ) Mathf . Clamp01 ( - ( normal . x + normal . y + normal . z ) ) ;
20 uv [ vertexIndex ] = ( uv [ vertexIndex ] + new Vector2 ( x , y ) ) * scale ;
21 }
22 this . cubeMesh . uv = uv ;
23 this . cubeMesh . UploadMeshData ( true ) ;
24 }
25
26 private void OnDestroy ( )
27 {
28 Destroy ( this . cubeMesh ) ;
29 }
30 }
デフォルトのキューブだと通常は下図のようにテクスチャが貼られますが...
実行時にUV配置を変更することで、テクスチャの貼られ方が下図のように変化します。
また、ブロック模様の軽減のため「Edit」→「Project Settings...」の「Quality」→「Shadows」の「Shadow Resolution」を「Very High Resolution」に、「Shadow Distance」をターゲットがギリギリ収まる程度まで小さくしました。
UV配置を横長にしたので出力画像のサイズも1024×512の横長にして、前回と同様にライトを回しながら撮影したところ...
下図のような影画像になりました。
##ステンシルシャドウによる方法(その1)
まずプロジェクト内に、以前別の方の「影のできる位置を求めてコライダーをつけたい 」への回答の際に作成したShadowMeshAssembler
を入れました。
そして上述のCubeMeshUnwrapper
内のthis.cubeMesh.UploadMeshData(true);
をthis.cubeMesh.UploadMeshData(false);
に変更し、メッシュ加工後もメッシュデータを読み書きできるようにしました。
ターゲットにはCubeMeshUnwrapper
に加えて、各面への頂点変換を保持するための下記スクリプトをアタッチしました。
C#
1 using System . Linq ;
2 using UnityEngine ;
3
4 [ RequireComponent ( typeof ( MeshFilter ) ) ]
5 public class FaceDecomposer : MonoBehaviour
6 {
7 public int FaceCount { get ; private set ; }
8 public Matrix4x4 [ ] UvMatrices { get ; private set ; }
9 public Matrix4x4 [ ] ObjectToUvMatrices { get ; private set ; }
10
11 private void Start ( )
12 {
13 var mesh = this . GetComponent < MeshFilter > ( ) . mesh ;
14 var vertices = mesh . vertices ;
15 var uv = mesh . uv ;
16 var triangles = mesh . triangles ;
17 this . FaceCount = triangles . Length / 3 ;
18 var matrices = Enumerable . Range ( 0 , this . FaceCount ) . Select (
19 faceIndex = >
20 {
21 var k0 = faceIndex * 3 ;
22 var k1 = k0 + 1 ;
23 var k2 = k0 + 2 ;
24 var i0 = triangles [ k0 ] ;
25 var i1 = triangles [ k1 ] ;
26 var i2 = triangles [ k2 ] ;
27 var v0 = vertices [ i0 ] ;
28 var v1 = vertices [ i1 ] ;
29 var v2 = vertices [ i2 ] ;
30 var t0 = uv [ i0 ] ;
31 var t1 = uv [ i1 ] ;
32 var t2 = uv [ i2 ] ;
33 var uvMatrix = new Matrix4x4 (
34 t2 - t0 ,
35 t1 - t0 ,
36 new Vector4 ( 0 , 0 , 1 , 0 ) ,
37 new Vector4 ( t0 . x , t0 . y , 0 , 1 ) ) ;
38 var objectMatrix = new Matrix4x4 (
39 v2 - v0 ,
40 v1 - v0 ,
41 Vector3 . Cross ( v2 - v0 , v1 - v0 ) . normalized ,
42 new Vector4 ( v0 . x , v0 . y , v0 . z , 1 ) ) ;
43 var objectToUvMatrix = uvMatrix * objectMatrix . inverse ;
44 return ( uvMatrix , objectToUvMatrix ) ;
45 } ) . ToArray ( ) ;
46 this . UvMatrices = matrices . Select ( m = > m . uvMatrix ) . ToArray ( ) ;
47 this . ObjectToUvMatrices = matrices . Select ( m = > m . objectToUvMatrix ) . ToArray ( ) ;
48 }
49 }
影の撮影はこれまでのShadowCaptor
に代わって下記ShadowVolumeCaptor
を使いました。
C#
1 using System ;
2 using System . Collections . Generic ;
3 using System . IO ;
4 using UnityEngine ;
5
6 public class ShadowVolumeCaptor : MonoBehaviour
7 {
8 private static readonly int LightDirectionId = Shader . PropertyToID ( "_LightDirection" ) ;
9 private static readonly int WorldToUvId = Shader . PropertyToID ( "_WorldToUv" ) ;
10
11 [ SerializeField ] private Shader faceMarkerShader ;
12 [ SerializeField ] private Shader shadowMarkerShader ;
13 [ SerializeField ] private Shader shadowDrawerShader ;
14 [ SerializeField ] private Vector2Int size = new Vector2Int ( 1024 , 1024 ) ;
15 [ SerializeField ] private Transform lightTransform ;
16
17 private readonly Dictionary < MeshFilter , Mesh > shadowVolumes = new Dictionary < MeshFilter , Mesh > ( ) ;
18 private Material faceMarkerMaterial ;
19 private Material shadowMarkerMaterial ;
20 private Material shadowDrawerMaterial ;
21 private Mesh face ;
22 private Mesh quad ;
23 private RenderTexture resultRenderTexture ;
24 private Texture2D resultTexture ;
25
26 private void Awake ( )
27 {
28 if ( this . faceMarkerShader == null )
29 {
30 Debug . LogError ( "Face marker shader is not set." ) ;
31 Destroy ( this ) ;
32 return ;
33 }
34 if ( this . shadowMarkerShader == null )
35 {
36 Debug . LogError ( "Shadow marker shader is not set." ) ;
37 Destroy ( this ) ;
38 return ;
39 }
40 if ( this . shadowDrawerShader == null )
41 {
42 Debug . LogError ( "Shadow drawer shader is not set." ) ;
43 Destroy ( this ) ;
44 return ;
45 }
46
47 this . faceMarkerMaterial = new Material ( this . faceMarkerShader ) ;
48 this . shadowMarkerMaterial = new Material ( this . shadowMarkerShader ) ;
49 this . shadowDrawerMaterial = new Material ( this . shadowDrawerShader ) ;
50 this . resultTexture = new Texture2D ( this . size . x , this . size . y ) ;
51 this . resultRenderTexture = new RenderTexture ( this . size . x , this . size . y , 24 ) ;
52 this . face = new Mesh
53 {
54 name = "Face" ,
55 vertices = new [ ] { Vector3 . zero , Vector3 . up , Vector3 . right } ,
56 triangles = new [ ] { 0 , 1 , 2 }
57 } ;
58 this . quad = new Mesh
59 {
60 name = "Quad" ,
61 vertices = new [ ] { new Vector3 ( - 1 , - 1 , 0 ) , new Vector3 ( - 1 , 1 , 0 ) , new Vector3 ( 1 , - 1 , 0 ) , new Vector3 ( 1 , 1 , 0 ) } ,
62 triangles = new [ ] { 0 , 1 , 2 , 3 , 2 , 1 }
63 } ;
64 Shader . WarmupAllShaders ( ) ;
65 }
66
67 private void OnDestroy ( )
68 {
69 Destroy ( this . faceMarkerMaterial ) ;
70 Destroy ( this . shadowMarkerMaterial ) ;
71 Destroy ( this . shadowDrawerMaterial ) ;
72 Destroy ( this . resultTexture ) ;
73 Destroy ( this . resultRenderTexture ) ;
74 Destroy ( this . face ) ;
75 Destroy ( this . quad ) ;
76 foreach ( var shadowVolume in this . shadowVolumes . Values )
77 {
78 Destroy ( shadowVolume ) ;
79 }
80 }
81
82 public void Capture ( FaceDecomposer target , string path )
83 {
84 if ( target == null )
85 {
86 throw new ArgumentNullException ( nameof ( target ) ) ;
87 }
88
89 if ( string . IsNullOrWhiteSpace ( path ) )
90 {
91 throw new ArgumentException ( "Invalid path." ) ;
92 }
93
94 // レンダーターゲットを設定し...
95 var activeTexture = RenderTexture . active ;
96 RenderTexture . active = this . resultRenderTexture ;
97 GL . Clear ( true , true , Color . white ) ;
98
99 // ライト方向を設定し...
100 this . shadowMarkerMaterial . SetVector ( LightDirectionId , this . lightTransform . forward ) ;
101
102 // シーン上の各MeshFilterについて...
103 var worldToTargetMatrix = target . transform . worldToLocalMatrix ;
104 foreach ( var meshFilter in FindObjectsOfType < MeshFilter > ( ) )
105 {
106 var shadowToWorldMatrix = meshFilter . transform . localToWorldMatrix ;
107
108 // シャドウボリュームメッシュを取得し(なければ生成し)...
109 if ( ! this . shadowVolumes . TryGetValue ( meshFilter , out var shadowVolume ) )
110 {
111 shadowVolume = ShadowMeshAssembler . AssembleShadowMesh ( meshFilter . sharedMesh ) ;
112 this . shadowVolumes [ meshFilter ] = shadowVolume ;
113 }
114
115 // ターゲットを構成する各面について...
116 var faceCount = target . FaceCount ;
117 for ( var i = 0 ; i < faceCount ; i ++ )
118 {
119 // まず面が占めるピクセルをマークする
120 this . faceMarkerMaterial . SetPass ( 0 ) ;
121 Graphics . DrawMeshNow ( this . face , target . UvMatrices [ i ] ) ;
122
123 // マークされたピクセルにシャドウボリュームをレンダリングする
124 this . shadowMarkerMaterial . SetMatrix ( WorldToUvId , target . ObjectToUvMatrices [ i ] * worldToTargetMatrix ) ;
125 this . shadowMarkerMaterial . SetPass ( 0 ) ;
126 Graphics . DrawMeshNow ( shadowVolume , shadowToWorldMatrix ) ;
127
128 // マーキングを解除する
129 this . faceMarkerMaterial . SetPass ( 1 ) ;
130 Graphics . DrawMeshNow ( this . face , target . UvMatrices [ i ] ) ;
131 }
132 }
133
134 // 影の中にいると判定された部分に黒を塗る
135 this . shadowDrawerMaterial . SetPass ( 0 ) ;
136 Graphics . DrawMeshNow ( this . quad , Matrix4x4 . identity ) ;
137
138 // 結果をTexture2Dに読み取らせてPNG形式にエンコードして保存する
139 this . resultTexture . ReadPixels ( new Rect ( 0 , 0 , this . size . x , this . size . y ) , 0 , 0 ) ;
140 RenderTexture . active = activeTexture ;
141 var resultData = this . resultTexture . EncodeToPNG ( ) ;
142 File . WriteAllBytes ( path , resultData ) ;
143 }
144 }