LoginSignup
13
10

More than 5 years have passed since last update.

【Shader】fBm(非整数ブラウン運動)を使ったシェーダお遊び

Last updated at Posted at 2018-06-15

はじめに

本エントリはThe Book of Shaders: Fractal Brownian Motionの内容をUnityで実装して遊んだみたという内容です
The Book of ShadersはGLSLで記述されていますが、Unityでシェーダを使ったお遊びにも非常に有益な内容が多くありますので、シェーダに興味のある人は是非内容をさらってみるといいと思います

fBm(Fractal Brownian Motion)

日本語だと非整数ブラウン運動と言うらしい

このテクニックはプログラムで地形を作り出すのによく用いられています。fBMの自己相似性は、山を形づくる侵食過程の自己相似性に似ているため、山脈の形を再現するのに最適です。 -The Book of Shaders
ふむふむなるほど

自己相似性と長期依存(long range dependence)を特徴とするガウス過程 - Wikipedia
完全に理解した

早速遊んでみる

The Book of ShadersのシェーダをQuadに貼り付けて画面全体に描画した感じです
ウネウネ感がきもちいい
fbm.gif

ソースコード

以前作ったcgincに追加する形でfBm関数を作成しています
インスペクタで設定するCol1~3の色を変えていくことでお好みのカラーリングに調整出来るようにしています。

Noise.cginc
float random(float2 co) {
    return frac(sin(dot(co.xy, float2(12.9898, 78.233))) * 43758.5453123);
}

float random(float3 co) {
    return frac(sin(dot(co.xyz, float3(12.9898, 78.233, 56.787))) * 43758.5453123);
}

float noise(float2 pos) {
    float2 i = floor(pos);
    float2 f = frac(pos);
    float a = random(i);
    float b = random(i + float2(1.0, 0.0));
    float c = random(i + float2(0.0, 1.0));
    float d = random(i + float2(1.0, 1.0));

    float2 u = f * f * (3.0 - 2.0 * f);

    return lerp(a, b, u.x) +
            (c - a) * u.y * (1.0 - u.x) +
            (d - b) * u.x * u.y;
}

float noise(float3 pos) {
    float3 ip = floor(pos);
    float3 fp = smoothstep(0, 1, frac(pos));
    float4 a = float4(
        random(ip + float3(0, 0, 0)),
        random(ip + float3(1, 0, 0)),
        random(ip + float3(0, 1, 0)),
        random(ip + float3(1, 1, 0)));
    float4 b = float4(
        random(ip + float3(0, 0, 1)),
        random(ip + float3(1, 0, 1)),
        random(ip + float3(0, 1, 1)),
        random(ip + float3(1, 1, 1)));
    a = lerp(a, b, fp.z);
    a.xy = lerp(a.xy, a.zw, fp.y);
    return lerp(a.x, a.y, fp.x);
}

//pseudo perlin noise
float perlin(float3 pos) {
    return  (noise(pos) * 32 +
            noise(pos * 2 ) * 16 +
            noise(pos * 4) * 8 +
            noise(pos * 8) * 4 +
            noise(pos * 16) * 2 +
            noise(pos * 32) ) / 63;
}

float perlin(float2 pos) {
    return  (noise(pos) * 32 +
            noise(pos * 2 ) * 16 +
            noise(pos * 4) * 8 +
            noise(pos * 8) * 4 +
            noise(pos * 16) * 2 +
            noise(pos * 32) ) / 63;
}

//fractal brownian motion
#define OCTAVES 5
float fbm(float2 pos) {
    float value = 0.0;
    float amplitude = .3;
    float frequency = 0.;

    for(int i = 0; i < OCTAVES; i++) {
        value += amplitude * noise(pos);
        pos *= 2.;
        amplitude *= .7;
    }
    return value;
}
FBM.shader
Shader "Custom/FBM"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}

        _Col1("col1", Color) = (1, 1, 1, 1)
        _Col2("col2", Color) = (1, 1, 1, 1)
        _Col3("col3", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Noise.cginc"

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

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

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Col1, _Col2, _Col3;

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

            fixed4 frag (v2f i) : SV_Target
            {
                float2 uv = float2(i.screenPos.xy * 5 / i.screenPos.w);
                //float2 uv = i.uv; //テクスチャUVでやるならこっち
                fixed3 col = 0.;

                float2 q = 0.;
                q.x = fbm(uv + 0.00 * _Time.y);
                q.y = fbm(uv + float2(1., 1.));

                float2 r = 0.;
                r.x = fbm(uv + 1. * q + float2(1.7, 9.2) + 0.15 * _Time.y);
                r.y = fbm(uv + 1. * q + float2(8.3, 2.8) + 0.126 * _Time.y);

                float f = fbm(uv + r);

                col = lerp(_Col1,
                           _Col2,
                           clamp((f * f) * 4.0, 0.0, 1.0));

                col = lerp(col,
                             float3(0., 0., 0.164706),
                             clamp(length(q), 0.0, 1.0));

                col = lerp(col,
                             _Col3,
                             clamp(length(r.x), 0.0, 1.0));

                return fixed4((f * f * f + .6 * f * f + .5 * f) * col, 1.);
            }
            ENDCG
        }
    }
}

オマケ(fBmを使った雲っぽい何か)

頂点アニメーションや法線にfBmを使えば雲っぽいモクモクが作れるのでは!と思ったけどイマイチ思うようにいかなかったので供養
頂点アニメーションを使っている関係上、ハイポリのスフィアを使っています
cloud.gif

Cloud.shader
Shader "Custom/Cloud"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
        _Color("Color", Color) = (1, 1, 1, 1)
        _RimColor ("Rim Color", Color) = (1, 1, 1, 1)
        _Amplitude("Amplitude", float) = 1.0
        _RimPower("RimPower", Range(0.1, 10.0)) = 3.0
    }

    SubShader
    {
        Tags{ "RenderType"="Transparent" "Queue"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
        CGPROGRAM
        #pragma surface surf Lambert vertex:vert alpha
        #pragma target 3.0      
        #include "Noise.cginc"

        sampler2D _MainTex;
        fixed4 _Color;
        fixed4 _RimColor;
        float _Amplitude;
        float _RimPower;

        struct Input
        {
            float2 uv_MainTex;
            float3 viewDir;
            float4 screenPos;
        };

        void vert(inout appdata_full v)
        {
            v.vertex.xyz += v.normal * (perlin(v.vertex.xyz + _Time.x) + fbm(v.vertex.xyz + _Time.x)) * _Amplitude;
        }

        void surf(Input IN, inout SurfaceOutput o)
        {
            float2 uv = float2(IN.screenPos.xy / IN.screenPos.w);
            fixed4 col = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            float dotProduct = saturate(dot(normalize(IN.viewDir), o.Normal));
            float rim = pow(1.0 - dotProduct, _RimPower);
            fixed3 rimColor = rim * _RimColor;

            o.Albedo = col.rgb + rimColor;
            o.Normal = fixed3(fbm(IN.uv_MainTex + float2(1.7 - _Time.x, 9.2 + _Time.x) * IN.uv_MainTex + float2(1.7 - _Time.x, 9.2 + _Time.x)),
                fbm(IN.uv_MainTex + float2(1. - _Time.x, 1. - _Time.x) * IN.uv_MainTex + float2(1.7 - _Time.x, 9.2 + _Time.x)),
                fbm(IN.uv_MainTex + float2(8.3 + _Time.x, 2.8 - _Time.x) * IN.uv_MainTex + float2(1.7 - _Time.x, 9.2 + _Time.x)));
            //o.Emission = rimColor;
            o.Alpha = col.a - rim;
        }

        ENDCG
    }
    Fallback "Diffuse"
}
13
10
0

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
13
10