第1回.(初級)カスタムライティング関数でRampシェーダーを作る
はじめに
今回はライティング関数をカスタマイズして、陰影をクッキリさせるシェーダを作ります。このように陰影の滑らかさを調整できるシェーダをRamp(傾斜)シェーダと呼ぶみたいです。
環境はUnity5.6.0f3です。
本記事で解説される新要素
- カスタムライティング関数
何もしないカスタムライティング
Unity - マニュアル: サーフェイスシェーダーライティングの例を参考に、カスタムライティングを行うシェーダを作成します。
Shader "Custom/Ramp" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Metallic ("Metallic", Range(0,1)) = 0.0 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM // Physically based Standard lighting model, and enable shadows on all light types #pragma surface surf Toon fullforwardshadows // Use shader model 3.0 target, to get nicer looking lighting #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader. // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing. // #pragma instancing_options assumeuniformscaling UNITY_INSTANCING_CBUFFER_START(Props) // put more per-instance properties here UNITY_INSTANCING_CBUFFER_END #include "UnityPBSLighting.cginc" inline half4 LightingToon(SurfaceOutputStandard s, half3 viewDir, UnityGI gi) { return LightingStandard(s, viewDir, gi); } inline void LightingToon_GI(SurfaceOutputStandard s, UnityGIInput data, inout UnityGI gi) { LightingStandard_GI(s, data, gi); } void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }
自動生成されたシェーダから変更した点は
#pragma surface surf Toon fullforwardshadows
StandardをToonに変更しています。これはStandardライティング関数をToonライティング関数に変えます、という意味になります。
更に以下が追加されています。
#include "UnityPBSLighting.cginc" inline half4 LightingToon(SurfaceOutputStandard s, half3 viewDir, UnityGI gi) { return LightingStandard(s, viewDir, gi); } inline void LightingToon_GI(SurfaceOutputStandard s, UnityGIInput data, inout UnityGI gi) { LightingStandard_GI(s, data, gi); }
実際のライティング関数がLightingToonとLightingToon_GIになります。LightingToon_GIはLightProbeやEnlightenからGIを求める関数のようですが、今回は改造しません。
今回は主にLightingToonを改造していきます。
このシェーダはStandardライティング関数と処理内容が同じなので、見た目も通常と全く変わりません。
Step関数でクッキリさせる
さて、LightingToon関数を改造して、陰影をクッキリさせます。
inline half4 LightingToon(SurfaceOutputStandard s, half3 viewDir, UnityGI gi) { float diffuse = saturate(dot(s.Normal, gi.light.dir)); return step(0.3, diffuse); }
diffuseの行でピクセル法線とライト方向の内積(dot)を計算することで、拡散反射項を求めています。
(さらにsaturateで0-1の範囲にクランプし、マイナスの値にならないようにしています)
step関数は、第2引数が第1引数以上のとき1を返し、それ以外のときは0を返す関数です。つまりdiffuseが0.3以上なら白、0.3未満なら黒になるということです。
色とGIを入れる
現在マテリアルの色を全く考慮していないので、Albedoをdiffuseに乗算して色を出します。
また暗部が暗すぎるので、GIからの値も加算してあげます。
inline half4 LightingToon(SurfaceOutputStandard s, half3 viewDir, UnityGI gi) { float diffuse = step(0.3,dot(s.Normal, gi.light.dir)); return float4(s.Albedo * diffuse + s.Albedo * gi.indirect.diffuse, 0); }
また、step関数を使うことでsaturateが不要だということに気づいたので、外してあります。
Rampテクスチャを使う
現在はstep関数で陰影を区切っていますが、これだと2段階の陰影しか作れませんし、段階ごとに色を変えるようなこともできません。
そこで、diffuseの値をUV座標のx値として用いるRampテクスチャを導入して、任意に段階を作れるように改造します。
またGIの明るさを調整できるように、"_GIPower"を追加します。
Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Metallic ("Metallic", Range(0,1)) = 0.0 _RampTex("Ramp (RGB)", 2D) = "white" {} _GIPower("GI Power",Range(0,1)) = 0.1 }
まずプロパティに"_RampTex"を追加します。デフォルトは白テクスチャなので、なにも設定しない場合は常に100%の明るさになります。"_GIPower"も追加しておきます。
sampler2D _MainTex;
sampler2D _RampTex;
float _GIPower;
サンプラー変数、"_GIPower"もちゃんと宣言しておきます。
inline half4 LightingToon(SurfaceOutputStandard s, half3 viewDir, UnityGI gi) { float diffuse = saturate(dot(s.Normal, gi.light.dir)); float3 ramp = tex2D(_RampTex, float2(1-diffuse, 0.5)).rgb; return float4(s.Albedo * ramp + s.Albedo * gi.indirect.diffuse * _GIPower, 0); }
_RampTexからフェッチする際に、(1-diffuse)をUV座標のXとしています。Yは固定の0.5ですが、ここをカスタマイズすればより変化を増やすことができます。
Xが(1-diffuse)なのは、テクスチャの左側が明、右側が暗になるようにしたかったからです。
またRampテクスチャは下記のもので、インポート設定でRepeatからClampに変更してあります。
完成したソースコードはこちらになります
Shader "Custom/Ramp" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Metallic ("Metallic", Range(0,1)) = 0.0 _RampTex("Ramp (RGB)", 2D) = "white" {} _GIPower("GI Power",Range(0,1)) = 0.1 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM // Physically based Standard lighting model, and enable shadows on all light types #pragma surface surf Toon fullforwardshadows // Use shader model 3.0 target, to get nicer looking lighting #pragma target 3.0 sampler2D _MainTex; sampler2D _RampTex; float _GIPower; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader. // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing. // #pragma instancing_options assumeuniformscaling UNITY_INSTANCING_CBUFFER_START(Props) // put more per-instance properties here UNITY_INSTANCING_CBUFFER_END #include "UnityPBSLighting.cginc" inline half4 LightingToon(SurfaceOutputStandard s, half3 viewDir, UnityGI gi) { float diffuse = saturate(dot(s.Normal, gi.light.dir)); float3 ramp = tex2D(_RampTex, float2(1-diffuse, 0.5)).rgb; return float4(s.Albedo * ramp + s.Albedo * gi.indirect.diffuse * _GIPower, 0); } inline void LightingToon_GI(SurfaceOutputStandard s, UnityGIInput data, inout UnityGI gi) { LightingStandard_GI(s, data, gi); } void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }
まとめ
今回はカスタムライティング関数を作って、シェーディング結果を段階的にするRampシェーダーを作成しました。
5.6になってINSTANCING用の自動生成コードが追加されていたり、カスタムライティングもGI関数とセットで作らないとエラーになるなど、少し複雑になっています。