質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.35%

Unity テクスチャをフェードさせたい

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 330
退会済みユーザー

退会済みユーザー

前提・実現したいこと

マテリアルに適応したテクスチャを、カメラのorthographicSizeに応じてフェードさせたい

該当のソースコード

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

public class MipMapExample : MonoBehaviour
{
    float cameraSize;
    public Texture2D tex128128;
    public Texture2D tex6464;
    public Texture2D tex3232;
    GameObject[] Quads = new GameObject[900];
    public GameObject Quad;

    private void Start()
    {
        int index = 0;
        for(int x = 0;x < 30; x++)
        {
            for(int y = 0;y < 30; y++)
            {
                Vector2 pos = new Vector2(x, y);
                Quads[index] = Instantiate(Quad, pos, Quaternion.identity);
                index++;
            }
        }
    }

    private void Update()
    {
        cameraSize = Camera.main.orthographicSize;

        foreach(GameObject item in Quads)
        {
            if (cameraSize < 7.5f)
            {
                item.GetComponent<Renderer>().material.mainTexture = tex128128;
            }
            else if (cameraSize < 15)
            {
                item.GetComponent<Renderer>().material.mainTexture = tex6464;
            }
            else if (cameraSize < 20f)
            {
                item.GetComponent<Renderer>().material.mainTexture = tex3232;
            }
        }
    }
}

試したこと

カメラのorthographicSizeが大きくなると、テクスチャが潰れて見えてしまうため、orthographicSizeに応じて、

128×128

64×64

32×32

という風に、小さいサイズのテクスチャに変更するようにしたのですが、テクスチャが突然変更されるため、違和感があります。orthographicSizeに応じて、テクスチャが緩やかに変更されていくようにする方法は、あるでしょうか?

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+2

私からも意見を申し上げさせていただくと、やはりsakura_hanaさんのおっしゃるようにUnityに搭載されているミップマップ機能を使った方が無難かもしれません。スクリプト側で毎フレーム全オブジェクトを総なめしてテクスチャをセットするというのは何だか大変そうな気がしますし、なるべくGPU側に処理を任せるのがいいんじゃないかと思います。

たとえばスクリプトを下記のようにすると、縮尺に応じてテクスチャの見た目がなめらかに変化するはずです。

using UnityEngine;

public class MipMapExample : MonoBehaviour
{
    public Texture2D tex128128;
    public Texture2D tex6464;
    public Texture2D tex3232;
    GameObject[] Quads = new GameObject[900];
    public GameObject Quad;

    // tex128128、tex6464、tex3232を一つにまとめたテクスチャ
    // 本来はシリアライズするべきフィールドではないが、できあがったテクスチャの内容を
    // インスペクター上で手軽に確認できるようSerializeFieldを付けた
    [SerializeField] private Texture2D tex;

    private void Start()
    {
        // 3レベルのミップマップを持つテクスチャを作成し...
        tex = new Texture2D(tex128128.width, tex128128.height, tex128128.format, 3, false);

        // tex128128、tex6464、tex3232のデータを各レベルにセットする
        // なお、GetRawTextureDataを使うにはテクスチャの「Read/Write Enabled」をオンにしておく必要がある
        tex.SetPixelData(tex128128.GetRawTextureData(), 0);
        tex.SetPixelData(tex6464.GetRawTextureData(), 1);
        tex.SetPixelData(tex3232.GetRawTextureData(), 2);

        // フィルターモードはトライリニア補間とする
        tex.filterMode = FilterMode.Trilinear;

        // 必要に応じてミップマップレベル選択にバイアスを設けることもできる
        // 通常はバイアスは0にしておけば最適なはずだが、ミップマップが切り替わる
        // タイミングを調整したい場合があるかもしれない
        // たとえば下記のように-1とすると、仮に自動選択されたミップマップが
        // レベル1...つまりtex6464であるような拡縮条件では、このレベルが1段階
        // 下がって0となり、レベル0...つまりtex128128が表示されるはず
        // 大ざっぱに言うと、バイアスをマイナスに振れば高解像度テクスチャ...つまり
        // tex128128やtex6464が表示されやすく、プラスに振れば低解像度テクスチャ...
        // つまりtex6464やtex3232が表示されやすいということになる
        tex.mipMapBias = -1.0f;

        // テクスチャをGPU側にアップロードする
        tex.Apply(false, true);

        // Quadをインスタンス化して配置する前に、まずQuadのマテリアルを複製してしまい...
        Renderer quadRenderer = Quad.GetComponent<Renderer>();
        Material quadMaterial = quadRenderer.sharedMaterial;
        Material newQuadMaterial = Instantiate(quadMaterial);

        // マテリアルにテクスチャをセットし...
        newQuadMaterial.mainTexture = tex;

        // それをquadRendererの共有マテリアルとする
        // インスタンス化されたすべてのQuadには共通のテクスチャを持たせているようだったので、
        // それならばいっそマテリアルごと共通化してしまっていいんじゃないかと思い、このようにしてみた
        quadRenderer.sharedMaterial = newQuadMaterial;

        int index = 0;
        for(int x = 0; x < 30; x++)
        {
            for(int y = 0; y < 30; y++)
            {
                Vector2 pos = new Vector2(x, y);
                Quads[index] = Instantiate(Quad, pos, Quaternion.identity);
                index++;
            }
        }

        // プレハブのマテリアルは元に戻しておく
        quadRenderer.sharedMaterial = quadMaterial;
    }
}

こういった形なら、Orthographic Sizeやゲーム画面の解像度だとかをいちいち考慮しなくても、縮尺に合わせて最適なミップマップレベルが選択されるでしょうからおすすめだと思います。

それとも、ご質問者さんのコードですとOrthographic Sizeを見てテクスチャを切り替えていますが、ご質問の文面通り「Orthographic Sizeが○○の時はミップマップレベル△△」みたいにOrthographic Sizeに依存した切り替え方をしたいということでしょうか?
そういった場合にはおそらくマテリアルのシェーダーコードもカスタマイズする必要があるんじゃないかと思います。
スクリプトを下記のようにして...

using UnityEngine;

public class MipMapExample2 : MonoBehaviour
{
    public float level0Size = 7.5f;
    public Texture2D tex128128;
    public float level1Size = 15.0f;
    public Texture2D tex6464;
    public float level2Size = 20.0f;
    public Texture2D tex3232;
    public GameObject Quad;

    GameObject[] Quads = new GameObject[900];

    [SerializeField] private Texture2D tex;

    private void Start()
    {
        // MipMapExampleと同様にテクスチャを作成する
        // バイアスは0のままいじらないでおく
        tex = new Texture2D(tex128128.width, tex128128.height, tex128128.format, 3, false);
        tex.SetPixelData(tex128128.GetRawTextureData(), 0);
        tex.SetPixelData(tex6464.GetRawTextureData(), 1);
        tex.SetPixelData(tex3232.GetRawTextureData(), 2);
        tex.filterMode = FilterMode.Trilinear;
        tex.Apply(false, true);

        // マテリアルも同様に準備し...
        Renderer quadRenderer = Quad.GetComponent<Renderer>();
        Material quadMaterial = quadRenderer.sharedMaterial;
        Material newQuadMaterial = Instantiate(quadMaterial);
        newQuadMaterial.mainTexture = tex;
        quadRenderer.sharedMaterial = newQuadMaterial;

        // ミップマップレベル選択用パラメーターを決める
        // まずゲーム画面のピクセル単位の高さを取得する
        int screenHeight = Screen.height;

        // 各レベルのV変化率を求める
        float dVdy0 = (this.level0Size * 2.0f) / screenHeight;
        float dVdy1 = (this.level1Size * 2.0f) / screenHeight;
        float dVdy2 = (this.level2Size * 2.0f) / screenHeight;

        // レベル間は3次スプライン曲線でつなぐことにする
        // そこでレベル0から1をつなぐ3次曲線とレベル1から2をつなぐ3次曲線の係数を決める
        // 計算手順は http://www.yamamo10.jp/yamamoto/lecture/2006/5E/interpolation/interpolation_html/node3.html の方法を使った
        // 曲線は「レベル0の時のV変化率にかけることで目的のV変化率を得るスケール値」を表しており、
        // シェーダー上で実測したV変化率がdVdy0の時は1.0、dVdy1の時は2.0、dVdy2の時は4.0を返すようにする
        float h0 = dVdy1 - dVdy0;
        float h1 = dVdy2 - dVdy1;
        float v1 = 6.0f * ((2.0f / h1) - (1.0f / h0));
        float u1 = (v1 * 0.5f) / (h0 + h1);
        float b1 = u1 * 0.5f;
        float a0 = u1 / (6.0f * h0);
        float a1 = -u1 / (6.0f * h1);
        float c0 = (1.0f / h0) - ((h0 * u1) / 6.0f);
        float c1 = (2.0f / h1) - ((h1 * 2.0f * u1) / 6.0f);
        Vector4 coefficients0 = new Vector4(a0, 0.0f, c0, 1.0f);
        Vector4 coefficients1 = new Vector4(a1, b1, c1, 2.0f);

        // マテリアルにパラメーターをセットする
        newQuadMaterial.SetVector("_ControlPoints", new Vector3(dVdy0, dVdy1, dVdy2));
        newQuadMaterial.SetVector("_Coefficients0", coefficients0);
        newQuadMaterial.SetVector("_Coefficients1", coefficients1);

        int index = 0;
        for(int x = 0; x < 30; x++)
        {
            for(int y = 0; y < 30; y++)
            {
                Vector2 pos = new Vector2(x, y);
                Quads[index] = Instantiate(Quad, pos, Quaternion.identity);
                index++;
            }
        }

        // プレハブのマテリアルは元に戻しておく
        quadRenderer.sharedMaterial = quadMaterial;
    }
}

マテリアルは下記のようにし...

Shader "Unlit/RemappedMipmap"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _ControlPoints ("Reference V Gradients", Vector) = (0, 0, 0, 0)
        _Coefficients0 ("Coefficients for Level 0 to 1", Vector) = (0, 0, 0, 0)
        _Coefficients1 ("Coefficients for Level 1 to 2", Vector) = (0, 0, 0, 0)
    }
    SubShader
    {
        Tags { "Queue"="Transparent" "RenderType"="Transparent" }
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

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

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _MainTex_TexelSize;
            float3 _ControlPoints;
            float4 _Coefficients0;
            float4 _Coefficients1;

            // 実測したV変化率からV変化率スケールを算出する
            float getScale(float v)
            {
                v = clamp(v, _ControlPoints.x, _ControlPoints.z);
                float segment = step(_ControlPoints.y, v);
                float x = v - lerp(_ControlPoints.x, _ControlPoints.y, segment);
                float x2 = x * x;
                return dot(lerp(_Coefficients0, _Coefficients1, segment), float4(x2 * x, x2, x, 1.0f));
            }

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                float2 gradient = float2(_MainTex_TexelSize.y * getScale(abs(ddy(i.uv.y))), 0.0);
                return tex2Dgrad(_MainTex, i.uv, gradient, gradient.yx);
            }

            ENDCG
        }
    }
}

各レベルは下図のような画像を使ったところ...

tex128128 tex6464 tex3232
図1 図2 図3

下図のような見た目になりました。Orthographic Sizeが7.5までは赤色で、7.5から15にかけて次第に緑色に変わっていき、15から20にかけて次第に青色に変化しています。

図4

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/08/20 17:21

    回答ありがとうございました。参考になりました。

    キャンセル

0

上手くいくか分かりませんが、間にもう一段階挟み、「128×128で見た目を64×64に近付けたテクスチャ」「64×64で見た目を32×32に近付けたテクスチャ」をセットすることで擬似的に補間出来ないでしょうか。

また、今回のニーズに合うか分かりませんが、Unity標準のmipmapシステムも確認してみるとよいかと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/08/19 13:07

    回答ありがとうございました

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.35%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る