血条效果
Freya Holmér 的《Shaders for GameDev》
[toc]
# 血条渐变
首先我们需要一个参数 (范围限定在 [0-1]),当健康度为 0 时显示红色,健康度为 1 时显示绿色.
然后我们需要血条随着健康度变化 (渐变)
这时候就需要用到渐变贴图.
血条类渐变贴图模板
另外两种渐变图
径向渐变
随角度渐变
通过_Health 得到渐变位置
所有的都基于一个 quad 面片,这个面片的 uv 坐标上有 x 分量,且 x 是 clamp (0,1) 的。所以我们可以通过设置一个 [0,1] 的 float 变量_Health 来确定当前渲染的是血条的哪个位置
然后我们可以以 `健康度` 为 Time 通道,计算渐变后的 rgb 值
float3 healthColor = lerp(float3(1,0,0),float3(0,1,0),_Health);
渐变图
将上面的渐变和遮罩结合起来
float3 bgColor = = float3(0,0,0)
float3 outCol = bgColor* healthbarMask +healthColor*(1- healthbarMask );
渐变叠加
# 让血量小于 20% 恒显示红色,大于 75% 恒显示绿色
这个只需要计算一下即可
float InverseLerp( float a, float b, float v ) {
return (v-a)/(b-a);
}
float tHealthColor = saturate(InverseLerp(0.2,0.8,_Health));
# 使用渐变贴图代替计算的颜色
我们可以在计算时得到对应 uv.x 时的灰度值,根据灰度值在 ramp 图上进行采样,贴图 uv 坐标上的 y 轴是一定完整映射的,但是需要通过 x 轴获取在 uv 图上的 x 坐标
float healthbarMask = _Health > i.uv.x;
float3 healthbarColor = tex2D( _MainTex, float2( _Health, i.uv.y) );
采样时 y 轴完整采样
采样贴图
# 有向距离场制作血条圆角
SDF (Signed Distance Field) 距离场
通俗点来说,距离场就是某个点到固定点的距离,或者固定模型,这个距离长可以为负值,负值就代表在区域内
比如一个对于中心的有向距离场 (计算很简单,直接 length (uv-(0.5,0.5)) 后的颜色输出到图上即可
注意,SDF 不一定是圆形,他只是一种描述了距离的方式
那么,如下这张图的距离场是什么
血条边框
答案是中间的一条线
距离场中心 (中间那条线)
上图代表的是每个中线上的点到周围的距离场
那么我们如何通过 Health 这个变量得到距离场呢?
首先,我们可以通过_Health 得到 uv 的 x 坐标,然后基于这个 x 坐标可以计算 `到他最近的中间那条线上某点的距离`, 比如 x 刚好在线下面,那么距离就是 uv.y 减去中间那条线的 y 坐标
否则 (如果不在线段上下方), 就找到线段两个端点,计算距离
计算两层 SDF 即可在制作可以伸缩的边框
代码:
Shader “Unlit/Healthbar” {
Properties {
[ToggleOff] _toggle("_reverse",Int) = 0
[NoScaleOffset] _MainTex (“Texture”, 2D) = “white” {}
_Health (“Health”, Range(0,1)) = 1
_BorderSize (“Border Size”, Range(0,0.5)) = 0.1
}
SubShader {
Tags
Pass {
ZWrite Off
// src \* srcAlpha + dst \* (1-srcAlpha)
Blend SrcAlpha OneMinusSrcAlpha // Alpha blending
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi\_compile
#pragma shader\_feature \_TOGGLE\_OFF
#include "UnityCG.cginc"
bool \_reverse;
struct MeshData {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct Interpolators {
float2 uv : TEXCOORD0;
float4 vertex : SV\_POSITION;
};
sampler2D \_MainTex;
float \_Health;
float \_BorderSize;
Interpolators vert (MeshData v) {
Interpolators o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
float InverseLerp( float a, float b, float v ) {
return (v-a)/(b-a);
}
float4 frag (Interpolators i) : SV\_Target {
// rounded corner clipping
float2 coords = i.uv;
coords.x \*= 8;
float2 pointOnLineSeg = float2( clamp( coords.x, 0.5, 7.5 ), 0.5);
float sdf = distance(coords, pointOnLineSeg) \* 2 - 1;
clip(-sdf);
float borderSdf = sdf + \_BorderSize;
float pd = fwidth(borderSdf); // screen space partial derivative
float borderMask = 1-saturate(borderSdf / pd);
//return float4(borderMask.xxx,1);
float healthbarMask = \_Health > i.uv.x;
float3 healthbarColor = tex2D( \_MainTex, float2( \_Health, i.uv.y) );
float flash = \_Health-0.2<=0?cos( \_Time.y \* 4 ) \* 0.4 + 1:1;
healthbarColor \*= flash;
#if defined(\_TOGGLE\_OFF)
healthbarColor = float3(healthbarColor.x, healthbarColor.z, healthbarColor.y);
float3 col = (healthbarColor) \* healthbarMask \* borderMask;
return float4(col, 1);
#endif
return float4(healthbarColor \* healthbarMask \* borderMask, 1);
}
ENDCG
}
}
}
# 血条反转
可以自己制作 texture 贴图,也可以直接通过代码交换 g 和 b 通道的值