// 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

// 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 a few small utility .hlsl files to help us
#include "/../ShaderLibrary/NiloUtilityHLSL/NiloAllUtilIncludes.hlsl"       

sampler2D _BaseMap;
sampler2D _OverrideAlphaMap;

#if _NILOTOON_DISSOLVE
    sampler2D _DissolveThresholdMap;
#endif

struct Attributes
{
    float4 positionOS   : POSITION;
    float2 uv           : TEXCOORD0;

    // to support GPU instancing and Single Pass Stereo rendering(VR), add the following section
    //------------------------------------------------------------------------------------------------------------------------------
    UNITY_VERTEX_INPUT_INSTANCE_ID  // in non OpenGL / non PSSL, will turn into -> uint instanceID : SV_InstanceID;
    //------------------------------------------------------------------------------------------------------------------------------                   
};

struct Varyings
{
    float4 positionHCS  : SV_POSITION;
    float2 uv           : TEXCOORD0;
    half fogFactor      : TEXCOORD1;

    // to support GPU instancing and Single Pass Stereo rendering(VR), add the following section
    //------------------------------------------------------------------------------------------------------------------------------
    UNITY_VERTEX_INPUT_INSTANCE_ID  // will turn into this in non OpenGL / non PSSL -> uint instanceID : SV_InstanceID;
    UNITY_VERTEX_OUTPUT_STEREO      // will turn into this in non OpenGL / non PSSL -> uint stereoTargetEyeIndexAsRTArrayIdx : SV_RenderTargetArrayIndex;
    //------------------------------------------------------------------------------------------------------------------------------
};

CBUFFER_START(UnityPerMaterial)
    float4  _BaseMap_ST;
	half4   _BaseColor;

    // 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;

    float   _DitherFadeoutAmount;
    float   _PerCharZOffset;
    float   _ZOffset;

    // _NILOTOON_DISSOLVE
    float   _DissolveAmount;

	half    _FadeoutByBaseMapAlpha;

    // Character ID for read global 1D array or load 1D texture
    uint   _CharacterID;
CBUFFER_END     

half3   _GlobalVolumeMulColor;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Global Arrays
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define MAX_CHARACTER_COUNT 128
float3 _NiloToonGlobalPerCharHeadBonePosWSArray[MAX_CHARACTER_COUNT];

Varyings vert(Attributes IN)
{
    Varyings OUT;

    // disable rendering sticker if minimum shader debug is on
#if _NILOTOON_FORCE_MINIMUM_SHADER
    return (Varyings)0;
#endif
    
    // to support GPU instancing and Single Pass Stereo rendering(VR), add the following section
    //------------------------------------------------------------------------------------------------------------------------------
    UNITY_SETUP_INSTANCE_ID(IN);                    // will turn into this in non OpenGL / non PSSL -> UnitySetupInstanceID(input.instanceID);
    UNITY_TRANSFER_INSTANCE_ID(IN, OUT);            // will turn into this in non OpenGL / non PSSL -> output.instanceID = input.instanceID;
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);     // will turn into this in non OpenGL / non PSSL -> output.stereoTargetEyeIndexAsRTArrayIdx = unity_StereoEyeIndex;
    //------------------------------------------------------------------------------------------------------------------------------

    OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);

    // fog, do it before any positionHCS.z's edit
    OUT.fogFactor = ComputeFogFactor(OUT.positionHCS.z);

    // zoffset
    OUT.positionHCS = NiloGetNewClipPosWithZOffsetVS(OUT.positionHCS, -(_ZOffset+_PerCharZOffset));

    // perspective removal
    float3 positionWS = mul(UNITY_MATRIX_M, float4(IN.positionOS.xyz,1)).xyz;
    OUT.positionHCS = NiloDoPerspectiveRemoval(OUT.positionHCS,positionWS,_NiloToonGlobalPerCharHeadBonePosWSArray[_CharacterID],_PerspectiveRemovalRadius,_PerspectiveRemovalAmount, _PerspectiveRemovalStartHeight, _PerspectiveRemovalEndHeight);

    OUT.uv = TRANSFORM_TEX(IN.uv,_BaseMap);

    return OUT;
}

half FogAsAlpha(real fogFactor)
{
    #if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
        return ComputeFogIntensity(fogFactor);
    #else
        return 1; // if fog is off, return constant 1 so it will not affect any multiplication (compiler will remove * 1)
    #endif
}

half4 frag(Varyings IN) : SV_Target
{
    // to support GPU instancing and Single Pass Stereo rendering(VR), add the following section
    //------------------------------------------------------------------------------------------------------------------------------
    UNITY_SETUP_INSTANCE_ID(IN);                     // in non OpenGL / non PSSL, MACRO will turn into -> UnitySetupInstanceID(input.instanceID);
    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(IN);    // in non OpenGL / non PSSL, MACRO will turn into -> unity_StereoEyeIndex = input.stereoTargetEyeIndexAsRTArrayIdx;
    //------------------------------------------------------------------------------------------------------------------------------
    
    // dither
#if _NILOTOON_DITHER_FADEOUT
    NiloDoDitherFadeoutClip(IN.positionHCS.xy, 1-_DitherFadeoutAmount);
#endif

    // dissolve (copied from nilotoon character shader's ApplyDissolve())
#if _NILOTOON_DISSOLVE
    // read black and white map
    half dissolveMapThresholdMapValue = tex2D(_DissolveThresholdMap, IN.uv).g;

    half dissolve = dissolveMapThresholdMapValue - _DissolveAmount;

    // clip threshold
    clip(dissolve);

    // no need to edit color, we only need clip()
    //...
#endif

    half4 customColor = tex2D(_BaseMap, IN.uv);

#if OVERRIDE_ALPHA
    customColor.a = tex2D(_OverrideAlphaMap, IN.uv).r; // TODO: accept r,g,b,a option
#endif

    customColor *= _BaseColor;

    half fogAsAlpha = FogAsAlpha(IN.fogFactor);
    half opacity = customColor.a * fogAsAlpha;

#if NiloToonStickerMultiply
    customColor.rgb = lerp(1,customColor.rgb, opacity);
#endif
#if NiloToonStickerAdditive
    customColor.rgb *= opacity;
    customColor.rgb *= _GlobalVolumeMulColor;
#endif

    // return const 1 alpha, since ColorMask is RGB, the return value of alpha doesn't matter
    return half4(customColor.rgb, 1);
}
