//Unity Toon Shader/HDRP //nobuyuki@unity3d.com //toshiyuki@unity3d.com (Universal RP/HDRP) #if SHADERPASS != SHADERPASS_FORWARD #error SHADERPASS_is_not_correctly_define #endif #ifndef SCALARIZE_LIGHT_LOOP // We perform scalarization only for forward rendering as for deferred loads will already be scalar since tiles will match waves and therefore all threads will read from the same tile. // More info on scalarization: https://flashypixels.wordpress.com/2018/11/10/intro-to-gpu-scalarization-part-2-scalarize-all-the-lights/ . // Note that it is currently disabled on gamecore platforms for issues with wave intrinsics and the new compiler, it will be soon investigated, but we disable it in the meantime. #define SCALARIZE_LIGHT_LOOP (defined(PLATFORM_SUPPORTS_WAVE_INTRINSICS) && !defined(LIGHTLOOP_DISABLE_TILE_AND_CLUSTER) && !defined(SHADER_API_GAMECORE) && SHADERPASS == SHADERPASS_FORWARD) #endif #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoopDef.hlsl" #include "EnvLighting.hlsl" #include "HDRPToonFunction.hlsl" #ifdef _WRITE_TRANSPARENT_MOTION_VECTOR #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/MotionVectorVertexShaderCommon.hlsl" PackedVaryingsType Vert(AttributesMesh inputMesh, AttributesPass inputPass) { VaryingsType varyingsType; varyingsType.vmesh = VertMesh(inputMesh); return MotionVectorVS(varyingsType, inputMesh, inputPass); } #ifdef TESSELLATION_ON PackedVaryingsToPS VertTesselation(VaryingsToDS input) { VaryingsToPS output; output.vmesh = VertMeshTesselation(input.vmesh); MotionVectorPositionZBias(output); output.vpass.positionCS = input.vpass.positionCS; output.vpass.previousPositionCS = input.vpass.previousPositionCS; return PackVaryingsToPS(output); } #endif // TESSELLATION_ON #else // _WRITE_TRANSPARENT_MOTION_VECTOR #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/VertMesh.hlsl" PackedVaryingsType Vert(AttributesMesh inputMesh) { VaryingsType varyingsType; varyingsType.vmesh = VertMesh(inputMesh); return PackVaryingsType(varyingsType); } #ifdef TESSELLATION_ON PackedVaryingsToPS VertTesselation(VaryingsToDS input) { VaryingsToPS output; output.vmesh = VertMeshTesselation(input.vmesh); return PackVaryingsToPS(output); } #endif // TESSELLATION_ON #endif // _WRITE_TRANSPARENT_MOTION_VECTOR #ifdef TESSELLATION_ON #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/TessellationShare.hlsl" #endif /////////////////////////////////////////////////////////////////////////////// // Attenuation Functions / /////////////////////////////////////////////////////////////////////////////// // Grafted from URP // Matches Unity Vanila attenuation // Attenuation smoothly decreases to light range. float DistanceAttenuation(float distanceSqr, half2 distanceAttenuation) { // We use a shared distance attenuation for additional directional and puctual lights // for directional lights attenuation will be 1 float lightAtten = rcp(distanceSqr); #if SHADER_HINT_NICE_QUALITY // Use the smoothing factor also used in the Unity lightmapper. half factor = distanceSqr * distanceAttenuation.x; half smoothFactor = saturate(1.0h - factor * factor); smoothFactor = smoothFactor * smoothFactor; #else // We need to smoothly fade attenuation to light range. We start fading linearly at 80% of light range // Therefore: // fadeDistance = (0.8 * 0.8 * lightRangeSq) // smoothFactor = (lightRangeSqr - distanceSqr) / (lightRangeSqr - fadeDistance) // We can rewrite that to fit a MAD by doing // distanceSqr * (1.0 / (fadeDistanceSqr - lightRangeSqr)) + (-lightRangeSqr / (fadeDistanceSqr - lightRangeSqr) // distanceSqr * distanceAttenuation.y + distanceAttenuation.z half smoothFactor = saturate(distanceSqr * distanceAttenuation.x + distanceAttenuation.y); #endif return lightAtten * smoothFactor; } float ApplyChannelAlpha( float alpha) { return lerp(1.0, alpha, _ComposerMaskMode); } bool UtsUseScreenSpaceShadow(DirectionalLightData light, float3 normalWS) { #if defined(RAY_TRACED_SCREEN_SPACE_SHADOW_FLAG) // Two different options are possible here // - We have a ray trace shadow in which case we have no valid signal for a transmission and we need to fallback on the rasterized shadow // - We have a screen space shadow and it already contains the transmission shadow and we can use it straight away bool visibleLight = 0.5 * dot(normalWS, -light.forward) + 0.5 > 0.0; bool validScreenSpaceShadow = (light.screenSpaceShadowIndex & SCREEN_SPACE_SHADOW_INDEX_MASK) != INVALID_SCREEN_SPACE_SHADOW; bool rayTracedShadow = (light.screenSpaceShadowIndex & RAY_TRACED_SCREEN_SPACE_SHADOW_FLAG) != 0.0; return (validScreenSpaceShadow && ((rayTracedShadow && visibleLight) || !rayTracedShadow)); #else return ( (light.screenSpaceShadowIndex & SCREEN_SPACE_SHADOW_INDEX_MASK) != INVALID_SCREEN_SPACE_SHADOW); #endif } #ifdef UNITY_VIRTUAL_TEXTURING #define VT_BUFFER_TARGET SV_Target1 #define EXTRA_BUFFER_TARGET SV_Target2 #else #define EXTRA_BUFFER_TARGET SV_Target1 #endif uniform sampler2D _RaytracedHardShadow; float4 _RaytracedHardShadow_TexelSize; void Frag(PackedVaryingsToPS packedInput, #ifdef OUTPUT_SPLIT_LIGHTING out float4 outColor : SV_Target0, // outSpecularLighting #ifdef UNITY_VIRTUAL_TEXTURING out float4 outVTFeedback : VT_BUFFER_TARGET, #endif out float4 outDiffuseLighting : EXTRA_BUFFER_TARGET, OUTPUT_SSSBUFFER(outSSSBuffer) #else out float4 outColor : SV_Target0 #ifdef UNITY_VIRTUAL_TEXTURING ,out float4 outVTFeedback : VT_BUFFER_TARGET #endif #ifdef _WRITE_TRANSPARENT_MOTION_VECTOR , out float4 outMotionVec : EXTRA_BUFFER_TARGET #endif // _WRITE_TRANSPARENT_MOTION_VECTOR #endif // OUTPUT_SPLIT_LIGHTING #ifdef _DEPTHOFFSET_ON , out float outputDepth : SV_Depth #endif ) { #ifdef _WRITE_TRANSPARENT_MOTION_VECTOR // Init outMotionVector here to solve compiler warning (potentially unitialized variable) // It is init to the value of forceNoMotion (with 2.0) outMotionVec = float4(2.0, 0.0, 0.0, 0.0); #endif UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(packedInput); FragInputs input = UnpackVaryingsMeshToFragInputs(packedInput.vmesh); #if defined(PLATFORM_SUPPORTS_PRIMITIVE_ID_IN_PIXEL_SHADER) && SHADER_STAGE_FRAGMENT #if (defined(VARYINGS_NEED_PRIMITIVEID) || (SHADERPASS == SHADERPASS_FULL_SCREEN_DEBUG)) input.primitiveID = packedInput.primitiveID; #endif #endif #if defined(VARYINGS_NEED_CULLFACE) && SHADER_STAGE_FRAGMENT input.isFrontFace = IS_FRONT_VFACE(packedInput.cullFace, true, false); #endif float4 Set_UV0 = input.texCoord0; UTSData utsData; // We need to readapt the SS position as our screen space positions are for a low res buffer, but we try to access a full res buffer. input.positionSS.xy = _OffScreenRendering > 0 ? (input.positionSS.xy * _OffScreenDownsampleFactor) : input.positionSS.xy; uint2 tileIndex = uint2(input.positionSS.xy) / GetTileSize(); // input.positionSS is SV_Position PositionInputs posInput = GetPositionInput(input.positionSS.xy, _ScreenSize.zw, input.positionSS.z, input.positionSS.w, input.positionRWS.xyz, tileIndex); #ifdef VARYINGS_NEED_POSITION_WS float3 V = GetWorldSpaceNormalizeViewDir(input.positionRWS); #ifdef MATERIAL_TYPE_EYE // Must have view Dir to work float2 viewT = TransformObjectToTangent(V, input.tangentToWorld); float2 parallaxOffset = viewT; parallaxOffset.y = -parallaxOffset.y; Set_UV0.xy = clamp(Set_UV0.xy -_EyeParallaxAmount * parallaxOffset, 0, 1); #endif #else // Unused float3 V = float3(1.0, 1.0, 1.0); // Avoid the division by 0 #endif #ifdef _SURFACE_TYPE_TRANSPARENT uint featureFlags = LIGHT_FEATURE_MASK_FLAGS_TRANSPARENT; #else uint featureFlags = LIGHT_FEATURE_MASK_FLAGS_OPAQUE; #endif SurfaceData surfaceData; // used to get normalWS; BuiltinData builtinData; // used to get lightlayersAndSoOn GetSurfaceAndBuiltinData(input, V, posInput, surfaceData, builtinData); outColor = float4(0.0, 0.0, 0.0, 0.0); float3x3 tangentTransform = input.tangentToWorld; float4 _MainTex_var = SAMPLE_TEXTURE2D(_BaseColorMap, sampler_BaseColorMap, TRANSFORM_TEX(Set_UV0, _BaseColorMap)); float4 normalLocal = 0; if (_Use_SSSLut) { normalLocal = SAMPLE_TEXTURE2D_LOD(_NormalMap, sampler_NormalMap, TRANSFORM_TEX(Set_UV0, _BaseColorMap), _SSSIntensity); } else { normalLocal = SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, TRANSFORM_TEX(Set_UV0, _BaseColorMap)); } normalLocal.rgb = UnpackNormalScale(normalLocal, _NormalScale); float3 _NormalMap_var = normalize(mul(normalLocal.rgb, tangentTransform)); float smoothness = _Smoothness; float coatRoughness = 1; float metallic = _Metallic; float ao = 1.0; float3 specularColor = 0; #ifdef _MASKMAP float4 _MaskMap_var = SAMPLE_TEXTURE2D(_MaskMap, sampler_MaskMap, TRANSFORM_TEX(Set_UV0, _BaseColorMap)); metallic = _MaskMap_var.x; metallic = lerp(_MetallicRemapMin, _MetallicRemapMax, metallic); ao = _MaskMap_var.y; ao = lerp(_AORemapMin, _AORemapMax, ao); smoothness = _MaskMap_var.w; smoothness = lerp(_SmoothnessRemapMin, _SmoothnessRemapMax, smoothness); #endif #ifdef _ANISOTROPYMAP surfaceData.anisotropy = SAMPLE_TEXTURE2D(_AnisotropyMap, sampler_AnisotropyMap, TRANSFORM_TEX(Set_UV0, _AnisotropyMap)).r; #if _PBR_Mode_KK surfaceData.anisotropy += ADD_IDX(_Anisotropy) - 0.5; #else surfaceData.anisotropy *= ADD_IDX(_Anisotropy); #endif #else surfaceData.anisotropy = 1.0; surfaceData.anisotropy *= ADD_IDX(_Anisotropy); #endif #ifdef _PBR_Mode_KK metallic = 0.0; coatRoughness = 1 - smoothness; smoothness *=_BSDFContribution; #endif #ifdef _PBR_Mode_TOON float3 _SpecTex_var = 1; #ifdef _SPECULARCOLORMAP _SpecTex_var = SAMPLE_TEXTURE2D(_SpecularColorMap, sampler_SpecularColorMap, TRANSFORM_TEX(Set_UV0, _BaseColorMap)).rgb; #endif specularColor = _SpecTex_var * _SpecularColor; #else specularColor = SpecularColor(_MainTex_var.rgb * _BaseColor.rgb, metallic); #endif surfaceData.baseColor = _MainTex_var.rgb; surfaceData.coatMask = _MainTex_var.a; surfaceData.metallic = metallic; surfaceData.ambientOcclusion = ao; surfaceData.specularOcclusion = GetSpecularOcclusionFromAmbientOcclusion(dot(_NormalMap_var, V), ao, PerceptualRoughnessToRoughness(1 - smoothness)); surfaceData.perceptualSmoothness = smoothness; surfaceData.normalWS = _NormalMap_var; surfaceData.specularColor = specularColor; float perceptualRoughness = 1 - surfaceData.perceptualSmoothness; float3 tangentDir = Orthonormalize(tangentTransform[0].rgb, surfaceData.normalWS); float3 bitangentDir = normalize(cross(surfaceData.normalWS, tangentDir)); BSDFData bsdfData = ConvertSurfaceDataToBSDFData(input.positionSS.xy, surfaceData); // used to calc shadow bsdfData.coatRoughness = coatRoughness; bsdfData.normalWS = surfaceData.normalWS; bsdfData.ambientOcclusion = surfaceData.ambientOcclusion; bsdfData.specularOcclusion = surfaceData.specularOcclusion; bsdfData.perceptualRoughness = perceptualRoughness; bsdfData.anisotropy = surfaceData.anisotropy; bsdfData.tangentWS = tangentDir; bsdfData.bitangentWS = bitangentDir; PreLightData preLightData = GetPreLightData(V, posInput, bsdfData); // used to calc shadow UTSAggregateLighting utsAggregateLighting; ZERO_INITIALIZE(UTSAggregateLighting, utsAggregateLighting); UTSLightData customMainLight; customMainLight.shadowValue = 1.0f; //UTSLightData mainPunctualLight; //mainPunctualLight.lightColor = float3(0, 0, 0); #define UNITY_PROJ_COORD(a) a #define UNITY_SAMPLE_SCREEN_SHADOW(tex, uv) tex2Dproj( tex, UNITY_PROJ_COORD(uv) ).r float inverseClipping = 0.0; LightLoopContext context; context.shadowContext = InitShadowContext(); context.shadowValue = 1; context.sampleReflection = 0.0; #if UNITY_VERSION >= 202120 && UNITY_VERSION < 202320 context.splineVisibility = -1; #endif #ifdef APPLY_FOG_ON_SKY_REFLECTIONS context.positionWS = posInput.positionWS; #endif // With XR single-pass and camera-relative: offset position to do lighting computations from the combined center view (original camera matrix). // This is required because there is only one list of lights generated on the CPU. Shadows are also generated once and shared between the instanced views. ApplyCameraRelativeXR(posInput.positionWS); // Initialize the contactShadow and contactShadowFade fields InitContactShadow(posInput, context); float3 i_normalDir = surfaceData.normalWS; int mainLightIndex = -1; float channelAlpha = 0.0f; float3 finalColor = float3(0.0f, 0.0f, 0.0f); if (featureFlags & LIGHTFEATUREFLAGS_DIRECTIONAL) { // because of light culling or light layer, we can not adopt this // https://unity.slack.com/archives/C06V7HDDW/p1580959470180800 // int mainLightIndex = _DirectionalShadowIndex; mainLightIndex = GetUtsMainLightIndex(builtinData); DirectionalLightData lightData; ZERO_INITIALIZE(DirectionalLightData, lightData); if (mainLightIndex >= 0) { lightData = _DirectionalLightDatas[mainLightIndex]; } float3 lightColor = ApplyCurrentExposureMultiplier(lightData.color); float3 lightDirection = -lightData.forward; #ifndef LIGHT_EVALUATION_NO_COOKIE if (lightData.cookieMode != COOKIEMODE_NONE) { float3 lightToSample = input.positionRWS - lightData.positionRWS; lightColor *= EvaluateCookie_Directional(context, lightData, lightToSample); } #endif UTSLightData utsLightData; utsLightData.lightDirection = lightDirection; utsLightData.lightColor = lightColor; utsLightData.diffuseDimmer = lightData.diffuseDimmer; utsLightData.specularDimmer = lightData.specularDimmer; utsLightData.shadowTint = lightData.shadowTint; utsLightData.penumbraTint = lightData.penumbraTint; customMainLight = utsLightData; // Evaluate sun shadows. if (_DirectionalShadowIndex >= 0) { DirectionalLightData light = _DirectionalLightDatas[_DirectionalShadowIndex]; #if defined(SCREEN_SPACE_SHADOWS_ON) && !defined(_SURFACE_TYPE_TRANSPARENT) && !defined(UTS_USE_RAYTRACING_SHADOW) if (UtsUseScreenSpaceShadow(light, bsdfData.normalWS)) { // HDRP Contact Shadow context.shadowValue = GetScreenSpaceColorShadow(posInput, light.screenSpaceShadowIndex).SHADOW_TYPE_SWIZZLE; } else #endif { // TODO: this will cause us to load from the normal buffer first. Does this cause a performance problem? float3 L = -light.forward; // Is it worth sampling the shadow map? if ((light.lightDimmer > 0) && (light.shadowDimmer > 0) && // Note: Volumetric can have different dimmer, thus why we test it here IsNonZeroBSDF(V, L, preLightData, bsdfData) && !ShouldEvaluateThickObjectTransmission(V, L, preLightData, bsdfData, light.shadowIndex)) { #if defined(UTS_USE_RAYTRACING_SHADOW) { /* struct PositionInputs { float3 positionWS; // World space position (could be camera-relative) float2 positionNDC; // Normalized screen coordinates within the viewport : [0, 1) (with the half-pixel offset) uint2 positionSS; // Screen space pixel coordinates : [0, NumPixels) uint2 tileCoord; // Screen tile coordinates : [0, NumTiles) float deviceDepth; // Depth from the depth buffer : [0, 1] (typically reversed) float linearDepth; // View space Z coordinate : [Near, Far] }; float4 size = _RaytracedHardShadow_TexelSize; */ float r = UNITY_SAMPLE_SCREEN_SHADOW(_RaytracedHardShadow, float4(posInput.positionNDC.xy, lightDirection * _ShadowBias, 1)); context.shadowValue = r; } #else { context.shadowValue = GetDirectionalShadowAttenuation(context.shadowContext, posInput.positionSS, posInput.positionWS + lightDirection * _ShadowBias, GetNormalForShadowBias(bsdfData), light.shadowIndex, L); } #endif // UTS_USE_RAYTRACING_SHADOW } #if defined (UTS_USE_RAYTRACING_SHADOW) else { float r = UNITY_SAMPLE_SCREEN_SHADOW(_RaytracedHardShadow, float4(posInput.positionNDC.xy, lightDirection * _ShadowBias, 1)); context.shadowValue = r; } #endif // UTS_USE_RAYTRACING_SHADOW } context.shadowValue = lerp(1, context.shadowValue, lightData.shadowDimmer); customMainLight.shadowValue = context.shadowValue; } #if defined(UTS_DEBUG_SELFSHADOW) if (_DirectionalShadowIndex >= 0) finalColor = UTS_SelfShdowMainLight(context, input, _DirectionalShadowIndex); #else UTS_MainLight(context, input, utsLightData, surfaceData, bsdfData, inverseClipping, channelAlpha, utsData, utsAggregateLighting); #endif int i = 0; // Declare once to avoid the D3D11 compiler warning. for (i = 0; i < (int) _DirectionalLightCount; ++i) { if (IsMatchingLightLayer(_DirectionalLightDatas[i].lightLayers, builtinData.renderingLayers)) { if (mainLightIndex != i) { float notDirectional = 0.0f; UTSLightData utsLightData; utsLightData.lightColor = ApplyCurrentExposureMultiplier(_DirectionalLightDatas[i].color); utsLightData.lightDirection = -_DirectionalLightDatas[i].forward; utsLightData.diffuseDimmer = _DirectionalLightDatas[i].diffuseDimmer; utsLightData.specularDimmer = _DirectionalLightDatas[i].specularDimmer; utsLightData.shadowTint = _DirectionalLightDatas[i].shadowTint; utsLightData.penumbraTint = _DirectionalLightDatas[i].penumbraTint; #if defined(UTS_DEBUG_SELFSHADOW) #else UTS_OtherLights(context, input, utsLightData, surfaceData, bsdfData, 0, i_normalDir, notDirectional, channelAlpha, utsAggregateLighting); #endif } } } } #undef EVALUATE_BSDF_ENV #undef EVALUATE_BSDF_ENV_SKY if (featureFlags & LIGHTFEATUREFLAGS_PUNCTUAL) { uint lightCount, lightStart; #ifndef LIGHTLOOP_DISABLE_TILE_AND_CLUSTER GetCountAndStart(posInput, LIGHTCATEGORY_PUNCTUAL, lightStart, lightCount); #else // LIGHTLOOP_DISABLE_TILE_AND_CLUSTER lightCount = _PunctualLightCount; lightStart = 0; #endif bool fastPath = false; #if SCALARIZE_LIGHT_LOOP uint lightStartLane0; fastPath = IsFastPath(lightStart, lightStartLane0); if (fastPath) { lightStart = lightStartLane0; } #endif // Scalarized loop. All lights that are in a tile/cluster touched by any pixel in the wave are loaded (scalar load), only the one relevant to current thread/pixel are processed. // For clarity, the following code will follow the convention: variables starting with s_ are meant to be wave uniform (meant for scalar register), // v_ are variables that might have different value for each thread in the wave (meant for vector registers). // This will perform more loads than it is supposed to, however, the benefits should offset the downside, especially given that light data accessed should be largely coherent. // Note that the above is valid only if wave intriniscs are supported. uint v_lightListOffset = 0; uint v_lightIdx = lightStart; float channelAlpha = 0.0f; [loop] // vulkan shader compiler can not unroll. while (v_lightListOffset < lightCount) { v_lightIdx = FetchIndex(lightStart, v_lightListOffset); #if SCALARIZE_LIGHT_LOOP uint s_lightIdx = ScalarizeElementIndex(v_lightIdx, fastPath); #else uint s_lightIdx = v_lightIdx; #endif if (s_lightIdx == -1) break; LightData s_lightData = FetchLight(s_lightIdx); // If current scalar and vector light index match, we process the light. The v_lightListOffset for current thread is increased. // Note that the following should really be ==, however, since helper lanes are not considered by WaveActiveMin, such helper lanes could // end up with a unique v_lightIdx value that is smaller than s_lightIdx hence being stuck in a loop. All the active lanes will not have this problem. if (s_lightIdx >= v_lightIdx) { v_lightListOffset++; if (IsMatchingLightLayer(s_lightData.lightLayers, builtinData.renderingLayers)) { float3 lightDirection; float4 distances; // {d, d^2, 1/d, d_proj} GetPunctualLightVectors(posInput.positionWS, s_lightData, lightDirection, distances); float4 lightColor = EvaluateLight_Punctual(context, posInput, s_lightData, lightDirection, distances); float3 additionalLightColor = ApplyCurrentExposureMultiplier(lightColor.rgb) * lightColor.a; const float notDirectional = 1.0f; UTSLightData utsLightData; utsLightData.lightColor = additionalLightColor; utsLightData.lightDirection = lightDirection; utsLightData.diffuseDimmer = s_lightData.diffuseDimmer; utsLightData.specularDimmer = s_lightData.specularDimmer; utsLightData.shadowTint = s_lightData.shadowTint; utsLightData.penumbraTint = s_lightData.penumbraTint; #if defined(UTS_DEBUG_SELFSHADOW) #else posInput.positionWS = posInput.positionWS + lightDirection * _ShadowBias; float shadow = EvaluateShadow_Punctual(context, posInput, s_lightData, builtinData, GetNormalForShadowBias(bsdfData), lightDirection, distances); context.shadowValue = shadow; posInput.positionWS = posInput.positionWS - lightDirection * _ShadowBias; if (length(utsLightData.lightColor) >= length(customMainLight.lightColor)) { float3 lightDirectionToObject; float4 distancesToObject; // {d, d^2, 1/d, d_proj} float3 objectCenter = (TransformObjectToWorld(float3(0, 0, 0))); GetPunctualLightVectors(objectCenter, s_lightData, lightDirectionToObject, distancesToObject); float4 lightColorToObject = EvaluateLight_Punctual(context, posInput, s_lightData, lightDirectionToObject, distancesToObject); float3 additionalLightColorToObject = ApplyCurrentExposureMultiplier(lightColorToObject.rgb) * lightColorToObject.a; customMainLight = utsLightData; customMainLight.lightColor = additionalLightColorToObject; customMainLight.lightDirection = lightDirectionToObject; customMainLight.shadowValue = context.shadowValue; } UTS_OtherLights(context, input, utsLightData, surfaceData, bsdfData, s_lightData.lightType, i_normalDir, notDirectional, channelAlpha, utsAggregateLighting); #endif } } } } //v.2.0.7 #if SHADEROPTIONS_AREA_LIGHTS if (featureFlags & LIGHTFEATUREFLAGS_AREA) { uint lightCount, lightStart; #ifndef LIGHTLOOP_DISABLE_TILE_AND_CLUSTER GetCountAndStart(posInput, LIGHTCATEGORY_AREA, lightStart, lightCount); #else lightCount = _AreaLightCount; lightStart = _PunctualLightCount; #endif // COMPILER BEHAVIOR WARNING! // If rectangle lights are before line lights, the compiler will duplicate light matrices in VGPR because they are used differently between the two types of lights. // By keeping line lights first we avoid this behavior and save substantial register pressure. // TODO: This is based on the current Lit.shader and can be different for any other way of implementing area lights, how to be generic and ensure performance ? uint i; if (lightCount > 0) { i = 0; uint last = lightCount - 1; LightData s_lightData = FetchLight(lightStart, i); [loop] // vulkan shader compiler can not unroll. while (i <= last) { if (IsMatchingLightLayer(s_lightData.lightLayers, builtinData.renderingLayers)) { float3 lightDirection = -s_lightData.forward; float3 areaLightColor = ApplyCurrentExposureMultiplier(s_lightData.color.rgb); const float notDirectional = 1.0f; UTSLightData utsLightData; utsLightData.lightColor = areaLightColor; utsLightData.lightDirection = lightDirection; utsLightData.diffuseDimmer = s_lightData.diffuseDimmer; utsLightData.specularDimmer = s_lightData.specularDimmer; utsLightData.shadowTint = s_lightData.shadowTint; utsLightData.penumbraTint = s_lightData.penumbraTint; bool isRectLight = s_lightData.lightType == GPULIGHTTYPE_RECTANGLE; float3 unL = s_lightData.positionRWS - posInput.positionWS; utsLightData.lightDirection = normalize(unL); float3 center = mul(preLightData.orthoBasisViewNormal, unL); float3 right = mul(preLightData.orthoBasisViewNormal, s_lightData.right); float3 up = mul(preLightData.orthoBasisViewNormal, s_lightData.up); float halfWidth = s_lightData.size.x * 0.5; float halfHeight = s_lightData.size.y * 0.5; float intensity = PillowWindowing(unL, s_lightData.right, s_lightData.up, halfWidth, halfHeight, s_lightData.rangeAttenuationScale, s_lightData.rangeAttenuationBias); // Make sure the light is front-facing (and has a non-zero effective area). intensity *= (isRectLight && dot(unL, s_lightData.forward) >= 0) ? 0 : 1; float4 ltcValue; // Diffuse ltcValue = EvaluateLTC_Area(isRectLight, center, right, up, halfWidth, halfHeight, transpose(preLightData.ltcTransformDiffuse), /*bsdfData.perceptualRoughness*/ 1.0f, s_lightData.cookieMode, s_lightData.cookieScaleOffset); //utsLightData.diffuseDimmer *= ltcValue.a * intensity; utsLightData.lightColor *= ltcValue.rgb * ltcValue.a * intensity; // Specular ltcValue = EvaluateLTC_Area(isRectLight, center, right, up, halfWidth, halfHeight, transpose(preLightData.ltcTransformSpecular[0]), bsdfData.perceptualRoughness, s_lightData.cookieMode, s_lightData.cookieScaleOffset); utsLightData.specularDimmer *= ltcValue.a * intensity; if (isRectLight) { //Evaluate the shadow part float shadow; posInput.positionWS = posInput.positionWS + utsLightData.lightDirection * _ShadowBias; #if defined(SCREEN_SPACE_SHADOWS_ON) && !defined(_SURFACE_TYPE_TRANSPARENT) if ((s_lightData.screenSpaceShadowIndex & SCREEN_SPACE_SHADOW_INDEX_MASK) != INVALID_SCREEN_SPACE_SHADOW) { shadow = GetScreenSpaceShadow(posInput, s_lightData.screenSpaceShadowIndex); } else #endif { shadow = EvaluateShadow_RectArea(context, posInput, s_lightData, builtinData, GetNormalForShadowBias(bsdfData), normalize(s_lightData.positionRWS), length(s_lightData.positionRWS)); } context.shadowValue = shadow; posInput.positionWS = posInput.positionWS - lightDirection * _ShadowBias; } #if defined(UTS_DEBUG_SELFSHADOW) #else UTS_OtherLights(context, input, utsLightData, surfaceData, bsdfData, s_lightData.lightType, i_normalDir, notDirectional, channelAlpha, utsAggregateLighting); //utsAggregateLighting.directDiffuse += ltcValue.rgb * ltcValue.a * intensity * s_lightData.diffuseDimmer; //utsAggregateLighting.directDiffuse += intensity; #endif /* if(s_lightData.lightType == GPULIGHTTYPE_RECTANGLE) { #if SHADEROPTIONS_BARN_DOOR // Apply the barn door modification to the light data RectangularLightApplyBarnDoor(s_lightData, posInput.positionWS); #endif if (dot(s_lightData.forward, unL) < FLT_EPS) { float3x3 lightToWorld = float3x3(s_lightData.right, s_lightData.up, -s_lightData.forward); unL = mul(unL, transpose(lightToWorld)); float halfWidth = s_lightData.size.x * 0.5; float halfHeight = s_lightData.size.y * 0.5; float range = s_lightData.range; float3 invHalfDim = rcp(float3(range + halfWidth, range + halfHeight, range)); float intensity; // Compute the light attenuation. #ifdef ELLIPSOIDAL_ATTENUATION // The attenuation volume is an axis-aligned ellipsoid s.t. // r1 = (r + w / 2), r2 = (r + h / 2), r3 = r. intensity = EllipsoidalDistanceAttenuation(unL, invHalfDim, s_lightData.rangeAttenuationScale, s_lightData.rangeAttenuationBias); #else // The attenuation volume is an axis-aligned box s.t. // hX = (r + w / 2), hY = (r + h / 2), hZ = r. intensity = BoxDistanceAttenuation(unL, invHalfDim, s_lightData.rangeAttenuationScale, s_lightData.rangeAttenuationBias); #endif if(intensity != 0.0f) { utsLightData.diffuseDimmer *= intensity; utsLightData.specularDimmer *= intensity; // Translate the light s.t. the shaded point is at the origin of the coordinate system. s_lightData.positionRWS -= posInput.positionWS; float4x3 lightVerts; // TODO: some of this could be precomputed. lightVerts[0] = s_lightData.positionRWS + s_lightData.right * -halfWidth + s_lightData.up * -halfHeight; // LL lightVerts[1] = s_lightData.positionRWS + s_lightData.right * -halfWidth + s_lightData.up * halfHeight; // UL lightVerts[2] = s_lightData.positionRWS + s_lightData.right * halfWidth + s_lightData.up * halfHeight; // UR lightVerts[3] = s_lightData.positionRWS + s_lightData.right * halfWidth + s_lightData.up * -halfHeight; // LR // Rotate the endpoints into the local coordinate system. lightVerts = mul(lightVerts, transpose(preLightData.orthoBasisViewNormal)); float3 ltcValue; // Evaluate the diffuse part // Polygon irradiance in the transformed configuration. float4x3 LD = mul(lightVerts, preLightData.ltcTransformDiffuse); float3 formFactorD; #ifdef APPROXIMATE_POLY_LIGHT_AS_SPHERE_LIGHT formFactorD = PolygonFormFactor(LD, real3(0,0,1), 4); ltcValue = PolygonIrradianceFromVectorFormFactor(formFactorD); #else ltcValue = PolygonIrradiance(LD); #endif utsLightData.diffuseDimmer *= ltcValue; // Evaluate the specular part // Polygon irradiance in the transformed configuration. float4x3 LS = mul(lightVerts, preLightData.ltcTransformSpecular); float3 formFactorS; #ifdef APPROXIMATE_POLY_LIGHT_AS_SPHERE_LIGHT formFactorS = PolygonFormFactor(LS, real3(0,0,1), 4); ltcValue = PolygonIrradianceFromVectorFormFactor(formFactorS); #else ltcValue = PolygonIrradiance(LS); #endif utsLightData.specularDimmer *= ltcValue; //Evaluate the shadow part float shadow; posInput.positionWS = posInput.positionWS + lightDirection * _ShadowBias; #if defined(SCREEN_SPACE_SHADOWS_ON) && !defined(_SURFACE_TYPE_TRANSPARENT) if ((s_lightData.screenSpaceShadowIndex & SCREEN_SPACE_SHADOW_INDEX_MASK) != INVALID_SCREEN_SPACE_SHADOW) { shadow = GetScreenSpaceShadow(posInput, s_lightData.screenSpaceShadowIndex); } else #endif { shadow = EvaluateShadow_RectArea(context, posInput, s_lightData, builtinData, GetNormalForShadowBias(bsdfData), normalize(s_lightData.positionRWS), length(s_lightData.positionRWS)); } context.shadowValue = shadow; posInput.positionWS = posInput.positionWS - lightDirection * _ShadowBias; #if defined(UTS_DEBUG_SELFSHADOW) #else UTS_OtherLights(context, input, utsLightData, surfaceData, bsdfData, s_lightData.lightType, i_normalDir, notDirectional, channelAlpha, utsAggregateLighting); #endif } } } else if(s_lightData.lightType == GPULIGHTTYPE_TUBE) { float len = s_lightData.size.x; float3 T = s_lightData.right; // Pick the major axis of the ellipsoid. float3 axis = s_lightData.right; // We define the ellipsoid s.t. r1 = (r + len / 2), r2 = r3 = r. // TODO: This could be precomputed. float range = s_lightData.range; float invAspectRatio = saturate(range / (range + (0.5 * len))); // Compute the light attenuation. float intensity = EllipsoidalDistanceAttenuation(unL, axis, invAspectRatio, s_lightData.rangeAttenuationScale, s_lightData.rangeAttenuationBias); if(intensity != 0.0f) { utsLightData.diffuseDimmer *= intensity; utsLightData.specularDimmer *= intensity; // Translate the light s.t. the shaded point is at the origin of the coordinate system. s_lightData.positionRWS -= posInput.positionWS; // TODO: some of this could be precomputed. float3 P1 = s_lightData.positionRWS - T * (0.5 * len); float3 P2 = s_lightData.positionRWS + T * (0.5 * len); // Rotate the endpoints into the local coordinate system. P1 = mul(P1, transpose(preLightData.orthoBasisViewNormal)); P2 = mul(P2, transpose(preLightData.orthoBasisViewNormal)); // Compute the binormal in the local coordinate system. float3 B = normalize(cross(P1, P2)); float ltcValue; // Evaluate the diffuse part ltcValue = LTCEvaluate(P1, P2, B, preLightData.ltcTransformDiffuse); utsLightData.diffuseDimmer *= ltcValue; // Evaluate the specular part ltcValue = LTCEvaluate(P1, P2, B, preLightData.ltcTransformSpecular); utsLightData.specularDimmer *= ltcValue; #if defined(UTS_DEBUG_SELFSHADOW) #else UTS_OtherLights(context, input, utsLightData, surfaceData, bsdfData, s_lightData.lightType, i_normalDir, notDirectional, channelAlpha, utsAggregateLighting); #endif } } */ } s_lightData = FetchLight(lightStart, min(++i, last)); } } } #endif // SHADEROPTIONS_AREA_LIGHTS #ifdef _EMISSIVE_SIMPLE float4 _Emissive_Tex_var = tex2D(_Emissive_Tex, TRANSFORM_TEX(Set_UV0, _Emissive_Tex)); float emissiveMask = _Emissive_Tex_var.a; emissive = _Emissive_Tex_var.rgb * _Emissive_Color.rgb * emissiveMask; #elif _EMISSIVE_ANIMATION //v.2.0.7 Calculation View Coord UV for Scroll float3 viewNormal_Emissive = (mul(UNITY_MATRIX_V, float4(i_normalDir, 0))).xyz; float3 NormalBlend_Emissive_Detail = viewNormal_Emissive * float3(-1, -1, 1); float3 NormalBlend_Emissive_Base = (mul(UNITY_MATRIX_V, float4(utsData.viewDirection, 0)).xyz * float3(-1, -1, 1)) + float3(0, 0, 1); float3 noSknewViewNormal_Emissive = NormalBlend_Emissive_Base * dot(NormalBlend_Emissive_Base, NormalBlend_Emissive_Detail) / NormalBlend_Emissive_Base.z - NormalBlend_Emissive_Detail; float2 _ViewNormalAsEmissiveUV = noSknewViewNormal_Emissive.xy * 0.5 + 0.5; float2 _ViewCoord_UV = RotateUV(_ViewNormalAsEmissiveUV, -(utsData.cameraDir * utsData.cameraRoll), float2(0.5, 0.5), 1.0); //Invert if it's "inside the mirror". if (utsData.signMirror < 0) { _ViewCoord_UV.x = 1 - _ViewCoord_UV.x; } else { _ViewCoord_UV = _ViewCoord_UV; } float2 emissive_uv = lerp(Set_UV0, _ViewCoord_UV, _Is_ViewCoord_Scroll); // float4 _time_var = _Time; float _base_Speed_var = (_time_var.g * _Base_Speed); float _Is_PingPong_Base_var = lerp(_base_Speed_var, sin(_base_Speed_var), _Is_PingPong_Base); float2 scrolledUV = emissive_uv + float2(_Scroll_EmissiveU, _Scroll_EmissiveV) * _Is_PingPong_Base_var; float rotateVelocity = _Rotate_EmissiveUV * 3.141592654; float2 _rotate_EmissiveUV_var = RotateUV(scrolledUV, rotateVelocity, float2(0.5, 0.5), _Is_PingPong_Base_var); float4 _Emissive_Tex_var = tex2D(_Emissive_Tex, TRANSFORM_TEX(Set_UV0, _Emissive_Tex)); float emissiveMask = _Emissive_Tex_var.a; _Emissive_Tex_var = tex2D(_Emissive_Tex, TRANSFORM_TEX(_rotate_EmissiveUV_var, _Emissive_Tex)); float _colorShift_Speed_var = 1.0 - cos(_time_var.g * _ColorShift_Speed); float viewShift_var = smoothstep(0.0, 1.0, max(0, dot(utsData.normalDirection, utsData.viewDirection))); float4 colorShift_Color = lerp(_Emissive_Color, lerp(_Emissive_Color, _ColorShift, _colorShift_Speed_var), _Is_ColorShift); float4 viewShift_Color = lerp(_ViewShift, colorShift_Color, viewShift_var); float4 emissive_Color = lerp(colorShift_Color, viewShift_Color, _Is_ViewShift); emissive = emissive_Color.rgb * _Emissive_Tex_var.rgb * emissiveMask; // //v.2.0.6: GI_Intensity with Intensity Multiplier Filter #endif // We directly calculate custome main light during the light loop in upper code to avoid extra calculation //customMainLight = GetCustomMainLightData(builtinData, mainPunctualLight); #if _SDFShadow || (_RECEIVE_HAIR_SHADOW && ENABLE_UTS_HAIR_SHAOW) float3 defaultLightDirection = normalize(UNITY_MATRIX_V[2].xyz + UNITY_MATRIX_V[1].xyz); float3 defaultLightColor = saturate(max(float3(0.05, 0.05, 0.05) * _Unlit_Intensity, max(ShadeSH9(float4(0.0, 0.0, 0.0, 1.0)), ShadeSH9(float4(0.0, -1.0, 0.0, 1.0)).rgb) * _Unlit_Intensity)); float3 customLightDirection = normalize(mul(UNITY_MATRIX_M, float4(((float3(1.0, 0.0, 0.0) * _Offset_X_Axis_BLD * 10) + (float3(0.0, 1.0, 0.0) * _Offset_Y_Axis_BLD * 10) + (float3(0.0, 0.0, -1.0) * lerp(-1.0, 1.0, _Inverse_Z_Axis_BLD))), 0)).xyz); float3 lightDirection = normalize(lerp(defaultLightDirection, customMainLight.lightDirection.xyz, any(customMainLight.lightDirection.xyz))); lightDirection = lerp(lightDirection, customLightDirection, _Is_BLD); float3 originalLightColor = customMainLight.lightColor.rgb; originalLightColor = lerp(originalLightColor, clamp(originalLightColor, ConvertFromEV100(_ToonEvAdjustmentValueMin ), ConvertFromEV100(_ToonEvAdjustmentValueMax)), _ToonEvAdjustmentCurve) * _Light_Intensity_Multiplier; float3 lightColor = lerp(max(defaultLightColor, originalLightColor), max(defaultLightColor, saturate(originalLightColor)), max(_Is_Filter_LightColor, _ToonLightHiCutFilter)); float4 _1st_ShadeMap_var = lerp(SAMPLE_TEXTURE2D(_1st_ShadeMap, sampler_BaseColorMap,TRANSFORM_TEX(Set_UV0, _1st_ShadeMap)), _MainTex_var, _Use_BaseAs1st); float3 _1st_Shade_var = lerp((_1st_ShadeMap_var.rgb * _1st_ShadeColor.rgb), ((_1st_ShadeMap_var.rgb * _1st_ShadeColor.rgb) * lightColor), _Is_LightColor_1st_Shade); float systemShadowValue = lerp(1.0f, saturate(customMainLight.shadowValue * 2.0f), _Set_SystemShadowsToBase); #endif #ifdef _SDFShadow // modified by Suomi @ 20230902 - SDFResult is used to sample SDF texture on the correct side float angle; bool rightside; float2 SDF_UV = TRANSFORM_TEX(Set_UV0, _BaseColorMap); float4 sdfRes = SDFResult(rightside, angle, customMainLight.lightDirection, SDF_UV); float sdfShadowValue = 1.0f - SDFMask(angle, sdfRes.r); utsAggregateLighting.directDiffuse = lerp(_1st_Shade_var, bsdfData.diffuseColor * _BaseColor.rgb * lightColor, sdfShadowValue * systemShadowValue); utsAggregateLighting.directSpecular = lerp(0, utsAggregateLighting.directSpecular, sdfShadowValue * systemShadowValue); utsAggregateLighting.directSpecular += _SDFNoseHighlightCoef * SDFNoseHighlight(angle, sdfRes.g, rightside, SDF_UV) * lightColor; #endif #if _RECEIVE_HAIR_SHADOW && ENABLE_UTS_HAIR_SHAOW // Push the face fragment view space position towards the light for a little bit float hairShadowOpacity = saturate(Remap(length(posInput.positionWS), float2(_HairShadowFadeOutDistance, _HairShadowFadeInDistance), float2(0, 1))); if(hairShadowOpacity > 0) { float3 viewLightDir = TransformWorldToViewDir(customMainLight.lightDirection); // / posInput.deviceDepth; when linearDepth grows large, the movement amount should be lower since we are getting further from the face. float3 cameraDirOS = normalize(TransformWorldToObject(GetCameraPositionWS())); float shadowLengthY = _HairShadowDistance * 5.0 * max(0.5, posInput.linearDepth * _HairShadowDistanceScaleFactor) / posInput.linearDepth; float2 shadowLength = float2(shadowLengthY * 2.0f, shadowLengthY); float3 camDirOS = normalize(TransformWorldToObject(GetCameraPositionWS())); float camDirFactor = 1 - smoothstep(0.1, 0.9, camDirOS.y); shadowLength.y *= camDirFactor; float2 samplingPoint = (input.positionSS.xy + shadowLength * viewLightDir.xy * (_ScreenSize.xy / float2 (1920.0f, 1080.0f))) * _ScreenSize.zw; // Use 1080p as the reference resolution to achieve consistent shadow lengths across various screen resolutions. // Then sample the hair buffer, to see if the fragment lands in shadow. float2 scaledUVs = samplingPoint * _HairShadowRTHandleScale; // We have to including the scaling factor for our shadow map since we are not going to allocate new texture if the rendering resolution changed. float hairDepth = SAMPLE_TEXTURE2D(_HairShadowTex, s_trilinear_clamp_sampler, scaledUVs).r; float depthCorrect = posInput.deviceDepth <= hairDepth + _HairShadowDepthBias ? 1 : 0; // Hair < Face means Hair are closer to camera // Note that we have LinearEyeDepth in the buffer. A comparison of depth is needed so that we don't project the shadow of hair behind the face. float hairShadow = lerp(0,hairShadowOpacity,depthCorrect); utsAggregateLighting.directDiffuse = lerp(utsAggregateLighting.directDiffuse, _1st_Shade_var, hairShadow * systemShadowValue); utsAggregateLighting.directSpecular = lerp(utsAggregateLighting.directSpecular, 0, hairShadow * systemShadowValue); } #endif // Ambient utsAggregateLighting.indirectDiffuse = ComputeIndirectDiffuse(posInput, bsdfData, V) * _ID_Intensity; utsAggregateLighting.indirectSpecular = ComputeIndirectSpecular(context, posInput, preLightData, bsdfData, surfaceData, builtinData, V) * _IR_Intensity; float3 finalColorWoEmissive = AccumulateAggregateLighting(utsAggregateLighting); finalColorWoEmissive = GetExposureAdjustedColor(finalColorWoEmissive); finalColorWoEmissive = ApplyCompensation(finalColorWoEmissive); finalColor = finalColorWoEmissive + emissive; #ifdef _IS_TRANSCLIPPING_OFF outColor = float4(finalColor, 1 * ApplyChannelAlpha(channelAlpha)); #elif _IS_TRANSCLIPPING_ON float Set_Opacity = saturate((inverseClipping + _Tweak_transparency)); outColor = EvaluateAtmosphericScattering(posInput, V, float4(finalColor, 1)); outColor = float4(outColor.rgb, Set_Opacity * ApplyChannelAlpha(channelAlpha)); #endif #if MATERIAL_TYPE_FRONT_HAIR && ENABLE_UTS_HAIR_BLENDING float2 screenUV = posInput.positionNDC * _HairBlendingRTHandleScale.xy; float4 hairBlendingMap = SAMPLE_TEXTURE2D(_HairBlendingTex, s_trilinear_clamp_sampler, screenUV); outColor.rgb = lerp(outColor.rgb, hairBlendingMap.rgb, hairBlendingMap.a * _HairBlendingFactor); #endif #if UTS_DEBUG_SHADOWMAP || UTS_DEBUG_SELFSHADOW outColor.rgb = 1; #ifdef UTS_DEBUG_SELFSHADOW outColor.rgb = min(finalColor, outColor.rgb); #endif #ifdef UTS_DEBUG_SHADOWMAP #ifdef UTS_DEBUG_SHADOWMAP_BINALIZATION outColor.rgb = min(context.shadowValue < 0.9f ? clamp(context.shadowValue - 0.2, 0.0, 0.9) : 1.0f, outColor.rgb); #else outColor.rgb = min(context.shadowValue, outColor.rgb); #endif #endif // ifdef UTS_DEBUG_SHADOWMAP #endif // defined(UTS_DEBUG_SHADOWMAP) || defined(UTS_DEBUG_SELFSHADOW) #ifdef _DEPTHOFFSET_ON outputDepth = posInput.deviceDepth; #endif #ifdef UNITY_VIRTUAL_TEXTURING outVTFeedback = builtinData.vtPackedFeedback; #endif //outColor.rgb = customMainLight.shadowValue; }