Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

やっと・・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ライトではなにもしないマクロになっている)
マクロ内ではライトの位置やライトの強さなどをテクスチャから読み出し、適切な値として返してくれるようになっています。

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away