おもちゃラボ

Unityで遊びを作ってます

【Unityシェーダ入門】ポリゴンをポイント(点)で表現する

Unityで3Dモデルを普通に描画するとポリゴンが表示されます。ここではポリゴンの頂点だけをポイントで表示する方法を紹介します。

頂点の表示方法を変えるだけなら、なんと2行のスクリプトだけですみます。それぞれの頂点に異なる色をつける場合はシェーダを使っています。

f:id:nn_hokuson:20170606195524j:plain

トポロジをポリゴンからポイントに変更する

3Dモデルの頂点情報はMeshで管理されていて、その頂点をどのように描画するかはMeshTopologyで定義されています。通常は面を描画するためにTrianglesトポロジが選択されています。

MeshTopologyには次のような種類があります。

トポロジ名 形状
Triangles 三角形
Quads 四角形
Lines
LineStrip 線分
Points

ここでは、頂点をポイントで表示するためにMeshTopology.Pointsを指定します。ワイヤーフレームで表示したい場合はMeshTopology.Linesを選択します。

f:id:nn_hokuson:20170606202826p:plain:w450

トポロジを変更してポイントで表示する

トポロジの変更は、スクリプトから簡単に行うことが出来ます。PointControllerという名前でスクリプトを作成し、次のプログラムを入力してください。

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

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

ここではGetComponentを使ってMeshFilterコンポーネントを取得し、メッシュの持つ頂点情報のトポロジをPointsに書き換えて再度保存しています。

スクリプトが入力できたら、作成したスクリプトを3Dモデルにアタッチして実行してみてください。次のように、頂点がポイントで表されたモデルが表示されると思います。

f:id:nn_hokuson:20170606200747j:plain:w500

頂点の色をグラデーションで表示する

ポイントごと(頂点ごと)に色を変えたい場合は、シェーダを書く必要があります。ここでは上からしたに向かってグラデーションになるようにポイントごとに色を付けるシェーダを作ってみましょう。

シェーダファイルと対応するマテリアルを作成します。プロジェクトビューで右クリックし、「Create」→「Shader」→「Unlit Shader」を選択してください。名前はGradationにしました。また、それにGaradationシェーダを選択した状態で「Create→Material」を選択し、対応するCustom_Gradationマテリアルも作成します。

f:id:nn_hokuson:20170606201303p:plain:w200

シェーダには次のプログラムを入力してください。

Shader "Custom/Gradation" {
    Properties {
        _Color ("Color", Color) = (1, 1, 1, 1)
    }
    SubShader {
        Tags { "RenderType"="Transparent" }
        Cull Off ZWrite On Blend SrcAlpha OneMinusSrcAlpha
        Pass {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            
            struct v2f {
                float4 pos : SV_POSITION;
                float3 worldPos : TEXCOORD0;
            };
            
            float4 _Color;

            v2f vert(appdata_base v)
            {
                v2f o;
				float3 n = UnityObjectToWorldNormal(v.normal);
                o.pos = UnityObjectToClipPos (v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;   
     
                return o;
            }
            
            half4 frag (v2f i) : COLOR
            {
            	float4 red  = float4(255.0/255,70.0/255,150.0/255,1);
            	float4 blue = float4(90.0/255,90.0/255,250.0/255,1);
                return lerp(red, blue, i.worldPos.y*0.2);
            }
            ENDCG
        }
    } 
    FallBack Off
}

ここではバーテックスシェーダで、処理している頂点のワールド座標を計算しています。フラグメントシェーダではこのワールド座標のy成分に基づいて頂点の色を決定します。うまく青色から赤色にグラデーションになるよう、Lerpメソッドで補間して計算しています。

実行結果は次のようになりました。

f:id:nn_hokuson:20170606195524j:plain:w500

頂点を動かして砂が飛び散るエフェクト

頂点を法線方向に動かすことで砂が飛び散るようなエフェクトを作ることも出来ます。バーテックスシェーダを次のように変更してください。

v2f vert(appdata_base v)
{
    v2f o;
    float3 n = UnityObjectToWorldNormal(v.normal);
    o.pos = UnityObjectToClipPos (v.vertex) + float4(n * _Displacement, 0);     
    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;   
     
    return o;
}

ここでは、ワールド座標系で法線情報を取得し、その方向に_Displacementぶん頂点を外側に動かしています。_Displacementはプロパティブロックで宣言して、インスペクタから操作できるようにしておきました。

f:id:nn_hokuson:20170606202232g:plain

【Unity】uGUIの文字をグラデーションで表示する

uGUIの文字は単色で塗りつぶしたり、Outlineコンポーネントでアウトラインを描画することはできますが(あまり綺麗じゃない)、グラデーションの表示には現在のところ対応していません。

そこで、uGUIの文字列にグラデーションをかけることができるスクリプトを作ってみたので、作り方を紹介したいと思います。

uGUIの文字をポリゴンとして扱う

「uGUIの文字を動かす」という記事でも書いたように、uGUIの文字は一文字一文字がポリゴンで描画されています。

f:id:nn_hokuson:20170521085157p:plain:w500

nn-hokuson.hatenablog.com

したがってこのポリゴンの頂点に異なる色を付けることでグラデーションのかかった文字を表示することが出来ます。

f:id:nn_hokuson:20170605204107j:plain:w450

文字にグラデーションを掛けるスクリプト

プロジェクトビューで右クリックし、「Create」→「C# Script」を選択し、「GradationController.cs」という名前でC#スクリプトを作りました。作成したスクリプトは次のようになります。

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

public class GradationController : BaseMeshEffect
{
	public Color colorTop    = Color.white;
	public Color colorBottom = Color.white;

	public override void ModifyMesh(VertexHelper vh)
	{
		if ( !IsActive() )
			return;

		List<UIVertex> vertices = new List<UIVertex>();

		vh.GetUIVertexStream(vertices);

		Gradation(vertices);

		vh.Clear();
		vh.AddUIVertexTriangleStream(vertices);
	}

	private void Gradation(List<UIVertex> vertices)
	{
		for (int i = 0; i < vertices.Count; i++)
		{
			UIVertex newVertex= vertices [i];
								
			newVertex.color = (i % 6 == 0 || i % 6 == 1 || i % 6 == 5) ? colorTop : colorBottom;

			vertices [i] = newVertex;
		}
	}
}

uGUIのTextのポリゴンにアクセスするにはBaseMeshEffectクラスのModifyMeshというメソッドをオーバーライドして使います。

https://docs.unity3d.com/jp/current/ScriptReference/UI.BaseMeshEffect.htmldocs.unity3d.com

このクラスからuGUIのTextが持つ全てのポリゴンを取得し、Gradationメソッドに渡しています。Gradationメソッドでは、ポリゴンの1頂点ごとにアクセスし、頂点の位置によって色を変えています。

頂点は次のような並びになっているので、頂点番号が0、1、5のときには上側の色、頂点番号が2、3、4のときは下側の色をつけています。

f:id:nn_hokuson:20170605204411p:plain:w150

実行結果

このスクリプトをuGUIのTextにアタッチして、インスペクタから色を設定する(TextコンポーネントのColorで設定した色は無視されます)とグラデーションデモ時が表示されます。

f:id:nn_hokuson:20170605205543j:plain

「Add Component」で「Outlineコンポーネント」を追加するとアウトラインがついたグラデーションの文字を表示することも出来ます。

f:id:nn_hokuson:20170605210137j:plain

uGUIに関してはこちらの参考書が詳しいです。

【Unityシェーダ入門】オブジェクトが重なった部分をくり抜く

特定のオブジェクトと重なった部分を透明にくり抜くシェーダを紹介します。このシェーダを使えば、次のように、好きな形で別のオブジェクトをくり抜くことができます。

f:id:nn_hokuson:20170601215112j:plain:w500

オブジェクトの形にくり抜くためには

今回のシェーダの原理は非常に簡単です。まずは抜きたい形のオブジェクトをA、抜かれる側のオブジェクトをBとしておきましょう。

f:id:nn_hokuson:20170601215300p:plain:w300

まずは、オブジェクトAをデプスバッファにだけ書き込み、カラーバッファには書き込まないようにします。これにより、画面には表示されないけれどもデプスバッファには書き込まれた状態になります。

f:id:nn_hokuson:20170601215505p:plain:w400

続いて、オブジェクトBを通常通り描画します。デプスバッファにはすでにオブジェクトAの情報が書き込まれているため、この部分だけは描画されないことになります。

f:id:nn_hokuson:20170601215547p:plain:w400

シェーダの作成

まずはシーンビューに抜くオブジェクト(球)と、抜かれるオブジェクト(立方体)を配置しておきましょう。

f:id:nn_hokuson:20170601215727j:plain

続いてシェーダとマテリアルを作成します。プロジェクトビューで右クリックして「Cutout.shader」そのシェーダをアタッチしたマテリアル「Custom_Cutout」を作成します。「Cutout.shader」には次のプログラムを入力してください。

Shader "Custom/Cutout" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
    }

    SubShader {
        Tags {"Queue" = "Geometry-1"}

        Pass{
            Zwrite On
            ColorMask 0
        }
    }
}

シェーダプログラムが作成できたら、球にアタッチしてください。球が透明になったと思います。

f:id:nn_hokuson:20170601220036j:plain:w500

床は透明にせずに残したい

この方法では球と重なったオブジェクトが全てくり抜かれることになります。例えば、くり抜いた立方体の下側に床を表示したい場合はどうすればよいでしょうか。普通に床を描画すると床までくり抜かれてしまいます。

f:id:nn_hokuson:20170601220153j:plain

球の情報がデプスバッファに書かれることで、それ以降に描画するオブジェクトは描画されなくなるのでした。ということは、球の情報をデプスバッファに書き込む前に床を描画すれば良さそうです。

描画する順番はシェーダのRenderQueueで指定できます。床用のシェーダファイルとマテリアルを作成しましょう。床のシェーダファイルはプロジェクトビューで右クリックし、「Create」→「Shader」→「Standard Surf Shader」を選択して作成後、次のようにTagを書き換えてください。

	Tags { "Queue"="Geometry-2" }

これにより、Render Queueの値は次のようになり、床→球→立方体の順序で描画される様になります。

オブジェクト Render Queue
背景画像 1998
1999
立方体 2000

作成した床用のマテリアルを床オブジェクトにアタッチして実行すると、実行結果は次のようになります。

f:id:nn_hokuson:20170601215112j:plain

動かしてみるとこんな感じです!

f:id:nn_hokuson:20170601220240g:plain