LoginSignup
24
21

More than 5 years have passed since last update.

[Unity] デフォルトのDiffuseライティングをカスタムシェーダで実装する

Last updated at Posted at 2014-11-17

やっと・・Vertex/FragmentシェーダでデフォルトのDiffuseライティングと同じ動きをする状態が分かりました。(こちらの記事を参考にしました)

以前にも似たような記事を書いていますが、こちらは微妙に動きがおかしかったりしたので、改めて書きたいと思います。

[2015.01.29 追記]

実際に実行したサンプルコードをGithubに上げておきました。

コード

まず最初にコードの全容を載せると、以下のようになるようです。
(上記記事からの引用です)

diffuse-lighting
Shader "BetterDiffuse" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,1)
        _MainTex ("Base (RGB) Alpha (A)", 2D) = "white" {}
    }
    SubShader {
        Tags {"Queue" = "Geometry" "RenderType" = "Opaque"}
        Pass {
            Tags {"LightMode" = "ForwardBase"}     
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "AutoLight.cginc"

            struct vertex_input {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord  : TEXCOORD0;
            };

            struct vertex_output {
                float4 pos : SV_POSITION;
                float2 uv  : TEXCOORD0;
                float3 lightDir : TEXCOORD1;
                float3 normal   : TEXCOORD2;
                LIGHTING_COORDS(3, 4)
                float3 vertexLighting : TEXCOORD5;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;
            fixed4 _LightColor0;


            vertex_output vert(vertex_input v) {
                vertex_output o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.texcoord.xy;
                o.lightDir = ObjSpaceLightDir(v.vertex);
                o.normal = v.normal;
                TRANSFER_VERTEX_TO_FRAGMENT(o);

                o.vertexLighting = float3(0.0, 0.0, 0.0);

                #ifdef VERTEXLIGHT_ON

                float3 worldN = mul((float3x3)_Object2World, SCALED_NORMAL);
                float4 worldPos = mul(_Object2World, v.vertex);

                for (int index = 0; index < 4; index++) {    
                   float4 lightPosition = float4(unity_4LightPosX0[index], unity_4LightPosY0[index], unity_4LightPosZ0[index], 1.0);
                   float3 vertexToLightSource = float3(lightPosition - worldPos);        
                   float3 lightDirection = normalize(vertexToLightSource);
                   float squaredDistance = dot(vertexToLightSource, vertexToLightSource);
                   float attenuation = 1.0 / (1.0  + unity_4LightAtten0[index] * squaredDistance);
                   float3 diffuseReflection = attenuation * float3(unity_LightColor[index]) * float3(_Color) * max(0.0, dot(worldN, lightDirection));         
                   o.vertexLighting = o.vertexLighting + diffuseReflection * 2;
                }

                #endif

                return o;
            }

            half4 frag(vertex_output i) : COLOR {
                i.lightDir = normalize(i.lightDir);
                fixed atten = LIGHT_ATTENUATION(i);

                fixed4 tex = tex2D(_MainTex, i.uv);
                tex *= _Color + fixed4(i.vertexLighting, 1.0);

                fixed diff = saturate(dot(i.normal, i.lightDir));

                fixed4 c;
                c.rgb = UNITY_LIGHTMODEL_AMBIENT.rgb * 2 * tex.rgb;
                c.rgb += (tex.rgb * _LightColor0.rgb * diff) * (atten * 2);
                c.a = tex.a + _LightColor0.a * atten;

                return c;
            }

            ENDCG
        }

        Pass {
            Tags {"LightMode" = "ForwardAdd"}    
            Blend One One  
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdadd

            #include "UnityCG.cginc"
            #include "AutoLight.cginc"

            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv  : TEXCOORD0;
                float3 lightDir : TEXCOORD2;
                float3 normal   : TEXCOORD1;
                LIGHTING_COORDS(3, 4)
            };

            v2f vert(appdata_tan v) {
                v2f o;

                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.texcoord.xy;

                o.lightDir = ObjSpaceLightDir(v.vertex);

                o.normal = v.normal;
                TRANSFER_VERTEX_TO_FRAGMENT(o);

                return o;
            }

            sampler2D _MainTex;
            fixed4 _Color;
            fixed4 _LightColor0;

            fixed4 frag(v2f i) : COLOR {
                i.lightDir = normalize(i.lightDir);
                fixed atten = LIGHT_ATTENUATION(i);
                fixed4 tex = tex2D(_MainTex, i.uv);
                tex *= _Color;
                fixed3 normal = i.normal;
                fixed diff = saturate(dot(normal, i.lightDir));

                fixed4 c;
                c.rgb = (tex.rgb * _LightColor0.rgb * diff) * (atten * 2);
                c.a = tex.a;

                return c;
            }

            ENDCG
        }
    }
    Fallback "VertexLit"
}

ポイント

見てもらうと分かりますが、だいぶ最小限なコードになっていると思います。
大事なポイントとしては、Tagsだけではなく、#pragma multi_compile_fwdaddを指定している点です。
(これを消すとおかしなレンダリング結果になります)

また、それ以外の点としてもいくつかのマクロを利用してライティングとシャドウをレンダリングしています。
まだマクロの詳細についてはしっかり把握できていませんが、SpotライトやPointライトの場合にだけ定義されるライトテクスチャがあり、それをlookupして利用するようです。

マクロは同じコードでも適宜、ライトの種類によってエラーが出ないように配慮した設計になっています。
(なので、凝ったことをやらないのであれば、これらのマクロを利用してライトやシャドウの色を取得するのが一番楽だと思います)

ifdefの定義

ちなみに色々いじっていて気づいたんですが、どうやらSpotライトやPointライトはそのライトが届く位置にない場合は処理を行わないようです。(冷静に考えれば負荷軽減のためにも当然)

LIGHT_ATTENUATIONマクロは該当パスがSpotライトなどのときにのみ、意味のある実行がされるようになっています。(Directionalライトではなにもしないマクロになっている)
マクロ内ではライトの位置やライトの強さなどをテクスチャから読み出し、適切な値として返してくれるようになっています。

これを元に色々いじっていけば、望んだライティングなどができそうです。
(最低限のコードがやっと見つかった・・( ;´Д`))

24
21
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
24
21