SmokeLighting(SixWayLightmap)

環境

Unity2022.2.2f1

概要

UnityがHDRPでVfxGraphに実装した「Six Way Smoke Lit」的なもののテストです

ポイントライトはRenderModeを「Important」に設定しています

QuadのGameObjectを作成して、マテリアルを設定すれば確認できます

テクスチャは参考のUnityのブログ記事から使えるもののリンクがあります

コード

Shader "lit/SmokeSixWayLightmap"
{
    Properties
    {
        _PisitiveTex("PisitiveTex", 2D) = "white" {}
        _NegativeTex("NegativeTex", 2D) = "white" {}
        _TexDivideX("TexDivideX", int) = 1
        _TexDivideY("TexDivideY", int) = 1
        [HDR]_EmissiveColor("EmissiveColor", Color) = (0,1,0,1)
    }

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

#define BILLBOARD

    struct appdata
    {
        float4 vertex : POSITION;
        float2 texcoord : TEXCOORD;
#if !defined(BILLBOARD)
        float3 normal : NORMAL;
        float4 tangent : TANGENT;
#endif
    };

    struct v2f
    {
        float4 pos : SV_POSITION;
        float2 texcoord : TEXCOORD0;
        float3 lightDirTS : TEXCOORD1;
        float3 lightDirTS2 : TEXCOORD2;
        float3 lightColor : TEXCOORD3;
        float3 lightColor2 : TEXCOORD4;
    };

    fixed4 _EmissiveColor;
    sampler2D _PisitiveTex;
    sampler2D _NegativeTex;
    uint _TexDivideX;
    uint _TexDivideY;

    float ComputeLightMap(float3 lightDirTS, float4 colPositiveRTBA, float4 colNegativeLBFE)
    {
        float lr = (lightDirTS.x > 0.0) ? (colPositiveRTBA.x) : (colNegativeLBFE.x);
        float tb = (lightDirTS.y > 0.0) ? (colPositiveRTBA.y) : (colNegativeLBFE.y);
        float fb = (-lightDirTS.z > 0.0) ? (colPositiveRTBA.z) : (colNegativeLBFE.z);
        float lightMap = lr * lightDirTS.x * lightDirTS.x
                       + tb * lightDirTS.y * lightDirTS.y
                       + fb * lightDirTS.z * lightDirTS.z;
        return lightMap;
    }

    // Shade4PointLightsから法線の処理を消したもの
    float3 FourPointLightsColor(
        float4 lightPosX, 
        float4 lightPosY, 
        float4 lightPosZ,
        float3 lightColor0, 
        float3 lightColor1, 
        float3 lightColor2, 
        float3 lightColor3,
        float4 lightAttenSq,
        float3 pos)
    {
        // to light vectors
        float4 toLightX = lightPosX - pos.x;
        float4 toLightY = lightPosY - pos.y;
        float4 toLightZ = lightPosZ - pos.z;

        // squared lengths
        float4 lengthSq = 0;
        lengthSq += toLightX * toLightX;
        lengthSq += toLightY * toLightY;
        lengthSq += toLightZ * toLightZ;

        // don't produce NaNs if some vertex position overlaps with the light
        lengthSq = max(lengthSq, 0.000001);

        // attenuation
        float4 atten = 1.0 / (1.0 + lengthSq * lightAttenSq);
        float4 diff = atten;

        // final color
        float3 col = 0;
        col += lightColor0 * diff.x;
        col += lightColor1 * diff.y;
        col += lightColor2 * diff.z;
        col += lightColor3 * diff.w;

        return col;
    }

    ENDCG

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "RenderType"="Transparent"
            "IgnoreProjector"="True"
            "LightMode" = "ForwardBase"
        }
        Pass
        {
            ZWrite Off
            Cull Back
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap
            #pragma vertex vert
            #pragma fragment frag

            v2f vert(appdata v)
            {
                v2f o;
#if defined(BILLBOARD)
                // Billboard
                float3x3 mtxRot = unity_ObjectToWorld;
                mtxRot._m00_m10_m20 = normalize(mtxRot._m00_m10_m20);
                mtxRot._m01_m11_m21 = normalize(mtxRot._m01_m11_m21);
                mtxRot._m02_m12_m22 = normalize(mtxRot._m02_m12_m22);
                v.vertex.xyz = mul(v.vertex.xyz, mul(UNITY_MATRIX_V, mtxRot));
#endif
                // TextureAnimation
                const uint dx = _TexDivideX;
                const uint dy = _TexDivideY;
                const float tdx = 1.0 / dx;
                const float tdy = 1.0 / dy;
                uint index = (uint)fmod(_Time.y * 30.0, dx * dy);
                uint ix = index % dx;
                uint iy = (dy - 1) - (index / dx);
                o.texcoord = float2(v.texcoord.x, v.texcoord.y) * float2(tdx, tdy);
                o.texcoord.x += ix * tdx;
                o.texcoord.y += iy * tdy;

                o.pos = UnityObjectToClipPos(v.vertex);
                float3 posWS = mul(unity_ObjectToWorld, v.vertex).xyz;
#if defined(BILLBOARD)
                float3 normalWS = -unity_CameraToWorld._m02_m12_m22;
                float3 tangentWS = unity_CameraToWorld._m00_m10_m20;
                float3 bitangentWS = unity_CameraToWorld._m01_m11_m21;
#else
                fixed3 normalWS = UnityObjectToWorldNormal(v.normal);
                fixed3 tangentWS = UnityObjectToWorldDir(v.tangent.xyz);
                fixed3 bitangentWS = cross(normalWS, tangentWS) * v.tangent.w;
#endif
                float3x3 mtxWSToTS = float3x3(tangentWS, bitangentWS, normalWS);

                // DirectionalLight
                float3 lightDirWS = _WorldSpaceLightPos0.xyz;
                o.lightDirTS = mul(mtxWSToTS, lightDirWS);
                o.lightColor = _LightColor0.rgb;
            
                // PointLights
                float3 lightPos = float3(0,0,0);
                uint lightCount = 0;
                [unroll]
                for (uint i = 0; i < 4; i++)
                {
                    lightPos += float3(unity_4LightPosX0[i], unity_4LightPosY0[i], unity_4LightPosZ0[i]);
                    lightCount += (unity_LightColor[i].a > 0) ? 1 : 0;
                }
                lightPos /= lightCount;

                float3 lightDirWS2 = normalize(lightPos - posWS.xyz); 
                o.lightDirTS2 = mul(mtxWSToTS, lightDirWS2);
                o.lightColor2 = FourPointLightsColor(
                  unity_4LightPosX0,
                  unity_4LightPosY0,
                  unity_4LightPosZ0,
                  unity_LightColor[0].rgb,
                  unity_LightColor[1].rgb,
                  unity_LightColor[2].rgb,
                  unity_LightColor[3].rgb,
                  unity_4LightAtten0,
                  posWS);
                return o;
            }

            half4 frag(v2f In) : COLOR
            {
                half4 colPositive = tex2D(_PisitiveTex, In.texcoord);
                half4 colNegative = tex2D(_NegativeTex, In.texcoord);

                // DirectionalLight
                float3 lightDirTS = normalize(In.lightDirTS);
                float lightmap01 = ComputeLightMap(lightDirTS, colPositive, colNegative);
                // PointLights
                float3 lightDirTS2 = normalize(In.lightDirTS2);
                float lightmap02 = ComputeLightMap(lightDirTS2, colPositive, colNegative);

                half3 rgb01 = lightmap01 * In.lightColor;
                half3 rgb02 = lightmap02 * In.lightColor2;
                half3 rgb = rgb01 + rgb02;

                half4 col = half4(rgb, colPositive.a);
                col.rgb += _EmissiveColor.rgb * colNegative.a;
                return col;
            }
            ENDCG
        }
    }
}

参考

Visual Effect Graph の 6 ウェイライティングを使ったリアルな煙のライティング | Unity Blog

GitHub - keijiro/SixWaySmokeTest

6-way lightmap WIP - #20 by Aaron - Real Time VFX

この記事はFrontとBackに近似値を使用してチャンネルを節約したものを解説している?

Smoke Lighting and texture re-usability in Skull & Bones - Real Time VFX

https://github.com/peeweek/Unity-URP-SmokeLighting