user 010beaea75 Chore: NiloToonURP 업데이트 및 배경 썸네일 갱신
- NiloToonURP 외부 에셋 업데이트
- 배경 씬 썸네일 16:9 해상도로 갱신
- 렌더 파이프라인 설정 업데이트
- 외부 셰이더 그래프 업데이트 (LEDScreen, PIDI Planar Reflections)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 01:28:02 +09:00

4739 lines
236 KiB
HLSL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-License-Identifier: (Not available for this version, you are only allowed to use this software if you have express permission from the copyright holder and agreed to the latest NiloToonURP EULA)
// Copyright (c) 2021 Kuroneko ShaderLab Limited
// For more information, visit -> https://github.com/ColinLeung-NiloCat/UnityURPToonLitShaderExample
// #pragma once is a safeguard best practice in almost every .hlsl,
// doing this can make sure your .hlsl's user can include this .hlsl anywhere anytime without producing any multi include conflict
#pragma once
#include "NiloToonCharacter_RiderSupport.hlsl"
// note:
// suffix OS means object space (e.g. positionOS = position object space)
// suffix WS means world space (e.g. positionWS = position world space)
// suffix VS means view space (e.g. positionVS = position view space)
// suffix CS means clip space (e.g. positionCS = position clip space)
// just helper defines to help us write less #if define code
// we always write #define XXX 1, instead of just #define XXX, so we can use both #if or #ifdef in shader code without problem
#if NiloToonSelfOutlinePass || NiloToonExtraThickOutlinePass || NiloToonCharacterAreaStencilBufferFillPass || NiloToonDepthOnlyOrDepthNormalPass || NiloToonPrepassBufferPass
#define NiloToonIsAnyOutlinePass 1
#endif
#if NiloToonForwardLitPass || NiloToonSelfOutlinePass
#define NiloToonIsAnyLitColorPass 1
#endif
#if NiloToonIsAnyLitColorPass || NiloToonDepthOnlyOrDepthNormalPass || NiloToonExtraThickOutlinePass || NiloToonPrepassBufferPass
#define ApplyZOffset 1 // foreach pass that will edit "ZOffsetFinalSum", will be included in "ApplyZOffset"
#endif
#if NiloToonIsAnyLitColorPass || NiloToonDepthOnlyOrDepthNormalPass || NiloToonExtraThickOutlinePass || NiloToonPrepassBufferPass || NiloToonCharacterAreaStencilBufferFillPass || NiloToonCharacterAreaColorFillPass
#define ApplyPerspectiveRemoval 1
#endif
// because _DETAIL always sample detail normal map
#if _NORMALMAP || _DETAIL || _KAJIYAKAY_SPECULAR || _DYNAMIC_EYE || _PARALLAXMAP
#define VaryingsHasTangentWS 1
#endif
#if _PARALLAXMAP
#define VaryingsHasViewDirTS 1
#endif
#if _ISFACE && _FACE_MASK_ON
#define NeedFaceMaskArea 1
#endif
#if _OCCLUSIONMAP || _MATCAP_OCCLUSION
#define AnyOcclusionEnabled 1
#endif
#if _BASEMAP_STACKING_LAYER1 || _BASEMAP_STACKING_LAYER2 || _BASEMAP_STACKING_LAYER3 || _BASEMAP_STACKING_LAYER4 || _BASEMAP_STACKING_LAYER5 || _BASEMAP_STACKING_LAYER6 || _BASEMAP_STACKING_LAYER7 || _BASEMAP_STACKING_LAYER8 || _BASEMAP_STACKING_LAYER9 || _BASEMAP_STACKING_LAYER10
#define AnyBaseMapStackingLayerEnabled 1
#endif
#if _NILOTOON_RECEIVE_URP_SHADOWMAPPING && _RECEIVE_URP_SHADOW
#define ShouldReceiveURPShadow 1
#endif
// to support change in Unity6.1 (The _FORWARD_PLUS shader keyword is deprecated in Unity6.1)
#if UNITY_VERSION >= 60010000
#define NILO_USE_CLUSTER_LIGHT_LOOP USE_CLUSTER_LIGHT_LOOP
#else
#define NILO_USE_CLUSTER_LIGHT_LOOP USE_FORWARD_PLUS
#endif
// We don't have "UnityCG.cginc" in SRP/URP's package anymore, so:
// Including the following two hlsl files is enough for shading with Universal Pipeline. Everything is included in them.
// - Core.hlsl will include SRP shader library, all constant buffers not related to materials (perobject, percamera, perframe).
// It also includes matrix/space conversion functions and fog.
// - Lighting.hlsl will include the light functions/data to abstract light constants. You should use GetMainLight and GetLight functions
// that initialize Light struct. Lighting.hlsl also include GI, Light BDRF functions. It also includes Shadows.
// Required by all Universal Render Pipeline shaders.
// It will include Unity built-in shader variables (except the lighting variables)
// (https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html)
// It will also include many utilitary functions.
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
// Include this if you are doing a lit shader. This includes lighting shader variables,
// lighting and shadow functions
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/ParallaxMapping.hlsl"
// include a bundle of small utility .hlsl files to help us write less code
#include "../../ShaderLibrary/NiloUtilityHLSL/NiloAllUtilIncludes.hlsl"
// we need to define this AFTER including Lighting.hlsl
#if _ADDITIONAL_LIGHTS || _ADDITIONAL_LIGHTS_VERTEX
#define NeedCalculateAdditionalLight 1
#endif
// all pass will share this Attributes struct (define data needed from Unity app to our vertex shader)
// TODO: optimize struct size for each pass. There maybe some pass that don't need some attributes
struct Attributes
{
float3 positionOS : POSITION; // vertex position in object space
float3 normalOS : NORMAL; // GetVertexNormalInputs(...) expect float3 normalOS input, don't write half3 to produce unneeded type conversion cost
float4 tangentOS : TANGENT; // GetVertexNormalInputs(...) expect float4 tangentOS input, don't write half4 to produce unneeded type conversion cost
float2 uv : TEXCOORD0; // the first uv (UV#0)
float2 uv2 : TEXCOORD1;
float2 uv3 : TEXCOORD2;
float2 uv4 : TEXCOORD3;
float3 uv8 : TEXCOORD7; // generated by NiloToon's editor script, store angle smoothed tangent space normal in mesh uv8((TEXCOORD7)), for improving outline and shadowmap normal bias
float4 color : COLOR; // vertex color, used for outline width mask, not as a color, so use float not half
uint vertexID : SV_VertexID; // SV_VertexID needs to be uint (https://docs.unity3d.com/Manual/SL-ShaderSemantics.html)
// to support GPU instancing and Single Pass Stereo rendering(VR), add the following section
//------------------------------------------------------------------------------------------------------------------------------
UNITY_VERTEX_INPUT_INSTANCE_ID // For non PSSL, equals to -> uint instanceID : SV_InstanceID;
//------------------------------------------------------------------------------------------------------------------------------
};
// all pass will share this Varyings struct (define data needed from vertex shader to fragment shader)
// Note: once a field is written here, no matter fragment shader use it or not
// you will pay for the rasterization interpolation cost,
// compiler can't help you in this case, you can check Unity's compiled shader code to confirm.
// so using #if to remove a field in this "v2f" Varyings struct is a meaningful optimization here
// Also, try to pack data into a single vec4 TEXCOORD
// "Pack values together as all varyings have four components, whether they are used or not. Putting two vec2 texture coordinates into a single vec4 value is a common practice"
// see https://developer.qualcomm.com/sites/default/files/docs/adreno-gpu/snapdragon-game-toolkit/gdg/gpu/best_practices_shaders.html#pack-shader-interpolators
struct Varyings
{
float4 positionCS : SV_POSITION;
float4 uv01 : TEXCOORD0; // need float for uv (half is not enough), if texture size is larger than 2048, half is not enough (https://forum.unity.com/threads/why-does-unity-recommend-us-to-use-float-type-for-texture-coordinates-in-the-shader.732920/#post-4894619)
float4 uv23 : TEXCOORD1;
float4 positionWS_ZOffsetFinalSum : TEXCOORD2; // xyz = positionWS, w = ZOffsetFinalSum
half4 SH_fogFactor : TEXCOORD3; // (4 half pack into 1 TEXCOORD) xyz: SampleSH(normalWS) * multipliers, w: fog factor
float4 normalWS_averageShadowAttenuation : TEXCOORD4; // (4 float pack into 1 TEXCOORD) xyz: normalWS, w: averageShadowAttenuation. URP14 LitForwardPass.hlsl define normalWS as float3 instead of half3, so we follow it.
float3 smoothedNormalWS : TEXCOORD5;
#if VaryingsHasViewDirTS
half3 viewDirTS : TEXCOORD6; // URP14 LitForwardPass.hlsl's viewDirTS is half3, we follow it.
#endif
#if VaryingsHasTangentWS
half4 tangentWS : TANGENT; // xyz: tangent, w: sign. (TODO: is TANGENT safe to use? In URP's LitForwardPass.hlsl, it is TEXCOORD, not TANGENT.)
#endif
half4 color : COLOR; // vertex color rgba, currently only for "control depth texture rimlight and shadow width by vertex color" or "debug shading"
// Note: unlike URP's LitForwardPass.hlsl, NiloToon didn't have
// float4 shadowCoord
// shadowCoord in NiloToon is always calculated in fragment shader
// debug use
//--------------------------------------------------------------------
#if _NILOTOON_DEBUG_SHADING
float3 uv8 : TEXCOORD7;
#endif
//--------------------------------------------------------------------
// to support GPU instancing and Single Pass Stereo rendering(VR), add the following section
//------------------------------------------------------------------------------------------------------------------------------
// see ShadowCasterPass.hlsl for why these lines are removed for NiloToonCharSelfShadowCasterPass
#if !NiloToonCharSelfShadowCasterPass
UNITY_VERTEX_INPUT_INSTANCE_ID // For non PSSL, equals to -> uint instanceID : SV_InstanceID;
UNITY_VERTEX_OUTPUT_STEREO // For non OpenGL and non PSSL, equals to -> uint stereoTargetEyeIndexAsRTArrayIdx : SV_RenderTargetArrayIndex; (when UNITY_STEREO_INSTANCING_ENABLED)
#endif
//------------------------------------------------------------------------------------------------------------------------------
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// local(per material) samplers and textures
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// note(1):
// all sampler2D, SAMPLER and TEXTURE2D do not need to put inside UnityPerMaterial CBUFFER{}
// note(2):
// > how to avoid [sampler max count = 16] limit(error message: Shader error in 'X': maximum ps_5_0 sampler register index (16) exceeded)?
// Everytime you write a sampler2D/SAMPLER, the sampler count for that shader will +1, when the sampler count reach the hardware limit,
// the shader will not be able to compile.
// Usually the sampler count limit is 16 for PC, 8(can be more for some devices?) for mobile.
// The solution to avoid sampler max count limit = reuse SAMPLER in shader, don't declare new SAMPLER/sampler2D for each texture if SAMPLER setting can be reused.
// But a disadvantage of reusing sampler is, reusing sampler will prevent user to change sampler setting from each texture's texture inspector
// Useful resources:
// - https://docs.unity3d.com/Manual/SL-SamplerStates.html
// - URP's TerrainLitPasses.hlsl -> SplatmapMix(...), you will see 4 different splat textures were sampled by 1 sampler(sampler reused)
// - https://forum.unity.com/threads/texture-samplers-limit-per-pass.605395/
// - https://forum.unity.com/threads/sampler-count-limit-for-pixel-shader-in-shader-model-5-0.452957/
// ref: https://docs.unity3d.com/Manual/SL-SamplerStates.html
// "Just like separate texture + sampler syntax, inline sampler states are not supported on some platforms.
// Currently they are implemented on Direct3D 11/12, PS4, XboxOne and Metal."
// *Note:
// We tested the above statement on some android mobile devices (GLES / Vulkan),
// we found that inline sampler is OK to use even GLES/Vulkan are not listed in supported platforms,
// so we will use inline sampler to help reducing sampler count.
// [sampler_linear_clamp, this sampler will be shared by some texture read if sampler state is linear & clamp]
// (see: https://docs.unity3d.com/Manual/SL-SamplerStates.html)
// use cases:
// - force linear clamp sampler to prevent ramp lighting / ramp specular sampling out of bound pixels of ramp texture using repeat wrap mode
SAMPLER(sampler_linear_clamp);
// [sampler_linear_repeat, this sampler will be shared by some texture read if sampler state is linear & repeat]
// (see: https://docs.unity3d.com/Manual/SL-SamplerStates.html)
// use cases:
// - force half of the base stacking layer sharing sampler_linear_repeat to prevent [sampler max count = 16] limit
SAMPLER(sampler_linear_repeat);
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
#if _ALPHAOVERRIDEMAP
sampler2D _AlphaOverrideTex;
#endif
#if _PARALLAXMAP
TEXTURE2D(_ParallaxMap); SAMPLER(sampler_ParallaxMap);
#endif
#if _BASEMAP_STACKING_LAYER1
// only the first 2 layers use it's own sampler, to allow more control for user
TEXTURE2D(_BaseMapStackingLayer1Tex); SAMPLER(sampler_BaseMapStackingLayer1Tex);
TEXTURE2D(_BaseMapStackingLayer1MaskTex);
#endif
#if _BASEMAP_STACKING_LAYER2
// only the first 2 layers use it's own sampler, to allow more control for user
TEXTURE2D(_BaseMapStackingLayer2Tex); SAMPLER(sampler_BaseMapStackingLayer2Tex);
TEXTURE2D(_BaseMapStackingLayer2MaskTex);
#endif
#if _BASEMAP_STACKING_LAYER3
TEXTURE2D(_BaseMapStackingLayer3Tex);
TEXTURE2D(_BaseMapStackingLayer3MaskTex);
#endif
#if _BASEMAP_STACKING_LAYER4
TEXTURE2D(_BaseMapStackingLayer4Tex);
TEXTURE2D(_BaseMapStackingLayer4MaskTex);
#endif
#if _BASEMAP_STACKING_LAYER5
TEXTURE2D(_BaseMapStackingLayer5Tex);
TEXTURE2D(_BaseMapStackingLayer5MaskTex);
#endif
#if _BASEMAP_STACKING_LAYER6
TEXTURE2D(_BaseMapStackingLayer6Tex);
TEXTURE2D(_BaseMapStackingLayer6MaskTex);
#endif
#if _BASEMAP_STACKING_LAYER7
TEXTURE2D(_BaseMapStackingLayer7Tex);
TEXTURE2D(_BaseMapStackingLayer7MaskTex);
#endif
#if _BASEMAP_STACKING_LAYER8
TEXTURE2D(_BaseMapStackingLayer8Tex);
TEXTURE2D(_BaseMapStackingLayer8MaskTex);
#endif
#if _BASEMAP_STACKING_LAYER9
TEXTURE2D(_BaseMapStackingLayer9Tex);
TEXTURE2D(_BaseMapStackingLayer9MaskTex);
#endif
#if _BASEMAP_STACKING_LAYER10
TEXTURE2D(_BaseMapStackingLayer10Tex);
TEXTURE2D(_BaseMapStackingLayer10MaskTex);
#endif
#if _NORMALMAP
sampler2D _BumpMap;
#endif
#if _EMISSION
sampler2D _EmissionMap;
TEXTURE2D(_EmissionMaskMap);
#if _EMISSION_ANIM_TINT_RAMPMAP
TEXTURE2D(_EmissionAnimTintRampMap);
#endif
#endif
#if _ENVIRONMENTREFLECTIONS
TEXTURE2D(_EnvironmentReflectionMaskMap);
#endif
#if _MATCAP_BLEND
sampler2D _MatCapAlphaBlendMap;
TEXTURE2D(_MatCapAlphaBlendMaskMap);
#endif
#if _MATCAP_ADD
sampler2D _MatCapAdditiveMap;
TEXTURE2D(_MatCapAdditiveMaskMap);
#endif
#if _MATCAP_OCCLUSION
sampler2D _MatCapOcclusionMap;
TEXTURE2D(_MatCapOcclusionMaskMap);
#endif
#if _RAMP_LIGHTING
TEXTURE2D(_DynamicRampLightingTex);
TEXTURE2D(_RampLightingTex);
#endif
#if _RAMP_LIGHTING_SAMPLE_UVY_TEX
sampler2D _RampLightingSampleUvYTex;
#endif
#if _RAMP_SPECULAR
TEXTURE2D(_RampSpecularTex);
#endif
#if _RAMP_SPECULAR_SAMPLE_UVY_TEX
sampler2D _RampSpecularSampleUvYTex;
#endif
#if _OCCLUSIONMAP
sampler2D _OcclusionMap;
#endif
#if _SHADING_GRADEMAP
sampler2D _ShadingGradeMap;
#endif
#if _SMOOTHNESSMAP
sampler2D _SmoothnessMap;
#endif
#if _SPECULARHIGHLIGHTS
sampler2D _SpecularMap;
#if _SPECULARHIGHLIGHTS_TEX_TINT
sampler2D _SpecularColorTintMap;
#endif
#endif
#if _NILOGLITTER
sampler2D _NiloGlitterTintColorTex;
#endif
#if _KAJIYAKAY_SPECULAR
#if _KAJIYAKAY_SPECULAR_TEX_TINT
sampler2D _HairStrandSpecularTintMap;
#endif
#endif
#if _ZOFFSETMAP
sampler2D _ZOffsetMaskTex;
#endif
#if _OUTLINEWIDTHMAP
sampler2D _OutlineWidthTex;
#endif
#if _OUTLINETINTCOLORMAP
sampler2D _OutlineTintColorMap;
#endif
#if _OUTLINEZOFFSETMAP
sampler2D _OutlineZOffsetMaskTex;
#endif
#if NeedFaceMaskArea
sampler2D _FaceMaskMap;
#endif
#if _SKIN_MASK_ON
TEXTURE2D(_SkinMaskMap);
#endif
#if _DYNAMIC_EYE
sampler2D _DynamicEyePupilMap;
sampler2D _DynamicEyePupilMaskTex;
sampler2D _DynamicEyeWhiteMap;
#endif
#if _DETAIL
TEXTURE2D(_DetailMask); SAMPLER(sampler_DetailMask);
TEXTURE2D(_DetailAlbedoMap); SAMPLER(sampler_DetailAlbedoMap);
TEXTURE2D(_DetailNormalMap); SAMPLER(sampler_DetailNormalMap);
#endif
#if _OVERRIDE_SHADOWCOLOR_BY_TEXTURE
sampler2D _OverrideShadowColorTex;
sampler2D _OverrideShadowColorMaskMap;
#endif
#if _OVERRIDE_OUTLINECOLOR_BY_TEXTURE
sampler2D _OverrideOutlineColorTex;
#endif
#if _SCREENSPACE_OUTLINE
sampler2D _ScreenSpaceOutlineDepthSensitivityTex;
sampler2D _ScreenSpaceOutlineNormalsSensitivityTex;
#endif
#if _DEPTHTEX_RIMLIGHT_SHADOW_WIDTHMAP
sampler2D _DepthTexRimLightAndShadowWidthTex;
#endif
#if _DEPTHTEX_RIMLIGHT_OPACITY_MASKMAP
sampler2D _DepthTexRimLightMaskTex;
#endif
#if _NILOTOON_SELFSHADOW_INTENSITY_MAP
sampler2D _NiloToonSelfShadowIntensityMultiplierTex;
#endif
#if _FACE_SHADOW_GRADIENTMAP
sampler2D _FaceShadowGradientMap;
sampler2D _FaceShadowGradientMaskMap;
#endif
#if _FACE_3D_RIMLIGHT_AND_SHADOW
sampler2D _Face3DRimLightAndShadow_NoseRimLightMaskMap;
sampler2D _Face3DRimLightAndShadow_NoseShadowMaskMap;
sampler2D _Face3DRimLightAndShadow_CheekRimLightMaskMap;
sampler2D _Face3DRimLightAndShadow_CheekShadowMaskMap;
#endif
#if _NILOTOON_DISSOLVE
sampler2D _DissolveThresholdMap;
#endif
#if _PER_MAT_DISSOLVE_ON
sampler2D _PerMaterialDissolveThresholdMap;
sampler2D _PerMaterialDissolvePatternMap;
#endif
#if _NILOTOON_PERCHARACTER_BASEMAP_OVERRIDE
TEXTURE2D(_PerCharacterBaseMapOverrideMap);
#endif
#if NiloToonCharacterAreaColorFillPass
TEXTURE2D(_CharacterAreaColorFillTexture);
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NiloToon's global textures
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if NiloToonIsAnyLitColorPass
TEXTURE2D(_NiloToonAverageShadowMapRT);
#endif
#if _NILOTOON_RECEIVE_SELF_SHADOW
TEXTURE2D(_NiloToonCharSelfShadowMapRT);
SAMPLER_CMP(sampler_NiloToonCharSelfShadowMapRT_LinearClampCompare); // need LinearClampCompare, else Unity6 will sample the shadow map in point filter
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CBUFFER (material local Uniforms)
// *you should put all per material uniforms of ALL passes inside the following UnityPerMaterial CBUFFER!
// else SRP batching is not possible!
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Material shader variables are not defined in SRP or URP shader library.
// This means _BaseColor, _BaseMap, _BaseMap_ST, and all variables in the Properties section of a shader
// must be defined by the shader itself. If you define all those properties in CBUFFER named
// UnityPerMaterial, SRP can cache the material properties between frames and reduce significantly the cost
// of each drawcall.
// In this case, although URP's LitInput.hlsl contains the CBUFFER for some of the material
// properties defined in .shader file. As one can see this is not part of the ShaderLibrary, it specific to the
// URP Lit shader.
// So we are not going to use LitInput.hlsl, we will implement everything by ourself.
// #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl" (we don't use it)
// put all your uniforms(usually things inside .shader file's properties{}) inside this CBUFFER, in order to make SRP batcher compatible
// see -> https://blogs.unity3d.com/2019/02/28/srp-batcher-speed-up-your-rendering/
// IMPORTANT NOTE: Do not #ifdef #endif the properties inside CBUFFER as SRP batcher can NOT handle different CBUFFER layouts.
// In order to be SRP Batcher compatible,
// the "UnityPerMaterial" cbuffer has to have the exact same size and layout across all variants of one subshader.
// Because in SRP Batcher, UnityPerMaterial data are persistent in GPU memory (so we don't want to have tons of different layout to update in GPU memory ).
// Just use the same declaration for this cbuffer. It won't hurt performance because we won't upload the data to GPU per drawcall
// (won't hurt performance as long as material properties are not changing between frames, else a CBUFFER re upload to GPU is needed once any material properties changed,
// since this CBUFFER is huge, the cost of editing material properties is huge!)
// https://forum.unity.com/threads/cbuffer-inconsistent-size-inside-a-subshader.784994/#post-5725762
// IMPORTANT NOTE:
// [Vulkan SRP Batching note]
// Due to constant buffer(UnityPerMaterial CBUFFER) size limitation of vulkan (only in some android Mali GPU devices),
// if we make the (UnityPerMaterial CBUFFER) size too large, character rendering will be completely wrong(disappear / render as random color).
// We hardcode removed SRP batching on VULKAN for now (using macro SHADER_API_VULKAN), until we find a new solution.
// buggy devices list:
// - (vivo Y73s) (vivo Y72) (TAS_AN00) (mate40) (Huawei Honor 9x), all are Mali-G__ GPU + vulkan.
// safe devices list:
// - "Adreno GPU + vulkan" is safe (Xiaomi8)(Samsung A70)(RedmiNote9Pro)
// - "OpenGLES + any GPU" is safe also
// *Split a big CBUFFER into 2 CBUFFER(UnityPerMaterial, UnityPerMaterialTwo) doesn't work
#ifndef SHADER_API_VULKAN
CBUFFER_START(UnityPerMaterial)
#endif
float _RenderCharacter;
// identity if material is currently controlled by NiloToonPerCharacterRenderController script
float _ControlledByNiloToonPerCharacterRenderController;
// enable rendering
float _EnableRendering;
// UV Control
float _EnableUVEditGroup;
float4 _UV0ScaleOffset;
float4 _UV1ScaleOffset;
float4 _UV2ScaleOffset;
float4 _UV3ScaleOffset;
float4 _UV0CenterPivotScalePos;
float4 _UV1CenterPivotScalePos;
float4 _UV2CenterPivotScalePos;
float4 _UV3CenterPivotScalePos;
float2 _UV0ScrollSpeed;
float2 _UV1ScrollSpeed;
float2 _UV2ScrollSpeed;
float2 _UV3ScrollSpeed;
float _UV0RotatedAngle;
float _UV1RotatedAngle;
float _UV2RotatedAngle;
float _UV3RotatedAngle;
float _UV0RotateSpeed;
float _UV1RotateSpeed;
float _UV2RotateSpeed;
float _UV3RotateSpeed;
float2 _MatCapUVTiling;
// base color
float4 _BaseMap_ST;
float _BaseMapUVIndex;
half4 _BaseColor;
half4 _BaseColor2;
half _BaseMapBrightness;
half3 _PerCharacterBaseColorTint;
half _MultiplyBRPColor;
half4 _Color; // BRP _Color
// back face tint color, force shadow
half4 _BackFaceBaseMapReplaceColor;
half3 _BackFaceTintColor;
half _BackFaceForceShadow;
// alpha override
half _AlphaOverrideStrength;
half _AlphaOverrideMode;
float _AlphaOverrideTexUVIndex;
half _AlphaOverrideTexInvertColor;
half _AlphaOverrideTexValueScale;
half _AlphaOverrideTexValueOffset;
half _ApplyAlphaOverrideOnlyWhenFaceForwardIsPointingToCamera;
half _ApplyAlphaOverrideOnlyWhenFaceForwardIsPointingToCameraRemapStart;
half _ApplyAlphaOverrideOnlyWhenFaceForwardIsPointingToCameraRemapEnd;
half4 _AlphaOverrideTexChannelMask;
// alpha test
half _Cutoff;
// fina output alpha
half _EditFinalOutputAlphaEnable;
half _ForceFinalOutputAlphaEqualsOne;
// z offset
float _ZOffsetEnable;
float _ZOffset;
float _ZOffsetMultiplierForTraditionalOutlinePass;
float4 _ZOffsetMaskMapChannelMask;
float _ZOffsetMaskMapInvertColor;
// Parallax
half _Parallax;
float _ParallaxSampleUVIndex;
uint _ParallaxApplyToUVIndex;
// BaseMap Alpha Blending Layer (1-10)
half _BaseMapStackingLayer1MasterStrength;
float _BaseMapStackingLayer1TexUVIndex;
half4 _BaseMapStackingLayer1TintColor;
half _BaseMapStackingLayer1TexIgnoreAlpha;
float2 _BaseMapStackingLayer1TexUVAnimSpeed;
float4 _BaseMapStackingLayer1TexUVScaleOffset;
float4 _BaseMapStackingLayer1TexUVCenterPivotScalePos;
half4 _BaseMapStackingLayer1MaskTexChannel;
float _BaseMapStackingLayer1ApplytoFaces;
uint _BaseMapStackingLayer1MaskUVIndex;
half _BaseMapStackingLayer1MaskTexAsIDMap;
half _BaseMapStackingLayer1MaskTexExtractFromID;
float _BaseMapStackingLayer1MaskInvertColor;
half _BaseMapStackingLayer1MaskRemapStart;
half _BaseMapStackingLayer1MaskRemapEnd;
uint _BaseMapStackingLayer1ColorBlendMode;
float _BaseMapStackingLayer1TexUVRotatedAngle;
float _BaseMapStackingLayer1TexUVRotateSpeed;
half _BaseMapStackingLayer2MasterStrength;
float _BaseMapStackingLayer2TexUVIndex;
half4 _BaseMapStackingLayer2TintColor;
half _BaseMapStackingLayer2TexIgnoreAlpha;
float2 _BaseMapStackingLayer2TexUVAnimSpeed;
float4 _BaseMapStackingLayer2TexUVScaleOffset;
float4 _BaseMapStackingLayer2TexUVCenterPivotScalePos;
half4 _BaseMapStackingLayer2MaskTexChannel;
float _BaseMapStackingLayer2ApplytoFaces;
uint _BaseMapStackingLayer2MaskUVIndex;
half _BaseMapStackingLayer2MaskTexAsIDMap;
half _BaseMapStackingLayer2MaskTexExtractFromID;
float _BaseMapStackingLayer2MaskInvertColor;
half _BaseMapStackingLayer2MaskRemapStart;
half _BaseMapStackingLayer2MaskRemapEnd;
uint _BaseMapStackingLayer2ColorBlendMode;
float _BaseMapStackingLayer2TexUVRotatedAngle;
float _BaseMapStackingLayer2TexUVRotateSpeed;
half _BaseMapStackingLayer3MasterStrength;
float _BaseMapStackingLayer3TexUVIndex;
half4 _BaseMapStackingLayer3TintColor;
half _BaseMapStackingLayer3TexIgnoreAlpha;
float2 _BaseMapStackingLayer3TexUVAnimSpeed;
float4 _BaseMapStackingLayer3TexUVScaleOffset;
float4 _BaseMapStackingLayer3TexUVCenterPivotScalePos;
half4 _BaseMapStackingLayer3MaskTexChannel;
float _BaseMapStackingLayer3ApplytoFaces;
uint _BaseMapStackingLayer3MaskUVIndex;
half _BaseMapStackingLayer3MaskTexAsIDMap;
half _BaseMapStackingLayer3MaskTexExtractFromID;
float _BaseMapStackingLayer3MaskInvertColor;
half _BaseMapStackingLayer3MaskRemapStart;
half _BaseMapStackingLayer3MaskRemapEnd;
uint _BaseMapStackingLayer3ColorBlendMode;
float _BaseMapStackingLayer3TexUVRotatedAngle;
float _BaseMapStackingLayer3TexUVRotateSpeed;
half _BaseMapStackingLayer4MasterStrength;
float _BaseMapStackingLayer4TexUVIndex;
half4 _BaseMapStackingLayer4TintColor;
half _BaseMapStackingLayer4TexIgnoreAlpha;
float2 _BaseMapStackingLayer4TexUVAnimSpeed;
float4 _BaseMapStackingLayer4TexUVScaleOffset;
float4 _BaseMapStackingLayer4TexUVCenterPivotScalePos;
half4 _BaseMapStackingLayer4MaskTexChannel;
float _BaseMapStackingLayer4ApplytoFaces;
uint _BaseMapStackingLayer4MaskUVIndex;
half _BaseMapStackingLayer4MaskTexAsIDMap;
half _BaseMapStackingLayer4MaskTexExtractFromID;
float _BaseMapStackingLayer4MaskInvertColor;
half _BaseMapStackingLayer4MaskRemapStart;
half _BaseMapStackingLayer4MaskRemapEnd;
uint _BaseMapStackingLayer4ColorBlendMode;
float _BaseMapStackingLayer4TexUVRotatedAngle;
float _BaseMapStackingLayer4TexUVRotateSpeed;
half _BaseMapStackingLayer5MasterStrength;
float _BaseMapStackingLayer5TexUVIndex;
half4 _BaseMapStackingLayer5TintColor;
half _BaseMapStackingLayer5TexIgnoreAlpha;
float2 _BaseMapStackingLayer5TexUVAnimSpeed;
float4 _BaseMapStackingLayer5TexUVScaleOffset;
float4 _BaseMapStackingLayer5TexUVCenterPivotScalePos;
half4 _BaseMapStackingLayer5MaskTexChannel;
float _BaseMapStackingLayer5ApplytoFaces;
uint _BaseMapStackingLayer5MaskUVIndex;
half _BaseMapStackingLayer5MaskTexAsIDMap;
half _BaseMapStackingLayer5MaskTexExtractFromID;
float _BaseMapStackingLayer5MaskInvertColor;
half _BaseMapStackingLayer5MaskRemapStart;
half _BaseMapStackingLayer5MaskRemapEnd;
uint _BaseMapStackingLayer5ColorBlendMode;
float _BaseMapStackingLayer5TexUVRotatedAngle;
float _BaseMapStackingLayer5TexUVRotateSpeed;
half _BaseMapStackingLayer6MasterStrength;
float _BaseMapStackingLayer6TexUVIndex;
half4 _BaseMapStackingLayer6TintColor;
half _BaseMapStackingLayer6TexIgnoreAlpha;
float2 _BaseMapStackingLayer6TexUVAnimSpeed;
float4 _BaseMapStackingLayer6TexUVScaleOffset;
float4 _BaseMapStackingLayer6TexUVCenterPivotScalePos;
half4 _BaseMapStackingLayer6MaskTexChannel;
float _BaseMapStackingLayer6ApplytoFaces;
uint _BaseMapStackingLayer6MaskUVIndex;
half _BaseMapStackingLayer6MaskTexAsIDMap;
half _BaseMapStackingLayer6MaskTexExtractFromID;
float _BaseMapStackingLayer6MaskInvertColor;
half _BaseMapStackingLayer6MaskRemapStart;
half _BaseMapStackingLayer6MaskRemapEnd;
uint _BaseMapStackingLayer6ColorBlendMode;
float _BaseMapStackingLayer6TexUVRotatedAngle;
float _BaseMapStackingLayer6TexUVRotateSpeed;
half _BaseMapStackingLayer7MasterStrength;
float _BaseMapStackingLayer7TexUVIndex;
half4 _BaseMapStackingLayer7TintColor;
half _BaseMapStackingLayer7TexIgnoreAlpha;
float2 _BaseMapStackingLayer7TexUVAnimSpeed;
float4 _BaseMapStackingLayer7TexUVScaleOffset;
float4 _BaseMapStackingLayer7TexUVCenterPivotScalePos;
half4 _BaseMapStackingLayer7MaskTexChannel;
float _BaseMapStackingLayer7ApplytoFaces;
uint _BaseMapStackingLayer7MaskUVIndex;
half _BaseMapStackingLayer7MaskTexAsIDMap;
half _BaseMapStackingLayer7MaskTexExtractFromID;
float _BaseMapStackingLayer7MaskInvertColor;
half _BaseMapStackingLayer7MaskRemapStart;
half _BaseMapStackingLayer7MaskRemapEnd;
uint _BaseMapStackingLayer7ColorBlendMode;
float _BaseMapStackingLayer7TexUVRotatedAngle;
float _BaseMapStackingLayer7TexUVRotateSpeed;
half _BaseMapStackingLayer8MasterStrength;
float _BaseMapStackingLayer8TexUVIndex;
half4 _BaseMapStackingLayer8TintColor;
half _BaseMapStackingLayer8TexIgnoreAlpha;
float2 _BaseMapStackingLayer8TexUVAnimSpeed;
float4 _BaseMapStackingLayer8TexUVScaleOffset;
float4 _BaseMapStackingLayer8TexUVCenterPivotScalePos;
half4 _BaseMapStackingLayer8MaskTexChannel;
float _BaseMapStackingLayer8ApplytoFaces;
uint _BaseMapStackingLayer8MaskUVIndex;
half _BaseMapStackingLayer8MaskTexAsIDMap;
half _BaseMapStackingLayer8MaskTexExtractFromID;
float _BaseMapStackingLayer8MaskInvertColor;
half _BaseMapStackingLayer8MaskRemapStart;
half _BaseMapStackingLayer8MaskRemapEnd;
uint _BaseMapStackingLayer8ColorBlendMode;
float _BaseMapStackingLayer8TexUVRotatedAngle;
float _BaseMapStackingLayer8TexUVRotateSpeed;
half _BaseMapStackingLayer9MasterStrength;
float _BaseMapStackingLayer9TexUVIndex;
half4 _BaseMapStackingLayer9TintColor;
half _BaseMapStackingLayer9TexIgnoreAlpha;
float2 _BaseMapStackingLayer9TexUVAnimSpeed;
float4 _BaseMapStackingLayer9TexUVScaleOffset;
float4 _BaseMapStackingLayer9TexUVCenterPivotScalePos;
half4 _BaseMapStackingLayer9MaskTexChannel;
float _BaseMapStackingLayer9ApplytoFaces;
uint _BaseMapStackingLayer9MaskUVIndex;
half _BaseMapStackingLayer9MaskTexAsIDMap;
half _BaseMapStackingLayer9MaskTexExtractFromID;
float _BaseMapStackingLayer9MaskInvertColor;
half _BaseMapStackingLayer9MaskRemapStart;
half _BaseMapStackingLayer9MaskRemapEnd;
uint _BaseMapStackingLayer9ColorBlendMode;
float _BaseMapStackingLayer9TexUVRotatedAngle;
float _BaseMapStackingLayer9TexUVRotateSpeed;
half _BaseMapStackingLayer10MasterStrength;
float _BaseMapStackingLayer10TexUVIndex;
half4 _BaseMapStackingLayer10TintColor;
half _BaseMapStackingLayer10TexIgnoreAlpha;
float2 _BaseMapStackingLayer10TexUVAnimSpeed;
float4 _BaseMapStackingLayer10TexUVScaleOffset;
float4 _BaseMapStackingLayer10TexUVCenterPivotScalePos;
half4 _BaseMapStackingLayer10MaskTexChannel;
float _BaseMapStackingLayer10ApplytoFaces;
uint _BaseMapStackingLayer10MaskUVIndex;
half _BaseMapStackingLayer10MaskTexAsIDMap;
half _BaseMapStackingLayer10MaskTexExtractFromID;
float _BaseMapStackingLayer10MaskInvertColor;
half _BaseMapStackingLayer10MaskRemapStart;
half _BaseMapStackingLayer10MaskRemapEnd;
uint _BaseMapStackingLayer10ColorBlendMode;
float _BaseMapStackingLayer10TexUVRotatedAngle;
float _BaseMapStackingLayer10TexUVRotateSpeed;
// normal map
half _BumpScale;
float _BumpMapApplytoFaces;
float _BumpMapUVIndex;
float4 _BumpMapUVScaleOffset;
float2 _BumpMapUVScrollSpeed;
// emission
float4 _EmissionMapTilingXyOffsetZw;
float2 _EmissionMapUVScrollSpeed;
half _EmissionIntensity;
half3 _EmissionColor;
half _MultiplyBaseColorToEmissionColor;
half _MultiplyLightColorToEmissionColor;
float _EmissionAnimTintRampMapSpeed;
half _EmissionMapUseSingleChannelOnly;
half4 _EmissionMapSingleChannelMask;
half4 _EmissionMaskMapChannelMask;
half _EmissionMaskMapInvertColor;
half _EmissionMaskMapRemapStart;
half _EmissionMaskMapRemapEnd;
// mat cap (alpha blend)
half _MatCapAlphaBlendUsage;
half4 _MatCapAlphaBlendTintColor;
float _MatCapAlphaBlendUvScale;
half _MatCapAlphaBlendMapAlphaAsMask;
half4 _MatCapAlphaBlendMaskMapChannelMask;
half _MatCapAlphaBlendMaskMapInvertColor;
half _MatCapAlphaBlendMaskMapRemapStart;
half _MatCapAlphaBlendMaskMapRemapEnd;
// mat cap (additive)
half _MatCapAdditiveMapAlphaAsMask;
half _MatCapAdditiveIntensity;
half _MatCapAdditiveExtractBrightArea;
half4 _MatCapAdditiveColor;
half _MatCapAdditiveMixWithBaseMapColor;
float _MatCapAdditiveUvScale;
half4 _MatCapAdditiveMaskMapChannelMask;
half _MatCapAdditiveMaskMapInvertColor;
half _MatCapAdditiveMaskMapRemapStart;
half _MatCapAdditiveMaskMapRemapEnd;
float _MatCapAdditiveApplytoFaces;
// mat cap (occlusion)
half _MatCapOcclusionIntensity;
half4 _MatCapOcclusionMapChannelMask;
half _MatCapOcclusionMapRemapStart;
half _MatCapOcclusionMapRemapEnd;
half _MatCapOcclusionMapAlphaAsMask;
float _MatCapOcclusionUvScale;
half4 _MatCapOcclusionMaskMapChannelMask;
half _MatCapOcclusionMaskMapInvert;
half _MatCapOcclusionMaskMapRemapStart;
half _MatCapOcclusionMaskMapRemapEnd;
// occlusion
half _OcclusionStrength;
half _OcclusionStrengthIndirectMultiplier;
half4 _OcclusionMapChannelMask;
float _OcclusionMapUVIndex;
half _OcclusionMapInvertColor;
half _OcclusionRemapStart;
half _OcclusionRemapEnd;
float _OcclusionMapApplytoFaces;
// shading grade map
half _ShadingGradeMapStrength;
half _ShadingGradeMapApplyRange;
half _ShadingGradeMapMidPointOffset;
half4 _ShadingGradeMapChannelMask;
half _ShadingGradeMapInvertColor;
half _ShadingGradeMapRemapStart;
half _ShadingGradeMapRemapEnd;
// Face Shadow Gradient Map
half4 _FaceShadowGradientMapChannel;
float _FaceShadowGradientMapUVIndex;
half _FaceShadowGradientMapInvertColor;
half _FaceShadowGradientOffset;
float _FaceShadowGradientMapUVxInvert;
float4 _FaceShadowGradientMapUVCenterPivotScalePos;
float4 _FaceShadowGradientMapUVScaleOffset;
float _DebugFaceShadowGradientMap;
half _FaceShadowGradientIntensity;
half _FaceShadowGradientMapFaceMidPoint;
half _FaceShadowGradientResultSoftness;
half4 _FaceShadowGradientMaskMapChannel;
half _FaceShadowGradientMaskMapInvertColor;
float _FaceShadowGradientMaskMapUVIndex;
half _FaceShadowGradientThresholdMin;
half _FaceShadowGradientThresholdMax;
half _IgnoreDefaultMainLightFaceShadow;
// Face 3D Rim Light and Shadow
half _Face3DRimLightAndShadow_NoseRimLightIntensity;
half3 _Face3DRimLightAndShadow_NoseRimLightTintColor;
half4 _Face3DRimLightAndShadow_NoseRimLightMaskMapChannel;
half _Face3DRimLightAndShadow_NoseShadowIntensity;
half3 _Face3DRimLightAndShadow_NoseShadowTintColor;
half4 _Face3DRimLightAndShadow_NoseShadowMaskMapChannel;
half _Face3DRimLightAndShadow_CheekRimLightIntensity;
half3 _Face3DRimLightAndShadow_CheekRimLightTintColor;
half4 _Face3DRimLightAndShadow_CheekRimLightMaskMapChannel;
half _Face3DRimLightAndShadow_CheekRimLightThreshold;
half _Face3DRimLightAndShadow_CheekRimLightSoftness;
half _Face3DRimLightAndShadow_CheekShadowIntensity;
half3 _Face3DRimLightAndShadow_CheekShadowTintColor;
half4 _Face3DRimLightAndShadow_CheekShadowMaskMapChannel;
half _Face3DRimLightAndShadow_CheekShadowThreshold;
half _Face3DRimLightAndShadow_CheekShadowSoftness;
// smoothness
half _Smoothness;
half _SmoothnessMapInputIsRoughnessMap;
half4 _SmoothnessMapChannelMask;
half _SmoothnessMapRemapStart;
half _SmoothnessMapRemapEnd;
// specular
float _SpecularMapUVIndex;
half4 _SpecularMapChannelMask;
half _SpecularMapAsIDMap;
half _SpecularMapExtractFromID;
half _SpecularMapInvertColor;
half _SpecularMapRemapStart;
half _SpecularMapRemapEnd;
half _UseGGXDirectSpecular;
half _SpecularReactToLightDirMode;
half _SpecularIntensity;
half3 _SpecularColor;
half _SpecularUseReplaceBlending;
half _MultiplyBaseColorToSpecularColor;
half _GGXDirectSpecularSmoothnessMultiplier;
half _SpecularColorTintMapUsage;
float _SpecularColorTintMapUseSecondUv;
float4 _SpecularColorTintMapTilingXyOffsetZw;
half _SpecularAreaRemapUsage;
half _SpecularAreaRemapMidPoint;
half _SpecularAreaRemapRange;
half _SpecularShowInShadowArea;
float _SpecularApplytoFaces;
// Glitter (NiloToon)
float _NiloGlitterIntensity;
float _NiloGlitterDensity;
float _NiloGlitterSize;
float _NiloGlitterRndomNormalStrength;
half3 _NiloGlitterTintColor;
uint _NiloGlitterUVIndex;
uint _NiloGlitterTintColorTexUVIndex;
// Environment Reflections
half _EnvironmentReflectionUsage;
half _EnvironmentReflectionShouldApplyToFaceArea;
half _EnvironmentReflectionBrightness;
half3 _EnvironmentReflectionColor;
half _EnvironmentReflectionTintAlbedo;
half _EnvironmentReflectionApplyReplaceBlending;
half _EnvironmentReflectionApplyAddBlending;
half _EnvironmentReflectionSmoothnessMultiplier;
half _EnvironmentReflectionFresnelEffect;
half _EnvironmentReflectionFresnelPower;
half _EnvironmentReflectionFresnelRemapStart;
half _EnvironmentReflectionFresnelRemapEnd;
half4 _EnvironmentReflectionMaskMapChannelMask;
half _EnvironmentReflectionMaskMapInvertColor;
half _EnvironmentReflectionMaskMapRemapStart;
half _EnvironmentReflectionMaskMapRemapEnd;
float _EnvironmentReflectionApplytoFaces;
// kayjiya-kay hair specular
half _HairStrandSpecularMixWithBaseMapColor;
half _HairStrandSpecularTintMapUsage;
float4 _HairStrandSpecularTintMapTilingXyOffsetZw;
half _HairStrandSpecularOverallIntensity;
half _HairStrandSpecularShapeFrequency;
half _HairStrandSpecularShapeShift;
half _HairStrandSpecularShapePositionOffset;
half _HairStrandSpecularMainIntensity;
half _HairStrandSpecularSecondIntensity;
half3 _HairStrandSpecularMainColor;
half3 _HairStrandSpecularSecondColor;
half _HairStrandSpecularMainExponent;
half _HairStrandSpecularSecondExponent;
uint _HairStrandSpecularUVIndex;
uint _HairStrandSpecularUVDirection;
// detail map
float _DetailUseSecondUv;
half4 _DetailMaskChannelMask;
half _DetailMaskInvertColor;
float4 _DetailMapsScaleTiling;
half _DetailAlbedoWhitePoint;
half _DetailAlbedoMapScale;
half _DetailNormalMapScale;
// shadow color
half _EnableShadowColor;
half _SelfShadowAreaHSVStrength;
half _SelfShadowAreaHueOffset;
half _SelfShadowAreaSaturationBoost;
half _SelfShadowAreaValueMul;
half3 _SelfShadowTintColor;
half _LitToShadowTransitionAreaIntensity;
half _LitToShadowTransitionAreaHueOffset;
half _LitToShadowTransitionAreaSaturationBoost;
half _LitToShadowTransitionAreaValueMul;
half3 _LitToShadowTransitionAreaTintColor;
half4 _LowSaturationFallbackColor;
half _OverrideBySkinShadowTintColor;
half3 _SkinShadowTintColor;
half3 _SkinShadowTintColor2;
half _SkinShadowBrightness;
half _OverrideByFaceShadowTintColor;
half3 _FaceShadowTintColor;
half3 _FaceShadowTintColor2;
half _FaceShadowBrightness;
// override shadow color by tex
half _OverrideShadowColorByTexMode;
half _OverrideShadowColorByTexIntensity;
half4 _OverrideShadowColorTexTintColor;
half _OverrideShadowColorTexIgnoreAlphaChannel;
half4 _OverrideShadowColorMaskMapChannelMask;
half _OverrideShadowColorMaskMapInvertColor;
// ramp lighting texture
float _RampLightTexMode;
half _RampLightingTexSampleUvY;
half _RampLightingNdotLRemapStart;
half _RampLightingNdotLRemapEnd;
half4 _RampLightingSampleUvYTexChannelMask;
half _RampLightingSampleUvYTexInvertColor;
half _RampLightingUvYRemapStart;
half _RampLightingUvYRemapEnd;
half _RampLightingFaceAreaRemoveEffect;
// ramp specular texture
half _RampSpecularTexSampleUvY;
half _RampSpecularWhitePoint;
// lighting style
half _AsUnlit;
half _CelShadeMidPoint;
half _CelShadeSoftness;
half _MainLightIgnoreCelShade;
half _MainLightSkinDiffuseNormalMapStrength;
half _MainLightNonSkinDiffuseNormalMapStrength;
half _IndirectLightFlatten;
half _AdditionalLightCelShadeMidPoint;
half _AdditionalLightCelShadeSoftness;
half _AdditionalLightIgnoreCelShade;
half _AdditionalLightIgnoreOcclusion;
half _AdditionalLightDistanceAttenuationClamp;
// lighting style for face area
float _OverrideCelShadeParamForFaceArea;
half _CelShadeMidPointForFaceArea;
half _CelShadeSoftnessForFaceArea;
half _MainLightIgnoreCelShadeForFaceArea;
float _OverrideAdditionalLightCelShadeParamForFaceArea;
half _AdditionalLightCelShadeMidPointForFaceArea;
half _AdditionalLightCelShadeSoftnessForFaceArea;
half _AdditionalLightIgnoreCelShadeForFaceArea;
// URP shadow mapping (directional light)
float _ReceiveURPShadowMapping; // on/off toggle per material
half _ReceiveURPShadowMappingAmount;
half _ReceiveURPShadowMappingAmountForFace;
half _ReceiveURPShadowMappingAmountForNonFace;
float _ReceiveSelfShadowMappingPosOffset;
float _ReceiveSelfShadowMappingPosOffsetForFaceArea;
half3 _URPShadowMappingTintColor;
// URP shadow mapping (additional light)
half _ReceiveURPAdditionalLightShadowMapping;
half _ReceiveURPAdditionalLightShadowMappingAmount;
half _ReceiveURPAdditionalLightShadowMappingAmountForNonFace;
half _ReceiveURPAdditionalLightShadowMappingAmountForFace;
// depth texture rim light and shadow
float _PerMaterialEnableDepthTextureRimLightAndShadow;
float _DepthTexRimLightAndShadowWidthMultiplier;
float _DepthTexRimLight3DRimMaskEnable;
float _DepthTexRimLight3DRimMaskThreshold;
float _DepthTexRimLightAndShadowWidthExtraMultiplier;
float _DepthTexRimLightAndShadowReduceWidthWhenCameraIsClose;
float _DepthTexRimLightAndShadowSafeViewDistance;
float _DepthTexRimLightIgnoreLightDir;
float _DepthTexShadowFixedDirectionForFace;
float _DepthTexRimLightWidthMultiplier;
float _DepthTexRimLightWidthClampForFace;
float _DepthTexShadowWidthMultiplier;
float _UseDepthTexRimLightAndShadowWidthMultiplierFromVertexColor;
float4 _DepthTexRimLightAndShadowWidthMultiplierFromVertexColorChannelMask;
float4 _DepthTexRimLightAndShadowWidthTexChannelMask;
half _DepthTexRimLightUsage;
half4 _DepthTexRimLightMaskTexChannelMask;
half _DepthTexRimLightMaskTexInvertColor;
half _DepthTexRimLightIntensity;
half3 _DepthTexRimLightTintColor;
half _DepthTexRimLightMixWithBaseMapColor;
half _DepthTexRimLightBlockByShadow;
float _DepthTexRimLightThresholdOffset;
float _DepthTexRimLightMinimumThresholdOffsetForFace;
float _DepthTexRimLightFadeoutRange;
half _DepthTexRimLight3DFallbackMidPoint;
half _DepthTexRimLight3DFallbackSoftness;
half _DepthTexRimLight3DFallbackRemoveFlatPolygonRimLight;
float _DepthTexRimLightFixDottedLineArtifactsExtendMultiplier;
half _DepthTexShadowUsage;
half _DepthTexShadowBrightness;
half _DepthTexShadowBrightnessForFace;
half3 _DepthTexShadowTintColor;
half3 _DepthTexShadowTintColorForFace;
float _DepthTexShadowThresholdOffset;
float _DepthTexShadowFadeoutRange;
float _FaceAreaCameraDepthTextureZWriteOffset; // a just enough ZOffset for face area when writing depth into _CameraDepthTexture.Good ZOffset range is about 0.04 to 0.06, which makes depth texture shadow easier to appear
// Character ID for read global 1D array or load 1D texture
uint _CharacterID;
// NiloToon self shadow mapping
float _EnableNiloToonSelfShadowMapping;
float _NiloToonSelfShadowMappingDepthBias;
float _NiloToonSelfShadowMappingNormalBias;
half _NiloToonSelfShadowIntensity;
half _NiloToonSelfShadowIntensityForNonFace;
half _NiloToonSelfShadowIntensityForFace;
half3 _NiloToonSelfShadowMappingTintColor;
float _EnableNiloToonSelfShadowMappingDepthBias;
float _EnableNiloToonSelfShadowMappingNormalBias;
// per char receive shadows
half _PerCharReceiveAverageURPShadowMap;
half _PerCharReceiveStandardURPShadowMap;
half _PerCharReceiveNiloToonSelfShadowMap;
// outline
float _RenderOutline;
float _PerCharacterRenderOutline;
float _OutlineUseBakedSmoothNormal;
// float _UnityCameraDepthTextureWriteOutlineExtrudedPosition; // removed since NiloToon0.17.10
float _OutlineUniformLengthInViewSpace;
half3 _OutlineTintColor;
half4 _OutlineTintColorSkinAreaOverride;
half3 _OutlineOcclusionAreaTintColor;
half _OutlineUseReplaceColor;
half3 _OutlineReplaceColor;
half _OutlineUsePreLightingReplaceColor;
half3 _OutlinePreLightingReplaceColor;
float _OutlineWidth;
float _OutlineWidthExtraMultiplier;
float _OutlineApplyAutoWidthAdjustment;
float _OutlineBaseZOffset;
float _OutlineZOffset;
float _OutlineZOffsetForFaceArea;
float _UseOutlineZOffsetTex;
float4 _OutlineZOffsetMaskTexChannelMask;
float _OutlineZOffsetMaskTexInvertColor;
float _OutlineZOffsetMaskRemapStart;
float _OutlineZOffsetMaskRemapEnd;
float _UseOutlineZOffsetMaskFromVertexColor;
float4 _OutlineZOffsetMaskFromVertexColor;
float _OutlineZOffsetMaskTexFromVertexColorInvertColor;
float _UseOutlineWidthMaskFromVertexColor;
float4 _OutlineWidthMaskFromVertexColor;
float4 _OutlineWidthTexChannelMask;
float _PerCharacterOutlineWidthMultiply;
half3 _PerCharacterOutlineColorTint;
half4 _PerCharacterOutlineColorLerp;
// override outline color by tex
half4 _OverrideOutlineColorTexTintColor;
half _OverrideOutlineColorTexIgnoreAlphaChannel;
half _OverrideOutlineColorByTexIntensity;
// screen space outline
float _ScreenSpaceOutlineWidth;
float _ScreenSpaceOutlineWidthIfFace;
float _ScreenSpaceOutlineDepthSensitivity;
float _ScreenSpaceOutlineDepthSensitivityIfFace;
float _ScreenSpaceOutlineNormalsSensitivity;
float _ScreenSpaceOutlineNormalsSensitivityIfFace;
half3 _ScreenSpaceOutlineTintColor;
half3 _ScreenSpaceOutlineOcclusionAreaTintColor;
half _ScreenSpaceOutlineUseReplaceColor;
half3 _ScreenSpaceOutlineReplaceColor;
half4 _ScreenSpaceOutlineDepthSensitivityTexChannelMask;
half4 _ScreenSpaceOutlineNormalsSensitivityTexChannelMask;
half _ScreenSpaceOutlineDepthSensitivityTexRemapStart;
half _ScreenSpaceOutlineDepthSensitivityTexRemapEnd;
half _ScreenSpaceOutlineNormalsSensitivityTexRemapStart;
half _ScreenSpaceOutlineNormalsSensitivityTexRemapEnd;
// dynamic eye
float _DynamicEyeSize;
half _DynamicEyeFinalBrightness;
half3 _DynamicEyeFinalTintColor;
half4 _DynamicEyePupilMaskTexChannelMask;
half3 _DynamicEyePupilColor;
float _DynamicEyePupilDepthScale;
float _DynamicEyePupilSize;
float _DynamicEyePupilMaskSoftness;
float4 _DynamicEyeWhiteMap_ST;
// color fill
half _CharacterAreaColorFillEnabled;
half4 _CharacterAreaColorFillColor;
uint _CharacterAreaColorFillTextureUVIndex;
float4 _CharacterAreaColorFillTextureUVTilingOffset;
float2 _CharacterAreaColorFillTextureUVScrollSpeed;
half _CharacterAreaColorFillRendersVisibleArea;
half _CharacterAreaColorFillRendersBlockedArea;
// extra thick outline
float _ExtraThickOutlineEnabled;
float _ExtraThickOutlineWidth;
float _ExtraThickOutlineMaxFinalWidth;
float3 _ExtraThickOutlineViewSpacePosOffset;
half4 _ExtraThickOutlineColor;
float _ExtraThickOutlineZOffset;
float _ExtraThickOutlineZWrite;
float _ExtraThickOutlineWriteIntoDepthTexture;
// gameplay effect
half3 _PerCharEffectTintColor;
half3 _PerCharEffectAddColor;
half4 _PerCharEffectLerpColor;
half3 _PerCharEffectRimColor;
half _PerCharEffectRimSharpnessPower;
half _PerCharEffectDesaturatePercentage;
// skin
float _IsSkin;
half4 _SkinMaskMapChannelMask;
half _SkinMaskMapInvertColor;
half _SkinMaskMapRemapStart;
half _SkinMaskMapRemapEnd;
half _SkinMaskMapAsIDMap;
half _SkinMaskMapExtractFromID;
// face
half4 _FaceMaskMapChannelMask;
half _FaceMaskMapInvertColor;
half _FaceMaskMapRemapStart;
half _FaceMaskMapRemapEnd;
//half3 _FaceForwardDirection; // converted to global array
//half3 _FaceUpDirection; // converted to global array
half _FixFaceNormalAmount;
half _FixFaceNormalAmountPerMaterial;
half _FixFaceNormalUseFlattenOrProxySphereMethod;
// per char bounding sphere
//float3 _CharacterBoundCenterPosWS; // converted to global array
float _CharacterBoundRadius;
// dither fadeout
float _DitherFadeoutAmount; // clip() only accept float
float _DitherFadeoutNormalScaleFix;
// perspective removal
float _PerspectiveRemovalAmount; // total amount
// perspective removal(sphere)
float _PerspectiveRemovalRadius;
//float3 _HeadBonePositionWS; // converted to global array
// perspective removal(world height)
float _PerspectiveRemovalStartHeight; // usually is world space pos.y 0
float _PerspectiveRemovalEndHeight;
// ZWrite (for disabling all _CameraDepthTexture related effect when _ZWrite = off)
float _ZWrite;
// Cull (for fixing outline problem when rendering planar reflection pass)
float _Cull;
// Blending (for outputing correct alpha at the exit of fragment shader)
float _SrcBlend;
float _DstBlend;
// _AllowNiloToonBloomOverrideGroup
float _AllowNiloToonBloomCharacterAreaOverride;
float _AllowedNiloToonBloomOverrideStrength;
// _NILOTOON_DISSOLVE
float _DissolveAmount;
uint _DissolveMode; // C# uses material.SetInteger(_DissolveMode,value), we must match with uint here, do not use float! else dissolve may not work at all in Unity2021.3.x
float _DissolveThresholdMapTilingX;
float _DissolveThresholdMapTilingY;
float _DissolveNoiseStrength;
float _DissolveBorderRange;
half3 _DissolveBorderTintColor;
// per material dissolve
half _PerMaterialDissolveCutoff;
half _PerMaterialDissolveThresholdMapUVTiling;
uint _PerMaterialDissolveThresholdMapUVIndex;
half _PerMaterialDissolvePatternMapStrength;
half4 _PerMaterialDissolveThresholdMapChannelMask;
half _PerMaterialDissolveThresholdMapInvertColor;
half _PerMaterialDissolvePatternMapUVTiling;
uint _PerMaterialDissolvePatternMapUVIndex;
half4 _PerMaterialDissolvePatternMapChannelMask;
half _PerMaterialDissolvePatternMapInvertColor;
half _PerMaterialDissolveEdgeWidth;
half3 _PerMaterialDissolveEdgeColor;
// per character basemap override
half _PerCharacterBaseMapOverrideAmount;
float4 _PerCharacterBaseMapOverrideTilingOffset;
float2 _PerCharacterBaseMapOverrideUVScrollSpeed;
half3 _PerCharacterBaseMapOverrideTintColor;
uint _PerCharacterBaseMapOverrideUVIndex;
uint _PerCharacterBaseMapOverrideBlendMode;
// per character ZOffset
float _PerCharZOffset;
// Decal
half _DecalAlbedoApplyStrength;
half _DecalNormalApplyStrength;
half _DecalOcclusionApplyStrength;
half _DecalSmoothnessApplyStrength;
half _DecalSpecularApplyStrength;
// Pass On/Off
float _AllowRenderURPShadowCasterPass;
float _AllowRenderDepthOnlyOrDepthNormalsPass;
float _AllowRenderNiloToonSelfShadowPass;
float _AllowRenderExtraThickOutlinePass;
float _AllowRenderNiloToonCharacterAreaStencilBufferFillPass;
float _AllowRenderNiloToonCharacterAreaColorFillPass;
// Per character effect on/off
float _AllowPerCharacterDissolve;
float _AllowPerCharacterDitherFadeout;
// Pre-Multiply alpha options
half _PreMultiplyAlphaIntoRGBOutput;
#ifndef SHADER_API_VULKAN
CBUFFER_END
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Global uniforms
// if an uniform is not a per material uniform,
// it is fine to write it outside of CBUFFER_START(UnityPerMaterial)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// a special uniform for applyShadowBiasFixToHClipPos() only
float3 _LightDirection;
float3 _LightPosition;
// global outline uniforms
float _GlobalShouldRenderOutline;
float _GlobalOutlineWidthMultiplier;
half3 _GlobalOutlineTintColor;
float _GlobalOutlineWidthAutoAdjustToCameraDistanceAndFOV;
// global shadow mapping uniforms
float _GlobalShouldReceiveShadowMapping;
half _GlobalReceiveShadowMappingAmount;
float _GlobalToonShaderNormalBiasMultiplier;
half3 _GlobalMainLightURPShadowAsDirectResultTintColor;
half _GlobalURPShadowAsDirectLightTintIgnoreMaterialURPUsageSetting;
half _GlobalNiloToonReceiveURPShadowblurriness;
// global self shadow mapping
float4x4 _NiloToonSelfShadowWorldToClip;
float4 _NiloToonSelfShadowParam;
float4 _NiloToonSelfShadowSoftShadowParam; // x: soft shadow quality | y: should reshape | z: reshape blur size
float _NiloToonSelfShadowRange;
float _NiloToonGlobalSelfShadowCasterDepthBias;
float _NiloToonGlobalSelfShadowCasterNormalBias;
float _NiloToonGlobalSelfShadowReceiverDepthBias;
float _NiloToonGlobalSelfShadowReceiverNormalBias;
half3 _NiloToonSelfShadowLightDirection;
half _NiloToonSelfShadowUseNdotLFix;
half _GlobalReceiveNiloToonSelfShadowMap;
float _GlobalReceiveSelfShadowMappingPosOffset;
// global occlusion uniforms
half _GlobalOcclusionStrength;
// global lighting uniforms
half _GlobalIndirectLightMultiplier;
half3 _GlobalIndirectLightMinColor;
// global depth diff rim light and shadow uniforms
float _GlobalEnableDepthTextureRimLigthAndShadow;
float _GlobalDepthTexRimLightAndShadowWidthMultiplier;
float _GlobalDepthTexRimLightDepthDiffThresholdOffset;
float _GlobalDepthTexRimLightCameraDistanceFadeoutStartDistance;
float _GlobalDepthTexRimLightCameraDistanceFadeoutEndDistance;
// global light uniforms
half3 _GlobalUserOverriddenMainLightDirVS;
half3 _GlobalUserOverriddenMainLightDirWS;
half3 _GlobalUserOverriddenMainLightColor;
half4 _GlobalUserOverriddenFinalMainLightDirWSParam; // xyz: final main light direction, w: 1 if enabled, else 0
half4 _GlobalUserOverriddenFinalMainLightColorParam; // rgb: final main light color, w: 1 if enabled, else 0
// global camera uniforms
float _CurrentCameraFOV;
// global camera fix
float2 _GlobalAspectFix;
float _GlobalFOVorOrthoSizeFix;
// global volume
half3 _GlobalVolumeMulColor;
half4 _GlobalVolumeLerpColor;
half3 _GlobalRimLightMultiplier;
half3 _GlobalRimLightMultiplierForOutlineArea;
half3 _GlobalIndirectLightMaxColor;
half3 _GlobalMainDirectionalLightMaxContribution;
half3 _GlobalAdditionalLightMaxContribution;
half3 _GlobalSpecularTintColor;
half _GlobalSpecularMinIntensity;
half _GlobalSpecularReactToLightDirectionChange;
half3 _GlobalMainDirectionalLightMultiplier;
half3 _GlobalAdditionalLightMultiplier;
half3 _GlobalAdditionalLightMultiplierForFaceArea;
half3 _GlobalAdditionalLightMultiplierForOutlineArea;
half _GlobalAdditionalLightApplyRimMask;
half _GlobalAdditionalLightRimMaskPower;
half _GlobalAdditionalLightRimMaskSoftness;
half3 _GlobalVolumeBaseColorTintColor;
half3 _GlobalCharacterOverallShadowTintColor;
half3 _GlobalCharacterOverallShadowTintColorForSkinFace;
half3 _GlobalCharacterOverallShadowTintColorForNonSkinFace;
half _GlobalCharacterOverallShadowStrength;
half _GlobalCinematic3DRimMaskEnabled;
half _GlobalCinematic3DRimMaskStrength_ClassicStyle;
half _GlobalCinematic3DRimMaskSharpness_ClassicStyle;
half _GlobalCinematic3DRimMaskBlur_ClassicStyle;
half _GlobalCinematic3DRimMaskWidth_ClassicStyle;
half _GlobalCinematic3DRimMaskStrength_DynamicStyle;
half _GlobalCinematic3DRimMaskWidth_DynamicStyle;
half _GlobalCinematic3DRimMaskBlur_DynamicStyle;
half _GlobalCinematic3DRimMaskSharpness_DynamicStyle;
half _GlobalCinematic3DRimMaskStrength_StableStyle;
half _GlobalCinematic3DRimMaskSharpness_StableStyle;
half _GlobalCinematic2DRimMaskStrength;
half _GlobalCinematicRimTintBaseMap;
half _GlobalAdditionalLightInjectIntoMainLightColor_Strength;
half _GlobalAdditionalLightInjectIntoMainLightColor_AllowCloseLightOverBright;
half _GlobalAdditionalLightInjectIntoMainLightColor_Desaturate;
half _GlobalAdditionalLightInjectIntoMainLightDirection_Strength;
// global screen space outline settings
float _GlobalScreenSpaceOutlineIntensityForChar;
float _GlobalScreenSpaceOutlineWidthMultiplierForChar;
float _GlobalScreenSpaceOutlineNormalsSensitivityOffsetForChar;
float _GlobalScreenSpaceOutlineDepthSensitivityOffsetForChar;
float _GlobalScreenSpaceOutlineDepthSensitivityDistanceFadeoutStrengthForChar;
half3 _GlobalScreenSpaceOutlineTintColorForChar;
// global screen space outline V2 settings
float _GlobalScreenSpaceOutlineV2IntensityForChar;
float _GlobalScreenSpaceOutlineV2WidthMultiplierForChar;
float _GlobalScreenSpaceOutlineV2EnableGeometryEdgeForChar;
float _GlobalScreenSpaceOutlineV2GeometryEdgeThresholdForChar;
float _GlobalScreenSpaceOutlineV2EnableNormalAngleEdgeForChar;
float _GlobalScreenSpaceOutlineV2NormalAngleCosThetaThresholdForChar;
// global utility
float _GlobalShouldDisableNiloToonDepthTextureRimLightAndShadow;
// rendering path related
float _NiloToonGlobalAllowUnityCameraDepthTextureWriteOutlineExtrudedPosition;
float _NiloToonGlobalAllowUnityCameraDepthTextureWriteFaceZOffset;
// global debug
float _GlobalToonShadeDebugCase;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Global Arrays
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Must match: NiloToonSetToonParamPass.k_MaxCharacterCount***** values!
#if defined(SHADER_API_MOBILE) && (defined(SHADER_API_GLES) || defined(SHADER_API_GLES30))
#define MAX_CHARACTER_COUNT 16
#elif defined(SHADER_API_MOBILE) || (defined(SHADER_API_GLCORE) && !defined(SHADER_API_SWITCH)) || defined(SHADER_API_GLES) || defined(SHADER_API_GLES3) // Workaround because SHADER_API_GLCORE is also defined when SHADER_API_SWITCH is
#define MAX_CHARACTER_COUNT 32
#else
#define MAX_CHARACTER_COUNT 128
#endif
// For properties that will change every frame, we don't put those properties in CBUFFER due to performance cost,
// so we use a global array instead (each character will using _CharacterID as index to get the data for that character)
// this method is similar to how URP get per additional light data from constant size global arrays (256 size light data arrays)
float3 _NiloToonGlobalPerCharFaceForwardDirWSArray[MAX_CHARACTER_COUNT];
float3 _NiloToonGlobalPerCharFaceUpwardDirWSArray[MAX_CHARACTER_COUNT];
float3 _NiloToonGlobalPerCharBoundCenterPosWSArray[MAX_CHARACTER_COUNT];
float3 _NiloToonGlobalPerCharHeadBonePosWSArray[MAX_CHARACTER_COUNT];
half3 _NiloToonGlobalPerCharMainDirectionalLightTintColorArray[MAX_CHARACTER_COUNT];
half3 _NiloToonGlobalPerCharMainDirectionalLightAddColorArray[MAX_CHARACTER_COUNT];
// max array size decided by URP's MAX_VISIBLE_LIGHTS,
// it matches C#'s UniversalRenderPipeline.maxVisibleAdditionalLights
// 16 (low-end mobile)
// 32 (mobile | SWITCH)
// 256 (other)
#if NeedCalculateAdditionalLight
half4 _NiloToonGlobalPerUnityLightDataArray[MAX_VISIBLE_LIGHTS];
half4 _NiloToonGlobalPerUnityLightDataArray2[MAX_VISIBLE_LIGHTS];
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Data Structs
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Passing multiple data between functions can be difficult if the param count is too large,
// so we write some struct to help us organize function calls param
struct UVData
{
// index 0: UV0
// index 1: UV1
// index 2: UV2
// index 3: UV3
// index 4: MatCapUV
// index 5: CharBoundUV
// index 6: ScreenSpaceUV
float2 allUVs[7];
// Performance warning of "dynamic index":
// dynamic index accessing an array may force the compiler to store the element values in off-chip memory!
// because you can't do dynamic indexing into registers.
// if we want the element values store in register for quick read, we need to use compile time constant index.
// it means a "ifelse chain or switch case that looks stupid" may perform better than a clean code dynamic index array access..!
// see:
// - https://developer.nvidia.com/blog/identifying-shader-limiters-with-the-shader-profiler-in-nvidia-nsight-graphics/
// - https://www.reddit.com/r/opengl/comments/ryig28/struct_switchindexing_with_20_values_faster_than/
float2 GetUV(uint index)
{
// In mobile test (Adreno612,Adreno619,MaliG57MC2):
// - UVsMethod 0 (constant index) is debug control, it is expected to be the fastest
// - UVsMethod 1/2 (ifelse chain / switch) is faster than UVsMethod 3
// - UVsMethod 3 (dynamic index) is the worst (NiloToonSampleScene)
// -------------------------------------
// NiloToon 0.16.16 (Method3, control):
// - low = 60fps (adreno619), 60fps (MaliG57), 25fps(adreno612)
// - mid = 38fps (adreno619), 60fps (MaliG57), 14fps(adreno612)
// - high = 25fps (adreno619), 44fps (MaliG57), 9fps(adreno612)
// - highest = 8fps (adreno619), 15fps (MaliG57), 3fps(adreno612)
// -------------------------------------
// NiloToon 0.16.16 (Method2, switch case):
// - low = 60fps (adreno619), 60fps (MaliG57), 22fps(adreno612)
// - mid = 35fps (adreno619), 60fps (MaliG57), 13fps(adreno612)
// - high = 21fps (adreno619), 41fps (MaliG57), 7fps(adreno612)
// - highest = 7fps (adreno619), 15fps (MaliG57), 3fps(adreno612)
// -------------------------------------
// NiloToon 0.16.16 (Method1, ifelse chain):
// - low = 60fps (adreno619), 60fps (MaliG57), 23fps(adreno612)
// - mid = 35fps (adreno619), 60fps (MaliG57), 13fps(adreno612)
// - high = 21fps (adreno619), 47fps (MaliG57), 7fps(adreno612)
// - highest = 8fps (adreno619), 11fps (MaliG57), 3fps(adreno612)
// -------------------------------------
// NiloToon 0.16.16 (Method0, dynamic index):
// - low = 60fps (adreno619), 60fps (MaliG57), 22fps(adreno612)
// - mid = 33fps (adreno619), 60fps (MaliG57), 12fps(adreno612)
// - high = 17fps (adreno619), 46fps (MaliG57), 6fps(adreno612)
// - highest = 6fps (adreno619), 10fps (MaliG57), 3fps(adreno612)
#define UVsMethod 1
#if UVsMethod == 0
// dynamic index array access, clean code but slower
return allUVs[index];
#elif UVsMethod == 1
// ifelse chain
if(index == 0) return allUVs[0];
if(index == 1) return allUVs[1];
if(index == 2) return allUVs[2];
if(index == 3) return allUVs[3];
if(index == 4) return allUVs[4];
if(index == 5) return allUVs[5];
if(index == 6) return allUVs[6];
return 0;
#elif UVsMethod == 2
// switch case
switch (index)
{
case 0: return allUVs[0];
case 1: return allUVs[1];
case 2: return allUVs[2];
case 3: return allUVs[3];
case 4: return allUVs[4];
case 5: return allUVs[5];
case 6: return allUVs[6];
}
return 0;
#elif UVsMethod == 3
// constant index array access (debug control)
return allUVs[0];
#endif
}
};
// this struct is similar to URP's SurfaceData.hlsl -> "SurfaceData" struct
// Note: don't name it "SurfaceData", because it will conflict with URP's own "SurfaceData" struct in URP's SurfaceData.hlsl
struct ToonSurfaceData
{
half3 albedo;
half alpha;
half3 emission;
half occlusion;
half3 specular;
half specularMask; // store user's remapped specular mask texture, for ramp specular
half3 normalTS;
half smoothness;
};
// this struct is similar to URP's Input.hlsl -> "InputData" struct
// any useful data for lighting will be put into this struct
struct ToonLightingData
{
float2 uv;
half3 normalWS;
half3 normalWS_NoNormalMap;
float3 positionWS;
half3 viewDirectionWS;
float selfLinearEyeDepth;
half averageShadowAttenuation;
half3 SH;
half isFaceArea;
half isSkinArea;
float2 SV_POSITIONxy;
half3 normalVS;
half3 reflectionVectorWS;
half NdotV;
half NdotV_NoNormalMap;
half rawURPShadowAttenuation;
#if VaryingsHasTangentWS
half3x3 TBN_WS;
half3 viewDirectionTS;
#endif
half4 vertexColor;
float ZOffsetFinalSum;
float2 normalizedScreenSpaceUV;
float2 aspectCorrectedScreenSpaceUV;
float facing;
float3 characterBoundBottomWS_positionWS;
float3 characterBoundTopWS_positionWS;
float2 characterBound2DRectUV01;
float2 matcapUV;
Light mainLight;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Shared functions across .hlsl(s)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// optimized function to get the UV0 (fastest)
float2 GetUV(Varyings varyings)
{
return varyings.uv01.xy;
}
// generic function to get any UV (fast)
float2 GetUV(Varyings varyings, uint index)
{
if(index == 0) return varyings.uv01.xy;
if(index == 1) return varyings.uv01.zw;
if(index == 2) return varyings.uv23.xy;
if(index == 3) return varyings.uv23.zw;
return 0;
/*
// same as the above code, but slower due to array assign and read
float2 uvArray[4];
uvArray[0] = varyings.uv01.xy;
uvArray[1] = varyings.uv01.zw;
uvArray[2] = varyings.uv23.xy;
uvArray[3] = varyings.uv23.zw;
return uvArray[index];
*/
}
// complex function to get a final uv (slow)
float2 CalcUV(UVData uvData, uint index, float4 scaleOffset, float2 scrollSpeed)
{
return CalcUV(uvData.GetUV(index), scaleOffset, _Time.y, scrollSpeed);
}
// Full complex function to get a final uv(slowest)
float2 CalcUV(UVData uvData, uint index, float4 scaleOffset, float2 scrollSpeed, float rotatedAngle, float rotateSpeed)
{
return CalcUV(uvData.GetUV(index), scaleOffset, _Time.y, scrollSpeed, rotatedAngle, rotateSpeed);
}
half4 GetCombinedBaseColor()
{
half4 combinedBaseColor = _BaseColor * _BaseColor2;
combinedBaseColor *= _MultiplyBRPColor ? _Color : 1;
return combinedBaseColor;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// #include .hlsl
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// all lighting equations will be inside this .hlsl,
// just by editing this .hlsl can control most of the visual result.
#include "NiloToonCharacter_LightingEquation.hlsl"
// all NiloToon supported ExternalAsset extension will go here
#include "NiloToonCharacter_ExtendDefinesForExternalAsset.hlsl"
#include "NiloToonCharacter_ExtendFunctionsForExternalAsset.hlsl"
// if you want to extend this shader without future update merge conflict, you should edit this .hlsl
// it is made for NiloToonURP's user to extend more global features by themselves in an isolated .hlsl file
#include "NiloToonCharacter_ExtendFunctionsForUserCustomLogic.hlsl"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// vertex shared functions
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Return: skin area
// Skin area is mainly for shadow color overriding to a skin shadow color.
half GetSkinArea(float2 uv)
{
half isSkinArea = _IsSkin;
#if _SKIN_MASK_ON
half skinMask = ExtractSingleChannel(SAMPLE_TEXTURE2D(_SkinMaskMap, sampler_BaseMap, uv), _SkinMaskMapChannelMask);// reuse sampler_BaseMap to save sampler count
skinMask = PostProcessMaskValue(skinMask, _SkinMaskMapAsIDMap, _SkinMaskMapExtractFromID, _SkinMaskMapInvertColor, _SkinMaskMapRemapStart, _SkinMaskMapRemapEnd);
isSkinArea *= skinMask;
#endif
return isSkinArea;
}
half GetFaceArea(float2 uv)
{
half isFaceArea = 0;
#if _ISFACE
#if _FACE_MASK_ON
// if enabled face mask texture, we treat only white area on _FaceMaskMap is face
isFaceArea = dot(tex2Dlod(_FaceMaskMap, float4(uv,0,0)), _FaceMaskMapChannelMask);
isFaceArea = _FaceMaskMapInvertColor? 1-isFaceArea : isFaceArea;
isFaceArea = invLerpClamp(_FaceMaskMapRemapStart,_FaceMaskMapRemapEnd,isFaceArea);
#else
// if no face mask, we assume the whole material is face when _ISFACE is on
isFaceArea = 1;
#endif
#endif
return isFaceArea;
}
// output:
// .xyz = lightingNormalWS (with face normal edit applied)
// .w = isFaceArea
half4 GetLightingNormalWS_FaceArea(half3 normalWS, float3 positionWS, float2 uv)
{
// default use original normalWS
half3 resultNormalWS = normalWS;
half isFaceArea = GetFaceArea(uv);
// disabled NiloToonDepthOnlyOrDepthNormalPass's face normal edit, so the normal in _CameraNormalTexture store the real normal
// in case any postprocess effect uses normal on face's pixels
#if _ISFACE && !NiloToonDepthOnlyOrDepthNormalPass
// [apply face normal edit]
// methodA: by forcing face normal becoming face's forward direction vector
half3 flatDirectionFixFaceNormalWS = _NiloToonGlobalPerCharFaceForwardDirWSArray[_CharacterID];
// methodB: by forcing face normal becoming sphere proxy normal, which use _HeadBonePositionWS as proxy sphere center
half3 proxySphereFixFaceNormalWS = positionWS - _NiloToonGlobalPerCharHeadBonePosWSArray[_CharacterID];
// let user select methodA or methodB
half3 fixedFaceNormalWS = lerp(flatDirectionFixFaceNormalWS,proxySphereFixFaceNormalWS,_FixFaceNormalUseFlattenOrProxySphereMethod);
//fixedFaceNormalWS = proxySphereFixFaceNormalWS; //TODO: this line better for face area additional light
resultNormalWS = normalize(lerp(resultNormalWS, fixedFaceNormalWS, _FixFaceNormalAmount * _FixFaceNormalAmountPerMaterial * isFaceArea * _ControlledByNiloToonPerCharacterRenderController)); // only normalize() once at final to improve performance
#endif
return half4(resultNormalWS,isFaceArea);
}
bool IsSmoothedNormalTSAvailableInMeshUV8(Attributes input)
{
// checks to confirm if uv8 is a correct smoothed normal:
// 1) is uv8 a unit vector?
// 2) uv8.xy should never equals to uv4.xy if uv8 is a correctly baked smoothed normal vector.
// 3) if uv8.z is 0, it is not a baked smooth normal (vrm character do not bake smoothed normal)
return (abs(dot(input.uv8,input.uv8)-1.0) < (1.0/255.0)) &&
all(input.uv8.xy != input.uv4.xy) &&
(input.uv8.z != 0)
;
}
bool ShouldOutlineUseBakedSmoothNormal(bool isSmoothedNormalAvailableInMeshUV8)
{
// apply material setting by user
return isSmoothedNormalAvailableInMeshUV8 && _OutlineUseBakedSmoothNormal;
}
float3 GetPossibleBakedSmoothedNormalWS(VertexNormalInputs vertexNormalInputs, bool isSmoothedNormalTSAvailableInMeshUV8, float3 smoothedNormalTS)
{
// by default we will use lighting normalWS(regular vertex normal) as extrude direction,
// it is usable but not perfect due to smoothing group's split normal
// which will produce discontinue outline for hard edge polygons(e.g. a cube's corner/edge, hair's end vertex)
float3 extrudeDirectionWS = vertexNormalInputs.normalWS;
// "world space smoothed normal" is a much better extrude direction for outline than simply using lighting normal(regular vertex normal),
// because smoothed normal doesn't have any split normal, so the outline will always be continuous(even on a cube), which looks much better.
// If we baked "tangent space smoothed normal" in model's uv8,
// we can convert it back to world space here and use it as a much better extrude direction for outline
if(isSmoothedNormalTSAvailableInMeshUV8)
{
extrudeDirectionWS = ConvertNormalTSToNormalTargetSpace(smoothedNormalTS, vertexNormalInputs.tangentWS,vertexNormalInputs.bitangentWS, vertexNormalInputs.normalWS);
}
return extrudeDirectionWS;
}
float3 TransformPositionWSToOutlinePositionWS(float width, VertexPositionInputs vertexPositionInputs, float3 extrudeDirectionWS)
{
width *= GetOutlineCameraFovAndDistanceFixMultiplier(vertexPositionInputs.positionVS.z, _CurrentCameraFOV, _GlobalOutlineWidthAutoAdjustToCameraDistanceAndFOV * _OutlineApplyAutoWidthAdjustment);
// [normalize length in view space]
if(_OutlineUniformLengthInViewSpace)
{
float3 extrudeDirectionVS = mul(UNITY_MATRIX_V,float4(extrudeDirectionWS,0)).xyz;
extrudeDirectionVS.z = 0;
extrudeDirectionVS = normalize(extrudeDirectionVS);
extrudeDirectionWS = mul(UNITY_MATRIX_I_V, float4(extrudeDirectionVS,0)).xyz;
}
// [this part is optional]
// you can make extrude direction normalized in screen space, which will produce screen space constant width outline
// https://www.videopoetics.com/tutorials/pixel-perfect-outline-shaders-unity/
// https://github.com/Santarh/MToon/blob/master/MToon/Resources/Shaders/MToonCore.cginc#L90
// TODO: do we need to support this option?
// ...
// [normalize in NDC]
// this part is wrong, just experimental code
/*
float3 extrudeDirectionNDC = mul(UNITY_MATRIX_VP,float4(extrudeDirectionWS,0));
extrudeDirectionNDC.z = 0;
extrudeDirectionNDC = normalize(extrudeDirectionNDC) * 12;
extrudeDirectionWS = mul(UNITY_MATRIX_I_VP, float4(extrudeDirectionNDC,0));
*/
// produce and return outline vertex position in world space
float3 outlinePositionWS = vertexPositionInputs.positionWS + extrudeDirectionWS * width;
return outlinePositionWS;
}
float ShouldRenderOutline()
{
return _RenderOutline && _PerCharacterRenderOutline;
}
bool ShouldDisableRendering()
{
// user can disable rendering:
// - per material
// - per NiloToonChar script
bool shouldDisableRendering = !(_EnableRendering && _RenderCharacter);
// when _DitherFadeoutAmount or _DissolveAmount is 100% (character is completely not visible due to NiloToonPerCharacterRenderController's dither or dissolve setting),
// discard all rendering in this vertex shader so we don't pollute URP's shadowmap and depth texture / depth normal texture
// it also helps improving performance because of skipping the fragment shader
shouldDisableRendering = shouldDisableRendering ||
(_AllowPerCharacterDitherFadeout && (_DitherFadeoutAmount >= 1.0)) ||
(_AllowPerCharacterDissolve && (_DissolveAmount >= 1.0));
// when rendering to _CameraColorTexture, this section will allow material's _RenderOutline toggle or per character script's _PerCharacterRenderOutline to control render outline or not, per material.
// Be careful not to run this section in DepthOnly/DepthNormalsOnly pass,
// in DepthOnly pass we only need to skip the outline position extrude, not invalid the vertex!
// so here only #if NiloToonSelfOutlinePass is used instead of #if NiloToonIsAnyOutlinePass.
#if NiloToonSelfOutlinePass
shouldDisableRendering = shouldDisableRendering || !ShouldRenderOutline();
#endif
// Material's "Pass On/Off" section
bool shouldRenderingExtraThickOutline = _AllowRenderExtraThickOutlinePass && _ExtraThickOutlineEnabled;
bool shouldRenderingCharacterAreaColorFill = _AllowRenderNiloToonCharacterAreaColorFillPass && _CharacterAreaColorFillEnabled;
bool shouldRenderCharacterAreaStencilBufferFill = (shouldRenderingExtraThickOutline || shouldRenderingCharacterAreaColorFill) && _AllowRenderNiloToonCharacterAreaStencilBufferFillPass;
#if NiloToonCharacterAreaStencilBufferFillPass
shouldDisableRendering = shouldDisableRendering || !shouldRenderCharacterAreaStencilBufferFill;
#endif
#if NiloToonExtraThickOutlinePass
shouldDisableRendering = shouldDisableRendering || !shouldRenderingExtraThickOutline;
#endif
#if NiloToonCharacterAreaColorFillPass
shouldDisableRendering = shouldDisableRendering || !shouldRenderingCharacterAreaColorFill;
#endif
#if NiloToonShadowCasterPass
shouldDisableRendering = shouldDisableRendering || !_AllowRenderURPShadowCasterPass;
#endif
#if NiloToonDepthOnlyOrDepthNormalPass
shouldDisableRendering = shouldDisableRendering || !_AllowRenderDepthOnlyOrDepthNormalsPass;
#endif
#if NiloToonCharSelfShadowCasterPass
shouldDisableRendering = shouldDisableRendering || !_AllowRenderNiloToonSelfShadowPass;
#endif
return shouldDisableRendering;
}
// [All pass's vertex shader will share this function]
// - if "NiloToonIsAnyOutlinePass" is not defined = do regular MVP transform
// - if "NiloToonIsAnyOutlinePass" is defined = do regular MVP transform + outline extrude vertex + all outline related task
Varyings VertexShaderAllWork(Attributes input)
{
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// init Varyings struct
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// init output struct with all 0 bits just to avoid "struct not init" warning/error
// but even passing 0 from vertex to fragment via Varying struct still has Rasterisation/interpolation cost
// so make sure Varyings struct is as small as possible by using {#if #endif} to remove unneeded things inside Varying struct
Varyings output = (Varyings)0;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// disable rendering (early exit)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if we should not render this material,
// execute "disable rendering"
if(ShouldDisableRendering())
{
// see [a trick to "delete" any vertex] below to understand what this line does
output.positionCS.w = 0;
return output;
//---------------------------------------------------------------------------------------------------------------------------
// [a trick to "delete" any vertex]
// https://forum.unity.com/threads/ignoring-some-triangles-in-a-vertex-shader.170834/#post-5327751
// if the output vertex's positionCS.w is NaN, GPU will invalid this vertex and all directly connected vertices(Degenerate triangles),
// we can use "positionCS.w = NaN" to invalid any vertices if needed.
// 1.this section is a correct and safe implementation, 0.0/0.0 is NaN, which can invalid any target vertex, but will produce "divide by zero" warning
// {
// output.positionCS.w = 0.0/0.0; //0.0/0.0 is NaN
// return output;
// }
// 2.this section is a correct and safe implementation, _NaN is NaN from C#, which can invalid any target vertex, and will NOT produce "divide by zero" warning
// {
// // C# -> Shader.SetGlobalFloat("_NaN", System.Single.NaN);
// output.positionCS.w = _NaN;
// return output;
// }
// 3.this section is a correct and safe implementation, asfloat(0x7fc00000) is NaN, which can invalid any target vertex, and will NOT produce "divide by zero" warning
// {
// output.positionCS = asfloat(0x7fc00000);
// return output;
// }
// 4.this section will work only if you are discarding the whole mesh, but the nice thing is, it will NOT produce any warning, so we choose this for discarding the whole mesh.
// {
// output.positionCS.w = 0; // it is 0 already even without writing this line, since Varyings struct was init with all 0 bits, but we still write this line for clarity
// return output;
// }
//---------------------------------------------------------------------------------------------------------------------------
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// after invalid/discard vertex, do this part asap.
// to support GPU instancing and Single Pass Stereo rendering(VR), add the following section
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
UNITY_SETUP_INSTANCE_ID(input); // will turn into this in non OpenGL and non PSSL -> UnitySetupInstanceID(input.instanceID);
// see URP's ShadowCasterPass.hlsl for why these lines are removed for NiloToonCharSelfShadowCasterPass
#if !NiloToonCharSelfShadowCasterPass
UNITY_TRANSFER_INSTANCE_ID(input, output); // will turn into this in non OpenGL and non PSSL -> output.instanceID = input.instanceID;
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); // will turn into this in non OpenGL and non PSSL -> output.stereoTargetEyeIndexAsRTArrayIdx = unity_StereoEyeIndex;
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// UV
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// TRANSFORM_TEX is the same as the old shader library.
// is it correct to apply the same _BaseMap_ST uv change to all UVs? maybe not!
// we should not affect all uv0-uv3 by _BaseMap's tiling offset, it maybe is a wrong design
// but removing it is a big breaking change, so let's keep it unchanged, and add a "UV Control" group as the alternative control
output.uv01.xy = TRANSFORM_TEX(input.uv ,_BaseMap);
output.uv01.zw = TRANSFORM_TEX(input.uv2,_BaseMap);
output.uv23.xy = TRANSFORM_TEX(input.uv3,_BaseMap);
output.uv23.zw = TRANSFORM_TEX(input.uv4,_BaseMap);
if(_EnableUVEditGroup)
{
output.uv01.xy = CalcUV(output.uv01.xy, _UV0ScaleOffset, _UV0CenterPivotScalePos, _Time.y, _UV0ScrollSpeed, _UV0RotatedAngle, _UV0RotateSpeed);
output.uv01.zw = CalcUV(output.uv01.zw, _UV1ScaleOffset, _UV1CenterPivotScalePos, _Time.y, _UV1ScrollSpeed, _UV1RotatedAngle, _UV1RotateSpeed);
output.uv23.xy = CalcUV(output.uv23.xy, _UV2ScaleOffset, _UV2CenterPivotScalePos, _Time.y, _UV2ScrollSpeed, _UV2RotatedAngle, _UV2RotateSpeed);
output.uv23.zw = CalcUV(output.uv23.zw, _UV3ScaleOffset, _UV3CenterPivotScalePos, _Time.y, _UV3ScrollSpeed, _UV3RotatedAngle, _UV3RotateSpeed);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// insert a performance debug early exit
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// exit as early as possible, to maximize performance difference, which can be used to estimate how expensive this shader is on target device.
#if _NILOTOON_FORCE_MINIMUM_SHADER
#if NiloToonIsAnyOutlinePass
output.positionCS = TransformObjectToHClip(input.positionOS + input.normalOS * 0.005); // any visible debug outline width is ok
#else
output.positionCS = TransformObjectToHClip(input.positionOS);
#endif
return output;
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Edit Attributes struct by other .hlsl
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// allow NiloToon's developer to support external AssetStore/GitHub assets by editing Attributes struct,
// using NiloToonCharacter_ExtendFunctionsForExternalAsset.hlsl (used by NiloToon's developer)
ApplyExternalAssetSupportLogicToVertexAttributeAtVertexShaderStart(input);
// allow NiloToon's user to edit Attributes struct,
// using NiloToonCharacter_ExtendFunctionsForUserCustomLogic.hlsl (used by NiloToon's user)
ApplyCustomUserLogicToVertexAttributeAtVertexShaderStart(input);
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Fill in VertexPositionInputs and VertexNormalInputs utility struct
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// VertexPositionInputs struct contains the position in multiple spaces (world, view, homogeneous clip space, NDC)
// Unity compiler will strip all unused references (say you don't use view space).
// Therefore there is more flexibility at no additional cost when using VertexPositionInputs struct.
VertexPositionInputs vertexPositionInput = GetVertexPositionInputs(input.positionOS);
// Similar to VertexPositionInputs, VertexNormalInputs will contain normal, tangent and bitangent
// in world space. If not used it will be stripped.
// NormalWS and tangentWS are already normalized,
// this is required to avoid skewing the direction during interpolation
// also required for per-vertex lighting and SH evaluation if needed
VertexNormalInputs vertexNormalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS);
float3 positionWS = vertexPositionInput.positionWS;
float3 positionVS = vertexPositionInput.positionVS;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Smoothed normal WS cache
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool isSmoothedNormalTSAvailableInMeshUV8 = IsSmoothedNormalTSAvailableInMeshUV8(input);
float3 possibleSmoothedNormalWS = GetPossibleBakedSmoothedNormalWS(vertexNormalInput, isSmoothedNormalTSAvailableInMeshUV8, input.uv8.xyz); // mesh's uv8 is smoothedNormalTS if baked by NiloToon
output.smoothedNormalWS = possibleSmoothedNormalWS;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ShouldOutlineUseBakedSmoothNormal cache
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool shouldOutlineUseBakedSmoothNormal = ShouldOutlineUseBakedSmoothNormal(isSmoothedNormalTSAvailableInMeshUV8);
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Fog
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if NiloToonIsAnyLitColorPass
// we must calculate fogFactor before any positionCS.z's edit (e.g. ZOffset edit)
const half fogFactor = ComputeFogFactor(vertexPositionInput.positionCS.z);
output.SH_fogFactor.w = fogFactor;
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Extrude positionWS for outline
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// extrude positionWS if this pass is any outline pass and this material wants to render outline
#if NiloToonIsAnyOutlinePass
// Inside NiloToonIsAnyOutlinePass, there are 5 passes.
// if ShouldRenderOutline() is false,
// - NiloToonSelfOutlinePass already early exit (see "discard rendering (early exit)" in the above section)
// - ExtraThickOutlinePass will ignore ShouldRenderOutline()
// so only:
// - NiloToonDepthOnlyOrDepthNormalPass || NiloToonPrepassBufferPass || NiloToonCharacterAreaStencilBufferFillPass
// will need to include the following if() section,
// these passes should be always in sync, since NiloToonPrepassBuffer pass rely on _CameraDepthTexture (DepthOnly | DepthNormalsOnly)
// else the depth comparision test in NiloToonPrepassBuffer pass will be incorrect.
// * NiloToonCharacterAreaStencilBufferFillPass should still be included here, but does not care any depth texture related checks
#if NiloToonDepthOnlyOrDepthNormalPass || NiloToonPrepassBufferPass || NiloToonCharacterAreaStencilBufferFillPass
// In this section, "Character non-color passes" means these passes:
// - DepthOnly | DepthNormalsOnly
// - NiloToonPrepassBuffer
// - NiloToonCharacterAreaStencilBufferFill
// 1)If ShouldRenderOutline() is false,
// no need to extrude in "Character non-color passes", simple to understand.
// 2)If shouldOutlineUseBakedSmoothNormal is false,
// which means we are trying to use split normal for extrude.
// Due to smoothing group's regular split normal, TransformPositionWSToOutlinePositionWS() may produce holes between polygons after extrude,
// which pollute "Character non-color passes" (e.g., pollute _CameraDepthTexture, which makes depth texture has holes, lead to 2D rim light producing bad result).
// so when the extrude normal is split normal, it is better NOT doing TransformPositionWSToOutlinePositionWS() if shouldOutlineUseBakedSmoothNormal is false for these "Character non-color passes"
// 3)If per material
// - _UnityCameraDepthTextureWriteOutlineExtrudedPosition
// is false (default is false),
// which means user want to skip this section, usually user will keep turn off _UnityCameraDepthTextureWriteOutlineExtrudedPosition in material to avoid ugly 2D rim light artifact appeared
// 4)If global uniform
// - _NiloToonGlobalAllowUnityCameraDepthTextureWriteOutlineExtrudedPosition
// is false, it means we are in deferred rendering mode, where true depth texture write is a must.
// we have no choice but to write the true depth without any extrude
if( ShouldRenderOutline()
#if !NiloToonCharacterAreaStencilBufferFillPass
&& shouldOutlineUseBakedSmoothNormal
&& false // _UnityCameraDepthTextureWriteOutlineExtrudedPosition, removed since NiloToon 0.17.10, not allow to use anymore
&& _NiloToonGlobalAllowUnityCameraDepthTextureWriteOutlineExtrudedPosition
#endif
)
#endif
{
float finalOutlineWidth = _OutlineWidth * _OutlineWidthExtraMultiplier * _PerCharacterOutlineWidthMultiply * _GlobalOutlineWidthMultiplier;
// outline width mul from texture
#if _OUTLINEWIDTHMAP
float outlineWidthTex2DlodExplicitMipLevel = 0;
float4 outlineWidthTexReadValueRGBA = tex2Dlod(_OutlineWidthTex, float4(input.uv,0,outlineWidthTex2DlodExplicitMipLevel));
float outlineWidthMultiplierByTex = dot(outlineWidthTexReadValueRGBA,_OutlineWidthTexChannelMask);
finalOutlineWidth *= outlineWidthMultiplierByTex;
#endif
// outline width mul from vertex color
finalOutlineWidth = _UseOutlineWidthMaskFromVertexColor? finalOutlineWidth * dot(input.color, _OutlineWidthMaskFromVertexColor) : finalOutlineWidth;
// ExtraThickOutlinePass will affect finalOutlineWidth at last, to ensure width control is isolated
#if NiloToonExtraThickOutlinePass || NiloToonDepthOnlyOrDepthNormalPass
#if NiloToonDepthOnlyOrDepthNormalPass
if(_ExtraThickOutlineEnabled && _ExtraThickOutlineZWrite && _ExtraThickOutlineWriteIntoDepthTexture)
#endif
{
// apply by add, not mul, to make extra thick outline's width has it's own isolated width
finalOutlineWidth += _ExtraThickOutlineWidth;
// if user used different outline width per material,
// here we expose min(x,_ExtraThickOutlineMaxFinalWidth) for user to have a uniform final ExtraThickOutline width
finalOutlineWidth = min(finalOutlineWidth, _ExtraThickOutlineMaxFinalWidth);
}
#endif
if(_GlobalShouldDisableNiloToonZOffset)
{
// in planar reflection pass, _GlobalShouldDisableNiloToonZOffset will set to true (= ZOffset is disabled),
// so we don't render face's outline, else ugly outline will appear in planar reflection pass
#if _ISFACE
// see [a trick to "delete" any vertex] about this section's logic
output.positionCS.w = 0;
return output;
#endif
// https://docs.unity3d.com/ScriptReference/Rendering.CullMode.html
// 0 is Off
// 1 is Front
// 2 is Back
// in planar reflection pass, _GlobalShouldDisableNiloToonZOffset will set to true (= ZOffset is disabled),
// so don't render Cull off material's outline
// only Cull Off has problem, so only disable outline when Cull Off
finalOutlineWidth = _Cull == 0 ? 0 : finalOutlineWidth;
}
// do positionWS extrude for outline
positionWS = TransformPositionWSToOutlinePositionWS(finalOutlineWidth, vertexPositionInput, shouldOutlineUseBakedSmoothNormal ? possibleSmoothedNormalWS : vertexNormalInput.normalWS);
// do ExtraThickOutline's view space pos offset(in world space)
#if NiloToonExtraThickOutlinePass
// transform view space position offset to world space, apply offset in world space
positionWS += mul((float3x3)UNITY_MATRIX_I_V, _ExtraThickOutlineViewSpacePosOffset).xyz;
#endif
}
#endif
#if _NILOTOON_DEBUG_SHADING
output.uv8 = input.uv8; // for showing tangent space smoothed normal as debug color, in fragment shader
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// PositionWS
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
output.positionWS_ZOffsetFinalSum.xyz = positionWS;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// TangentWS
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if VaryingsHasTangentWS
real sign = input.tangentOS.w * GetOddNegativeScale();
output.tangentWS = half4(vertexNormalInput.tangentWS.xyz, sign);
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// PositionCS
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// we can't reuse vertexPositionInput.positionCS directly, because positionWS maybe edited in "Extrude positionWS for outline" section,
// hence we will need to recompute positionCS using TransformWorldToHClip() to ensure positionCS's correctness
#if !NiloToonCharSelfShadowCasterPass
output.positionCS = TransformWorldToHClip(positionWS);
#else
// in XR, VP matrix will be overridden by URP, you can't edit it via cmd.SetViewProjectionMatrices, so we need to use our own VP matrix
output.positionCS = mul(_NiloToonSelfShadowWorldToClip, float4(positionWS,1));
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// output normalWS
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
output.normalWS_averageShadowAttenuation.xyz = vertexNormalInput.normalWS;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// faceArea
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
half faceArea = GetFaceArea(GetUV(output));
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// viewDirTS
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// note: put this section after all require vectors by GetViewDirectionTangentSpace() are ready
#if VaryingsHasViewDirTS
half3 viewDirWS = GetWorldSpaceNormalizeViewDir(vertexPositionInput.positionWS);
half3 viewDirTS = GetViewDirectionTangentSpace(output.tangentWS, output.normalWS_averageShadowAttenuation.xyz, viewDirWS);
output.viewDirTS = viewDirTS;
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ZOffset sum init
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
float ZOffsetFinalSum = 0;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Outline ZOffset
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// this section will apply clip space ZOffset (view space unit input) to outline position
// doing this can hide ugly/unwanted outline, usually we will apply Outline ZOffset for face/eye
// Only apply to _CameraColorTexture's outline pass(NiloToonSelfOutlinePass), don't apply to any depth texture pass
#if NiloToonSelfOutlinePass
// we have separated settings for "face" and "not face" vertices
float outlineZOffset = lerp(_OutlineZOffset,_OutlineZOffsetForFaceArea,faceArea);
// [ZOffset mask from vertex color]
if(_UseOutlineZOffsetMaskFromVertexColor)
{
float zoffsetFromVertexColor = dot(input.color, _OutlineZOffsetMaskFromVertexColor);
zoffsetFromVertexColor = _OutlineZOffsetMaskTexFromVertexColorInvertColor ? 1 - zoffsetFromVertexColor : zoffsetFromVertexColor;
outlineZOffset *= zoffsetFromVertexColor;
}
// [ZOffset mask texture]
#if _OUTLINEZOFFSETMAP
float outlineZOffsetMask = 1;
// [Read ZOffset mask texture]
// we can't use tex2D() in vertex shader because ddx & ddy is unknown before rasterization,
// so use tex2Dlod() with an explicit mip level 0, you need to put explicit mip level 0 inside the 4th component of tex2Dlod()'s' input param
float outlineZOffsetTex2DlodExplicitMipLevel = 0;
outlineZOffsetMask = dot(tex2Dlod(_OutlineZOffsetMaskTex, float4(input.uv,0,outlineZOffsetTex2DlodExplicitMipLevel)),_OutlineZOffsetMaskTexChannelMask);
// [Remap ZOffset texture value]
// flip texture read value so default black area = apply ZOffset, because usually outline mask texture are using this format(black = hide outline, white = do nothing)
outlineZOffsetMask = 1 - outlineZOffsetMask;
// [allow user to invert again if needed]
outlineZOffsetMask = _OutlineZOffsetMaskTexInvertColor ? 1-outlineZOffsetMask : outlineZOffsetMask;
outlineZOffsetMask = invLerpClamp(_OutlineZOffsetMaskRemapStart,_OutlineZOffsetMaskRemapEnd,outlineZOffsetMask);// allow user to remap
// [Apply ZOffset, Use remapped value as ZOffset mask]
outlineZOffset *= outlineZOffsetMask;
#endif
outlineZOffset += _OutlineBaseZOffset;
// this line make ZOffset sync with camera distance corrected outline width
// If we don't do this, once camera is far away, zoffset will become not enough because outline width keep growing larger
// also stop reduce zoffset when camera is too close using max(1,x)
// TODO: we should share the GetOutlineCameraFovAndDistanceFixMultiplier() call to save some performance?
outlineZOffset *= max(1,GetOutlineCameraFovAndDistanceFixMultiplier(positionVS.z, _CurrentCameraFOV, _GlobalOutlineWidthAutoAdjustToCameraDistanceAndFOV * _OutlineApplyAutoWidthAdjustment) / 0.0025);
ZOffsetFinalSum += -outlineZOffset;
#endif
#if NiloToonExtraThickOutlinePass
ZOffsetFinalSum += -_ExtraThickOutlineZOffset;
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// URP Shadow bias (matching URP16's ShadowCasterPass.hlsl)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ShadowCaster pass needs special process to edit positionCS, else shadow artifact will appear
// see GetShadowPositionHClip() in URP/Shaders/ShadowCasterPass.hlsl
#if NiloToonShadowCasterPass
// Why not just call URP's GetShadowPositionHClip() directly?
// because it will require us to include ShadowCasterPass.hlsl
// which will include ShadowCasterPass.hlsl's struct Attributes, it is not what we want
// this part(PUNCTUAL_LIGHT_SHADOW) exists in URP12 or above's GetShadowPositionHClip()
#if _CASTING_PUNCTUAL_LIGHT_SHADOW
float3 lightDirectionWS = normalize(_LightPosition - positionWS);
#else
float3 lightDirectionWS = _LightDirection;
#endif
float3 normalWSForShadowCaster = possibleSmoothedNormalWS; // replace to smoothed normal if possible
// normalBias will produce "holes" in shadowmap, so we add a slider to control it globally
float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWSForShadowCaster * _GlobalToonShaderNormalBiasMultiplier, lightDirectionWS));
#if UNITY_REVERSED_Z
positionCS.z = min(positionCS.z, UNITY_NEAR_CLIP_VALUE);
#else
positionCS.z = max(positionCS.z, UNITY_NEAR_CLIP_VALUE);
#endif
output.positionCS = positionCS;
return output; //return, since later calculations should be not related to shadow caster pass anymore
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NiloToon Self Shadow caster pass's Shadow bias (matching URP16's ShadowCasterPass.hlsl)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NiloToon's self ShadowCaster pass needs special process to edit positionCS, else shadow artifact will appear
#if NiloToonCharSelfShadowCasterPass
{
// TODO: improve shadow bias using: https://zhuanlan.zhihu.com/p/370951892
//------------------------------------------------------------
// settings
float depthBias = min(_NiloToonGlobalSelfShadowCasterDepthBias + -_NiloToonSelfShadowMappingDepthBias * _EnableNiloToonSelfShadowMappingDepthBias,0);
float normalBias = min(_NiloToonGlobalSelfShadowCasterNormalBias + -_NiloToonSelfShadowMappingNormalBias * _EnableNiloToonSelfShadowMappingNormalBias,0);
//------------------------------------------------------------
// referencing GetShadowPositionHClip() in URP/Shaders/ShadowCasterPass.hlsl
float3 positionWS = vertexPositionInput.positionWS;
float3 normalWS = possibleSmoothedNormalWS; // replace to smoothed normal will produce better result
float3 lightDirection = _NiloToonSelfShadowLightDirection; // pointing from vertex to light
// copy from URP/ShaderLibrary/Shadows.hlsl's ApplyShadowBias(...)
//-------------------------------------------------------------
float invNdotL = 1.0 - saturate(dot(lightDirection, normalWS));
float scale = invNdotL * normalBias;
// normal bias is negative since we want to apply an inset normal offset
positionWS = lightDirection * depthBias.xxx + positionWS;
positionWS = normalWS * scale.xxx + positionWS;
//-------------------------------------------------------------
// copy from URP/Shaders/ShadowCasterPass.hlsl's GetShadowPositionHClip(...)
//-------------------------------------------------------------
#if !NiloToonCharSelfShadowCasterPass
float4 positionCS = TransformWorldToHClip(positionWS);
#else
// in XR, VP matrix will be overridden by URP, you can't edit it via cmd.SetViewProjectionMatrices, so we need to use our own VP matrix
float4 positionCS = mul(_NiloToonSelfShadowWorldToClip, float4(positionWS,1));
#endif
#if UNITY_REVERSED_Z
positionCS.z = min(positionCS.z, UNITY_NEAR_CLIP_VALUE);
#else
positionCS.z = max(positionCS.z, UNITY_NEAR_CLIP_VALUE);
#endif
output.positionCS = positionCS;
return output; //return, since later calculations should be not related to shadow caster pass anymore
//-------------------------------------------------------------
}
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ZOffset
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// per material ZOffset (originally created to push eyebrow over hair / face expression mesh (e.g >///<) over face like a alpha blend decal/sticker)
#if NiloToonIsAnyLitColorPass
if(_ZOffsetEnable)
{
float zoffset = -_ZOffset;
// let ZOffset affect outline pass also, with extra control
#if NiloToonSelfOutlinePass
zoffset *= _ZOffsetMultiplierForTraditionalOutlinePass;
#endif
#if _ZOFFSETMAP
float zOffsetTex2DlodExplicitMipLevel = 0;
float zOffsetMultiplierByTex = dot(_ZOffsetMaskMapChannelMask,tex2Dlod(_ZOffsetMaskTex, float4(input.uv,0,zOffsetTex2DlodExplicitMipLevel)));
zOffsetMultiplierByTex = _ZOffsetMaskMapInvertColor ? 1-zOffsetMultiplierByTex : zOffsetMultiplierByTex;
zoffset *= zOffsetMultiplierByTex;
#endif
ZOffsetFinalSum += zoffset;
}
#endif
// per character ZOffset
#if ApplyZOffset
ZOffsetFinalSum += -_PerCharZOffset;
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Vertex color
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
output.color = input.color;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SH (indirect from lightprobe)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// *moved from fragment shader to vertex shader for performance reason,
// it is ok due to lightprobe's low frequency result color data
// [calculate SH in vertex shader to save some cycles]
// If we call SampleSH(0), this will hide all 3D feeling by ignoring all detail SH.
// We default only use the constant term, = SampleSH(0)
// because we just want to get some average envi indirect color only.
// Hardcode 0 can enable compiler optimization to remove no-op,
// but here we don't hardcode 0, instead we use a uniform variable(_IndirectLightFlatten) to control the normal,
// which is slower but allow more flexibility for user
#if NiloToonIsAnyLitColorPass
half3 normalWSForSH = output.normalWS_averageShadowAttenuation.xyz;
normalWSForSH *= 1-_IndirectLightFlatten; // make the normal become 0 when _IndirectLightFlatten is 1
half3 SH = SampleSH(normalWSForSH) * _GlobalIndirectLightMultiplier;
output.SH_fogFactor.rgb = SH;
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Average Shadow Attenuation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// use LOAD_TEXTURE2D instead of tex2dLOD for simplify uv code and also for better performance
// _NiloToonAverageShadowMapRT is a "N x 1" texture, so LOAD index is (charID,0)
#if NiloToonIsAnyLitColorPass
if(_ControlledByNiloToonPerCharacterRenderController)
{
output.normalWS_averageShadowAttenuation.w = lerp(1,LOAD_TEXTURE2D(_NiloToonAverageShadowMapRT,float2(_CharacterID,0)).r,_PerCharReceiveAverageURPShadowMap); // floating point RT, so r channel
}
else
{
output.normalWS_averageShadowAttenuation.w = 1;
}
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Face's depth texture ZOffset
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// for face vertices, use ZOffset to push back depth write to _CameraDepthTexture (not _CameraColorTexture),
// doing this can:
// - prevent face cast 2D depth texture self shadow on face (which looks like artifact)
// - make hair/hat/sunglass... easier to cast 2D depth texture shadow on face
#if NiloToonDepthOnlyOrDepthNormalPass && _ISFACE
float cameraDepthTextureZOffsetMask = 1;
#if NeedFaceMaskArea
cameraDepthTextureZOffsetMask = faceArea;
#endif
// zoffset should be always greater or equal to depthDiffThreshold, to avoid face cast 2D depth texture self shadow
ZOffsetFinalSum += -_FaceAreaCameraDepthTextureZWriteOffset * cameraDepthTextureZOffsetMask * _NiloToonGlobalAllowUnityCameraDepthTextureWriteFaceZOffset;
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Apply ZOffset
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The reason to only apply ZOffset(NiloGetNewClipPosWithZOffsetVS()) only once per vertex shader, instead of multiple times,
// is to reduce precision loss.
// Precision loss exists if we call NiloGetNewClipPosWithZOffsetVS() multiple times in the same vertex shader.
#if ApplyZOffset
output.positionCS = NiloGetNewClipPosWithZOffsetVS(output.positionCS, ZOffsetFinalSum);
output.positionWS_ZOffsetFinalSum.w = ZOffsetFinalSum;
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Remove perspective camera distortion
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if ApplyPerspectiveRemoval
output.positionCS = NiloDoPerspectiveRemoval(output.positionCS,positionWS,_NiloToonGlobalPerCharHeadBonePosWSArray[_CharacterID],_PerspectiveRemovalRadius,_PerspectiveRemovalAmount, _PerspectiveRemovalStartHeight, _PerspectiveRemovalEndHeight);
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// User custom logic
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ApplyCustomUserLogicToVertexShaderOutputAtVertexShaderEnd(output, input);
return output;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Above is vertex shader section, below is fragment shader section
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// fragment shared functions (Step1: if DEBUG, run these DEBUG functions and early exit at fragment functions's early start point)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
half4 Get_NILOTOON_FORCE_MINIMUM_FRAGMENT_SHADER_result(Varyings input)
{
half3 debugColor = SAMPLE_TEXTURE2D(_BaseMap,sampler_BaseMap, GetUV(input)).rgb;
#if NiloToonIsAnyOutlinePass
debugColor *= 0.25; // any value is valid, we just want to darken outline a bit
#endif
return half4(debugColor,1);
}
half4 Get_NILOTOON_DEBUG_SHADING_result(Varyings input, ToonSurfaceData surfaceData, ToonLightingData lightingData, UVData uvData)
{
#if _NILOTOON_DEBUG_SHADING
// Performance is not important here, since it is a debug shader function.
// if _NILOTOON_DEBUG_SHADING is active, we only want to render NiloToonForwardLitPass
#if !NiloToonForwardLitPass
{
clip(-1);
return 0;
}
#endif
// Example of switch case in hlsl:
// https://github.com/SickheadGames/HL2GLSL/blob/master/tests/HL2GLSL/FlowControl.hlsl
switch(_GlobalToonShadeDebugCase)
{
case 0: return SAMPLE_TEXTURE2D(_BaseMap,sampler_BaseMap, lightingData.uv);
case 1: return half4(surfaceData.albedo, surfaceData.alpha);
case 2: return 1;
case 3: return half4(surfaceData.occlusion.xxx,1);
case 4: return half4(surfaceData.emission,1);
case 5: return half4(lightingData.normalWS * 0.5 + 0.5,1); // remap to match URP's normalWS in URP Rendering Debugger
case 6: return half4(lightingData.uv,0,1);
case 7: return half4(input.color.r.xxx,1);
case 8: return half4(input.color.g.xxx,1);
case 9: return half4(input.color.b.xxx,1);
case 10: return half4(input.color.a.xxx,1);
case 11: return half4(input.color.rgb,1);
case 12: return half4(surfaceData.specular.rgb * surfaceData.specularMask,1);
case 13: return half4(input.uv8 * 0.5 + 0.5,1);
case 14: return half4(surfaceData.albedo * dot(lightingData.normalWS,lightingData.viewDirectionWS), surfaceData.alpha);
case 15: return half4(lightingData.isFaceArea.xxx,1);
case 16: return half4(lightingData.isSkinArea.xxx,1);
case 17: return half4(surfaceData.alpha.xxx,1);
case 18: return half4(lightingData.normalVS,1);
case 19: return half4(lightingData.averageShadowAttenuation.xxx,1);
case 20: return half4(lightingData.SH,1);
case 21: return half4(uvData.GetUV(0),0,1);
case 22: return half4(uvData.GetUV(1),0,1);
case 23: return half4(uvData.GetUV(2),0,1);
case 24: return half4(uvData.GetUV(3),0,1);
case 25: return half4(input.SH_fogFactor.w.xxx,1);
case 26: return half4(lightingData.ZOffsetFinalSum.xxx,1);
case 27: return half4(lightingData.viewDirectionWS,1);
case 28: return half4(surfaceData.normalTS * 0.5 + 0.5,1); // remap to match URP's normalTS in URP Rendering Debugger
case 29: return half4(surfaceData.smoothness.xxx,1);
case 30: return half4(lightingData.facing.xxx,1);
case 31: return half4(lightingData.characterBound2DRectUV01,0,1);
case 32: return half4(lightingData.matcapUV * 0.5 + 0.5,0,1);
}
#endif
return 1;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// fragment shared functions (Step1.5: edit uv by parallax map)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// copy and modified using URP's LitInput.hlsl -> ApplyPerPixelDisplacement(...)
void ApplyPerPixelDisplacement(inout Varyings input)
{
// Note: this section will affect detail uv
#if _PARALLAXMAP
float2 uvoffset = ParallaxMapping(TEXTURE2D_ARGS(_ParallaxMap, sampler_ParallaxMap), input.viewDirTS, _Parallax, GetUV(input,_ParallaxSampleUVIndex));
switch(_ParallaxApplyToUVIndex)
{
case 0: input.uv01.xy += uvoffset; break;
case 1: input.uv01.zw += uvoffset; break;
case 2: input.uv23.xy += uvoffset; break;
case 3: input.uv23.zw += uvoffset; break;
}
#endif
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// fragment shared functions (Step2: prepare data structs for lighting calculation)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if AnyBaseMapStackingLayerEnabled
half4 GetStackingLayerRGBAResult(
float2 layerTexUV,
float4 layerTexUVTilingOffset,
float4 layerTexUVCenterPivotScalePos,
float2 layerTexUVScrollSpeed,
float layerTexRotatedAngle,
float layerTexRotateSpeed,
half4 layerTexTintColor,
half4 layerMaskTexChannel,
float2 layerMaskUV,
Texture2D layerTex,
Texture2D layerMaskTex,
SamplerState layerTexSamplerState,
float facing,
bool layerTexIgnoreAlpha,
float applyToFaces,
bool invertMask,
bool layerMaskAsID,
half layerMaskExtractFromID,
half layerMaskRemapStart,
half layerMaskRemapEnd
)
{
// UV's old method (in NiloToon 0.15.x or below)
//float2 stackingLayerUV = (uv + layerTexUVScaleOffset.zw + frac(_Time.yy * layerTexUVScrollSpeed)) * layerTexUVScaleOffset.xy;
// UV's new method (in NiloToon 0.16.0 or above, match lilToon's logic)
// (the result is different to the old method, it is an intended breaking change in order to match lilToon)
float2 stackingLayerUV = CalcUV(
layerTexUV,
layerTexUVTilingOffset,
layerTexUVCenterPivotScalePos,
_Time.y,
layerTexUVScrollSpeed,
layerTexRotatedAngle,
layerTexRotateSpeed);
// Note:
// "The Team Color Problem" by Ben Golus
// It is possible to improve the result by using "Pre-multiplied Color",
// but it will require the user to prepare special textures for it, which means it is not practical for NiloToon
// https://bgolus.medium.com/the-team-color-problem-b70ec69d109f
// Sample Texture, sharing the sampler to avoid sampler max count = 16 limit
half4 stackingLayerRGBAResult = SAMPLE_TEXTURE2D(layerTex, layerTexSamplerState, stackingLayerUV);
if(layerTexIgnoreAlpha)
{
stackingLayerRGBAResult.a = 1;
}
stackingLayerRGBAResult *= layerTexTintColor;
// for mask texture, we reuse _BaseMap's sampler to reduce sampler count since in most cases the setting should be the same
half stackingLayerMask = ExtractSingleChannel(SAMPLE_TEXTURE2D(layerMaskTex, sampler_BaseMap, layerMaskUV), layerMaskTexChannel);
stackingLayerMask = PostProcessMaskValue(stackingLayerMask,layerMaskAsID,layerMaskExtractFromID,invertMask,layerMaskRemapStart,layerMaskRemapEnd);
stackingLayerRGBAResult.a *= stackingLayerMask;
// render face mask
if(applyToFaces != 0)
{
if(applyToFaces == 1) stackingLayerRGBAResult.a *= saturate(-facing);
if(applyToFaces == 2) stackingLayerRGBAResult.a *= saturate(facing);
}
return stackingLayerRGBAResult;
}
void ApplyStackingLayer(inout half4 baseLayerRGBA, half4 topLayerRGBA, uint colorBlendMode, half applyStrength, bool isOpaque)
{
topLayerRGBA.a *= applyStrength;
// to prevent any opaque material using basemap with 0 alpha causing bug in NormalRGBA blending
if(isOpaque && colorBlendMode == 0)
{
// convert NormalRGBA -> NormalRGB
colorBlendMode = 1;
}
baseLayerRGBA = BlendColor(baseLayerRGBA, topLayerRGBA, colorBlendMode);
}
#endif
half4 GetFinalBaseColor(Varyings input, UVData uvData, float facing)
{
half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uvData.GetUV(_BaseMapUVIndex));
#if _ALPHAOVERRIDEMAP
half alphaOverrideStrength = _AlphaOverrideStrength;
if(_ApplyAlphaOverrideOnlyWhenFaceForwardIsPointingToCamera > 0)
{
// copied from https://docs.unity3d.com/Packages/com.unity.shadergraph@12.1/manual/Camera-Node.html?q=camera%20node
half3 cameraBackwardDir = mul(UNITY_MATRIX_M, float4(transpose(mul(UNITY_MATRIX_I_M, UNITY_MATRIX_I_V)) [2].xyz,0)).xyz;
half faceForwardFadeoutControl = invLerpClamp(_ApplyAlphaOverrideOnlyWhenFaceForwardIsPointingToCameraRemapStart, _ApplyAlphaOverrideOnlyWhenFaceForwardIsPointingToCameraRemapEnd,dot(_NiloToonGlobalPerCharFaceForwardDirWSArray[_CharacterID], cameraBackwardDir)); // find the angle between faceForward vector and cameraBackwardDir
faceForwardFadeoutControl = pow(faceForwardFadeoutControl,4); // fadeout faster
faceForwardFadeoutControl = lerp(1,faceForwardFadeoutControl, _ApplyAlphaOverrideOnlyWhenFaceForwardIsPointingToCamera); // allow user to control it
alphaOverrideStrength *= faceForwardFadeoutControl;
}
half newAlpha = dot(tex2D(_AlphaOverrideTex, uvData.GetUV(_AlphaOverrideTexUVIndex)),_AlphaOverrideTexChannelMask);
newAlpha = _AlphaOverrideTexInvertColor ? 1-newAlpha : newAlpha;
newAlpha = saturate(newAlpha * _AlphaOverrideTexValueScale + _AlphaOverrideTexValueOffset); // alpha MAD of liltoon
if(_AlphaOverrideMode == 0)
{
// Mode: Replace
color.a = lerp(color.a,newAlpha,alphaOverrideStrength);
}
else if(_AlphaOverrideMode == 1)
{
// Mode: Multiply
color.a = color.a * lerp(1,newAlpha,alphaOverrideStrength);
}
else if(_AlphaOverrideMode == 2)
{
// Mode: Add
color.a = saturate(color.a + newAlpha * alphaOverrideStrength);
}
else
{
// Mode: Subtract
color.a = saturate(color.a - newAlpha * alphaOverrideStrength);
}
#endif
color *= GetCombinedBaseColor(); // edit color's rgba(including alpha also, not just rgb), since _BaseColor/_BaseColor2/_Color is per material, using _BaseColor/_BaseColor2/_Color's alpha to control rendering alpha should be intentional by user
color.rgb *= _BaseMapBrightness;
// to prevent opaque material with 0 alpha to use RGBA normal blending
bool isOpaque = (_SrcBlend == 1) && (_DstBlend == 0);
#if _ALPHATEST_ON
isOpaque = isOpaque && (_Cutoff <= 0);
#endif
// add 10 photoshop layer, after _BaseMap's edit finished.
// This is designed for face makeup, decal, logo, tattoo etc
#if _BASEMAP_STACKING_LAYER1
{
const half4 layer1Content = GetStackingLayerRGBAResult(
uvData.GetUV(_BaseMapStackingLayer1TexUVIndex),
_BaseMapStackingLayer1TexUVScaleOffset,
_BaseMapStackingLayer1TexUVCenterPivotScalePos,
_BaseMapStackingLayer1TexUVAnimSpeed,
_BaseMapStackingLayer1TexUVRotatedAngle,
_BaseMapStackingLayer1TexUVRotateSpeed,
_BaseMapStackingLayer1TintColor,
_BaseMapStackingLayer1MaskTexChannel,
uvData.GetUV(_BaseMapStackingLayer1MaskUVIndex),
_BaseMapStackingLayer1Tex,
_BaseMapStackingLayer1MaskTex,
sampler_BaseMapStackingLayer1Tex, // only the first 2 layers use it's own sampler, to allow more control for user
facing,
_BaseMapStackingLayer1TexIgnoreAlpha,
_BaseMapStackingLayer1ApplytoFaces,
_BaseMapStackingLayer1MaskInvertColor,
_BaseMapStackingLayer1MaskTexAsIDMap,
_BaseMapStackingLayer1MaskTexExtractFromID,
_BaseMapStackingLayer1MaskRemapStart,
_BaseMapStackingLayer1MaskRemapEnd);
ApplyStackingLayer(color, layer1Content, _BaseMapStackingLayer1ColorBlendMode, _BaseMapStackingLayer1MasterStrength, isOpaque);
}
#endif
#if _BASEMAP_STACKING_LAYER2
{
const half4 layer2Content = GetStackingLayerRGBAResult(
uvData.GetUV(_BaseMapStackingLayer2TexUVIndex),
_BaseMapStackingLayer2TexUVScaleOffset,
_BaseMapStackingLayer2TexUVCenterPivotScalePos,
_BaseMapStackingLayer2TexUVAnimSpeed,
_BaseMapStackingLayer2TexUVRotatedAngle,
_BaseMapStackingLayer2TexUVRotateSpeed,
_BaseMapStackingLayer2TintColor,
_BaseMapStackingLayer2MaskTexChannel,
uvData.GetUV(_BaseMapStackingLayer2MaskUVIndex),
_BaseMapStackingLayer2Tex,
_BaseMapStackingLayer2MaskTex,
sampler_BaseMapStackingLayer2Tex, // only the first 2 layers use it's own sampler, to allow more control for user
facing,
_BaseMapStackingLayer2TexIgnoreAlpha,
_BaseMapStackingLayer2ApplytoFaces,
_BaseMapStackingLayer2MaskInvertColor,
_BaseMapStackingLayer2MaskTexAsIDMap,
_BaseMapStackingLayer2MaskTexExtractFromID,
_BaseMapStackingLayer2MaskRemapStart,
_BaseMapStackingLayer2MaskRemapEnd);
ApplyStackingLayer(color, layer2Content, _BaseMapStackingLayer2ColorBlendMode, _BaseMapStackingLayer2MasterStrength, isOpaque);
}
#endif
#if _BASEMAP_STACKING_LAYER3
{
const half4 layer3Content = GetStackingLayerRGBAResult(
uvData.GetUV(_BaseMapStackingLayer3TexUVIndex),
_BaseMapStackingLayer3TexUVScaleOffset,
_BaseMapStackingLayer3TexUVCenterPivotScalePos,
_BaseMapStackingLayer3TexUVAnimSpeed,
_BaseMapStackingLayer3TexUVRotatedAngle,
_BaseMapStackingLayer3TexUVRotateSpeed,
_BaseMapStackingLayer3TintColor,
_BaseMapStackingLayer3MaskTexChannel,
uvData.GetUV(_BaseMapStackingLayer3MaskUVIndex),
_BaseMapStackingLayer3Tex,
_BaseMapStackingLayer3MaskTex,
sampler_linear_clamp, // for layer 3-10, we hardcode sampler_linear_clamp or sampler_linear_repeat to avoid adding more sampler count
facing,
_BaseMapStackingLayer3TexIgnoreAlpha,
_BaseMapStackingLayer3ApplytoFaces,
_BaseMapStackingLayer3MaskInvertColor,
_BaseMapStackingLayer3MaskTexAsIDMap,
_BaseMapStackingLayer3MaskTexExtractFromID,
_BaseMapStackingLayer3MaskRemapStart,
_BaseMapStackingLayer3MaskRemapEnd);
ApplyStackingLayer(color, layer3Content, _BaseMapStackingLayer3ColorBlendMode, _BaseMapStackingLayer3MasterStrength, isOpaque);
}
#endif
#if _BASEMAP_STACKING_LAYER4
{
const half4 layer4Content = GetStackingLayerRGBAResult(
uvData.GetUV(_BaseMapStackingLayer4TexUVIndex),
_BaseMapStackingLayer4TexUVScaleOffset,
_BaseMapStackingLayer4TexUVCenterPivotScalePos,
_BaseMapStackingLayer4TexUVAnimSpeed,
_BaseMapStackingLayer4TexUVRotatedAngle,
_BaseMapStackingLayer4TexUVRotateSpeed,
_BaseMapStackingLayer4TintColor,
_BaseMapStackingLayer4MaskTexChannel,
uvData.GetUV(_BaseMapStackingLayer4MaskUVIndex),
_BaseMapStackingLayer4Tex,
_BaseMapStackingLayer4MaskTex,
sampler_linear_repeat, // for layer 3-10, we hardcode sampler_linear_clamp or sampler_linear_repeat to avoid adding more sampler count
facing,
_BaseMapStackingLayer4TexIgnoreAlpha,
_BaseMapStackingLayer4ApplytoFaces,
_BaseMapStackingLayer4MaskInvertColor,
_BaseMapStackingLayer4MaskTexAsIDMap,
_BaseMapStackingLayer4MaskTexExtractFromID,
_BaseMapStackingLayer4MaskRemapStart,
_BaseMapStackingLayer4MaskRemapEnd);
ApplyStackingLayer(color, layer4Content, _BaseMapStackingLayer4ColorBlendMode, _BaseMapStackingLayer4MasterStrength, isOpaque);
}
#endif
#if _BASEMAP_STACKING_LAYER5
{
const half4 layer5Content = GetStackingLayerRGBAResult(
uvData.GetUV(_BaseMapStackingLayer5TexUVIndex),
_BaseMapStackingLayer5TexUVScaleOffset,
_BaseMapStackingLayer5TexUVCenterPivotScalePos,
_BaseMapStackingLayer5TexUVAnimSpeed,
_BaseMapStackingLayer5TexUVRotatedAngle,
_BaseMapStackingLayer5TexUVRotateSpeed,
_BaseMapStackingLayer5TintColor,
_BaseMapStackingLayer5MaskTexChannel,
uvData.GetUV(_BaseMapStackingLayer5MaskUVIndex),
_BaseMapStackingLayer5Tex,
_BaseMapStackingLayer5MaskTex,
sampler_linear_clamp, // for layer 3-10, we hardcode sampler_linear_clamp or sampler_linear_repeat to avoid adding more sampler count
facing,
_BaseMapStackingLayer5TexIgnoreAlpha,
_BaseMapStackingLayer5ApplytoFaces,
_BaseMapStackingLayer5MaskInvertColor,
_BaseMapStackingLayer5MaskTexAsIDMap,
_BaseMapStackingLayer5MaskTexExtractFromID,
_BaseMapStackingLayer5MaskRemapStart,
_BaseMapStackingLayer5MaskRemapEnd);
ApplyStackingLayer(color, layer5Content, _BaseMapStackingLayer5ColorBlendMode, _BaseMapStackingLayer5MasterStrength, isOpaque);
}
#endif
#if _BASEMAP_STACKING_LAYER6
{
const half4 layer6Content = GetStackingLayerRGBAResult(
uvData.GetUV(_BaseMapStackingLayer6TexUVIndex),
_BaseMapStackingLayer6TexUVScaleOffset,
_BaseMapStackingLayer6TexUVCenterPivotScalePos,
_BaseMapStackingLayer6TexUVAnimSpeed,
_BaseMapStackingLayer6TexUVRotatedAngle,
_BaseMapStackingLayer6TexUVRotateSpeed,
_BaseMapStackingLayer6TintColor,
_BaseMapStackingLayer6MaskTexChannel,
uvData.GetUV(_BaseMapStackingLayer6MaskUVIndex),
_BaseMapStackingLayer6Tex,
_BaseMapStackingLayer6MaskTex,
sampler_linear_repeat, // for layer 3-10, we hardcode sampler_linear_clamp or sampler_linear_repeat to avoid adding more sampler count
facing,
_BaseMapStackingLayer6TexIgnoreAlpha,
_BaseMapStackingLayer6ApplytoFaces,
_BaseMapStackingLayer6MaskInvertColor,
_BaseMapStackingLayer6MaskTexAsIDMap,
_BaseMapStackingLayer6MaskTexExtractFromID,
_BaseMapStackingLayer6MaskRemapStart,
_BaseMapStackingLayer6MaskRemapEnd);
ApplyStackingLayer(color, layer6Content, _BaseMapStackingLayer6ColorBlendMode, _BaseMapStackingLayer6MasterStrength, isOpaque);
}
#endif
#if _BASEMAP_STACKING_LAYER7
{
const half4 layer7Content = GetStackingLayerRGBAResult(
uvData.GetUV(_BaseMapStackingLayer7TexUVIndex),
_BaseMapStackingLayer7TexUVScaleOffset,
_BaseMapStackingLayer7TexUVCenterPivotScalePos,
_BaseMapStackingLayer7TexUVAnimSpeed,
_BaseMapStackingLayer7TexUVRotatedAngle,
_BaseMapStackingLayer7TexUVRotateSpeed,
_BaseMapStackingLayer7TintColor,
_BaseMapStackingLayer7MaskTexChannel,
uvData.GetUV(_BaseMapStackingLayer7MaskUVIndex),
_BaseMapStackingLayer7Tex,
_BaseMapStackingLayer7MaskTex,
sampler_linear_clamp, // for layer 3-10, we hardcode sampler_linear_clamp or sampler_linear_repeat to avoid adding more sampler count
facing,
_BaseMapStackingLayer7TexIgnoreAlpha,
_BaseMapStackingLayer7ApplytoFaces,
_BaseMapStackingLayer7MaskInvertColor,
_BaseMapStackingLayer7MaskTexAsIDMap,
_BaseMapStackingLayer7MaskTexExtractFromID,
_BaseMapStackingLayer7MaskRemapStart,
_BaseMapStackingLayer7MaskRemapEnd);
ApplyStackingLayer(color, layer7Content, _BaseMapStackingLayer7ColorBlendMode, _BaseMapStackingLayer7MasterStrength, isOpaque);
}
#endif
#if _BASEMAP_STACKING_LAYER8
{
const half4 layer8Content = GetStackingLayerRGBAResult(
uvData.GetUV(_BaseMapStackingLayer8TexUVIndex),
_BaseMapStackingLayer8TexUVScaleOffset,
_BaseMapStackingLayer8TexUVCenterPivotScalePos,
_BaseMapStackingLayer8TexUVAnimSpeed,
_BaseMapStackingLayer8TexUVRotatedAngle,
_BaseMapStackingLayer8TexUVRotateSpeed,
_BaseMapStackingLayer8TintColor,
_BaseMapStackingLayer8MaskTexChannel,
uvData.GetUV(_BaseMapStackingLayer8MaskUVIndex),
_BaseMapStackingLayer8Tex,
_BaseMapStackingLayer8MaskTex,
sampler_linear_repeat, // for layer 3-10, we hardcode sampler_linear_clamp or sampler_linear_repeat to avoid adding more sampler count
facing,
_BaseMapStackingLayer8TexIgnoreAlpha,
_BaseMapStackingLayer8ApplytoFaces,
_BaseMapStackingLayer8MaskInvertColor,
_BaseMapStackingLayer8MaskTexAsIDMap,
_BaseMapStackingLayer8MaskTexExtractFromID,
_BaseMapStackingLayer8MaskRemapStart,
_BaseMapStackingLayer8MaskRemapEnd);
ApplyStackingLayer(color, layer8Content, _BaseMapStackingLayer8ColorBlendMode, _BaseMapStackingLayer8MasterStrength, isOpaque);
}
#endif
#if _BASEMAP_STACKING_LAYER9
{
const half4 layer9Content = GetStackingLayerRGBAResult(
uvData.GetUV(_BaseMapStackingLayer9TexUVIndex),
_BaseMapStackingLayer9TexUVScaleOffset,
_BaseMapStackingLayer9TexUVCenterPivotScalePos,
_BaseMapStackingLayer9TexUVAnimSpeed,
_BaseMapStackingLayer9TexUVRotatedAngle,
_BaseMapStackingLayer9TexUVRotateSpeed,
_BaseMapStackingLayer9TintColor,
_BaseMapStackingLayer9MaskTexChannel,
uvData.GetUV(_BaseMapStackingLayer9MaskUVIndex),
_BaseMapStackingLayer9Tex,
_BaseMapStackingLayer9MaskTex,
sampler_linear_clamp, // for layer 3-10, we hardcode sampler_linear_clamp or sampler_linear_repeat to avoid adding more sampler count
facing,
_BaseMapStackingLayer9TexIgnoreAlpha,
_BaseMapStackingLayer9ApplytoFaces,
_BaseMapStackingLayer9MaskInvertColor,
_BaseMapStackingLayer9MaskTexAsIDMap,
_BaseMapStackingLayer9MaskTexExtractFromID,
_BaseMapStackingLayer9MaskRemapStart,
_BaseMapStackingLayer9MaskRemapEnd);
ApplyStackingLayer(color, layer9Content, _BaseMapStackingLayer9ColorBlendMode, _BaseMapStackingLayer9MasterStrength, isOpaque);
}
#endif
#if _BASEMAP_STACKING_LAYER10
{
const half4 layer10Content = GetStackingLayerRGBAResult(
uvData.GetUV(_BaseMapStackingLayer10TexUVIndex),
_BaseMapStackingLayer10TexUVScaleOffset,
_BaseMapStackingLayer10TexUVCenterPivotScalePos,
_BaseMapStackingLayer10TexUVAnimSpeed,
_BaseMapStackingLayer10TexUVRotatedAngle,
_BaseMapStackingLayer10TexUVRotateSpeed,
_BaseMapStackingLayer10TintColor,
_BaseMapStackingLayer10MaskTexChannel,
uvData.GetUV(_BaseMapStackingLayer10MaskUVIndex),
_BaseMapStackingLayer10Tex,
_BaseMapStackingLayer10MaskTex,
sampler_linear_repeat, // for layer 3-10, we hardcode sampler_linear_clamp or sampler_linear_repeat to avoid adding more sampler count
facing,
_BaseMapStackingLayer10TexIgnoreAlpha,
_BaseMapStackingLayer10ApplytoFaces,
_BaseMapStackingLayer10MaskInvertColor,
_BaseMapStackingLayer10MaskTexAsIDMap,
_BaseMapStackingLayer10MaskTexExtractFromID,
_BaseMapStackingLayer10MaskRemapStart,
_BaseMapStackingLayer10MaskRemapEnd);
ApplyStackingLayer(color, layer10Content, _BaseMapStackingLayer10ColorBlendMode, _BaseMapStackingLayer10MasterStrength, isOpaque);
}
#endif
// final per character + global edit
color.rgb *= _PerCharacterBaseColorTint * _GlobalVolumeBaseColorTintColor; // edit rgb only, since they are not per material, edit alpha per character/globally should be not intentional by user
return color;
}
half3 GetFinalEmissionColor(Varyings input, half3 baseColor)
{
#if _EMISSION
float2 uv = GetUV(input) * _EmissionMapTilingXyOffsetZw.xy + _EmissionMapTilingXyOffsetZw.zw + frac(_Time.yy * _EmissionMapUVScrollSpeed);
half4 emissionMapSampleValue = tex2D(_EmissionMap, uv);
half3 emissionResult = _EmissionMapUseSingleChannelOnly ? dot(emissionMapSampleValue,_EmissionMapSingleChannelMask) : emissionMapSampleValue.rgb; // alpha is ignored if using rgb mode
emissionResult *= _EmissionColor.rgb * _EmissionIntensity;
emissionResult *= lerp(1,baseColor,_MultiplyBaseColorToEmissionColor); // let user optionally mix base color to emission color
// anim tint ramp map
#if _EMISSION_ANIM_TINT_RAMPMAP
half3 rampSampleColor = SAMPLE_TEXTURE2D_LOD(_EmissionAnimTintRampMap, sampler_linear_clamp, half2(frac(_Time.y * _EmissionAnimTintRampMapSpeed), 0.5),0).rgb;
emissionResult *= rampSampleColor;
#endif
// mask by an optional mask texture
// (decided to not use shader_feature for this mask section, else too much shader_feature is used)
float2 maskMapUV = GetUV(input);
half emissionMask = dot(SAMPLE_TEXTURE2D(_EmissionMaskMap, sampler_BaseMap, maskMapUV),_EmissionMaskMapChannelMask); // reuse sampler_BaseMap to save sampler count
emissionMask = _EmissionMaskMapInvertColor? 1-emissionMask : emissionMask;
emissionMask = invLerpClamp(_EmissionMaskMapRemapStart, _EmissionMaskMapRemapEnd, emissionMask);
emissionResult *= emissionMask;
return emissionResult;
#else
return 0; // default emission value is black when turn off
#endif
}
half GetFinalOcculsion(UVData uvData, float facing)
{
#if _OCCLUSIONMAP
half4 texValue = tex2D(_OcclusionMap, uvData.GetUV(_OcclusionMapUVIndex));
half occlusionValue = dot(texValue, _OcclusionMapChannelMask);
occlusionValue = _OcclusionMapInvertColor? 1-occlusionValue : occlusionValue;
occlusionValue = invLerpClamp(_OcclusionRemapStart, _OcclusionRemapEnd, occlusionValue); // should remap first,
// render face mask (Both,0 | Front,2 | Back,1)
half shouldApplyToTargetRenderFace = !((_OcclusionMapApplytoFaces == 1 && facing == 1.0) || (_OcclusionMapApplytoFaces == 2 && facing == -1.0));
occlusionValue = lerp(1, occlusionValue, _OcclusionStrength * _GlobalOcclusionStrength * shouldApplyToTargetRenderFace); // then apply per material and per volume fadeout.
return occlusionValue;
#else
return 1; // default occulusion value is 1 when turn off
#endif
}
half GetFinalSmoothness(Varyings input)
{
half smoothnessResult = _Smoothness;
#if _SMOOTHNESSMAP
half4 texValue = tex2D(_SmoothnessMap, GetUV(input));
half smoothnessMultiplierByTexture = dot(texValue, _SmoothnessMapChannelMask);
smoothnessMultiplierByTexture = _SmoothnessMapInputIsRoughnessMap ? 1-smoothnessMultiplierByTexture : smoothnessMultiplierByTexture;
smoothnessMultiplierByTexture = invLerpClamp(_SmoothnessMapRemapStart, _SmoothnessMapRemapEnd, smoothnessMultiplierByTexture); // remap
smoothnessResult *= smoothnessMultiplierByTexture; // apply
#endif
return smoothnessResult;
}
// return .rgb is specular RGB, return .a is specular mask
half4 GetFinalSpecularRGBA(UVData uvData, half3 baseColor, float facing)
{
#if _SPECULARHIGHLIGHTS
// mask
half4 texValue = tex2D(_SpecularMap, uvData.GetUV(_SpecularMapUVIndex));
half specularMask = dot(texValue, _SpecularMapChannelMask);
specularMask = _SpecularMapAsIDMap ? abs(specularMask * 255 - _SpecularMapExtractFromID) < 2 ? 1 : 0 : specularMask;
specularMask = _SpecularMapInvertColor ? 1 - specularMask : specularMask;
specularMask = invLerpClamp(_SpecularMapRemapStart, _SpecularMapRemapEnd, specularMask); // should remap first,
// * render face mask
if(_SpecularApplytoFaces != 0)
{
if(_SpecularApplytoFaces == 1) specularMask *= saturate(-facing);
if(_SpecularApplytoFaces == 2) specularMask *= saturate(facing);
}
half3 specularRGBResult = _SpecularColor * _SpecularIntensity;// then apply intensity / color
specularRGBResult *= lerp(1,baseColor,_MultiplyBaseColorToSpecularColor); // let user optionally mix base color to specular color
#if _SPECULARHIGHLIGHTS_TEX_TINT
float2 specularColorTintMapUV = uvData.GetUV(_SpecularColorTintMapUseSecondUv ? 1 : 0);
specularColorTintMapUV = specularColorTintMapUV * _SpecularColorTintMapTilingXyOffsetZw.xy + _SpecularColorTintMapTilingXyOffsetZw.zw;
specularRGBResult *= lerp(1,tex2D(_SpecularColorTintMap, specularColorTintMapUV).rgb,_SpecularColorTintMapUsage);
#endif
return half4(specularRGBResult,specularMask);
#else
return 0; // default specular value is 0 when turn off
#endif
}
half3 GetFinalNormalTS(UVData uvData, float facing)
{
#if _NORMALMAP
// render face mask (Both,0 | Front,2 | Back,1)
if((_BumpMapApplytoFaces == 1 && facing == 1.0) || (_BumpMapApplytoFaces == 2 && facing == -1.0))
{
return half3(0,0,1);
}
const float2 uv = CalcUV(uvData, _BumpMapUVIndex, _BumpMapUVScaleOffset, _BumpMapUVScrollSpeed);
return UnpackNormalScale(tex2D(_BumpMap, uv), _BumpScale);
#else
return half3(0,0,1); //default value of normal map when turn off, pointing out in tangent space, if converted back to world space it will be the same as raw vertex normal
#endif
}
void DoClipTestToTargetAlphaValue(half alpha)
{
#if _ALPHATEST_ON
clip(alpha - _Cutoff);
#endif
}
uint NiloGetMeshRenderingLayer()
{
// SHADER_LIBRARY_VERSION_MAJOR is deprecated for Unity2022.2 or later, so we will use UNITY_VERSION instead
// see -> https://github.com/Cyanilux/URP_ShaderCodeTemplates/blob/main/URP_SimpleLitTemplate.shader#L145
#if UNITY_VERSION >= 202220 // (for URP 14 or above)
uint meshRenderingLayers = GetMeshRenderingLayer();
#else
uint meshRenderingLayers = GetMeshRenderingLightLayer();
#endif
return meshRenderingLayers;
}
InputData NiloGetLightLoopInputData(ToonLightingData lightingData)
{
// LIGHT_LOOP_BEGIN needs:
// - inputData.normalizedScreenSpaceUV
// - inputData.positionWS
// so we create a temp InputData
InputData inputData = (InputData)0;
inputData.normalizedScreenSpaceUV = lightingData.normalizedScreenSpaceUV;
inputData.positionWS = lightingData.positionWS;
return inputData;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copy and simplify from URP10.2.2's LitInput.hlsl's detail map logic - START
// this section only exist #if _DETAIL
#if _DETAIL
// Used for scaling detail albedo. Main features:
// - Depending if detailAlbedo brightens or darkens, scale magnifies effect.
// - No effect is applied if detailAlbedo is 0.5.
half3 ScaleDetailAlbedo(half3 detailAlbedo, half scale)
{
// detailAlbedo = detailAlbedo * 2.0h - 1.0h;
// detailAlbedo *= _DetailAlbedoMapScale;
// detailAlbedo = detailAlbedo * 0.5h + 0.5h;
// return detailAlbedo * 2.0f;
// A bit more optimized
return 2.0h * detailAlbedo * scale - scale + 1.0h;
}
half3 ApplyDetailAlbedo(float2 detailUv, half3 albedo, half detailMask)
{
half3 detailAlbedo = SAMPLE_TEXTURE2D(_DetailAlbedoMap, sampler_DetailAlbedoMap, detailUv).rgb;
detailAlbedo = ScaleDetailAlbedo(detailAlbedo, _DetailAlbedoMapScale);
detailAlbedo *= 0.5 / _DetailAlbedoWhitePoint;
return albedo * LerpWhiteTo(detailAlbedo, detailMask); // apply detail albedo's method is just 1 simple multiply
}
half3 ApplyDetailNormal(float2 detailUv, half3 normalTS, half detailMask)
{
// Not using UnpackNormal(...) for mobile & switch now
// because we want to unify mobile and non mobile detail normalmap rendering, since performance differernce is small
// honestly no one will care 1 more MUL in 2021, even for mobile
half3 detailNormalTS = UnpackNormalScale(SAMPLE_TEXTURE2D(_DetailNormalMap, sampler_DetailNormalMap, detailUv), _DetailNormalMapScale);
// With UNITY_NO_DXT5nm unpacked vector is not normalized for BlendNormalRNM
// For visual consistancy we should do normalize() in all cases,
// but here we only normalize #if UNITY_NO_DXT5nm, for performance reason
#if UNITY_NO_DXT5nm
detailNormalTS = normalize(detailNormalTS);
#endif
// TODO: detailMask should lerp the angle of the quaternion rotation, not the normals
return lerp(normalTS, BlendNormalRNM(normalTS, detailNormalTS), detailMask);
}
#endif
// Copy and simplify from URP10.2.2's LitInput.hlsl's detail map logic - END
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// similar to URP's LitForwardPass.hlsl -> InitializeStandardLitSurfaceData()
ToonSurfaceData InitializeSurfaceData(Varyings input, UVData uvData, float facing, half3 normalTS, half detailMask, float2 detailUV)
{
ToonSurfaceData output;
// albedo(Base Color) & alpha
half4 baseColorFinal = GetFinalBaseColor(input, uvData, facing);
ApplyExternalAssetSupportLogicToBaseColor(baseColorFinal, input, uvData, facing);
ApplyCustomUserLogicToBaseColor(baseColorFinal, input, uvData, facing);
output.albedo = baseColorFinal.rgb;
output.alpha = baseColorFinal.a;
// alpha clip and dither fadeout
DoClipTestToTargetAlphaValue(output.alpha);// let clip() early exit asap once alpha value is known
#if _NILOTOON_DITHER_FADEOUT
NiloDoDitherFadeoutClip(input.positionCS.xy, 1-_DitherFadeoutAmount*_AllowPerCharacterDitherFadeout);
#endif
// occlusion
output.occlusion = GetFinalOcculsion(uvData, facing);
// smoothness
output.smoothness = GetFinalSmoothness(input);
// normalTS
output.normalTS = normalTS;
// Detail albedo and normal (only enable in non-DEBUG)
#if _DETAIL && !_NILOTOON_DEBUG_SHADING
output.albedo = ApplyDetailAlbedo(detailUV, output.albedo, detailMask);
#endif
///////////////////////////////////////////////////////////
// after Detail albedo and normal,
// do all functions that require detail albedo and normal
///////////////////////////////////////////////////////////
// emission
output.emission = GetFinalEmissionColor(input, output.albedo);
// specular & specular mask (not roughness)
half4 specularResult = GetFinalSpecularRGBA(uvData, output.albedo, facing);
output.specular = specularResult.rgb;
output.specularMask = specularResult.a;
return output;
}
UVData InitializeUV0ToUV3(Varyings input)
{
UVData uvData = (UVData)0; // init to 0 to make compiler happy
uvData.allUVs[0] = input.uv01.xy;
uvData.allUVs[1] = input.uv01.zw;
uvData.allUVs[2] = input.uv23.xy;
uvData.allUVs[3] = input.uv23.zw;
return uvData;
}
half3 CalculateLightInjectionWeightedDirection(Light light, half Avg3AdditionalLightPixelColor)
{
return light.direction * Avg3AdditionalLightPixelColor;
}
#if NeedCalculateAdditionalLight
struct NiloToonPerAdditionalLightData
{
half injectIntoMainLightColor;
half injectIntoMainLightDirection;
half injectIntoMainLightColorApplyDesaturate;
half additiveLightIntensity;
half injectIntoMainLightBackLightOcclusion2D;
half injectIntoMainLightBackLightOcclusion3D;
bool ignoreRenderingLayer;
};
NiloToonPerAdditionalLightData GetNiloToonPerAdditionalLightData(int lightIndex)
{
// copy the code of GetAdditionalLight(...) for find the final GPU array index
#if NILO_USE_CLUSTER_LIGHT_LOOP
int realGPULightArrayIndex = lightIndex;
#else
int realGPULightArrayIndex = GetPerObjectLightIndex(lightIndex);
#endif
half4 niloToonPerUnityLightData = _NiloToonGlobalPerUnityLightDataArray[realGPULightArrayIndex];
half4 niloToonPerUnityLightData2 = _NiloToonGlobalPerUnityLightDataArray2[realGPULightArrayIndex];
NiloToonPerAdditionalLightData data;
data.injectIntoMainLightColor = niloToonPerUnityLightData.x;
data.injectIntoMainLightDirection = niloToonPerUnityLightData.y;
data.injectIntoMainLightColorApplyDesaturate = niloToonPerUnityLightData.z;
data.additiveLightIntensity = niloToonPerUnityLightData.w;
data.injectIntoMainLightBackLightOcclusion2D = niloToonPerUnityLightData2.x;
data.injectIntoMainLightBackLightOcclusion3D = niloToonPerUnityLightData2.y;
data.ignoreRenderingLayer = niloToonPerUnityLightData2.z > 0.5;
return data;
}
#endif
// similar to URP's LitForwardPass.hlsl -> InitializeInputData()
ToonLightingData InitializeLightingData(Varyings input, half3 normalTS, float facing, half faceArea)
{
ToonLightingData lightingData;
lightingData.uv = GetUV(input);
lightingData.positionWS = input.positionWS_ZOffsetFinalSum.xyz;
lightingData.viewDirectionWS = GetWorldSpaceNormalizeViewDir(lightingData.positionWS);
half3 normalWS = input.normalWS_averageShadowAttenuation.xyz;
// We should re-normalize all direction unit vector after interpolation.
// Here even _NORMALMAP is false, we still normalize() to ensure correctness (unit vector in vertex shader, after interpolation, is NOT always unit vector in fragment shader).
// Not doing NormalizeNormalPerPixel(normalWS) will affect GGX specular result greatly since specular depends on normal quality heavily!
normalWS = NormalizeNormalPerPixel(normalWS);
lightingData.normalWS = normalWS;
lightingData.normalWS_NoNormalMap = normalWS; // extra: save the normalized interpolated vertex normal into lightingData, don't let normal map affect this vector
#if VaryingsHasTangentWS
// [you can reference this section from URP's LitForwardPass.hlsl -> InitializeInputData(...)]
float sgn = input.tangentWS.w; // should be either +1 or -1. No need to apply unity_WorldTransformParams here because it was applied in vertex shader already
// no need to normalize bitangentWS if you only use normalWS in later calculations,
// since we normalize normalWS at the end anyway when we assign normalWS's result to lightingData.
float3 bitangentWS = sgn * cross(input.normalWS_averageShadowAttenuation.xyz, input.tangentWS.xyz);
lightingData.TBN_WS = half3x3(input.tangentWS.xyz, bitangentWS, input.normalWS_averageShadowAttenuation.xyz);
#if defined(VaryingsHasViewDirTS)
half3 viewDirTS = input.viewDirTS;
#else
half3 viewDirTS = GetViewDirectionTangentSpace(input.tangentWS, input.normalWS_averageShadowAttenuation.xyz, lightingData.viewDirectionWS);
#endif
lightingData.viewDirectionTS = viewDirTS;
#endif
// if any normalmap is enabled, convert normalTS in normalmap to normalWS
#if _NORMALMAP || _DETAIL
normalWS = TransformTangentToWorld(normalTS, lightingData.TBN_WS); // apply normal-mapped result normalWS (rotation-only matrix's transpose equals inverse)
normalWS = NormalizeNormalPerPixel(normalWS); // since T & B are not normalized, we need to normalize result normalWS after TBN matrix mul
lightingData.normalWS = normalWS;
#endif
//---------------------------------------------------------------------------------------
// all normal map related data should init after normalmap is applied to lightingData.normalWS
//---------------------------------------------------------------------------------------
// if no one use lightingData.normalVS, unity's shader compiler will remove this line's calculation,
// same as URP's VertexPositionInputs and VertexNormalInputs struct
lightingData.normalVS = mul((half3x3)UNITY_MATRIX_V, lightingData.normalWS).xyz;
// see URP's GlobalIllumination.hlsl -> half3 GlobalIllumination(...)'s reflectVector
lightingData.reflectionVectorWS = reflect(-lightingData.viewDirectionWS,lightingData.normalWS);
lightingData.NdotV = saturate(dot(lightingData.normalWS,lightingData.viewDirectionWS));
// Note: NiloToon don't have "shadowCoord" from vertex shader, "shadowCoord" is always calculated in fragment shader
// ...
// [Why not passing isFaceArea from vertex shader, but recalculate in frag shader again instead?]
// because the gap between vertices will produce ugly isFaceArea in fragment shader due to interpolation, so we need to calculate in frag shader again to ensure correctness
// toggle "_ISFACE" will affect face lighting and shadowing
// if is face: override normalWS by user defined face forward direction & face area mask, in vertex shader
// if is not face: don't edit normal, use mesh's normal directly
// normalWS face area edit already done in vertex shader
// so here we don't need to edit normalWS
lightingData.isFaceArea = faceArea;
lightingData.isSkinArea = GetSkinArea(lightingData.uv);
lightingData.SV_POSITIONxy = input.positionCS.xy;
// note for future XR development:
// ref code that may help fixing VR problem: https://docs.unity3d.com/Manual/SinglePassStereoRendering.html
//lightingData.screenUV = input.screenPos.xy / input.screenPos.w;
//lightingData.screenUV = UnityStereoTransformScreenSpaceTex(lightingData.screenUV);
// [why not just pass abs(positionVS.z) from vertex shader? it seems it is also linearEyeDepth]
// because we want lightingData.selfLinearEyeDepth having the same format as Convert_SV_PositionZ_ToLinearViewSpaceDepth(tex2D(_CameraDepthTexture)),
// recalculate selfLinearEyeDepth from positionCS.z provided much better precision when doing depth texture shadow depth comparison logic.
// A "Samsung A70" mobile precision test @ 2021-3-23 confirmed depth texture shadow precision improved a lot using this line instead of abs(positionVS.z) where positionVS is from vertex shader
lightingData.selfLinearEyeDepth = Convert_SV_PositionZ_ToLinearViewSpaceDepth(input.positionCS.z);
lightingData.ZOffsetFinalSum = input.positionWS_ZOffsetFinalSum.w;
lightingData.SH = input.SH_fogFactor.xyz;
lightingData.averageShadowAttenuation = input.normalWS_averageShadowAttenuation.w;
lightingData.NdotV_NoNormalMap = saturate(dot(lightingData.normalWS_NoNormalMap, lightingData.viewDirectionWS));
lightingData.vertexColor = input.color;
lightingData.normalizedScreenSpaceUV = GetNormalizedScreenSpaceUV(input.positionCS);
lightingData.aspectCorrectedScreenSpaceUV = GetAspectRatioCorrectedNormalizedScreenSpaceUV(lightingData.normalizedScreenSpaceUV);
lightingData.facing = facing;
float3 characterBoundCenterPosWS = _NiloToonGlobalPerCharBoundCenterPosWSArray[_CharacterID];
lightingData.characterBoundBottomWS_positionWS = characterBoundCenterPosWS - float3(0,_CharacterBoundRadius,0);
lightingData.characterBoundTopWS_positionWS = characterBoundCenterPosWS + float3(0,_CharacterBoundRadius,0);
// Note:
// for (NiloToonShadowCasterPass || NiloToonCharSelfShadowCasterPass), UNITY_MATRIX_V is converting the vertex to shadow camera's View space,
// but for a correct dissolve, it should be converting the vertex to main camera's View space, it is a contradiction since there can be many cameras.
// This will result the screen space dissolve not having a stable direction for (NiloToonShadowCasterPass || NiloToonCharSelfShadowCasterPass)
// there is no 100% correct solution to this problem, the best we can do is to match the dissolve to main camera's view space, and ignore other cameras.
lightingData.characterBound2DRectUV01 = Calc3DSphereTo2DUV(lightingData.positionWS, characterBoundCenterPosWS, _CharacterBoundRadius);
// make characterBound2DRectUV01 uv matching in all pass
// TODO: this line is not needed, it only flip the uv.y, without fixing the problem
#if NiloToonShadowCasterPass || NiloToonCharSelfShadowCasterPass
lightingData.characterBound2DRectUV01.y = 1-lightingData.characterBound2DRectUV01.y;
#endif
// [require normalmap finish first]
// a shared matcap uv for all matcap features
lightingData.matcapUV = CalcMatCapUV(lightingData.viewDirectionWS,lightingData.normalWS);
lightingData.matcapUV = _EnableUVEditGroup ? lightingData.matcapUV * _MatCapUVTiling : lightingData.matcapUV;
//--------------------------------------------------------------------
// MainLight Note
//--------------------------------------------------------------------
// [Which directional light is main light?]
// For main light index, see URP's UniversalRenderPipeline.cs -> GetMainLightIndex(...)
// - If Sun exists, it is main light (RenderSettings.sun)
// - If Sun not exists, the brightest directional light is main light
// - If multiple brightest directional light exist, the first one found is main light, so it is random
//
// It is shaded outside the light loop and it has a specific set of variables and shading path
// so we can be as fast as possible in the case when there's only a single directional light in scene
// There are 3 overloads of GetMainLight(...) in URP16:
// - Light GetMainLight()
// - Light GetMainLight(float4 shadowCoord)
// - Light GetMainLight(float4 shadowCoord, float3 positionWS, half4 shadowMask)
//
// we will call the most basic GetMainLight() first, then complete
// - shadowAttenuation
// - color (light cookie color multiply)
// in later steps. So we can skip shadowAttenuation calculation if not used
//--------------------------------------------------------------------
// Inject all additional lights into mainLight
//--------------------------------------------------------------------
uint meshRenderingLayers = NiloGetMeshRenderingLayer();
Light mainLight = GetMainLight();
// cache original value
half3 originalMainLightDirection = mainLight.direction;
half3 originalMainLightColor = mainLight.color;
// init main light color as 0, fill in main light only if mainLight's IsMatchingLightLayer is true
mainLight.color = 0;
//----------------------------------------------------------------
// 0.init Main light's color and direction
#ifdef _LIGHT_LAYERS
if (IsMatchingLightLayer(mainLight.layerMask, meshRenderingLayers))
#endif
{
// user can override main light's direction + color by
// - NiloToonCharRenderingControlVolume
// - NiloToonCharacterMainLightOverrider
// if user didn't override, the value assigned will be the same as original mainLight.color/direction
mainLight.direction = _GlobalUserOverriddenMainLightDirWS;
mainLight.color = _GlobalUserOverriddenMainLightColor;
// support main light's light cookie
// since we are calling a basic version GetMainLight(), we have to fill in required data by ourselves
#if defined(_LIGHT_COOKIES)
real3 cookieColor = SampleMainLightCookie(lightingData.positionWS);
mainLight.color *= cookieColor;
#endif
// although main directional light doesn't have a "DistanceAttenuation" like point or spot light,
// multiply distanceAttenuation with a directional light color will make Light's Culling mask works(the game object's layer, not the new URP rendering layer),
// this line equals to: *= unity_LightData.z; // unity_LightData.z is 1 when not culled by the culling mask, otherwise 0.
// (see URP's Lighting.hlsl's GetMainLight())
mainLight.color *= mainLight.distanceAttenuation;
mainLight.color *= lightingData.averageShadowAttenuation;
mainLight.color *= _GlobalMainDirectionalLightMultiplier;
}
// apply per char main light control (from NiloToonCharacterLightController scripts)
// *don't put this in the above if(){...}, since this feature don't care mainLight exist or not, it should always execute
mainLight.color = mainLight.color * _NiloToonGlobalPerCharMainDirectionalLightTintColorArray[_CharacterID] + _NiloToonGlobalPerCharMainDirectionalLightAddColorArray[_CharacterID];
//----------------------------------------------------------------
// 1. inject indirect light into main light
// [sample APV and replace SH, if project enabled APV]
#if (defined(PROBE_VOLUMES_L1) || defined(PROBE_VOLUMES_L2))
lightingData.SH = SAMPLE_GI(lightingData.SH,
GetAbsolutePositionWS(lightingData.positionWS),
lightingData.normalWS * (1-_IndirectLightFlatten), // do normal flatten for indirect light
lightingData.viewDirectionWS,
lightingData.SV_POSITIONxy,
1, // probe occlusion, since this shader is for dynamic character, we use a constant 1
1); // shadowmask, since this shader is for dynamic character, we use a constant 1
#endif
// [pick the highest between indirect and main directional light = max(direct light,indirect light)]
// anime character artist usually don't want the concept of indirect + direct light
// because it will ruin the final color easily,
// what anime character artist want is:
// - if direct lighting is bright enough -> keep character's result same as albedo texture that they draw
// - if in a dark environment -> switch to use light probe/APV to make character blend into the environment color
// *max() can prevent result completely black, if light probe was not baked and no direct light is active
const half3 indirectLight = min(max(lightingData.SH,_GlobalIndirectLightMinColor),_GlobalIndirectLightMaxColor);
mainLight.color = max(mainLight.color, indirectLight);
//----------------------------------------------------------------
// [the following light loop will apply "additional light inject into main light" (from NiloToonAdditionalLightStyle volume)]
//----------------------------------------------------------------
#if NeedCalculateAdditionalLight
InputData inputData = NiloGetLightLoopInputData(lightingData);
uint pixelLightCount = GetAdditionalLightsCount();
half3 additionalLightsDirectionWeightedSum = 0;
half3 additionalLightsColorSum = 0;
//----------------------------------------------------------------
// 2.inject Forward+'s directional additional light into main light [Forward+ only]
// this is an extra for-loop that only exists in Forward+,
// and is only looping directional lights as additional light, not including main directional light, or any point/spot light
// Note: as Directional lights would be in all clusters since they don't have a 3D volume, so they don't go into the cluster structure.
// Instead, they are stored first in the light buffer(array), so here we can loop them easily starting from index 0.
#if NILO_USE_CLUSTER_LIGHT_LOOP
for (uint lightIndex = 0; lightIndex < min(URP_FP_DIRECTIONAL_LIGHTS_COUNT, MAX_VISIBLE_LIGHTS); lightIndex++)
{
#if UNITY_VERSION >= 60010000
CLUSTER_LIGHT_LOOP_SUBTRACTIVE_LIGHT_CHECK
#else
FORWARD_PLUS_SUBTRACTIVE_LIGHT_CHECK
#endif
Light light = GetAdditionalLight(lightIndex, input.positionWS_ZOffsetFinalSum.xyz); // since URP10, you must provide shadowMask in order to receive shadowmap
NiloToonPerAdditionalLightData niloToonPerAdditionalLightData = GetNiloToonPerAdditionalLightData(lightIndex);
#ifdef _LIGHT_LAYERS
if (niloToonPerAdditionalLightData.ignoreRenderingLayer || IsMatchingLightLayer(light.layerMask, meshRenderingLayers))
#endif
{
// support additional directional light's light cookie
// since we are calling a basic version GetMainLight(), we have to fill in required data by ourselves
#if defined(_LIGHT_COOKIES)
real3 cookieColor = SampleAdditionalLightCookie(lightIndex, inputData.positionWS);
light.color *= cookieColor;
#endif
// - don't * light.shadowAttenuation, shadow map looks ugly (shadow map doesn't exist for additional directional light in URP)
half3 additionalLightPixelColor = light.color * light.distanceAttenuation;
half Avg3AdditionalLightPixelColor = NiloAvg3(additionalLightPixelColor);
// simple sum, don't need to multiply any dot(N,L) mask, keep the color sum clean and blurry!
additionalLightsColorSum += lerp(additionalLightPixelColor,Avg3AdditionalLightPixelColor,niloToonPerAdditionalLightData.injectIntoMainLightColorApplyDesaturate) * niloToonPerAdditionalLightData.injectIntoMainLightColor;
// [1]
// It is possible to additionally multiply "saturate(dot(light.direction,lightingData.normalWS) * k + (1-k))" to weighted sum,
// it will produce double side rim light when multiple lights having different direction,
// but double side rim light is not the style that NiloToon want to produce
// [2]
// simply use Avg3() as weight for weighted sum, not Luminance(), since red/blue light should have the same directional weight as green light
additionalLightsDirectionWeightedSum += CalculateLightInjectionWeightedDirection(light,Avg3AdditionalLightPixelColor) * niloToonPerAdditionalLightData.injectIntoMainLightDirection;
}
}
#endif
//----------------------------------------------------------------
// 3.inject only point/spot additional light into main light [if Forward+]
// 3.inject all additional light into main light [if Forward/Deferred]
LIGHT_LOOP_BEGIN(pixelLightCount)
// TODO: try characterBoundCenterPosWS for GetAdditionalLight(...)
Light light = GetAdditionalLight(lightIndex, lightingData.positionWS); // not provide shadowMask in order to not receive additional light's shadowmap
NiloToonPerAdditionalLightData niloToonPerAdditionalLightData = GetNiloToonPerAdditionalLightData(lightIndex);
#ifdef _LIGHT_LAYERS
if (niloToonPerAdditionalLightData.ignoreRenderingLayer || IsMatchingLightLayer(light.layerMask, meshRenderingLayers))
#endif
{
// support additional directional light's light cookie
// since we are calling a basic version GetMainLight(), we have to fill in required data by ourselves
#if defined(_LIGHT_COOKIES)
real3 cookieColor = SampleAdditionalLightCookie(lightIndex, inputData.positionWS);
light.color *= cookieColor;
#endif
// - don't * light.shadowAttenuation, shadow map looks ugly
// - optionally apply saturate() to light.distanceAttenuation to cap it in 0~1, since it is 1/(d^2), it can go very high when light is close to character where d < 1
half3 additionalLightPixelColor = light.color * lerp(saturate(light.distanceAttenuation),light.distanceAttenuation,_GlobalAdditionalLightInjectIntoMainLightColor_AllowCloseLightOverBright);
half Avg3AdditionalLightPixelColor = NiloAvg3(additionalLightPixelColor);
half backLightOcclusion2DMask = saturate(dot(lightingData.viewDirectionWS, light.direction));
half backLightOcclusion3DMask = smoothstep(0,0.1,dot(lightingData.normalWS, light.direction));
//maskTerm = dot(V, L) * 0.5 + 0.5; // possible
//maskTerm = smoothstep(0.4,0.6,dot(N, L)); // face may look ugly, not te best one.
// simple sum, don't need to multiply any dot(N,L) mask, keep the color sum clean and blurry!
additionalLightsColorSum += lerp(additionalLightPixelColor,Avg3AdditionalLightPixelColor,niloToonPerAdditionalLightData.injectIntoMainLightColorApplyDesaturate)
* niloToonPerAdditionalLightData.injectIntoMainLightColor
* lerp(1, backLightOcclusion2DMask,niloToonPerAdditionalLightData.injectIntoMainLightBackLightOcclusion2D)
* lerp(1, backLightOcclusion3DMask,niloToonPerAdditionalLightData.injectIntoMainLightBackLightOcclusion3D)
;
// [1]
// It is possible to additionally multiply "saturate(dot(light.direction,lightingData.normalWS) * k + (1-k))" to weighted sum,
// it will produce double side rim light when multiple lights having different direction,
// but double side rim light is not the style that NiloToon want to produce
// [2]
// simply use Avg3() as weight for weighted sum, not Luminance(), since red/blue light should have the same directional weight as green light
additionalLightsDirectionWeightedSum += CalculateLightInjectionWeightedDirection(light,Avg3AdditionalLightPixelColor) * niloToonPerAdditionalLightData.injectIntoMainLightDirection;
}
LIGHT_LOOP_END
// Note: NiloToon doesn't have any additional vertex light
additionalLightsColorSum *= _GlobalAdditionalLightInjectIntoMainLightColor_Strength;
additionalLightsDirectionWeightedSum *= _GlobalAdditionalLightInjectIntoMainLightDirection_Strength;
// *Avg3 as weight for weighted sum, not Luminance, since red/blue light should have the same weight as green light
// (you must make it sync with the weight method of other additionalLightsDirectionWeightedSum)
half Avg3DirectionalLightPixelColor = NiloAvg3(mainLight.color);
half3 mainDirectionalLightDirectionWeightedSum = CalculateLightInjectionWeightedDirection(mainLight,Avg3DirectionalLightPixelColor);
// normalize the weighted sum of all light's direction, to get the final merged main light's direction
// brighter light will affect the final merged main light's direction more due to using Avg3 as weight
// *Add a constant to avoid lightDirectionsWeightedSum is 0, where normal(0) will produce error
mainLight.direction = normalize(mainDirectionalLightDirectionWeightedSum + additionalLightsDirectionWeightedSum + half3(0,0.001,0));
// apply mainLight.color injection at last, don't let it affect mainLight.direction
// *Also apply an optional Desaturate to the inject color
mainLight.color += lerp(additionalLightsColorSum,Luminance(additionalLightsColorSum),_GlobalAdditionalLightInjectIntoMainLightColor_Desaturate);
#endif
// [final override mainLight]
mainLight.direction = _GlobalUserOverriddenFinalMainLightDirWSParam.w ? _GlobalUserOverriddenFinalMainLightDirWSParam.xyz : mainLight.direction;
mainLight.color = _GlobalUserOverriddenFinalMainLightColorParam.w ? _GlobalUserOverriddenFinalMainLightColorParam.rgb : mainLight.color;
/*
// TODO: possible mainLight.color tonemapping?
half uselightColorTonemapping = sin(_Time.y * 3.1415 * 2) > 0;
if(uselightColorTonemapping > 0)
{
mainLight.color = lerp(mainLight.color, mainLight.color / (mainLight.color+1.0), uselightColorTonemapping);
}
*/
// [_ReceiveSelfShadowMappingPosOffset]
// this uniform will control the extra depth bias of URP shadowmap,
// doing this extra depth bias is usually for hiding ugly self shadow for shadow sensitive area like face
#if ShouldReceiveURPShadow
float materialDepthBias = lerp(_ReceiveSelfShadowMappingPosOffset,_ReceiveSelfShadowMappingPosOffsetForFaceArea,lightingData.isFaceArea);
float3 URPShadowTestPosWS = lightingData.positionWS + originalMainLightDirection * (materialDepthBias+_GlobalReceiveSelfShadowMappingPosOffset);
// always compute any type of URP's ShadowCoord in the fragment shader instead of vertex shader now, due to URP having shadow-cascades
// https://forum.unity.com/threads/shadow-cascades-weird-since-7-2-0.828453/#post-5516425
float4 URPShadowCoord = TransformWorldToShadowCoord(URPShadowTestPosWS);
mainLight.shadowAttenuation = MainLightRealtimeShadow(URPShadowCoord);
#endif
// copy before edit
lightingData.rawURPShadowAttenuation = mainLight.shadowAttenuation;
// apply URP shadowmap intensity (material, per character, renderer feature)
#if ShouldReceiveURPShadow
half materialReceiveShadowMappingAmount = lerp(_ReceiveURPShadowMappingAmountForNonFace,_ReceiveURPShadowMappingAmountForFace, lightingData.isFaceArea) * _ReceiveURPShadowMappingAmount;
mainLight.shadowAttenuation = lerp(1,mainLight.shadowAttenuation, materialReceiveShadowMappingAmount * _GlobalReceiveShadowMappingAmount * _PerCharReceiveStandardURPShadowMap);
#endif
lightingData.mainLight = mainLight;
return lightingData;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// fragment shared functions (Step3: override surfaceData.albedo)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// [Dynamic Eye]
// currently using a simple method only
// (not HDRP's method: https://github.com/Unity-Technologies/Graphics/blob/c6ae7599b33ca48852eddd592b3e40f33f2dd982/com.unity.render-pipelines.high-definition/Runtime/Material/Eye/EyeUtils.hlsl)
#if _DYNAMIC_EYE
half3 CalculateDynamicEyeColor(float2 inputUV, float3 inputViewDirTS)
{
// scale uv using eye center as pivot
float2 uv = _DynamicEyeSize * (inputUV - 0.5) + 0.5;
// eye Pupil Alpha
float eyePupilAlpha = dot(tex2D(_DynamicEyePupilMaskTex, uv),_DynamicEyePupilMaskTexChannelMask);
// inner eye color (eye pupil)
float2 offset = -0.73 * inputViewDirTS * _DynamicEyePupilDepthScale * eyePupilAlpha + uv;
float2 normalizeResult = normalize((offset - 0.5) * 0.5);
float2 innerEyeUV = lerp(offset, 0.5 + normalizeResult, ((0.8 / _DynamicEyeSize * _DynamicEyePupilSize ) * (1 - 2 * _DynamicEyeSize * length(inputUV - 0.5))));
half3 innerEyeColor = tex2D(_DynamicEyePupilMap, innerEyeUV) *_DynamicEyePupilColor;
// outer eye color (eye white)
float2 outerEyeUV = inputUV * _DynamicEyeWhiteMap_ST.xy + _DynamicEyeWhiteMap_ST.zw;
half3 outerEyeColor = tex2D(_DynamicEyeWhiteMap, outerEyeUV);
// combine eye color (inner or outer by mask)
half finalPupilMask = saturate(eyePupilAlpha / _DynamicEyePupilMaskSoftness);
half3 combinedEyeColor = lerp(outerEyeColor, innerEyeColor, finalPupilMask);
return combinedEyeColor * _DynamicEyeFinalTintColor * _DynamicEyeFinalBrightness;
}
#endif
void ApplyDynamicEye(inout ToonSurfaceData surfaceData, Varyings varyings, ToonLightingData lightingData)
{
#if _DYNAMIC_EYE
surfaceData.albedo = CalculateDynamicEyeColor(GetUV(varyings), lightingData.viewDirectionTS);
#endif
}
void ApplyPostLightingPerCharacterBaseMapOverride(inout half3 originalSurfaceColor, UVData uvData)
{
#if _NILOTOON_PERCHARACTER_BASEMAP_OVERRIDE
float2 uv = uvData.GetUV(_PerCharacterBaseMapOverrideUVIndex);
uv -= 0.5;
uv = CalcUV(uv,_PerCharacterBaseMapOverrideTilingOffset, _Time.y, _PerCharacterBaseMapOverrideUVScrollSpeed);
uv += 0.5;
half4 texValue = SAMPLE_TEXTURE2D(_PerCharacterBaseMapOverrideMap, sampler_BaseMap, uv);
texValue.rgb *= _PerCharacterBaseMapOverrideTintColor;
texValue.a *= _PerCharacterBaseMapOverrideAmount;
half4 originalColorRGBA = half4(originalSurfaceColor.r,originalSurfaceColor.g,originalSurfaceColor.b,1);
half4 resultColorRGBA = BlendColor(originalColorRGBA,texValue, _PerCharacterBaseMapOverrideBlendMode);
originalSurfaceColor = resultColorRGBA.rgb;
//we don't want to edit alpha, so just edit rgb
#endif
}
// [MatCap(Color Replace)]
void ApplyMatCapColorReplace(inout ToonSurfaceData surfaceData, Varyings varyings, ToonLightingData lightingData)
{
#if _MATCAP_BLEND
half matCapAlphaBlendFinalUsage = _MatCapAlphaBlendUsage;
// mask by an optional mask texture
// (decided to not use shader_feature for this mask section, else too much shader_feature is used)
half matCapAlphaBlendMask = dot(SAMPLE_TEXTURE2D(_MatCapAlphaBlendMaskMap, sampler_BaseMap, GetUV(varyings)),_MatCapAlphaBlendMaskMapChannelMask); // reuse sampler_BaseMap to save sampler count
matCapAlphaBlendMask = _MatCapAlphaBlendMaskMapInvertColor? 1-matCapAlphaBlendMask : matCapAlphaBlendMask;
matCapAlphaBlendMask = invLerpClamp(_MatCapAlphaBlendMaskMapRemapStart, _MatCapAlphaBlendMaskMapRemapEnd, matCapAlphaBlendMask);
matCapAlphaBlendFinalUsage *= matCapAlphaBlendMask;
float2 matCapBlendUV = lightingData.matcapUV.xy * 0.5 * _MatCapAlphaBlendUvScale + 0.5;
half4 matCapTextureReadRGBA = tex2D(_MatCapAlphaBlendMap, matCapBlendUV) *_MatCapAlphaBlendTintColor;
// do [alpha as mask]
matCapAlphaBlendFinalUsage *= lerp(1,matCapTextureReadRGBA.a,_MatCapAlphaBlendMapAlphaAsMask); // allow MatCap texture's alpha channel as mask also
surfaceData.albedo = lerp(surfaceData.albedo,matCapTextureReadRGBA.rgb,matCapAlphaBlendFinalUsage);
#endif
}
// [Mat Cap(additive)]
void ApplyMatCapAdditive(inout ToonSurfaceData surfaceData, Varyings varyings, ToonLightingData lightingData)
{
#if _MATCAP_ADD
// read matcap texture
float2 matCapAddUV = lightingData.matcapUV.xy * (0.5 * _MatCapAdditiveUvScale) + 0.5;
half4 matCapTextureReadRGBA = tex2D(_MatCapAdditiveMap, matCapAddUV);
matCapTextureReadRGBA = pow(abs(matCapTextureReadRGBA),abs(_MatCapAdditiveExtractBrightArea+1));
half4 matCapAdditiveRGBA = _MatCapAdditiveColor; // contains alpha also
// intensity slider
matCapAdditiveRGBA.rgb *= _MatCapAdditiveIntensity; // only affect rgb
// let user select mix with basecolor or not
matCapAdditiveRGBA.rgb *= lerp(1,surfaceData.albedo,_MatCapAdditiveMixWithBaseMapColor);
// mask by an optional mask texture
// (decided to not use shader_feature for this mask section, else too much shader_feature is used)
half matCapAdditiveMask = dot(SAMPLE_TEXTURE2D(_MatCapAdditiveMaskMap, sampler_BaseMap, GetUV(varyings)),_MatCapAdditiveMaskMapChannelMask); // reuse sampler_BaseMap to save sampler count
matCapAdditiveMask = _MatCapAdditiveMaskMapInvertColor ? 1-matCapAdditiveMask : matCapAdditiveMask;
matCapAdditiveMask = invLerpClamp(_MatCapAdditiveMaskMapRemapStart, _MatCapAdditiveMaskMapRemapEnd, matCapAdditiveMask);
matCapAdditiveRGBA.rgb *= matCapAdditiveMask; // only affect rgb
// mix RGBA with matcap texture
half4 result = matCapTextureReadRGBA * matCapAdditiveRGBA;
// do [alpha as mask]
half alphaAsMask = lerp(1,result.a,_MatCapAdditiveMapAlphaAsMask); // allow MatCap texture's alpha channel as mask also
result.rgb *= alphaAsMask;
// render face mask
if(_MatCapAdditiveApplytoFaces != 0)
{
if(_MatCapAdditiveApplytoFaces == 1) result.rgb *= saturate(-lightingData.facing);
if(_MatCapAdditiveApplytoFaces == 2) result.rgb *= saturate(lightingData.facing);
}
half3 finalAdd = result.rgb;
surfaceData.albedo += finalAdd;
surfaceData.alpha = saturate(surfaceData.alpha + Luminance(finalAdd)); // semi-transparent's reflection should NOT be affected by alpha, so we add alpha on specular area to cancel it
#endif
}
// [Mat Cap(occlusion)]
void ApplyMatCapOcclusion(inout ToonSurfaceData surfaceData, Varyings varyings, ToonLightingData lightingData)
{
#if _MATCAP_OCCLUSION
// read matcap texture
float2 matCapOcclusionUV = lightingData.matcapUV.xy * (0.5 * _MatCapOcclusionUvScale) + 0.5;
half4 matCapTextureReadRGBA = tex2D(_MatCapOcclusionMap, matCapOcclusionUV);
half matCapTextureReadOcclusion = dot(matCapTextureReadRGBA, _MatCapOcclusionMapChannelMask);
matCapTextureReadOcclusion = invLerpClamp(_MatCapOcclusionMapRemapStart, _MatCapOcclusionMapRemapEnd, matCapTextureReadOcclusion); // remap
// mask by an optional mask texture
// (decided to not use shader_feature for this mask section, else too much shader_feature is used)
half matCapOcclusionMask = dot(SAMPLE_TEXTURE2D(_MatCapOcclusionMaskMap, sampler_BaseMap, GetUV(varyings)),_MatCapOcclusionMaskMapChannelMask); // reuse sampler_BaseMap to save sampler count
matCapOcclusionMask = _MatCapOcclusionMaskMapInvert ? 1-matCapOcclusionMask : matCapOcclusionMask;
matCapOcclusionMask = invLerpClamp(_MatCapOcclusionMaskMapRemapStart, _MatCapOcclusionMaskMapRemapEnd, matCapOcclusionMask);
// occlsion *= matcap texture occlsion & [alpha as mask]
half resultOcclusion = lerp(1, matCapTextureReadOcclusion, saturate(matCapOcclusionMask * lerp(1,matCapTextureReadRGBA.a,_MatCapOcclusionMapAlphaAsMask) * _MatCapOcclusionIntensity));
// apply to occlusion
surfaceData.occlusion *= resultOcclusion;
#endif
}
// [Environment Reflections]
void ApplyEnvironmentReflections(inout ToonSurfaceData surfaceData, Varyings varyings, ToonLightingData lightingData)
{
// [How to sample reflection probe correctly?]
// see URP's GlobalIllumination.hlsl ->
// half3 GlobalIllumination(BRDFData brdfData, BRDFData brdfDataClearCoat, float clearCoatMask,
// half3 bakedGI, half occlusion, float3 positionWS,
// half3 normalWS, half3 viewDirectionWS)
#if _ENVIRONMENTREFLECTIONS
const half smoothness = saturate(_EnvironmentReflectionSmoothnessMultiplier * surfaceData.smoothness);
// PerceptualRoughness is equal to the (1-raw smoothness texture) linear value
half roughness = PerceptualSmoothnessToPerceptualRoughness(smoothness);
#if UNITY_VERSION > 202220
half3 GlossyEnvironmentReflectionResult = GlossyEnvironmentReflection(lightingData.reflectionVectorWS, lightingData.positionWS, roughness, surfaceData.occlusion, lightingData.normalizedScreenSpaceUV);
#else
half3 GlossyEnvironmentReflectionResult = GlossyEnvironmentReflection(lightingData.reflectionVectorWS, lightingData.positionWS, roughness, surfaceData.occlusion);
#endif
half3 environmentReflection = GlossyEnvironmentReflectionResult * _EnvironmentReflectionColor * _EnvironmentReflectionBrightness;
environmentReflection *= lerp(1,surfaceData.albedo,_EnvironmentReflectionTintAlbedo);
half applyIntensity = _EnvironmentReflectionUsage;
// mask by an optional mask texture
// (decided to not use shader_feature for this mask section, else too much shader_feature is used)
half mask = dot(SAMPLE_TEXTURE2D(_EnvironmentReflectionMaskMap, sampler_BaseMap, GetUV(varyings)),_EnvironmentReflectionMaskMapChannelMask); // reuse sampler_BaseMap to save sampler count
mask = _EnvironmentReflectionMaskMapInvertColor? 1-mask : mask;
mask = invLerpClamp(_EnvironmentReflectionMaskMapRemapStart, _EnvironmentReflectionMaskMapRemapEnd, mask);
applyIntensity *= mask; // only affect rgb
// NdotV as mask (fresnel effect)
applyIntensity *= lerp(1,invLerpClamp(_EnvironmentReflectionFresnelRemapStart,_EnvironmentReflectionFresnelRemapEnd,pow(1-lightingData.NdotV,abs(_EnvironmentReflectionFresnelPower))),_EnvironmentReflectionFresnelEffect);
// render face mask (Both,0 | Front,2 | Back,1)
applyIntensity *= !((_EnvironmentReflectionApplytoFaces == 1 && lightingData.facing == 1.0) || (_EnvironmentReflectionApplytoFaces == 2 && lightingData.facing == -1.0));
// remove face area apply
#if _ISFACE
applyIntensity *= (1-lightingData.isFaceArea * (1-_EnvironmentReflectionShouldApplyToFaceArea));
#endif
// apply to albedo
surfaceData.albedo = lerp(surfaceData.albedo,environmentReflection,applyIntensity * _EnvironmentReflectionApplyReplaceBlending);
surfaceData.albedo += environmentReflection * applyIntensity * _EnvironmentReflectionApplyAddBlending;
//surfaceData.alpha = lerp(surfaceData.alpha,1, applyIntensity); // TODO: find out a good way to do environment reflection, similar to matcap(additive)
#endif
}
// [back face color tint]
void ApplyBackFaceColorTintIfForwardLitPass(inout half3 originalSurfaceColor, Varyings varyings, ToonLightingData lightingData, half facing)
{
#if NiloToonForwardLitPass
originalSurfaceColor *= facing < 0 ? _BackFaceTintColor : 1;
#endif
}
void ApplyBackFaceForceShadowIfForwardLitPass(inout ToonSurfaceData surfaceData, ToonLightingData lightingData)
{
#if NiloToonForwardLitPass
surfaceData.occlusion *= lightingData.facing < 0 ? 1 - _BackFaceForceShadow : 1;
#endif
}
void ApplyBackFaceReplaceBaseMapColorIfForwardLitPass(inout ToonSurfaceData surfaceData, ToonLightingData lightingData)
{
#if NiloToonForwardLitPass
surfaceData.albedo = lightingData.facing < 0 ? lerp(surfaceData.albedo, _BackFaceBaseMapReplaceColor.rgb, _BackFaceBaseMapReplaceColor.a) : surfaceData.albedo;
#endif
}
void ApplyAlbedoPreLightingEditIfOutlinePass(inout ToonSurfaceData surfaceData)
{
#if NiloToonIsAnyOutlinePass
surfaceData.albedo = lerp(surfaceData.albedo, _OutlinePreLightingReplaceColor, _OutlineUsePreLightingReplaceColor);
// override by texture
//ApplyOutlineColorOverrideByTexture(originalSurfaceColor, lightingData);
#endif
}
void ApplyURPDecal(inout ToonSurfaceData surfaceData, Varyings varyings, inout ToonLightingData lightingData)
{
#ifdef _DBUFFER
// cache
half3 originalAlbedo = surfaceData.albedo;
half3 originalNormalWS = lightingData.normalWS;
half originalOcclusion = surfaceData.occlusion;
half originalSmoothness = surfaceData.smoothness;
half3 originalSpecular = surfaceData.specular;
// apply decal
half tempMetallic = 0; // we don't use metallic for now
ApplyDecal(
varyings.positionCS,
surfaceData.albedo,
surfaceData.specular,
lightingData.normalWS,
tempMetallic,
surfaceData.occlusion,
surfaceData.smoothness
);
// control apply %
surfaceData.albedo = lerp(originalAlbedo, surfaceData.albedo, _DecalAlbedoApplyStrength);
lightingData.normalWS = lerp(originalNormalWS, lightingData.normalWS, _DecalNormalApplyStrength);
surfaceData.occlusion = lerp(originalOcclusion, surfaceData.occlusion, _DecalOcclusionApplyStrength);
surfaceData.smoothness = lerp(originalSmoothness, surfaceData.smoothness, _DecalSmoothnessApplyStrength);
surfaceData.specular = lerp(originalSpecular, surfaceData.specular, _DecalSpecularApplyStrength);
#endif
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// fragment shared functions (Step4: calculate lighting & final color)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Similar to URP's Lighting.hlsl -> UniversalFragmentPBR(...),
// This function contains no lighting logic, it just pass lighting results data around and sum them.
// The job done in this function is "do shadow mapping depth test positionWS offset"
half3 ShadeAllLights(inout ToonSurfaceData surfaceData, ToonLightingData lightingData, Varyings input, UVData uvData)
{
///////////////////////////////////////////////////////////////////
// Main Directional Light
///////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------
// Light struct is provided by URP to abstract light shader variables.
//
// struct Light
// {
// half3 direction;
// half3 color;
// float distanceAttenuation; // full-float precision required on some platforms
// half shadowAttenuation;
// uint layerMask;
// };
//
// URP takes different shading approaches depending on light and platform.
// You should never reference light shader variables in your shader, instead use the
// - GetMainLight(...)
// - GetAdditionalLight(...)
// funcitons to fill this Light struct.
//
// (see struct and function calls, see URP's RealtimeLights.hlsl)
//----------------------------------------------------------------------------
uint meshRenderingLayers = NiloGetMeshRenderingLayer();
half3 mainLightResult = 0;
half depthTexRimArea = 0;
#ifdef _LIGHT_LAYERS
// Note:
// Since NiloToon 0.16.0, all additional lights will be merged into lightingData.mainLight's color & direction,
// so it doesn't make sense to use mainLight.layerMask to cull ShadeMainLight(...) anymore,
// the chance of 0 lights in scene is so low, so we skip the if check completely now.
//if (IsMatchingLightLayer(lightingData.mainLight.layerMask, meshRenderingLayers)) // Disabled since NiloToon 0.16.0
#endif
{
half4 result = ShadeMainLight(surfaceData, input, lightingData, lightingData.mainLight, uvData);
mainLightResult = result.rgb;
depthTexRimArea = result.a;
}
///////////////////////////////////////////////////////////////////
// additional light (as additive rim light in any style)
///////////////////////////////////////////////////////////////////
half3 additionalLightResult = 0;
#if NeedCalculateAdditionalLight
// NOTE: for additional light's sample code, you can copy from URP's Lighting.hlsl's UniversalFragmentPBR(...)
// LIGHT_LOOP_BEGIN needs:
// - inputData.normalizedScreenSpaceUV
// - inputData.positionWS
// so we create a temp InputData
InputData inputData = (InputData)0;
inputData.normalizedScreenSpaceUV = lightingData.normalizedScreenSpaceUV;
inputData.positionWS = lightingData.positionWS;
half3 additionalLightSum = 0;
// shadowMask is related to lightmap's baked shadow mask, which we can ignore it by setting default value (1,1,1,1),
// we still need this to call the correct GetAdditionalLight(...) overload method
half4 shadowMask = half4(1, 1, 1, 1);
uint pixelLightCount = GetAdditionalLightsCount();
// this is an extra for-loop that only exists in Forward+,
// and is only looping directional lights as additional light, not including main directional light, or any point/spot light
// Note: as Directional lights would be in all clusters since they don't have a 3D volume, so they don't go into the cluster structure.
// Instead, they are stored first in the light buffer array, so here we can loop them easily starting from index 0.
#if NILO_USE_CLUSTER_LIGHT_LOOP
for (uint lightIndex = 0; lightIndex < min(URP_FP_DIRECTIONAL_LIGHTS_COUNT, MAX_VISIBLE_LIGHTS); lightIndex++)
{
#if UNITY_VERSION >= 60010000
CLUSTER_LIGHT_LOOP_SUBTRACTIVE_LIGHT_CHECK
#else
FORWARD_PLUS_SUBTRACTIVE_LIGHT_CHECK
#endif
Light light = GetAdditionalLight(lightIndex, input.positionWS_ZOffsetFinalSum.xyz, shadowMask); // since URP10, you must provide shadowMask in order to receive shadowmap
NiloToonPerAdditionalLightData niloToonPerAdditionalLightData = GetNiloToonPerAdditionalLightData(lightIndex);
#ifdef _LIGHT_LAYERS
if (niloToonPerAdditionalLightData.ignoreRenderingLayer || IsMatchingLightLayer(light.layerMask, meshRenderingLayers))
#endif
{
// Different function(simpler and faster function) used to shade additional lights.
additionalLightSum += CalculateAdditiveSingleAdditionalLight(light, lightingData) * niloToonPerAdditionalLightData.additiveLightIntensity;
}
}
#endif
// [If Forward+]
// this is another for-loop, is for looping all effective point and spot additional lights, without any directional lights.
// since point/spot lights have their own 3D volume inside camera frustum, URP cull them by building Forward+'s light list per 3D cell using CPU jobs for intersection test + binning.
// [If non Forward+]
// this is the only for-loop, and will loop all additional lights(maximum 8) in a simple Forward way,
// loop can include any additional directional/point/spot lights
LIGHT_LOOP_BEGIN(pixelLightCount)
Light light = GetAdditionalLight(lightIndex, input.positionWS_ZOffsetFinalSum.xyz, shadowMask); // you must provide shadowMask in order to receive additional light's shadowmap
NiloToonPerAdditionalLightData niloToonPerAdditionalLightData = GetNiloToonPerAdditionalLightData(lightIndex);
#ifdef _LIGHT_LAYERS
if (niloToonPerAdditionalLightData.ignoreRenderingLayer || IsMatchingLightLayer(light.layerMask, meshRenderingLayers))
#endif
{
// A different function(simpler and faster function) is used to shade additional lights.
// which do not look as good as ShadeMainLight, but it is faster and support NiloToonCinematicRimLightVolume
additionalLightSum += CalculateAdditiveSingleAdditionalLight(light, lightingData) * niloToonPerAdditionalLightData.additiveLightIntensity;
}
LIGHT_LOOP_END
// [NiloToon's Volume] max contribution clamp (will not clamp rim area)
additionalLightSum = lerp(min(additionalLightSum,_GlobalAdditionalLightMaxContribution),additionalLightSum,depthTexRimArea);
// [NiloToon's Volume] rim mask
half additionalLightRimMask = pow(1-lightingData.NdotV_NoNormalMap,_GlobalAdditionalLightRimMaskPower);
additionalLightRimMask = smoothstep(0.5-_GlobalAdditionalLightRimMaskSoftness,0.5+_GlobalAdditionalLightRimMaskSoftness,additionalLightRimMask);
additionalLightSum *= lerp(1, additionalLightRimMask, _GlobalAdditionalLightApplyRimMask);
// [NiloToon's Volume] cinematic 2D rim mask
additionalLightSum *= lerp(1, depthTexRimArea, _GlobalCinematic2DRimMaskStrength);
// [NiloToon's Volume] since light can become over bright after modification by Cinematic rim, so we remove light intensity for outline area, which looks better
#if NiloToonSelfOutlinePass
additionalLightSum *= 1.0 - _GlobalCinematic3DRimMaskEnabled;
#endif
// [NiloToon's Volume] intensity multiplier for different area
additionalLightSum *= _GlobalAdditionalLightMultiplier;
additionalLightSum *= lerp(1,_GlobalAdditionalLightMultiplierForFaceArea,lightingData.isFaceArea);
#if NiloToonSelfOutlinePass
additionalLightSum *= _GlobalAdditionalLightMultiplierForOutlineArea;
#endif
additionalLightResult = additionalLightSum * lerp(surfaceData.occlusion,1,_AdditionalLightIgnoreOcclusion);
additionalLightResult *= lerp(surfaceData.albedo, 1, _GlobalCinematic3DRimMaskEnabled * (1-_GlobalCinematicRimTintBaseMap)); // when cinematic is on, additional light is rim light, so it should not * albedo 100%
// when cinematic rim light is on, it is better to remove the additional light on face area,
// since NiloToon's face normal is usually flatten to face forward direction, which can producing weird rim light.
// TODO: make 3D rim light use raw normal, and provide option in cinematic rim light volume to allow showing face rim light
additionalLightResult *= lerp(1,1-_GlobalCinematic3DRimMaskEnabled,lightingData.isFaceArea);
#endif
///////////////////////////////////////////////////////////////////
// Emission
///////////////////////////////////////////////////////////////////
half3 emissionResult = 0;
#if _EMISSION
emissionResult = ShadeEmission(surfaceData, lightingData, lightingData.mainLight);
#endif
///////////////////////////////////////////////////////////////////
// Composite all lighting result
///////////////////////////////////////////////////////////////////
return CompositeAllLightResults(mainLightResult, additionalLightResult, emissionResult);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// fragment shared functions (Step5: outline color edit)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ApplyOutlineColorOverrideByTexture(inout half3 originalSurfaceColor, ToonLightingData lightingData)
{
#if _OVERRIDE_OUTLINECOLOR_BY_TEXTURE
half4 outlineColorOverrideTexSampleValue = tex2D(_OverrideOutlineColorTex, lightingData.uv) * _OverrideOutlineColorTexTintColor;
outlineColorOverrideTexSampleValue.a = lerp(outlineColorOverrideTexSampleValue.a,1,_OverrideOutlineColorTexIgnoreAlphaChannel);
originalSurfaceColor = lerp(originalSurfaceColor, outlineColorOverrideTexSampleValue.rgb, outlineColorOverrideTexSampleValue.a * _OverrideOutlineColorByTexIntensity);
#endif
}
void ApplySurfaceToClassicOutlineColorEdit(inout half3 originalSurfaceColor, ToonSurfaceData surfaceData, ToonLightingData lightingData)
{
half3 outlineTintColor = lerp(_OutlineTintColor, _OutlineTintColorSkinAreaOverride.rgb, _OutlineTintColorSkinAreaOverride.a * lightingData.isSkinArea);
#if _OUTLINETINTCOLORMAP
outlineTintColor *= tex2D(_OutlineTintColorMap, lightingData.uv);
#endif
originalSurfaceColor *= outlineTintColor * _PerCharacterOutlineColorTint * _GlobalOutlineTintColor;
originalSurfaceColor *= lerp(_OutlineOcclusionAreaTintColor, 1, surfaceData.occlusion);
// _OutlineUseReplaceColor will not account for lighting. To account for lighting, see _OutlineUsePreLightingReplaceColor
originalSurfaceColor = lerp(originalSurfaceColor, _OutlineReplaceColor, _OutlineUseReplaceColor);
// override by texture
ApplyOutlineColorOverrideByTexture(originalSurfaceColor, lightingData);
// override by per char script gameplay effect
originalSurfaceColor = lerp(originalSurfaceColor, _PerCharacterOutlineColorLerp.rgb, _PerCharacterOutlineColorLerp.a);
}
// [Second Pass Extrude Cull front Outline]
void ApplySurfaceToClassicOutlineColorEditIfOutlinePass(inout half3 originalSurfaceColor, ToonSurfaceData surfaceData, ToonLightingData lightingData)
{
#if NiloToonIsAnyOutlinePass
ApplySurfaceToClassicOutlineColorEdit(originalSurfaceColor,surfaceData,lightingData);
#endif
}
void ApplySurfaceToScreenSpaceOutlineColorEditIfSurfacePass(inout half3 originalSurfaceColor, ToonSurfaceData surfaceData, ToonLightingData lightingData)
{
// this function will only affect ForwardLit pass's surface pixels!
// while outline pass will only use ApplySurfaceToOutlineColorEditIfOutlinePass(...), but not this function
#if NiloToonForwardLitPass && _SCREENSPACE_OUTLINE && _NILOTOON_GLOBAL_ENABLE_SCREENSPACE_OUTLINE
// if _ZWrite is off (= 0), screen space outline is disabled.
// because if _ZWrite is off (= 0), this material should be a transparent material,
// and transparent material won't write to _CameraDepthTexture,
// which makes screen space outline not correct (screen space outline rely on _CameraDepthTexture and _CameraNormalsTexture),
// so disable screen space outline automatically if _Zwrite is off
if(_ZWrite)
{
// outline width
float finalOutlineWidth = lerp(_ScreenSpaceOutlineWidth, _ScreenSpaceOutlineWidthIfFace, lightingData.isFaceArea) * _GlobalScreenSpaceOutlineWidthMultiplierForChar;
// depth sensitivity
float finalDepthSensitivity = max(0,lerp(_ScreenSpaceOutlineDepthSensitivity, _ScreenSpaceOutlineDepthSensitivityIfFace, lightingData.isFaceArea) + _GlobalScreenSpaceOutlineDepthSensitivityOffsetForChar);
// depth sensitivity texture apply
half4 depthSensitivityTexValue = tex2D(_ScreenSpaceOutlineDepthSensitivityTex, lightingData.uv);
half depthSensitivityMultiplierByTex = dot(depthSensitivityTexValue, _ScreenSpaceOutlineDepthSensitivityTexChannelMask);
depthSensitivityMultiplierByTex = invLerpClamp(_ScreenSpaceOutlineDepthSensitivityTexRemapStart, _ScreenSpaceOutlineDepthSensitivityTexRemapEnd, depthSensitivityMultiplierByTex); // should remap first,
finalDepthSensitivity *= depthSensitivityMultiplierByTex;
// normals sensitivity
float finalNormalsSensitivity = max(0,lerp(_ScreenSpaceOutlineNormalsSensitivity, _ScreenSpaceOutlineNormalsSensitivityIfFace, lightingData.isFaceArea) + _GlobalScreenSpaceOutlineNormalsSensitivityOffsetForChar);
// normals sensitivity texture apply
half4 normalsSensitivityTexValue = tex2D(_ScreenSpaceOutlineNormalsSensitivityTex, lightingData.uv);
half normalsSensitivityMultiplierByTex = dot(normalsSensitivityTexValue, _ScreenSpaceOutlineNormalsSensitivityTexChannelMask);
normalsSensitivityMultiplierByTex = invLerpClamp(_ScreenSpaceOutlineNormalsSensitivityTexRemapStart, _ScreenSpaceOutlineNormalsSensitivityTexRemapEnd, normalsSensitivityMultiplierByTex); // should remap first,
finalNormalsSensitivity *= normalsSensitivityMultiplierByTex;
//----------------------------------------------------------------------------------------------------------------------------------------
float isScreenSpaceOutlineArea = IsScreenSpaceOutline(lightingData.SV_POSITIONxy,
finalOutlineWidth,
finalDepthSensitivity,
finalNormalsSensitivity,
lightingData.selfLinearEyeDepth,
_GlobalScreenSpaceOutlineDepthSensitivityDistanceFadeoutStrengthForChar,
_CurrentCameraFOV,
lightingData.viewDirectionWS);
isScreenSpaceOutlineArea *= _GlobalScreenSpaceOutlineIntensityForChar; // intensity control
originalSurfaceColor *= lerp(1,_ScreenSpaceOutlineTintColor * _GlobalScreenSpaceOutlineTintColorForChar,isScreenSpaceOutlineArea);
originalSurfaceColor *= lerp(_ScreenSpaceOutlineOcclusionAreaTintColor, 1, surfaceData.occlusion);
originalSurfaceColor = lerp(originalSurfaceColor, _ScreenSpaceOutlineReplaceColor, _ScreenSpaceOutlineUseReplaceColor * isScreenSpaceOutlineArea);
// override by texture
half3 originalSurfaceColorBeforeOverrideByTexture = originalSurfaceColor;
ApplyOutlineColorOverrideByTexture(originalSurfaceColor, lightingData);
originalSurfaceColor = lerp(originalSurfaceColorBeforeOverrideByTexture, originalSurfaceColor, isScreenSpaceOutlineArea);
}
#endif
}
void ApplySurfaceToScreenSpaceOutlineV2ColorEditIfSurfacePass(inout half3 originalSurfaceColor, ToonSurfaceData surfaceData, ToonLightingData lightingData)
{
// this function will only affect ForwardLit pass's surface pixels!
// while outline pass will only use ApplySurfaceToOutlineColorEditIfOutlinePass(...), but not this function
#if NiloToonForwardLitPass && _SCREENSPACE_OUTLINE_V2 && _NILOTOON_GLOBAL_ENABLE_SCREENSPACE_OUTLINE_V2
// if _ZWrite is off (= 0), screen space outline is disabled.
// because if _ZWrite is off (= 0), this material should be a transparent material,
// and transparent material won't write to _CameraDepthTexture,
// which makes screen space outline not correct (screen space outline rely on _CameraDepthTexture and _CameraNormalsTexture),
// so disable screen space outline automatically if _Zwrite is off
if(_ZWrite)
{
// TODO: receive from renderer feature V2
float width = _GlobalScreenSpaceOutlineV2WidthMultiplierForChar;
float GeometryEdgeThreshold = _GlobalScreenSpaceOutlineV2GeometryEdgeThresholdForChar;
float NormalAngleCosThetaThresholdMin = _GlobalScreenSpaceOutlineV2NormalAngleCosThetaThresholdForChar;
float NormalAngleCosThetaThresholdMax = cos(DegToRad(180));
bool DrawGeometryEdge = _GlobalScreenSpaceOutlineV2EnableGeometryEdgeForChar;
bool DrawNormalAngleEdge = _GlobalScreenSpaceOutlineV2EnableNormalAngleEdgeForChar;
bool DrawMaterialIDBoundary = true;
bool DrawCustomIDBoundary = true;
bool DrawWireframe = false;
if(lightingData.isFaceArea)
{
DrawGeometryEdge = false;
DrawNormalAngleEdge = false;
}
if(lightingData.isSkinArea)
{
DrawNormalAngleEdge = false;
}
float isScreenSpaceOutlineArea = IsScreenSpaceOutlineV2(
lightingData.SV_POSITIONxy,
width,
GeometryEdgeThreshold,
NormalAngleCosThetaThresholdMin,
NormalAngleCosThetaThresholdMax,
DrawGeometryEdge,
DrawNormalAngleEdge,
DrawMaterialIDBoundary,
DrawCustomIDBoundary,
DrawWireframe,
lightingData.positionWS);
half3 outlineSurfaceColor = originalSurfaceColor;
ApplySurfaceToClassicOutlineColorEdit(outlineSurfaceColor,surfaceData,lightingData);
originalSurfaceColor = lerp(originalSurfaceColor,outlineSurfaceColor,isScreenSpaceOutlineArea * _GlobalScreenSpaceOutlineV2IntensityForChar);
}
#endif
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// fragment shared functions (Step6: per volume and per character color edit)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// [Volume]
void ApplyVolumeEffectColorEdit(inout half3 color)
{
//////////////////////////////////////////////////////////////////////////////////
// (not lighting, usually for cinematic needs like cut scene)
//////////////////////////////////////////////////////////////////////////////////
color *= _GlobalVolumeMulColor;
color = lerp(color,_GlobalVolumeLerpColor.rgb,_GlobalVolumeLerpColor.a);
}
// [Per Character script]
void ApplyPerCharacterEffectColorEdit(inout half3 color, ToonLightingData lightingData)
{
//////////////////////////////////////////////////////////////////////////////////
// (not lighting, usually for gameplay needs like character selection)
//////////////////////////////////////////////////////////////////////////////////
// desaturate
color = lerp(color,Luminance(color), _PerCharEffectDesaturatePercentage);
// mul add
color.rgb = color.rgb * _PerCharEffectTintColor + _PerCharEffectAddColor;
// lerp
color.rgb = lerp(color.rgb,_PerCharEffectLerpColor.rgb,_PerCharEffectLerpColor.a);
// rim light (outline pass don't need this)
#if NiloToonForwardLitPass
// Use PolygonNdotV instead of NdotV,
// because ToonLightingData's NdotV is normalmap applied & normalized, we DONT want normalmap affecting rim light here.
half NdotV = lightingData.NdotV_NoNormalMap;
half rim = 1-NdotV;
rim = pow(rim,_PerCharEffectRimSharpnessPower);
// apply(additive)
color.rgb += (rim * (1-lightingData.isFaceArea)) * _PerCharEffectRimColor; // rim light not show on face because it looks ugly
#endif
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// fragment shared functions (Step7: dissolve)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void DoPerMatDissolve(inout half3 color, Varyings input, ToonLightingData lightingData, UVData uvData)
{
#if _PER_MAT_DISSOLVE_ON
half dissolveSampleValue = dot(tex2D(_PerMaterialDissolveThresholdMap, uvData.GetUV(_PerMaterialDissolveThresholdMapUVIndex) * _PerMaterialDissolveThresholdMapUVTiling), _PerMaterialDissolveThresholdMapChannelMask);
dissolveSampleValue = _PerMaterialDissolveThresholdMapInvertColor ? 1-dissolveSampleValue : dissolveSampleValue;
half dissolvePatternSampleValue = dot(tex2D(_PerMaterialDissolvePatternMap, uvData.GetUV(_PerMaterialDissolvePatternMapUVIndex) * _PerMaterialDissolvePatternMapUVTiling), _PerMaterialDissolvePatternMapChannelMask);
dissolvePatternSampleValue = _PerMaterialDissolvePatternMapInvertColor ? 1-dissolvePatternSampleValue : dissolvePatternSampleValue;
half patternOffset = (dissolvePatternSampleValue * 2.0 - 1.0) * _PerMaterialDissolvePatternMapStrength;
half finalClipValue = dissolveSampleValue - _PerMaterialDissolveCutoff + patternOffset;
clip(finalClipValue);
// color at edge
color.rgb = lerp(color.rgb, _PerMaterialDissolveEdgeColor, _PerMaterialDissolveCutoff > 0 ? invLerpClamp(_PerMaterialDissolveEdgeWidth,0, finalClipValue) : 0);
#endif
}
void ApplyDissolve(inout half3 color, Varyings input, ToonLightingData lightingData, UVData uvData)
{
// do per material dissolve first
DoPerMatDissolve(color, input, lightingData, uvData);
//------------------------------------------------------------
// then do per character dissolve
#if _NILOTOON_DISSOLVE
// matching to NiloToonPerCharacterRenderController.cs's enum DissolveMode
#define DISSOLVEMODE_UV1 1
#define DISSOLVEMODE_UV2 2
#define DISSOLVEMODE_WorldSpaceNoise 3
#define DISSOLVEMODE_WorldSpaceVerticalUpward 4
#define DISSOLVEMODE_WorldSpaceVerticalDownward 5
#define DISSOLVEMODE_ScreenSpaceNoise 6
#define DISSOLVEMODE_ScreenSpaceVerticalUpward 7
#define DISSOLVEMODE_ScreenSpaceVerticalDownward 8
half finalDissolveAmount = _DissolveAmount * _AllowPerCharacterDissolve;
if(finalDissolveAmount <= 0) return;
float noiseStrength = _DissolveNoiseStrength * 0.1;
float2 uvTiling = float2(_DissolveThresholdMapTilingX,_DissolveThresholdMapTilingY);
half dissolveMapThresholdMapValue = 0;
// UV1
if(_DissolveMode == DISSOLVEMODE_UV1)
{
float2 uv = GetUV(input) * uvTiling;
dissolveMapThresholdMapValue = tex2D(_DissolveThresholdMap, uv).g;
}
else
// UV2
if(_DissolveMode == DISSOLVEMODE_UV2)
{
float2 uv = uvData.GetUV(1) * uvTiling;
dissolveMapThresholdMapValue = tex2D(_DissolveThresholdMap, uv).g;
}
else
// WorldSpaceNoise
if(_DissolveMode == DISSOLVEMODE_WorldSpaceNoise)
{
float2 uv;
uv = lightingData.positionWS.xz * uvTiling;
dissolveMapThresholdMapValue = tex2D(_DissolveThresholdMap, uv).g;
uv = lightingData.positionWS.xy * uvTiling;
dissolveMapThresholdMapValue *= tex2D(_DissolveThresholdMap, uv).g;
uv = lightingData.positionWS.yz * uvTiling;
dissolveMapThresholdMapValue *= tex2D(_DissolveThresholdMap, uv).g;
}
else
// WorldSpaceVerticalUpward
if(_DissolveMode == DISSOLVEMODE_WorldSpaceVerticalUpward)
{
float2 uv = lightingData.positionWS.xz * uvTiling;
float noise = tex2D(_DissolveThresholdMap, uv).g * noiseStrength;
dissolveMapThresholdMapValue = invLerpClamp(lightingData.characterBoundBottomWS_positionWS.y,lightingData.characterBoundTopWS_positionWS.y,lightingData.positionWS.y + noise);
}
else
// WorldSpaceVerticalDownward
if(_DissolveMode == DISSOLVEMODE_WorldSpaceVerticalDownward)
{
float2 uv = lightingData.positionWS.xz * uvTiling;
float noise = tex2D(_DissolveThresholdMap, uv).g * noiseStrength;
dissolveMapThresholdMapValue = invLerpClamp(lightingData.characterBoundTopWS_positionWS.y,lightingData.characterBoundBottomWS_positionWS.y,lightingData.positionWS.y + noise);
}
else
// ScreenSpaceNoise
if(_DissolveMode == DISSOLVEMODE_ScreenSpaceNoise)
{
// since the default bounding sphere is 2.5m, when compared to world space, it should have 2.5x Tiling
float2 uv = lightingData.characterBound2DRectUV01 * uvTiling * 2.5;
dissolveMapThresholdMapValue = tex2D(_DissolveThresholdMap, uv).g;
}
else
// ScreenSpaceVerticalUpward
if(_DissolveMode == DISSOLVEMODE_ScreenSpaceVerticalUpward)
{
// since the default bounding sphere is 2.5m, when compared to world space, it should have 2.5x Tiling
float2 uv = lightingData.characterBound2DRectUV01 * uvTiling * 2.5;
float noise = tex2D(_DissolveThresholdMap, uv).g * noiseStrength;
dissolveMapThresholdMapValue = saturate(lightingData.characterBound2DRectUV01.y + noise);
}
else
// ScreenSpaceVerticalDownward
if(_DissolveMode == DISSOLVEMODE_ScreenSpaceVerticalDownward)
{
// since the default bounding sphere is 2.5m, when compared to world space, it should have 2.5x Tiling
float2 uv = lightingData.characterBound2DRectUV01 * uvTiling * 2.5;
float noise = tex2D(_DissolveThresholdMap, uv).g * noiseStrength;
dissolveMapThresholdMapValue = saturate((1-lightingData.characterBound2DRectUV01.y) + noise);
}
half dissolve = dissolveMapThresholdMapValue - finalDissolveAmount;
// clip threshold
clip(dissolve-0.0001);
// HDR color tint to "near threshold area"
color = lerp(color, _DissolveBorderTintColor, smoothstep(finalDissolveAmount + _DissolveBorderRange, finalDissolveAmount, dissolveMapThresholdMapValue));
#endif
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// fragment shared functions (Step8: fog)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ApplyFog(inout half3 color, Varyings input)
{
// Mix the pixel color with fog color.
// You can optionally use MixFogColor to override the fog color with a custom one.
float fogCoord = InitializeInputDataFog(float4(input.positionWS_ZOffsetFinalSum.xyz, 1.0), input.SH_fogFactor.w);
color = MixFog(color, fogCoord);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// fragment shared functions (Step9: override final output alpha)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ApplyOverrideOutputAlpha(inout half outputAlpha)
{
// not worth an if() here, so use a?b:c
outputAlpha = _EditFinalOutputAlphaEnable ? (_ForceFinalOutputAlphaEqualsOne ? 1.0 : outputAlpha) : outputAlpha;
// auto output "alpha == 1" for default opaque material (_SrcBlend = One, _DstBlend = Zero)
// since when rendering opaque material, most of the cases the alpha value on RT is expected to be one, but not alpha value of _BaseMap
// forcing 1 will make rendering character mask(RT's alpha) always correct.
// https://docs.unity3d.com/ScriptReference/Rendering.BlendMode.html
if((_SrcBlend == 1) && (_DstBlend == 0))
outputAlpha = 1;
#if NiloToonSelfOutlinePass
outputAlpha = 1; // outline pass always render as opaque colors, semi-transparent outline is not allowed, so for outline we force output alpha = 1 to make alpha correct when user use RenderTexture's alpha as mask
#endif
}
void InitAllData(inout Varyings input, float facing, half faceArea, out UVData uvData, out ToonLightingData lightingData, out ToonSurfaceData surfaceData)
{
//////////////////////////////////////////////////////////////////////////////////////////
// Apply Parallax, edit uv before any uv copy or texture sampling
//////////////////////////////////////////////////////////////////////////////////////////
ApplyPerPixelDisplacement(input);
//////////////////////////////////////////////////////////////////////////////////////////
// UV0~3 copy to struct
//////////////////////////////////////////////////////////////////////////////////////////
uvData = InitializeUV0ToUV3(input);
//////////////////////////////////////////////////////////////////////////////////////////
// prepare all data struct for lighting function
//////////////////////////////////////////////////////////////////////////////////////////
half3 normalTS = GetFinalNormalTS(uvData,facing);
// normalTS apply detail normal
half detailMask = 1;
float2 detailUV = 0;
#if _DETAIL && !_NILOTOON_DEBUG_SHADING
detailMask = dot(_DetailMaskChannelMask, SAMPLE_TEXTURE2D(_DetailMask, sampler_DetailMask, GetUV(input)));
detailMask = _DetailMaskInvertColor? 1-detailMask : detailMask;
detailUV = uvData.GetUV(_DetailUseSecondUv ? 1 : 0) * _DetailMapsScaleTiling.xy + _DetailMapsScaleTiling.zw; // TODO: we want to allow UV0~3, but it will be a breaking change of _DetailUseSecondUv
normalTS = ApplyDetailNormal(detailUV, normalTS, detailMask);
#endif
lightingData = InitializeLightingData(input,normalTS, facing, faceArea);
// fill-in remaining UVData slots
uvData.allUVs[4] = lightingData.matcapUV * 0.5 + 0.5;
uvData.allUVs[5] = lightingData.characterBound2DRectUV01;
uvData.allUVs[6] = lightingData.aspectCorrectedScreenSpaceUV;
surfaceData = InitializeSurfaceData(input, uvData, facing, normalTS, detailMask, detailUV);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// only NiloToonCharacter.shader will be calling this function by using
// #pragma fragment FragmentShaderAllWork
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// [Note: VFACE vs SV_IsFrontFace]
// Don't use VFACE anymore(it is a DX9 legacy semantics), use SV_IsFrontFace instead,
// because VFACE can't compile correctly(no compile error log, but rendering is wrong in build) on DX12(XBox One GameCore)
// - https://forum.unity.com/threads/using-vface-in-surface-shader.460941/#post-3787066
// - https://github.com/microsoft/DirectXShaderCompiler/issues/3494
// - https://forum.unity.com/threads/unity-is-adding-a-new-dxc-hlsl-compiler-backend-option.1086272/
// - https://docs.google.com/document/d/1yHARKE5NwOGmWKZY2z3EPwSz5V_ZxTDT8RnRl521iyE/edit#
// We now use these highlevel define and macro provided by Unity:
// - FRONT_FACE_TYPE (usually defined as -> bool)
// - FRONT_FACE_SEMANTIC (usually defined as -> SV_IsFrontFace)
// - IS_FRONT_VFACE(IsFrontFace, 1.0, -1.0) (usually defined as -> IsFrontFace ? 1.0 : -1.0)
void FragmentShaderAllWork(Varyings input, FRONT_FACE_TYPE IsFrontFace : FRONT_FACE_SEMANTIC
, out half4 outColor : SV_Target0
#ifdef _WRITE_RENDERING_LAYERS
// Unity6.2 changed float4 -> uint
#if UNITY_VERSION >= 60020000
, out uint outRenderingLayers : SV_Target1
#else
, out float4 outRenderingLayers : SV_Target1
#endif
#endif
)
{
// [Note: SV_POSITION (input.positionCS)'s value in fragment shader]
// In the vertex shader, you output SV_POSITION, which contains the clip space position (positionCS) after the model-view-projection (MVP) transformation.
// In the fragment shader, if you read SV_POSITION's value, it's not the same as the clip space position from the vertex shader, because it has been transformed to screen space coordinates.
// When you access input.positionCS.xyzw (SV_POSITION) in the fragment shader, you will get:
// - x: for DX10 or later = the pixel center position: integer index x on RenderTarget + 0.5 (e.g. 0.5~1919.5 in a 1920x1080 render target), you can use input.positionCS.xy for LOAD texture. You can also get a screen space 0~1 uv by dividing it by the render target width
// - y: for DX10 or later = the pixel center position: integer index y on RenderTarget + 0.5 (e.g. 0.5~1079.5 in a 1920x1080 render target), you can use input.positionCS.xy for LOAD texture. You can also get a screen space 0~1 uv by dividing it by the render target height
// - z: SV_DEPTH, it is the same as NDC's z, the depth value that is written to the depth buffer, it is 0~1 for all graphics API
// - w: positive value of view space depth or view space z, which is the same as the w component in clip space (positionCS)
// or
// fragment SV_POSITION.x = (CLIP_POSITION.x / CLIP_POSITION.w + 1) / 2 * w
// fragment SV_POSITION.y = (CLIP_POSITION.y / CLIP_POSITION.w + 1) / 2 * h
// fragment SV_POSITION.z = CLIP_POSITION.z / CLIP_POSITION.w
// fragment SV_POSITION.w = CLIP_POSITION.w
// * the NDC range of DirectX is x[-1, 1]y[-1, 1]z[0, 1]
// * For more information about SV_POSITION's value in the fragment shader, see:
// https://zhuanlan.zhihu.com/p/597918725
// https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics
// https://zhuanlan.zhihu.com/p/455189480
// to support GPU instancing and Single Pass Stereo rendering(VR), add the following section
//------------------------------------------------------------------------------------------------------------------------------
// see ShadowCasterPass.hlsl for why these lines are removed for NiloToonCharSelfShadowCasterPass
#if !NiloToonCharSelfShadowCasterPass
UNITY_SETUP_INSTANCE_ID(input); // in non OpenGL and non PSSL, MACRO will turn into -> UnitySetupInstanceID(input.instanceID);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); // in non OpenGL and non PSSL, MACRO will turn into -> unity_StereoEyeIndex = input.stereoTargetEyeIndexAsRTArrayIdx;
#endif
//------------------------------------------------------------------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////////////////////
// flip normalWS by IS_FRONT_VFACE()
//////////////////////////////////////////////////////////////////////////////////////////
float facing = IS_FRONT_VFACE(IsFrontFace, 1.0, -1.0);
// not apply "flip normal" to "NiloToonOutline" lit color pass, because we need to original normal for light shading and shadowmap normal bias
#if !NiloToonIsAnyOutlinePass
input.normalWS_averageShadowAttenuation.xyz *= facing;
input.smoothedNormalWS *= facing;
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Find out face area, and edit normalWS if pixel is within face area
// (must do this in fragment shader, else if we interpolate normalWS result based on vertex level "IsFace", it looks very bad)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
half4 lightingNormal_faceArea = GetLightingNormalWS_FaceArea(input.normalWS_averageShadowAttenuation.xyz,input.positionWS_ZOffsetFinalSum.xyz,input.uv01.xy);
input.normalWS_averageShadowAttenuation.xyz = lightingNormal_faceArea.xyz;
half faceArea = lightingNormal_faceArea.w;
//////////////////////////////////////////////////////////////////////////////////////////
// insert a performance debug minimum shader early exit section here
//////////////////////////////////////////////////////////////////////////////////////////
// exit asap, to maximize performance difference
// doing this can create a net GPU time difference between "Unlit/Texture" vs "full fragment shader"
// which reflect the cost of this fragment shader quite correctly on target device
#if _NILOTOON_FORCE_MINIMUM_SHADER
outColor = Get_NILOTOON_FORCE_MINIMUM_FRAGMENT_SHADER_result(input);
return;
#endif
//////////////////////////////////////////////////////////////////////////////////////////
// Init all data structs
//////////////////////////////////////////////////////////////////////////////////////////
UVData uvData;
ToonLightingData lightingData;
ToonSurfaceData surfaceData;
InitAllData(input, facing, faceArea, uvData, lightingData, surfaceData);
//////////////////////////////////////////////////////////////////////////////////////////
// insert debug shading early exit section here
//////////////////////////////////////////////////////////////////////////////////////////
#if _NILOTOON_DEBUG_SHADING
outColor = Get_NILOTOON_DEBUG_SHADING_result(input, surfaceData, lightingData, uvData);
return;
#endif
//////////////////////////////////////////////////////////////////////////////////////////
// apply struct ToonSurfaceData's edit (using lightingData's data also)
//////////////////////////////////////////////////////////////////////////////////////////
// (apply first) edit albedo
ApplyDynamicEye(surfaceData, input, lightingData);
ApplyMatCapColorReplace(surfaceData, input, lightingData);
// (apply later) also edit albedo, by reflection features
ApplyEnvironmentReflections(surfaceData, input, lightingData);
ApplyMatCapAdditive(surfaceData, input, lightingData);
// (apply last) replace albedo for back face
ApplyBackFaceReplaceBaseMapColorIfForwardLitPass(surfaceData, lightingData);
// edit occlusion
ApplyMatCapOcclusion(surfaceData, input, lightingData);
ApplyBackFaceForceShadowIfForwardLitPass(surfaceData, lightingData);
//////////////////////////////////////////////////////////////////////////////////////////
// apply URP decal
//////////////////////////////////////////////////////////////////////////////////////////
ApplyURPDecal(surfaceData, input, lightingData);
//////////////////////////////////////////////////////////////////////////////////////////
// apply pre-lighting outline color calculation
//////////////////////////////////////////////////////////////////////////////////////////
ApplyAlbedoPreLightingEditIfOutlinePass(surfaceData);
//////////////////////////////////////////////////////////////////////////////////////////
// apply all lighting calculation
//////////////////////////////////////////////////////////////////////////////////////////
half3 color = ShadeAllLights(surfaceData, lightingData, input, uvData);
//////////////////////////////////////////////////////////////////////////////////////////
// apply outline color calculation
//////////////////////////////////////////////////////////////////////////////////////////
ApplySurfaceToClassicOutlineColorEditIfOutlinePass(color, surfaceData, lightingData);
ApplySurfaceToScreenSpaceOutlineColorEditIfSurfacePass(color, surfaceData, lightingData);
ApplySurfaceToScreenSpaceOutlineV2ColorEditIfSurfacePass(color, surfaceData, lightingData);
//////////////////////////////////////////////////////////////////////////////////////////
// apply per "material>character>volume" color edit
// (Apply this section after outline, due to outline color user settings are usually for the original color)
//////////////////////////////////////////////////////////////////////////////////////////
// apply order = from local to global (per material > per character > per volume(global))
ApplyBackFaceColorTintIfForwardLitPass(color, input, lightingData, facing);
ApplyPostLightingPerCharacterBaseMapOverride(color, uvData);
ApplyPerCharacterEffectColorEdit(color, lightingData);
ApplyVolumeEffectColorEdit(color);
//////////////////////////////////////////////////////////////////////////////////////////
// apply dissolve
//////////////////////////////////////////////////////////////////////////////////////////
ApplyDissolve(color, input, lightingData, uvData);
//////////////////////////////////////////////////////////////////////////////////////////
// apply fog (with extend logic functions before and after fog)
//////////////////////////////////////////////////////////////////////////////////////////
ApplyExternalAssetSupportLogicBeforeFog(color, surfaceData, lightingData, input, uvData);
ApplyCustomUserLogicBeforeFog(color, surfaceData, lightingData, input, uvData);
ApplyFog(color, input);
ApplyExternalAssetSupportLogicAfterFog(color, surfaceData, lightingData, input, uvData);
ApplyCustomUserLogicAfterFog(color, surfaceData, lightingData, input, uvData);
//////////////////////////////////////////////////////////////////////////////////////////
// override output alpha
//////////////////////////////////////////////////////////////////////////////////////////
ApplyOverrideOutputAlpha(surfaceData.alpha);
//////////////////////////////////////////////////////////////////////////////////////////
// Inverse Tonemapping
//////////////////////////////////////////////////////////////////////////////////////////
// we can apply inverse tonemap to color inorder to use URP's tonemapping, but it will make bloom extremely bright, so likely not a good idea
// see https://zhuanlan.zhihu.com/p/14603997646?utm_psn=1857106055890354177&fbclid=IwZXh0bgNhZW0CMTAAAR26OV4Aooye9-I3cRDhDAeokolYIMjF6jxR1G-qNzGE6UDxRJwUjdIWuRQ_aem_tTvZmD9FNdQYSqUf7pE2Kw
/*
// Inverse for Unity's official Neutral
float3 InvTonemap_Neutral4(float3 RGB)
{
float3 t = RGB*RGB;
return 5.2f*t*t - 3.0f*t*RGB + 0.7f*t + RGB;
}
// Inverse for Unity's official ACES
float3 InvTonemap_ACES(float3 RGB)
{
float3 t = RGB * RGB;
return (0.5f*RGB*t + 0.3f*t + 0.7f*RGB + 0.45f) * sqrt(RGB);
}
*/
//////////////////////////////////////////////////////////////////////////////////////////
// output
//////////////////////////////////////////////////////////////////////////////////////////
outColor = half4(color, surfaceData.alpha);
// pre-multiply alpha option
outColor.rgb *= _PreMultiplyAlphaIntoRGBOutput ? outColor.a : 1;
#ifdef _WRITE_RENDERING_LAYERS
// Unity6.2 changed API
#if UNITY_VERSION >= 60020000
outRenderingLayers = EncodeMeshRenderingLayer();
#else
uint renderingLayers = GetMeshRenderingLayer();
outRenderingLayers = float4(EncodeMeshRenderingLayer(renderingLayers), 0, 0, 0);
#endif
#endif
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// fragment shared functions
// (only for ShadowCaster pass, DepthOnly and NiloToonSelfShadowCaster pass to use only)
// (not used in other pass)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
half BaseColorAlphaClipTest(Varyings input, FRONT_FACE_TYPE IsFrontFace : FRONT_FACE_SEMANTIC) : SV_TARGET
{
#if !NiloToonCharSelfShadowCasterPass
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
#endif
//////////////////////////////////////////////////////////////////////////////////////////
// flip normalWS by IS_FRONT_VFACE()
//////////////////////////////////////////////////////////////////////////////////////////
float facing = IS_FRONT_VFACE(IsFrontFace, 1.0, -1.0);
//////////////////////////////////////////////////////////////////////////////////////////
// Init all data structs
//////////////////////////////////////////////////////////////////////////////////////////
UVData uvData;
ToonLightingData lightingData;
ToonSurfaceData surfaceData;
InitAllData(input, facing, 0, uvData, lightingData, surfaceData);
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
// if _AlphaClip is off, there is no reason to call GetFinalBaseColor()
// so here we use #if _ALPHATEST_ON to avoid calling GetFinalBaseColor() as an optimization
#if _ALPHATEST_ON
DoClipTestToTargetAlphaValue(GetFinalBaseColor(input,uvData, facing).a);
#endif
// dither fade out should affect URP's shadowmap and depth texture / depth normal texture also
#if _NILOTOON_DITHER_FADEOUT
NiloDoDitherFadeoutClip(input.positionCS.xy, 1-_DitherFadeoutAmount*_AllowPerCharacterDitherFadeout);
#endif
// dissolve should affect URP's shadowmap and depth texture / depth normal texture also
half3 dummy = half3(0,0,0);
ApplyDissolve(dummy,input,lightingData, uvData);
return input.positionCS.z;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// fragment shared functions
// (only for DepthNormal pass to use only)
// (not used in other pass)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void BaseColorAlphaClipTest_AndDepthNormalColorOutput(Varyings input, FRONT_FACE_TYPE IsFrontFace : FRONT_FACE_SEMANTIC
, out half4 outNormalWS : SV_Target0
#ifdef _WRITE_RENDERING_LAYERS
// Unity6.2 changed float4 -> uint
#if UNITY_VERSION >= 60020000
, out uint outRenderingLayers : SV_Target1
#else
, out float4 outRenderingLayers : SV_Target1
#endif
#endif
)
{
#if !NiloToonCharSelfShadowCasterPass
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
#endif
BaseColorAlphaClipTest(input, IsFrontFace);
#if defined(_GBUFFER_NORMALS_OCT)
float3 normalWS = normalize(input.normalWS_averageShadowAttenuation.xyz);
float2 octNormalWS = PackNormalOctQuadEncode(normalWS); // values between [-1, +1], must use fp32 on some platforms.
float2 remappedOctNormalWS = saturate(octNormalWS * 0.5 + 0.5); // values between [ 0, 1]
half3 packedNormalWS = PackFloat2To888(remappedOctNormalWS); // values between [ 0, 1]
outNormalWS = half4(packedNormalWS, 0.0);
#else
float3 normalWS = NormalizeNormalPerPixel(input.normalWS_averageShadowAttenuation.xyz);
outNormalWS = half4(normalWS, 0.0);
#endif
// TODO: should we apply?
//outNormalWS *= saturate(1 - _DitherFadeoutAmount * _AllowPerCharacterDitherFadeout * _DitherFadeoutNormalScaleFix);
#ifdef _WRITE_RENDERING_LAYERS
// Unity6.2 changed API
#if UNITY_VERSION >= 60020000
outRenderingLayers = EncodeMeshRenderingLayer();
#else
uint renderingLayers = GetMeshRenderingLayer();
outRenderingLayers = float4(EncodeMeshRenderingLayer(renderingLayers), 0, 0, 0);
#endif
#endif
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// fragment shared functions
// (only for NiloToonPrepassBuffer pass to use only)
// (not used in other pass)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
float4 BaseColorAlphaClipTest_AndNiloToonPrepassBufferColorOutput(Varyings input, FRONT_FACE_TYPE IsFrontFace : FRONT_FACE_SEMANTIC) : SV_TARGET
{
BaseColorAlphaClipTest(input, IsFrontFace);
// use the same method to calculate linear depth from self(SV_POSITION.z) & scene(Load(_CameraDepthTexture))
float sceneLinearDepth = Convert_SV_PositionZ_ToLinearViewSpaceDepth(LoadSceneDepth(input.positionCS.xy));
float selfLinearDepth = Convert_SV_PositionZ_ToLinearViewSpaceDepth(input.positionCS.z);
// When the RT depth and MSAA format of _CameraDepthTexture match _NiloToonPrepassBufferRT,
// we can perform precise depth comparisons to reject "scene blocked char pixels."
// but still, it is possible to have problems similar Shadow acne, so a very small depth bias may help
// Bias should not be affected by platform, since platform difference (Z reverse) is handled inside Convert_SV_PositionZ_ToLinearViewSpaceDepth()'s LinearEyeDepth()
// DX11 = - correct , + wrong
// Vulkan = - correct , + wrong
// OPENGLES = - correct , + wrong
// -0.00003 is the minimum number to work for a 24bit depth texture
// no reasonable value works for 16bit depth texture (should we allow user to increase it? expose in renderer feature?)
float depthBias = - 0.0001; // we use a "~3.3x larger than -0.00003" bias just in case
// Add gradient bias for edges to fix pixel holes
float2 depthGradient = float2(ddx(selfLinearDepth), ddy(selfLinearDepth));
float gradientBias = length(depthGradient) * 0.5;
depthBias = depthBias - gradientBias;
if(sceneLinearDepth < selfLinearDepth + depthBias)
return 0;
float isFace = GetFaceArea(input.uv01.xy);
// if character pixel is still visible(not blocked by scene), draw to _NiloToonPrepassBufferTex
return float4(isFace,_AllowNiloToonBloomCharacterAreaOverride * _AllowedNiloToonBloomOverrideStrength,0,1);
}