血条效果

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 通道的值