うら干物書き

ゲームを作りたい!

【Unity】カメラに映っていないメッシュを映っていないにもかかわらず描画する

 見えないところで起こっていることはわからないものです。
 私は過去、平行世界を三回ほど救ったことがあるのですが、この世界の人々は気づいてすらいません。

 さて、というわけで今回はカメラに映っていないメッシュを映っていないにもかかわらず描画することにチャレンジしてみます。

 映っていないのに描画する必要があるのか?
 それは誰も称賛しないのに世界を救う必要があるのか? という問いかけと同じ意味なのです。

 わかりましたね。それでは描画していきますよ。


この記事にはUnity2017.3.0f3を使用しています。

事の発端は先日作ったシェーダー

 話が少し変わりますが、視錐台カリングというものがあります。
 視錐台をフラスタムとよんでフラスタムカリングとも言っていたような気もしますが、そんなカリングがあった気がします。
docs.unity3d.com

 カメラの視錐台内のものは描画して、外のものは描画しないというものですねー。

f:id:urahimono:20180120205709p:plain
f:id:urahimono:20180120205722p:plain

 これはとってもいいことです。
 カメラに映っていないのなら、わざわざ描画の処理をする必要ないですもんね。
 描画するのだってタダじゃないんですから。パソコンやスマホだってカロリー使いますよ。

 他にもおくるーじょんカリングというものもあった気がしますが、それはまた別のお話です。
docs.unity3d.com

 ただ今回は冒頭で話した通り、カメラに映っていないものを描画したいのです。
 まあFrameDebuggerを使うならいざしらず、カメラに映っていないところを描画の有無なんざ把握しようがないんですけど。

 何故こんなことをやろうと思ったのかと言えば、先日作成した描画位置をずらすシェーダーを作ったときに起きた問題が事の発端です。
www.urablog.xyz

 このシェーダーを使ったマテリアルのプロパティにオフセットを設定することで描画位置を実際のオブジェクトの位置からずらすことが出来ます。

f:id:urahimono:20180120205738p:plain

 ただこのオブジェクトを移動させていくと、本来オブジェクトが描画されるあたりの位置がカメラの視錐台から外れた場合に、ずらして描画する位置がカメラの視錐台内でも描画されなくなってしまうのです。

f:id:urahimono:20180120205753g:plain
f:id:urahimono:20180120205825p:plain

 マジかよ。
 ただでさえ、作ったはいいけど何に使えばわからないシェーダーなのに、より使いづらくなってしまう。
 何か手を考えるとしましょう。

バウンディングボリュームをいじって何とかする

 さて、この問題を解決するにはどうすればいいのでしょう。
 そもそも問題なのは私の許可なく視錐台カリングなどというもので、勝手に描画物を分別しているところでしょう。
 こういう差別的な行為が、学校などのイジメにつながっていくのです。
 こんな悪い視錐台カリングは無効にしてしまいましょう。

 ……。
 うん、そんなやり方知らん!

 視錐台カリングのOnOffのようなプロパティなんざ見たことないですね。
 そもそも視錐台カリングを無効にした日なんかには、描画処理がパンクしてしまうでしょうね。

 うーん、どうしようかな。
 このオブジェクトの描画の有効範囲をもう少し大きくするだけでいいんだけどな。
 これとかどうかな、バウンディングボリューム
 Renderer.boundsをいじることで何とかならないだろうか。
docs.unity3d.com

ForceRenderer.cs

using UnityEngine;

[RequireComponent( typeof( Renderer ) )]
public class ForceRenderer : MonoBehaviour
{
    [SerializeField]
    private float m_expandAmount = 100.0f;

    private void Start()
    {
        var renderer = GetComponent<Renderer>();
        renderer.bounds.Expand( m_expandAmount );
    }

} // class ForceRenderer

f:id:urahimono:20180120205848p:plain

 何も変わらんね。
 そもそもこのスクリプト、ちゃんと機能してんのかな。
 ログの出力処理を追加してboundsの値を確認してみましょう。

ForceRenderer.cs

using UnityEngine;

[RequireComponent( typeof( Renderer ) )]
public class ForceRenderer : MonoBehaviour
{
    [SerializeField]
    private float m_expandAmount = 100.0f;

    private void Start()
    {
        var renderer = GetComponent<Renderer>();
        Debug.LogFormat( "Before:{0}", renderer.bounds.size );

        renderer.bounds.Expand( m_expandAmount );
        Debug.LogFormat( "After:{0}", renderer.bounds.size );
    }

} // class ForceRenderer

Before:(1.0, 1.0, 1.0)
After:(1.0, 1.0, 1.0)

 値が変わっとらんね。何故だ!
 Boundsについてもう少し調べてみましょう。
docs.unity3d.com

 あっ、これ構造体だった。
 ということは値型だねー。
 それじゃRenderer.boundsで取得したBoundsの値を変更しても何の意味もないね。
 こちらでBoundsを作成し、Renderer.boundsにセットしてあげるようにしなくてはいけないね。

 ……。
 あー……、Renderer.boundsは読み取り専用だわ。Getプロパティしかない。
 どうやら私の戦いはここまでのようだ。

 んー、Rendererではダメだったが、Meshそのものはどうだろう。
 MeshFilter経由でMeshを取得し、Meshにあるboundsをいじる方法はどうだろうか。
 Mesh.boundsはSetプロパティもあるから、好き勝手できるはずだ。
docs.unity3d.com

ForceRenderer.cs

using UnityEngine;

[RequireComponent( typeof( MeshFilter ) )]
public class ForceRenderer : MonoBehaviour
{
    [SerializeField]
    private float m_expandAmount = 100.0f;

    private void Start()
    {
        var meshFilter  = GetComponent<MeshFilter>();

        var bounds      = meshFilter.mesh.bounds;
        bounds.Expand( m_expandAmount );
        meshFilter.mesh.bounds  = bounds;
    }

} // class ForceRenderer

f:id:urahimono:20180120205903g:plain
f:id:urahimono:20180120205916p:plain

 やった! 描画されたぞ!
 まあ、boundsの値の設定方法はもっとマシな計算方法を使ったほうがいいだろうけど。
 Expand()100ぶっこむとか適当にもほどがあるしな。
 同じメッシュ全てに適応させていいなら、sharedMeshの使用も検討してみてもよさそうだ。
 先日作ったシェーダーのとき同様にランタイム時しか動かないから、ランタイム時以外でも対応する場合はExecuteInEditModeを使ってスクリプトを改良する必要がありそうですねー。

 しかし、長々とやってきたんだけど、このboundsをいじくりまわして描画範囲を変更する方法は、果たして正攻法なのだろうか。
 ちょーっと無理やり感があるような気がするんだけど。

 もう少し方法を検証したいところだけど、私に助けを呼ぶ声が聞こえてくる。
 どうやらまた並行世界の平和が乱れてしまったらしい。
 やむを得まい。
 プログラムはここまでにして、正義の英雄の仕事に戻ろうか。