おもちゃラボ

Unityで遊びを作ってます

【Blender】オブジェクトを円形に等間隔で並べる方法またはポン・デ・リングの作り方

Blenderを使って同じオブジェクトを円形に並べたいときには配列複製を使うと便利です。時計盤やポン・デ・リング、花びらなど、使いドコロは結構たくさんあります(ホンマか・・・)

配列複製は横方向にオブジェクトを増やしたいときによく使いますが、実は円形にオブジェクトを配置したいときにも使うことができます。

f:id:nn_hokuson:20180306212430j:plain

複製したいオブジェクトを配置する

ポン・デ・リングを作る想定でいきます。まずは複製するための球を配置します。Ctrl+Aでメニューを開き追加→メッシュ→ICO球を選択します。

f:id:nn_hokuson:20180306202927j:plain

続けて今配置した球を原点から少し上に移動します。移動する時はエディットモードで移動することに注意して下さい。移動後は球のマニュピレータが原点に残っていることを確認して下さい。

f:id:nn_hokuson:20180306203223j:plain

複製したいオブジェクトを回転・拡大縮小している場合は、メニューからオブジェクト→クリア→回転 or 拡大縮小を選択し、これらのパラメータをデフォルトの状態に戻しておいて下さい。

f:id:nn_hokuson:20180306203539j:plain:w450

配列複製モディファイアを使う

次に回転の中心になるところ(今回は原点)に十字のオブジェクトをおきます。Ctrl+Aでメニューを開き追加→エンプティ→十字を選択してください。作成した十字が原点にない場合は、原点まで移動して下さい。

f:id:nn_hokuson:20180306203858j:plain

この状態で球の配列複製をしていきます。オブジェクトモードで球を右クリックで選択し、モディファイアから配列複製を選択します。

f:id:nn_hokuson:20180306204346j:plain

このままでは、いつもと同じように横方向にオブジェクトが複製されてしまいますね。そこで、円形配列になるように設定します。

オフセット(倍率)のチェックを外し、かわりにオフセット(OBJ)にチェックを入れます。また、オブジェクトには原点に配置した十字を指定して下さい。また、数はとりあえず「1」にしておきましょう。

f:id:nn_hokuson:20180306211403p:plain:w250

円形に配列複製する

球オブジェクトを配列複製で円形に並べます。球オブジェクト(十字ではない)を右クリックで選択して、「r+y+36」と入力しY軸を中心に36度回転させます。

f:id:nn_hokuson:20180306211700j:plain:w600

さて、これで準備OKです。最後に一気に球の複製を行いましょう。配列複製モディファイアの数を10まで増やせばポン・デ・リングの完成です。

f:id:nn_hokuson:20180306212202g:plain:w540

ちゃんとしたポン・デ・リングの作り方はコチラです。

cookpad.com

食べられないミスタードナッツはこちらで買えます。

【Unityシェーダ入門】3Dモデルをワイヤーフレームで表示する

前回の記事では3Dモデルの頂点を点(ドット)で表示する方法を紹介しました。今回は頂点の表示ではなく辺の表示、つまりポリゴンをワイヤーフレームで表示してみます。また、綺麗にワイヤーフレームを表示するためのシェーダも作成します!

f:id:nn_hokuson:20180301183740j:plain:w600

メッシュのトポロジを変更する

3Dモデルをワイヤーフレームで表示するには、頂点を表示したときと同様にメッシュの再構築を行います。

まずはメッシュの再構築をするスクリプトを作成してみましょう。プロジェクトウィンドウで右クリックし、Create→Script→C# Scriptを選択して、wireframe.csを作成してください。

ファイルが出来たら次のスクリプトを入力してください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class wireframe : MonoBehaviour 
{
    void Start()
    {
        MeshFilter meshFilter = GetComponent<MeshFilter>();
        meshFilter.mesh.SetIndices(meshFilter.mesh.GetIndices(0), MeshTopology.Lines, 0);
    }
}

Startメソッドの中でMeshFilterコンポーネントからメッシュ情報を取り出し、MeshTopologyにLineを指定しています。

スクリプトが入力できたら、wireframe.csを3Dモデルにアタッチしてください。

f:id:nn_hokuson:20180301191759j:plain

ちなみに今回のモデルはこちらのAssetを使わさせていただきました。

実行結果はこちらです。残念ながらあまり綺麗・・・ではないですね。

f:id:nn_hokuson:20180301192522j:plain:w600

ワイヤーフレームを綺麗に表示する

上の方法でワイヤーフレームが綺麗に表示されていないのは主に次のような原因が考えられます。

  • ワイヤーフレームが不透明でアンチエイリアスが効いていない
  • Standard Shaderのテクスチャが反映されていてゴチャってる


そこで単色できれいに発色するようなシェーダを作りましょう。まずはプロジェクトウィンドウで右クリックし、Create→Shader→Unlit Shaderを選択して、wireframe.shaderという名前で保存してください。また、作成したwireframe.shaderを右クリックしてCreate→Materialでwireframe.matというマテリアルを作成しておきます。

f:id:nn_hokuson:20180301192745p:plain:w250

wireframe.shaderのシェーダファイルに次のシェーダプログラムを入力して下さい。

Shader "Custom/wireframe" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _Emission ("Emission", Range(0,3)) = 1.0
    }
    SubShader {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        LOD 200
        Cull off
        Blend SrcAlpha OneMinusSrcAlpha 

        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows alpha:fade
        #pragma target 3.0

        struct Input {
            float2 uv_MainTex;
        };
                
        half _Emission;
        fixed4 _Color;

        void surf (Input IN, inout SurfaceOutputStandard o) {
            fixed4 c = _Color;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
            o.Metallic = o.Smoothness = 0;
            o.Emission = _Emission;
        }
        ENDCG

    }
    FallBack "Diffuse"
}

このシェーダではワイヤーフレームの色とEmissionをインスペクタから設定できるようになっています。ワイヤーフレームの色には透明度も反映されます。透明度を高めに設定することで線が重なったときに加算合成されて綺麗に表示されます。

シェーダが入力できたら、wireframeマテリアルを3Dモデルにアタッチしましょう。

f:id:nn_hokuson:20180301193044j:plain

マテリアルがアタッチできたら、最後にインスペクタからワイヤーフレームの色とEmissionを指定します。今回は次のように設定しました。カラーピッカーで透明度は高く設定していることに注意して下さい。

f:id:nn_hokuson:20180301193316p:plain:w300

Unityで実行してみて、先程よりも綺麗に表示されていることを確認してみましょう!

f:id:nn_hokuson:20180301183740j:plain:w600

まとめ

3Dモデルをワイヤーフレームを表示するための方法を紹介しました。標準のStandard Shaderでは綺麗に表示できないため、オリジナルのシェーダも作成しました。

前回紹介した、頂点をドットで表示する方法はこちらの記事を参考にして下さい。

nn-hokuson.hatenablog.com


【Unity】LineRendererでは引けない線を引く

Unityで線を引きたいときは、LineRendererを使うのが簡単なのですが、テクスチャの張り方を変えたり、線の太さを動的に変えたりと、アレンジした線を引こうとすると、とたんに難しくなります。

ということで、この記事ではLineRendererを使わずに自力で線を引く方法を紹介します。

f:id:nn_hokuson:20180227213645g:plain:w560

記事の内容は次のとおりです。

線を引くアルゴリズム

基本的には自分で線を描画するためのポリゴンを作って、そこにテクスチャを貼っていく感じです。ポリゴンの形状は線の流れに沿って柔軟に変化させます。

f:id:nn_hokuson:20180227203043j:plain:w500

Unityで自作のポリゴンを作る方法はこちらの記事で紹介しているので、合わせて参考にしてください。

nn-hokuson.hatenablog.com

線に沿ってポリゴンを生成するアルゴリズムは次のような流れになります。まず、最初にタッチされた点に2点ぶんの頂点を生成します。

f:id:nn_hokuson:20180227203626p:plain:w200

タッチの座標が移動したら、前タッチ座標から今タッチ座標までのベクトルを求めて、そのベクトルを90度回した点と-90度回した点に新しい頂点を2つ生成します。

f:id:nn_hokuson:20180227204246p:plain:w400

前回と今回の頂点をあわせて4頂点できるので、これで三角形ポリゴンを2つ描画します。あとはこれを繰り返し、タッチの移動に従って四角形のポリゴンを生成していきます。

f:id:nn_hokuson:20180227204531p:plain:w420

ポリゴンを動的に生成するLineスクリプトを作る

プロジェクトウィンドウで右クリックし、Create→C# Scriptを選択、「Line.cs」というスクリプトを作り、次のプログラムを入力してください。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Line : MonoBehaviour
{
    public Material _mat;

    List<Vector3> points = new List<Vector3>();
    List<Vector3> vertices = new List<Vector3>();
    List<Vector2> uvs = new List<Vector2>();
    List<int> tris = new List<int>();

    Mesh mesh;
    int offset = 0;
    float xoffset = 0;
    float penSize = 0.6f;          // 筆の太さ
    float uScrollSpeed = 0.18f;  // テクスチャの伸びる速度

    void CreateMesh(float size)
    {
        Vector2 prev = this.points[this.points.Count - 2];
        Vector2 top = this.points[this.points.Count - 1];
        Vector2 dir = (top - prev).normalized;

        Vector2 plus90  = top + new Vector2(-dir.y, dir.x) * size;
        Vector2 minus90 = top + new Vector2(dir.y, -dir.x) * size;

        // 頂点を追加
        this.vertices.Add(minus90);
        this.vertices.Add(plus90);

        // UVを追加
        this.uvs.Add(new Vector2(xoffset, 0));
        this.uvs.Add(new Vector2(xoffset, 1));
        xoffset += (top - prev).magnitude / 6.0f;////uScrollSpeed; 

        // インデックスを追加
        this.tris.Add(offset);
        this.tris.Add(offset + 1);
        this.tris.Add(offset + 2);
        this.tris.Add(offset + 1);
        this.tris.Add(offset + 3);
        this.tris.Add(offset + 2);

        offset += 2;

        mesh.vertices = this.vertices.ToArray();
        mesh.uv = this.uvs.ToArray();
        mesh.triangles = this.tris.ToArray();

        GetComponent<MeshFilter>().sharedMesh = mesh;
        GetComponent<MeshRenderer>().material = _mat;
    }

    public void PenDown(Vector3 tp)
    {
        // 開始点を保存
        this.points.Add(tp);

        // 頂点を2つ生成
        this.vertices.Add(tp);
        this.vertices.Add(tp);

        // uv座標を設定
        this.uvs.Add(new Vector2(0, 1f));
        this.uvs.Add(new Vector2(0, 0));
        this.offset = 0;

        // メッシュ生成
        this.mesh = new Mesh();
    }

    public void PenMove(Vector3 tp, float size)
    {
        this.points.Add(tp);

        CreateMesh(size);
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            this.points.Clear();
            this.vertices.Clear();
            this.uvs.Clear();
            this.tris.Clear();

            Vector3 tp = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            PenDown(tp);
        }
        else if (Input.GetMouseButton(0))
        {
            Vector3 tp = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            PenMove(tp, this.penSize);
        }
    }
}

このプログラムでは次の3つのListを使っています。

  • タッチが移動した座標を保存するpoints
  • ポリゴンの頂点座標を保存するvertices
  • ポリゴンを描画するためのインデックスを保存するindex

CreateMeshメソッドでは現在タッチされている座標をもとに追加すべきポリゴンの頂点座標を2つと、UV座標を2つ、インデックス6つ(三角形のポリゴンを2個描画するため)計算し、リストに追加しています。

CreateMeshメソッド中の次のコードでは、現在のタッチ座標と1フレーム前のタッチ座標から移動ベクトルdirを計算しています。dirはnormalizedで正規化されていることに注意して下さい。

        Vector2 prev = this.points[this.points.Count - 2];
        Vector2 top = this.points[this.points.Count - 1];
        Vector2 dir = (top - prev).normalized;

次のスクリプトでは回転行列を使って、現在のタッチ座標(top)を中心に、移動ベクトル(dir)を+90度回した点の座標と-90度回した点の座標を計算しています。

        Vector2 plus90  = top + new Vector2(-dir.y, dir.x) * size;
        Vector2 minus90 = top + new Vector2(dir.y, -dir.x) * size;

ポリゴンの生成プログラムについて詳しくは次の記事を合わせて参照ください。

nn-hokuson.hatenablog.com

このプログラムでは、1本の線しか描画出来ないプログラムになっていますが、Meshを動的に生成することで、簡単に複数の線を同時に引けるようになります。

Lineスクリプトをアタッチする

プログラムができたら、Pen.csをアタッチするためのGameObjectを作ります。ヒエラルキーウインドウからCreate→Empty Objectを選択して名前をLineに変更します。生成したPenオブジェクトにLine.csをドラッグ&ドロップします。

f:id:nn_hokuson:20180227211410j:plain

また、描画にはMesh FilterコンポーネントとMesh Rendererコンポーネントが必要になるため、インスペクタのAdd Componentからこれらのコンポーネントを追加して下さい。

f:id:nn_hokuson:20180227211603p:plain:w300

マテリアルをアタッチする

線を描画するために使用するテクスチャを指定します。プロジェクトウィンドウで右クリックしてCreate→Materialを選択し、インスペクタからMaterialのシェーダを「Unlit/Transparent」に変更したうえで、テクスチャをセットしてください。

f:id:nn_hokuson:20180227212225p:plain:w300

最後にPenのスクリプトにいま作成したテクスチャをセットします。ヒエラルキーウインドウでPenを選択し、インスペクタの「mat」の欄にマテリアルをドラッグ&ドロップしてください。

f:id:nn_hokuson:20180227212541j:plain

線を描いてみる

Unityを実行して画面上をドラッグしてみてください。ドラッグした軌跡に従って線が描けると思います。

f:id:nn_hokuson:20180227213645g:plain:w560

テクスチャの伸び方を変えたい場合はPen.csのuspeedを変更してください。これによりテクスチャのu方向へのサンプリング速度が変化します。

また、線の太さを変えたい場合はpenSizeのパラメータを変更してください。このpenSizeをペンの移動に応じて変更することで、動的に太さが変化する線を書くことが出来ます。

f:id:nn_hokuson:20180227214211j:plain:w300