コースティクス

環境

Unity2021.2.18f1

概要

テクスチャを使用したコースティクスです。

参考リンクのものを実装しました。ルミナンスマスクは実装していません。

以下のサイトのテクスチャを使用させていただいています。

Water Caustics Effect (Small) | OpenGameArt.org

Shader "Custom/Caustics"
{
    Properties
    {
        _CausticsTexture("Caustics Texture", 2D) = "white" {}
        _CausticsSpeed("CausticsSpeed", float) = 0.05
        _CausticsScale("CausticsScale", float) = 3
        _CausticsStrength("CausticsStrength", float) = 1
        _CausticsSplit("CausticsSplit", float) = 0.002
        _CausticsFadeRadius("CausticsFadeRadius", float) = 0.3
        _CausticsFadeStrength("CausticsFadeStrength", float) = 0.6
    }
    SubShader
    {
        Tags
        { 
            "RenderType" = "Transparent"
            "Queue" = "Transparent"
            //"LightMode" = "ForwardBase"
        }
        Pass
        {
            ZWrite Off
            ZTest Always
            Cull Front
            Lighting Off
            Cull Back
            Blend SrcAlpha One//OneMinusSrcAlpha

            CGPROGRAM
            //#pragma multi_compile_fwdbase
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            //#include "AutoLight.cginc"
            //#include "Lighting.cginc"

            half4 _Color;
            sampler2D _CameraDepthTexture;
            float _CausticsSpeed;
            float _CausticsScale;
            float _CausticsStrength;
            float _CausticsSplit;
            float _CausticsFadeRadius;
            float _CausticsFadeStrength;

            struct VSInput
            {
                float4 vertex : POSITION;
                float3 texcoord : TEXCOORD0;
            };
            struct VSOut
            {
                float4 positionCS :SV_POSITION;
                float4 positionSS : TEXCOORD0;
                float3 rayVS : TEXCOORD1;
                float radius : TEXCOORD3;
            };

            half4x4 _MainLightDirection;
            sampler2D _CausticsTexture;

            VSOut vert(VSInput v)
            {
                VSOut o;
                o.positionCS = UnityObjectToClipPos(v.vertex);
                o.positionSS = ComputeScreenPos(o.positionCS);
                o.rayVS = UnityObjectToViewPos(v.vertex).xyz;
                o.rayVS.z *= -1;
                o.radius = length(UNITY_MATRIX_M._m01_m11_m21) * 0.5;
                return o;
            }

            half2 Panner(half2 uv, half speed, half tiling)
            {
                return (half2(1, 0) * _Time.y * speed) + (uv * tiling);
            }

            half3 SampleCaustics(half2 uv, half split)
            {
                half2 uv1 = uv + half2(split, split);
                half2 uv2 = uv + half2(split, -split);
                half2 uv3 = uv + half2(-split, -split);

                half r = tex2D(_CausticsTexture, uv1).r;
                half g = tex2D(_CausticsTexture, uv2).r;
                half b = tex2D(_CausticsTexture, uv3).r;

                return half3(r, g, b);
            }

            fixed4 frag(VSOut i) : Color
            {
                const float far = _ProjectionParams.z;
                i.rayVS = i.rayVS * (far / i.rayVS.z);
                float depth;
                float3 normalVS;
                float2 positionSS = i.positionSS.xy / i.positionSS.w;
                depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, positionSS);
                depth = Linear01Depth(depth);
                float4 positionVS = float4(i.rayVS * depth, 1);
                float3 positionWS = mul(unity_CameraToWorld, positionVS).xyz;
/*
                half4 color = half4(frac(positionWS.xyz), 1.0);
                #if UNITY_REVERSED_Z
                    if (depth < 0.0001) return half4(0,0,0,1);
                #else
                    if (depth > 0.9999) return half4(0,0,0,1);
                #endif
*/
                // calculate position in object-space coordinates
                float3 positionOS = mul(unity_WorldToObject, float4(positionWS, 1.0)).xyz;
/*
                // create bounding box mask
                float boundingBoxMask = all(step(positionOS, 0.5) * (1 - step(positionOS, -0.5)));
                color.rgb = boundingBoxMask;
*/              
                half2 uv = mul(positionWS, _MainLightDirection).xy;

                half2 uv1 = Panner(uv, 0.75 * _CausticsSpeed, 1 / _CausticsScale);
                half2 uv2 = Panner(uv, 1 * _CausticsSpeed, -1 / _CausticsScale);

                half3 tex1 = SampleCaustics(uv1, _CausticsSplit);
                half3 tex2 = SampleCaustics(uv2, _CausticsSplit);

                half edgeFadeMask = 1 - saturate((distance(positionOS, 0) - _CausticsFadeRadius) / (1 - _CausticsFadeStrength));
                half3 caustics = min(tex1, tex2) * _CausticsStrength * edgeFadeMask;

                return fixed4(caustics, 1);
            }
            ENDCG
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
public class Caustics : MonoBehaviour
{
    private Material _causticsMaterial = null;
    private void OnEnable()
    {
        var renderer = GetComponent<Renderer>();
        _causticsMaterial = renderer.sharedMaterial;
    }
    private void Update()
    {
        var sunMatrix = RenderSettings.sun.transform.localToWorldMatrix;
        _causticsMaterial.SetMatrix("_MainLightDirection", sunMatrix);
    }
}

感想

色収差の部分のテクスチャフェッチは重いので、パラメータの値が決まったら、あらかじめテクスチャのUVにチャンネルごとにオフセットを入れておくと良いと思いました。

参考

Rendering realtime caustics