簡易スカルプト機能の作り方

<!-- wp:paragraph -->
![sculpting-ec.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570910/36c38c67-702c-98ce-3a05-5cf1d4956b03.jpeg)

<p>積木製作の北村です。<br />先日、コンクリートをプレイヤーの自由な操作で削りたいという相談を受けました。<br />今回はUnityで簡易的なスカルプト機能を実装してこの表現を作ってみます。</p>

## 今回作ったもの

<p>先に完成図をお見せすると、こんな感じです。</p>
<p><a href="https://tsumikiseisaku.com/blog/images/sculpting/sculpting-Demo.mp4">sculpting-Demo.mp4 </a></p>


<!-- wp:heading -->
## メッシュの準備
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>今回削っていくコンクリートのモデルはこちらです。</p>
<!-- /wp:paragraph -->

<!-- wp:image {"sizeSlug":"large"} -->
<figure class="wp-block-image size-large"><img src="https://tsumikiseisaku.com/blog/images/sculpting/sculpting-SculptMesh_Scene.jpg" alt="" /></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>今回は中央手前の赤枠で囲われた角の部分を削っていきます。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>スカルプトで表現する都合上、削る範囲のメッシュはポリゴンを細かく分割しておく必要があります。</p>
<!-- /wp:paragraph -->

<!-- wp:image {"sizeSlug":"large"} -->
<figure class="wp-block-image size-large"><img src="https://tsumikiseisaku.com/blog/images/sculpting/sculpting-SculptMesh_Wireframe.jpg" alt="" /></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>UVも通常のアセット制作時と同様に開いておきましょう。</p>
<!-- /wp:paragraph -->

<!-- wp:image {"sizeSlug":"large"} -->
<figure class="wp-block-image size-large"><img src="https://tsumikiseisaku.com/blog/images/sculpting/sculpting-SculptMesh_UV.jpg" alt="" /></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>また、今回は切削範囲の境界を目立たせたくない為、<br />スカルプトを適用する強度をメッシュの頂点カラーに記録しておきます。</p>
<!-- /wp:paragraph -->

<!-- wp:image {"sizeSlug":"large"} -->
<figure class="wp-block-image size-large"><img src="https://tsumikiseisaku.com/blog/images/sculpting/sculpting-SculptMesh_VertexColor.jpg" alt="" /></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>メッシュの変形は赤い部分に強く適用され、黒い部分には適用されません。</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
## メッシュの変形
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>では、実際にメッシュの変形を行いましょう。<br />基本的な処理の流れとしては、</p>
<!-- /wp:paragraph -->

<!-- wp:list {"ordered":true} -->
<ol>
<li>頂点同士のつながり方を取得</li>
<li>操作部位から一定の範囲内の頂点のみ操作部位から遠ざける</li>
<li>動かした頂点に対し、頂点同士のつながり方を利用してスムージングを行う</li>
<li>移動後の頂点の位置関係とつじつまが合うように法線を再計算する</li>
</ol>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>となります。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>1については、クラスの初期化の段階でC#で頂点の状況をチェックして、<br />得られた結果をComputeShaderに渡します。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>2は頂点の位置から移動の方向を取得できるのでシンプルです。</p>
<!-- /wp:paragraph -->


https://gist.github.com/TSUMIKISEISAKU/6d2c841b630c431d26830261aa6885ab


<!-- wp:paragraph -->
<p>今回はメッシュが必ず決まった方向に凹むので、頂点の移動の方向に制限を掛けています。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>3については、操作中の頂点と繋がっている複数頂点の位置の加重平均を取ることにします。<br />操作中の頂点に近い頂点ほど計算結果に与える影響が強くなります。</p>
<!-- /wp:paragraph -->

<!-- wp:image {"sizeSlug":"large"} -->
<figure class="wp-block-image size-large"><img src="https://tsumikiseisaku.com/blog/images/sculpting/sculpting-SculptingSystemDiagram_01.jpg" alt="" /></figure>
<!-- /wp:image -->

<!-- wp:image {"sizeSlug":"large"} -->
<figure class="wp-block-image size-large"><img src="https://tsumikiseisaku.com/blog/images/sculpting/sculpting-SculptingSystemDiagram_02.jpg" alt="" /></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>以下の資料のLaplacian Smoothingについての解説を参考に実装しました。<br /><a href="https://graphics.stanford.edu/courses/cs468-12-spring/LectureSlides/06_smoothing.pdf">https://graphics.stanford.edu/courses/cs468-12-spring/LectureSlides/06_smoothing.pdf</a></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
https://gist.github.com/TSUMIKISEISAKU/37a913d980605628d981921d04a7e2b9
<p>&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>4については、以下の記事を参考に三角ポリゴン単位で計算を行います。<br /><a href="https://qiita.com/aa_debdeb/items/486456e8352070055fac">https://qiita.com/aa_debdeb/items/486456e8352070055fac</a></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
https://gist.github.com/TSUMIKISEISAKU/4345e284a70b1d6438bedc990f1fc3ff
<!-- /wp:paragraph -->

<!-- wp:image {"sizeSlug":"large"} -->
<figure class="wp-block-image size-large"><img src="https://tsumikiseisaku.com/blog/images/sculpting/sculpting-SculptingSystemDiagram_03.jpg" alt="" /></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>これらの処理をComputeShaderで実装していきます。</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
## テクスチャの適用
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>今回はコンクリートの破砕の表現なので、削った部分のテクスチャの切り替えも併せて実装します。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>テクスチャの切り替えを実装するには、どのような切り替え方をしたいかをRenderTextureに記録する必要があります。<br />今回はスカルプトのComputeShaderの中で、操作対象の頂点付近のピクセルを断面用のテクスチャの領域として記録します。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>処理の流れは以下のようになります。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
##### 1 操作対象の頂点からUV座標を取得
<p>ComputeShaderに渡した頂点の情報から、動かす頂点のUV座標を取得します。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
##### 2 取得したUV座標の周囲のピクセルをテクスチャ変更範囲として記録
<p>RenderTexture上で、1で取得したUV座標の周囲のピクセルの値を変更します。<br />以下にメッシュのUVレイアウトのイメージで図を書いてみました。<br />図の中央の頂点が操作対象で、その周囲の赤い部分がテクスチャを差し替える範囲になるイメージです。</p>
<!-- /wp:paragraph -->

<!-- wp:image {"sizeSlug":"large"} -->
<figure class="wp-block-image size-large"><img src="https://tsumikiseisaku.com/blog/images/sculpting/sculpting-SculptingSystemDiagram_04.jpg" alt="" /></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>以下は実際に記録されたRenderTextureの例です。<br />黒以外の色になっている領域がテクスチャを断面用の物に差し替える範囲です。</p>
<!-- /wp:paragraph -->

<!-- wp:image {"sizeSlug":"large"} -->
<figure class="wp-block-image size-large"><img src="https://tsumikiseisaku.com/blog/images/sculpting/sculpting-UVOffset_RGB.jpg" alt="" /></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
##### 3 断面のテクスチャを更新の度にオフセットさせる為、そのオフセット値をランダムに生成
オフセット値はRenderTextureのR, Gチャンネルに記録します。<br />テクスチャを横にずらす際のオフセット値はR、縦にずらす場合はGチャンネルに記録しています。</p>
<!-- /wp:paragraph -->

<!-- wp:image {"sizeSlug":"large"} -->
<figure class="wp-block-image size-large"><img src="https://tsumikiseisaku.com/blog/images/sculpting/sculpting-UVOffset_R.jpg" alt="" /></figure>
<!-- /wp:image -->

<!-- wp:image {"sizeSlug":"large"} -->
<figure class="wp-block-image size-large"><img src="https://tsumikiseisaku.com/blog/images/sculpting/sculpting-UVOffset_G.jpg" alt="" /></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
##### 4 コンクリートのシェーダーでRenderTextureを読み込み、テクスチャを切り替え
シェーダー内での個々のテクスチャのマッピングは、今回の場合Triplanarを使うのが便利です。</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
## 実装例
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>実際にテストシーンで使ったComputeShaderはこちらです。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><a href="https://gist.github.com/TSUMIKISEISAKU/5123d8c583770c94be2d7c6d7d34e37f">ConcreteSculpting.compute</a></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>&nbsp;</p>
<p>上記のComputeShaderを呼び出すC#スクリプトは以下の物を使いました。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><a href="https://gist.github.com/TSUMIKISEISAKU/dea0a0a77a091ba48fc85a6442425082">SculptManager.cs</a></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><a href="https://gist.github.com/TSUMIKISEISAKU/89f94a2e965ac479cc5a9b0f69277451">KernelParamsHandler.cs</a></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>&nbsp;</p>
<p>描画されるメッシュの変形にはGraphicsBufferとRWByteAddressBuffer、ByteAddressBufferを使用しております。<br />インタラクションに使用するためのMeshColliderの更新にも同じ方法を使いたかったのですが、うまくMeshColliderが更新されませんでした…<br />その為、代わりにRWStructuredBufferから取得した値を配列に入れてMeshCollider用のメッシュに代入しています。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>その他、テストシーンでの入力取得に以下のスクリプトも使用しています。<br /><a href="https://gist.github.com/TSUMIKISEISAKU/1e8ad7f9d10ab01633437bef1d284b9b">ConcreteBreakTrigger_Demo.cs</a><br /><a href="https://gist.github.com/TSUMIKISEISAKU/9e12490d42c1485f705c7af718201df8">ConcreteSculptManager.cs</a><br /><a href="https://gist.github.com/TSUMIKISEISAKU/e4fcfdb4ebe24715c569773e20d226dd">ControllerInputHandler.cs</a></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>ここに各種シェーダーやテクスチャ、パーティクル等を加えて完成すると以下のようになります。</p>
<p><a href="https://tsumikiseisaku.com/blog/images/sculpting/sculpting-Demo.mp4">sculpting-Demo.mp4</a><br /><a </p>
<!-- /wp:paragraph -->

<!-- wp:video --><!-- /wp:video -->

<!-- wp:heading -->
## 参考
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>今回は以下のページを参考に実装しました。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>・<a href="https://graphics.stanford.edu/courses/cs468-12-spring/LectureSlides/06_smoothing.pdf">Stanford University: Mesh Smoothing</a></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>・<a href="https://www.youtube.com/watch?v=-FluPdNtMr4">UC Davis: Mesh Smoothing</a></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>・<a href="https://gamedevbill.com/shader-graph-normal-calculation/">Game Dev Bill: How to Calculate Shader Graph Normal</a></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>・<a href="https://qiita.com/aa_debdeb/items/486456e8352070055fac">【Unity】Advanced Mesh APIでメッシュの頂点をCompute Shaderで動かす</a></p>
<!-- /wp:paragraph -->