Unityで、PBRなライティング環境をセットアップしてみよう


こんにちは。セガ・インタラクティブ技術統括室の大森です。
TAとして、アーケードタイトルにUnityを採用する際の描画設計を お手伝いしています。


Unity5以降、Unityの標準シェーダーにはPBRワークフローが採用されています。アート素材の量産性が高いことから すっかり普及したPBRワークフローですが、その恩恵を受ける為にはフォトリアル系の知識が欠かせません。

ところで、Unityプロジェクトの初期設定は、旧来シェーダーでの絵作りとも互換性を保つように設定されていて、そのままPBRワークフローを始めると混乱を招く部分があります。社内では この混乱を避ける為、プロジェクト立ち上げ時に、カラースペースやライティング単位を設定しておく事を勧めています。


この記事では、ライティング環境のセットアップに使えるシェーダーやスクリプトを共有しつつ、具体的なライティング設定の一例を紹介します。ぜひハンズオンで、自作のモデルやステージデータを使って、試しながら読んで頂けたらと思います。


f:id:katsuhiko_omori:20190402225501j:plain
このスクリーンショットで、ターゲットとした屋外ライティング環境は以下のようになります。

太陽に直交した面に 入ってくる光の量(照度) 太陽から 85,000 青空(半球)から 20,000 合計    105,000 [lux]
太陽の傾斜角が66.6度。水平地面に 入ってくる光の量(照度) 太陽から 78,000 青空(半球)から 20,000 合計    98,000 [lux]
この環境に照らされたグレー18.42%地面の明るさ(輝度) 青空から=日陰   1,173 合計=日向    5,747 [nt]
青空の明るさ(輝度) 平均     6,366 [nt]
白い雲    10,000 [nt]
水平線    8,000 [nt]
青い空    4,000 [nt]
天頂部    1,500 [nt]
カメラの露出補正(EV100準拠の絶対補正値) 15 [EV]

…これが常に正しい値!という訳ではなく、現実にありうる値の一例として。
この環境をセットアップしていきます。



Unityプロジェクトを作成する

今回のハンズオンは、Unity2018.3以降に対応しています。*1
標準の フォワードレンダリング+HDRカメラ設定を、そのまま使用して進めます。
ポスプロにはPost Processing V2を、シェーダー内部のライブラリとしてCore RP Libraryも利用します。

まずは、新しいプロジェクトを作り、必要なパッケージとスクリプトをインストールしましょう。

必要なパッケージをインストールする

  1. Unityを起動し、プロジェクトを新規作成。Templateは3Dにする
    f:id:sgtech:20190422122620g:plain
  2. PostProcessingとRender-Pipelines.Coreのパッケージをインストールする
    • パッケージマネージャ ウィンドウを開く Window > Package Manager
      f:id:sgtech:20190422122817g:plain
    • Advanced > Show Preview Packages をオンにして プレビューパッケージを表示する
      f:id:sgtech:20190422122815g:plain
    • PostProcessingをインストールする
      f:id:sgtech:20190422122813g:plain
    • Render-Pipelines.Coreをインストールする
      f:id:sgtech:20190422122811g:plain


SEGA TECH Blogからスクリプトをインポートする

…すみません、このBlog、スクリプトファイルを直接添付することが 出来ません。
お手数おかけしますが、以下3つのスクリプトを テキストファイルに コピペ, 保存し、
Unityプロジェクトにインポートしてご利用ください。

  1. それぞれの、▼ファイル名 をクリックして、コード内容を表示
  2. 表示されたコードをダブルクリックすると全文選択されるので、右クリック>コピーする
  3. メモ帳に コードをペーストし、それぞれ指定のファイル名で、Unityプロジェクトの Assetsフォルダ内へ保存する

litColorSpace.cs

using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Rendering;

namespace SegaTechBlog
{
    public class litColorSpace : MonoBehaviour
    {
#if UNITY_EDITOR
        [MenuItem("SegaTechBlog/LightsIntensity/Linear")]
        private static void luliTrue()
        {
            GraphicsSettings.lightsUseLinearIntensity = true;
            EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo();
            var scn = EditorSceneManager.GetSceneManagerSetup();
            EditorSceneManager.OpenScene(scn[0].path);
        }

        [MenuItem("SegaTechBlog/LightsIntensity/Gamma")]
        private static void luliFalse()
        {
            GraphicsSettings.lightsUseLinearIntensity = false;
            EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo();
            var scn = EditorSceneManager.GetSceneManagerSetup();
            EditorSceneManager.OpenScene(scn[0].path);
        }
#endif
    }
}

litChkLib.hlsl

#ifndef SEGATB_CHS_INCLUDED
#define SEGATB_CHS_INCLUDED
// ------------------------------------------------------------------------------------
// SEGATB _ COMMON FOR ALL PASS
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
CBUFFER_START(UnityPerCamera)
	float4 _Time;	float3 _WorldSpaceCameraPos;	float4 _ProjectionParams;	float4 _ScreenParams;	float4 _ZBufferParams;	float4 unity_OrthoParams;
CBUFFER_END
CBUFFER_START(UnityPerCameraRare)
	float4x4 unity_CameraToWorld;
CBUFFER_END
CBUFFER_START(UnityLighting)
	float4 _WorldSpaceLightPos0;
	float4 unity_4LightPosX0;	float4 unity_4LightPosY0;	float4 unity_4LightPosZ0;	half4 unity_4LightAtten0;	half4 unity_LightColor[8];
	half4 unity_DynamicLightmap_HDR;
CBUFFER_END
CBUFFER_START(UnityShadows)
	float4 unity_LightShadowBias;
CBUFFER_END
CBUFFER_START(UnityPerDraw)
	float4x4 unity_ObjectToWorld;	float4x4 unity_WorldToObject;	float4 unity_LODFade;	float4 unity_WorldTransformParams;
	real4 unity_SpecCube0_HDR;
	float4 unity_LightmapST;	float4 unity_DynamicLightmapST;
	real4 unity_SHAr;	real4 unity_SHAg;	real4 unity_SHAb;	real4 unity_SHBr;	real4 unity_SHBg;	real4 unity_SHBb;	real4 unity_SHC;
CBUFFER_END
CBUFFER_START(UnityPerFrame)
	float4x4 glstate_matrix_projection;	float4x4 unity_MatrixV;	float4x4 unity_MatrixInvV;	float4x4 unity_MatrixVP;
CBUFFER_END
CBUFFER_START(UnityReflectionProbes)
	float4 unity_SpecCube0_BoxMax;	float4 unity_SpecCube0_BoxMin;	float4 unity_SpecCube0_ProbePosition;
CBUFFER_END
#define UNITY_MATRIX_M     unity_ObjectToWorld
#define UNITY_MATRIX_I_M   unity_WorldToObject
#define UNITY_MATRIX_V     unity_MatrixV
#define UNITY_MATRIX_I_V   unity_MatrixInvV
#define UNITY_MATRIX_P     OptimizeProjectionMatrix(glstate_matrix_projection)
#define UNITY_MATRIX_VP    unity_MatrixVP
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl"
 
float4x4 OptimizeProjectionMatrix(float4x4 M)
{
    M._21_41 = 0;
    M._12_42 = 0;
    return M;
}

float3 CheckColorValue(float3 color, float targetValue, float targetScale, float range)
{
    targetValue *= targetScale;
    float lum = dot(color, float3(0.2126729, 0.7151522, 0.072175));
    float3 outColor;
    outColor.g = saturate(max(range - abs(lum - targetValue), 0.0) * 10000) * 1.2; // just in range
    outColor.r = saturate(max(lum - targetValue + range, 0.0) * 10000) - outColor.g * 0.5; // over    range
    outColor.b = saturate(max(targetValue - lum + range, 0.0) * 10000) - outColor.g * 0.5; // under   range

    float rhythm = sin(lum / targetScale * 10.0 + _Time.w) * 0.35;
    outColor.g += 0.123;
    return outColor * (0.65 + rhythm);
}

// ------------------------------------------------------------------------------------
//
#ifdef SEGATB_FORWARD

#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonLighting.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/BSDF.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/EntityLighting.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/ImageBasedLighting.hlsl"

float _ChkTargetValue, _ChkTargetScale, _ChkRange;
half4 _LightColor0;

UNITY_INSTANCING_BUFFER_START(PerInstance)
	UNITY_DEFINE_INSTANCED_PROP(float4, _AlbedoColor)
	UNITY_DEFINE_INSTANCED_PROP(float, _Metallic)
	UNITY_DEFINE_INSTANCED_PROP(float, _Anisotropy)
	UNITY_DEFINE_INSTANCED_PROP(float, _Smoothness)
	UNITY_DEFINE_INSTANCED_PROP(float, _EmitIntensity)
UNITY_INSTANCING_BUFFER_END(PerInstance)

TEXTURE2D_SHADOW(_ShadowMapTexture);	SAMPLER(sampler_ShadowMapTexture);
TEXTURECUBE(unity_SpecCube0);			SAMPLER(samplerunity_SpecCube0);
TEXTURE2D(unity_Lightmap);				SAMPLER(samplerunity_Lightmap);
TEXTURE2D(unity_LightmapInd);
TEXTURE2D(unity_DynamicLightmap);		SAMPLER(samplerunity_DynamicLightmap);
TEXTURE2D(unity_DynamicDirectionality);
TEXTURE2D(_MainTex);					SAMPLER(sampler_MainTex);
TEXTURE2D(_MetallicGlossMap);			SAMPLER(sampler_MetallicGlossMap);
TEXTURE2D(_NormalMap);					SAMPLER(sampler_NormalMap);

// ------------------------------------------------------------------
struct VertexInput
{
    float4 posOS	 : POSITION;
    float3 normalOS  : NORMAL;
    float4 tangentOS : TANGENT;
    float4 uv0		 : TEXCOORD0;
    float2 uvLM		 : TEXCOORD1;
    float2 uvDLM	 : TEXCOORD2;
    float4 vColor	 : COLOR;
	UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct VertexOutput
{
    float4 posCS					 : SV_POSITION;
    float4 uv						 : TEXCOORD0;
    float4 tangentToWorldAndPosWS[3] : TEXCOORD1;
    float3 viewDirWS				 : TEXCOORD4;
    float4 posNDC					 : TEXCOORD5;
    float4 ambientOrLightmapUV		 : TEXCOORD6;
	UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct GeometrySTB
{
    float3 posWS;
    float3 verNormalWS;
    float3 normalWS;
    float3 tangentWS;
    float3 binormalWS;
};
struct CameraSTB
{
    float3 posWS;
    float3 dirWS;
    float  distanceWS;
    float2 pixelPosSCS;
};
struct LightSTB
{
    float3 dirWS;
    float3 color;
    float  atten;
};
struct SubLightsGeometrySTB
{
    float3 lightVectorWS[4];
    float  distanceSqr[4];
    float  lightAtten[4];
};
struct MaterialSTB
{
    float3 albedoColor;
    float3 reflectColor;
    float  grazingTerm;
    float  alpha;
    float  perceptualRoughness;
    float2 anisoRoughness;
    float  surfaceReduction;
    float  microOcclusion;
    float3 emitColor;
    float3 testValue;
    float  reflectOneForTest;
};
struct LitParamPerViewSTB
{
    float  specOcclusion;
    float  NdotV;
    float  envRefl_fv;
    float3 reflViewWS;
    float  partLambdaV;
};
struct LitParamPerLightSTB
{
    float3 specularColor;
    float3 diffuseColor;
    float3 testValue;
};
struct LitParamPerEnvironmentSTB
{
    float3 reflectColor;
    float3 diffuseColor;
    float3 testValue;
};

float4 GetPosNDC(float4 posCS)
{
	float4 posNDC;
	float4 ndc = posCS * 0.5f;
	posNDC.xy  = float2(ndc.x, ndc.y * _ProjectionParams.x) + ndc.w;
	posNDC.zw  = posCS.zw;
	return posNDC;
}

float F_Pow5(float u)
{
	float x = 1.0 - u;
	float x2 = x * x;
	float x5 = x * x2 * x2;
	return x5;
}

float3 BoxProjectedCubemapDirection(float3 reflViewWS, float3 posWS, float4 cubemapCenter, float4 boxMin, float4 boxMax)
{
    UNITY_BRANCH if (cubemapCenter.w > 0.0)
    {
        float3 nrdir = normalize(reflViewWS);
		float3 rbmax    = (boxMax.xyz - posWS) / nrdir;
		float3 rbmin    = (boxMin.xyz - posWS) / nrdir;
		float3 rbminmax = (nrdir > 0.0f) ? rbmax : rbmin;
        float  fa       = min(min(rbminmax.x, rbminmax.y), rbminmax.z);
        posWS     -= cubemapCenter.xyz;
        reflViewWS = posWS + nrdir * fa;
    }
    return reflViewWS;
}

// ------------------------------------------------------------------
GeometrySTB GetGeometry(VertexOutput input, float2 uv)
{
    GeometrySTB output;
    output.posWS = float3(input.tangentToWorldAndPosWS[0].w, input.tangentToWorldAndPosWS[1].w, input.tangentToWorldAndPosWS[2].w);	
    float3 verTangentWS  = input.tangentToWorldAndPosWS[0].xyz;
    float3 verBinormalWS = input.tangentToWorldAndPosWS[1].xyz;
    output.verNormalWS   = normalize(input.tangentToWorldAndPosWS[2].xyz);

#ifdef _NORMALMAP
    half4  normalMap  = SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, uv);
    float3 normalMapTS;
    normalMapTS.xy    = normalMap.wy *2.0 - 1.0;
    normalMapTS.z     = sqrt(1.0 - saturate(dot(normalMapTS.xy, normalMapTS.xy)));
    output.normalWS   = normalize(verTangentWS * normalMapTS.x + verBinormalWS * normalMapTS.y + output.verNormalWS * normalMapTS.z);
    output.tangentWS  = normalize(verTangentWS - dot(verTangentWS, output.normalWS) * output.normalWS);
    float3 newBB      = cross(output.normalWS, output.tangentWS);
    output.binormalWS = newBB * FastSign(dot(newBB, verBinormalWS));
#else
    output.normalWS   = output.verNormalWS;
    output.tangentWS  = normalize(verTangentWS);
    output.binormalWS = normalize(verBinormalWS);
#endif
    return output;
}

CameraSTB GetCamera(VertexOutput input, GeometrySTB geo)
{
    CameraSTB output;
    output.posWS       = _WorldSpaceCameraPos;
    output.dirWS       = normalize(input.viewDirWS);
    output.distanceWS  = LinearEyeDepth(geo.posWS, UNITY_MATRIX_V);
    output.pixelPosSCS = input.posNDC.xy / input.posNDC.w;
    return output;
}

LightSTB GetMainLight(CameraSTB cam)
{
    LightSTB output;
#if defined (DIRECTIONAL) || defined (DIRECTIONAL_COOKIE)
 #if defined(_NOPIDIV) && !defined(_VSUN_LIGHT_COLOR) && !defined(_VPOINT_LIGHT_COLOR)
    output.color = _LightColor0.rgb *PI;
 #else
    output.color = _LightColor0.rgb;
 #endif
    half atten = 1.0;
 #if defined(SHADOWS_SCREEN)
	atten = SAMPLE_TEXTURE2D(_ShadowMapTexture, sampler_ShadowMapTexture, cam.pixelPosSCS).x;
 #endif
	output.atten = atten;
    output.dirWS = _WorldSpaceLightPos0.xyz;
#else
	output.color = 0;
	output.atten = 0;
	output.dirWS = float3(0,0,1);
#endif
	return output;
}

SubLightsGeometrySTB GetSubLightsGeometry(GeometrySTB geo)
{
    SubLightsGeometrySTB output;
    float4 toLightX = unity_4LightPosX0 - geo.posWS.x;
    float4 toLightY = unity_4LightPosY0 - geo.posWS.y;
    float4 toLightZ = unity_4LightPosZ0 - geo.posWS.z;
    float4 distanceSqr = 0.0;
    distanceSqr += toLightX * toLightX;
    distanceSqr += toLightY * toLightY;
    distanceSqr += toLightZ * toLightZ;
    output.lightVectorWS[0] = float3(toLightX.x, toLightY.x, toLightZ.x);
    output.lightVectorWS[1] = float3(toLightX.y, toLightY.y, toLightZ.y);
    output.lightVectorWS[2] = float3(toLightX.z, toLightY.z, toLightZ.z);
    output.lightVectorWS[3] = float3(toLightX.w, toLightY.w, toLightZ.w);
    output.distanceSqr[0] = distanceSqr.x;
    output.distanceSqr[1] = distanceSqr.y;
    output.distanceSqr[2] = distanceSqr.z;
    output.distanceSqr[3] = distanceSqr.w;
    output.lightAtten[0] = unity_4LightAtten0.x;
    output.lightAtten[1] = unity_4LightAtten0.y;
    output.lightAtten[2] = unity_4LightAtten0.z;
    output.lightAtten[3] = unity_4LightAtten0.w;
    return output;
}

LightSTB GetSubLight(uint index, SubLightsGeometrySTB subLightsGeo)
{
    LightSTB output;
#if defined(_NOPIDIV) && !defined(_VSUN_LIGHT_COLOR) && !defined(_VPOINT_LIGHT_COLOR)
    output.color = unity_LightColor[index].xyz * PI;
#else
    output.color = unity_LightColor[index].xyz;
#endif

    UNITY_BRANCH if ((output.color.r + output.color.g + output.color.b) != 0.0)
    {
        float distanceSqr = max(subLightsGeo.distanceSqr[index], (PUNCTUAL_LIGHT_THRESHOLD * PUNCTUAL_LIGHT_THRESHOLD));
#if defined(_NOPIDIV)
		output.atten = 1.0 / (1.0 + distanceSqr * subLightsGeo.lightAtten[index]);
#else
        float invDistanceSqr   = 1.0 / distanceSqr;
        float lightAttenFactor = distanceSqr * subLightsGeo.lightAtten[index] * 0.04;
		lightAttenFactor      *= lightAttenFactor;
		lightAttenFactor       = saturate(1.0 - lightAttenFactor);
		lightAttenFactor      *= lightAttenFactor;
        output.atten = max(invDistanceSqr * lightAttenFactor, 0.0);
#endif
        output.dirWS = SafeNormalize(subLightsGeo.lightVectorWS[index]);
    }
    else
    {
        output.atten = 0.0;
        output.dirWS = float3(0,0,1);
    }
    return output;
}

MaterialSTB GetMaterial(float2 uv)
{
    MaterialSTB output;
    half4 colParams = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);
    half4 matParams = SAMPLE_TEXTURE2D(_MetallicGlossMap, sampler_MetallicGlossMap, uv);
    float4 matColor   = UNITY_ACCESS_INSTANCED_PROP(PerInstance, _AlbedoColor);
    float  metallic   = UNITY_ACCESS_INSTANCED_PROP(PerInstance, _Metallic);
    float  anisotropy = UNITY_ACCESS_INSTANCED_PROP(PerInstance, _Anisotropy);
    float  smoothness = UNITY_ACCESS_INSTANCED_PROP(PerInstance, _Smoothness);
    float  emmision   = UNITY_ACCESS_INSTANCED_PROP(PerInstance, _EmitIntensity);
    float  occlusion  = 1.0;
#ifdef _COLMAP
    matColor   *= colParams;
#endif
#ifdef _METMAP
    metallic   *= matParams.x;
#endif
#ifdef _OCCMAP
    occlusion  *= matParams.y;
#endif
#ifdef _SMTMAP
    smoothness *= matParams.w;
#endif

    float oneMinusReflectivity = (1.0 - metallic) * 0.96;
    output.albedoColor  = matColor.rgb * oneMinusReflectivity;
    output.reflectColor = lerp(half3(0.04, 0.04, 0.04), matColor.rgb, metallic);
    output.grazingTerm  = saturate(smoothness + (1.0 - oneMinusReflectivity));
    output.alpha        = matColor.a;
    output.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(smoothness);
    ConvertAnisotropyToRoughness(output.perceptualRoughness, anisotropy, output.anisoRoughness.x, output.anisoRoughness.y);
    output.anisoRoughness.x    = max(output.anisoRoughness.x, 0.0005);
    output.anisoRoughness.y    = max(output.anisoRoughness.y, 0.0005);
    output.surfaceReduction    = 1.0 / (output.perceptualRoughness * output.perceptualRoughness + 1.0);
    output.microOcclusion = occlusion;
    output.emitColor      = matColor.rgb * emmision;

#if defined(_VMAT_COLOR)
    output.testValue = matColor.rgb;
#elif defined(_VMAT_DIFFUSE_COLOR)
	output.testValue = output.albedoColor;
#elif defined(_VMAT_METALLIC)
	output.testValue = metallic;
#elif defined(_VMAT_SMOOTHNESS)
	output.testValue = smoothness;
#elif defined(_VMAT_OCCLUSION)
	output.testValue = occlusion;
#else
    output.testValue = 0;
#endif
    output.reflectOneForTest = lerp(0.04, 1.0, metallic);
    return output;
}

LitParamPerViewSTB GetLitParamPerView(GeometrySTB geo, CameraSTB cam, MaterialSTB mat)
{
    LitParamPerViewSTB output;
    output.specOcclusion = GetHorizonOcclusion(cam.dirWS, geo.normalWS, geo.verNormalWS, 0.8);
    output.NdotV = ClampNdotV(dot(geo.normalWS, cam.dirWS));
    output.envRefl_fv = F_Pow5(saturate(output.NdotV));
    output.reflViewWS = reflect(-cam.dirWS, geo.normalWS);
    float TdotV        = dot(geo.tangentWS,  cam.dirWS);
    float BdotV        = dot(geo.binormalWS, cam.dirWS);
    output.partLambdaV = GetSmithJointGGXAnisoPartLambdaV(TdotV, BdotV, output.NdotV, mat.anisoRoughness.x, mat.anisoRoughness.y);
    return output;
}

LitParamPerLightSTB GetLitByTheLight(GeometrySTB geo, CameraSTB cam, MaterialSTB mat, LitParamPerViewSTB lip, LightSTB theLight)
{
    LitParamPerLightSTB output;
    float NdotL = dot(geo.normalWS, theLight.dirWS);
#if defined(_VSUN__) && defined(_VPOINT__)
    UNITY_BRANCH if (NdotL > 0.0)
    {
#endif
        float3 halfDir = SafeNormalize(theLight.dirWS + cam.dirWS);
        float LdotV = dot(theLight.dirWS, cam.dirWS);
        float NdotH = dot(geo.normalWS,   halfDir);
        float LdotH = dot(theLight.dirWS, halfDir);
        float TdotL = dot(geo.tangentWS,  theLight.dirWS);
        float BdotL = dot(geo.binormalWS, theLight.dirWS);
        float TdotH = dot(geo.tangentWS,  halfDir);
        float BdotH = dot(geo.binormalWS, halfDir);
        float spec_fv = F_Pow5(saturate(LdotH));
		float  occlusion    = ComputeMicroShadowing(mat.microOcclusion * 1.6 +0.2, NdotL, 1.0);
		float3 occlusionCol = GTAOMultiBounce(occlusion, mat.albedoColor);

        float  specTermD     = D_GGXAniso(TdotH, BdotH, NdotH, mat.anisoRoughness.x, mat.anisoRoughness.y);
        float  specTermG     = V_SmithJointGGXAniso(0, 0, lip.NdotV, TdotL, BdotL, NdotL, mat.anisoRoughness.x, mat.anisoRoughness.y, lip.partLambdaV);
        float3 specTermF     = mat.reflectColor + (1 - mat.reflectColor) * spec_fv;
		output.specularColor = (specTermD * specTermG * saturate(NdotL) * theLight.atten * occlusion * lip.specOcclusion) * specTermF * theLight.color;

        float  diffuseTerm   = DisneyDiffuse(lip.NdotV, NdotL, LdotV, mat.perceptualRoughness);
        output.diffuseColor  = (diffuseTerm * saturate(NdotL) * theLight.atten * occlusionCol) * theLight.color;

#if defined(_VSUN_LIGHT_COLOR) || defined(_VPOINT_LIGHT_COLOR)
		output.testValue = theLight.color;
#elif defined(_VSUN_LIGHT_ILLUMINANCE) || defined(_VPOINT_LIGHT_ILLUMINANCE) || defined(_VGET_TOTAL_ILLUMINANCE)
		output.testValue = theLight.color *saturate(NdotL) * theLight.atten * occlusion;
#elif defined(_VSUN_SHADE_LAMBERT) || defined(_VPOINT_SHADE_LAMBERT)
		output.testValue = theLight.color *saturate(NdotL) * theLight.atten * occlusion *INV_PI;
#elif defined(_VSUN_SHADE_SPECULAR) || defined(_VPOINT_SHADE_SPECULAR) || defined(_VGET_TOTAL_REFLECTION)
		output.testValue = (specTermD * specTermG * saturate(NdotL) * theLight.atten * occlusion * lip.specOcclusion) * (mat.reflectOneForTest + (1 - mat.reflectOneForTest) * spec_fv) * theLight.color;
#elif defined(_VSUN_SHADE_SPEC_DGF) || defined(_VPOINT_SHADE_SPEC_DGF)
		output.testValue.r = specTermD;
		output.testValue.g = specTermG;
		output.testValue.b = specTermF;
#elif defined(_VSUN_SHADE_SPEC_D) || defined(_VPOINT_SHADE_SPEC_D)
		output.testValue = specTermD;
#elif defined(_VSUN_SHADE_SPEC_G) || defined(_VPOINT_SHADE_SPEC_G)
		output.testValue = specTermG;
#elif defined(_VSUN_SHADE_SPEC_F) || defined(_VPOINT_SHADE_SPEC_F)
		output.testValue = mat.reflectOneForTest + (1 - mat.reflectOneForTest) * spec_fv;
#else
        output.testValue = 0;
#endif
#if defined(_VSUN__) && defined(_VPOINT__)
    }
    else
    {
        output.specularColor = 0.0;
		output.diffuseColor  = 0.0;
        output.testValue = 0;
    }
#endif
	return output;
}

LitParamPerEnvironmentSTB GetLitByEnvironment(VertexOutput input, GeometrySTB geo, MaterialSTB mat, LitParamPerViewSTB lip)
{
    LitParamPerEnvironmentSTB output;
	float  occlusion    = ComputeMicroShadowing(mat.microOcclusion * 0.8 +0.3, lip.NdotV, 1.0);
	float3 occlusionCol = GTAOMultiBounce( saturate(mat.microOcclusion *1.2), mat.albedoColor);

#if defined(LIGHTPROBE_SH)
    output.diffuseColor      = max( SHEvalLinearL0L1(geo.normalWS, unity_SHAr, unity_SHAg, unity_SHAb)+ input.ambientOrLightmapUV.rgb, 0.0);
#elif defined(DIRLIGHTMAP_COMBINED)
	half4 decodeInstructions = half4(LIGHTMAP_HDR_MULTIPLIER, LIGHTMAP_HDR_EXPONENT, 0.0h, 0.0h);
	{
		float4 direction          = SAMPLE_TEXTURE2D(unity_LightmapInd, samplerunity_Lightmap, input.ambientOrLightmapUV.xy);
		float4 encodedIlluminance = SAMPLE_TEXTURE2D(unity_Lightmap,    samplerunity_Lightmap, input.ambientOrLightmapUV.xy);
		float3 illuminance        = DecodeLightmap(encodedIlluminance, decodeInstructions);
		float  halfLambert        = dot(geo.normalWS, direction.xyz - 0.5) + 0.5;
		output.diffuseColor       = illuminance * halfLambert / max(1e-4, direction.w);
	}
 #if defined(DYNAMICLIGHTMAP_ON)
	{
		float4 direction          = SAMPLE_TEXTURE2D(unity_DynamicDirectionality, samplerunity_DynamicLightmap, input.ambientOrLightmapUV.zw);
		float4 encodedIlluminance = SAMPLE_TEXTURE2D(unity_DynamicLightmap,		  samplerunity_DynamicLightmap, input.ambientOrLightmapUV.zw);
		float3 illuminance        = DecodeLightmap(encodedIlluminance, decodeInstructions);
		float  halfLambert        = dot(geo.normalWS, direction.xyz - 0.5) + 0.5;
		output.diffuseColor      += illuminance * halfLambert / max(1e-4, direction.w);
	}
 #endif
#else
    output.diffuseColor      = 0.0;
#endif
    output.diffuseColor *= occlusionCol;

#if defined(UNITY_SPECCUBE_BOX_PROJECTION)
    float3 reflViewWS = BoxProjectedCubemapDirection(lip.reflViewWS, geo.posWS, unity_SpecCube0_ProbePosition, unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax);
#else
    float3 reflViewWS = lip.reflViewWS;
#endif
    half  reflMipLevel      = PerceptualRoughnessToMipmapLevel(mat.perceptualRoughness);
    half4 encodedIrradiance = SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, reflViewWS, reflMipLevel);
#if !defined(UNITY_USE_NATIVE_HDR)
    half3 irradiance = DecodeHDREnvironment(encodedIrradiance, unity_SpecCube0_HDR);
#else
    half3 irradiance = encodedIrradiance.rbg;
#endif
    output.reflectColor = mat.microOcclusion * mat.surfaceReduction * irradiance * lerp(mat.reflectColor, mat.grazingTerm, lip.envRefl_fv);

#if defined(_VENV_LIGHT_ILLUMINANCE)
	output.testValue = output.diffuseColor *PI;
#elif defined(_VENV_SHADE_LAMBERT)
	output.testValue = output.diffuseColor;
#elif defined(_VENV_SHADE_REFLECTION)
	output.testValue = mat.microOcclusion * mat.surfaceReduction * irradiance * lerp(1.0, mat.grazingTerm, lip.envRefl_fv);
#elif defined(_VMAT_SPECULAR_COLOR)
	output.testValue = lerp(mat.reflectColor, mat.grazingTerm, lip.envRefl_fv);
#elif defined(_VGET_TOTAL_ILLUMINANCE)
	output.testValue = output.diffuseColor *PI;
#elif defined(_VGET_TOTAL_REFLECTION)
	output.testValue = occlusion * mat.surfaceReduction * irradiance;
#else
    output.testValue = 0;
#endif
    return output;
}

// ------------------------------------------------------------------
VertexOutput ChsForwardVertex( VertexInput input)
{
    VertexOutput output;
    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_TRANSFER_INSTANCE_ID(input, output);

    float4 posWS = mul(UNITY_MATRIX_M, float4(input.posOS.xyz, 1.0));
    output.posCS = mul(UNITY_MATRIX_VP, posWS);

    float3   camPosWS = _WorldSpaceCameraPos;
    output.viewDirWS  = camPosWS - posWS.xyz;

    float3   normalWS   = normalize( mul( (float3x3) UNITY_MATRIX_M, input.normalOS));
    float4   tangentWS  = float4( normalize( mul( (float3x3) UNITY_MATRIX_M, input.tangentOS.xyz)), input.tangentOS.w);
    float    sign       = tangentWS.w * unity_WorldTransformParams.w;
	float3   binormalWS = cross( normalWS, tangentWS.xyz) * sign;

	float4 ndc       = output.posCS * 0.5f;
	output.posNDC.xy = float2(ndc.x, ndc.y * _ProjectionParams.x) + ndc.w;
	output.posNDC.zw = output.posCS.zw;

#ifdef DIRLIGHTMAP_COMBINED
	output.ambientOrLightmapUV.xy  = input.uvLM.xy  * unity_LightmapST.xy        + unity_LightmapST.zw;
 #ifdef DYNAMICLIGHTMAP_ON
	output.ambientOrLightmapUV.zw  = input.uvDLM.xy * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
 #else
	output.ambientOrLightmapUV.zw  = 0;
 #endif
#elif LIGHTPROBE_SH
	output.ambientOrLightmapUV.rgb = SHEvalLinearL2(normalWS, unity_SHBr, unity_SHBg, unity_SHBb, unity_SHC);
	output.ambientOrLightmapUV.w   = 0;
#else
    output.ambientOrLightmapUV     = 0;
#endif

    output.uv.xy = input.uv0.xy;
    output.uv.zw = 0;
    output.tangentToWorldAndPosWS[0].xyz = tangentWS.xyz;
    output.tangentToWorldAndPosWS[1].xyz = binormalWS;
    output.tangentToWorldAndPosWS[2].xyz = normalWS;
    output.tangentToWorldAndPosWS[0].w = posWS.x;
    output.tangentToWorldAndPosWS[1].w = posWS.y;
    output.tangentToWorldAndPosWS[2].w = posWS.z;
    return output;
}

float4 ChsForwardFragment( VertexOutput input ) : SV_Target
{
    UNITY_SETUP_INSTANCE_ID(input);
    float2             uv  = input.uv.xy;
    GeometrySTB        geo = GetGeometry(input, uv);
    CameraSTB          cam = GetCamera(input, geo);	
    MaterialSTB        mat = GetMaterial(uv);
    LitParamPerViewSTB lip = GetLitParamPerView(geo, cam, mat);

    LightSTB            sun    = GetMainLight(cam);
    LitParamPerLightSTB litSun = GetLitByTheLight(geo, cam, mat, lip, sun);

    LitParamPerEnvironmentSTB litEnv = GetLitByEnvironment(input, geo, mat, lip);

	LitParamPerLightSTB litSubLights;
	litSubLights.diffuseColor  = 0.0;
	litSubLights.specularColor = 0.0;
	litSubLights.testValue     = 0.0;
#ifdef LIGHTPROBE_SH
 #ifdef VERTEXLIGHT_ON
	SubLightsGeometrySTB subLightsGeo = GetSubLightsGeometry(geo);
	for (int i = 0; i < 3; i++) {
		LightSTB subLight = GetSubLight(i, subLightsGeo);
        UNITY_BRANCH if (subLight.atten != 0.0)
		{
            LitParamPerLightSTB litSubLight = GetLitByTheLight(geo, cam, mat, lip, subLight);
			litSubLights.diffuseColor  += litSubLight.diffuseColor;
			litSubLights.specularColor += litSubLight.specularColor;
			litSubLights.testValue     += litSubLight.testValue;
		}
	}
 #endif
#endif

    float3 color = ( litSun.diffuseColor + litEnv.diffuseColor + litSubLights.diffuseColor ) * mat.albedoColor + litSun.specularColor + litEnv.reflectColor + litSubLights.specularColor + mat.emitColor;
	float  alpha = mat.alpha;

#if defined(_VMAT_COLOR) || defined(_VMAT_DIFFUSE_COLOR) || defined(_VMAT_METALLIC) || defined(_VMAT_SMOOTHNESS) || defined(_VMAT_OCCLUSION)
	color = mat.testValue;
#elif defined(_VGET_TOTAL_ILLUMINANCE) || defined(_VGET_TOTAL_REFLECTION)
	color = litSun.testValue + litEnv.testValue + litSubLights.testValue;
#elif defined(_VGET_SUN_ONLY)
	color = litSun.diffuseColor * mat.albedoColor + litSun.specularColor;
#elif defined(_VGET_ENV_ONLY)
	color = litEnv.diffuseColor * mat.albedoColor + litEnv.reflectColor;
#elif defined(_VGET_POINTLIGHT_ONLY)
	color = litSubLights.diffuseColor * mat.albedoColor + litSubLights.specularColor;
#elif defined(_VSUN_LIGHT_COLOR) || defined(_VSUN_LIGHT_ILLUMINANCE) || defined(_VSUN_SHADE_LAMBERT) || defined(_VSUN_SHADE_SPECULAR) || defined(_VSUN_SHADE_SPEC_DGF) || defined(_VSUN_SHADE_SPEC_D) || defined(_VSUN_SHADE_SPEC_G) || defined(_VSUN_SHADE_SPEC_F)
	color = litSun.testValue;
#elif defined(_VENV_LIGHT_ILLUMINANCE) || defined(_VENV_SHADE_LAMBERT) || defined(_VENV_SHADE_REFLECTION) || defined(_VMAT_SPECULAR_COLOR)
	color = litEnv.testValue;
#elif defined(_VPOINT_LIGHT_COLOR) || defined(_VPOINT_LIGHT_ILLUMINANCE) || defined(_VPOINT_SHADE_LAMBERT) || defined(_VPOINT_SHADE_SPECULAR) || defined(_VPOINT_SHADE_SPEC_DGF) || defined(_VPOINT_SHADE_SPEC_D) || defined(_VPOINT_SHADE_SPEC_G) || defined(_VPOINT_SHADE_SPEC_F)
	color = litSubLights.testValue;
#endif

#ifdef _CHECKVALUE
	color = CheckColorValue(color, _ChkTargetValue, _ChkTargetScale, _ChkRange);
#endif
	return float4(color, alpha);
}

#endif //SEGATB_FORWARD
// ---------------------------------------------------------------------------
//
#ifdef SEGATB_SHADOWCASTER

struct VertexInput
{
    float4 posOS    : POSITION;
    float3 normalOS : NORMAL;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct VertexOutput
{
    float4 posCS : SV_POSITION;
};

// ------------------------------------------------------------------
VertexOutput DepthOnlyVertex(VertexInput input)
{
    VertexOutput output;
    UNITY_SETUP_INSTANCE_ID(input);

    float4 posWS = mul(UNITY_MATRIX_M, float4(input.posOS.xyz, 1.0));

    if (unity_LightShadowBias.z != 0.0)
    {
        float3 normalWS   = normalize(mul((float3x3) UNITY_MATRIX_M, input.normalOS));
        float3 lightDirWS = normalize(_WorldSpaceLightPos0.xyz - posWS.xyz * _WorldSpaceLightPos0.w);
        float  shadowCos  = dot(normalWS, lightDirWS);
        float  shadowSine = sqrt(1 - shadowCos * shadowCos);
        float  normalBias = unity_LightShadowBias.z * shadowSine;
        posWS.xyz        -= normalWS * normalBias;
    }

    output.posCS = mul(UNITY_MATRIX_VP, posWS);

    if (unity_LightShadowBias.y != 0.0)
    {
#ifdef UNITY_REVERSED_Z
		output.posCS.z += max(-1, min(unity_LightShadowBias.x / output.posCS.w, 0));
		output.posCS.z  = min(output.posCS.z, output.posCS.w * UNITY_NEAR_CLIP_VALUE);
#else
        output.posCS.z += saturate(unity_LightShadowBias.x / output.posCS.w);
        output.posCS.z  = max(output.posCS.z, output.posCS.w * UNITY_NEAR_CLIP_VALUE);
#endif
    }
    return output;
}

half4 DepthOnlyFragment(VertexOutput input) : SV_TARGET
{
    return 0;
}

#endif //SEGATB_SHADOWCASTER
// ---------------------------------------------------------------------------
//
#ifdef SEGATB_META

float4 _AlbedoColor;
float  _Metallic, _EmitIntensity;
float  unity_OneOverOutputBoost;
float  unity_MaxOutputValue;
float  unity_UseLinearSpace;

CBUFFER_START(UnityMetaPass)
	bool4 unity_MetaVertexControl;	 // x = use uv1 as raster position	// y = use uv2 as raster position
	bool4 unity_MetaFragmentControl; // x = return albedo				// y = return normal
CBUFFER_END

TEXTURE2D(_MainTex);            SAMPLER(sampler_MainTex);
TEXTURE2D(_MetallicGlossMap);	SAMPLER(sampler_MetallicGlossMap);

// ------------------------------------------------------------------
struct VertexInput
{
    float4 posOS : POSITION;
    float2 uv0   : TEXCOORD0;
    float2 uvLM  : TEXCOORD1;
    float2 uvDLM : TEXCOORD2;
};
struct VertexOutput
{
    float4 posCS : SV_POSITION;
    float4 uv    : TEXCOORD0;
};
struct MaterialSTB
{
    float3 albedoColor;
    float3 emitColor;
};

// ------------------------------------------------------------------
MaterialSTB GetMaterial(float2 uv)
{
    MaterialSTB output;
    half4 colParams = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);
    half4 matParams = SAMPLE_TEXTURE2D(_MetallicGlossMap, sampler_MetallicGlossMap, uv);
    float4 matColor = _AlbedoColor;
    float metallic = _Metallic;
    float emmision = _EmitIntensity;
#ifdef _COLMAP
    matColor   *= colParams;
#endif
#ifdef _METMAP
    metallic   *= matParams.x;
#endif

#if !defined(EDITOR_VISUALIZATION)
	output.albedoColor = matColor.rgb *( 1.0 - metallic *0.5)  *( 0.5 + matColor.a *0.5) ;
#else
	output.albedoColor = matColor;
#endif

    output.emitColor = matColor.rgb * emmision;
    return output;
}

// ------------------------------------------------------------------
VertexOutput MetaVertex(VertexInput input)
{
    VertexOutput output;

    float3 posTXS = input.posOS.xyz;
    if (unity_MetaVertexControl.x)
    {
        posTXS.xy = input.uvLM * unity_LightmapST.xy + unity_LightmapST.zw;
        posTXS.z  = posTXS.z > 0 ? REAL_MIN : 0.0f;
    }
    if (unity_MetaVertexControl.y)
    {
        posTXS.xy = input.uvDLM * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
        posTXS.z = posTXS.z > 0 ? REAL_MIN : 0.0f;
    }
    output.posCS = mul(UNITY_MATRIX_VP, float4(posTXS, 1.0));

    output.uv.xy = input.uv0.xy;
    output.uv.zw = 0;
    return output;
}

half4 MetaFragment(VertexOutput input) : SV_TARGET
{
    half4 color = 0;
    float2 uv = input.uv.xy;

    MaterialSTB mat = GetMaterial(uv);

    if (unity_MetaFragmentControl.x)
    {
        color = half4(mat.albedoColor, 1.0);    
        unity_OneOverOutputBoost = saturate(unity_OneOverOutputBoost);	// d3d9 shader compiler doesn't like NaNs and infinity.   
        color.rgb = clamp(PositivePow(color.rgb, unity_OneOverOutputBoost), 0, unity_MaxOutputValue);	// Apply Albedo Boost from LightmapSettings.
    }
    if (unity_MetaFragmentControl.y)
    {
        color = half4(mat.emitColor, 1.0);
    }
    return color;
}

#endif //SEGATB_META
// ---------------------------------------------------------------------------
// ------------------------------------------------------------------------------------
#endif //SEGATB_CHS_INCLUDED

litChk.shader

Shader "SegaTechBlog/lightingChecker" {
	Properties {
		[Header(__ Material Params __________)][Space(5)]
		_AlbedoColor ("Color",      Color)            = (0.4663, 0.4663, 0.4663, 1)
		_Metallic    ("Metallic",   Range(0.0, 1.0))  = 0.0
		_Anisotropy	 ("Anisotropy", Range(-1.0, 1.0)) = 0.0
		_Smoothness  ("Smoothness", Range(0.0, 1.0))  = 0.5	[Space(15)]
		[Toggle(_COLMAP)]   _UseColorMap      ("@ Color Map",                    Float) = 1
		[NoScaleOffset]     _MainTex          ("Color(RGB), Alpha(A)",            2D)    = "white" {}
		[Toggle(_METMAP)]   _UseMetMap        ("@ Mat Map:Metallic",             Float) = 1
		[Toggle(_OCCMAP)]   _UseOccMap        ("@ Mat Map:Occlusion",            Float) = 1
		[Toggle(_SMTMAP)]   _UseSmtMap        ("@ Mat Map:Smoothness",           Float) = 1
		[NoScaleOffset]     _MetallicGlossMap ("Metal(R), Occlude(G), Smooth(A)", 2D)    = "white" {}
		[Toggle(_NORMALMAP)]_UseNormalMap     ("@ Normal Map",                   Float) = 0
		[NoScaleOffset]     _NormalMap        ("Tangent Normal(RGB)",             2D)    = "bump" {}
		[Header(__ View One Element ___________)][Space(5)]
		          [KeywordEnum(_,COLOR,DIFFUSE_COLOR,SPECULAR_COLOR,METALLIC,SMOOTHNESS,OCCLUSION)]_VMAT("> View Material Element", Float) = 0
		[Space(5)][KeywordEnum(_,LIGHT_COLOR,LIGHT_ILLUMINANCE,SHADE_LAMBERT,SHADE_SPECULAR,SHADE_SPEC_DGF,SHADE_SPEC_D,SHADE_SPEC_G,SHADE_SPEC_F)]_VSUN("> View Sun Light Element", Float) = 0
		[Space(5)][KeywordEnum(_,LIGHT_ILLUMINANCE,SHADE_LAMBERT,SHADE_REFLECTION)]_VENV("> View Environment Light Element", Float) = 0
		[Space(5)][KeywordEnum(_,LIGHT_COLOR,LIGHT_ILLUMINANCE,SHADE_LAMBERT,SHADE_SPECULAR,SHADE_SPEC_DGF,SHADE_SPEC_D,SHADE_SPEC_G,SHADE_SPEC_F)]_VPOINT("> View Sub Light Element", Float) = 0
		[Space(5)][KeywordEnum(_,TOTAL_ILLUMINANCE,TOTAL_REFLECTION)]_VGET("> View Total Light Amount", Float) = 0
		[Space(15)]
		[Header(__ Measure The Value __________)][Space(5)]
		[Toggle(_CHECKVALUE)]_CheckValue("> Measure The Output Value", Float) = 0
		[Space(5)]_ChkTargetValue(" ORANGE-GREEN-BLUE", Range(-0.1, 5.0)) = 0.1842
		[Enum(x0.01,0.01, x0.1,0.1, x1,1.0, x10,10.0, x100,100.0, x1000,1000.0, x10000,10000.0)]_ChkTargetScale("    (Higher - Hit - Lower)", Range( 0.001, 1000.0)) = 1.0
		[Space(8)][PowerSlider(2.0)]_ChkRange(" Tolerance", Range(0.0032, 10.0)) = 0.045
		[Space(30)]
		[Header(__ Other Options ____________)][Space(5)]
		[Toggle(_NOPIDIV)]_NoPiDiv("No INV_PI as UnityStandard", Float) = 0
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 100
		Pass {
			Name "FORWARD"
			Tags{ "LightMode" = "ForwardBase"}
			ZWrite On
			Blend One Zero
			BlendOp Add
			HLSLPROGRAM
			#pragma target 3.5
			#pragma multi_compile_instancing
			#pragma instancing_options assumeuniformscaling
			#pragma multi_compile _ VERTEXLIGHT_ON
			#pragma shader_feature DIRECTIONAL
			#pragma shader_feature SHADOWS_SCREEN
			#pragma multi_compile _ LIGHTPROBE_SH DIRLIGHTMAP_COMBINED
			#pragma multi_compile _ UNITY_USE_NATIVE_HDR UNITY_LIGHTMAP_RGBM_ENCODING UNITY_LIGHTMAP_DLDR_ENCODING
			#pragma shader_feature DYNAMICLIGHTMAP_ON
			#pragma shader_feature _NOPIDIV
			#pragma shader_feature _COLMAP
			#pragma shader_feature _METMAP
			#pragma shader_feature _OCCMAP
			#pragma shader_feature _SMTMAP
			#pragma shader_feature _NORMALMAP
			#pragma multi_compile _ _VMAT_COLOR _VMAT_DIFFUSE_COLOR _VMAT_SPECULAR_COLOR _VMAT_METALLIC _VMAT_SMOOTHNESS _VMAT_OCCLUSION _VSUN_LIGHT_COLOR _VSUN_LIGHT_ILLUMINANCE _VSUN_SHADE_LAMBERT _VSUN_SHADE_SPECULAR _VSUN_SHADE_SPEC_DGF _VSUN_SHADE_SPEC_D _VSUN_SHADE_SPEC_G _VSUN_SHADE_SPEC_F _VENV_LIGHT_ILLUMINANCE _VENV_SHADE_LAMBERT _VENV_SHADE_REFLECTION _VPOINT_LIGHT_COLOR _VPOINT_LIGHT_ILLUMINANCE _VPOINT_SHADE_LAMBERT _VPOINT_SHADE_SPECULAR _VPOINT_SHADE_SPEC_D _VPOINT_SHADE_SPEC_G _VPOINT_SHADE_SPEC_F _VGET_TOTAL_ILLUMINANCE _VGET_TOTAL_REFLECTION
			#pragma multi_compile _ _VSUN__
			#pragma multi_compile _ _VPOINT__
			#pragma shader_feature _CHECKVALUE
			#pragma vertex   ChsForwardVertex
			#pragma fragment ChsForwardFragment
			#define SEGATB_FORWARD
			#include "litChkLib.hlsl"
			ENDHLSL
		}
		Pass {
			Name "ShadowCaster"
			Tags{"LightMode" = "ShadowCaster"}
			ZWrite On
			ColorMask 0
			HLSLPROGRAM
			#pragma target 3.5
			#pragma multi_compile_instancing
			#pragma instancing_options assumeuniformscaling
			#pragma vertex   DepthOnlyVertex
			#pragma fragment DepthOnlyFragment
			#define SEGATB_SHADOWCASTER
			#include "litChkLib.hlsl"
			ENDHLSL
		}
		Pass {
			Name "META"
			Tags{"LightMode" = "Meta"}
			Cull Off
			HLSLPROGRAM
			#pragma shader_feature _COLMAP
			#pragma shader_feature _METMAP
			#pragma shader_feature EDITOR_VISUALIZATION
			#pragma vertex   MetaVertex
			#pragma fragment MetaFragment
			#define SEGATB_META
			#include "litChkLib.hlsl"
			ENDHLSL
		}
	}
}


 f:id:sgtech:20190422122809g:plain



マテリアルカラーの測定

さっそく、スフィアを置いて、ライティング検証用マテリアルを適用してみます。

マテリアルを作る

  1. シーンに、プリミティブのスフィアを置く GameObject > 3D Object > Sphere
    f:id:sgtech:20190422122847g:plain
  2. Assetsフォルダに、マテリアルを新規作成する  Assets > Create > Material
    f:id:sgtech:20190422122845g:plain
  3. マテリアルのシェーダーを、SegaTechBlog / lightingChecker に切り替える
    f:id:sgtech:20190422122843g:plain
  4. スフィアに、作成したマテリアルを割り当てる
    f:id:sgtech:20190422122841g:plainf:id:sgtech:20190422122839g:plain


マテリアルの デフォルトカラーを見てみると、グレー 119[sRGB] となっています。 この色は、印刷物や塗装に
おける 一般的な指標である、CIE L*a*b* ミドルグレー:反射率 18.42[%] に相当します。
*2
f:id:sgtech:20190422122917j:plain
このカラーが 正しい反射率になっていることを、マテリアルの 出力値測定機能を使って確認してみましょう。

マテリアルカラーを測定する

  1. マテリアルの View Material Elementプルダウンメニューから COLOR を選択
    > マテリアルカラーが単色で出力表示されます。
    f:id:sgtech:20190422122915g:plainf:id:sgtech:20190422122913g:plain
  2. Measure The Output Valueチェックボックスを オン
    f:id:sgtech:20190422122910g:plain
  3. スライダーを動かして、緑色に光るところを探す
    > 緑色になったとき、そのスライダーの値が、現在のマテリアル出力値となります。
    f:id:sgtech:20190422122908g:plainf:id:sgtech:20190422123005g:plain
    測定した結果は おおよそ0.46、反射率46[%]となってしまいました。
    これは、Unityプロジェクトのカラースペースが、初期設定では Gammaになっているからです。

    プロジェクトがGamma設定のとき、Unityは 色空間をコントロールしません。カラーの入力は sRGB(ガンマ2.2)ですが、これを そのままライティング計算に使い、ガンマ2.2用モニターに そのままの値を出力します。 結果として、入力した色は そのまま表示されるのですが、ライティング計算が 誤ったGamma色空間で行われることになるので、…なんだか…こう…濃くてギラッとしたライティング結果になりがちです。



プロジェクトのカラースペースを、Linearに切り替えます。

カラースペースを変更する

  1. プロジェクト設定ウィンドウを開く Edit> Project Settings
  2. Playerタブを開き、カラースペースのプルダウンメニューからLinearを選択
    f:id:sgtech:20190422123003g:plain
  3. 再び、マテリアルの 計測スライダーを動かして、緑色に光るところを探すf:id:sgtech:20190422123001g:plain
    今度の測定結果は 期待どおり、 おおよそ0.18、反射率18[%]になりました。
    プロジェクトがLinear設定のとき、Unityは 色空間をコントロールして、ライティング計算をLinear色空間で行います。sRGB(ガンマ2.2)カラーの入力を リニアカラーに変換し、これをライティング計算に使います。最後に、ライティング結果を リニアカラーからsRGBカラーに戻した値を、ガンマ2.2用モニターに出力します。結果として、入力した色は そのまま表示され、かつ、ライティング計算も正しく行われる形になります。 つまり、カラースペース設定変更の前後で、モニターに表示されるマテリアルカラーの見た目は変化しません。シェーダー内部での値と、ライティング結果が 変化します。
     f:id:sgtech:20190422122958g:plain f:id:sgtech:20190422122956g:plain



ライトカラーの測定

このマテリアルで、モデルを照らしているライトのカラーも 表示, 測定することができます。

ライトカラーを計測する

  1. マテリアルの View SunLight Elementプルダウンメニューから LIGHT_COLOR を選択
    > 太陽(Directional Light)のカラーが単色で出力表示されます。
    f:id:sgtech:20190422123052g:plainf:id:sgtech:20190422123050g:plain
  2. ふたたび Measure The Output Valueをオンにし、スライダーを動かして、緑色に光るところを探す
    > 測定結果は おおよそ0.9になりました。
    f:id:sgtech:20190422123048g:plain

    Directional Lightオブジェクトを選択してみると、Intensityが1、カラーに少し黄色が入っていて 、
    …だいたい0.9、合ってる!という感じがします。
    f:id:sgtech:20190422123046g:plain
    もっと 大きな値も入れてみましょう。

  3. Directional LightのIntensityを 2 に上げるf:id:sgtech:20190422123044g:plain
  4. Sphereを選択し、マテリアルの測定スライダーを動かして、緑色に光るところを探す
    > 測定結果は おおよそ 4.14 となりました。1.8になるはずが…。
    f:id:sgtech:20190422123134g:plain

    これは、ライトのIntensityがsRGB(ガンマ2.2)値として扱われているからです。ライトのColorは色なのでsRGB扱いで良いですが、Intensity(ライトの強さ)や Indirect Multiplier(ライトマップを焼くときの 強さ補正値) にガンマが掛かってしまうのは、PBRライティング環境を設定するうえで 邪魔になります。
    この仕様、Unityとしては、Gamma設定のプロジェクトとの整合性を狙った仕様なのかもしれません。 ほかにも、HDRIイメージを天球に貼るときに使う Skybox/Cubemapマテリアルの Exposure値が、同様の仕様になっています。



Project SettingsのGraphicsタブに、ライト強度をリニア値として扱うオプション設定が存在します。
が、GUI上には表示されていないので、スクリプトを使って切り替えます。*3

ライト強度のカラースペースを変更する

  1. メニューから SegaTechBlog > LightsIntensity > Linear を選択f:id:sgtech:20190422123131g:plain
  2. マテリアルの測定スライダーを動かして、緑色に光るところを探す
    > 今度の測定結果は 期待どおり、 おおよそ1.8になりました。
    f:id:sgtech:20190422123129g:plain



現実世界では、ライトに照らされたとき モデル全体にライトカラーの光が届く訳ではなく、ライトに正面を向いた所は明るく照らされますが、ライトから横に90度向いた面には光が届きません。

ライトから投げつけた光の粒が、正面ほど 多く当たり、横向きの面には あまり当たらない というイメージです。f:id:sgtech:20190422122647g:plain
この、ライトからの光が 面に当たった量のことを、照度(illuminance)といいます。
照度を出力表示してみましょう。

ライトの照度と表面の輝度を比較する

  1. マテリアルの View SunLight Elementプルダウンメニューから LIGHT_ILLUMINANCE を選択
    > 太陽(Directional Light)の照度が出力表示されます。
    f:id:sgtech:20190422123126g:plainf:id:sgtech:20190422123124g:plain
  2. Measure The Output Valueをオン
    > 太陽正面方向の照度が ちょうど1.8(=ライトカラーと同じ値)になっています。
    f:id:sgtech:20190422123210g:plainf:id:sgtech:20190422123208g:plain

    面に当たった光の量が照度。ですが、これが そのまま、面の 見た目の明るさになるわけではありません。

    カメラから見て、その ライトアップされた面が見えている、ということは、面で跳ね返った光がカメラに向かって飛び込んできた、ということになります。
    f:id:sgtech:20190422122637g:plain

    いわゆる完全拡散反射面、まったくスペキュラの無い、どこから見ても おなじ明るさに見える材質があるとすると、面に当たった光を 全方向 (面の真横までくれば見えないので、半球の範囲) に分配して飛ばさなければいけません。
    f:id:sgtech:20190422122642g:plain

    面に入ってきた光が1なのに、 全方向に1ずつ跳ね返す、というのは物理的にありえません。
    面から 全方向に1ずつ跳ね返すためには、約3倍…ちょうど円周率π倍の光が、面に当たる必要があります。
    逆に、面に当たった光が1だと、そこから跳ね返って 特定の方向に進み ちょうどカメラに当たる光の量は 1 / π となります。

    面で跳ね返って飛んできて カメラに当たった(見えた)光の量のことを、その面の輝度(luminance)といいます。 完全拡散面の輝度を出力表示してみましょう。


  3. マテリアルの View SunLight Elementプルダウンメニューから SHADE_LAMBERT を選択
    > 反射率100%, 完全拡散反射面 の輝度が出力表示されます。 …照度表示から 1 / π 暗くなってるだけですが。
    f:id:sgtech:20190422123206g:plainf:id:sgtech:20190422123204g:plain
    UnityのStandardシェーダーでは、この シェーディング時に照度をπで割る処理を省略しています。 旧来シェーダーとの互換性をとった判断と思われます。 明るさ 1 のライトで照らしたのに、白い面が 0.3のグレー にしかならない!というのが 馴染みにくかったのかもしれません。
    こういった仕様の場合、現実的な光の値を使いたい時には 手計算でπで割った数字を入れて対処することで 限定的には対応できます。 この対処方法については、記事のまとめで 改めて触れます。



太陽の明るさを設定する

準備ができたので、ターゲットの屋外ライティング環境に合わせていきましょう。

光学単位を そのまま使うには、日中屋外の数値は ケタが大きすぎるので、1/5000にしてみます。
そうすると、冒頭で提示したターゲット環境テーブルは、以下のような値になります。

太陽に直交した面に 入ってくる光の量(照度) 太陽から 17.0 青空(半球)から 4.0 合計     21.0 [lux/5k]
太陽の傾斜角が66.6度。水平地面に 入ってくる光の量(照度) 太陽から 15.6 青空(半球)から 4.0 合計     19.6 [lux/5k]
この環境に照らされたグレー18.42%地面の明るさ(輝度) 青空 =日陰   0.23 合計 =日向    1.15 [nt/5k]
青空の明るさ(輝度) 平均     1.27 [nt/5k]
白い雲     2.0 [nt/5k]
水平線     1.6 [nt/5k]
青い空     0.8 [nt/5k]
天頂部     0.3 [nt/5k]
カメラの露出補正(相対補正値) -2.975 [EV]




屋外環境のライティングを整えるのに、スフィア1個では さすがに無理があるので、最低限のシーンデータを用意します。 その他、自作のモデルなどあれば インポートして配置してみてください。lightingCheckerマテリアルは Unity Standard仕様のカラーマップ,ノーマルマップ,マテリアルマップを そのまま適用できます。

屋外環境のシーンデータを用意する

  1. プリミティブの箱を置く GameObject > 3D Object > Cube
    f:id:sgtech:20190422123202g:plain
  2. 箱にも スフィアと同じlightingCheckerマテリアルを割り当てるf:id:sgtech:20190422123241g:plain
  3. プリミティブの板を 地面として置く GameObject > 3D Object > Plane
    f:id:sgtech:20190422123239g:plain
  4. lightingCheckerマテリアルを複製して、板に割り当てるf:id:sgtech:20190422123237g:plain
  5. 板をStaticに指定する
    f:id:sgtech:20190422123235g:plain
  6. ライトプローブを置き、地面に埋まらないよう1.2m程度 持ち上げる GameObject > Light > Light Probe Group
    f:id:sgtech:20190422123233g:plain
  7. リフレクションプローブを置き、地面に埋まらないよう1.2m程度 持ち上げる GameObject > Light > Reflection Probe
    f:id:sgtech:20190422123316g:plain


ごく一般的なシーンセットアップになっていると思います。

 Unity初めての方に このシーンデータの説明

  • Staticに指定した板は、動かさない 背景モデル扱いになります。事前にGlobal Illuminationでライトマップが焼かれ、ゲーム中では このライトマップを利用して 環境光からの拡散反射(diffuse)シェーディングが表現されます。
  • それ以外のモデルは、ライトプローブを利用して環境光からの拡散反射(diffuse)シェーディングを表現します。ライトプローブには、事前にGlobal Illuminationによるライティング情報が焼かれて入っています。
  • リフレクションプローブには、事前に環境マップ(HDRのCubeMapテクスチャ)が焼かれて入っています。全てのモデルは、この環境マップを利用して 環境光からの鏡面反射(reflection)シェーディングを表現します。
  • リフレクションプローブを置かなくても天球が鏡面反射に使われますが、それだと地面板が 実モデルの輝度で映り込まないので、リフレクションプローブを置きました。
  • Global Illuminationの事前計算は、デフォルトだと エディタ上では 必要に応じて自動更新されます。自動更新が邪魔になったら、Lightingウィンドウを開いてAuto Generateチェックボックスをオフにすれば 手動更新になります。だいたい皆、邪魔になってきて切りますが、今回のようなライティング環境セットアップ中は オンのままが便利です。
  • メインライトである太陽から直接のライティングは、全てのモデルにおいて、リアルタイムシェーディングで表現されます。




太陽のライトカラーには、 太陽に直交した面に 太陽から 入ってくる光の量(照度)17.0 [lux/5k] を設定します。

太陽のライトカラーを設定する

  1. 箱のマテリアルの、View SunLight Elementプルダウンメニューから LIGHT_COLOR を選択
  2. Measure The Output Valueをオン、スライダーを 1.7 に設定、すぐ下の掛け数を x10 に設定
    > これで、出力値が17( = 1.7x10 )のときに 緑に光る設定になりました。
    f:id:sgtech:20190422123314g:plain
  3. Directional Lightオブジェクトを選択し、モデルが緑に光るまで ライトのIntensityを上げていく
    > 18.67 で緑に光りました。(いちおう検算するとライトカラーの黄色を掛けて ちょうど17になります。*4
    f:id:sgtech:20190422123311g:plainf:id:sgtech:20190422123309g:plain




太陽の角度を66.6度にセットし、
このとき 水平面に 入ってくる光の量(照度)15.6 [lux/5k] になっていることを確認してみましょう。

太陽の向きを設定する

  1. マテリアルの View SunLight Elementプルダウンメニューから LIGHT_ILLUMINANCE を選択f:id:sgtech:20190422123307g:plain
  2. Directional LightのRotationを (90.0, 0, 0) に設定
    > 試しに、太陽の角度を90度にセットしてみました。
     箱の上面が緑になっているので、このライトで真上から照らすと照度17.0であることが再確認できました。

    f:id:sgtech:20190422123348g:plainf:id:sgtech:20190422123346g:plain
  3. Directional LightのRotationを (66.6, 0, 0) に設定
    > 太陽の角度を66.6度に 傾けてセットしました。
    f:id:sgtech:20190422123343g:plain
  4. マテリアルのスライダーを 1.56 に設定
    > 箱の上面が緑になりました。66.6度から照らした時、水平面の照度15.6にできていることが確認できました。
    f:id:sgtech:20190422123341g:plainf:id:sgtech:20190422123339g:plain



空の明るさを設定する

次に、空を調整します。 デフォルトの天球マテリアルは パラメータを 変更出来ないので、Skybox/Proceduralマテリアルを新規作成し、シーンにセットして使います。 Skybox/Proceduralマテリアルに 出力値の測定機能はありませんが、モデルに映り込んだ天球を、 lightingCheckerマテリアルで測定することができます。
ただし Skybox/Proceduralマテリアルには はっきりした白い雲を表示する機能が無いので省略し、 空の平均 1.27 [nt/5k] , 水平線 1.6 [nt/5k] , 青い空 0.8 [nt/5k] , 天頂部 0.3 [nt/5k] の4つの輝度をターゲットに設定します。

空の輝度を設定する

  1. Assetsフォルダに、マテリアルを新規作成する  Assets > Create > Material
  2. 作成したマテリアルのシェーダーを、Skybox / Proceduralに切り替える
    f:id:sgtech:20190422123428g:plain
  3. ライティング設定ウィンドウを開く Window > Rendering > Lighting Settings
  4. Skybox Materialに、作成したSkybox/Proceduralマテリアルを割り当てる
    f:id:sgtech:20190422123425g:plain
  5. lightingCheckerマテリアルを選択し、 View Environment Light Elementプルダウンメニューから SHADE_REFLECTION を選択
    > ボケた天球が映りました。
    f:id:sgtech:20190422123421g:plainf:id:sgtech:20190422123418g:plain
  6. Measure The Output Valueをオン、スライダーを 0.8 に設定、掛け数を x1 に設定
    > これで、出力値0.8の部分が 緑に光る設定になりました。f:id:sgtech:20190422123414g:plain
  7. Smoothnessを動かして、天球の どのあたりが0.8になっているか観察する
    > デフォルト天球は、水平線でも0.7くらいで かなり暗いようです。
    f:id:sgtech:20190422123629g:plain
  8. Skybox/ProceduralマテリアルのExposureの値を上げて、青い空が主に0.8になるよう寄せていく
    > デフォの1.3から6.0まで上げると、青い空0.8周辺、天頂0.4で平均1.2くらいの、ほどよい値になりました。
    f:id:sgtech:20190422123626g:plainf:id:sgtech:20190422123621g:plain

天球全体の輝度をスケールするだけの、すこし雑な調整です。 水平線が 明るめになってしまいました。
Skybox/Proceduralマテリアルの Exposure以外のパラメータを変更すると、大気スキャッタ計算のバランスが変わって、空の色味がズレていくので、天球の輝度調整に使うことは お勧めしません。
白い雲の表現や、水平線付近の減衰(地表からの埃によるフォグ)などについて 細かくバランスを取るには、skyboxシェーダーのカスタマイズをしたり、シーンデータとして遠景モデルを配置していく必要があります。



空の輝度が決まったところで、今度は 水平地面に 青空(半球)から 入ってくる光の量(照度)4.0 [lux/5k] を確認してみましょう。

空の照度を設定する

  1. lightingCheckerマテリアルを選択し、 View Environment Light Elementプルダウンメニューから LIGHT_ILLUMINANCE を選択
    > 環境光からの照度が表示されました。真っ白です。
    f:id:sgtech:20190422123619g:plainf:id:sgtech:20190422123617g:plain
  2. Directional Lightの Indirect Multiplierを 0 に設定
    > 一旦、地面に反射した太陽光の照り返しを消しました。これで 空からの照度だけを計測できます。
    f:id:sgtech:20190422123701g:plainf:id:sgtech:20190422123659g:plain
  3. Measure The Output Valueをオン、スライダーを 4.0 に設定
    > これで、出力値が4のときに 緑に光る設定になりました。
    f:id:sgtech:20190422123657g:plain
  4. ライティング設定ウィンドウを開く Window > Rendering > Lighting Settings
  5. Environment Lighting > Intensity Multiplierの値を変えて、箱の上面が緑になる値を探す
    > 1.151 で、ちょうど 箱の上面が緑になりました。これで 空(半球)からの照度を4.0に設定できました。
    f:id:sgtech:20190422123738g:plainf:id:sgtech:20190422123655g:plain

Unityにおける 空からの環境照明は、鏡面反射(環境マップ)と拡散反射(ライトマップやライトプローブ)が 別々の仕組みで提供されています。この為、空の輝度をピッタリ決めたから 拡散反射の照度も自動で正しい値になる、という風には なかなかいきません。手動で微調整が必要です。




地面照り返しの明るさを設定する

最後に、さきほど一旦0にした、地面で反射した太陽光の照り返し の照度を調整しましょう。 ターゲット環境に この照度の値は無いので、ざっくり算出します。 このライティング環境に照らされたグレー18.42%地面の、日向での明るさ(輝度)1.15 [nt/5k] なので、これが完全拡散反射面として 下一面に広がっていた場合、下半球からの照度は 1.15 x π = 3.613 [lux/5k] となります。 この値に合わせてみましょう。

地面照り返しの照度を設定する

  1. 箱ではなく、地面板の方の lightingCheckerマテリアルを選択し、 View Total Light Amountプルダウンメニューから TOTAL_ILLUMINANCE を選択
    > 地面への、太陽や空からの照度の合計が出力表示されました。
    f:id:sgtech:20190422123735g:plain
  2. Measure The Output Valueをオン、スライダーを動かして、緑色に光るところを探す
    > 地面への照度は19.6( = 1.96x10 )。ターゲット環境の、 太陽の傾斜角が66.6度。水平地面に 入ってくる光の量(照度)合計 19.6 [lux/5k] に 一致していることが確認できました。
    f:id:sgtech:20190422123731g:plainf:id:sgtech:20190422123733g:plain
  3. こんどは 箱のほうの lightingCheckerマテリアルを選択し、 View Environment Light Elementプルダウンメニューから LIGHT_ILLUMINANCE を選択
  4. Measure The Output Valueをオン、スライダーを 3.613 に設定f:id:sgtech:20190422123728g:plain
  5. 箱を下から見上げながら、Directional Lightの Indirect Multiplierを上げていき、箱の下面が緑になる値を探す
    > 0.32 で、ちょうど 箱の下面が緑になりました。これで 地面照り返しの照度を 3.613に設定できました。
    f:id:sgtech:20190422123821g:plainf:id:sgtech:20190422123819g:plain


カメラの露出を設定する

ライティング設定は完了しましたが、画面が ほぼ真っ白です。1/5000していても、まだ、日中の屋外は眩しすぎます。 Unityの標準カメラには HDRの露出補正機能が無いので、ポストプロセスエフェクトを使って、カメラを適正露出に補正しましょう。

Post-Processingのセットアップ

  1. Assetsフォルダに、Post-processing Profileを新規作成する  Assets > Create > Post-processing Profile
    f:id:sgtech:20190422123817g:plain
  2. シーンに、Emptyを置く GameObject > Create Empty
    f:id:sgtech:20190422123815g:plain
  3. Emptyに、ポスプロ設定保持用コンポーネントを追加  Add Component > Rendering > Post-process Volume
    f:id:sgtech:20190422123813g:plain
  4. Post Process VolumeコンポーネントのProfile欄に、さっき作ったPost-processing Profileをセット
    > これで、カメラが このEmptyに近づくと セットしたポスプロ設定が使われるようになりました。*5
    f:id:sgtech:20190422123901g:plain
  5. Post Process Volumeコンポーネントの Is Globalをオン
    > これでカメラとEmptyの位置に関わらず、シーン内では常に このポスプロ設定が使われるようになりました。
    f:id:sgtech:20190422123859g:plain
  6. Emptyを、PostProcessingレイヤーに所属させるf:id:sgtech:20190422123856g:plain
  7. Main Cameraに、ポスプロ設定取得用コンポーネントを追加  Add Component > Rendering > Post-process Layer
    f:id:sgtech:20190422123854g:plain
  8. Post Process LayerコンポーネントのLayer欄に、PostProcessingレイヤーを指定
    > これで、PostProcessingレイヤーに置かれたPost Process Volumeを カメラが取得するようになりました。
    f:id:sgtech:20190422123852g:plain



手動で露出補正する

  1. Post-processing Profileに、Color Gradingエフェクトを追加  Add Effect > Unity > Color Grading
    f:id:sgtech:20190422123948g:plain
  2. Color Gradingの Post-exposureを -2.975 に設定
    > ターゲット環境の カメラの露出設定(相対露出補正値)-2.975 [EV] を設定し、無事に グレーな地面をグレーに表示することができました!
    f:id:sgtech:20190422123945g:plainf:id:sgtech:20190422123943g:plain



…これで、フォトリアルな質感や 屋外のライティングを 表現できるようになったのか、少し試してみましょう。

マテリアルにバリエーションを出してみる

  1. スフィアを複製し、新しいマテリアルを割り当てて、ゴールド のパラメータを設定f:id:sgtech:20190422123939g:plain
  2. 白いモルタル のパラメータを設定
    f:id:sgtech:20190422124101g:plain
  3. 半渇きの土 のパラメータを設定
    f:id:sgtech:20190422124059g:plain
  4. 草 のパラメータを設定
    f:id:sgtech:20190422124057g:plainf:id:sgtech:20190422124055j:plain



悪くありませんが、ゴールドのハイライトの 色の飛び方が下品です。カラーグレーディングにACES色空間を使うことで、高輝度成分を上品に表現した色調調整が やり易くなります。

カラーグレーディングにACESを利用する

  1. Color Gradingの Modeを ACES に設定
    > 高輝度部分のコントラストが柔らかくなり、上品な質感になりました。
    f:id:sgtech:20190422124053g:plainf:id:sgtech:20190422124139j:plain
  2. Post-processing Profileに、Bloomエフェクトを追加。Intensityを2に設定
    > ついでにブルームを載せたところ…、画面全体が ぼんやり光ってしまいました!
    f:id:sgtech:20190422124137g:plainf:id:sgtech:20190422124135j:plain



実は、Color Gradingの Post-exposureはフィルム現像段階での露出補正を模したもので、撮影時のカメラによる露出補正ではありません。ブルームやDoFエフェクトはレンズで起きる現象を模したものなので、露出補正前の輝度(ほぼ白く飛んでいる)に対して エフェクトが掛けられており、その結果 画面全体がブルームしています。
撮影段階で 適切に露出補正するには、Auto Exposureエフェクトを使います。

オートで露出補正する

  1. Color Gradingの Post-exposureをオフにするf:id:sgtech:20190422124132g:plain
  2. Post-processing Profileに、Auto Exposureエフェクトを追加。Minimumを -6、Maximumを 6、Exposure Compensationを 0.4 に設定
    > Min,Maxには オート露出の 露出補正可動範囲を設定します。Exposure Compensationには、画面内の輝度の平均値を、どれくらいの明るさに変換して表示したいかを 設定します。
    f:id:sgtech:20190422124127g:plainf:id:sgtech:20190422124221j:plain



撮影段階で適切に露出補正が行われ、ブルームは 高輝度部分にだけ発生するようになりました。
ターゲット環境で設定していた露出補正値(EV100準拠の絶対露出補正値 EV15 = 1/5000単位での輝度値に対する相対露出補正値 -2.975EV)は、事前に求めた 撮影対象の平均的輝度値を おおよそ0.1として表示するように設定(その結果 グレイ18%が 約0.18で表示される事が期待)されていますが、このAuto Exposureフィルタでは、動的に 現在の画面の平均輝度が求められ、その値が0.4として表示されるようなスケール値が 画面全体にかけられます。
結果として、画面に明るいものが多く映るほど、露出は絞られ 暗い所が より暗く表示されるようになります。

f:id:sgtech:20190422124215g:plainf:id:sgtech:20190422124210g:plain




まとめ

つくったライティング環境の活用

お疲れ様でした。ライティング環境のセットアップは 以上です。つくったライティング環境の活用方法ですが、まずは 主要なアセットを一ステージ分くらい用意して、仮組みしてみるのが良いと思います。
f:id:sgtech:20190422124231j:plain

アセットが増えて 質感のバリエーションがでてくると、画面のコントラストを作るには カメラやライティングに どんなバリエーションが必要なのかが、はっきりしてきます。そして、プロジェクト内で 質感の違いを どう表現してゆくか、またカラーコレクションの方向性なども、順を追って 絵の仕様を決めていける段階になります。


最終的なゲーム画面、コンセプトアートを実現していく中では、独自表現のマテリアルを追加することがあります。
f:id:sgtech:20190422122623g:plain
ゲーム的に必要性が高ければ、天球に雲を加えたり、大気フォグ表現についても 独自で追加する必要があります。
f:id:sgtech:20190422124207j:plainf:id:sgtech:20190422124204j:plain
こういった拡張を行った際は、ぜひ拡張後に もう一度ライティング環境のテストを行ってください。
基準となる 輝度や照度の比率を、コントロールし続けることが大切です。



今回のセットアップは PBRとして素直な値を設定しましたが、実際のUnityの描画フローには 多くの種類があり、それぞれに特性があります。とても全容は書ききれませんが、いくつか 代表的なものについて、対応をリストしておきます。

Standard

  • ライト直接光によるシェーディング輝度はπ倍明るい。
    • この環境でフィジカルな光単位を扱う場合、手動でライトintensityに1/πした値を入れる必要がある。
    • lightingCheckerマテリアルのNo INV_PI as UnityStandardをオンにすれば、この仕様に沿った値を確認できる。
    • 厳密には正しくない対応である(ライト強度を手動で暗くしても、2次反射以降のシェーディング輝度が1/πされる訳ではない)ことに注意しつつ、仕様として飲み込む方向で。
  • (初期設定では)ライトの強度にガンマがかかっている。
  • ポイントライトの強度減衰がPBR準拠(距離自乗減衰)ではない。
    • これも No INV_PI as UnityStandardをオン,オフして仕様の違いを確認できます。
  • シェーディングモデルは、ローエンド機種でなければPBR準拠。


LWRP

  • まだPreview版を抜けたばかりで、仕様が一般化されていない。
  • ライト直接光によるシェーディング輝度はπ倍明るい。
  • ライトの強度はリニア値。
  • ポイントライトの強度減衰はPBR準拠(距離自乗減衰)。
  • シェーディングモデルは、ローエンド機種でなければPBR準拠。


HDRP

  • まだPreview版で、仕様が確定していない。
  • ライト直接光によるシェーディング輝度は正しい。
  • ライトの強度はリニア値で、光学単位の 大きな数値を そのまま入力できる(内部的には EV100単位のpre-exposure値を算出して事前にかけることで 値の爆発を避けている)。
  • ポイントライトの強度減衰はPBR準拠(距離自乗減衰)。
  • ポイントライトの 光源サイズを規定したり、ライン光源, 面光源を利用できる(距離自乗減衰ではなく エリアライトの減衰カーブが扱える)。
  • ポスプロは独自仕様となる(PostProcessing V3に相当)。
  • シェーディングモデルはPBR準拠、かつ SSS,Aniso,ClearCoat表現が追加されてDisney BSDF相当に近づいた。*6


Gammaワークフロー全般

  • おおむね2Dのゲームだから Photoshopと レイヤー合成の見た目を合わせてくれ、というケース。わかります!
  • とはいえ このワークフロー、実質 カラースペース管理の放棄なので、PBRライティングとの共存は 無理です。
  • もしコストが割けるのであれば、Linearワークフロー内に Gammaワークフロー的な部分を作ることは可能です。




ターゲット ライティング環境テーブルを 自分で用意してみたい時は、以下のメモを参考にしてください。

ライティング環境の求め方

屋外の場合

・まず、太陽と空からの照度を計測します。シンプルな手法が Moving Frostbite to Physically based renderingに載っています。

Measurements were taken at ground level with the light meter’s sensor angled horizontally or perpendicular to the sun (⊥ index). Measurements were performed at various hours of a sunny day with few clouds, in Stockholm in July. The sky values were obtained by occluding the sun with a small object (i.e. casting a shadow on the sensor).

https://seblagarde.wordpress.com/2015/07/14/siggraph-2014-moving-frostbite-to-physically-based-rendering/
  1. 照度計を使い、太陽正面向きの照度を計測 = A [lux]
  2. 太陽が直射する方向だけ 小さなもので隠して(=照度センサーを影にいれて)、空の照度を計測 = B [lux]
  3. 太陽からの照度 C = (A-B) [lux]

個人的に おすすめの照度計は セコニックのL-478Dです。オプションのビューファインダーを付ければ輝度計にもなって便利です。2台欲しい!

・太陽の傾斜角は、場所と時間に基づいて 算出してくれるサイトが いくつもあります。 = Θ

・グレー18%完全拡散反射面である水平地面の輝度、日向は、0.1842 * ( C * cos(Θ) + B ) / π [nt]
・ 同 日陰の輝度は、0.1842 * B / π [nt]
・空の輝度の平均は、B  / π [nt]

・空の細かいところの輝度は、輝度計で計測します。雲や空の輝度は、環境によって かなり大きい振れ幅で変化します。なるべく細かく計測して、ならした値を使いましょう。 こちらの PV Lighthouse - ALBEDO 太陽光発電に関するレクチャーでは、地面の色が いかに空の輝度に影響を与えるかが説明されています。地面に雪が積もると 照り返しで空が2倍明るくなって、太陽光発電にも貢献するそうです!面白いですね。

・その他、標準反射板を持っていれば、輝度計で計測しておくことで、グレー地面輝度の検算が可能になりそうです。

屋内の場合

・屋内のライティングは、もっと複雑な照明設計になってくるので、難しいところです。
・とりあえず、1/5000 lumen単位だと 数字が小さすぎて扱いにくいです。一般的な室内で300[lux] = 0.06[lux/5k]など。間をとるか、場所で切り分けるか、HDRPのような動的光学単位が必要になります。
・実在の部屋や照明器具を 計測, HDR撮影して 寄せていくのが、比較的 現実的な正攻法になるかと思います。
・屋内ライティングは、グレー18%よりも高い反射率を 部屋の標準マテリアルにして 整える必要があります。現代の建物は、手元の照度や 空間の照度、床と壁のコントラストを 省電力で実現する為に、比較的 高反射率の素材を使っている傾向があります。 Panasonic - P.L.A.M. - 各種材料反射率の表で屋内向けの建材や塗装を見てみると、自然物や屋外資材と比べて 高めの反射率が並んでいます。 この場合も、露出補正用のターゲットにはグレー18%を使います。

露出補正値

・EV100の値は、照度計で計測することができます。また、Google FilamentのPBRガイドには、ターゲットの輝度や フラット面への照度、半球への照度から EV100単位の露出補正値を算出する方法が載っています。

自前のシェーダーに測定機能を付けたい

・今回のシェーダー、litChkLib.hlslに入っているCheckColorValue関数を ご覧ください。シンプルな仕組みです。
・Core RP Library使いやすそう!と思った方には、Catlike Coding - Scriptable Render Pipelineチュートリアルが お勧めです。


みなさんも一緒に働きませんか?

長文、読んでくださってありがとうございました。
この記事で 興味を持たれた方、セガ・インタラクティブで私たちと一緒に働いてみませんか?

アーケードゲーム開発は、ハイエンドPC相当の特定GPU構成、個性的な筐体をターゲットに、中小規模ゲーム開発が楽しめる、グラフィックDev好きには なかなか たまらない環境です!
採用情報については、以下のリンクを、是非ご確認ください。
sega-games.co.jp


©SEGA

*1:Unity2019.1では Global Illminationの初期設定が変更された為、環境ライティング系の調整結果が Blogと異なる値に落ち着く。
また、Unity2018.2以前のバージョンでは、シェーダー内の Core RP Libraryへのパスを書き換える必要がある。

*2:Gamma Color119 / 255 \fallingdotseq 0.46 , Gamma Color^{2.2} \fallingdotseq Linear Color なので 0.46^{2.2} \fallingdotseq 0.18
これは完全白色が反射率100%であるという仮定下でのL*a*b*ミドルグレーとなる。

*3:https://docs.unity3d.com/2018.3/Documentation/ScriptReference/Rendering.GraphicsSettings-lightsUseLinearIntensity.html

*4:ライトのデフォルト黄色はGamma Color(255, 244, 214)/255 これは Linear Color(1.0, 0.9074, 0.6800)
 , これに輝度変換係数の(0.2126729, 0.7151522, 0.072175)を掛けて足しあわせ 黄色の輝度は0.911
 ,  18.67*0.911 \fallingdotseq 17.0

*5:カメラが近づいたと見なす距離は、EmptyにBox Colliderを追加して設定する。

*6:Disney BSDFについてはSiggraph2012 - Practical physically-based shading in film and game productionのPhysically Based Shading at Disney とSiggraph2015 - Physically based shading in theory and practiceのExtending the Disney BRDF to a BSDF with Integrated Subsurface Scattering。また 同名やPrincipled BSDFで検索すると 様々な粒度の情報が得られる。

Powered by はてなブログ