質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.46%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

3回答

1789閲覧

unity 3D 切り取り

T_T2

総合スコア1

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2021/11/19 02:30

編集2021/11/19 02:34

前提・実現したいこと

物体(削られる側)と対象A(削る側)が位置しており、物体に対象Aが接触すると物体が削れていく3D Unityを作成したい。
ゲームでは3Dで彫刻ができるイメージで、物体(削られる側)対象A(削る側)はblenderで出力された.objが使えればよいと考えています。
2020.4にBongoさんが回答されていたスクリプトhttps://teratail.com/questions/252026?sort=2#3794を拝見しましたが、応用できなかったので質問させていただきました。unityで3Dの切り取りは可能でしょうか。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Bongo

2021/11/19 19:42

確かにあれは見た目こそ3Dではあるものの、実態は2Dでの削り取りですからね... 後ろ向きな意見で申しわけないのですが、何かもっと妥協できる要素はないでしょうかね? といいますのも、もし本気で削られる側も任意のOBJ、削る側も任意のOBJとなりますと、以前別の方の「unityでgameobjectを部分的に消去したい」(https://teratail.com/questions/210980 )で挙げさせていただいた「Simple and Robust Boolean Operations for Triangulated Surfaces」(https://arxiv.org/pdf/1308.4434.pdf )みたいな方法で頑張らないといけない気がしまして、2Dの「unity 切り取り」ですら3回答にまたがってしまったことを考えると、回答に投稿するには相当長大になってしまう、まともな速度で動かせるか分からない、そもそも実装できるか自信がない...という印象です(自身の勉強には面白そうな題材ですので挑戦するのはやぶさかではないのですが、おそらくだいぶ時間がかかってしまうでしょう...)。 「unityでgameobjectを部分的に消去したい」の回答で申し上げた妥協案の場合、モデルを単なるキューブの集合体で表現しているため1回答におさまりました。また、さらに別の方で「unity オブジェクト 衝突 凹ませる」(https://teratail.com/questions/287774 )ではSkinnedMeshRendererで直方体をへこませてみましたが、彫刻のような精密な変形はできないでしょう。 アセットストアを探してみても、なかなかこれぞというものはなさそうな感じですね... 「Sculpt」で検索(https://assetstore.unity.com/?category=tools&q=Sculpt&orderBy=1 )してみたところ出てきたVoxel Generator(https://assetstore.unity.com/packages/tools/terrain/voxel-generator-162883 )だとかはすごそうに思えますが、任意のモデル同士で削り取りを行うものではなさそうに見えます。 EditorSculpt(https://assetstore.unity.com/packages/tools/modeling/editorsculpt-63320 )も面白そうですが、エディター上でのモデリングであることと、「任意のモデルを任意のモデルで削り取る」というのとはちょっと違うかな...という印象ですね。 何か「これと似た挙動にしたい」みたいな実例をご提示いただけると実現(あるいは妥協点の模索)の手がかりになるかもしれません。
T_T2

2021/11/20 01:48

Bongoさんから直接ご連絡いただけるとは思ってもいませんでした!こんな抽象的な質問にも丁寧に対応してくださって感謝しかありません。 さて本題ですが、添付された資料を拝見しましたが、やはり現状では一筋縄ではいかなそうですね… しかしそのなかでも、https://teratail.com/questions/287774に記載されていた「ブロック表面に沿ってSphereColliderを持ったオブジェクト」をみて思いついたのですが、任意のOBJをすべて細かいドット状にして、その細かいドットに当たったものを消していく?ような方法はいかかでしょうか。 自分が考えている彫刻は、小さいものでアクセサリーなんかを細かくかたちずくれたらいいなと思っています。
guest

回答3

0

パート3

削り役の棍棒もシーンに配置し、こちらには下記スクリプトをアタッチしました。ですが実際のところこちらのスクリプトは削り取りを実演してみせる上での脇役で、マウス操作に応じて棍棒を動かし、スペースキーで削り取りを実行する機能しかありません。

lang

1using System.Collections; 2using UnityEngine; 3 4[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] 5public class Carver : MonoBehaviour 6{ 7 [SerializeField] private Carvee target; 8 [SerializeField][Range(0.0f, 50.0f)] private float rotationSpeed = 10.0f; 9 [SerializeField][Range(0.0f, 5.0f)] private float pushPullSpeed = 1.0f; 10 [SerializeField] private Color inactiveColor = Color.green; 11 [SerializeField] private Color activeColor = Color.red; 12 13 private Material carverMaterial; 14 private Mesh carverMesh; 15 private float distance; 16 private float horizontalAngle; 17 private float verticalAngle; 18 19 private IEnumerator Start() 20 { 21 yield return null; 22 Cursor.lockState = CursorLockMode.Locked; 23 var meshFilter = this.GetComponent<MeshFilter>(); 24 this.carverMesh = meshFilter.sharedMesh; 25 var meshRenderer = this.GetComponent<MeshRenderer>(); 26 this.carverMaterial = meshRenderer.material; 27 var relativePosition = this.transform.position - this.target.Center; 28 this.distance = relativePosition.magnitude; 29 var direction = relativePosition.normalized; 30 this.verticalAngle = Mathf.Asin(direction.y) * Mathf.Rad2Deg; 31 this.horizontalAngle = Mathf.Atan2(direction.z, direction.x) * Mathf.Rad2Deg; 32 } 33 34 private void Update() 35 { 36 if (this.carverMesh == null) 37 { 38 return; 39 } 40 41 // マウス左右で水平に、上下で垂直に回転し 42 // 左ボタン・右ボタンで前後に動かす 43 var mouseX = Input.GetAxis("Mouse X"); 44 var mouseY = Input.GetAxis("Mouse Y"); 45 var push = Input.GetMouseButton(0) ? this.pushPullSpeed * Time.deltaTime : 0.0f; 46 var pull = Input.GetMouseButton(1) ? this.pushPullSpeed * Time.deltaTime : 0.0f; 47 this.distance = Mathf.Max(0.0f, (this.distance - push) + pull); 48 this.horizontalAngle = (this.horizontalAngle + (mouseX * this.rotationSpeed)) % 360.0f; 49 this.verticalAngle = Mathf.Clamp(this.verticalAngle + (mouseY * this.rotationSpeed), -90.0f, 90.0f); 50 var h = this.horizontalAngle * Mathf.Deg2Rad; 51 var v = this.verticalAngle * Mathf.Deg2Rad; 52 var sinH = Mathf.Sin(h); 53 var cosH = Mathf.Cos(h); 54 var sinV = Mathf.Sin(v); 55 var cosV = Mathf.Cos(v); 56 var direction = new Vector3(cosH * cosV, sinV, sinH * cosV); 57 var center = this.target.Center; 58 this.transform.position = center + (direction * this.distance); 59 this.transform.LookAt(center); 60 61 // スペースキーを押している間、毎フレームCarveを行う 62 if (Input.GetKey(KeyCode.Space)) 63 { 64 this.carverMaterial.color = this.activeColor; 65 this.target.Carve(this.carverMesh, this.transform); 66 } 67 else 68 { 69 this.carverMaterial.color = this.inactiveColor; 70 } 71 } 72 73 private void OnDestroy() 74 { 75 Cursor.lockState = CursorLockMode.None; 76 } 77}

プレイモードにすると、Carveeが元々のメッシュをベースにモアイ像の断面を描き、ボリュームテクスチャを生成します。
スクリプト内のvolumeは下図のようになっており、Carve実行によってボクセルを消していくことで削られたように見せようと考えました。

図4

削る様子はこんな感じです。彫刻...というにはちょっと精密さに欠け、貴重な遺物を破壊しているだけになってしまいました。

図5

弱点としては、やはりある程度の解像度がないと精密に削れない点でしょうね。今回はvoxelsPerUnitが128で、身長をおよそ2mに調整した状態でボクセル化しており、テクスチャのサイズは120×95×272です。元々のモアイ像は下図のようになめらかですが...

図6

今回の程度のボクセル密度だと、実行時の外観は3Dプリンターで成形したばかりのようにデコボコです。

図7

かといってもっと解像度を上げようにも、縦横高さが2倍になると体積は8倍に膨れ上がってしまいますから簡単にはいかないでしょうね...

また、彫り上がったものを保存する機能は省略しました。保存するとしたらボリュームデータとしてそのまま保存するか、あるいはマーチングキューブ法だとかで表面を張り、メッシュに変換してやることになるかと思います。

投稿2021/11/22 10:27

編集2021/11/22 10:31
Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

パート2

断面描画用、およびシーン上での描画用に下記シェーダーを作成しました。

ShaderLab

1Shader "Volume/Slice" 2{ 3 Properties 4 { 5 _MainTex ("Texture", 2D) = "white" {} 6 } 7 SubShader 8 { 9 // メッシュの断面を描くパス 10 // 両面描画とし、表面なら透明、裏面なら白で塗る 11 // これを使い、閉じたポリゴンモデルをニアクリップ面で切断するような構図で 12 // 描画すると、切断面から裏面を覗く形になって断面の形が描かれるはず 13 Pass 14 { 15 Cull Off 16 17 CGPROGRAM 18 #pragma vertex vert 19 #pragma fragment frag 20 #include "UnityCG.cginc" 21 22 float4 vert(float4 vertex : POSITION) : SV_POSITION 23 { 24 return UnityObjectToClipPos(vertex); 25 } 26 27 fixed4 frag(fixed faceDirection : VFACE) : SV_Target 28 { 29 return (1.0 - faceDirection) * 0.5; 30 } 31 ENDCG 32 } 33 34 // 断面を削り取るパス 35 // 削られる側の断面画像を削る側の断面画像で削る際に使う 36 // 削る側の断面を白黒反転し、削られる側の断面に 37 // 乗算合成することで削り取る 38 Pass 39 { 40 Cull Off 41 ZWrite Off 42 ZTest Always 43 Blend DstColor Zero 44 45 CGPROGRAM 46 #pragma vertex vert 47 #pragma fragment frag 48 #include "UnityCG.cginc" 49 50 struct appdata 51 { 52 float4 vertex : POSITION; 53 float2 uv : TEXCOORD0; 54 }; 55 56 struct v2f 57 { 58 float4 vertex : SV_POSITION; 59 float2 uv : TEXCOORD0; 60 }; 61 62 v2f vert(appdata v) 63 { 64 v2f o; 65 o.vertex = UnityObjectToClipPos(v.vertex); 66 o.uv = v.uv; 67 return o; 68 } 69 70 sampler2D _MainTex; 71 72 fixed4 frag(v2f i) : SV_Target 73 { 74 return 1.0 - tex2D(_MainTex, i.uv); 75 } 76 ENDCG 77 } 78 } 79} 80

ShaderLab

1Shader "Volume/Volume" 2{ 3 Properties 4 { 5 _MainTex ("Volume", 3D) = "white" {} 6 _Color ("Color", Color) = (1.0, 1.0, 1.0, 1.0) 7 } 8 SubShader 9 { 10 Tags { "Queue" = "AlphaTest" "RenderType" = "Opaque" } 11 12 Pass 13 { 14 Tags { "LightMode" = "ForwardBase" } 15 16 Cull Back 17 ZTest Always 18 19 CGPROGRAM 20 #pragma vertex vert 21 #pragma fragment frag 22 #pragma multi_compile_fwdbase 23 #include "UnityCG.cginc" 24 #include "Lighting.cginc" 25 26 #define DIRECTIONAL_EPSILON 0.0009765625 27 #define INITIAL_STEP_LENGTH 0.03125 28 #define TERMINAL_EXTENSION (INITIAL_STEP_LENGTH * 2.0) 29 #define STEP_COUNT 64 30 #define EXTRA_DEPTH_BIAS 0.03125 31 32 sampler3D _MainTex; 33 fixed4 _Color; 34 float3 _VoxelSize; 35 float3 _BoundsSize; 36 float4x4 _VolumeToLocal; 37 float4x4 _LocalToVolume; 38 sampler2D _CameraDepthTexture; 39 40 float3 worldToObjectPos(float3 worldPos) {return mul(unity_WorldToObject, float4(worldPos, 1.0)).xyz;} 41 float3 objectToWorldPos(float3 localPos) {return mul(unity_ObjectToWorld, float4(localPos, 1.0)).xyz;} 42 float3 worldToObjectVec(float3 worldVec) {return mul((float3x3)unity_WorldToObject, worldVec);} 43 float3 objectToWorldVec(float3 localVec) {return mul((float3x3)unity_ObjectToWorld, localVec);} 44 float3 getWorldCameraPos() {return UNITY_MATRIX_I_V._14_24_34;} 45 float3 getWorldCameraDir() {return -UNITY_MATRIX_V._31_32_33;} 46 bool isPerspective() {return any(UNITY_MATRIX_P._41_42_43);} 47 48 float3 getWorldCameraOrigin(float3 worldPos) 49 { 50 return isPerspective() 51 ? getWorldCameraPos() 52 : worldPos + dot(getWorldCameraPos() - worldPos, getWorldCameraDir()) * getWorldCameraDir(); 53 } 54 55 float3 getVolumeBoundsFaces(float3 volumePos, float3 volumeViewDir, float faceOffset) 56 { 57 float3 signs = sign(volumeViewDir); 58 return -(signs * (volumePos - 0.5) + faceOffset) / (abs(volumeViewDir) + (1.0 - abs(signs)) * DIRECTIONAL_EPSILON); 59 } 60 61 float getVolumeBoundsFrontFace(float3 volumePos, float3 volumeViewDir) 62 { 63 float3 lengths = getVolumeBoundsFaces(volumePos, volumeViewDir, 0.5); 64 return max(max(max(lengths.x, lengths.y), lengths.z), 0.0); 65 } 66 67 float sampleOpaqueZ(float4 screenPos) 68 { 69 float rawDepth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(screenPos)); 70 return isPerspective() 71 ? LinearEyeDepth(rawDepth) 72 : -dot(unity_CameraInvProjection._33_34, float2(_ProjectionParams.x * (rawDepth * 2.0 - 1.0), 1.0)); 73 } 74 75 // worldPosからworldViewDir方向を見た時の、ボリューム空間における衝突点を求める 76 // wはヒットした場合1.0、ヒットしなかった場合-1.0となる 77 float4 getVolumeContact(float3 worldPos, float3 worldViewDir, float4 screenPos) 78 { 79 float3 worldOrigin = getWorldCameraOrigin(worldPos); 80 float3 localOrigin = worldToObjectPos(worldOrigin); 81 float3 volumeOrigin = mul(_LocalToVolume, float4(localOrigin, 1.0)).xyz; 82 float maxZ = min(dot(worldPos - worldOrigin, worldViewDir), sampleOpaqueZ(screenPos)); 83 float3 worldTerminal = worldOrigin + worldViewDir * maxZ; 84 float3 localTerminal = worldToObjectPos(worldTerminal); 85 float3 volumeTerminal = mul(_LocalToVolume, float4(localTerminal, 1.0)).xyz; 86 float3 volumeViewDir = normalize(volumeTerminal - volumeOrigin); 87 float3 volumeContact = volumeOrigin; 88 float terminalDistance = distance(volumeOrigin, volumeTerminal) + TERMINAL_EXTENSION; 89 float progress = getVolumeBoundsFrontFace(volumeOrigin, volumeViewDir); 90 float step = INITIAL_STEP_LENGTH; 91 float sign = 1.0; 92 float hit = -1.0; 93 [loop] 94 for (int i = 0; i < STEP_COUNT; i++) 95 { 96 progress += step; 97 if (progress > terminalDistance) 98 { 99 hit = -1.0; 100 break; 101 } 102 volumeContact = volumeOrigin + volumeViewDir * progress; 103 if ((tex3D(_MainTex, volumeContact).r - 0.5) * sign > 0.0) 104 { 105 hit = 1.0; 106 sign *= -1.0; 107 step *= -0.5; 108 } 109 } 110 return float4(volumeContact, hit); 111 } 112 113 // ボリューム空間上の座標から、その周辺を観察してワールド法線を求める 114 // 傾きを算出できなかった場合はゼロベクトルを返す 115 float3 getWorldNormal(float3 volumePos) 116 { 117 float3 volumeNormal; 118 volumeNormal.x = tex3D(_MainTex, volumePos - float3(_VoxelSize.x, 0.0, 0.0)).r - tex3D(_MainTex, volumePos + float3(_VoxelSize.x, 0.0, 0.0)).r; 119 volumeNormal.y = tex3D(_MainTex, volumePos - float3(0.0, _VoxelSize.y, 0.0)).r - tex3D(_MainTex, volumePos + float3(0.0, _VoxelSize.y, 0.0)).r; 120 volumeNormal.z = tex3D(_MainTex, volumePos - float3(0.0, 0.0, _VoxelSize.z)).r - tex3D(_MainTex, volumePos + float3(0.0, 0.0, _VoxelSize.z)).r; 121 float3 localNormal = mul(volumeNormal, (float3x3)_LocalToVolume); 122 float3 worldNormal = mul(localNormal, (float3x3)unity_WorldToObject); 123 float normalSqrLength = dot(worldNormal, worldNormal); 124 if (normalSqrLength > 0.0) 125 { 126 return worldNormal / sqrt(normalSqrLength); 127 } 128 return 0.0; 129 } 130 131 struct v2f 132 { 133 float4 vertex : SV_POSITION; 134 float3 worldPos : TEXCOORD0; 135 float3 worldViewDir : TEXCOORD1; 136 float4 screenPos : TEXCOORD2; 137 }; 138 139 v2f vert(float4 vertex : POSITION) 140 { 141 v2f o; 142 o.vertex = UnityObjectToClipPos(vertex); 143 o.worldPos = objectToWorldPos(vertex.xyz); 144 o.worldViewDir = isPerspective() ? -UnityWorldSpaceViewDir(o.worldPos) : getWorldCameraDir(); 145 o.screenPos = ComputeScreenPos(o.vertex); 146 return o; 147 } 148 149 half4 frag(v2f i) : SV_Target 150 { 151 float4 contact = getVolumeContact(i.worldPos, normalize(i.worldViewDir), i.screenPos); 152 clip(contact.w); 153 float3 worldNormal = getWorldNormal(contact.xyz); 154 155 // ディレクショナルライト1個のランバートライティングで 156 // 影も省略した簡易な(手抜きの)陰影付け 157 half3 color = max(dot(_WorldSpaceLightPos0.xyz, worldNormal), 0.0) * _LightColor0 * _Color.rgb + ShadeSH9(half4(worldNormal, 1.0)); 158 return half4(color, 1.0); 159 } 160 ENDCG 161 } 162 } 163}

モアイ像はスケールを調整して身長2mぐらいでシーン上に配置しました。
モアイ像にCarveeをアタッチし、これらシェーダーファイルをsliceShadervolumeShaderにセットしています。

図3

なお、もう一つMoaiControllerというものがアタッチされていますが、これは下記のようにモアイ像をくるくる回すだけのもので、本題の削り取りとは関係ありません。

lang

1using UnityEngine; 2 3public class MoaiController : MonoBehaviour 4{ 5 [SerializeField][Range(0.0f, 90.0f)] private float angularSpeed = 90.0f; 6 7 private void Update() 8 { 9 this.transform.Rotate(0.0f, this.angularSpeed * Time.deltaTime, 0.0f, Space.World); 10 } 11}

(パート3へ続く...)

投稿2021/11/22 10:26

Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

モデルを細かいドットの集合体として扱うとおっしゃるのも候補として適切だと思います。ボクセル的なアプローチと言えるかもしれませんね。

その方針でどうなるか試してみました。実験材料として、まず削られる役は大英博物館によるモアイ像を...

図1

削る役として自前でちょこっと作った謎の棍棒を用意しました。

図2

モアイ像をインポートすると、ガワとなるゲームオブジェクトの中に「default」という名称のメッシュオブジェクトが入った構造のプレハブになるかと思います。このdefaultを取り出して「Moai」と名前を変え、下記スクリプトをアタッチしました。

lang

1using UnityEngine; 2using UnityEngine.Rendering; 3 4[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] 5public class Carvee : MonoBehaviour 6{ 7 [SerializeField] private Shader sliceShader; // ここにインスペクター上で断面描画用シェーダーをセットする 8 [SerializeField] private Shader volumeShader; // ここにインスペクター上でボリューム描画用シェーダーをセットする 9 [SerializeField][Min(0)] private int voxelsPerUnit = 128; // 1mあたりのボクセル数 10 [SerializeField] private Color color = Color.white; 11 12 private Bounds volumeBounds; 13 private Material initialMaterial; 14 private Material sliceMaterial; 15 private Material volumeMaterial; 16 private Matrix4x4 orthoMatrix; 17 private Matrix4x4 sliceMatrix; 18 private Mesh sourceMesh; 19 private Mesh volumeMesh; 20 private MeshFilter meshFilter; 21 private MeshRenderer meshRenderer; 22 private RenderTexture slice0; 23 private RenderTexture slice1; 24 private RenderTexture volume; 25 private readonly Vector3[] carverBoundsVertices = new Vector3[8]; 26 private Vector3 initialScale; 27 private Vector3 voxelSize; 28 private Vector3Int textureSizeInt; 29 30 public Vector3 Center => this.transform.TransformPoint(this.volumeBounds.center); 31 32 private void Start() 33 { 34 // 本来のメッシュのバウンディングボックス、オブジェクトのスケール、 35 // voxelsPerUnitをもとにボリュームのサイズを決める 36 this.meshFilter = this.GetComponent<MeshFilter>(); 37 this.sourceMesh = this.meshFilter.sharedMesh; 38 this.initialScale = this.transform.localScale; 39 var sourceBounds = this.sourceMesh.bounds; 40 var scaledSourceBounds = new Bounds( 41 Vector3.Scale(sourceBounds.center, this.initialScale), 42 Vector3.Scale(sourceBounds.size, this.initialScale)); 43 var textureSize = scaledSourceBounds.size * this.voxelsPerUnit; 44 this.textureSizeInt = new Vector3Int( 45 Mathf.CeilToInt(textureSize.x) + 2, 46 Mathf.CeilToInt(textureSize.y) + 2, 47 Mathf.CeilToInt(textureSize.z) + 2); 48 textureSize = this.textureSizeInt; 49 50 // 本来のメッシュを包むようにボリューム描画用メッシュを作る 51 this.volumeBounds = new Bounds(scaledSourceBounds.center, textureSize / this.voxelsPerUnit); 52 var vMin = this.volumeBounds.min; 53 var vMax = this.volumeBounds.max; 54 this.volumeMesh = new Mesh 55 { 56 name = this.sourceMesh.name + " Volume", 57 vertices = new[] 58 { 59 vMin, 60 new Vector3(vMax.x, vMin.y, vMin.z), 61 new Vector3(vMin.x, vMax.y, vMin.z), 62 new Vector3(vMax.x, vMax.y, vMin.z), 63 new Vector3(vMin.x, vMin.y, vMax.z), 64 new Vector3(vMax.x, vMin.y, vMax.z), 65 new Vector3(vMin.x, vMax.y, vMax.z), 66 vMax 67 }, 68 triangles = new[] 69 { 70 0, 1, 2, 3, 2, 1, 71 0, 2, 4, 6, 4, 2, 72 0, 4, 1, 5, 1, 4, 73 7, 5, 6, 4, 6, 5, 74 7, 3, 5, 1, 5, 3, 75 7, 6, 3, 2, 3, 6 76 } 77 }; 78 this.volumeMesh.RecalculateBounds(); 79 this.meshFilter.sharedMesh = this.volumeMesh; 80 81 // 単純化のため、オブジェクト自体のスケールは1倍とした 82 this.transform.localScale = Vector3.one; 83 84 // ボリュームを作る 85 this.volume = new RenderTexture(this.textureSizeInt.x, this.textureSizeInt.y, 0, RenderTextureFormat.R8) 86 { 87 dimension = TextureDimension.Tex3D, 88 volumeDepth = this.textureSizeInt.z, 89 useMipMap = false 90 }; 91 this.volume.Create(); 92 93 // ボリュームにメッシュの断面を描く 94 var activeTexture = RenderTexture.active; 95 this.sliceMaterial = new Material(this.sliceShader); 96 this.slice0 = new RenderTexture(this.textureSizeInt.x * 4, this.textureSizeInt.y * 4, 24, RenderTextureFormat.R8); 97 this.slice1 = new RenderTexture(this.textureSizeInt.x * 2, this.textureSizeInt.y * 2, 0, RenderTextureFormat.R8); 98 this.slice0.Create(); 99 this.slice1.Create(); 100 var volumeSize = this.volumeBounds.size; 101 this.sliceMatrix = Matrix4x4.TRS( 102 Vector3.zero, 103 Quaternion.identity, 104 new Vector3( 105 this.initialScale.x / volumeSize.x, 106 this.initialScale.y / volumeSize.y, 107 this.initialScale.z / volumeSize.z)) * Matrix4x4.TRS( 108 -sourceBounds.center, 109 Quaternion.identity, 110 Vector3.one); 111 var modelMatrix = this.sliceMatrix; 112 var translationZ = modelMatrix.m23; 113 this.voxelSize = new Vector3(1.0f / textureSize.x, 1.0f / textureSize.y, 1.0f / textureSize.z); 114 this.orthoMatrix = Matrix4x4.Ortho(-0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f); 115 GL.PushMatrix(); 116 GL.LoadIdentity(); 117 GL.LoadProjectionMatrix(this.orthoMatrix); 118 for (var i = 1; i < (this.textureSizeInt.z - 1); i++) 119 { 120 RenderTexture.active = this.slice0; 121 modelMatrix.m23 = translationZ - ((i + 0.5f) * this.voxelSize.z); 122 this.sliceMaterial.SetPass(0); 123 GL.Clear(true, true, Color.clear); 124 Graphics.DrawMeshNow(this.sourceMesh, modelMatrix); 125 Graphics.Blit(this.slice0, this.slice1); 126 Graphics.Blit(this.slice1, this.volume, 0, i); 127 } 128 GL.PopMatrix(); 129 RenderTexture.active = activeTexture; 130 131 // sliceMatrixはゲーム実行中にCarveを行う際にも使用されるが、 132 // Carvee自体のスケールは1倍に修正しているので、それに合わせて 133 // 変更を加えておく 134 this.sliceMatrix = Matrix4x4.TRS( 135 Vector3.zero, 136 Quaternion.identity, 137 new Vector3( 138 1.0f / volumeSize.x, 139 1.0f / volumeSize.y, 140 1.0f / volumeSize.z)) * Matrix4x4.TRS( 141 -this.volumeBounds.center, 142 Quaternion.identity, 143 Vector3.one); 144 145 // ボリューム描画用マテリアルを作り、レンダラーにセットする 146 this.meshRenderer = this.GetComponent<MeshRenderer>(); 147 this.initialMaterial = this.meshRenderer.sharedMaterial; 148 this.volumeMaterial = new Material(this.volumeShader); 149 this.meshRenderer.sharedMaterial = this.volumeMaterial; 150 var volumeToLocal = Matrix4x4.TRS(vMin, Quaternion.identity, volumeSize); 151 var localToVolume = Matrix4x4.Inverse(volumeToLocal); 152 this.volumeMaterial.mainTexture = this.volume; 153 this.volumeMaterial.color = this.color; 154 this.volumeMaterial.SetVector("_VoxelSize", this.voxelSize); 155 this.volumeMaterial.SetVector("_BoundsSize", volumeSize); 156 this.volumeMaterial.SetMatrix("_VolumeToLocal", volumeToLocal); 157 this.volumeMaterial.SetMatrix("_LocalToVolume", localToVolume); 158 } 159 160 // 削り取りを行う時は、削りに使うメッシュとそれのTransformを引数にしてCarveを実行する 161 public void Carve(Mesh carverMesh, Transform carverTransform) 162 { 163 // まず削りメッシュとボリュームの交差範囲を求めて、 164 // 更新するテクスチャの範囲を決める 165 var v = this.carverBoundsVertices; 166 var carverLocalBounds = carverMesh.bounds; 167 v[0] = carverLocalBounds.min; 168 v[7] = carverLocalBounds.max; 169 v[1] = new Vector3(v[7].x, v[0].y, v[0].z); 170 v[2] = new Vector3(v[0].x, v[7].y, v[0].z); 171 v[3] = new Vector3(v[7].x, v[7].y, v[0].z); 172 v[4] = new Vector3(v[0].x, v[0].y, v[7].z); 173 v[5] = new Vector3(v[7].x, v[0].y, v[7].z); 174 v[6] = new Vector3(v[0].x, v[7].y, v[7].z); 175 var min = new Vector3(Mathf.Infinity, Mathf.Infinity, Mathf.Infinity); 176 var max = new Vector3(Mathf.NegativeInfinity, Mathf.NegativeInfinity, Mathf.NegativeInfinity); 177 foreach (var carverLocalPosition in v) 178 { 179 var carveeLocalPosition = 180 this.transform.InverseTransformPoint(carverTransform.TransformPoint(carverLocalPosition)); 181 min = Vector3.Min(min, carveeLocalPosition); 182 max = Vector3.Max(max, carveeLocalPosition); 183 } 184 if (!this.volumeBounds.Intersects(new Bounds((min + max) * 0.5f, max - min))) 185 { 186 return; 187 } 188 var volumeMinZ = this.volumeBounds.min.z; 189 var volumeMaxZ = this.volumeBounds.max.z; 190 int GetIndex(float z, float minZ, float maxZ, int texSize) => Mathf.Clamp( 191 Mathf.RoundToInt((Mathf.InverseLerp(minZ, maxZ, z) * texSize) - 0.5f), 192 0, 193 texSize - 1); 194 var fromIndex = GetIndex(Mathf.Max(volumeMinZ, min.z), volumeMinZ, volumeMaxZ, this.textureSizeInt.z); 195 var toIndex = GetIndex(Mathf.Min(volumeMaxZ, max.z), volumeMinZ, volumeMaxZ, this.textureSizeInt.z); 196 197 // ボリュームをメッシュの形で削り取る 198 var activeTexture = RenderTexture.active; 199 var modelMatrix = this.sliceMatrix * this.transform.worldToLocalMatrix * carverTransform.localToWorldMatrix; 200 var translationZ = modelMatrix.m23; 201 GL.PushMatrix(); 202 GL.LoadIdentity(); 203 GL.LoadProjectionMatrix(this.orthoMatrix); 204 for (var i = fromIndex; i <= toIndex; i++) 205 { 206 RenderTexture.active = this.slice0; 207 modelMatrix.m23 = translationZ - ((i + 0.5f) * this.voxelSize.z); 208 this.sliceMaterial.SetPass(0); 209 GL.Clear(true, true, Color.clear); 210 Graphics.DrawMeshNow(carverMesh, modelMatrix); 211 Graphics.Blit(this.slice0, this.slice1); 212 Graphics.Blit(this.slice0, this.volume, this.sliceMaterial, 1, i); 213 } 214 GL.PopMatrix(); 215 RenderTexture.active = activeTexture; 216 } 217 218 private void OnDestroy() 219 { 220 this.meshRenderer.sharedMaterial = this.initialMaterial; 221 this.meshFilter.sharedMesh = this.sourceMesh; 222 this.transform.localScale = this.initialScale; 223 Destroy(this.sliceMaterial); 224 Destroy(this.volumeMaterial); 225 Destroy(this.volumeMesh); 226 Destroy(this.volume); 227 Destroy(this.slice0); 228 Destroy(this.slice1); 229 } 230}

(パート2へ続く...)

投稿2021/11/22 10:26

Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

T_T2

2021/11/22 13:47

素晴らしいです!やりたいことができていて感動しました。 早速実行してみたのですが、どうもプレイモードにするとUnityが強制終了してしまします。 色々と落ちる原因を探ってみますと、「Carvee」によるボリュームテクスチャ生成時の実行が原因のようです。この解決策はありますでしょうか。
T_T2

2021/11/22 14:34

追記です! 単純な立方体で実行してみたところ、正常に動きました!PCの限界が来ていたのかもしれません... 今後は、形作ったデータの保存や表面のガタガタへの対策をしていこうかと思います。 この技術力は凄まじいですね...今後もつまずいたらご相談させていただきたいと思います! 本当にありがとうございました。
T_T2

2021/11/24 06:33

D3D11: Failed to create render texture resource (3D, 1164x2821x1392 mips=1 dxfmt=60, hr=0x0) 任意のオブジェクトを実行しようとすると、上記のエラーメッセージが表示されていまいます。 サイズが小さすぎるのでしょうか?
Bongo

2021/11/24 06:51

生成されるテクスチャのサイズが大きすぎるようですね。メッセージに1164x2821x1392と表示されているようですが、普通の2Dのテクスチャにたとえれば1164x2821のテクスチャを1392枚確保することに相当するはずです。ヤバさをご想像いただけますでしょうか... オブジェクトのサイズをもっと小さくしたり、voxelsPerUnitをもっと小さくしたりしてボリュームのサイズを抑えないとならないでしょう。 回答中でも申し上げましたが、精密にするには解像度を上げたいけれど、3Dテクスチャは簡単にサイズが膨れ上がってハードウェアの限界に達しやすいのが最大の弱点ですね...
fana

2021/11/24 07:03

用いている実装手段を全く知らない者が適当ほざきますが, Octree的な構造でボクセルデータを軽くできたり…みたいな可能性はないのでしょうか?
Bongo

2021/11/24 10:19 編集

いえ、貴重なご意見ありがとうございました。可能性としてはあり得るかと思います。追記依頼欄でちょっと言及しましたVoxel Generatorの紹介ムービーでは、掘っている部分の周辺だけ局所的に分割数を上げているようなシーンがあり(https://youtu.be/_Hc3H5EhoTI?t=72 )、ボリュームの全体の大きさとボクセルの精密さを両立させるには、ああいった工夫が必要なのかもしれません。 今回はボリューム全体を一塊の3Dテクスチャにする方針にしてしまいましたので、ボクセルの密度は全領域で一定になってしまいました。八分木的なデータ構造にするとなると、だいぶ改修を加えないとならなそうです... [追記] Sparse Voxel Octree(https://ja.wikipedia.org/wiki/Sparse_Voxel_Octree )みたいな分野の文献が手がかりになるかもしれませんね。面白そうではありますが、あいにくこういった技術は未経験で、仮にやってみるにしてもじっくり下調べが必要そうです。
T_T2

2021/11/26 01:37 編集

貴重なご意見ありがとうございます! Sparse Voxel Octreeに関する論文を読みましたが、理論理屈など言いたいことはわかるのですが、実装させるとなると厳しいですね... これは新たな質問になると思いますので、新たなQuestionとして投稿させて頂きたいと思います。
fana

2021/11/26 01:34

> Sparse Voxel Octree 私が Octree的な構造でどうの とか言った際のイメージは,まさにこれです. (ボクセルのデータサイズ削減 と 当たり判定処理の枝刈り の両方に利用できそうに思うけど,実装しろと言われると激しくつらそう)
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.46%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問