💦

[Unity]画像をグリッド状に分けて一部だけを表示するシェーダーを作ってみた

2024/05/26に公開

はじめに

タイトルでは何を言いたいのかさっぱりかもしれませんが、作ったものをなんと説明すればいいかわからずこんなタイトルになってしまいました。
下のgifのように穴あきにすることができます。


元画像

大事なこと

シェーダーコード

CheckerShader
Shader "Unlit/CheckerShader"
{
	SubShader
	{
		Tags { "RenderType" = "Opaque" }
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

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

			struct v2f
			{
				float2 uv : TEXCOORD0;
				UNITY_FOG_COORDS(1)
				float4 vertex : SV_POSITION;
				float4 pos : TEXCOORD1;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;

			// 横セル数
			int _CellRowCount;

			// 縦セル数
			int _CellColCount;

			// 一応300で確保
			// 動的に長さを変えれないものか
			float _FillCellArray[300];


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

			fixed4 frag(v2f i) : SV_Target
			{
				// MainTexから
				fixed4 color = tex2D(_MainTex, i.uv);

			// 比較しやすいように uv を拡大する
			float2 newUV = i.uv * float2(_CellRowCount, _CellColCount);

			// 該当の座標がどのセル(マス目)に属するか調べていく
			for (int row = 0; row < _CellRowCount; row++)
			{
				for (int col = 0; col < _CellColCount; col++)
				{
					// セルの範囲内か調べる
					int Left = step(row, newUV.x);
					int Right = step(newUV.x, row + 1);
					int Down = step(col, newUV.y);
					int Up = step(newUV.y, col + 1);
					int isInnner = Left * Right * Up * Down;

					// 仕方なしif文
					if (isInnner == 1)
					{
						int index = row + (_CellRowCount * col);

						// FillCellArrayは着色情報
						// 0 : なし 1: 色あり
						return color * _FillCellArray[index];
					}
				}
			}

			// 色なしで返しておく
			return color * 0;
		}
		ENDCG
	}
	}
}

使い方

  1. 適当なマテリアルを作成
  2. ShaderをUnlit > CheckerShader に設定
  3. 適当なオブジェクトに下記のスクリプトをアタッチする
  4. インスペクターに表示されている項目を適当に設定
マテリアル適用側のCS
CheckerTest.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
using UniRx;

public class CheckerTest : MonoBehaviour
{
    //! Imageコンポーネント
    [SerializeField]
    protected Image _img = null;

    //! 使用するテクスチャ
    [SerializeField]
    protected Texture2D _imgTex = null;

    //! マテリアルアセット
    [SerializeField]
    protected Material _materialAsset = null;

    //! ボタン
    [SerializeField]
    protected Button _generateButton = null;
    
    //! 生成したマテリアル
    protected Material _material = null;

    private void Start()
    {
        // ボタンを押した時のイベント
        _generateButton.OnClickAsObservable().Subscribe( _ => Generate() ).AddTo(this);

        // マテリアルを生成
        _material = Instantiate(_materialAsset);

        // Imageのマテリアルに設定
        _img.material = _material;
    }

    protected void Generate()
    {
        // 横マス数
        int rowCount = 7;
        
        // 縦マス数
        int colCount = 5;

        // どのマスに画像を表示するかランダムで決めている
        // Trueなら画像を表示
        float[] fillCellArray = new float[rowCount * colCount];
        for (int row = 0; row < rowCount; row++)
        {
            for (int col = 0; col < colCount; col++)
            {
                int value = (int)UnityEngine.Random.Range(0, 2);
                var index = row + (rowCount * col);
                fillCellArray[index] = value;
            }
        }

        // マテリアルに値を設定
        {
            // テクスチャ
            _material.SetTexture("_MainTex", _imgTex);
            
            // 横のマス数
            _material.SetInt("_CellRowCount", rowCount);
            
            // 縦のマス数
            _material.SetInt("_CellColCount", colCount);

            // どのマスに画像を表示するか
            _material.SetFloatArray("_FillCellArray", fillCellArray);
        }
        
    }

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

補足

        // マテリアルに値を設定
        {
            // テクスチャ
            _material.SetTexture("_MainTex", _imgTex);
            
            // 横のマス数
            _material.SetInt("_CellRowCount", rowCount);
            
            // 縦のマス数
            _material.SetInt("_CellColCount", colCount);

            // どのマスに画像を表示するか
            _material.SetFloatArray("_FillCellArray", fillCellArray);
        }

マテリアルに干渉しているのは上記の部分。
fillCellArrayの中身を良い感じにいじれば、画像を徐々に表示させるなんてこともできると思います。

また、マテリアルはInstantiateしたものに変更を加えるようにしています。
こうしている理由は、インスペクタで拾ったアセットやImageコンポーネントから拾ったものに変更を加えてしまうと、アセットファイルそのものが書き換わってしまって煩わしいからです。

おわりに

冒頭でも記載していますが、シェーダー初心者が作ったものなので、鵜呑みにしないようご注意ください。
なんなら、間違いを指摘してもらいたくて記事にしている節があります。

もしこの記事が何かしらのお役に立てれば幸いです。

Discussion