2015年1月15日木曜日

【Unity】Android実機でOpenGL描画ができない?シェーダが機能しない?


こいつがアンドロイド実機でどれくらい動くのか少しテストをしてみたんです。
そうしたら実機では前方にあるキューブ以外きれいさっぱり消えてしまった。
エディタ上では問題なく表示されていたし、UnityRemote4でも問題はなかったのに…

消えてしまったのはOpenGLで描画されたものでした。
ということで似たような状況を再現、原因を調べました。


新しいシーンを用意、下記2つのプログラムをカメラにアタッチ。
シーンにはカメラ以外何もありません。

using UnityEngine;
using System.Collections;

public class GLLine2 : MonoBehaviour
{
    private Material m_lineMaterial;
    public int _fieldSide;
    
    Vector3[] _vertices;
    public Color _color;
    
    void Start ()
    {
        // ライン描画用のマテリアルを生成.//
        m_lineMaterial = new Material(
        "Shader \"myShader\" {" +
        "SubShader {" +
        "Pass {" +
        " ZWrite Off" +
        "Blend SrcAlpha One" +
        " Cull Off" + 
        " BindChannels {" +
        " Bind \"vertex\", vertex Bind \"color\", color" +
        " }" +
        "}" +
        "}" +
        "}"
        );//material end

        m_lineMaterial.hideFlags = HideFlags.HideAndDontSave;
        m_lineMaterial.shader.hideFlags = HideFlags.HideAndDontSave;

        //各頂点の座標、初期位置決定
        _vertices = new Vector3[_fieldSide*_fieldSide];
        for(int i=0; i<_fieldSide; i++)
        {
            for(int j=0; j<_fieldSide; j++)
            {
                _vertices[j + _fieldSide * i] = new Vector3(j,0,i);
            }
        }
    }

    void OnRenderObject() {
        if (m_lineMaterial != null) {
        m_lineMaterial.SetPass(0);
        GL.PushMatrix();
        GL.Begin(GL.LINES);
        GL.Color(_color);
        for(int i=0; i<_fieldSide; i++)
        {
            for(int j=0; j<_fieldSide - 1; j++)
            {
                GL.Vertex3(_vertices[j + i*_fieldSide].x, _vertices[j+i*_fieldSide].y, _vertices[j+i*_fieldSide].z);
                GL.Vertex3(_vertices[j+1 +i*_fieldSide].x, _vertices[j+1 +i*_fieldSide].y, _vertices[j+1 +i*_fieldSide].z);
            }
        }
        for(int i=0; i<_fieldSide; i++)
        {
            for(int j=0; j<_fieldSide - 1; j++)
            {
                GL.Vertex3(_vertices[i + j*_fieldSide].x, _vertices[i + j*_fieldSide].y, _vertices[i + j*_fieldSide].z);
                GL.Vertex3(_vertices[i + (j+1)*_fieldSide].x, _vertices[i + (j+1)*_fieldSide].y, _vertices[i + (j+1)*_fieldSide].z);
            }
        }
        GL.End();
        GL.PopMatrix();
        }
    }
}

using UnityEngine;
using System.Collections;

public class TestOpenGL2android : MonoBehaviour
{
    private Material m_material;
    
    void Start ()
        {
        // マテリアルの生成.
        m_material = new Material(
        "Shader \"myShader\" {" +
        "SubShader {" +
        "Pass {" +
        " ZWrite Off" +
        " Cull Off" + 
        " BindChannels {" +
        " Bind \"vertex\", vertex Bind \"color\", color" +
        " }" +
        "}" +
        "}" +
        "}"
        );

        m_material.hideFlags = HideFlags.HideAndDontSave;
        m_material.shader.hideFlags = HideFlags.HideAndDontSave;
    }

    void OnPostRender() 
    {
        if (m_material != null) 
        {
        m_material.SetPass(0);// マテリアルのパス0を割り当て.
        GL.PushMatrix();
        GL.LoadOrtho(); // 2D描画として処理.
        GL.Begin(GL.TRIANGLES);
        GL.Color(Color.red);
        GL.Vertex3( 0.0f, 0.0f, 0.0f );
        GL.Color(Color.green);
        GL.Vertex3(0.0f, 1.0f, 0.0f );
        GL.Color(Color.blue);
        GL.Vertex3(0.5f, 0.5f, 0.0f );
        GL.End();
        GL.PopMatrix();
        }
    }
}

2つ目のプログラムはここのものを参考に、もといまるぱくりです。本当にありがとうございます。
なぜ2つ用意したのかというと OnRenderObject() と OnPostRender() のどちらかが原因になっているかもしれないと思ったからです。
実機に落とし込みます。

正常ですね。
しかしキューブを置くと…

暗くなります。ラインの格子は消えます。
しかし色こそ着いていないものの OpenGL で書かれたトライアングルは消えていません。

ということは OpenGL の描画はできている。ラインも描かれている。
シェーダかマテリアルか、その辺に原因はありそうです。

もう少し調べます。
今度は画面端にキューブを置いてみます。エディア上ではこんなかんじ。

実機

画面内にキューブが入るとキューブ以外のシェーダが機能しなくなるようです。
シーンの中のキューブの有無が直接関係しているわけではないのですね。

1つ目のプログラム、Start内のシェーダをコメントアウトしてみます。

ラインが出てきました。
色についてはやはりキューブが画面内におさまっていると真っ黒になってしまいます。



<追記>
GL.LINES による描画は行われていて、スクリプト内に書かれたシェーダが標準のシェーダと同一画面に収まった時に何らかの理由から機能しなくなるというので正解のようです。
ちなみに最初に GL.LINES によるピンクの格子が消えてしまったのはアルファブレンディングの設定が Blend SrcAlpha One となっていたからでした。
画面左のトライアングルについても同様のアルファブレンディングを指定したところ、やはり同じように消えてしまいました。

解決策は一応見つかった…んですかね。とりあえずこれを見ていただきたい。

Sprite Renderer を持つウサギを描画画面に入れるとスクリプトで割り当てたシェーダの機能が復活、もとい正しく機能するんです。

デフォルトのキューブもしっかり入っています。


以下、他にわかった事を。

実機上では線の色を変更する場合、マテリアルを割り当てる必要がある。
割り当ててるんだけどな…ということでマテリアルを割り当てない場合はどうなるか。
上で示した GLLine2.cs のマテリアル割り当てに関わる部分(m_lineMaterialとシェーダ)をコメントアウトしてみると…

確かにこれはダメですね。
ちなみに画面内にSpriteRendererを持つオブジェクトがあるとこの格子は表示されなくなります。

混迷を極めてまいりました。

マテリアル割り当てる → Gameビューでは問題なく表示される。実機では SpriteRenderer の存在により割り当てたシェーダが機能する。

マテリアル割り当てない → Gameビューでデフォルトのシェーダの有無により割り当てたシェーダが機能しなくなる。かつ、SpriteRenderer の存在により割り当てたシェーダが機能しないどころかOpenGLによる描画すら消す(もしくは透明になっている)

そろそろ勘弁していただきたいのですが…

とりあえず GLLine2.cs のマテリアル割り当ては元に戻します。
続いてもう1つスクリプトを追加します。
using UnityEngine;
using System.Collections;

public class DebugLine : MonoBehaviour {

    // line material
    private Material    m_LineMaterial;

    void Awake()
    {
        m_LineMaterial = createLineMaterial( Color.white );
    }

    void OnRenderObject()
    {
        Vector3 vPos    = Vector3.zero;
        Quaternion qRot = Quaternion.identity;
        Matrix4x4 mtx   = Matrix4x4.TRS( vPos, qRot, Vector3.one );

        GL.PushMatrix();
        {
            GL.MultMatrix( mtx );

            m_LineMaterial.color = Color.yellow;
            m_LineMaterial.SetPass( 0 );
            
            GL.Begin( GL.LINES );
            {
                GL.Vertex( new Vector3(0,0,0) );
                GL.Vertex( new Vector3(0,0,10) );
            }
            GL.End();

            // change color
            m_LineMaterial.color = Color.cyan;
            m_LineMaterial.SetPass( 0 );

            GL.Begin( GL.LINES );
            {
                GL.Vertex( new Vector3(2,0,0) );
                GL.Vertex( new Vector3(2,0,10) );
            }
            GL.End();
        }
        GL.PopMatrix();
    }

    private Material    createLineMaterial( Color color )
    {
        return new Material(
            "Shader \"Lines/Background\" {"+
            "    Properties { "+
            "        _Color (\"Main Color\", Color) = ("+color.r+","+color.g+","+color.b+","+color.a+")"+
            "    }"+
            "    SubShader {"+
            "        Pass {"+
            "            ZWrite on "+
            "            Blend SrcAlpha OneMinusSrcAlpha "+
            "            Colormask RGBA "+
            "            Lighting Off "+
            "            Offset 1, 1 "+
            "            Color[_Color] "+
            "        }"+
            "    }"+
            "}"
            );
    }
}

実機に落とし込みます。縦、



追加したスクリプトが描くラインはデフォルトシェーダと競合していません。(競合って表現は正しいのだろうか?)
DebugLine.cs のマテリアルを GLLine2.cs のマテリアルに近づけていきます。

ZWrite on → ZWrite Off に変更
Blend SrcAlpha OneMinusSrcAlpha → Blend SrcALpha One に変更
BindChannels の追加

ここらへんまでやっても変化が全くありません。ブレンディングを変えたからサチるようにはなりましたが…

Colormask RGBA
Lighting Off
Offset 1, 1

それぞれを消して実機で動作確認を繰り返しても変化なし。(7パターン)

どうもシェーダ内に Color[_Color] がないのが原因な気がしてきました。
GLLine2.cs では GL.Color(_color) としていて _color の値はmetaファイルから読み込んでね!としているのがまずいのではないかと…

最終的にGLLine2.csはこうなりました。
using UnityEngine;
using System.Collections;

public class GLLine2 : MonoBehaviour
{
    private Material m_lineMaterial;
    public int _fieldSide = 50;
    Vector3[] _vertices;
    //public Color _color;

    void Start ()
    {
        // ライン描画用のマテリアルを生成.//
        m_lineMaterial = createLineMaterial(Color.white);
        m_lineMaterial.hideFlags = HideFlags.HideAndDontSave;
        m_lineMaterial.shader.hideFlags = HideFlags.HideAndDontSave;

        //各頂点の座標、初期位置決定
        _vertices = new Vector3[_fieldSide*_fieldSide];
        for(int i=0; i<_fieldSide; i++)
        {
            for(int j=0; j<_fieldSide; j++)
            {
            _vertices[j + _fieldSide * i] = new Vector3(j,0,i);
            }
        }
    }

    void OnRenderObject() {
        if (m_lineMaterial != null) {
        m_lineMaterial.SetPass(0);//setPassはShaderのPass,2passの内1Pass目なら0
        GL.PushMatrix();
        GL.Begin(GL.LINES);

        //GL.Color(_color);//競合で消える
        m_lineMaterial.color = Color.cyan;//反映されるのはcyanのみ
        m_lineMaterial.SetPass( 0 );

        for(int i=0; i<_fieldSide; i++)
        {
            for(int j=0; j<_fieldSide - 1; j++)
            {
                GL.Vertex3(_vertices[j + i*_fieldSide].x, _vertices[j+i*_fieldSide].y, _vertices[j+i*_fieldSide].z);
                GL.Vertex3(_vertices[j+1 +i*_fieldSide].x, _vertices[j+1 +i*_fieldSide].y, _vertices[j+1 +i*_fieldSide].z);
            }
        }

        //GL.Color(Color.yellow);//競合で消える
        m_lineMaterial.color = Color.magenta;
        m_lineMaterial.SetPass( 0 );

        for(int i=0; i<_fieldSide; i++)
        {
            for(int j=0; j<_fieldSide - 1; j++)
            {
                GL.Vertex3(_vertices[i + j*_fieldSide].x, _vertices[i + j*_fieldSide].y, _vertices[i + j*_fieldSide].z);
                GL.Vertex3(_vertices[i + (j+1)*_fieldSide].x, _vertices[i + (j+1)*_fieldSide].y, _vertices[i + (j+1)*_fieldSide].z);
            }
        }

        GL.End();
        GL.PopMatrix();
        }//if end
    }

    private Material createLineMaterial( Color color )
    {
        return new Material(
            "Shader \"myShader\" {" +
            "Properties { "+
            "_Color (\"Main Color\", Color) =  ("+color.r+","+color.g+","+color.b+","+color.a+")"+
            "}"+
            "SubShader {" +
            "Pass {" +
            " ZWrite Off" +
            "Blend SrcAlpha One" +
            " Cull Off" +
            " Color[_Color]"+
            " BindChannels {" +
            " Bind \"vertex\", vertex Bind \"color\", color" +
            " }" +
            "}" +
            "}" +
            "}"
        );//material end
    }
}

Sceneビュー及びGameビュー

実機縦

実機横

実機ではこちらの狙い通り、横線にシアン、縦線にマゼンタを適用できています。
しかしエディタ上では縦横ともにマゼンタになってしまっています。(シェーダエラーじゃないですよ。シアン一色にもできました。)
このやり方でエディタに2色表示させるにはシェーダに2Pass目が必要なのかもしれません。


まとめると、

GL.Color(Color.yellow);
GL.Color(_color); //_colorはpublic指定、インスペクタで値を設定、その値はmetaファイルに保持される。

のようなやり方だと実機でデフォルトシェーダに負け、色が消える。
アルファブレンディングの設定によっては線も消える。(アルファ値ゼロとして扱われるからか?)
実機でデフォルトシェーダと共存するには

割り当てるマテリアル(シェーダ)内に Color を用意
m_lineMaterial.color = Color.magenta;
m_lineMaterial.SetPass(0);

割り当てたマテリアルの持つ変数 .color を変えるとうまくいくようです。


こんなところですかね。
しかしここまで実機とエディタで差が出るとは… シェーダは怖いですね。
最後にひとつ忘れていたので… 使用した端末は Xperia A2 です。他の端末でどうなるかは保証しかねます。これ最初に言うべきだったな…



参考にさせていただいたサイト様はこちら。
  ・Post Position : 【Unity】GLを使ってデバッグ用のライン描画
  ・OnionGinger : Unity GL (1) ライン描画




0 件のコメント:

コメントを投稿