Unity中实现UI的质感和圆角
质感思路有两种:
一种是玻璃质感的做法,抓取UI后面的图像做模糊(build是GrabPass,urp抓图像我有写过在往期文章),这个方式网络上有很多就不写了;
另外一种是使用CubeMap的方式去模拟质感,这种用贴图的方式会更省性能,我这里主要讲的是第二种,其中需要注意的点是给CubeMap采样的时候需要将顶点转换为世界坐标,不然会出现极坐标的情况(上图为极坐标,下图是正常的);
Unity UI质感和圆角
如果你用shaderGraph可能需要用自定义节点去写转换,黑盒似乎也会出现极坐标,具体的你可以自行测试;
关键代码:
vert:
output.worldPos = mul(unity_ObjectToWorld,input.vertex);
----------------------------------------------------------
Frag:
float3 viewDir = normalize(UnityWorldSpaceViewDir(input.worldPos.xyz));
float3 vrDirWS = reflect(-viewDir, input.worldNormal);
float3 var_Cubemap = texCUBElod(_Cubemap,float4(vrDirWS,6));
color.rgb += var_Cubemap;
全部代码如下:
Shader "Unlit/RoundedBoxUI"
{
Properties
{
[PerRendererData] _MainTex ("Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_Cubemap ("HDRTex", cube) = "white" {} // 输入HDR单图//YJJ
//_CubemapMip("_CubemapMip",Range(0,7)) = 6
_RotationY ("RotationY", Range(0, 360)) = 0
[HideInInspector] _StencilComp ("Stencil Comparison", Float) = 0
[HideInInspector] _Stencil ("Stencil ID", Float) = 0
[HideInInspector] _StencilOp ("Stencil Operation", Float) = 0
[HideInInspector] _StencilWriteMask ("Stencil Write Mask", Float) = 255
[HideInInspector] _StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
_BorderWidth ("Border Width", Float) = 0
[Enum(NoBorder,0,OnlyBorder,1,Both,2)] _BorderColorType ("Border Type", Int) = 0
[Toggle(IMAGE_SDF)] _UseImageAsSDF ("Use Image as SDF", Float) = 0
[Enum(Off,0,On,1)]_ZWrite ("ZWrite", Float) = 1.0
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
}
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
Cull Off
Lighting Off
ZWrite [_ZWrite]
ZTest [unity_GUIZTestMode]
Blend SrcAlpha OneMinusSrcAlpha, OneMinusDstAlpha One
ColorMask [_ColorMask]
Pass
{
Name "Default"
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#pragma multi_compile __ UNITY_UI_CLIP_RECT
#pragma multi_compile __ UNITY_UI_ALPHACLIP
#pragma multi_compile __ IMAGE_SDF
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#include "Box2DSignedDistance.cginc"
struct vertexInput
{
float4 vertex : POSITION;
float4 color : COLOR;
float4 texcoord : TEXCOORD0;
//--- Custom
float4 borderRadius : TEXCOORD1;
float3 normal : NORMAL;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct fragmentInput
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float4 texcoord : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
//--- Custom
float4 borderRadius : TEXCOORD2;
float3 worldNormal : TEXCOORD3;
float3 worldPos : TEXCOORD4;
UNITY_VERTEX_OUTPUT_STEREO
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
fixed4 _TextureSampleAdd;
float4 _ClipRect;
float _BorderWidth;
int _BorderColorType; samplerCUBE _Cubemap; float _RotationY; //float _CubemapMip;
float4 RotateAroundYInDegrees (float4 vertex, float degrees)
{
float alpha = degrees * 3.14 / 180.0;
float sina, cosa;
sincos(alpha, sina, cosa);
float2x2 m = float2x2(cosa, -sina, sina, cosa);
return float4(mul(m, vertex.xz), vertex.yw).xzyw;
}
float4 RotateAroundXInDegrees (float4 vertex, float degrees)
{
float alpha = degrees * 3.14 / 180.0;
float sina, cosa;
sincos(alpha, sina, cosa);
// 创建绕X轴的旋转矩阵
float2x2 m = float2x2(cosa, -sina, sina, cosa);
// 创建完整的旋转矩阵
float4x4 rotationMatrix = float4x4(
1.0, 0.0, 0.0, 0.0,
0.0, cosa, -sina, 0.0,
0.0, sina, cosa, 0.0,
0.0, 0.0, 0.0, 1.0
);
// 旋转顶点并返回
return mul(rotationMatrix, vertex);
}
float4 RotateAroundZInDegrees (float4 vertex, float degrees)
{
float alpha = degrees * 3.14 / 180.0;
float sina, cosa;
sincos(alpha, sina, cosa);
// 创建绕Z轴的旋转矩阵
float4x4 rotationMatrix = float4x4(
cosa, -sina, 0.0, 0.0,
sina, cosa, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
);
// 旋转顶点并返回
return mul(rotationMatrix, vertex);
}
fragmentInput vert(vertexInput input)
{
fragmentInput output;
UNITY_INITIALIZE_OUTPUT(fragmentInput, output);
UNITY_SETUP_INSTANCE_ID(input);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
output.worldPosition = input.vertex;
output.worldPos = mul(unity_ObjectToWorld,input.vertex);//YJJ//给CubeMap采样使用
output.vertex = UnityObjectToClipPos(output.worldPosition);
output.texcoord = input.texcoord;
output.color = input.color * _Color;
output.borderRadius = input.borderRadius;
output.worldNormal = UnityObjectToWorldNormal(input.normal);
return output;
}
fixed4 frag (fragmentInput input) : SV_Target
{
float2 rectSize = input.texcoord.zw;
float2 uv = input.texcoord.xy * rectSize;
uv = uv - rectSize * 0.5;
float dist = sdRoundBox(uv, rectSize * 0.5 - (_BorderWidth).xx, input.borderRadius);
float2 ddDist = float2(ddx(dist), ddy(dist));
float ddDistLen = length(ddDist);
float alpha = saturate(((dist - _BorderWidth) / ddDistLen) + 1.0);
float borderParam = saturate((dist) / ddDistLen);
half4 color = half4(0.0, 0.0, 0.0, 0.0);
#ifdef IMAGE_SDF
float4 texSample = tex2D(_MainTex, input.texcoord) + _TextureSampleAdd;
float c_dist = texSample.x - 0.1;
float c_mask = smoothstep(0.00, 0.2, c_dist);
color = input.color;
color.a *= 1.0 - alpha;
color.a *= saturate(c_mask);
#else
color = (tex2D(_MainTex, input.texcoord) + _TextureSampleAdd) * input.color;
color.a *= 1.0 - alpha;
#endif
//描边YJJ
//color.a = 1;//挤出描边
//描边End
if (_BorderColorType == 1) {
color.a *= borderParam;
}
//color.rgb *= 1.0 - borderParam;
//color.a *= c_dist;
#ifdef UNITY_UI_CLIP_RECT
color.a *= UnityGet2DClipping(input.worldPosition.xy, _ClipRect);
#endif
#ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001);
#endif
//魔改反射效果
float3 viewDir = normalize(UnityWorldSpaceViewDir(input.worldPos.xyz));
//float3 vDirWS=normalize(_WorldSpaceCameraPos.xyz - input.worldPosition.xyz);
float3 vrDirWS = reflect(-viewDir, input.worldNormal);
vrDirWS = RotateAroundZInDegrees(float4(vrDirWS,1),0).xyz;
float3 var_Cubemap = texCUBElod(_Cubemap,float4(vrDirWS,6));
color.rgb += var_Cubemap;
//return float4(var_Cubemap,1);
return color;
}
ENDCG
}
}
}
脚本:
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class RoundedBoxUIProperties : UIBehaviour, IMeshModifier
{
private Image _image;
public Vector4 borderRadius;
[Range(0,7)]public float CubemapMip = 6;
#if UNITY_EDITOR
protected override void OnValidate()
{
if (_image == null)
{
_image = gameObject.GetComponent<Image>();
if (_image == null) return;
}
_image.SetAllDirty();
}
#endif
protected override void OnEnable()
{
_image = gameObject.GetComponent<Image>();
}
protected override void OnDisable()
{
_image = null;
}
protected override void Start()
{
StartCoroutine(DelayVertexGeneration());
}
IEnumerator DelayVertexGeneration()
{
yield return new WaitForSeconds(0.1f);
if (_image == null)
{
_image = gameObject.GetComponent<Image>();
if (_image == null) yield break;
}
_image.SetAllDirty();
}
public void ModifyMesh(Mesh mesh)
{
}
public void ModifyMesh(VertexHelper verts)
{
if (_image == null)
{
_image = gameObject.GetComponent<Image>();
if (_image == null) return;
}
var rectTransform = (RectTransform)transform;
var rect = rectTransform.rect;
var offset = new Vector4(rect.x, rect.y, Mathf.Abs(rect.width), Mathf.Abs(rect.height));
UIVertex vert = new UIVertex();
for (int i = 0; i < verts.currentVertCount; i++)
{
verts.PopulateUIVertex(ref vert, i);
var uv0 = vert.uv0;
uv0.z = offset.z;
uv0.w = offset.w;
vert.uv0 = uv0;
vert.uv1 = borderRadius * 0.5f;
verts.SetUIVertex(vert, i);
}
}
// [ContextMenu("UI模糊调整")]
// public void UIMipBlur(){
// Shader.SetGlobalFloat("_CubemapMip",CubemapMip);
// float Test = Shader.GetGlobalFloat("_CubemapMip");
// Debug.Log("" + Test);
// }
}
Box2DSignedDistance.cginc
float sdRoundBox( in float2 p, in float2 b, in float4 r )
{
// We choose the radius based on the quadrant we're in
// We cap the radius based on the minimum of the box half width/height
r.xy = (p.x>0.0)?r.xy : r.zw;
r.x = (p.y>0.0)?r.x : r.y;
r.x = min(2.0f*r.x, min(b.x, b.y));
float2 q = abs(p)-b+r.x;
return min(max(q.x,q.y),0.0) + length(max(q,0.0)) - r.x;
}
其中shader部分写了一些矩阵用于调整角度的方法,可以自行删减冗余;