Posted at

UNITY 古典シェーダ Part2 Ambient~複数ライト

Part1 https://qiita.com/YukiMiyatake/items/f71aa766de336fbc6781


Ambient

https://github.com/YukiMiyatake/UnityLesson/tree/Shader_5

物理ベースだと GI(グローバルイルミネーション)を使う

これは光の回析などを計算したライトモデル

ただし古典シェーダでは一律に明るさをあたえる Ambientライト


設定

設定はメニューの

Window -> Lighting -> Setting

9.png

Environment Lightingで設定します

Sourceを Colorにすると Ambient色も変更可能


シェーダ

シェーダからは UNITY_LIGHTMODEL_AMBIENT として取得できる

基本的にAmbientは 他のカラーに足せば良い

前回の続きで、ディフューズ、スペキュラーに足してみる

             float3 frag(v2f i) : SV_Target

{
float3 L = normalize(_WorldSpaceLightPos0.xyz);
float3 V = normalize(_WorldSpaceCameraPos - i.vertexW.xyz);
float3 N = i.normal;
float3 H = normalize(L + V);

//Ambient
float3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;

// texture albedo
float4 tex = tex2D(_MainTex, i.uv);

// Diffuse(HalfLambert)
float3 NdotL = dot(N, L);
float3 diffuse = (NdotL*0.5 + 0.5) * _LightColor0.rgb;

// Speculer
// float3 specular = pow(max(0.0, dot(reflect(-L, N), V)), _Spec1Power) * _Spec1Color.xyz; // reflection
float3 specular = pow(max(0.0, dot(H, N)), _Spec1Power) * _Spec1Color.xyz; // Half vector

return (ambient + diffuse) * tex + specular;
}

これで、Ambientが反映される


ライトの強さ

今まではライトの角度のみで強さに関しては無視していたので

ライトの強度を反映させる

LIGHT_ATTENUATION を使うために AutoLight.cgincを includeする

float3 lightCol = _LightColor0.rgb * LIGHT_ATTENUATION(i);

float3 diffuse = (NdotL*0.5 + 0.5) * lightCol;

float3 specular = pow(max(0.0, dot(H, N)), _Spec1Power) * _Spec1Color.xyz * lightCol; // Half vector

こんな感じで、光源色と強さをdiffuseやSpecularに乗算すればいい


複数ライト

https://github.com/YukiMiyatake/UnityLesson/tree/Shader_6

UNITYでは複数のライトを扱うことが出来る

Forwardレンダリングモードでは

一番影響の高いディレクショナルライトのみが ForwardBaseで処理され

その他のライトは ForwardAdd で処理される

今の状態では ForwardBaseしか書いていないので、2つ目のライトを与えても

反映されないので

ForwardAddパスを追加する

シェーダーの中身は ForwardBaseと同じでよい

ただし Ambientライトは1回しか加えてはいけないので ForwardAddでは加算しない

ブレンドモードも OneOne などにしなければうまく加算されないので ブレンドモードも気をつけること

BlendModeなし

10.png

ForwardBaseのみ

11.png

ForwardAdd追加(赤いライト追加)

12.png

これで複数ライトの影響を与えられるようになった


Vertexシェーダでのライト計算

https://github.com/YukiMiyatake/UnityLesson/tree/Shader_7

今までは 補間された法線をつかい、フラグメントシェーダでピクセル単位でライト計算をしていた

頂点数にくらべ ピクセル数のほうが圧倒的に多く、可能であれば計算は頂点シェーダにまかせたい

ので、試しに

ForwardBaseは今までどおりの フラグメントシェーダでのライティングを行い

ForwardAddでは 頂点シェーダで頂点ごとライティング計算を行い補間を行い フラグメントシェーダではライト計算を行わない事にした

ただし、スペキュラのような ピーキーなライトでは、頂点による補間ではよい結果が出ないことが想定されるので

あくまでテスト

ForwardAddの構造体

    struct appdata

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

struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD1;

float3 diffuse : TEXCOORD2;
float3 specular : TEXCOORD3;
};

フラグメントシェーダではもうライティング計算をおこなわないので、ワールド座標も法線も不要

頂点シェーダで diffuseとspecularを計算して 補完し送るだけだ

ここでセマンティクスを TEXCOORD2、TEXCOORD3にしているのは あまり理由がない

たしか今のHLSLでは TEXCOORDとCOLORには明確な違いがなくなったと思ってる

ので 多分どちらを使っても良いと思う

ForwardAddシェーダーの中身

    v2f vert(appdata v)

{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
float4 vertexW = mul(unity_ObjectToWorld, v.vertex);

o.uv = TRANSFORM_TEX(v.uv, _MainTex);
float3 normal = UnityObjectToWorldNormal(v.normal);

float3 L = normalize(_WorldSpaceLightPos0.xyz);
float3 V = normalize(_WorldSpaceCameraPos - vertexW.xyz);
float3 N = normal;
float3 H = normalize(L + V);
float3 lightCol = _LightColor0.rgb * LIGHT_ATTENUATION(i);
float3 NdotL = dot(N, L);

o.diffuse = (NdotL*0.5 + 0.5) * lightCol;
o.specular = pow(max(0.0, dot(H, N)), _Spec1Power) * _Spec1Color.xyz * lightCol; // Half vector

return o;
}

float3 frag(v2f i) : SV_Target
{

// texture albedo
float4 tex = tex2D(_MainTex, i.uv);

return i.diffuse * tex + i.specular;
}

今まで行っていたライト計算を 頂点シェーダにもってきただけだ

それにより フラグメントシェーダが 非常にコンパクトになり

そうとう速度が速くなったと思われる

肝心のレンダリング結果

通常

13.png

軽量

14.png

予想以上に変化がなかった

もちろん モデルのポリゴン数やライトの状況によっては 全く使えない事もあるし

そもそも 法線マッピングは メインライト以外影響しなくなるので

限定的ではあるが 覚えておいて損はないかと。

私はパフォーマンスチューニングで結構使います