質問するログイン新規登録

回答編集履歴

1

ステンシルシャドウによる方法を追記

2021/01/01 20:25

投稿

Bongo
Bongo

スコア10816

answer CHANGED
@@ -8,30 +8,30 @@
8
8
  [RequireComponent(typeof(MeshFilter))]
9
9
  public class CubeMeshUnwrapper : MonoBehaviour
10
10
  {
11
- private Mesh cubeMesh;
11
+ private Mesh cubeMesh;
12
12
 
13
- private void Awake()
13
+ private void Awake()
14
- {
14
+ {
15
- this.cubeMesh = this.GetComponent<MeshFilter>().mesh;
15
+ this.cubeMesh = this.GetComponent<MeshFilter>().mesh;
16
- var normals = this.cubeMesh.normals;
16
+ var normals = this.cubeMesh.normals;
17
- var uv = this.cubeMesh.uv;
17
+ var uv = this.cubeMesh.uv;
18
- var scale = new Vector2(0.25f, 0.5f);
18
+ var scale = new Vector2(0.25f, 0.5f);
19
- for (var vertexIndex = 0; vertexIndex < normals.Length; vertexIndex++)
19
+ for (var vertexIndex = 0; vertexIndex < normals.Length; vertexIndex++)
20
- {
20
+ {
21
- var normal = normals[vertexIndex];
21
+ var normal = normals[vertexIndex];
22
- var absNormalYz = new Vector2(Mathf.Abs(normal.y), Mathf.Abs(normal.z));
22
+ var absNormalYz = new Vector2(Mathf.Abs(normal.y), Mathf.Abs(normal.z));
23
- var x = (int)Vector2.Dot(absNormalYz, new Vector2(1, 2));
23
+ var x = (int)Vector2.Dot(absNormalYz, new Vector2(1, 2));
24
- var y = (int)Mathf.Clamp01(-(normal.x + normal.y + normal.z));
24
+ var y = (int)Mathf.Clamp01(-(normal.x + normal.y + normal.z));
25
- uv[vertexIndex] = (uv[vertexIndex] + new Vector2(x, y)) * scale;
25
+ uv[vertexIndex] = (uv[vertexIndex] + new Vector2(x, y)) * scale;
26
- }
26
+ }
27
- this.cubeMesh.uv = uv;
27
+ this.cubeMesh.uv = uv;
28
- this.cubeMesh.UploadMeshData(true);
28
+ this.cubeMesh.UploadMeshData(true);
29
- }
29
+ }
30
30
 
31
- private void OnDestroy()
31
+ private void OnDestroy()
32
- {
32
+ {
33
- Destroy(this.cubeMesh);
33
+ Destroy(this.cubeMesh);
34
- }
34
+ }
35
35
  }
36
36
  ```
37
37
 
@@ -53,4 +53,211 @@
53
53
 
54
54
  下図のような影画像になりました。
55
55
 
56
- ![図5](c8dd4d990e0457384d66e415089de67d.gif)
56
+ ![図5](c8dd4d990e0457384d66e415089de67d.gif)
57
+
58
+ ##ステンシルシャドウによる方法(その1)
59
+
60
+ まずプロジェクト内に、以前別の方の「[影のできる位置を求めてコライダーをつけたい](https://teratail.com/questions/235539)」への回答の際に作成した`ShadowMeshAssembler`を入れました。
61
+ そして上述の`CubeMeshUnwrapper`内の`this.cubeMesh.UploadMeshData(true);`を`this.cubeMesh.UploadMeshData(false);`に変更し、メッシュ加工後もメッシュデータを読み書きできるようにしました。
62
+ ターゲットには`CubeMeshUnwrapper`に加えて、各面への頂点変換を保持するための下記スクリプトをアタッチしました。
63
+
64
+ ```C#
65
+ using System.Linq;
66
+ using UnityEngine;
67
+
68
+ [RequireComponent(typeof(MeshFilter))]
69
+ public class FaceDecomposer : MonoBehaviour
70
+ {
71
+ public int FaceCount { get; private set; }
72
+ public Matrix4x4[] UvMatrices { get; private set; }
73
+ public Matrix4x4[] ObjectToUvMatrices { get; private set; }
74
+
75
+ private void Start()
76
+ {
77
+ var mesh = this.GetComponent<MeshFilter>().mesh;
78
+ var vertices = mesh.vertices;
79
+ var uv = mesh.uv;
80
+ var triangles = mesh.triangles;
81
+ this.FaceCount = triangles.Length / 3;
82
+ var matrices = Enumerable.Range(0, this.FaceCount).Select(
83
+ faceIndex =>
84
+ {
85
+ var k0 = faceIndex * 3;
86
+ var k1 = k0 + 1;
87
+ var k2 = k0 + 2;
88
+ var i0 = triangles[k0];
89
+ var i1 = triangles[k1];
90
+ var i2 = triangles[k2];
91
+ var v0 = vertices[i0];
92
+ var v1 = vertices[i1];
93
+ var v2 = vertices[i2];
94
+ var t0 = uv[i0];
95
+ var t1 = uv[i1];
96
+ var t2 = uv[i2];
97
+ var uvMatrix = new Matrix4x4(
98
+ t2 - t0,
99
+ t1 - t0,
100
+ new Vector4(0, 0, 1, 0),
101
+ new Vector4(t0.x, t0.y, 0, 1));
102
+ var objectMatrix = new Matrix4x4(
103
+ v2 - v0,
104
+ v1 - v0,
105
+ Vector3.Cross(v2 - v0, v1 - v0).normalized,
106
+ new Vector4(v0.x, v0.y, v0.z, 1));
107
+ var objectToUvMatrix = uvMatrix * objectMatrix.inverse;
108
+ return (uvMatrix, objectToUvMatrix);
109
+ }).ToArray();
110
+ this.UvMatrices = matrices.Select(m => m.uvMatrix).ToArray();
111
+ this.ObjectToUvMatrices = matrices.Select(m => m.objectToUvMatrix).ToArray();
112
+ }
113
+ }
114
+ ```
115
+
116
+ 影の撮影はこれまでの`ShadowCaptor`に代わって下記`ShadowVolumeCaptor`を使いました。
117
+
118
+ ```C#
119
+ using System;
120
+ using System.Collections.Generic;
121
+ using System.IO;
122
+ using UnityEngine;
123
+
124
+ public class ShadowVolumeCaptor : MonoBehaviour
125
+ {
126
+ private static readonly int LightDirectionId = Shader.PropertyToID("_LightDirection");
127
+ private static readonly int WorldToUvId = Shader.PropertyToID("_WorldToUv");
128
+
129
+ [SerializeField] private Shader faceMarkerShader;
130
+ [SerializeField] private Shader shadowMarkerShader;
131
+ [SerializeField] private Shader shadowDrawerShader;
132
+ [SerializeField] private Vector2Int size = new Vector2Int(1024, 1024);
133
+ [SerializeField] private Transform lightTransform;
134
+
135
+ private readonly Dictionary<MeshFilter, Mesh> shadowVolumes = new Dictionary<MeshFilter, Mesh>();
136
+ private Material faceMarkerMaterial;
137
+ private Material shadowMarkerMaterial;
138
+ private Material shadowDrawerMaterial;
139
+ private Mesh face;
140
+ private Mesh quad;
141
+ private RenderTexture resultRenderTexture;
142
+ private Texture2D resultTexture;
143
+
144
+ private void Awake()
145
+ {
146
+ if (this.faceMarkerShader == null)
147
+ {
148
+ Debug.LogError("Face marker shader is not set.");
149
+ Destroy(this);
150
+ return;
151
+ }
152
+ if (this.shadowMarkerShader == null)
153
+ {
154
+ Debug.LogError("Shadow marker shader is not set.");
155
+ Destroy(this);
156
+ return;
157
+ }
158
+ if (this.shadowDrawerShader == null)
159
+ {
160
+ Debug.LogError("Shadow drawer shader is not set.");
161
+ Destroy(this);
162
+ return;
163
+ }
164
+
165
+ this.faceMarkerMaterial = new Material(this.faceMarkerShader);
166
+ this.shadowMarkerMaterial = new Material(this.shadowMarkerShader);
167
+ this.shadowDrawerMaterial = new Material(this.shadowDrawerShader);
168
+ this.resultTexture = new Texture2D(this.size.x, this.size.y);
169
+ this.resultRenderTexture = new RenderTexture(this.size.x, this.size.y, 24);
170
+ this.face = new Mesh
171
+ {
172
+ name = "Face",
173
+ vertices = new[] {Vector3.zero, Vector3.up, Vector3.right},
174
+ triangles = new[] {0, 1, 2}
175
+ };
176
+ this.quad = new Mesh
177
+ {
178
+ name = "Quad",
179
+ vertices = new[] {new Vector3(-1, -1, 0), new Vector3(-1, 1, 0), new Vector3(1, -1, 0), new Vector3(1, 1, 0)},
180
+ triangles = new[] {0, 1, 2, 3, 2, 1}
181
+ };
182
+ Shader.WarmupAllShaders();
183
+ }
184
+
185
+ private void OnDestroy()
186
+ {
187
+ Destroy(this.faceMarkerMaterial);
188
+ Destroy(this.shadowMarkerMaterial);
189
+ Destroy(this.shadowDrawerMaterial);
190
+ Destroy(this.resultTexture);
191
+ Destroy(this.resultRenderTexture);
192
+ Destroy(this.face);
193
+ Destroy(this.quad);
194
+ foreach (var shadowVolume in this.shadowVolumes.Values)
195
+ {
196
+ Destroy(shadowVolume);
197
+ }
198
+ }
199
+
200
+ public void Capture(FaceDecomposer target, string path)
201
+ {
202
+ if (target == null)
203
+ {
204
+ throw new ArgumentNullException(nameof(target));
205
+ }
206
+
207
+ if (string.IsNullOrWhiteSpace(path))
208
+ {
209
+ throw new ArgumentException("Invalid path.");
210
+ }
211
+
212
+ // レンダーターゲットを設定し...
213
+ var activeTexture = RenderTexture.active;
214
+ RenderTexture.active = this.resultRenderTexture;
215
+ GL.Clear(true, true, Color.white);
216
+
217
+ // ライト方向を設定し...
218
+ this.shadowMarkerMaterial.SetVector(LightDirectionId, this.lightTransform.forward);
219
+
220
+ // シーン上の各MeshFilterについて...
221
+ var worldToTargetMatrix = target.transform.worldToLocalMatrix;
222
+ foreach (var meshFilter in FindObjectsOfType<MeshFilter>())
223
+ {
224
+ var shadowToWorldMatrix = meshFilter.transform.localToWorldMatrix;
225
+
226
+ // シャドウボリュームメッシュを取得し(なければ生成し)...
227
+ if (!this.shadowVolumes.TryGetValue(meshFilter, out var shadowVolume))
228
+ {
229
+ shadowVolume = ShadowMeshAssembler.AssembleShadowMesh(meshFilter.sharedMesh);
230
+ this.shadowVolumes[meshFilter] = shadowVolume;
231
+ }
232
+
233
+ // ターゲットを構成する各面について...
234
+ var faceCount = target.FaceCount;
235
+ for (var i = 0; i < faceCount; i++)
236
+ {
237
+ // まず面が占めるピクセルをマークする
238
+ this.faceMarkerMaterial.SetPass(0);
239
+ Graphics.DrawMeshNow(this.face, target.UvMatrices[i]);
240
+
241
+ // マークされたピクセルにシャドウボリュームをレンダリングする
242
+ this.shadowMarkerMaterial.SetMatrix(WorldToUvId, target.ObjectToUvMatrices[i] * worldToTargetMatrix);
243
+ this.shadowMarkerMaterial.SetPass(0);
244
+ Graphics.DrawMeshNow(shadowVolume, shadowToWorldMatrix);
245
+
246
+ // マーキングを解除する
247
+ this.faceMarkerMaterial.SetPass(1);
248
+ Graphics.DrawMeshNow(this.face, target.UvMatrices[i]);
249
+ }
250
+ }
251
+
252
+ // 影の中にいると判定された部分に黒を塗る
253
+ this.shadowDrawerMaterial.SetPass(0);
254
+ Graphics.DrawMeshNow(this.quad, Matrix4x4.identity);
255
+
256
+ // 結果をTexture2Dに読み取らせてPNG形式にエンコードして保存する
257
+ this.resultTexture.ReadPixels(new Rect(0, 0, this.size.x, this.size.y), 0, 0);
258
+ RenderTexture.active = activeTexture;
259
+ var resultData = this.resultTexture.EncodeToPNG();
260
+ File.WriteAllBytes(path, resultData);
261
+ }
262
+ }
263
+ ```