はじめに
meshをvoxel化して大量描画して当たり判定もつけてハデに破壊してみました。いくつかの記事に分けてまとめてみます。(最適化はできていないので、コンセプト的な実装として読んでください。)
- unity)映画「ピクセル」みたいにボクセルを大量描画してハデに破壊したい❶ ~cubeの大量描画編~ - のりまき日記
- unity)映画「ピクセル」みたいにボクセルを大量描画してハデに破壊したい❷ ~スクリーンスペース当たり判定編~ - のりまき日記
- unity)映画「ピクセル」みたいにボクセルを大量描画してハデに破壊したい❸ ~meshのvoxelize編~ - のりまき日記
スクリーンスペース当たり判定とは
GBufferで生成されるDepthテクスチャやNormalsテクスチャを使って軽量に当たり判定を行うものです。画面に映っている情報でしか当たり判定できないのでまったく厳密ではないのですが、結構それっぽく見えます。描画時にDepthテクスチャで奥行きを比較し、奥にあればNormalsテクスチャで反射ベクトルを計算して跳ね返すイメージです。
はじめて見たのは以下の記事でした。かっこいいです。 i-saint.hatenablog.com
実装にあたっては以下の記事を参考にさせていただきました。 tips.hecomi.com
DepthテクスチャやNormalsテクスチャ
Deferredレンダリングを行う際に描画用にいくつかの画像が作成されます(GBuffer)。その中に含まれる画像です。今回のプロジェクトはDeferredレンダリングを使っていなかったので別の方法で取り出します。
private void Start() { Camera.main.depthTextureMode = DepthTextureMode.Depth | DepthTextureMode.DepthNormals; ... }
描画負荷は増えますが上記で似たような画像を生成してくれます。shaderに_CameraDepthTexture
や_CameraDepthNormalsTexture
を定義すると取り出せるので見てみます。
memo
複数カメラがあると上記ではうまく取り出せないことがありました。カメラごとに画像を保持したい場合はCommandBufferを使用して以下のようにキープできます。
var buf = new CommandBuffer(); buf.name = "_KeepDepthNormalsTexture"; var depthNormalsTexture = new RenderTexture(Camera.main.pixelWidth, Camera.main.pixelHeight, 24, RenderTextureFormat.ARGB32); buf.CopyTexture(BuiltinRenderTextureType.DepthNormals, depthNormalsTexture); Camera.main.AddCommandBuffer(CameraEvent.BeforeForwardAlpha, buf);
「CommandBuffer」や「ComputeBuffer」や「ComputeShader」など、似たような単語がたくさん出てきてこんがらがります。全然違うものです。
memo
デプスバッファとデプステクスチャは同じじゃないよ
いろいろ調べている時に上記文言をよく見かけました。あまり理解できなかったので今度ちゃんと調べてみようと思いました。
- Multiple cameras depth buffer incorrect in shader - Unity Forum
- Custom shader not writing to depth buffer - Unity Forum
計算用shaderに渡す
void DispatchInit() { ... m_CubeComputeShader.SetVector("_ScreenSize", new Vector2(Camera.main.pixelWidth, Camera.main.pixelHeight)); } void DispatchUpdate() { ... m_CubeComputeShader.SetTextureFromGlobal(m_UpdateKernel, "_CameraDepthTexture", "_CameraDepthTexture"); m_CubeComputeShader.SetTextureFromGlobal(m_UpdateKernel, "_CameraDepthNormalsTexture", "_CameraDepthNormalsTexture"); m_CubeComputeShader.SetMatrix("_MATRIX_VP", GetViewProjectionMatrix(Camera.main)); ... }
画像のほかにViewProjectionMatrixも渡しています。ワールド座標からスクリーンポジションに変換してスクリーン上のuvを計算するのに使います。
note
_CameraDepthNormalsTexture
から法線もデプスも取り出せるのですが、このデプスはLinear01Depth
を通ったのと同様の値になっています。生のデプスはプロジェクション変換時に非線形になるのですが、今回はそっちを使いたいので_CameraDepthTexture
も渡しています。
以下の記事にとても詳しく説明されていました。2つの8bitから16bitに戻すDecodeFloatRG
の実装が面白かったです。
計算用shaderに当たり判定を実装する
inline float3 DecodeViewNormalStereo(float4 enc4) { float kScale = 1.7777; float3 nn = enc4.xyz*float3(2*kScale,2*kScale,0) + float3(-kScale,-kScale,1); float g = 2.0 / dot(nn.xyz,nn.xyz); float3 n; n.xy = g*nn.xy; n.z = g-1; return n; } ... [numthreads(8,1,1)] void Update (uint id : SV_DispatchThreadID) { Particle p = _Particles[id]; if (p.active) { // 移動先を取得 float3 nextPos = p.position + p.velocity * _DeltaTime; // 移動先のスクリーン座標 float4 vpPos = mul(_MATRIX_VP, float4(nextPos, 1.0)); // スクリーン座標からuvにする float2 uv = vpPos.xy / vpPos.w * 0.5 + 0.5; // uvから画像のインデックスにする float2 coord = uv * _ScreenSize; // ピクセルを取り出す float depth = _CameraDepthTexture[coord]; float3 normal = DecodeViewNormalStereo(_CameraDepthNormalsTexture[coord]) * float3(1.0, 1.0, -1.0); // 非線形のデプス float pDepth = vpPos.z / vpPos.w; // 移動先が奥なら if (pDepth < depth) { // 反射ベクトルを計算 p.velocity -= 2 * dot(p.velocity, normal) * normal; } // 空気抵抗を計算 float drug = .3; p.velocity *= (1 - _DeltaTime * drug); // 速度を更新 p.velocity += _Gravity * _DeltaTime; // 位置を更新 p.position += p.velocity * _DeltaTime; // 回転を更新 p.rotation += p.angularVelocity * _DeltaTime; // ライフタイムを更新 p.time += _DeltaTime; p.active = (p.time <= p.lifeTime); _ActiveIndexes.Append(id); } _Particles[id] = p; }
note
inline float3 DecodeViewNormalStereo(float4 enc4) { float kScale = 1.7777; float3 nn = enc4.xyz*float3(2*kScale,2*kScale,0) + float3(-kScale,-kScale,1); float g = 2.0 / dot(nn.xyz,nn.xyz); float3 n; n.xy = g*nn.xy; n.z = g-1; return n; }
UnityCG.cgincに定義されているものを持ってきました。
float3 normal = DecodeViewNormalStereo(_CameraDepthNormalsTexture[coord]) * float3(1.0, 1.0, -1.0);
法線を取得する処理です。以下の記事を参考にしました。
// 反射ベクトルを計算 p.velocity -= 2 * dot(p.velocity, normal) * normal;
以下の記事を参考にしました。壁ずりベクトルを使うと粘性のあるインクなどの表現もできそうです。
// 空気抵抗を計算 float drag = .3; p.velocity *= (1 - _DeltaTime * drag);
抵抗がないとずっと同じ速度で跳ね返り続けるので空気抵抗を入れました。以下の記事にunity内での実装が載っていました。
How drag is calculated by Unity engine.? - Unity Answers
おわり
つぎはメッシュをボクセル化してみます。