うにてぃブログ

UnityやUnreal Engineの記事を書いていきます

まとめ

ライブラリ

github.com

デバッグツール
Easing関数
Missing Script 検索ツール
モデル撮影ツール
Module化ライブラリ
Unity上で使える Git Tool
単純なメッシュを作成するツール
カメラ一覧を表示
アセットのディレクトリを移動する
テクスチャ生成ツール
対象のコンポーネントを利用している Prefab や Scene を探すツール
CustomInspector の テンプレートコードを出力するツール
アセットの参照を確認するツール
Hierarchy に存在するオブジェクトの参照を調べるツール
Texture を加工するツール
ProjectWindow を拡張するツール
AnimationClip の Path を一括で置換するツール

アドベントカレンダー

hacchi-man.hatenablog.com

【Unity】エディターが使用するShaderVariantリストを取得する方法

Unityでの開発において、Shader Variantの管理はパフォーマンスとビルドサイズに大きく影響を与える重要な要素です。特に、どのShader Variantが実際に使用されているのかを把握することは、効率的な開発に繋がります。

本記事では、UnityEditor上でShader Variantリストを確認する方法について説明します。

UnityEditorでShader Variantリストを確認する方法

UnityEditorには、現在使用されているShader Variantを確認するための便利な機能が用意されています。 それが、Project Settings / Graphics Settings / Currently Tracked セクションです。

さらに、Save to asset... ボタンを使用することで、ShaderVariantsファイルを生成することが可能です。

このファイルには、利用しているShader Variantの詳細が記録されます。これにより、Variantの管理やデバッグが簡単になります。

ShaderVariantsファイルの内容

生成されたShaderVariantsファイルの内容は以下のようになっています。

ただし、Editor上でしか使用されないShaderも含まれている場合があります。そのため、不要なShaderは適切に削除する必要があります。

以下は生成されたShaderVariantsファイルの例です:

注意点

プレイモードで確認: "Currently Tracked"セクションは、プレイモードで動作しているShader Variantを確認するために特化しています。プレイモードで動作させた後に確認することをお勧めします。

不要なShaderの削除: 不要なShader Variantが含まれていると、ビルドサイズが増大する可能性があります。生成されたShaderVariantsファイルをもとに、使用していないVariantを削除してください。

【Unity】Y軸固定ビルボードシェーダーの実装方法

今回は、Unity Shaderを使ってY軸固定ビルボードシェーダーを作成する方法を解説します。ビルボードシェーダーは、木や草、エフェクトなど、常にカメラに向けて表示したいオブジェクトに非常に便利です。

しかし、すべての軸で回転すると不自然になる場合があります。例えば、木や草は上下方向(Y軸)に対して回転しない方が自然です。そのため、Y軸を固定したビルボードを作成します。

シェーダー

以下がY軸固定ビルボードシェーダーのコードです。

shader コードをコピーする

Shader "Custom/Billboard"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "Queue"="Transparent" "RenderType"="Transparent" }
        
        Blend SrcAlpha OneMinusSrcAlpha
        Cull Off

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;

                // オブジェクトのスケールを取得
                float3 scale = float3(
                    length(unity_ObjectToWorld._m00_m10_m20),
                    length(unity_ObjectToWorld._m01_m11_m21),
                    length(unity_ObjectToWorld._m02_m12_m22)
                );

                // オブジェクトの中心位置を取得
                float3 worldPos = mul(unity_ObjectToWorld, float4(0, 0, 0, 1)).xyz;
                
                float3 cameraPos = _WorldSpaceCameraPos;
                float3 toCamera = normalize(cameraPos - worldPos);
                float3 up = float3(0, scale.y, 0);
                float3 right = normalize(cross(up, toCamera)) * scale.x;
                
                // ビルボードの頂点位置を計算
                float3 pos = worldPos + right * v.vertex.x + up * v.vertex.y;

                o.vertex = UnityWorldToClipPos(pos);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

Shader詳細

1. オブジェクトの中心位置を取得:

worldPos でオブジェクトのワールド座標における中心位置を取得します。

float3 worldPos = mul(unity_ObjectToWorld, float4(0, 0, 0, 1)).xyz;

2. ビルボードの面を定義するための軸を計算:

上方向(up)とカメラへの方向ベクトル(toCam)の外積から、ビルボードの右方向(right)を計算します。

float3 right = normalize(cross(up, toCam));

3. 各頂点の新しい位置を計算:

オブジェクトの中心位置(worldPos)を基準に、頂点のローカル座標(v.vertex.x、v.vertex.y)を使って新しい頂点位置を計算します。

float3 pos = worldPos + right * v.vertex.x + up * v.vertex.y;

動作確認

下の画像の左側は、ビルボードシェーダーを適用した木の画像です。カメラを回転させても木が常に正面を向いていることがわかります。

【Unity】ビルボードシェーダーの実装方法

今回は、Unityでビルボードシェーダを実装し、カメラに常に正面を向くようにする方法について解説します。

ビルボードとは

ビルボードは、オブジェクト(通常は平面)が常にカメラを向くように描画するシェーダです。これにより、2Dのテクスチャを3D空間で立体的に見せることができます。ゲーム開発では、パーティクルエフェクトなどによく使用されます。

Shader

このシェーダでは、カメラの位置とワールドの上方向ベクトルを使用して、ビルボードが常にカメラを正面に向くようにしています。

Shader "Custom/Billboard_Full_NoCameraUp"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "Queue"="Transparent" "RenderType"="Transparent" }
        
        Blend SrcAlpha OneMinusSrcAlpha
        Cull Off

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;

                // オブジェクトのスケールを取得
                float3 scale = float3(
                    length(unity_ObjectToWorld._m00_m10_m20),
                    length(unity_ObjectToWorld._m01_m11_m21),
                    length(unity_ObjectToWorld._m02_m12_m22)
                );

                // オブジェクトの中心位置を取得
                float3 worldPos = mul(unity_ObjectToWorld, float4(0, 0, 0, 1)).xyz;
                float3 cameraPos = _WorldSpaceCameraPos;
                float3 toCamera = normalize(cameraPos - worldPos);
                float3 up = float3(0, 1, 0);

                if (abs(dot(toCamera, up)) > 0.9999)
                {
                    // カメラが真上または真下にある場合の右方向ベクトル
                    up = float3(1, 0, 0);
                }

                float3 right = normalize(cross(up, toCamera)) * scale.x;
                up = normalize(cross(toCamera, right)) * scale.y;
                float3 pos = worldPos + right * v.vertex.x + up * v.vertex.y;

                o.vertex = UnityWorldToClipPos(pos);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

シェーダの解説

右方向ベクトルと上方向ベクトルの再計算
// up と toCamera の外積から右方向ベクトルを計算します。
float3 right = normalize(cross(up, toCamera)) * scale.x;
// 新しい right と toCamera の外積から上方向ベクトルを再計算します。
up = normalize(cross(toCamera, right)) * scale.y;
ビルボードの頂点位置を計算
// オブジェクトの中心位置に、頂点のローカル座標を right と up でスケーリングして加算します。
float3 pos = worldPos + right * v.vertex.x + up * v.vertex.y;
o.vertex = UnityWorldToClipPos(pos);

動作確認

このシェーダを適用して、カメラを様々な位置や角度に動かしてみました。その結果、ビルボードは常にカメラを正面に向きました

【Unity】重心座標系を用いたワイヤーフレームの描画方法

今回は、Unityで重心座標系を利用してオブジェクトのワイヤーフレームを描画する方法を紹介します。

ワイヤーフレーム表示はデバッグや特殊なビジュアルエフェクトに役立ちますが、重心座標系を使うことで効率的に実装できます。

重心座標系とは

重心座標系(バリセントリック座標とも呼ばれます)は、三角形の各頂点に対する重みを表す座標系です。三角形内の任意の点を、頂点の重みの組み合わせとして表現できます。この性質を利用して、シェーダー内でエッジを検出し、ワイヤーフレームを描画することが可能です。

実装手順

1. 重心座標系の割り当て

まず、メッシュの各頂点に重心座標系を割り当てます。今回は頂点カラーに重心座標系のデータを割り当て、共有頂点による問題を解決するために新しくメッシュを作成します。

using UnityEngine;

[RequireComponent(typeof(MeshFilter))]
public class BarycentricCoordinatesAssigner : MonoBehaviour
{
    [SerializeField]
    private MeshFilter _meshFilter;

    private Mesh _generateMesh;
    
    private void Reset()
    {
        _meshFilter = GetComponent<MeshFilter>();
    }

    private void OnDestroy()
    {
        if (_generateMesh)
        {
            Destroy(_generateMesh);
            _generateMesh = null;
        }
    }

    private void Start()
    {
        var mesh = _meshFilter.sharedMesh;
        var triangles = mesh.triangles;
        var verteces = mesh.vertices;
        
        var trianglesLength = triangles.Length;

        var barycentricCoords = new Color[trianglesLength];
        var newTriangles = new int[trianglesLength];
        var newVerteces = new Vector3[trianglesLength];
        
        for (int i = 0; i < trianglesLength; i += 3)
        {
            for (int j = 0; j < 3; j++)
            {
                var index = i + j;
                
                newTriangles[index] = index;
                newVerteces[index] = verteces[triangles[index]];
                var color = new Color(0, 0, 0);
                color[j] = 1f;
                barycentricCoords[index] = color;
            }
        }

        var newMesh = new Mesh();
        newMesh.vertices = newVerteces;
        newMesh.triangles = newTriangles;
        newMesh.colors = barycentricCoords;

        _generateMesh = newMesh;
        _meshFilter.mesh = _generateMesh;
    }
}

これを適応するとメッシュの頂点カラーは以下のようになります

2. Shader の作成

次に、重心座標系を利用するカスタムシェーダーを作成します。頂点カラーの最小値を取得し、それを太さ用のパラメータと比較してワイヤーフレームを描画します。

Shader "Custom/Wireframe"
{
    Properties
    {
        _WireColor ("Wireframe Color", Color) = (1,1,1,1)
        _Thickness ("Wireframe Thickness", Range(0.0001, 1.0)) = 0.02
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            struct appdata
            {
                float4 vertex : POSITION;
                float4 color : COLOR;
            };

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

            fixed4 _WireColor;
            float _Thickness;

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

            fixed4 frag (v2f i) : SV_Target
            {
                float edge = min(min(i.color.r, i.color.g), i.color.b);
                float alpha = step(edge, _Thickness);
                clip(alpha - 0.01);
                return _WireColor;
            }
            ENDCG
        }
    }
}

3. 動作確認

先ほど作成したスクリプトとシェーダーを使用してマテリアルを作成し、オブジェクトに適用します。シーンを再生すると、以下のようにワイヤーフレームが表示されます。

まとめ

重心座標系を利用することで、追加のジオメトリや複雑なシェーダーを使わずに効率的にワイヤーフレームを描画できます。

注意点

ワイヤーフレームの太さを完全に均一にすることは難しい場合があります。重心座標系を利用した方法では、線の太さは三角形の形状やサイズに依存します。 ・Unity Editor以外の環境(ビルド後の実行環境)では、メッシュのRead/Writeが有効になっている必要があります。メッシュのインポート設定でRead/Write Enabledをチェックしてください。

【Unity】ShaderGraph で Cube の境界を表示するシェーダ

この記事では、ShaderGraphを使ってキューブオブジェクトの境界線を強調して表示するシェーダの作成手順を紹介します。 このシェーダにより、キューブのエッジが明確に表示され、境界線が視覚的に強調されます。以下、ShaderGraphの各ステップについて詳しく説明していきます。

ShaderGraph

キューブの位置とサイズの取得

まず、PositionノードとScaleノードを使用してキューブの位置とサイズを取得します。Object Spaceを指定することで、キューブのローカル座標での計算が可能になり、境界に関する情報を精度よく取得できます。

キューブの境界検出

次に、SubtractやSplitノードを活用し、キューブの各軸(X, Y, Z)ごとの境界までの距離を算出します。この計算では、キューブの中心点から各面までの距離を求めることで、エッジ部分を検出するための基準が作られます。

絶対値による距離の統一化

Absoluteノードを用いて、求めた距離の絶対値を取得し、正負の方向を問わず一貫した距離情報を生成します。これにより、キューブの全ての面に対して均一に境界線を表示できる基盤が整います。

ステップ関数による境界表示設定

Stepノードを利用して、キューブのエッジに沿った境界線表示の条件を定義します。このノードでは、設定した閾値に基づき、キューブのエッジ部分が視覚化されるように制御されます。

SSだと見ずらい場合があるのでこちらからShaderGraphをDLしてください

結論

このShaderGraphを使用することで、キューブのエッジを強調して視覚的に明確な境界線を表示することが可能です。 またスケールが変わったとしても正しく表示されるようになっています

本記事を参考にして、キューブの境界線表示を実現するShaderGraphの構造とその意図を理解しやすくなれば幸いです。 また、さらなる効果やカスタマイズについても、ぜひ挑戦してみてください。

【Unity】透明オブジェクトの境界を際立たせる

ゲームやアプリケーションで透明なオブジェクトを扱う際、その境界が分かりにくくなることがあります。特に、他の物体と重なる場合に視覚的な混乱が生じがちです。

今回は、Unityのシェーダーを使って透明オブジェクトが他のオブジェクトに近づいた際に境界を際立たせる方法を紹介します。

実装の概要

このエフェクトは、Screen PositionノードとScene Depthノードを利用して、オブジェクト同士の距離を計算し、一定の範囲内で色やアルファ値を変化させることで、透明オブジェクトの境界を際立たせます。

Screen Positionノード

透明オブジェクトのスクリーン上の位置情報を取得するノードです。ここで注目するのはW成分で、この値は透視投影によりオブジェクトの距離を示します。

Scene Depthノード

Scene Depthノードを使用すると、カメラからの深度(Z値)を取得できます。透明オブジェクトの背後にある物体の深度と比較することで、他のオブジェクトとの距離を把握し、近接するオブジェクトの境界部分を際立たせます。

距離に基づく色変化の計算

Screen PositionのW成分を利用して距離を計算し、透明オブジェクトに色の変化を加えることで、近接している場合にその境界が目立つようにします。

実装Shader

ShaderGraphでの構成は以下の通りです:

SSだと見ずらい場合があるのでこちらからShaderGraphをDLしてください

確認

実際に以下のパラメータを設定したところ、オブジェクトの境界が色で際立つ効果が確認できました。

【Unity】ScrollRectのScrollBarの当たり判定を広くする方法

UGUIでは、RaycastPaddingを利用して、見た目と当たり判定の範囲を変更することが可能です。今回は、この原理を使ってScrollBarの当たり判定を広くする方法を試してみた結果について解説します。

通常、RaycastPaddingを設定することで、UI要素の当たり判定を拡大・縮小できます。これをScrollBarに適用することで、スライダーやスクロールバーのクリック範囲を広げようと考え、実際にRaycastPaddingの値を変更してみました。

その結果、当たり判定自体は広がったのですが、ハンドルがガタガタ動くようになってしまいました。

原因の調査

内部の処理を確認してみたところ、ハンドルの動作に関する判定はRaycastPaddingでチェックされておらず、Rectのサイズを基準にしていることがわかりました。

if (!RectTransformUtility.RectangleContainsScreenPoint(m_HandleRect, screenPosition, camera))

このため、単純にRaycastPaddingで範囲を広げるだけでは、期待通りに動作しません。

解決方法

範囲を広くしたい場合は、以下のような構築にする必要があります:

当たり判定用のBarと、同じサイズのHandleを配置します。 Handleの子要素に、見た目用の小さなImageを設定します。 このように、ヒット判定用の大きな要素と、表示用の小さな要素を分離することで、当たり判定を広げつつ、見た目はそのままに保つことができます。

結果

この構造に変更することで、当たり判定を広げてもハンドルが正しく動作するようになりました。