质感思路有两种:
一种是玻璃质感的做法,抓取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 OffLighting OffZWrite [_ZWrite]ZTest [unity_GUIZTestMode]Blend SrcAlpha OneMinusSrcAlpha, OneMinusDstAlpha OneColorMask [_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;//--- Customfloat4 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;//--- Customfloat4 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_SDFfloat4 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);#elsecolor = (tex2D(_MainTex, input.texcoord) + _TextureSampleAdd) * input.color;color.a *= 1.0 - alpha;#endif//描边YJJ//color.a = 1;//挤出描边//描边Endif (_BorderColorType == 1) {color.a *= borderParam;}//color.rgb *= 1.0 - borderParam;//color.a *= c_dist;#ifdef UNITY_UI_CLIP_RECTcolor.a *= UnityGet2DClipping(input.worldPosition.xy, _ClipRect);#endif#ifdef UNITY_UI_ALPHACLIPclip (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_EDITORprotected override void OnValidate(){if (_image == null){_image = gameObject.GetComponent<Image>();if (_image == null) return;}_image.SetAllDirty();}
#endifprotected 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/heightr.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部分写了一些矩阵用于调整角度的方法,可以自行删减冗余;