本日はShader学習枠です。
今回勉強も兼ねてMRTG StandardShaderを改造していきます。
〇NPRシェーダーとは?
NPR(No Photorealistic Rendering)とは、写実的ではないレンダリングを指します。
つまりリアルではない見た目となるという意味です。
NPRの中には漫画調(Toon)、セルルック、絵画調など様々なデフォルトされた見た目を含んでいます。
今回は最終目標としてMRGTシェーダーをベースとしたToonシェーダーの開発を目指しまずはライティングをいじっていきます。
〇MRGT StandardShaderの改造
MRTGのシェーダーはウーバーシェーダーが採用されており、使用する機能だけ有効になる仕組みとなっています。
今回はまずチェックボックスを作成して、元の機能と併用できるように増築する形で進めます。
①MRGTStandardシェーダーのPropertiesブロックに[Toggle]アトリビュートで変数を追加します。
Properties { [Toggle(_Toon)] _ToonShading("NPRLighting", Float) = 1.0//追加 // Main maps.
これによってNPRLightingのチェックボックスが使用可能になります。
なおMRGTStandardシェーダーはStandardShaderGUI.csによって表示をカスタマイズされているため今回は一時的にコメントアウトしています。
②GraphicsToolsStandardProgram.hlslのFeaturesに作成したNPRLightingのトグル用のShaderFeatureを宣言します。
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #ifndef GRAPHICS_TOOLS_STANDARD_PROGRAM #define GRAPHICS_TOOLS_STANDARD_PROGRAM #pragma vertex VertexStage #pragma fragment PixelStage // Comment in to help with RenderDoc debugging. //#pragma enable_d3d11_debug_symbols /// <summary> /// Features. /// </summary> #pragma shader_feature_local _Toon//追加 #pragma shader_feature_local _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHABLEND_TRANS_ON _ADDITIVE_ON #pragma shader_feature_local _DISABLE_ALBEDO_MAP #pragma shader_feature_local_fragment _ _METALLIC_TEXTURE_ALBEDO_CHANNEL_A _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A #pragma shader_feature_local _CHANNEL_MAP #pragma shader_feature_local _NORMAL_MAP #pragma shader_feature_local_fragment _EMISSION #pragma shader_feature_local _TRIPLANAR_MAPPING #pragma shader_feature_local _LOCAL_SPACE_TRIPLANAR_MAPPING #pragma shader_feature_local_fragment _USE_SSAA #pragma shader_feature_local _DIRECTIONAL_LIGHT #pragma shader_feature_local_fragment _SPECULAR_HIGHLIGHTS
これでコード内で#if defined(_Toon) ~#endifで条件分岐が可能になります。
③次にライティングの処理を実装します。ライトの処理は770行目当たりから始まります。
// Blinn phong lighting. #if defined(_DIRECTIONAL_LIGHT) #if defined(_URP) Light directionalLight = GetMainLight(); half3 directionalLightDirection = directionalLight.direction; half3 directionalLightColor = directionalLight.color; #else half3 directionalLightDirection = _WorldSpaceLightPos0.xyz; half3 directionalLightColor = _LightColor0.rgb; #endif #if defined(_Toon)//ここから追加 half diffuse = ceil(saturate(dot(worldNormal,directionalLightDirection.xyz)+_ToonAmount)); half shadow = (1-diffuse)*0.5; diffuse = diffuse + shadow; #else //ここまで追加 half diffuse = max(0.0, dot(worldNormal, directionalLightDirection.xyz)); #endif #if defined(_SPECULAR_HIGHLIGHTS) half halfVector = max(0.0, dot(worldNormal, normalize(directionalLightDirection.xyz + worldViewDir))); half specular = saturate(pow(halfVector, _Shininess * pow(_Smoothness, 4.0)) * (_Smoothness * 2.0) * _Metallic); #else half specular = 0.0; #endif #endif
今回はまずURP用に処理を実装しています。
Light directionalLight = GetMainLight(); half3 directionalLightDirection = directionalLight.direction; half3 directionalLightColor = directionalLight.color;
事前にライト情報が取得されています。
従来ライトを使用する場合は次のように変数diffuseに値が処理され格納されるようになっています。
half diffuse = max(0.0, dot(worldNormal, directionalLightDirection.xyz));
これは一般的によく見るNdotLという処理で、ライト情報と法線のドット積です。
これでライトの当たり方(影と光)を算出しており、この際にマイナス値になる部分に関してはmax関数により0以上に補正されています。
ここを今回次のように変更しました。
half diffuse = ceil(saturate(dot(worldNormal,directionalLightDirection.xyz)+_ToonAmount)); half shadow = (1-diffuse)*0.5; diffuse = diffuse + shadow;
ceil関数はFloor関数に似て次の整数を返す値です。
中は基本的にNdotLのままで、係数としてこちらで要した_ToonAmountを使用しています。
またsaturate関数は値を0~1に収めるものです。
つまりceil関数によりdiffuseは0か1の二つどちらかを返すようになります。
dot(worldNormal,directionalLightDirection.xyz)//=NdotL
は陰影の計算のためこの値の結果を0or1に変換することで影がはっきり分かれるNPRを実現しました。
_ToonAmountは閾値で、0,1の領域を調整します。
しかしこのままではdiffuseは0,1の値をとり、0の領域は光があたらない真っ暗の領域となってしまいのちの計算で厄介です。
shadowはこれを避けるために使用しており、1-diffuseの影の部分を抽出して、0.5をかけて、黒(0)ではなくグレー(0.5)へと影を調整しています。
half shadow = (1-diffuse)*0.5;
以上で基礎的なNPRシェーダーのライティングを実装しました。
Toonシェーダーと呼ぶには少し早いですが、ここから新たな機能を実装していきます。