回答編集履歴

1

画面上へのレンダリングとテクスチャ上へのレンダリングを統合

2023/01/28 08:00

投稿

Bongo
Bongo

スコア10807

test CHANGED
@@ -7,84 +7,84 @@
7
7
  ```ShaderLab
8
8
  Shader "Unlit/RenderMeshToTexture"
9
9
  {
10
- Properties
11
- {
12
- _BrushColor ("Brush Color", Color) = (1.0, 0.0, 0.0, 1.0)
13
- _BrushSize ("Brush Size", Float) = 1.0
14
- _BrushStrength ("Brush Strength", Range(0.0, 1.0)) = 0.75
15
- _Mouse ("World Space Mouse Position", Vector) = (0.0, 0.0, 0.0, 0.0)
16
- }
17
-
18
- SubShader
19
- {
20
- Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "RenderPipeline" = "UviversalRenderPipeline" }
21
-
22
- Pass
23
- {
24
- Cull Off
25
- ZWrite Off
26
- ZTest Always
27
- Blend SrcAlpha OneMinusSrcAlpha
28
-
29
- HLSLPROGRAM
30
-
31
- #pragma vertex vert
32
- #pragma fragment frag
33
-
34
- #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
35
-
36
- struct Attributes
37
- {
38
- float4 positionOS : POSITION;
39
- float2 uv : TEXCOORD0;
40
- };
41
-
42
- struct Varyings
43
- {
44
- float3 positionWS : TEXCOORD0;
45
- float4 positionHCS : SV_POSITION;
46
- };
47
-
48
- Varyings vert(Attributes IN)
49
- {
50
- Varyings OUT;
51
-
52
- // ブラシとの距離を測定するのにワールド座標が必要になるので、
53
- // ワールド座標をフラグメントシェーダーに送る
54
- OUT.positionWS = TransformObjectToWorld(IN.positionOS.xyz);
55
-
56
- // UV空間でレンダリングしたいので、UV座標を頂点座標とみなして用いる
57
- OUT.positionHCS = float4(IN.uv * 2.0 - 1.0, 0.0, 1.0);
58
-
59
- // プラットフォームによってレンダーテクスチャの
60
- // 座標系の上下が違うので、適宜ひっくり返す
61
- #if UNITY_UV_STARTS_AT_TOP
62
- OUT.positionHCS.y *= -1.0;
63
- #endif
64
-
65
- return OUT;
66
- }
67
-
68
- half4 _BrushColor;
69
- float _BrushSize;
70
- float _BrushStrength;
71
- float4 _Mouse;
72
-
73
- half4 frag(Varyings IN) : SV_Target
74
- {
75
- // シェーダーグラフでの場合と同様の手順でブラシの不透明度を算出し...
76
- float alpha = (1.0 - smoothstep(_BrushSize * _BrushStrength, _BrushSize, distance(_Mouse.xyz, IN.positionWS))) * _Mouse.w * _BrushColor.a;
77
-
78
- // 十分に透明なフラグメントはレンダリングする必要がないためクリッピングし...
79
- clip(alpha - 0.0001);
80
-
81
- // ブラシの色と不透明度を組み合わせて出力する
82
- return half4(_BrushColor.rgb, alpha);
83
- }
84
-
85
- ENDHLSL
86
- }
87
- }
10
+ Properties
11
+ {
12
+ _BrushColor ("Brush Color", Color) = (1.0, 0.0, 0.0, 1.0)
13
+ _BrushSize ("Brush Size", Float) = 1.0
14
+ _BrushStrength ("Brush Strength", Range(0.0, 1.0)) = 0.75
15
+ _Mouse ("World Space Mouse Position", Vector) = (0.0, 0.0, 0.0, 0.0)
16
+ }
17
+
18
+ SubShader
19
+ {
20
+ Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "RenderPipeline" = "UviversalRenderPipeline" }
21
+
22
+ Pass
23
+ {
24
+ Cull Off
25
+ ZWrite Off
26
+ ZTest Always
27
+ Blend SrcAlpha OneMinusSrcAlpha
28
+
29
+ HLSLPROGRAM
30
+
31
+ #pragma vertex vert
32
+ #pragma fragment frag
33
+
34
+ #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
35
+
36
+ struct Attributes
37
+ {
38
+ float4 positionOS : POSITION;
39
+ float2 uv : TEXCOORD0;
40
+ };
41
+
42
+ struct Varyings
43
+ {
44
+ float3 positionWS : TEXCOORD0;
45
+ float4 positionHCS : SV_POSITION;
46
+ };
47
+
48
+ Varyings vert(Attributes IN)
49
+ {
50
+ Varyings OUT;
51
+
52
+ // ブラシとの距離を測定するのにワールド座標が必要になるので、
53
+ // ワールド座標をフラグメントシェーダーに送る
54
+ OUT.positionWS = TransformObjectToWorld(IN.positionOS.xyz);
55
+
56
+ // UV空間でレンダリングしたいので、UV座標を頂点座標とみなして用いる
57
+ OUT.positionHCS = float4(IN.uv * 2.0 - 1.0, 0.0, 1.0);
58
+
59
+ // プラットフォームによってレンダーテクスチャの
60
+ // 座標系の上下が違うので、適宜ひっくり返す
61
+ #if UNITY_UV_STARTS_AT_TOP
62
+ OUT.positionHCS.y *= -1.0;
63
+ #endif
64
+
65
+ return OUT;
66
+ }
67
+
68
+ half4 _BrushColor;
69
+ float _BrushSize;
70
+ float _BrushStrength;
71
+ float4 _Mouse;
72
+
73
+ half4 frag(Varyings IN) : SV_Target
74
+ {
75
+ // シェーダーグラフでの場合と同様の手順でブラシの不透明度を算出し...
76
+ float alpha = (1.0 - smoothstep(_BrushSize * _BrushStrength, _BrushSize, distance(_Mouse.xyz, IN.positionWS))) * _Mouse.w * _BrushColor.a;
77
+
78
+ // 十分に透明なフラグメントはレンダリングする必要がないためクリッピングし...
79
+ clip(alpha - 0.0001);
80
+
81
+ // ブラシの色と不透明度を組み合わせて出力する
82
+ return half4(_BrushColor.rgb, alpha);
83
+ }
84
+
85
+ ENDHLSL
86
+ }
87
+ }
88
88
  }
89
89
  ```
90
90
 
@@ -96,72 +96,72 @@
96
96
  [RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]
97
97
  public class MeshPainter : MonoBehaviour
98
98
  {
99
- static readonly int s_mainTextureProperty = Shader.PropertyToID("_MainTexture");
100
- static readonly int s_brushColorProperty = Shader.PropertyToID("_BrushColor");
101
- static readonly int s_brushSizeProperty = Shader.PropertyToID("_BrushSize");
102
- static readonly int s_brushStrengthProperty = Shader.PropertyToID("_BrushStrength");
103
- static readonly int s_mouseProperty = Shader.PropertyToID("_Mouse");
104
-
105
- // インスペクター上で、ここに前述のレンダーテクスチャ描画用のシェーダーをセットしておく
106
- [SerializeField] Shader m_renderToTextureShader;
107
-
108
- Material m_mat;
109
- Material m_renderToTextureMat;
110
- Texture m_initialTexture;
111
- RenderTexture m_paintedTexture;
112
- Mesh m_mesh;
113
-
114
- void Start()
115
- {
116
- // Start時にモデルのマテリアルのテクスチャを
117
- // 元々のテクスチャをコピーしたレンダーテクスチャに差し替えておく
118
- m_mat = GetComponent<MeshRenderer>().material;
119
- m_initialTexture = m_mat.GetTexture(s_mainTextureProperty);
120
- m_paintedTexture = new RenderTexture(m_initialTexture.width, m_initialTexture.height, 0);
121
- Graphics.Blit(m_initialTexture, m_paintedTexture);
122
- m_mat.SetTexture(s_mainTextureProperty, m_paintedTexture);
123
-
124
- // レンダーテクスチャ描画用のマテリアルを作っておく
125
- m_renderToTextureMat = new Material(m_renderToTextureShader);
126
- m_renderToTextureMat.SetColor(s_brushColorProperty, m_mat.GetColor(s_brushColorProperty));
127
- m_renderToTextureMat.SetFloat(s_brushSizeProperty, m_mat.GetFloat(s_brushSizeProperty));
128
- m_renderToTextureMat.SetFloat(s_brushStrengthProperty, m_mat.GetFloat(s_brushStrengthProperty));
129
-
130
- // メッシュも取得しておく
131
- m_mesh = GetComponent<MeshFilter>().sharedMesh;
132
- }
133
-
134
- void OnDestroy()
135
- {
136
- // コンポーネント破壊時には不要なオブジェクトも破壊し、
137
- // テクスチャも元に戻すことにする
138
- Destroy(m_renderToTextureMat);
139
- Destroy(m_paintedTexture);
140
- m_mat.mainTexture = m_initialTexture;
141
- }
142
-
143
- void Update()
144
- {
145
- Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
146
- Vector4 mwp = Vector4.zero;
147
-
148
- if (Input.GetMouseButton(0) && Physics.Raycast(ray, out RaycastHit hit))
149
- {
150
- mwp = hit.point;
151
- mwp.w = 1.0f;
152
-
153
- // マウスボタンを押しており、かつレイがヒットしているなら
154
- // レンダーテクスチャへのレンダリングも行う
155
- RenderTexture activeTexture = RenderTexture.active;
156
- RenderTexture.active = m_paintedTexture;
157
- m_renderToTextureMat.SetVector(s_mouseProperty, mwp);
158
- m_renderToTextureMat.SetPass(0);
159
- Graphics.DrawMeshNow(m_mesh, transform.localToWorldMatrix);
160
- RenderTexture.active = activeTexture;
161
- }
162
-
163
- m_mat.SetVector(s_mouseProperty, mwp);
164
- }
99
+ static readonly int s_mainTextureProperty = Shader.PropertyToID("_MainTexture");
100
+ static readonly int s_brushColorProperty = Shader.PropertyToID("_BrushColor");
101
+ static readonly int s_brushSizeProperty = Shader.PropertyToID("_BrushSize");
102
+ static readonly int s_brushStrengthProperty = Shader.PropertyToID("_BrushStrength");
103
+ static readonly int s_mouseProperty = Shader.PropertyToID("_Mouse");
104
+
105
+ // インスペクター上で、ここに前述のレンダーテクスチャ描画用のシェーダーをセットしておく
106
+ [SerializeField] Shader m_renderToTextureShader;
107
+
108
+ Material m_mat;
109
+ Material m_renderToTextureMat;
110
+ Texture m_initialTexture;
111
+ RenderTexture m_paintedTexture;
112
+ Mesh m_mesh;
113
+
114
+ void Start()
115
+ {
116
+ // Start時にモデルのマテリアルのテクスチャを
117
+ // 元々のテクスチャをコピーしたレンダーテクスチャに差し替えておく
118
+ m_mat = GetComponent<MeshRenderer>().material;
119
+ m_initialTexture = m_mat.GetTexture(s_mainTextureProperty);
120
+ m_paintedTexture = new RenderTexture(m_initialTexture.width, m_initialTexture.height, 0);
121
+ Graphics.Blit(m_initialTexture, m_paintedTexture);
122
+ m_mat.SetTexture(s_mainTextureProperty, m_paintedTexture);
123
+
124
+ // レンダーテクスチャ描画用のマテリアルを作っておく
125
+ m_renderToTextureMat = new Material(m_renderToTextureShader);
126
+ m_renderToTextureMat.SetColor(s_brushColorProperty, m_mat.GetColor(s_brushColorProperty));
127
+ m_renderToTextureMat.SetFloat(s_brushSizeProperty, m_mat.GetFloat(s_brushSizeProperty));
128
+ m_renderToTextureMat.SetFloat(s_brushStrengthProperty, m_mat.GetFloat(s_brushStrengthProperty));
129
+
130
+ // メッシュも取得しておく
131
+ m_mesh = GetComponent<MeshFilter>().sharedMesh;
132
+ }
133
+
134
+ void OnDestroy()
135
+ {
136
+ // コンポーネント破壊時には不要なオブジェクトも破壊し、
137
+ // テクスチャも元に戻すことにする
138
+ Destroy(m_renderToTextureMat);
139
+ Destroy(m_paintedTexture);
140
+ m_mat.mainTexture = m_initialTexture;
141
+ }
142
+
143
+ void Update()
144
+ {
145
+ Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
146
+ Vector4 mwp = Vector4.zero;
147
+
148
+ if (Input.GetMouseButton(0) && Physics.Raycast(ray, out RaycastHit hit))
149
+ {
150
+ mwp = hit.point;
151
+ mwp.w = 1.0f;
152
+
153
+ // マウスボタンを押しており、かつレイがヒットしているなら
154
+ // レンダーテクスチャへのレンダリングも行う
155
+ RenderTexture activeTexture = RenderTexture.active;
156
+ RenderTexture.active = m_paintedTexture;
157
+ m_renderToTextureMat.SetVector(s_mouseProperty, mwp);
158
+ m_renderToTextureMat.SetPass(0);
159
+ Graphics.DrawMeshNow(m_mesh, transform.localToWorldMatrix);
160
+ RenderTexture.active = activeTexture;
161
+ }
162
+
163
+ m_mat.SetVector(s_mouseProperty, mwp);
164
+ }
165
165
  }
166
166
  ```
167
167
 
@@ -171,3 +171,130 @@
171
171
 
172
172
  ただし上図の通り、UVが不連続になっている部分で塗り漏らしが発生し、下地がスジとして見えてしまうという欠陥があります。これについては、だいぶ前の話になってしまいますが「[[Unity]UV共有してる面にもUV2を使って一か所だけにペイントしたい](https://teratail.com/questions/183848)」とのご質問で同様の現象がありまして、toranoさんの場合は隙間を埋めるよう画像処理を加えることで、私の場合はメッシュのポリゴンを膨張させた状態で再度レンダリングすることで対処しました。
173
173
  ご質問者さんの包丁も、ご提示のUV展開図を拝見しますにけっこうUV不連続があるようですので、スジが目立って見苦しくなってしまうかもしれません。その場合はコメントいただければ対策を組み込んでみようかと思うのですが、あいにく本職の方で面倒な仕事が来てしまったところでして、Unityをいじれるのは後日となってしまうかと思います。
174
+
175
+ ## 画面上へのレンダリングとテクスチャ上へのレンダリングを統合
176
+
177
+ グラフの全体図は下図のようになりました。テクスチャ上へのレンダリングもこのシェーダーでまかなうようにした都合上、Render FaceはBothとして裏面も描画するように設定しています。
178
+
179
+ ![図1](https://ddjkaamml8q8x.cloudfront.net/questions/2023-01-28/ef7f80c5-0bab-489a-87b1-741f1fe05edf.png)
180
+
181
+ Blackboardに追加した`RenderToTexture`はブーリアンキーワードで、画面レンダリングモードとテクスチャレンダリングモードを切り替えるためのものです。
182
+
183
+ ![図2](https://ddjkaamml8q8x.cloudfront.net/questions/2023-01-28/12cb51b8-f9de-4871-8d45-2f90f8e22abc.png)
184
+
185
+ Vertexコンテキストには[Custom Interpolator](https://docs.unity3d.com/Packages/com.unity.shadergraph@14.0/manual/Custom-Interpolators.html)として`WorldPosition`と`WorldNormal`を追加しています。
186
+
187
+ ![図3](https://ddjkaamml8q8x.cloudfront.net/questions/2023-01-28/42222016-1e6c-418b-9269-99062cf5fd09.png)
188
+
189
+ ![図4](https://ddjkaamml8q8x.cloudfront.net/questions/2023-01-28/70c31a32-d77c-4009-a3ad-4fa7c8877f95.png)
190
+
191
+ そして、オブジェクトにアタッチするスクリプトは下記のように変更しました。
192
+
193
+ ```C#
194
+ using UnityEngine;
195
+ using UnityEngine.Rendering;
196
+
197
+ [RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]
198
+ public class MeshPainter2 : MonoBehaviour
199
+ {
200
+ static readonly int s_mainTextureProperty = Shader.PropertyToID("_MainTexture");
201
+ static readonly int s_objectToWorldProperty = Shader.PropertyToID("_ObjectToWorld");
202
+ static readonly int s_objectToWorldNormalProperty = Shader.PropertyToID("_ObjectToWorldNormal");
203
+ static readonly int s_mouseProperty = Shader.PropertyToID("_Mouse");
204
+ static readonly int s_normalProperty = Shader.PropertyToID("_Normal");
205
+
206
+ public bool m_updateMainTextureEveryFrame;
207
+
208
+ Material m_mat;
209
+ Mesh m_mesh;
210
+ Texture2D m_mainTexture;
211
+ RenderTexture m_paintedTexture;
212
+ LocalKeyword m_renderToTextureKeyword;
213
+ int m_passIndex = -1;
214
+
215
+ public void UpdateMainTexture()
216
+ {
217
+ if (m_paintedTexture == null)
218
+ {
219
+ return;
220
+ }
221
+
222
+ // レンダーテクスチャの内容をメインテクスチャに読み取る
223
+ RenderTexture activeTexture = RenderTexture.active;
224
+ RenderTexture.active = m_paintedTexture;
225
+ m_mainTexture.ReadPixels(new Rect(0, 0, m_paintedTexture.width, m_paintedTexture.height), 0, 0);
226
+ m_mainTexture.Apply();
227
+ RenderTexture.active = activeTexture;
228
+ }
229
+
230
+ void Start()
231
+ {
232
+ // マテリアルからメインテクスチャを取得しておく
233
+ m_mat = GetComponent<MeshRenderer>().material;
234
+ m_mainTexture = m_mat.GetTexture(s_mainTextureProperty) as Texture2D;
235
+ m_paintedTexture = new RenderTexture(m_mainTexture.width, m_mainTexture.height, 0, m_mainTexture.graphicsFormat);
236
+ Graphics.Blit(m_mainTexture, m_paintedTexture);
237
+ m_mat.SetTexture(s_mainTextureProperty, m_paintedTexture);
238
+
239
+ // シェーダーのモードを切り替えるためのキーワードを用意しておく
240
+ m_renderToTextureKeyword = new LocalKeyword(m_mat.shader, "RENDER_TO_TEXTURE");
241
+
242
+ // メッシュも取得しておく
243
+ m_mesh = GetComponent<MeshFilter>().sharedMesh;
244
+ }
245
+
246
+ void LateUpdate()
247
+ {
248
+ Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
249
+ Vector4 mwp = Vector4.zero;
250
+ Vector4 mwn = Vector4.zero;
251
+
252
+ if (Input.GetMouseButton(0) && Physics.Raycast(ray, out RaycastHit hit))
253
+ {
254
+ mwp = hit.point;
255
+ mwp.w = 1.0f;
256
+ mwn = hit.normal;
257
+ }
258
+
259
+ m_mat.SetVector(s_mouseProperty, mwp);
260
+
261
+ if (mwp.w > 0.0f)
262
+ {
263
+ // マウスボタンを押しており、かつレイがヒットしているなら
264
+ // レンダーテクスチャへのレンダリングも行う
265
+ m_mat.SetVector(s_normalProperty, mwn);
266
+ m_mat.SetMatrix(s_objectToWorldProperty, transform.localToWorldMatrix);
267
+ m_mat.SetMatrix(s_objectToWorldNormalProperty, transform.worldToLocalMatrix.transpose);
268
+ RenderTexture activeTexture = RenderTexture.active;
269
+ RenderTexture tempTexture = RenderTexture.GetTemporary(m_paintedTexture.descriptor);
270
+ Graphics.Blit(m_paintedTexture, tempTexture);
271
+ RenderTexture.active = tempTexture;
272
+ m_mat.EnableKeyword(m_renderToTextureKeyword);
273
+
274
+ if (m_passIndex < 0)
275
+ {
276
+ m_passIndex = m_mat.FindPass("Universal Forward");
277
+ }
278
+
279
+ GL.PushMatrix();
280
+ m_mat.SetPass(m_passIndex);
281
+ Camera currentCamera = Camera.current;
282
+ GL.LoadProjectionMatrix(currentCamera == null ? Matrix4x4.identity : currentCamera.cameraToWorldMatrix);
283
+ Graphics.DrawMeshNow(m_mesh, Matrix4x4.identity);
284
+ GL.PopMatrix();
285
+ Graphics.Blit(tempTexture, m_paintedTexture);
286
+ RenderTexture.active = activeTexture;
287
+ RenderTexture.ReleaseTemporary(tempTexture);
288
+ m_mat.DisableKeyword(m_renderToTextureKeyword);
289
+
290
+ // m_updateMainTextureEveryFrameがtrueなら、ペイント中は
291
+ // 毎フレームm_paintedTextureの内容をm_mainTextureに読み取る
292
+ // ただし、この読み取りは比較的重い部類の処理なので要注意
293
+ if (m_updateMainTextureEveryFrame)
294
+ {
295
+ UpdateMainTexture();
296
+ }
297
+ }
298
+ }
299
+ }
300
+ ```