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) ライン描画




2015年1月13日火曜日

【Unity】 CSVとの連携

CSVファイルを使ってフィールドを作成したいと思ったのでCSVファイルとの連携について書きます。今回は読み込みのみ。

まずCSVファイルを用意します。今回は50×50、計2500個のデータを有するCSVファイルを用意しました。

用意したCSVファイルの拡張を.txtに変え、Assets/Resources の中に入れます。Resourcesフォルダがない場合は作ってください。

テキストにするとカンマが入ります。

続いてプログラムはこちら
using UnityEngine;
using System.Collections;

public class CSVReader : MonoBehaviour {
    int _size = 50;
    string[,] _box;

    void Start () 
    {
        _box = new string[_size, _size];

        //テキストファイルの読み込み
        TextAsset _fieldTxt = Resources.Load("CSV/field0", typeof(TextAsset)) as TextAsset;
        //区切り条件指定
        //char[] _kugiri = {'\r','\n'};//改行区切り、二種類
        char[] _kugiri = {'\n'};
        //String配列に格納
        string[] _fieldString = _fieldTxt.text.Split(_kugiri);

        for(int i = 0; i < _size; i++)
        {
            string[] _tempLetter = _fieldString[i].Split(',');

            for(int j=0; j < _size ; j++)
            {
                _box[j, i] = _tempLetter[j];
                Debug.Log (j+":"+i+" ; " + _box[j,i]);
            }
        }
    }
}

再生するとコンソールはこんな感じになります。

ちゃんとCSVファイルからデータを読み込み、stringの配列に格納できていますね。
このままではstring型なのでint.Parseなどでint型に変換してフィールドに落とし込みます。

CSVデータ適用前

適用後


上に書いたプログラムはtempLetter[]なんて使ってるあたり酷いし必ずもっといいやり方があるはずなので探してみてください。というかどなたかやり方教えてください。

//char[] _kugiri = {'\r','\n'};//改行区切り、二種類
がコメントアウトされているのは50×50のデータを1本のストリングで扱おうとした時の名残りです。
'\r' をカンマだと思っていたんですがそもそもそれが間違っていて、カンマを同時に指定する方法がなかなか見つからなかったので逃げに走りました。


以下私的メモ


■ Error CS0101

error CS0101: The namespace `global::' already contains a definition for `GLLine'

同じ名前のスクリプトがあるときに出るエラー。上記エラーの場合、

public class GLLine : MonoBehaviour { ~~ }

これがプロジェクト内に2つあります。
スクリプトをコピーして名前を変え忘れてるとかそんなところだと思います。

2015年1月4日日曜日

【Unity】MeshTopology.Lines と vertices

前回 MeshTopology.Quads をいじっている過程で MeshTopology.Lines が使えるなと思い、これでピラミッドが作りたいなと思ったので作ってみました。

ちなみにこれが今までのピラミッドです。

トポロジーを Lines にするとこうなります。

頂点数65535個という上限でこんな頂点の使い方をしているとフィールドが狭くなってしまうし、プログラムが面倒になるしであまり良いことがないのでガッツリ削り取りたいんですよね。
正直四角メッシュが使えればこんなことで悩む必要なかったんですが…

とりあえずFieldを作ってみます。

ここまでは問題なくできました。
あとは頂点の座標をいじり隆起させるだけです。

できませんでした。
確かに指定した頂点座標は変化していました。しかしメッシュは一向に動かず。

こんな風に動いてくれるはずだったんですが…

トポロジーを Lines にしたことか、もしくは triangles を完全放棄してるのが原因かな?少し検証します。



<追記>

できました。どうも再描画する時に

_mesh.vertices = _vertices;
GetComponent<MeshFilter>().mesh = _mesh;
_meshFilter.mesh.SetIndices(MakeIndices(), MeshTopology.Lines, 0);

この3つをセットでやらなければならないようです。
デフォルトのトポロジーなら一行目だけで済んだんですが少しやることが増えているみたい。

_meshFilter.mesh.SetIndices(MakeIndices(), MeshTopology.Lines, 0);
とか必要ない気もするんですけどねぇ…でもこれ入れないと頂点動かした瞬間メッシュが全部消えてしまうんですよね。



<追追記>
このやり方、mesh をどんどん書き換えるわけですが Gameビューの Statisticsを 見てもらえればわかる通りえらいメモリを喰います。
書き換える mesh が小さいものならいいんですが頂点数が大きいものだとあっという間に VRAM usage が跳ね上がり、1GB超えたあたりでフリーズもしくはクラッシュしますので気をつけてください。

Lineを引きたいだけならOpenGLを直接叩いた方が軽いようなので自分はそうしました。

2015年1月3日土曜日

【Unity】MeshTopology.Quads, 四角ポリゴンについて

四角ポリゴンがほしい(切実)

UnityはOpenGLが使えるはずなのになんで四角ポリゴンが使えないの?
読み込まれた四角ポリゴンは勝手に三角ポリゴンにされてしまいます。

↓三角ポリゴンにされたパンダくん。こいつはBlender上では四角ポリゴンで作られていたのだ。


今回は MeshTopology なるものをなんとかすれば四角ポリゴンが使えないかな?と思って試行錯誤し、その過程で得られたものをまとめることにします。


超参考: 自習室 UnityでありもののMeshをワイヤーフレームで描画する


すばらしいですね。
MeshTopology.Lines を使うことでプリミティブな Sphere をここまでかっこよくすることができるとは…
このプログラムについては自習室さんの方で丁寧に解説されているのでそちらを参考にしてください。

今回はあくまでMeshTopology.Quadsが使えるか使えないかということのみに焦点を絞ります。
参考にしてつくったプログラムがこちら

using UnityEngine;
using System.Collections;

public class MakeQuads : MonoBehaviour {

    Mesh _mesh;
    private MeshFilter _meshFilter;
    private Vector3[] _newVertices;

    void Awake()
    {
        //Reference
        _meshFilter = GetComponent<MeshFilter>();
    }

    void Start () 
    {
        _mesh = new Mesh();
        _newVertices = new Vector3[4];
        Vector2[] _newUV = new Vector2[4];

        //Set vertices
        _newVertices[0]=new Vector3(0,0,0);
        _newVertices[1]=new Vector3(1,0,0);
        _newVertices[2]=new Vector3(0,0,1);
        _newVertices[3]=new Vector3(1,0,1);

        //Set UV
        _newUV[0]= new Vector2(0,0);
        _newUV[1]= new Vector2(1,0);
        _newUV[2]= new Vector2(0,1);
        _newUV[3]= new Vector2(1,1);

        _mesh.vertices = _newVertices;
        _mesh.uv = _newUV;

        //Meshの設定、名前付け
        GetComponent<MeshFilter>().mesh = _mesh;
        GetComponent<MeshFilter>().mesh.name = "LineQuadMesh";

        _meshFilter.mesh.SetIndices(MakeIndices(), MeshTopology.Quads, 0);

        //再計算、必要?SetIndicesの前でも後でも表示は変わらなかった。
        _mesh.RecalculateNormals();
        _mesh.RecalculateBounds();

        //MeshTopologyの確認
        MeshTopology topo = _meshFilter.mesh.GetTopology(0);
        Debug.Log(topo); // Quads と出力されるはず
    }

    int[] MakeIndices()
    {
        int[] indices = new int[4];

        indices[0]=0;
        indices[1]=2;
        indices[2]=3;
        indices[3]=1;

        return indices;
    }
}

こいつを空のオブジェクトにアタッチ。
それから Mesh Filter と Mesh Renderer のコンポーネントを持たせて再生してみてください。

ピンクの四角いオブジェクトが出ると思います。
ちゃんと四角い形です。コンソールにはQuadsの文字が出ているはずです。
MeshTopology.Quadsを使う ということは一応成功しているわけですね。

しかしワイヤーフレームを表示するとわかりますが三角ポリゴンで構成されています。

ピンクなのはマテリアルを持たせていないからなので適当にマテリアルを作って貼り付ければ問題ありません。ついでに画像を用意してテクスチャとして貼ってみましょう。


エディタで作れるQuadと大差ないですね。
ということで MeshTopology.Quads では四角ポリゴンは作れないという結果で終わってしまいました。
MeshTopology.Quads はテッセレーションシェーディングで使う以外使い道はなさそうです。

以下メモのようなもの

MakeIndices() におけるインデックス番号の設定の仕方によってワイヤーフレームの表示、表示される面の向きが変わります。
時計周り、時計の反対周り、Z字など試した結果、時計周りに配置すれば上から見えることがわかりました。



このまま終わるのは悔しいので擬似的に作ってみました。

左側がエディタで作った Quad 、右側が擬似です。
各頂点の頂点座標を指定し、トポロジーを Lines にしてつなげたものになります。頂点数は4、インデックスは8になります。
四角ポリゴンっぽく見えます。が、面はありませんので面にテクスチャを貼ることはできません。

一応トポロジーが Lines でもテクスチャ貼ることはできるんですがこうなります。

右は普通の Sphere ですのでそのトポロジーは Triangles になります。

2015年1月1日木曜日

テスト


【Unity】Shaderはじめました。その3.5

あけましておめでとうございます。2015年になってしまいました。

前回のその3にて頂点色を表示するシェーダというものがありました。

オブジェクトが真っ白であまりにも味気ないのと、結局頂点色ってなんなの?という思いがありましたので追記ということで少しやっていくことにします。


【Vertex color】


Shader !Debug/Vertex color {
    SubShader {
        Pass {
            Fog { Mode Off }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            // vertex input: position, color
            struct appdata {
                float4 vertex : POSITION;
                fixed4 color : COLOR;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                fixed4 color : COLOR;
            };

            v2f vert (appdata v) {
                v2f o;
                o.pos = mul( UNITY_MATRIX_MVP, v.vertex );
                o.color = v.color;
                return o;
            }

            fixed4 frag (v2f i) : COLOR0 { return i.color; }
            ENDCG
        }
    }
}

シェーダそのものは前回と同じものを使用します。相変わらず真っ白ですね。
中央のキューブはそのまま、左右のキューブはエディタでスケールを変えたものになります。
今回はオブジェクトに以下のスクリプトをアタッチします。

using UnityEngine;
using System.Collections;

public class vcolor : MonoBehaviour {

    void Start()
    {
        Mesh mesh = GetComponent<MeshFilter>().mesh;
        Vector3[] vertices = mesh.vertices;
        int[] triangles = mesh.triangles;
        Color[] colors = new Color[vertices.Length];
        int i = 0;

        while (i < vertices.Length)
        {
            colors[i] = Color.Lerp(Color.red, Color.green, vertices[i].y);
            i++;
        }
        mesh.colors = colors;

        //頂点位置の確認
        for(int j = 0; j < vertices.Length; j++)
        {
            Debug.Log ("vertices " + j + " : "+ vertices[j]);
        }

        //triangles確認
        for(int j = 0; j < vertices.Length; j++)
        {
            Debug.Log ("triangles " + j + " : "+ triangles[j]);
        }
    }
}

頂点位置とtrianglesの確認は消していただいてかまいません。
while文とその周りが重要です。再生するとこのようになります。


大したことはしていません。順を追ってやっていきましょう。

■Mesh mesh = GetComponent<MeshFilter>().mesh;
MeshFilterコンポーネントの参照をします。
まずここで参照しないと続く .vertices や .colors などのMeshクラスの持つ変数が利用できないので必ずしてください。

■Color[] colors = new Color[vertices.Length];
頂点の数だけ色を用意したいので配列を作り、領域を確保します。
vertices.Length が頂点の数になります。Vector3[] vertices の配列の長さを返しているわけです。

■colors[i] = Color.Lerp(Color.red, Color.green, vertices[i].y);
while文を使って全頂点に適用しています。
頂点のy座標が0なら赤、1以上なら緑になるように色を設定しています。
Lerp()を使うことでグラデーションを表現しています。

この3つのオブジェクト、色の変化が似ていると思いませんか?
明らかにy座標違うじゃん!もっと色に違いがあるべきなんじゃないの?!と思うのが自然です。

キューブの頂点らは以下のようになっています。


黒文字で書かれているのが頂点座標です。Vector3 vertices[n] にあたるものです。
その絶対値は全て0.5です。エディタ上でオブジェクトのスケールをどんなに変えてもこの値が変わることはありませんでした。
なので大きさが違ったり形が違ったりしても色が同じように付くのです。

もう少し実験してみましょう。先ほどまでアタッチしていたスクリプトをはずし、新たに次のスクリプトをアタッチします。

using UnityEngine;
using System.Collections;

public class vcolor2 : MonoBehaviour 
{

    // Use this for initialization
    void Start () 
    {
        Mesh mesh = GetComponent<MeshFilter>().mesh;
        Vector3[] vertices = mesh.vertices;
        Color[] colors = new Color[vertices.Length];

        //個別に設定
        colors[5] = Color.red;
        colors[6] = Color.blue;
        colors[12] = Color.green;
        colors[20] = Color.yellow;

        mesh.colors = colors;
    }
}

再生するとこのように色が付きます。

greenはどこにあるのかというと下の面にあります。


色を指定した頂点の周りには色がついています。また指定していない部分は黒くなっています。
色のつく範囲ですが、色指定された頂点のある triangle上のみに限られています。
これはこのキューブが12枚の triangle で構成されているからです。
黄色に染まっている面と青に染まっている面は見かけ上つながっていますが実際にはつながっていないということです。
見かけ上このキューブは8個の頂点を持っています。しかし実際は24個の頂点を持っているのです。
そして同じ座標にある vertices[6], vertices[12], vertices[20] はそれぞれ独立していて連動しません。vertices[6] を動かしても他の2つは微動だにしません。

もう少し実験しましょう。
次はコイツです。色指定する頂点以外の頂点をすべて透明にします。

using UnityEngine;
using System.Collections;

public class vcolor2 : MonoBehaviour 
{
// Use this for initialization
    void Start () 
    {
        Mesh mesh = GetComponent<MeshFilter>().mesh;
        Vector3[] vertices = mesh.vertices;
        Color[] colors = new Color[vertices.Length];

        for(int i = 0; i < vertices.Length; i++)
        {
            colors[i] = Color.clear;
        }

        //個別に設定
        colors[5] = Color.red;
        colors[6] = Color.blue;
        colors[12] = Color.green;
        colors[20] = Color.yellow;
        mesh.colors = colors;
    }
}

これで透明に…ならない?!

あれ?おかしいな…と思って色々いじってみたんですが状況は

・頂点色無指定の状態だと真っ白
・何か一つでも指定すれば指定していないものはRGBA全て 0.0 となり黒く表示される
・Alpha 0.0 でも透明にならない

というところから動かず。
各頂点の alpha はしっかり 0.0 になっているのでこれはもうシェーダの問題だ。
Vertex color はそういうシェーダなんだ。

そんなことで片づけてしまうのは非常に悔しいのですがシェーダの中身をいじっても透明にならないので打ち切ります。


ざんねん わたしの ぼうけんは これで おわってしまった




<追記>




すばらしい。すばらしいぞ。
やったことはシェーダのPassの頭の部分(Fogの真上)に
    ZWrite Off
    Blend SrcAlpha OneMinusSrcAlph
の2行を追加しただけ。
標準的なアルファブレンディングだそうです。
ブレンディングについてはここ

2014年12月31日水曜日

【Unity】 Shaderはじめました。その3

こんにちは。はじまりました、その3。

Unity公式の頂点プログラムへ頂点データ流し込み / Providing vertex data to vertex programs

ここにあるサンプルを使っていきます。
トーラスノットモデル?そんなものうちにはないよ。


【VertexInputSimple】

画像の真ん中にあるキューブが無回転のもの

Shader VertexInputSimple {
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            
            struct v2f {
                float4 pos : SV_POSITION;
                fixed4 color : COLOR;
            };

            v2f vert (appdata_base v){
                v2f o;
                o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
                o.color.xyz = v.normal * 0.5 + 0.5;
                o.color.w = 1.0;
                return o;
            }

            fixed4 frag (v2f i) : COLOR0 { return i.color; }

            ENDCG
        }
    } 
}

回転のかかっているものは少し色が変わっているのがわかるでしょうか?
これは o.color.xyz = v.normal * 0.5 + 0.5; の v.normal が変わっているからですね。
この v.normal は3次元のベクトルであり、Global座標の値なので、オブジェクトが回転すると変化します。
無回転のキューブの場合、各面の法線ベクトルと o.color.xyz の値(RGB 0~255 換算)は下図のようになります。

+0.5 が0~255換算だと +128 にあたります。

おまけ:今作ってるピラミッドにこのシェーダを適用してみました。


ピラミッドの端の部分、色の変わり目の色の変化がなだらかですね。(そのせいで三角がたくさん見えます)
本当はもっとソリッドにキッチリキッカリ変化させたいんですけどね…これは今後の自分の課題です。
ちなみにこれ、メッシュはちゃんと直線のみで構成されています。ピクセル単位でみるとsmoothingされてしまうのか多少変わってしまうようです。


【UV 1】


Shader !Debug/UV 1 {
    SubShader {
        Pass {
            Fog { Mode Off }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            // vertex input: position, UV

            struct appdata {
                float4 vertex : POSITION;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
            };

            v2f vert (appdata v) {
                v2f o;
                o.pos = mul( UNITY_MATRIX_MVP, v.vertex );
                o.uv = float4( v.texcoord.xy, 0, 0 );
                return o;
            }

            half4 frag( v2f i ) : COLOR {
                half4 c = frac( i.uv );
                if (any(saturate(i.uv) - i.uv))
                c.b = 0.5;
                return c;
            }

            ENDCG
        }
    }
}

画像だけみると少し前にやったシェーダにとても似ていますね。
各面、各ピクセルの色がUV座標(texcoord)の値によって変化しています。
UV座標は黒赤緑黄で示され、UV座標外(0~1以外)の値に対しては青色が適用されるようです。
UV座標のデバッグに使える便利なシェーダらしいです。

そういえば texcoord : TEXCOORD0 は float4 なんですね。使っているのは xy だけですが zw には何が入っているんでしょう?
あ、0入ってましたね。すみません。この値いじると何か変わるんですかね?


■ Fog { Mode Off }
霧(フォグ)の指定

■ v2f
このネーミング、Vertex to Fragment の略みたいですね。

half4
ベクトル型、16bit浮動小数点ベクトル。
ちなみに float4 は32bit浮動小数点ベクトル、int4 は32bit符号付き整数ベクトル。

frac()
入力された値の小数部を返す。入力は0以上1未満の値。
それ以外の値は if(any(saturate~~))で処理するのか?

any()
入力された値のいずれかの成分が0以外である場合は True, それ以外の場合は False を返す。
any(saturate(i.uv) - i.uv) の値は i.uv の値が0~1の場合0になるので False を返します。それ以外の場合 True を返し、if文が実行されます。

saturate()
入力された値を0~1の範囲にクランプする。
clamp()と同じ、clamp()は範囲の最大と最小を設定できる。

■ c.b
c.b = 0.5; この .b はなんでしょう。普通に考えれば blue の b なんですが…
もしくは half4 の上から順に a, b, c, d で…そうすると c になるから違いますね。
とりあえずここで例外(0~1以外)を青く染めます。


【UV 2】


手前が UV 2 です。多少違いますね。逆側から見るとこうなります。


Shader !Debug/UV 2 {
    SubShader {
        Pass {
            Fog { Mode Off }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            // vertex input: position, second UV
            struct appdata {
                float4 vertex : POSITION;
                float4 texcoord1 : TEXCOORD1;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
            };

            v2f vert (appdata v) {
                v2f o;
                o.pos = mul( UNITY_MATRIX_MVP, v.vertex );
                o.uv = float4( v.texcoord1.xy, 0, 0 );
                return o;
            }

            half4 frag( v2f i ) : COLOR {
                half4 c = frac( i.uv );
                if (any(saturate(i.uv) - i.uv))
                c.b = 0.5;
                return c;
            }
            ENDCG
        }
    }
}

マルチテクスチャとかやる時に使うんですかね。 Unityでは1つのオブジェクトに2つのテクスチャが使えるらしいです。 逆に言えば2つしか使えない。3つ以上使う方法もあるにはあるらしいですが僕はわかりません。

このコード… UV 1 と全く同じです。違うのは TEXCOORD0 が TEXCOORD1 になっているだけです。
この 0 と 1 で2つのテクスチャを管理しているんですね。


【Vertex color】


Shader !Debug/Vertex color {
    SubShader {
        Pass {
            Fog { Mode Off }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            // vertex input: position, color
            struct appdata {
                float4 vertex : POSITION;
                fixed4 color : COLOR;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                fixed4 color : COLOR;
            };

            v2f vert (appdata v) {
                v2f o;
                o.pos = mul( UNITY_MATRIX_MVP, v.vertex );
                o.color = v.color;
                return o;
            }

            fixed4 frag (v2f i) : COLOR0 { return i.color; }
            ENDCG
        }
    }
}

頂点色を表示するシェーダになります。キューブもシリンダーも何に適用しても真っ白になりました。
頂点位置と頂点色を struct appdata に持たせそれを最後まで継承し、使っています。
Unityで生成できるオブジェクト(キューブとかスフィアとか)の頂点色、デフォルトが白ってことなんですかね。


【Normals】
一番最初にやった VertexInputSimple と同じです。


【Tangents】

.normal を .tangent に変えてるだけです。出てくる色が変わります。
接線ベクトルの値で色づけしています。
真ん中のキューブの一番手前の面と上の面、同じ色ですね。
濃水色なんですがRGBの値は255換算で (0, 128, 128) でした。
接線ベクトルに直すと(-1, 0, 0)になります。
これで法線ベクトルと接線ベクトルの値がわかったので各面の従法線ベクトルの値も、それによってつく色も予測できますね。実際にやってみましょう。


【Binormals】


Shader !Debug/Binormals {
    SubShader {
        Pass {
            Fog { Mode Off }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            // 頂点入力: position, normal, tangent
            struct appdata {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float4 color : COLOR;
            };

            v2f vert (appdata v) {
                v2f o;
                o.pos = mul( UNITY_MATRIX_MVP, v.vertex );
                // 従法線を計算
                float3 binormal = cross( v.normal, v.tangent.xyz ) * v.tangent.w;
                o.color.xyz = binormal * 0.5 + 0.5;
                o.color.w = 1.0;
                return o;
            }

            fixed4 frag (v2f i) : COLOR0 { return i.color; }

            ENDCG
        }
    }
}

従法線ベクトルの値で色づけしています。
フレミングの左手の法則を覚えていますか?電・磁・力 ですね。それと同じです。
法線・接線・従法線になります。Normal Tangent Binormal、 NTB、呼びづらい。

やってることは今までの Normals とか Tangents とかと同じです。
vert内の float3 binormal = cross( v.normal, v.tangent.xyz ) * v.tangent.w; で従法線の計算をしているだけなので今までのものがわかっていれば問題ないと思います。

cross()
2つの浮動小数点3Dベクトルの外積を返す。



終了です。実は前回前々回のその1その2の方が内容的には難しかったみたいです。
どちらかといえばこちらが導入的な位置付けだったのかな?今更なんですが。仕方ありませんね。ごめんなさい。