今日まなび(20240827):Quadに張った透過テクスチャの形にあわせて影を落とす①

「今日まなび」は最低1日1h、ゲーム作るために学びたいことなんでもいいから学んでいくコーナー。
完全自分用でまとめることは考えなくてOK.1記事1h。(1hでどんだけ学べるかのスピード感もみていきたいので)
1hのうちに次回学ぶことも決定しておくこと

環境:unity2021.3.40f


直近の目標まとめ。 →透過テクスチャを張ったQuadにカスタムShaderを適応したら影がでなくなってしまったので影をつけたい

今日の学び:

前回調べたとりあえず影を落とす処理をQuadに反映してみたらこのようになった。 右はStandardシェーダーで、左がカスタムShaderである。

たしかに影は落ちたが、透過部分が反映されておらずメッシュの形(今回はQuadを使っているので4角形)で影が落ちてしまった。


というわけでStandardシェーダーの中身をみて、透過部分を考慮して影を落としている部分を調査したら以下の部分のようだった。

 //  Shadow rendering pass
 Pass {
     Name "ShadowCaster"
     Tags { "LightMode" = "ShadowCaster" }

     ZWrite On ZTest LEqual

     CGPROGRAM
     #pragma target 3.0

     // -------------------------------------


     #pragma shader_feature_local _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
     #pragma shader_feature_local _METALLICGLOSSMAP
     #pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
     #pragma shader_feature_local _PARALLAXMAP
     #pragma multi_compile_shadowcaster
     #pragma multi_compile_instancing
     // Uncomment the following line to enable dithering LOD crossfade. Note: there are more in the file to uncomment for other passes.
     //#pragma multi_compile _ LOD_FADE_CROSSFADE

     #pragma vertex vertShadowCaster
     #pragma fragment fragShadowCaster

     #include "UnityStandardShadow.cginc"

     ENDCG
 }

次回、これを自作Shaderに適応できるか試す

TODO (調べたいこと、やりたいこととか疑問)

・自作shaderを適応したQuadでも影を落としたい ←今進行中
・ことなるポストプロセスを適応したい場合にカメラを分けなくてもいい方法あるんじゃないか
・Unityで深度バッファの値をどうやって取得できる?
・法線マップを扱う
・shaderで図形描いたりするのによく使う関数と使い方まとめ

今日まなび(20240826):影を落とすShader+Unityのレンダリング基礎知識おさらい

「今日まなび」は最低1日1h、ゲーム作るために学びたいことなんでもいいから学んでいくコーナー。
完全自分用でまとめることは考えなくてOK.1記事1h。(1hでどんだけ学べるかのスピード感もみていきたいので)
1hのうちに次回学ぶことも決定しておくこと


本日は自作Shaderを適応すると影が描画されない課題を調査していきます。
▼右がStanderdシェーダー、左が自作スタムShader

今日の学び:とりあえず影を落とす処理を作成

影を描画する用のPassを追加すると影が出る。

 Pass
    {
        //実態を描く:自分がカスタムした部分
    }
    Pass
    {
        Tags{ "LightMode"="ShadowCaster" }

        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma multi_compile_shadowcaster

        struct appdata
        {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
        };

        struct v2f
        {
            V2F_SHADOW_CASTER;
        };

        v2f vert (appdata v)
        {
            v2f o;
            TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
            return o;
        }

        fixed4 frag (v2f i) : SV_Target
        {
            SHADOW_CASTER_FRAGMENT(i)
        }
        ENDCG
    }

今日の学び:レンダリングパス

上記をコピペすれば影を落とせるがそれでは理解していないのでまずはレンダリングまわりの基礎知識をおさらいする。

レンダリングパイプライン

Unityでは現時点で、ビルドインパイプライン(BRPとか略されてることもよくある)、URP、HDRPの3つ用意されている。 これに+してレンダリングパイプラインをカスタムすることもでき、これをSRPという。

BRPのレンダリングパスは2つでカメラ単位に設定可能

で、まずは基本のBRPを見ていく。 BRPのレンダリングパスは、フォワードレンダリング、ディファードシェーディングの2つある(レガシーになっているのは省略する) デフォルトはフォワードレンダリングになっている。 ディファードシェーディングはより忠実なライトと影の表現ができるものだが、フォワードでできていた機能が一部使えないなど制限もある。 基本フォワードにしていて必要な場面でディファードを使っていくのがよさそう?

docs.unity3d.com

プロジェクト全体で設定する方法と、+カメラ単位に設定を上書きすることもできる

TODO (調べたいこと、やりたいこととか疑問)

・Quadの影を落としたい ←今進行中
・ことなるポストプロセスを適応したい場合にカメラを分けなくてもいい方法あるんじゃないか
・Unityで深度バッファの値をどうやって取得できる?
・法線マップを扱う
・shaderで図形描いたりするのによく使う関数と使い方まとめ

今日まなび(20240825):自分で4角形のメッシュを作成してテクスチャを張ってみる

「今日まなび」は最低1日1h、ゲーム作るために学びたいことなんでもいいから学んでいくコーナー。
完全自分用でまとめることは考えなくてOK.1記事1h。(1hでどんだけ学べるかのスピード感もみていきたいので)
1hのうちに次回学ぶことも決定しておくこと


本日はこちらの内容を学んでいきます。普段何気なく使っているQuadやPlaneのメッシュはどういう風に情報を持っているかが少しわかる nn-hokuson.hatenablog.com

今日の学び

4角形のメッシュを作成するには、ポリゴンが2つ必要になる。

・頂点
 →1ポリゴンは3つの頂点で構成される
・UV座標(テクスチャ座標)
 →頂点ごとにテクスチャのどの位置に対応するかを指定。0~1
・インデックス
 →頂点を表す番号。
 4角形を描きたい場合、ポリゴンは3角形なのでポリゴンが2つ必要になる。
 その際に2頂点は重なる。これは通常メモリの無駄使いであるため、
 別途インデックスの番号を付与して頂点配列のどの位置の情報を使うかを定義したものをインデックスと呼ぶ。
 4角形なら6頂点のうちの4頂点使うことになる。
 この値を時計まわりに見える順番で定義することによってその面が表だということを表すことができる。
 裏になっていればカリングされてしまうので順番は重要。

4角形のメッシュを生成してテクスチャを張る

using UnityEngine;

//4角形のメッシュ作ってテクスチャ張る
public class Test20240825 : MonoBehaviour
{
    public Material mat;

    void Start()
    {
        //頂点の位置
        float SIZE = 4;
        Mesh mesh = new Mesh();
        mesh.vertices = new Vector3[] {
            new Vector3 (-SIZE, -SIZE, 0), //左下
            new Vector3 (-SIZE, SIZE, 0), //左上
            new Vector3 (SIZE , -SIZE, 0),//右下
            new Vector3 (SIZE ,  SIZE, 0), //右上
        };

        //UV:頂点と画像側の位置の対応 ※0~1で定義する
        mesh.uv = new Vector2[] {
            new Vector2 (0, 0), //左下
            new Vector2 (0, 1), //左上
            new Vector2 (1, 0), //右下
            new Vector2 (1, 1), //右上
        };

        //インデックス:↑で作った頂点やUV配列のどの情報を使うか 
        //※1ポリゴン(3頂点分)時計まわりに見える順番に定義(表面という意味)する
        mesh.triangles = new int[] {
            0, 1, 2, //左下 - 左上 - 右下 の三角形
            1, 3, 2, //左上 - 右上 - 右下 の三角形
        };

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


        int polygonCount = mesh.triangles.Length / 3;
        Debug.Log("ポリゴン数=" + polygonCount + " 頂点数=" + polygonCount * 3);
    }
}

TODO (調べたいこと、やりたいこととか疑問)

・Quadに影をつける
・Unityで深度バッファの値をどうやって取得できる?
・法線マップを扱う
・shaderで図形描いたりするのによく使う関数と使い方まとめ

今日まなび(20240824):shader レンダリング手順おさらい+QuadとかPlaneのポリゴン数いくつ?

「今日まなび」は最低1日1h、ゲーム作るために学びたいことなんでもいいから学んでいくコーナー。
完全自分用でまとめることは考えなくてOK.1記事1h。(1hでどんだけ学べるかのスピード感もみていきたいので)
1hのうちに次回学ぶことも決定しておくこと

今日の学び:レンダリング手順のおさらい

ポリゴン…3つの頂点から構成される
メッシュ…複数のポリゴンで形成される

まずは単純にポリゴン1つだけ描画するときのことを考える。

1:頂点の位置を決定

ポリゴン1つは3つの頂点なので、頂点の位置を決めるための処理を3回行う

2:ポリゴンの表裏を判定

ポリゴンは表裏の情報を持ち(※)、
描画負荷の軽減のため裏面を向いているポリゴンは描画しない手法がとられることが多い。
この手法を「カリングCull ing」という。

※Unityではポリゴンの表裏は頂点の定義されている順番で判断する。時計まわりに見える方向を表とする

3:ポリゴンが画面上のどの画素に描画されるか決定

ラスタライズ
1との違いがあんまりわからないが、頂点位置から実際に描画する画素を決めるということだろう

4:描画しようとしている画素にすでに別のポリゴンが描画されているか確認する

すでに描画されているポリゴンがあるかは「Zbuffer」「深度バッファ」を確認する。
これは描画する画面と同じ画素数分だけある情報で画素単位にカメラからの位置=Zを保持している。

すでに別のポリゴンが描画されている場合、ZBufferの値を参照して
新しく描画しているものの方が値が小さい=カメラに近い=手前 であるなら上書きする。奥にあると判断された場合は上書きしない。
これは画素単位に判断される。
この比較のことを「Z Test」と呼ぶ。

5:画素の色を決定する

ピクセルシェーダーで変更できる部分

6:ZBufferの値を更新

ZWriteと呼ぶ

 今日のまとめ

レンダリングはこのやって行われる

・頂点の位置決定 →頂点Shader ★かなり自由に処理を変更できる
・ポリゴンの表裏を確認して描画するか決める →カリング ☆ある程度設定を変更することは可能
・描画する画素を決定 →ラスタライズ ☆ある程度設定を変更することは可能
・深度バッファを比較してうわがきする画素を決定 →Zテスト ☆ある程度設定を変更することは可能
・画素の色を決めて描画 →ピクセルShader ★かなり自由に処理を変更できる
・深度バッファを上書き →ZWrite ☆ある程度設定を変更することは可能

QuadとかPlaneのポリゴン数いくつ?

QuadやPanelにアタッチされているMeshFilterから以下のコードでポリゴン数を算出できる。

meshFilter.sharedMesh.triangles.Length / 3`;

結果 Quad=2個 Plane=200個 だった。同じ四角でもかなり数が違うみたいだ。

<検証に使ったコード>

public class Shader20240824 : MonoBehaviour
{
    public MeshFilter meshFilter;

    // Start is called before the first frame update
    void Start()
    {
        int polygonCount = meshFilter.sharedMesh.triangles.Length / 3;
        Debug.Log("ポリゴン数=" + polygonCount + " 頂点数=" + polygonCount * 3);
    }

}

TODO (調べたいこと、やりたいこととか疑問)

・Unityで深度バッファの値をどうやって取得できる?
・法線マップを扱う
・shaderで図形描いたりするのによく使う関数と使い方まとめ

【TextMeshPro】既存のTextMeshProのフォントアセットを更新する方法

前提:TextMeshProのフォントアセット?

TextMeshProでフォントを使うには、ttfなどのフォントファイルのままではなく、
TextMeshProのFontAssetCreatorでTextMeshProで使える形式(拡張子は.assetになってる)に変換してから使用する。
今回はこのファイルを更新(新規にフォントを追加したいのではなく)したい場合の手順を記載する。

▼詳細はこちら www.snoopopo.com

なんで更新したいの

フォントアセットを作る際に日本語の場合、そのフォントで表示したい文字を列挙する必要がありますがそこに「髙」を入れていませんでした。
フォントの配布元サイトを確認するとフォントはこの文字に対応しているため、
フォントアセットを作るときに「髙」含めていなかった事が原因でUnityで表示できていません。

▲ない文字は□で表示されてしまいます

※たぬき油性マジック というフォントを使わせて頂きました。 tanukifont.com

既存のTextMeshProのフォントファイルを更新する方法

作成した時と同じようにTextMeshProのFontAssetCreatorを開きます。
既存のフォントアセットは削除しないで下さい。

フォントアセットのGenerationSettingsから転記する

↑のキャプチャまでの設定値は、元のフォントアセットのGenerationSettingsにあります。
なので変えたい部分以外はこの設定値を元に設定していけばOKです。

Character Set について

「Custom Characters」にすると任意の文字を入力できます。基本的に日本語を表示したい場合はこちらの設定をしているかと思います。
「Select Font Asset」には、既存のフォントアセットを指定します。
指定するとCustom CharacterListに作成した時に設定した文字の一覧が自動的に入力されます。

今回はここに「髙」を追加して、Generate Font Atlasボタンを押して作り直します。

既存のフォントアセットを上書きする

フォントファイルができたら、元のフォントアセットを上書きしましょう。
ここで元のフォントアセットと別ファイルにしてしまうと、元々参照していた部分に再設定しないといけないので上書きがよいです。
(例えば以下のようにTextMeshProのコンポーネントに既に指定している場合)

これで「髙」の字を表示することができました!

【TextMeshPro】<material>タグで使うフォントマテリアルをResouces以外の場所に置く

前提:<material>タグ?


TextMeshProの<material>タグを使うとテキスト内の一部に指定したマテリアル(shader)に適応することが出来ます。

ここだけ<material="testTmproMaterial">マテリアル</material>を適応してるよ

このとき使うマテリアルは、TMP SettingsのDefault Font Asset に設定された
Resoucesフォルダ配下に予め配置しておく必要があります。(デフォルトだとResouces/Fonts & Materials/配下になります。)


しかしResouces配下におかないといけないというのは不都合が出てくる場合があります。
Addressableの登場から昨今はResoucesを積極的に使う現場は少なくなっているかと思います。
仮にフォントファイルをAddressableで管理した場合、
このフォントマテリアルもAddressable側に配置しないと二重でメモリに読み込まれたりといった問題も発生するようです。

本題:フォントマテリアルをResouces以外から読み込めるようにする

MaterialReferenceManager.AddFontMaterialを使って実行時にマテリアルを読み込む事で、
Resouces以外の場所にあるフォントマテリアルを使う事ができます。

public class TestTmproMaterial : MonoBehaviour
{
    [SerializeField] private Material mat; //今回は単純にしたいので設定したいマテリアルをここにアタッチしてます

    void Start()
    {
        int hashCode = TMP_TextUtilities.GetSimpleHashCode(mat.name); //GetHashCodeではないので注意
        if(!MaterialReferenceManager.TryGetMaterial(hashCode, out Material tmpMat))
    {
            MaterialReferenceManager.AddFontMaterial(hashCode, mat);
    }
    }
}

【uGUI】ContentsSizeFitterのサイズ反映をすぐに行う方法と注意点

前提

ContentsSizeFitterは設定したコンテンツにあわせてRectTransformのサイズを調整してくれるものだが、これは即反映されない。

たとえば以下のように、テキストにContentsSizeFitterを設定し、テキストの親クラスにテキストの周りに表示する枠画像をもった状態があるとする。

そして実行時にテキストの内容を「あいうえお」→「はろーわーるど!」に変えて、枠画像のサイズもそれにあわせて調整したいとする。

using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class test_contentsSizeFitter : MonoBehaviour
{

    public Image frame;
    public TextMeshProUGUI tmp;

    void Start()
    {
        tmp.SetText("はろーわーるど!");
        frame.rectTransform.sizeDelta = tmp.rectTransform.sizeDelta + new Vector2(40, 60);
    }

}

これの実行結果は↓で意図しないものになる。

ContentsSizeFitterのサイズ反映すぐに行う

そこで、ContentSizeFitter の SetLayoutHorizontalSetLayoutVerticalを同じフレーム内で呼び出し、
即時ContentsSizeFitterのサイズ反映を行うようにする

using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class test_contentsSizeFitter : MonoBehaviour
{

    public Image frame;
    public TextMeshProUGUI tmp;

    void Start()
    {
        tmp.SetText("はろーわーるど!");

        ContentSizeFitter csf = tmp.GetComponent<ContentSizeFitter>();
        csf.SetLayoutHorizontal();        //←add!!!
        csf.SetLayoutVertical();        //←add!!!

        frame.rectTransform.sizeDelta = tmp.rectTransform.sizeDelta + new Vector2(40, 60);
    }
}

実行結果は以下になり、これで意図した対応ができた。

注意点:ContentSizeFitter をアタッチしたGameObjectがアクティブでないと働かない

非アクティブになっているGameObjectに対して同等の処理を行いたい場合には注意が必要だ。

注意が必要な状態というのは以下のどちらもあてはまる。ようはヒエラルキーでグレーアウトして非表示になっているという状態。
①ContentSizeFitterをアタッチしたGameObject自体が非アクティブの場合


②ContentSizeFitterをアタッチしたGameObjectの親が非アクティブの場合


こんなケースはないと思う人もいるかもしれないが、なくはない。
非表示のゲームオブジェクトにテキストを設定した後、後で特定のタイミングで表示するようなケースだ。

というわけで、実行時には非アクティブなテキストの内容を「あいうえお」→「はろーわーるど!」に変えて、枠画像のサイズもそれにあわせて調整。その後、ボタンを押すとテキストが表示されるという状況を作ってみる。

using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class test_contentsSizeFitter : MonoBehaviour
{
    public Button activeButton;

    public Image frame;
    public TextMeshProUGUI tmp;

    void Start()
    {
        activeButton.onClick.AddListener(() =>
        {//ボタンを押すと予め、非表示だったテキストが表示される
            this.tmp.gameObject.SetActive(true);
        });

        tmp.SetText("はろーわーるど!");

        ContentSizeFitter csf = tmp.GetComponent<ContentSizeFitter>();
        csf.SetLayoutHorizontal();
        csf.SetLayoutVertical();

        frame.rectTransform.sizeDelta = tmp.rectTransform.sizeDelta + new Vector2(40, 60);
    }

この実行結果は以下で意図した結果にならないのだ。


というわけでこれに対応するには、以下が考えられる。 ①表示したときにContentSizeFitter の SetLayoutHorizontalSetLayoutVerticalを呼ぶ ②テキスト設定時にサイズ反映するタイミングで一旦アクティブにする

②で対応しようとすると以下のようなソースになる。

using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class test_contentsSizeFitter : MonoBehaviour
{
    public Button activeButton;

    public Image frame;
    public TextMeshProUGUI tmp;

    void Start()
    {
        activeButton.onClick.AddListener(() =>
        {
            this.tmp.gameObject.SetActive(true);
        });

        tmp.SetText("はろーわーるど!");

        ContentSizeFitter csf = tmp.GetComponent<ContentSizeFitter>();
        bool active = csf.gameObject.activeInHierarchy;
        csf.gameObject.SetActive(true); //一時的にアクティブに
        csf.SetLayoutHorizontal();
        csf.SetLayoutVertical();
    if (!active)
    {
            csf.gameObject.SetActive(false); //元に戻す
    }

        frame.rectTransform.sizeDelta = tmp.rectTransform.sizeDelta + new Vector2(40, 60);
    }
}

これで意図した表示になった↓