フルスクリーンでレイマーチ(RayMarching)

環境

Unity2021.3.4f1

概要

フルスクリーンでRayMarchingをやってみました。

この10個の球体からなるメタボールと影はRayMarchingで描画しています。

黄色の床はUnityのPlaneのGameObjectです。

「3D Objects > Quad」でQuadを作成して、カメラの階層に置き、このシェーダのマテリアルを設定すれば動作すると思います。

ClusterのワールドクラフトでRayMarchingを実装してみました。

cluster.mu

Clusterでは自作スクリプトを動作させることができないのでシェーダで全部やっています。 デスクトップ版でしか試してないのでVRだとまともに見えないかも知れません。

Shader "Custom/Raymarching"
{
    Properties
    {
        _RayStep("Ray Step", Range(16, 128)) = 32
        _RayDistanceMin("Ray Distance Min", Range(0.001, 0.1)) = 0.1
        _SpecularColor("Specular Color", Color) = (1,1,1,1)
        _SpecularExp("SpecularExp", Range(4, 128)) = 64
    }

    CGINCLUDE
    #include "UnityCG.cginc"
    #include "Lighting.cginc"

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

    struct fout
    {
        float4 col : SV_Target;
        float depth : SV_Depth;
    };

    uint _RayStep;
    float _RayDistanceMin;
    float4 _SpecularColor;
    float _SpecularExp;

    float4x4 inverse(float4x4 m)
    {
        float4x4 cofactors = float4x4(
             determinant(float3x3(m._22_23_24, m._32_33_34, m._42_43_44)),
            -determinant(float3x3(m._21_23_24, m._31_33_34, m._41_43_44)),
             determinant(float3x3(m._21_22_24, m._31_32_34, m._41_42_44)),
            -determinant(float3x3(m._21_22_23, m._31_32_33, m._41_42_43)),

            -determinant(float3x3(m._12_13_14, m._32_33_34, m._42_43_44)),
             determinant(float3x3(m._11_13_14, m._31_33_34, m._41_43_44)),
            -determinant(float3x3(m._11_12_14, m._31_32_34, m._41_42_44)),
             determinant(float3x3(m._11_12_13, m._31_32_33, m._41_42_43)),

             determinant(float3x3(m._12_13_14, m._22_23_24, m._42_43_44)),
            -determinant(float3x3(m._11_13_14, m._21_23_24, m._41_43_44)),
             determinant(float3x3(m._11_12_14, m._21_22_24, m._41_42_44)),
            -determinant(float3x3(m._11_12_13, m._21_22_23, m._41_42_43)),

            -determinant(float3x3(m._12_13_14, m._22_23_24, m._32_33_34)),
             determinant(float3x3(m._11_13_14, m._21_23_24, m._31_33_34)),
            -determinant(float3x3(m._11_12_14, m._21_22_24, m._31_32_34)),
             determinant(float3x3(m._11_12_13, m._21_22_23, m._31_32_33))
        );
        return transpose(cofactors) / determinant(m);
    }

    inline bool IsOrtho()
    {
        return UNITY_MATRIX_P._m33 == 1.0;
    }
    inline float3 GetCameraPos()
    {
        float3 cpos = mul(inverse(UNITY_MATRIX_V), float4(0, 0, 0, 1)).xyz;
        return cpos;
    }

    inline float3 GetOrthoCameraForward(float3 posWS)
    {
        float3 viewDirWS = UNITY_MATRIX_V[2].xyz;

        float3 rayDirWS = normalize(posWS - GetCameraPos());
        if (dot(viewDirWS, rayDirWS) < 0)
            viewDirWS *= -1;

        viewDirWS *= _ProjectionParams.x;
        return viewDirWS;
    }

    float3 GetViewDirectionWS(float3 posWS)
    {
        float3 rayDirWS;
        [branch]
        if (IsOrtho())
        {
            rayDirWS = GetOrthoCameraForward(posWS);
        }
        else
        {
            rayDirWS = normalize(posWS - GetCameraPos());
        }
        return rayDirWS;
    }

    inline float GetDepthNear()
    {
#if defined(UNITY_REVERSED_Z)
        float near = 1.0;
#else
        float near = 0.0;
#endif
        return near;
    }

    inline float GetDepthFar()
    {
#if defined(UNITY_REVERSED_Z)
        float far = 0.0;
#else
        float far = 1.0;
#endif
        return far;
    }

    inline float GetProjNear()
    {
        float near = GetDepthNear();
#if defined(SHADER_API_GLCORE) || defined(SHADER_API_OPENGL) || defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)
        near = near * 2.0 - 1.0;
#endif
        return near;
    }

    inline float ComputeDepth(float4 posPS)
    {
        float z = posPS.z / posPS.w;
#if defined(SHADER_API_GLCORE) || defined(SHADER_API_OPENGL) || defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)
        return z * 0.5 + 0.5;
#else 
        return z;
#endif 
    }

    void VertCommon(appdata v, inout float4 o_pos, inout float3 o_posWS, inout float3 o_rayWS, inout float3 o_lightWS, inout float3 o_viewWS)
    {
        o_pos = float4(v.uv * 2.0 - 1.0, GetProjNear(), 1.0);

        float4 posWS = mul(inverse(UNITY_MATRIX_VP), o_pos);
        o_posWS = posWS.xyz / posWS.w;
        o_rayWS = GetViewDirectionWS(o_posWS);
        o_lightWS = UnityWorldSpaceLightDir(posWS);
        o_viewWS = UnityWorldSpaceViewDir(posWS);
    }

    inline float DistSphare(float3 posWS, float3 spherePos)
    {
        float radius = 0.5;
        return length(posWS - spherePos) - radius;
    }

    inline float SmoothUnion(float d1, float d2, float k)
    {
        float h = clamp(0.5 + 0.5 * (d2 - d1) / k, 0.0, 1.0);
        return lerp(d2, d1, h) - k * h * (1.0 - h);
    }

    float Distance(float3 posWS)
    {
        float dist = 1000.0;
        for (uint i = 0; i < 10; i++)
        {
            float d = DistSphare(posWS, float3(0.75 * i, 0.0, sin(_Time.y + 1.0 * i)));
            dist = SmoothUnion(dist, d, 0.25);
        }
        return dist;
    }

    float3 Normal(float3 posWS)
    {
        float d = 0.0001;
        return normalize(float3(
            Distance(posWS + float3(d, 0.0, 0.0)) - Distance(posWS + float3(-d, 0.0, 0.0)),
            Distance(posWS + float3(0.0, d, 0.0)) - Distance(posWS + float3(0.0, -d, 0.0)),
            Distance(posWS + float3(0.0, 0.0, d)) - Distance(posWS + float3(0.0, 0.0, -d))
            ));
    }

    fout FragCommon(float3 i_posWS, float3 i_rayWS, float3 i_lightWS, float3 i_viewWS)
    {
        fout o;
        float3 posWS = i_posWS;
        float3 rayDirWS = normalize(i_rayWS);

        const float minDist = _RayDistanceMin;
        float dist = 1000.0;

        for (uint s = 0; s < _RayStep && dist > minDist; s++)
        {
            dist = Distance(posWS);
            posWS += rayDirWS * dist;
        }
        if (dist < minDist)
        {
#if defined(SHADOWS_DEPTH)
            o.col = 0.0;
#else
            float3 normalWS = Normal(posWS);
            float3 lightWS = normalize(i_lightWS);
            float3 viewWS = normalize(i_viewWS);
            fixed3 diffuse = _LightColor0.rgb * saturate(dot(normalWS, lightWS));
            fixed3 halfDirWS = normalize(lightWS + viewWS);
            fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(saturate(dot(halfDirWS, normalWS)), _SpecularExp);
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
            fixed4 finalColor = fixed4(ambient + diffuse + specular, 1.0);
            o.col = finalColor;
#endif
            float4 posPS = mul(UNITY_MATRIX_VP, float4(posWS, 1.0));
            o.depth = ComputeDepth(posPS);
        }
        else
        {
            o.col = 0.0;
            o.depth = GetDepthFar();
        }
        return o;
    }

    ENDCG

    SubShader
    {
        Tags { "Queue" = "Geometry-1"}
        Pass
        {
            Tags{"LightMode" = "ForwardBase" }
            LOD 100
            Cull Off
            Blend SrcAlpha OneMinusSrcAlpha
            Zwrite On
            ZTest LEqual

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 posWS : TEXCOORD1;
                float3 rayWS : TEXCOORD2;
                float3 lightWS : TEXCOORD3;
                float3 viewWS : TEXCOORD4;
            };

            v2f vert(appdata v)
            {
                v2f o;
                VertCommon(v, o.pos, o.posWS, o.rayWS, o.lightWS, o.viewWS);
                return o;
            }
            fout frag(v2f i)
            {
                fout o;
                o = FragCommon(i.posWS, i.rayWS, i.lightWS, i.viewWS);// , _WorldSpaceCameraPos);
                return o;
            }
            ENDCG
        }

        Pass
        {
            Name "ShadowCaster"
            Tags{"LightMode" = "ShadowCaster"}
            LOD 100
            Cull Off
            Zwrite On
            ZTest LEqual

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

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 posWS : TEXCOORD1;
                float3 rayWS : TEXCOORD2;
                float3 lightWS : TEXCOORD3;
                float3 viewWS : TEXCOORD4;
                //V2F_SHADOW_CASTER;
            };
            v2f vert(appdata v)
            {
                v2f o;
                VertCommon(v, o.pos, o.posWS, o.rayWS, o.lightWS, o.viewWS);
                //TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
                return o;
            }

            fout frag(v2f i)
            {
                fout o;
                o = FragCommon(i.posWS, i.rayWS, i.lightWS, i.viewWS);
                return o;
            }
            ENDCG
        }
    }
}

参考

【Unity】レイマーチング超入門チュートリアル前編。板ポリに球体を描く【ライティングあり】 | ぐるたかログ