各向异性
在此前的文章中谈及的材质都属于各向同性,但本文即将谈及的头发材质却属于各向异性
各向异性顾名思义,在不同的方向有着不同的特性(吸光度、折射率等等)
各向异性和各向同性主要的差异体现在视觉上,即和光反应后的物理视觉效果。比如一枚CD硬盘,放置在阳光,即使观察不变,但360°旋转它,导致观察到的视觉效果差距是很大的
那么导致各向异性的原因是什么呢?各向异性材质在微观上有一些方向性的细丝,这些细丝以宏观角度来看是不易察觉的。这里所指的细丝即下图中黑色箭头的射线

在光照模型中,可以把这些细丝看作直径很小且长度很长的圆柱,在宏观上来看,在上图这跟粗的圆柱的某一点的光照实际为它一圈的多个直径很小且长度很长的圆柱的光照的总和
头发构造
真实世界的毛发主要由纤维构造,也可分成多层结构,有中心的发髓(Medulla)、内部的皮质(Cortex)和表皮的角质层(Cuticle)构成。如下图所示:

- 角质层:包裹在发丝最外层的是一种组织,很薄,形如瓦片般层层堆叠,互相依附从而构成了发丝的外层几何形态
角质层放大后,可见坑坑洼洼的微表面,它是造成高光和反射的介质。此外,光线照射毛发表皮之后,还会发生透射和次反射:

毛发放大数千倍后的微表面存在坑洼,具有较统一的指向性,由根部指向尾部,在图形学可用切线及各向异性属性来衡量这一现象:

方案
Shell Based Fur
即多层渲染,以一根头发举例,不渲染这个头发,而是渲染这跟头发上以根部到顶部的多个面片,只要这些面片距离足够的近,就能产生单根头发的错觉

- 缺点:若头发越长,需要的横截面就越多,越影响性能
Card & Mesh
目前主要体现在写实和卡渲的风格差距上
人体发量大体在10w左右,使用strands渲染对性能影响巨大。因此划分面片或者Mesh 将是一种妥协式的解决方案

- Cards
优点:
- 渲染效率高:几何体数量远少于真实发丝,光栅化压力小
- 与光照模型无冲突: 配合 Kajiya-Kay 或 Marschner 模型的近似版本,可以轻易实现漂亮的各向异性高光
- 阴影处理简单:可以直接利用现有的 Shadow Map 管线,不需要特殊的Deep Shadow算法
- 物理模拟成本低
缺点:
- Overdraw严重:大量相互重叠的半透明片状物会导致严重浪费
- 透视失真:当相机视角与片面平行时,毛发会显得非常薄
- 难以处理复杂的动态交互
- Mesh
优点:
- 无死角的真实感
- 适配DXR
- 减少 Overdraw 压力:Visibility Buffer 剔除无用像素
- 物理精度高
缺点
- 带宽压力大:顶点数据大
- 抗锯齿挑战:细如像素的发丝会产生严重的亚像素走样
- 算法复杂度: 需要处理复杂的排序、深度合成、毛发求交加速算法
曲面细分
最早的Hairwork用于2013年的《使命召唤:幽灵》上

优点:
- 节省显存与 IO 带宽
无需将数十万根毛发的数据从 CPU 传输到 GPU
-
LOD:可以根据相机距离动态调整细分级别
缺点:
- 光栅化开销大
-
计算开销
-
自阴影与排序
毛发的真实感高度依赖深层阴影、不透明度映射。增加阴影图的负担,且半透明排序依然是性能杀手
为什么现在很少见了?曲面细分能做的事,Compute Shader也能做,且更加灵活
视察贴图

优点
- 厚度感:在完全平坦的几何面上,能呈现出发丝直立、堆叠的视觉错觉
- 几何开销极低:只需要一个简单的 Quad 或低模 Mesh
- 物理碰撞逻辑简单:处理环境碰撞的逻辑非常简单,只需修改高度图
- 遮挡效果: 能够实现发丝之间的相互遮挡,当视角倾斜时,远处发丝会被近处遮住
缺点
- Shader开销大:Ray Marching次数越多,效果越好,但计算量呈线性增长
- 自阴影很复杂:在视差空间内计算自阴影非常复杂且昂贵,通常需要再次进行射线步进查找光源方向
- 边缘平原效应:从侧面(接近 90°)观察时,模型边缘依然是平整的轮廓
头发模型
KajiyaKay
Kajiya Kay模型将头发抽象为一种不透明的单圆柱,在垂直于发丝的朝向(fiber direction)上观察高光走向,但不能产生透射和内部反射
KajiyaKay和Blinn-Phong高光一样需要用到normal,由于每根头发一圈360°都存在normal,这样会导致极大的计算量,但头发的副切线(下图中的T,沿着发丝方向)是唯一的,KajiyaKay以该副切线做文章,最终模拟出了各向异性高光

- 光照主要分为Diffuse和Specular
- Diffuse:Lambert
但和Lambert还是有区别。前面提到当计算发丝上一点的光照时,实际是计算发丝这一点周围一圈上所有直径很小但长度很长的细丝的光照总和,即该点切面半圆上的积分

- Specular:Phong
同样的,Phone所有所区别。光照击中头发之后,所求的依旧是切面的所有反射方向,但反射方向是沿着切线而不是法线以镜面反射角度射出的


- 但实际计算中,会用到另一个公式

与前一个公式的不同之处有:
- 考虑毛发的自阴影,防止diffuse项过亮
- 头发的高光项改写成了两层高光,其中一层高光是有颜色的,另外一层高光是没有颜色的,且两层高光的相互错开一点点
- Shift贴图
仅仅使用当前光照算法得到的结果是不够的,该算法得到的天使环会非常规则,并不会出现动漫中高光的些许偏移。为了得到高光的便宜效果,需要对副切线T添加offset,这就需要用到一张贴图,该贴图存储每根头发的高光offset值
计算shit bitTangent公式如下:
float3 KajiyaKayShiftTangent(float3 tangent, float3 normal, float shift)
{
float3 bitTangent = tangent + normal * shift;
return normalize(bitTangent);
}
- 缺点
- kajiya是经验模型,并非基于物理。多用于移动端
- 无法复现出发丝的许多其他光学特性
Marschner
Marschner等人在KajiyaKay的基础上,提出了更为全面和准确的物理与数学模型,后面相关的算法都是对Marschner的拟合与改进
该方法研究分析了真实世界的毛发构成及特性,抽象为如下图所示的光照模型:

对应的横截面光照模型图:

该模型将光照在毛发的作用分成3部位:
- 反射(R):光线到达角质层直接被反射,产生主高光,受毛发切线和各向异性影响

-
传输-传输(TT):光线透射角质层进入皮层,角质层内层折射,又透射到空气中。产生透射光

-
传输-反射-传输(TRT):光线透过真皮层,在真皮层内部被角质层折射的部分,产生次高光

理论上光线应该可以在内部继续弹射,但因为能量吸收,导致这些效果是相当微弱的,所以得到的公式如下:

对头发着色时,每个像素P都有R,TT,TRT 三项,可以把每项分解成 M、N、A,其中 M 项是轴向散射函数(Longitudinal scattering),N 项是垂面(方位角)散射函数(Azimuthal scattering),A项是吸收和反射

那么是什么轴向散射函数,什么又是方位角散射函数呢?
- 轴向散射函数M
- 定义
处理光线在圆柱侧视图(上图左边)上的反射和折射,主要控制高光形状、宽度、位置
- 作用
-
定义高光宽度:M 函数中的标准差参数(β)决定了高光看起来是“干涩、发散”的还是“油亮、锐利”的
- 模拟角质层偏移:M 函数通过引入一个偏移量 α,使得反射高光(R)向发根偏移,而透射高光(TRT)向发尖偏移
- 能量守恒
-
方位角散射函数N
- 定义
处理光线在圆形横截面(上图右边)上的反射和折射,主要控制反射、透射、散射颜色
- 作用
-
产生“环状高光”的明暗变化:发丝并不是从所有角度在所有侧面看起来都一样亮的。方位角函数计算了视角与光源在圆柱横截面上的夹角 ϕ
-
模拟焦散与彩色高光:由于圆柱体的几何特性,光线在执行 TRT 路径时,会在特定的方位角产生能量聚焦
这形成了一道比主高光位置稍偏、带有头发颜色且非常明亮的区域
-
控制背光透射:TT 分量控制了光线如何穿过发丝。这决定了在逆光下,头发边缘是否会产生那种半透明的、发光的“轮廓光”效果
但事实上,Marschner模型是非常非常复杂的(这里不会提及Marschner模型具体的样子,有兴趣的可以看看这位大佬讲的),根本无法用实时的要求来实现Marschner。后续提出的方案都是基于Marschner来做近似,从而优化性能
在谈论Marschner的优化方案前,还需要先对毛发与光交互的物理过程有一个比较清晰的数学模型,定义其中的重要符号与变量。如下图所示,右边是发丝的模型,中间是发丝的横截面

其中:
u(轴向矢量):沿着发根朝向发梢的方向。表示横截面所处位置的切线方向
v-w面(法平面):v、w、u构成右手坐标系,v轴方向为发丝横截面的长轴方向,w轴方向为发丝横截面的短轴方向(发丝横截面为椭圆)
ω_i:入射光方向
θ_i(入射角):入射光和法平面的夹角
\phi_i(入射方位角):法平面v轴转向入射平面和法平面交线为止
ω_r(散射光方向):需要计算得到的目标向量。可用于表示R、TT、TRT中任意一种出射光向量
θ_r(散射角):从散射光和法平面交线开始,转到ω_r
\phi_r(散射方位角):从法平面v轴转到散射平面为止
UE对Marschner的拟合
Diffuse
Diffuse采用Kajiya Kay漫反射结合多重散射近似方案。但需注意,该方案是非物理的
多重散射:发丝与发丝之间的多次弹射最终落入像机的光能。头发并不是一根根孤立的个体,而是一簇簇的发丝聚合,多重散射的本质就是以发丝聚合体为单位所发出的一种漫反射(diffuse),理论上它没有特别的传播方向,理想状况下就如同普通散射一般,多重散射会与发丝簇的宏观法线以及入射光线方向夹角的余弦相关,此外还可以纳入阴影强度 (shadow),并考虑上头发的基础颜色。这里多重散射采用的是双向散射的近似,在节省大量时间的情况下,和非近似的效果相差无几
公式如下:

其中:
n:在UE中定义为fake normal,可用公式n = normalize(v - N * dot(N, v))得到,N为发丝的宏观法线
Luma:从alebdo中提取的lum值,可用公式lum = dot(albedo, (0.3, 0.59, 0.11))得到
但UE实际实现中,对\frac{dot(N, L) + 1}{4 \pi}做了另一种近似,额外考虑物体的metallic
总的来说,上述公式分为三部分:
- 颜色项(\frac{albedo}{Luma(albedo)})^{1 - shadow}:
\frac{albedo}{Luma(albedo)}:去掉亮度,只考虑色度
1 - shadow:若越往shadow深处走,1 - shadow越大,防止shadow处死黑;而亮部,1 - shadow越小且接近0,导致整体偏向1(偏白)。但\frac{albedo}{Luma(albedo)}有可能大于1,色度越高的,最终色度会更高;若小于1(色度越低的),最终色度会越低。总的来说shadow处的,色度大于1的,越靠近shadow,色度越高,色度小于1的,越靠近shadow,色度越低(拉高饱和度)
- 补偿项\sqrt{alebdo}:因为光线在头发间会多次散射——相当于(albedo)^n,能量会不断降低,尤其是暗的深色的头发,变黑会非常快,所以这里对颜色做了补偿
-
散射项(\frac{N · L + 1}{4Π}):
$N · L + 1$:因为光线会透射,所以,头发的光照分布不会像默认光照一样,衰减的特别快,所以这里是$N · L + 1$
$\frac{1}{4Π}$:能量守恒
Specular
R项
M项
不论是R、TT、TRT,其M项都类似的,使用高斯分布近似
但UE中,对于反射分量M_R,采用效果更好但是更加昂贵的Weta模型,而没用高斯分布,原因是该模型是能量守恒的,对于基于物理的环境光,能给出相比高斯函数更加真实的辐射亮度估计。公式如下:

其中:
β:为与roughness相关的参数
θ_i:入射角,定义了入射光与法平面之间的夹角
$I_0 (\frac{cos_{θi}cos{θ_r}}{v}) $:第一类修正贝塞尔函数
更节省的是高斯曲线拟合:

β:与roughness相关的参数,为roughness^2
N项
模拟光线随方位角变化而衰减的物理现象


其中:
- \phi:\phi_r - \phi_i,入射和出射光线在法平面上投影线段的夹角
A项
使用Schlick近似


其中:
- η:材质的折射率
- x:在R项中,x = dot(V, H)
TT项
M项
采用高斯函数近似

其中:
β:与roughness相关的参数。TT项的β为0.5*roughness^2
α:轴向的偏移角。可暴露给美术调节
N项
一旦折射光进入发丝内部后,光路解算起来就会变得相对复杂许多,主要复杂在衰减项A、方位角分布函数Dp

其中:
h:入射光所在直线到圆心的距离
u:光线波长
衰减项A:可分解为两部分,一部分是折射定律f,通过折射率η和入射角计算光线的折射和反射占比,由Fresnel定义;另一部分是吸收项T,用于估算光的能量在发丝内部传播被吸收的程度

θ_d = cos(abs(θi - θr) / 2)
方位角分布函数DP:给定h,估算目标光路对方位角\phi_r的贡献度,随后对h积分将所有光路求和
可以认定DP为一个均值为0的高斯分布,当入参为0时, \phi - Φ(p, h) = 0时取到极大值
被减数Φ意为待测方位角的变化量(\phi_r - \phi_i),它通过p确定内部反射次数且用h确定原始入射角度,以得到当前光路的实际出射方向
吸收项T
该项为N项的一部分,用于估算光的能量在发丝内部传播被吸收的程度,主要为头发提供染色(当光线与电介质反应,少部分被直接反射,大部分会折射进内部,其中一部分的光子容易被散布在物体内部的金属离子或其他组织(色素)吸收从而完成染色,最后在若干次反射后再次折射出物体表面,形成散射光被人眼或摄像机捕捉到)
由此可见,T项一定会带有光波长μ参数以便对头发颜色控制,此外T项还需要考虑在物质内传播的路径长度,这和入射角相关,也和反射次数有关
定义如下:

UE对该公式做了优化,它们从Pixar提出的简化版起手:

其中:
- γ_t:折射角
- ζ(C):输入颜色的整体波长
UE将γ_t进一步拆解,并将BaseColor代替ζ(C)(公式中的h会在下方的A项中提到)


方位角分布函数DP
UE通过产生各种高斯函数的逼近提出了自己的分布公式,TT项的DP函数如下:

TT输出曲线如下:

可以看出,函数大约在\phi = π时取到最大值,也就是当入射方位角\phi_i与出射方位角\phi_r相差π时
A项
UE使用的标准模型,f项使用Schlick近似:

这里为了求取f项,先定义以下公式:

这里的η'并不是材质的折射率,而是法平面投影上的折射率
第一项α是给出的一个定义,后续会用
第二个式子η'是由UE4提出的改良后的折射率公式,与材质折射率η、θ_d(V和H)有关
第三个式子为近似
θ_d = \frac{θ_r - θ_i}{2}
UE4依据Weta模型提出了用于计算h_{tt}(f项)的表达式:

再使用二倍角公式得到h_{TT}^2:

但这样的表达式的计算代价是较大的,UE又对其拟合,得到如下近似公式:

TT项的标准模型中,p = 1,因此F = (1 - f)^2,而f的入参为cos_{θ} * sqrt(saturate(1 - Htt * Htt))
TRT项
M项
和TT项一模一样,但TRT项的β为2*roughness^2
N项
T项
TRT项的T项TRT,UE4舍去幂指数分子的部分,改用常数项模拟

DP项
TRT项的DP项依旧采用高斯函数近似,公式如下:

输出图像如下:

可以看出,函数大约在\phi = 0时取到最大值,也就是当入射方位角\phi_i与出射方位角\phi_r相差为0时
A项
TRT项的A项,p = 2,可得F = (1 - f)^2f,但f项的入参除cos_θ外UE仅仅给了个常数0.5f——即cosθ * 0.5f
实现
这里实现两套方案,一套低消耗,一套高消耗
Marschner近似
Diffuse
Kajiya Kay漫反射结合多重散射近似方案,并纳入shadow、alebdo,在节省大量时间的情况下,和非近似的效果相差无几
float3 KajiyaKayDiffuse(float3 albedo, float metallic, float3 lightDir, float3 viewDir, float3 normal, float shadow)
{
float3 fakeNormal = normalize(viewDir - normal * dot(viewDir, normal));
normal = fakeNormal;
float warp = 1;
float NoL = saturate(dot(normal, lightDir) + warp) / pow2(1.f + warp);
float KajiyaDiffuse = 1.f - abs(dot(normal, lightDir));
// 漫反射项的另一种近似,考虑了表面金属度
float diffuseScatter = lerp(NoL, KajiyaDiffuse, 0.33f) * metallic * INV_PI;
float3 luma = Luma(albedo);
float3 albedoOverLuma = abs(albedo / max(luma, 0.0001f));
float3 scatterTint = shadow < 1.f ? pow(albedoOverLuma, 1.f - shadow) : 1.f;
return sqrt(abs(albedo)) * diffuseScatter * scatterTint;
}
metallic = 1时,效果如下:

Specular
R
- M项
M项可以分为两种,一种是用高斯近似,另一种是用效果更好但是更加昂贵的Weta模型
Weta模型:
// 第一类修正贝塞尔函数 I0(x) 的实现 float ModifiedBesselI0(float x) { // 处理特殊情况 if (x == 0.f) { return 1.f; } float sum = 0.f; float term = 1.f; // 初始项 (x/2)^0 / 0!^2 = 1 int k = 0; [unroll(64)] for(; term > FLT_EPS;) { sum += term; k++; term *= (x * x) / (4.0 * k * k); } return sum; } float M = 0.f; float v = pow2(clampedRoughness); M += rcp(v * exp(2.f / v)); M *= exp((1.f - sinThetaL * sinThetaV) / v); M *= ModifiedBesselI0(cosThetaL * cosThetaV / v);这里存在一个问题,当roughness过小,小于0.14的样子,M结果是INF。因为此时v = 0.0196,exp(2.f / v) = 1.9 * 10^{44},这一值已经超过float的上限3.402 \times 10^{38},所以需要用到高斯近似,来避免这个问题
高斯近似:
float Hair_g(float roughness, float Theta) { return exp(-0.5f * pow2(Theta) / pow2(roughness)) / (sqrt(TWO_PI) * roughness); } // Alpha[0] = -0.07 M = Hair_g(pow2(clampedRoughness), sinThetaL + sinThetaV - Alpha[0]);因为M项表示高光形状大小,且和L、V有关,所以需要求和L与T的角度差、V与T的角度差,但求角度差需要asin(),开销较高,不如直接sin
-
N项
float N = 0.25f * cosHalfPhi; - A项
float Hair_F(float CosTheta) { const float n = 1.55; const float F0 = pow2((1 - n) / (1 + n)); return F0 + (1 - F0) * pow5(1 - CosTheta); } float A = Hair_F(sqrt(saturate(0.5f + 0.5f * VoL))) * lerp(1, 0.5f, saturate(-VoL)); - 合并效果
TT
-
M项
// Alpha[1] = 0.035 M = Hair_g(0.5f * pow2(clampedRoughness), sinThetaL + sinThetaV - Alpha[1]); - N项
- 吸收项T
float a = rcp(n_prime); float hTT = cosHalfPhi * (1.f + a * (0.6f - 0.8f * cosPhi)); T = pow(brdf_data.albedo, 0.5f * sqrt(1.f - pow2(hTT * a)) * rcp(cosThetaD));- 分布函数DP
DP = exp(-3.65f * cosPhi - 3.98f); - A项
float fTT = Hair_F(cosThetaD * sqrt(saturate(1.f - pow2(hTT)))); A = pow2(1.f - fTT); - 合并效果

TRT
-
M项
// Alpha[2]:0.12 M = Hair_g(2.f * pow2(clampedRoughness), sinThetaL + sinThetaV - Alpha[2]); - N项
- T项
T = pow(brdf_data.albedo, 0.8f * rcp(cosThetaD));- DP项
DP = exp(17.f * cosPhi - 16.78f); - A项
float fTRT = Hair_F(cosThetaD * 0.5f); A = pow2(1.f - fTRT) * fTRT; - 合并效果

-
添加GI和参数后

KajiyaKay
Diffuse
float3 KajiyaKayShiftTangent(float3 tangent, float3 normal, float shift)
{
float3 bitTangent = tangent + normal * shift;
return normalize(bitTangent);
}
Specular
float3 KajiyaKaySpecular(float width, float intensity, float3 shiftTangent, float3 lightDir, float3 viewDir)
{
float3 o;
float3 H = normalize(lightDir + viewDir);
float ToH = dot(shiftTangent, H);
float sinToH = sqrt(1.f - pow2(ToH));
o = smoothstep(-1, 0, ToH);
o *= pow(sinToH, width * 10.f);
o *= intensity;
return o;
}
两个高光,两个shiftTangent
float3 shiftTangent1 = lerp(lightData.bitTangentWS + _KajiyaKayFirstOffset, KajiyaKayShiftTangent(lightData.tangentWS, lightData.normalWS, brdfData.anisotropy + _KajiyaKayFirstOffset), _AnisotropyIntensity);
float3 shiftTangent2 = lerp(lightData.bitTangentWS + _KajiyaKaySecondOffset, KajiyaKayShiftTangent(lightData.tangentWS, lightData.normalWS, brdfData.anisotropy + _KajiyaKaySecondOffset), _AnisotropyIntensity);
float3 specular = KajiyaKaySpecular(_KajiyaKayFirstWidth, _KajiyaKayFirstIntensity, shiftTangent1, light.direction, lightData.viewDirWS) * _KajiyaKayFirstSpecularColor;
specular += KajiyaKaySpecular(_KajiyaKaySecondWidth, _KajiyaKaySecondIntensity, shiftTangent2, light.direction, lightData.viewDirWS) * _KajiyaKaySecondSpecularColor;
specular *= smoothstep(-1, 1, brdfData.NoL);
如何渲染?
问题来了,头发这种半透明用alpha blend合适吗?可以但有缺点:
- 开销大
- 因为头发是多个面片穿插在一起的,在半透明下会导致渲染顺序错误
因此需要一个方案,既满足开销低、不关心渲染顺序、半透明模样——即AlphaTest、DitherOpacityMask、TAA
最终效果
KajiyaKay效果如下:

Marschner近似效果如下:


优缺点
优点:
- 性能友好:大量的预计算和指数函数拟合
- 对美术友好
缺点
- 没有真正的空间多重散射: Diffuse无法模拟光线在体积内部的散射晕染
-
依赖插片与 TAA:为了掩盖 Cards 之间的穿插和单薄感,需要用到Alpha-to-Coverage 或 Dithered Alpha + TAA,角色高斯移动有鬼影
- 不能用3x3Dilation :因为clip的原因,若当前帧没渲染毛发,上一帧渲染了,会将背景与毛发混合,导致噪点(无法收敛到稳定的值)或闪烁(上一帧渲染了且是高光头发,这一帧没渲染)
-
解决方案:Coverage Mask。额外开一张RT,若未clip则写入1,在头发TAA开始前对这张RT模糊,这样会得到三种情况:
- 不在头发边缘的pixel:Coverage Mask = 0。历史帧权重0.95
- 头发边缘的pixel:Coverage Mask = [0.1, 0.9]。这些导致鬼影的源头,降低他们的历史帧权重(常规思路会想到缩小clip box,但这里不行,dither的存在颜色本来就在跳动有噪点,要是每帧都缩放clip box,噪点会更加明显)
- 实体毛发内部的pixel:Coverage Mask = 1。不受影响,历史帧权重0.95
- 排序问题:虽然用到Mask,可以不额外处理半透明的排序问题,但也存在一个问题:头发是由很多张card叠加的,上一帧没渲染前层card(甚至前面几层都没渲染)、渲染后层card,这一帧渲染了前后层card,这里混合就是最前层与后层混合,是错误的物理混合,尤其是附近的其他pixel可能是正常的,这个是错的会导致效果不丝滑,有噪点
-
解决方案:双层 Z-Prepass。不让整片毛发都参与 Dither ,而是让头发core区域固定clip,裁剪掉头发边缘;而头发边缘 Dither
- 第一次Z-Prepass:深度写入Core。core以固定的cutoff clip,裁剪头发边缘,只写入core部分
- 第二次Z-Prepass:深度写入边缘。边缘部分dither clip,深度测试LESS_EQUAL,只写入深度值大于等于最前面的core
- GBuffer:深度测试EQUAL。只渲染通过深度测试的片元
虽然不能完美避免,但可以极大降低噪点
-
Fake Normal不适配DXR














Comments | NOTHING