HeavenStudioPlus/Assets/Plugins/UnityURPToonLitShaderExampl.../SimpleURPToonLitOutlineExam...

412 lines
17 KiB
HLSL

// For more information, visit -> https://github.com/ColinLeung-NiloCat/UnityURPToonLitShaderExample
// #pragma once is a safe guard best practice in almost every .hlsl (need Unity2020 or up),
// doing this can make sure your .hlsl's user can include this .hlsl anywhere anytime without producing any multi include conflict
#pragma once
// 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"
// 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 the material
// properties defined above. 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 will include some utility .hlsl files to help us
#include "NiloOutlineUtil.hlsl"
#include "NiloZOffset.hlsl"
#include "NiloInvLerpRemap.hlsl"
// note:
// subfix OS means object spaces (e.g. positionOS = position object space)
// subfix WS means world space (e.g. positionWS = position world space)
// subfix VS means view space (e.g. positionVS = position view space)
// subfix CS means clip space (e.g. positionCS = position clip space)
// all pass will share this Attributes struct (define data needed from Unity app to our vertex shader)
struct Attributes
{
float3 positionOS : POSITION;
half3 normalOS : NORMAL;
half4 tangentOS : TANGENT;
float2 uv : TEXCOORD0;
};
// all pass will share this Varyings struct (define data needed from our vertex shader to our fragment shader)
struct Varyings
{
float2 uv : TEXCOORD0;
float4 positionWSAndFogFactor : TEXCOORD1; // xyz: positionWS, w: vertex fog factor
half3 normalWS : TEXCOORD2;
float4 positionCS : SV_POSITION;
};
///////////////////////////////////////////////////////////////////////////////////////
// CBUFFER and Uniforms
// (you should put all uniforms of all passes inside this single UnityPerMaterial CBUFFER! else SRP batching is not possible!)
///////////////////////////////////////////////////////////////////////////////////////
// all sampler2D don't need to put inside CBUFFER
sampler2D _BaseMap;
sampler2D _EmissionMap;
sampler2D _OcclusionMap;
sampler2D _OutlineZOffsetMaskTex;
// 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/
CBUFFER_START(UnityPerMaterial)
// high level settings
float _IsFace;
// base color
float4 _BaseMap_ST;
half4 _BaseColor;
// alpha
half _Cutoff;
// emission
float _UseEmission;
half3 _EmissionColor;
half _EmissionMulByBaseColor;
half3 _EmissionMapChannelMask;
// occlusion
float _UseOcclusion;
half _OcclusionStrength;
half4 _OcclusionMapChannelMask;
half _OcclusionRemapStart;
half _OcclusionRemapEnd;
// lighting
half3 _IndirectLightMinColor;
half _CelShadeMidPoint;
half _CelShadeSoftness;
// shadow mapping
half _ReceiveShadowMappingAmount;
float _ReceiveShadowMappingPosOffset;
half3 _ShadowMapColor;
// outline
float _OutlineWidth;
half3 _OutlineColor;
float _OutlineZOffset;
float _OutlineZOffsetMaskRemapStart;
float _OutlineZOffsetMaskRemapEnd;
CBUFFER_END
//a special uniform for applyShadowBiasFixToHClipPos() only, it is not a per material uniform,
//so it is fine to write it outside our UnityPerMaterial CBUFFER
float3 _LightDirection;
struct ToonSurfaceData
{
half3 albedo;
half alpha;
half3 emission;
half occlusion;
};
struct ToonLightingData
{
half3 normalWS;
float3 positionWS;
half3 viewDirectionWS;
float4 shadowCoord;
};
///////////////////////////////////////////////////////////////////////////////////////
// vertex shared functions
///////////////////////////////////////////////////////////////////////////////////////
float3 TransformPositionWSToOutlinePositionWS(float3 positionWS, float positionVS_Z, float3 normalWS)
{
//you can replace it to your own method! Here we will write a simple world space method for tutorial reason, it is not the best method!
float outlineExpandAmount = _OutlineWidth * GetOutlineCameraFovAndDistanceFixMultiplier(positionVS_Z);
return positionWS + normalWS * outlineExpandAmount;
}
// if "ToonShaderIsOutline" is not defined = do regular MVP transform
// if "ToonShaderIsOutline" is defined = do regular MVP transform + push vertex out a bit according to normal direction
Varyings VertexShaderWork(Attributes input)
{
Varyings output;
// VertexPositionInputs contains 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 with this struct.
VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS);
// Similar to VertexPositionInputs, VertexNormalInputs will contain normal, tangent and bitangent
// in world space. If not used it will be stripped.
VertexNormalInputs vertexNormalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS);
float3 positionWS = vertexInput.positionWS;
#ifdef ToonShaderIsOutline
positionWS = TransformPositionWSToOutlinePositionWS(vertexInput.positionWS, vertexInput.positionVS.z, vertexNormalInput.normalWS);
#endif
// Computes fog factor per-vertex.
float fogFactor = ComputeFogFactor(vertexInput.positionCS.z);
// TRANSFORM_TEX is the same as the old shader library.
output.uv = TRANSFORM_TEX(input.uv,_BaseMap);
// packing positionWS(xyz) & fog(w) into a vector4
output.positionWSAndFogFactor = float4(positionWS, fogFactor);
output.normalWS = vertexNormalInput.normalWS; //normlaized already by GetVertexNormalInputs(...)
output.positionCS = TransformWorldToHClip(positionWS);
#ifdef ToonShaderIsOutline
// [Read ZOffset mask texture]
// we can't use tex2D() in vertex shader because ddx & ddy is unknown before rasterization,
// so use tex2Dlod() with an explict mip level 0, put explict mip level 0 inside the 4th component of param uv)
float outlineZOffsetMaskTexExplictMipLevel = 0;
float outlineZOffsetMask = tex2Dlod(_OutlineZOffsetMaskTex, float4(input.uv,0,outlineZOffsetMaskTexExplictMipLevel)).r; //we assume it is a Black/White texture
// [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)
outlineZOffsetMask = 1-outlineZOffsetMask;
outlineZOffsetMask = invLerpClamp(_OutlineZOffsetMaskRemapStart,_OutlineZOffsetMaskRemapEnd,outlineZOffsetMask);// allow user to flip value or remap
// [Apply ZOffset, Use remapped value as ZOffset mask]
output.positionCS = NiloGetNewClipPosWithZOffset(output.positionCS, _OutlineZOffset * outlineZOffsetMask + 0.03 * _IsFace);
#endif
// ShadowCaster pass needs special process to positionCS, else shadow artifact will appear
//--------------------------------------------------------------------------------------
#ifdef ToonShaderApplyShadowBiasFix
// see GetShadowPositionHClip() in URP/Shaders/ShadowCasterPass.hlsl
// https://github.com/Unity-Technologies/Graphics/blob/master/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl
float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, output.normalWS, _LightDirection));
#if UNITY_REVERSED_Z
positionCS.z = min(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
#else
positionCS.z = max(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
#endif
output.positionCS = positionCS;
#endif
//--------------------------------------------------------------------------------------
return output;
}
///////////////////////////////////////////////////////////////////////////////////////
// fragment shared functions (Step1: prepare data structs for lighting calculation)
///////////////////////////////////////////////////////////////////////////////////////
half4 GetFinalBaseColor(Varyings input)
{
return tex2D(_BaseMap, input.uv) * _BaseColor;
}
half3 GetFinalEmissionColor(Varyings input)
{
half3 result = 0;
if(_UseEmission)
{
result = tex2D(_EmissionMap, input.uv).rgb * _EmissionMapChannelMask * _EmissionColor.rgb;
}
return result;
}
half GetFinalOcculsion(Varyings input)
{
half result = 1;
if(_UseOcclusion)
{
half4 texValue = tex2D(_OcclusionMap, input.uv);
half occlusionValue = dot(texValue, _OcclusionMapChannelMask);
occlusionValue = lerp(1, occlusionValue, _OcclusionStrength);
occlusionValue = invLerpClamp(_OcclusionRemapStart, _OcclusionRemapEnd, occlusionValue);
result = occlusionValue;
}
return result;
}
void DoClipTestToTargetAlphaValue(half alpha)
{
#if _UseAlphaClipping
clip(alpha - _Cutoff);
#endif
}
ToonSurfaceData InitializeSurfaceData(Varyings input)
{
ToonSurfaceData output;
// albedo & alpha
float4 baseColorFinal = GetFinalBaseColor(input);
output.albedo = baseColorFinal.rgb;
output.alpha = baseColorFinal.a;
DoClipTestToTargetAlphaValue(output.alpha);// early exit if possible
// emission
output.emission = GetFinalEmissionColor(input);
// occlusion
output.occlusion = GetFinalOcculsion(input);
return output;
}
ToonLightingData InitializeLightingData(Varyings input)
{
ToonLightingData lightingData;
lightingData.positionWS = input.positionWSAndFogFactor.xyz;
lightingData.viewDirectionWS = SafeNormalize(GetCameraPositionWS() - lightingData.positionWS);
lightingData.normalWS = normalize(input.normalWS); //interpolated normal is NOT unit vector, we need to normalize it
return lightingData;
}
///////////////////////////////////////////////////////////////////////////////////////
// fragment shared functions (Step2: calculate lighting & final color)
///////////////////////////////////////////////////////////////////////////////////////
// all lighting equation written inside this .hlsl,
// just by editing this .hlsl can control most of the visual result.
#include "SimpleURPToonLitOutlineExample_LightingEquation.hlsl"
// this function contains no lighting logic, it just pass lighting results data around
// the job done in this function is "do shadow mapping depth test positionWS offset"
half3 ShadeAllLights(ToonSurfaceData surfaceData, ToonLightingData lightingData)
{
// Indirect lighting
half3 indirectResult = ShadeGI(surfaceData, lightingData);
//////////////////////////////////////////////////////////////////////////////////
// Light struct is provided by URP to abstract light shader variables.
// It contains light's
// - direction
// - color
// - distanceAttenuation
// - shadowAttenuation
//
// URP take different shading approaches depending on light and platform.
// You should never reference light shader variables in your shader, instead use the
// -GetMainLight()
// -GetLight()
// funcitons to fill this Light struct.
//////////////////////////////////////////////////////////////////////////////////
//==============================================================================================
// Main light is the brightest directional light.
// 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
// You can pass optionally a shadowCoord. If so, shadowAttenuation will be computed.
Light mainLight = GetMainLight();
float3 shadowTestPosWS = lightingData.positionWS + mainLight.direction * (_ReceiveShadowMappingPosOffset + _IsFace);
#ifdef _MAIN_LIGHT_SHADOWS
// compute the shadow coords in the fragment shader now due to this change
// https://forum.unity.com/threads/shadow-cascades-weird-since-7-2-0.828453/#post-5516425
// _ReceiveShadowMappingPosOffset will control the offset the shadow comparsion position,
// doing this is usually for hide ugly self shadow for shadow sensitive area like face
float4 shadowCoord = TransformWorldToShadowCoord(shadowTestPosWS);
mainLight.shadowAttenuation = MainLightRealtimeShadow(shadowCoord);
#endif
// Main light
half3 mainLightResult = ShadeSingleLight(surfaceData, lightingData, mainLight, false);
//==============================================================================================
// All additional lights
half3 additionalLightSumResult = 0;
#ifdef _ADDITIONAL_LIGHTS
// Returns the amount of lights affecting the object being renderer.
// These lights are culled per-object in the forward renderer of URP.
int additionalLightsCount = GetAdditionalLightsCount();
for (int i = 0; i < additionalLightsCount; ++i)
{
// Similar to GetMainLight(), but it takes a for-loop index. This figures out the
// per-object light index and samples the light buffer accordingly to initialized the
// Light struct. If ADDITIONAL_LIGHT_CALCULATE_SHADOWS is defined it will also compute shadows.
int perObjectLightIndex = GetPerObjectLightIndex(i);
Light light = GetAdditionalPerObjectLight(perObjectLightIndex, lightingData.positionWS); // use original positionWS for lighting
light.shadowAttenuation = AdditionalLightRealtimeShadow(perObjectLightIndex, shadowTestPosWS); // use offseted positionWS for shadow test
// Different function used to shade additional lights.
additionalLightSumResult += ShadeSingleLight(surfaceData, lightingData, light, true);
}
#endif
//==============================================================================================
// emission
half3 emissionResult = ShadeEmission(surfaceData, lightingData);
return CompositeAllLightResults(indirectResult, mainLightResult, additionalLightSumResult, emissionResult, surfaceData, lightingData);
}
half3 ConvertSurfaceColorToOutlineColor(half3 originalSurfaceColor)
{
return originalSurfaceColor * _OutlineColor;
}
half3 ApplyFog(half3 color, Varyings input)
{
half fogFactor = input.positionWSAndFogFactor.w;
// Mix the pixel color with fogColor. You can optionaly use MixFogColor to override the fogColor
// with a custom one.
color = MixFog(color, fogFactor);
return color;
}
// only the .shader file will call this function by
// #pragma fragment ShadeFinalColor
half4 ShadeFinalColor(Varyings input) : SV_TARGET
{
//////////////////////////////////////////////////////////////////////////////////////////
// first prepare all data for lighting function
//////////////////////////////////////////////////////////////////////////////////////////
// fillin ToonSurfaceData struct:
ToonSurfaceData surfaceData = InitializeSurfaceData(input);
// fillin ToonLightingData struct:
ToonLightingData lightingData = InitializeLightingData(input);
// apply all lighting calculation
half3 color = ShadeAllLights(surfaceData, lightingData);
#ifdef ToonShaderIsOutline
color = ConvertSurfaceColorToOutlineColor(color);
#endif
color = ApplyFog(color, input);
return half4(color, surfaceData.alpha);
}
//////////////////////////////////////////////////////////////////////////////////////////
// fragment shared functions (for ShadowCaster pass & DepthOnly pass to use only)
//////////////////////////////////////////////////////////////////////////////////////////
void BaseColorAlphaClipTest(Varyings input)
{
DoClipTestToTargetAlphaValue(GetFinalBaseColor(input).a);
}