286 lines
11 KiB
HLSL
286 lines
11 KiB
HLSL
#ifndef NOVA_UIBLOCK_2D
|
|
#define NOVA_UIBLOCK_2D
|
|
|
|
#if defined(NOVA_OUTER_BORDER) || defined(NOVA_INNER_BORDER) || defined(NOVA_CENTER_BORDER)
|
|
#define NOVA_BORDER 1
|
|
#endif
|
|
|
|
#define NOVA_NO_INDEX_BUFFER 1
|
|
#define NOVA_PREMUL_COLORS
|
|
|
|
#include "Nova.cginc"
|
|
#include "Generated/UIBlock2D.g.cginc"
|
|
|
|
NOVA_DECLARE_BUFFER(SubQuadVert, _NovaSubQuadVerts);
|
|
|
|
#if defined(NOVA_DYNAMIC_IMAGE)
|
|
#define NOVA_IMAGE 1
|
|
sampler2D _NovaDynamicTexture;
|
|
|
|
#elif defined(NOVA_STATIC_IMAGE)
|
|
#define NOVA_IMAGE 1
|
|
UNITY_DECLARE_TEX2DARRAY(_NovaTextureArray);
|
|
#endif
|
|
|
|
NOVA_DECLARE_BUFFER(UIBlock2DData, _NovaData);
|
|
|
|
// In order to avoid matrix mul, we just transform the axes using the matrices in several steps
|
|
float2 GetBlockFromViewScale(float4x4 rootFromBlock)
|
|
{
|
|
float3 blockSpaceX = mul(rootFromBlock, float4(1, 0, 0, 1)).xyz;
|
|
float3 blockSpaceY = mul(rootFromBlock, float4(0, 1, 0, 1)).xyz;
|
|
float3 blockSpaceRoot = rootFromBlock._m03_m13_m23;
|
|
|
|
float3 viewSpaceRoot = UnityObjectToViewPos(blockSpaceRoot);
|
|
|
|
float2 scale;
|
|
scale.x = length(UnityObjectToViewPos(blockSpaceX) - viewSpaceRoot);
|
|
scale.y = length(UnityObjectToViewPos(blockSpaceY) - viewSpaceRoot);
|
|
return abs(scale) > NOVA_EPSILON ? 1.0 / scale : 0;
|
|
}
|
|
|
|
v2f NovaVert(NovaQuadVert v, uint instanceID : SV_InstanceID, uint vid : SV_VERTEXID)
|
|
{
|
|
NovaVertInit(instanceID, v2f, o);
|
|
|
|
uint vertIndex = InstanceIDToVertIndex(instanceID, vid, 4u);
|
|
|
|
NOVA_GET_BUFFER_ITEM_SubQuadVert(vert, vertIndex, _NovaSubQuadVerts);
|
|
NOVA_GET_BUFFER_ITEM_UIBlock2DData(shaderData, vert.BlockDataIndex, _NovaData);
|
|
|
|
NOVA_GET_BUFFER_ITEM_TransformAndLighting(transformAndLighting, shaderData.TransformIndex, _NovaTransformsAndLighting);
|
|
|
|
float2 blockPos = vert.Pos;
|
|
|
|
float3 rootSpace = mul(transformAndLighting.RootFromBlock, float4(blockPos, 0, 1)).xyz;
|
|
float2 halfBlockSize = .5 * shaderData.QuadSize;
|
|
|
|
// Bump up the size so we have space to soften the edges
|
|
float3 viewSpaceVertPos = UnityObjectToViewPos(rootSpace);
|
|
float minScreenDimension = min(_ScreenParams.x, _ScreenParams.y);
|
|
float sizeIncreaseAmount = 1.5 * _NovaEdgeSoftenWidth * abs(viewSpaceVertPos.z) / minScreenDimension;
|
|
|
|
float2 borderedHalfSize = halfBlockSize;
|
|
#if defined(NOVA_OUTER_BORDER)
|
|
borderedHalfSize += shaderData.BorderWidth;
|
|
#elif defined(NOVA_CENTER_BORDER)
|
|
borderedHalfSize += .5 * shaderData.BorderWidth;
|
|
#endif
|
|
|
|
float2 epsilon = .001 * borderedHalfSize;
|
|
float2 sizeIncreaseMask = step(abs(borderedHalfSize - abs(blockPos)), epsilon);
|
|
sizeIncreaseMask *= vert.EdgeSoftenMask;
|
|
float2 scale = GetBlockFromViewScale(transformAndLighting.RootFromBlock);
|
|
blockPos += sign(blockPos) * sizeIncreaseMask * sizeIncreaseAmount * scale;
|
|
rootSpace = mul(transformAndLighting.RootFromBlock, float4(blockPos, 0, 1)).xyz;
|
|
|
|
float nFactor = GetNFactor(halfBlockSize);
|
|
float2 nHalfSize = halfBlockSize * nFactor;
|
|
|
|
float2 nPos = blockPos * nFactor;
|
|
SetNPos(o, nPos);
|
|
|
|
float nCornerRadius = shaderData.CornerRadius * nFactor;
|
|
SetNCornerRadius(o, nCornerRadius);
|
|
float2 nCornerOrigin = nHalfSize - nCornerRadius;
|
|
SetNCornerOrigin(o, nCornerOrigin);
|
|
|
|
|
|
float3 worldPos = NovaRootToWorldPos(rootSpace);
|
|
o.pos = UnityWorldToClipPos(worldPos);
|
|
|
|
SetEdgeSoftenDisabled(o, 1.0 - vert.EdgeSoftenMask);
|
|
|
|
NovaColorToV2F(Color, o, shaderData.PrimaryColor);
|
|
NovaColorToV2F(GradientColor, o, shaderData.GradientColor);
|
|
|
|
float2 unrotatedGradientSpacePos = blockPos - shaderData.GradientCenter;
|
|
float2 gradientSpacePos = 0;
|
|
gradientSpacePos.x = unrotatedGradientSpacePos.x * shaderData.GradientRotationSinCos.y + unrotatedGradientSpacePos.y * shaderData.GradientRotationSinCos.x;
|
|
gradientSpacePos.y = unrotatedGradientSpacePos.x * shaderData.GradientRotationSinCos.x - unrotatedGradientSpacePos.y * shaderData.GradientRotationSinCos.y;
|
|
float2 gradientUV = gradientSpacePos * shaderData.GradientSizeReciprocal;
|
|
SetGradientSpaceUV(o, gradientUV);
|
|
|
|
#if defined(NOVA_RADIAL_FILL)
|
|
float2 sinCos;
|
|
float2 radialFillSpacePos = RadialFillVert(nPos, shaderData.RadialFillCenter, shaderData.RadialFillRotation, shaderData.RadialFillAngle, nFactor, sinCos);
|
|
SetRadialFillSpacePos(o, radialFillSpacePos);
|
|
SetRadialFillSinCos(o, sinCos);
|
|
#endif
|
|
|
|
#if defined(NOVA_IMAGE)
|
|
float2 uv = SafeDividePositive(blockPos, halfBlockSize);
|
|
float2 imageUV = uv * vert.UVZoom + vert.CenterUV;
|
|
SetImageUV(o, imageUV);
|
|
#endif
|
|
|
|
#if defined(NOVA_CLIPPING)
|
|
SetRootPos(o, rootSpace);
|
|
#endif
|
|
|
|
#if defined(NOVA_STATIC_IMAGE)
|
|
float packSlice = (float)shaderData.TexturePackSlice;
|
|
SetTextureBufferIndex(o, packSlice);
|
|
#endif
|
|
|
|
#if defined(NOVA_BORDER)
|
|
NovaColorToV2F(BorderColor, o, shaderData.BorderColor);
|
|
float borderNWidth = shaderData.BorderWidth * nFactor;
|
|
#if defined(NOVA_CENTER_BORDER)
|
|
// Store half width to avoid doing it in frag
|
|
float halfBorderNWidth = .5 * borderNWidth;
|
|
SetBorderNWidth(o, halfBorderNWidth);
|
|
#else
|
|
SetBorderNWidth(o, borderNWidth);
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(NOVA_INNER_SHADOW)
|
|
NovaColorToV2F(ShadowColor, o, shaderData.ShadowColor);
|
|
float2 nShadowOffset = shaderData.ShadowOffset * nFactor;
|
|
float2 nShadowSpacePos = nPos - nShadowOffset;
|
|
SetNShadowSpacePos(o, nShadowSpacePos);
|
|
|
|
float shadowRadius = max(shaderData.ShadowBlur, shaderData.CornerRadius - shaderData.ShadowWidth);
|
|
float nShadowRadius = shadowRadius * nFactor;
|
|
SetNShadowRadius(o, nShadowRadius);
|
|
float nShadowBlur = shaderData.ShadowBlur * nFactor;
|
|
SetNShadowBlur(o, nShadowBlur);
|
|
|
|
float nShadowWidth = shaderData.ShadowWidth * nFactor;
|
|
float2 nShadowOrigin = ClampPositive(nHalfSize - nShadowWidth - nShadowRadius);
|
|
SetNShadowOrigin(o, nShadowOrigin);
|
|
#endif
|
|
|
|
#if defined(NOVA_LIT)
|
|
NovaSetLitV2FParams(o, transformAndLighting);
|
|
SetWorldPos(o, worldPos);
|
|
float3 rootNormal = NovaRootFromBlockNormal(transformAndLighting.RootFromBlock, v.Normal);
|
|
float3 worldNormal = UnityObjectToWorldNormal(rootNormal);
|
|
SetWorldNormal(o, worldNormal);
|
|
|
|
NovaInitInstance(appdata_full, appdata);
|
|
appdata.vertex = float4(rootSpace, 1);
|
|
appdata.normal = rootNormal;
|
|
|
|
NovaDoLitVert(o, worldPos, worldNormal, appdata);
|
|
#endif
|
|
|
|
return o;
|
|
}
|
|
|
|
fixed4 NovaFrag(v2f i) : SV_Target
|
|
{
|
|
NovaFragInit(i);
|
|
|
|
half2 gradientSpaceUV = GetGradientSpaceUV(i);
|
|
half gradientLerpVal = length(gradientSpaceUV);
|
|
fixed4 color = lerp(GetGradientColor(i), GetColor(i), min(1.0, gradientLerpVal));
|
|
|
|
#if defined(NOVA_DYNAMIC_IMAGE)
|
|
fixed4 texColor = tex2D(_NovaDynamicTexture, ToUnityUV(GetImageUV(i)));
|
|
color = ApplyColorTint(color, texColor);
|
|
|
|
#elif defined(NOVA_STATIC_IMAGE)
|
|
fixed4 texColor = UNITY_SAMPLE_TEX2DARRAY(_NovaTextureArray, float3(ToUnityUV(GetImageUV(i)), GetTextureBufferIndex(i)));
|
|
color = ApplyColorTint(color, texColor);
|
|
#endif
|
|
|
|
half2 clampedCornerSpace;
|
|
half distanceOutsideBounds = DistanceFromCircleEdge(GetNPos(i), GetNCornerOrigin(i), GetNCornerRadius(i), clampedCornerSpace);
|
|
half softenWidth = GetSoftenWidth(GetNPos(i));
|
|
half softenInverse = 1.0 / softenWidth;
|
|
half clipWeight = GetClipWeight10(distanceOutsideBounds, softenInverse);
|
|
|
|
#if defined(NOVA_INNER_SHADOW)
|
|
half distFromShadowOrigin = Q1Distance(GetNShadowOrigin(i), abs(GetNShadowSpacePos(i)));
|
|
half distFromShadowEdge = distFromShadowOrigin - GetNShadowRadius(i);
|
|
|
|
// This is mathematically equivalent to smoothstep(-GetShadowBlur(i), GetShadowBlur(i), distanceFromShadowEdge)
|
|
half t = saturate((.5 * distFromShadowEdge) / GetNShadowBlur(i) + .5);
|
|
half shadowWeight = t * t * (3.0 - 2.0 * t);
|
|
|
|
fixed4 shadowColor = GetShadowColor(i);
|
|
shadowColor *= shadowWeight;
|
|
color = BlendPremul(color, shadowColor);
|
|
#endif
|
|
|
|
#if NOVA_BORDER
|
|
// Need to correct the weight for when the border is very thin or has zero width
|
|
fixed4 borderColor = lerp(color, GetBorderColor(i), saturate(GetBorderNWidth(i) * softenInverse.x));
|
|
#endif
|
|
|
|
#if defined(NOVA_OUTER_BORDER)
|
|
// We want the border to have a sharp corner when the body does
|
|
half distanceOutsideBorder = GetNCornerRadius(i) < NOVA_EPSILON ? CMax2(clampedCornerSpace) : distanceOutsideBounds.x;
|
|
distanceOutsideBorder -= GetBorderNWidth(i);
|
|
|
|
// x => borderSoften
|
|
// y => body to border transition weight
|
|
half2 borderWeights = GetClipWeight10(half2(distanceOutsideBorder, distanceOutsideBounds.x), softenInverse.xx);
|
|
|
|
// Transition to border color
|
|
color = lerp(borderColor, color, borderWeights.y);
|
|
// Replace clipweight with outer edge clip
|
|
clipWeight = borderWeights.x;
|
|
|
|
#elif defined(NOVA_CENTER_BORDER)
|
|
// Apply the clip weight to the body, since the border may be transparent
|
|
color = ApplyClipWeight(color, clipWeight);
|
|
|
|
// We want the border to have a sharp corner when the body does
|
|
// x => inner edge
|
|
// y => outer edge
|
|
half2 dists = half2(distanceOutsideBounds.x, GetNCornerRadius(i) < NOVA_EPSILON ? CMax2(clampedCornerSpace) : distanceOutsideBounds.x);
|
|
// For center borders, we actually store half width
|
|
half2 distancesOutsideBorderEdges = dists + half2(GetBorderNWidth(i), -GetBorderNWidth(i));
|
|
half2 borderWeights = GetClipWeight01(distancesOutsideBorderEdges, softenInverse.x);
|
|
borderWeights.y = 1.0 - borderWeights.y;
|
|
|
|
borderColor *= borderWeights.x;
|
|
color = BlendPremul(color, borderColor);
|
|
clipWeight = borderWeights.y;
|
|
|
|
#elif defined(NOVA_INNER_BORDER)
|
|
half distanceOutsideInnerBorderEdge = distanceOutsideBounds.x + GetBorderNWidth(i);
|
|
half borderWeight = GetClipWeight01(distanceOutsideInnerBorderEdge, softenInverse.x);
|
|
fixed4 blended = BlendPremul(color, borderColor);
|
|
color = lerp(color, blended, borderWeight);
|
|
#endif
|
|
|
|
#if defined(NOVA_CLIP_RECT)
|
|
color = ApplyGlobalColorModification(color);
|
|
#elif defined(NOVA_CLIP_MASK)
|
|
color = ApplyClipMaskAndColorModifiers(color, GetRootPos(i));
|
|
#endif
|
|
|
|
#if defined(NOVA_LIT)
|
|
#if defined(NOVA_SHADOW_CAST_PASS)
|
|
// Don't assign back to color because the shadow caster pass just returns 0,
|
|
// but we want to clip
|
|
NovaDoLightingCalculations(i, color);
|
|
#else
|
|
color = NovaDoLightingCalculations(i, color);
|
|
#endif
|
|
#endif
|
|
|
|
clipWeight *= step(GetEdgeSoftenDisabled(i), clipWeight);
|
|
color = ApplyClipWeight(color, clipWeight);
|
|
|
|
#if defined(NOVA_RADIAL_FILL)
|
|
float radialFillClipWeight = GetRadialFillClipWeight(GetRadialFillSpacePos(i), GetRadialFillSinCos(i), softenWidth.x, softenInverse.x);
|
|
color = ApplyClipWeight(color, radialFillClipWeight);
|
|
#endif
|
|
|
|
#if defined(NOVA_CLIPPING)
|
|
color = ApplyVisualModiferClipping(color, GetRootPos(i));
|
|
#endif
|
|
|
|
#if defined(NOVA_LIT)
|
|
clip(color.a - NOVA_EPSILON);
|
|
#endif
|
|
|
|
return color;
|
|
}
|
|
|
|
#endif |