LoginSignup
10
7

More than 5 years have passed since last update.

Unity ちょっとシェーダーで遊んでみた

Last updated at Posted at 2017-12-20

本記事はサムザップ Advent Calendar 2017の20日目の記事です。
昨日は @fujisyun さんの プロジェクトが上手くいかなくなる前に知っておくこと その3 でした。

記事の最後に「楽しみ」なんて
そんなサプライズは辛いですよ!!!

っと、プレッシャーに負けず
初めていきましょう。

本記事について

最近、知識だけになっていて、あまり実装に触れていなかった Unity の Shader 部分
ちょっと機会があったので、これを気に勉強がてら遊んでみたもの(過去の振り返り)になります。

◆素材

hiTex.png
画像一枚のみ!
α値 も入っていない、この画像がどうなっていくのか!

それでは遊んでいきましょう!

◆表示

今回は MeshFilter と MeshRenderer で表示するので、これらのコンポーネントを付けた GameObject の用意(以下、細かい部分は省略 ※)
※即席で作った概念的なソースコードを記載します。初期のソースコード残ってないのです。ごめんなさいm(__)m

    gameObject.AddComponent<MeshFilter>();
    gameObject.AddComponent<MeshRenderer>();

MeshFilter に渡す Mesh の用意。

    Mesh mesh = new Mesh();

    Texture2D tex = "上記の画像";
    float halfWidth = tex.width * 0.5f;
    float halfHeight = tex.height * 0.5f;

    mesh.vertices = new Vector3[]
    {
        new Vector3( -halfWidth, -halfHeight, 0.0f ),
        new Vector3( -halfWidth,  halfHeight, 0.0f ),
        new Vector3(  halfWidth,  halfHeight, 0.0f ),
        new Vector3(  halfWidth, -halfHeight, 0.0f ),
    };
    mesh.uv = new Vector2[]
    {
        new Vector2( 0.0f, 0.0f ),
        new Vector2( 0.0f, 1.0f ),
        new Vector2( 1.0f, 1.0f ),
        new Vector2( 1.0f, 0.0f ),
    };
    mesh.colors = new Color[] { Color.white, Color.white, Color.white, Color.white, };
    mesh.normals = new Vector3[] { Vector3.back, Vector3.back, Vector3.back, Vector3.back, };
    mesh.triangles = new int[] { 0, 1, 2, 0, 2, 3, };

    mesh.MarkDynamic();
    mesh.RecalculateNormals();
    mesh.RecalculateBounds();

Shader は、シンプルな vertex と fragment の用意

    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    }

    SubShader
    {
        Pass
        {
            #pragma vertex vert
            #pragma fragment frag

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

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

            sampler2D _MainTex;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.uv;
                return o;
            }
            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
        }
    }

MeshRenderer に渡す Material の用意

    material.mainTexture = "上記の画像";
    material.shader = "上記のShader"

これで最低限必要なものが整ったで実行してみます。
Step01.png
ハイ!準備した画像が表示されました。

◆Shader

まずは fragment 部分で加工したいと思います。

・グレースケール化 ( fragment )

画像その物の色は、今回は必要ないのでグレースケール化します。(Google先生に聞けば、計算式は簡単に出てきます。)

今回使った勝手に単純化した式
Y=R\times0.3+G\times0.6+B\times0.1

これを fragment の中で計算し RGB に代入して表示してみます。

    fixed4 col = tex2D(_MainTex, i.uv);
    fixed y = col.r*0.3 + col.g*0.6 + col.b * 0.1;

    col = fixed4(y, y, y, 0.0);

Step02.png

こんな単純な式でも、グレースケールになりました。


・彩色 ( fragment )

次は、これに色を入れていきたいと思います。

2階調に分けて色を入れていきましょう。
Shader の外から変更できるように Properties に入れちゃいます。

    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
        _AlphaRate("Alpha Rate", Range(0.0,1.0)) = 0.0
        _Color1("Color1", Color) = (0.0,0.0,0.0,0.0)
        _Color1MaxRate("Color1 MaxRate", Range(0.0,1.0)) = 0.0
        _Color2("Color2", Color) = (0.0,0.0,0.0,0.0)
        _Color2MaxRate("Color2 MaxRate", Range(0.0,1.0)) = 0.0
    }
    SubShader
    {
        Pass
        {
            sampler2D _MainTex;
            fixed4 _Color1;
            fixed4 _Color2;
            float _AlphaRate;
            float _Color1MaxRate;
            float _Color2MaxRate;


            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed y = col.r*0.3 + col.g*0.6 + col.b * 0.1;

                if( y <= _AlphaRate )
                {
                    col = fixed4( 0, 0, 0, 0 ); // _AlphaRate 以下は、αで抜く
                }
                else if( y <= _Color1MaxRate )
                {
                    col = _Color1;
                }
                else if( y <= _Color2MaxRate )
                {
                    col = _Color2;
                }

                return col;
            }
        }
    }

Step03.png
うーん、汚いけど2色になったし、これで良しとしよう。


・明滅 ( vertex / fragment )

今回は、指定時間の特定の閾値で明滅する処理をやっていきたいと思います。

まず、こんな感じの処理を Shader に追加しました。
時間を見ながら処理を変化させるので _Time を使います。

    float rate( float maxRate, float time )
    {
        maxRate *= time;

        float timeRate = _Time.y % time;

        if (timeRate < maxRate)
        {
            return clamp(timeRate / maxRate, 0.0, time);
        }
        else
        {
            return (time - clamp((timeRate - maxRate) / (time - maxRate), 0.0, time));
        }
    }

時間の指定を Shader の外から変更できるように Properties に追加します。

    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
        _AlphaRate("Alpha Rate", Range(0.0,1.0)) = 0.0
        _Color1("Color1", Color) = (0.0,0.0,0.0,0.0)
        _Color1MaxRate("Color1 MaxRate", Range(0.0,1.0)) = 0.0
        _Color2("Color2", Color) = (0.0,0.0,0.0,0.0)
        _Color2MaxRate("Color2 MaxRate", Range(0.0,1.0)) = 0.0

        _LifeTime("LifeTime", float) = 0.0
    }
    SubShader
    {
        Pass
        {
            sampler2D _MainTex;
            fixed4 _Color1;
            fixed4 _Color2;
            float _AlphaRate;
            float _Color1MaxRate;
            float _Color2MaxRate;

            float _LifeTime;
        }
    }

これらの処理を vertex で行い fragment に渡します。
値を渡すため構造体に追加。
fragment で受け取った値をカラーに乗算する。

    struct v2f
    {
        float4 vertex : SV_POSITION;
        float2 uv : TEXCOORD0;
        float rate : PSIZE;
    };
    v2f vert(appdata v)
    {
        v2f o;
        o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
        o.uv = v.uv;
        o.rate = rate( 0.7, _LifeTime );
        return o;
    }
    fixed4 frag(v2f i) : SV_Target
    {
        fixed4 col = tex2D(_MainTex, i.uv);
        fixed y = col.r*0.3 + col.g*0.6 + col.b * 0.1;

        if( y <= _AlphaRate )
        {
            col = fixed4( 0, 0, 0, 0 ); // _AlphaRate 以下は、αで抜く
        }
        else if( y <= _Color1MaxRate )
        {
            col = _Color1;
        }
        else if( y <= _Color2MaxRate )
        {
            col = _Color2;
        }
        col *= i.rate;
        return col;
    }

Step04.gif


・回転 ( vertex )

Shader 内で回転処理を行います。
時間に合わせて変化させるので _Time を使います
sin / cos を使うので、時間から角度を出し radians で変化します。

    v2f vert(appdata v)
    {
        v2f o;

        float rot = radians( _Time.y * 128 % 360.0 ); // 回転が遅かったので 128倍!
        float x = (v.vertex.x * cos(rot)) - (v.vertex.y * sin(rot));
        float y = (v.vertex.x * sin(rot)) + (v.vertex.y * cos(rot));
        v.vertex.x = x;
        v.vertex.y = y;

        v.vertex.x += v.vertex.x * sin(rot) * 0.3; // ついでにユラユラ

        o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
        o.uv = v.uv;
        o.rate = rate( 0.7, _LifeTime );
        return o;
    }

Step05.gif

◆メッシュを量産

ここまで来たので、もっといっぱい表示してみようと思い
画像10枚分、Mesh は一つのまま、中の vertex を4頂点 → 40頂点に増やし
一定の範囲内で、頂点をズラして作成してみました。
Step06.gif
あれ? もっとバラバラに出るのを想定していたんだが。。。
頂点毎に _Time が変わると思い込んでいました。

◆遊びが本気に

どうにかして、バラバラに動かしたい!!
1Mesh 1Shader でバラバラにしたい!!

ながーいながーい(意外と短い)格闘の末
こんなやり方ありなのか?と思うこともやりつつ出来上がったのがこれ
Step07.gif

こんなやり方とは、
使ってない変数があったんで使ってみたです。
具体的には Mesh の uv2 uv3 uv4 です。
Vector2 が 3つ。Float 6つ 分の値が各頂点にバラバラに送れる。
値がバラバラなら vertex もバラバラに動かせると考えたわけです。

◆おわりに

最後は、無駄なプライド?というか意地というか欲求が出てしまいましたが、
一枚の画像を Mesh にいろんな情報を渡して
大部分を Shader で無理やり動かしてみたでした。

これを元に何かの仕組みを作っていきたいですね。

そして、これが皆様にとって、何かしらの参考になれば嬉しい限りです。

即席で作ったコードばかりを載せていたので
最終的なコードを載せておきます。

各ソース
FireTestMono.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Assets.Scripts
{
    public class FireTestMono : MonoBehaviour
    {
        public int Emit = 30;
        public float EmitRange = 15.0f;
        [Range(0.0f, 360.0f)]public float EmitRot = 90.0f;
        [Range(0.1f, 60.0f)]public float EmitLifeTime = 0.8f;

        void Start()
        {
            Fire.FIRE_TEST = this;
            new Fire();
        }
    }
}
Fire.cs
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Assets.Scripts
{
    public class Fire
    {
        public const string FireMaterialPath = "hi/hiMat";

        public static FireTestMono FIRE_TEST = null; // テスト用に無理やり呼び出すように

        public static int Emit { get { return FIRE_TEST == null ? 0 : FIRE_TEST.Emit; } }
        public static float EmitRange { get { return FIRE_TEST == null ? 0.0f : FIRE_TEST.EmitRange; } }
        public static float EmitRot { get { return FIRE_TEST == null ? 0.0f : FIRE_TEST.EmitRot; } }
        public static float EmitLifeTime { get { return FIRE_TEST == null ? 0.0f : FIRE_TEST.EmitLifeTime; } }
        public static float EmittingTime{ get { return 1.0f/(float)Emit; } }

        Material fireMaterial = null;
        GameObject fireObject = null;

        MeshFilter meshFilter = null;
        MeshRenderer meshRenderer = null;

        TextureMeshInfo meshInfo = null;

        List<EmitObject> emitObject = new List<EmitObject>();

        public Fire()
        {
            DataLoad();
        }
        protected void DataLoad()
        {
            if( fireMaterial == null )
                Fire.FIRE_TEST.StartCoroutine( DataLoadAsync( FireMaterialPath ) );
        }

        protected IEnumerator DataLoadAsync( string path )
        {
            ResourceRequest request = Resources.LoadAsync<Material>( path );

            while( request.isDone ) yield return null;

            if( request.asset != null )
            {
                fireMaterial = request.asset as Material;
            }

            Create( FIRE_TEST.gameObject );

        }

        public GameObject Create( GameObject parent )
        {
            if( fireObject == null )
            {
                fireObject = new GameObject( "FireObject" );

                fireObject.transform.parent = parent.transform;
                fireObject.transform.localPosition = Vector3.back;
                fireObject.transform.localRotation = Quaternion.identity;
                fireObject.transform.localScale = Vector3.one;

                fireObject.layer = parent.gameObject.layer;

                meshFilter = fireObject.AddComponent<MeshFilter>();
                meshRenderer = fireObject.AddComponent<MeshRenderer>();


                meshInfo = new TextureMeshInfo( (Texture2D)fireMaterial.mainTexture );


                //emitObject.Add( new EmitObject( new Vector3( -192.0f, -64.0f, 0.0f ), meshInfo, ViewUpdata ) );
                //emitObject.Add( new EmitObject( new Vector3( -128.0f, -32.0f, 0.0f ), meshInfo, ViewUpdata ) );
                emitObject.Add( new EmitObject( new Vector3( -64.0f, -16.0f, 0.0f ), meshInfo, ViewUpdata ) );
                emitObject.Add( new EmitObject( new Vector3( 0.0f, 0.0f, 0.0f ), meshInfo, ViewUpdata ) );
                emitObject.Add( new EmitObject( new Vector3( 64.0f, -16.0f, 0.0f ), meshInfo, ViewUpdata ) );
                //emitObject.Add( new EmitObject( new Vector3( 128.0f, -32.0f, 0.0f ), meshInfo, ViewUpdata ) );
                //emitObject.Add( new EmitObject( new Vector3( 192.0f, -64.0f, 0.0f ), meshInfo, ViewUpdata ) );
            }

            return fireObject;
        }

        public void ViewUpdata()
        {
            if( meshFilter.sharedMesh != null )
            {
                meshFilter.sharedMesh.Clear();
                meshFilter.sharedMesh.ClearBlendShapes();
            }

            meshFilter.sharedMesh = GetMesh();
            meshFilter.sharedMesh.MarkDynamic();
            meshFilter.sharedMesh.RecalculateNormals();
            meshFilter.sharedMesh.RecalculateBounds();

            meshRenderer.sharedMaterial = fireMaterial;
        }



        public Mesh GetMesh()
        {
            Mesh mesh = new Mesh();
            Vector3[] vertice = new Vector3[] { };
            Vector2[] uv = new Vector2[] { };
            Vector2[] uv2 = new Vector2[] { };
            Vector2[] uv3 = new Vector2[] { };
            Vector2[] uv4 = new Vector2[] { };
            Color[] color = new Color[] { };
            int[] triangle = new int[] { };
            Vector3[] normal = new Vector3[] { };

            int triangleCount = 0;
            for( int index = 0; index < emitObject.Count; index++ )
            {
                List<TextureMeshInfo> meshLIst = emitObject[ index ].GetEmitMesh();
                for( int infoIndex = 0; infoIndex < meshLIst.Count; infoIndex++ )
                {
                    vertice = AddValue<Vector3>( vertice, meshLIst[ infoIndex ].vertice );
                    uv = AddValue<Vector2>( uv, meshLIst[ infoIndex ].uv );
                    uv2 = AddValue<Vector2>( uv2, meshLIst[ infoIndex ].uv2 );
                    uv3 = AddValue<Vector2>( uv3, meshLIst[ infoIndex ].uv3 );
                    uv4 = AddValue<Vector2>( uv4, meshLIst[ infoIndex ].uv4 );
                    color = AddValue<Color>( color, meshLIst[ infoIndex ].verticeColor );
                    normal = AddValue<Vector3>( normal, meshLIst[ infoIndex ].normal );
                    for( int Count = 0; Count < meshLIst[ infoIndex ].triangle.Length; Count++ )
                    {
                        triangle = AddValue<int>( triangle, new int[] { meshLIst[ infoIndex ].triangle[ Count ] + ( triangleCount * 4 ) } );
                    }
                    triangleCount++;
                }
            }

            mesh.vertices = vertice;
            mesh.uv = uv;
            mesh.uv2 = uv2;
            mesh.uv3 = uv3;
            mesh.uv4 = uv4;
            mesh.colors = color;
            mesh.triangles = triangle;
            mesh.normals = normal;

            mesh.MarkDynamic();
            mesh.RecalculateNormals();
            mesh.RecalculateBounds();

            return mesh;
        }

        public static T[] AddValue<T>( T[] vert, T[] add )
        {
            List<T> list = vert.ToList();

            for( int i = 0; i < add.Length; i++ )
            {
                list.Add( add[ i ] );
            }
            return list.ToArray();
        }
    }

    public class TextureMeshInfo
    {
        public int width;
        public int height;

        public Vector3[] vertice;
        public Vector2[] uv;
        public Vector2[] uv2;
        public Vector2[] uv3;
        public Vector2[] uv4;
        public Color[] verticeColor;
        public Vector3[] normal;
        public int[] triangle;

        public TextureMeshInfo( Texture2D tex )
        {
            width = tex.width;
            height = tex.height;

            SetVertice();
            SetUV();
            SetVerticeColor();
            SetNormal();
            SetTriangle();
        }

        public TextureMeshInfo( TextureMeshInfo info )
        {
            vertice = info.vertice;
            uv = info.uv;
            uv2 = info.uv2;
            uv3 = info.uv3;
            uv4 = info.uv4;
            verticeColor = info.verticeColor;
            normal = info.normal;
            triangle = info.triangle;
        }

        private void SetVertice()
        {
            float halfWidth = width * 0.5f;
            float halfHeight = height * 0.5f;

            vertice = new Vector3[]
            {
                new Vector3( -halfWidth, -halfHeight, 0.0f ),
                new Vector3( -halfWidth,  halfHeight, 0.0f ),
                new Vector3(  halfWidth,  halfHeight, 0.0f ),
                new Vector3(  halfWidth, -halfHeight, 0.0f ),
            };
        }

        private void SetUV()
        {
            uv = new Vector2[]
            {
                new Vector2( 0.0f, 0.0f ),
                new Vector2( 0.0f, 1.0f ),
                new Vector2( 1.0f, 1.0f ),
                new Vector2( 1.0f, 0.0f ),
            };
            uv2 = new Vector2[] { };
            uv3 = new Vector2[] { };
            uv4 = new Vector2[] { };
        }

        public void SetVerticeColor()
        {
            verticeColor = new Color[] { Color.white, Color.white, Color.white, Color.white, };
        }

        public void SetNormal()
        {
            normal = new Vector3[] { Vector3.back, Vector3.back, Vector3.back, Vector3.back, };
        }

        public void SetTriangle()
        {
            triangle = new int[] { 0, 1, 2, 0, 2, 3, };
        }
    }


    public class EmitObject
    {
        public Vector3 pivotPosition = Vector3.zero;
        public float objectScale = 1.0f;

        public TextureMeshInfo textureMeshInfo = null;


        public List<EmitInfo> emitList = new List<EmitInfo>();

        public System.Action View = null; // Test用に無理やりコールするため

        public EmitObject( Vector3 pivot, TextureMeshInfo meshInfo, System.Action view )
        {
            View = view;
            pivotPosition = pivot;
            textureMeshInfo = new TextureMeshInfo( meshInfo );

            Fire.FIRE_TEST.StartCoroutine( Emitting( Time.realtimeSinceStartup ) );
        }

        public List<TextureMeshInfo> GetEmitMesh()
        {
            List<TextureMeshInfo> meshs = new List<TextureMeshInfo>();
            for( int index = 0; index < emitList.Count; index++ )
            {
                for( int i = 0; i < emitList[ index ].textureMeshInfo.uv4.Length; i++ )
                {
                    emitList[ index ].textureMeshInfo.uv4[ i ] = (new Vector2( emitList[ index ].position.x, emitList[ index ].position.y ) + new Vector2( pivotPosition.x, pivotPosition.y ));
                }
                meshs.Add( emitList[ index ].textureMeshInfo );
            }
            return meshs;
        }

        private IEnumerator Emitting( float startTime )
        {
            while( true )
            {
                while( emitList.Count >= Fire.Emit )
                {
                    yield return null;
                }

                EmitInfo emit = new EmitInfo( textureMeshInfo, Random.Range( -Fire.EmitRange, Fire.EmitRange ), Random.Range( 0.0f, Fire.EmitRot ), Random.Range( 0.1f, 1.0f ) );
                emitList.Add( emit );
                View();

                Fire.FIRE_TEST.StartCoroutine( Release( Time.realtimeSinceStartup, Fire.EmitLifeTime, emit ) );
                yield return null;
            }
        }

        private IEnumerator Release( float startTime, float time, EmitInfo mesh )
        {
            while( Time.realtimeSinceStartup - startTime < time )
                yield return null;

            emitList.Remove( mesh );
        }

        public class EmitInfo
        {
            public TextureMeshInfo textureMeshInfo = null;

            public Vector3 position = Vector3.zero;
            public float rotation = 0.0f;
            public float scale = 0.0f;
            public EmitInfo( TextureMeshInfo meshInfo, float randomEmitRange, float randomRotation, float randomScale )
            {
                textureMeshInfo = new TextureMeshInfo( meshInfo );

                Init( randomEmitRange, randomRotation, randomScale );
            }
            public void Init( float randomEmitRange, float randomRotation, float randomScale )
            {
                float x = ( randomEmitRange * Mathf.Cos( ( Time.realtimeSinceStartup % 360.0f ) * ( 3.14f * 180.0f ) ) ) - ( randomEmitRange * Mathf.Sin( ( Time.realtimeSinceStartup % 360.0f ) * ( 3.14f * 180.0f ) ) );
                float y = ( randomEmitRange * Mathf.Sin( ( Time.realtimeSinceStartup % 360.0f ) * ( 3.14f * 180.0f ) ) ) + ( randomEmitRange * Mathf.Cos( ( Time.realtimeSinceStartup % 360.0f ) * ( 3.14f * 180.0f ) ) );

                position = new Vector3( x, y, 0.0f );
                rotation = randomRotation;
                scale = randomScale;

                for ( int i = 0; i < textureMeshInfo.uv.Length; i++ )
                {
                    textureMeshInfo.uv2 = Fire.AddValue<Vector2>( textureMeshInfo.uv2, new Vector2[] { new Vector2( randomRotation, randomScale ) } );
                    textureMeshInfo.uv3 = Fire.AddValue<Vector2>( textureMeshInfo.uv3, new Vector2[] { new Vector2( Random.Range( 0.1f, Fire.EmitLifeTime - 0.1f ), 0.0f ) } );
                    textureMeshInfo.uv4 = Fire.AddValue<Vector2>( textureMeshInfo.uv4, new Vector2[] { position } );
                }
            }
        }
    }
}
hi.shader
Shader "hiShader"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
        _AlphaRate("Alpha Rate", Range(0.0,1.0)) = 0.0
        _Color1("Color1", Color) = (0.0,0.0,0.0,0.0)
        _Color1MaxRate("Color1 MaxRate", Range(0.0,1.0)) = 0.0
        _Color2("Color2", Color) = (0.0,0.0,0.0,0.0)
        _Color2MaxRate("Color2 MaxRate", Range(0.0,1.0)) = 0.0

        _UpMove("UpMove", Float) = 0.0
    }
    SubShader
    {
        Tags
        {
            "Queue" = "Transparent"
            "IgnoreProjector" = "True"
            "RenderType" = "Transparent"
        }
        LOD 200

        Pass
        {
            Blend One One
            //Blend SrcAlpha OneMinusSrcAlpha

            Cull Back //Back | Front | Off
            ZTest Always
            ZWrite Off
            //Fog { Color (1.0,0,0,1.0) }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //#include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float2 uv2 : TEXCOORD1;
                float2 uv3 : TEXCOORD2;
                float2 uv4 : TEXCOORD3;
            };

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

            sampler2D _MainTex;
            fixed4 _Color1;
            fixed4 _Color2;
            float _AlphaRate;
            float _Color1MaxRate;
            float _Color2MaxRate;

            float _UpMove;

            float rand(fixed2 co) {
                return frac(sin(dot(co, fixed2(12.9898, 78.233))) * 43758.5453);
            }

            float rate( float maxRate, float time )
            {
                maxRate *= time;

                float timeRate = _Time.y % time;

                if (timeRate < maxRate)
                {
                    return clamp(timeRate / maxRate, 0.0, time);
                }
                else
                {
                    return (time - clamp((timeRate - maxRate) / (time - maxRate), 0.0, time));
                }
            }

            v2f vert(appdata v)
            {
                v2f o;
                float rotation = v.uv2.x;
                float scale = v.uv2.y;
                float lifetime = v.uv3.x;


                float rot = radians( rotation );
                if (lifetime > 0.4)
                {
                    float x = (v.vertex.x * cos(rot)) - (v.vertex.y * sin(rot));
                    float y = (v.vertex.x * sin(rot)) + (v.vertex.y * cos(rot));
                    v.vertex.x = x;
                    v.vertex.y = y;
                }
                else
                {
                    float x = (v.vertex.x * cos(-rot)) - (v.vertex.y * sin(-rot));
                    float y = (v.vertex.x * sin(-rot)) + (v.vertex.y * cos(-rot));
                    v.vertex.x = x;
                    v.vertex.y = y;
                }

                v.vertex.x += v.vertex.x * sin( radians( ( _Time.y * 128 % 360.0 ) + rotation ) ) * 0.3;

                v.vertex.x += v.uv4.x;
                v.vertex.y += v.uv4.y;

                v.vertex.y += _UpMove * (_Time.y % lifetime);

                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.uv;
                o.rate = rate( 0.7, lifetime );
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                // グレースケール化 + 黒をアルファ抜き
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed y = col.r*0.3 + col.g*0.6 + col.b * 0.1;

                if( y <= _AlphaRate )
                {
                    col = fixed4( 0, 0, 0, 0 ); // 0.01以下は、αで抜く
                }
                else if( y <= _Color1MaxRate )
                {
                    col = _Color1;
                }
                else if( y <= _Color2MaxRate )
                {
                    col = _Color2;
                }

                col *= i.rate;

                return col;
            }
            ENDCG
        }
    }
}

10
7
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
7