热力图(https://baike.baidu.com/item/%E7%83%AD%E5%8A%9B%E5%9B%BE)是元宇宙中很常见的一种变现形式,它用高亮的环形闭合曲线表现某个地区的【密度】分布情况,这个密度可以是海拔、温湿度、人流量等各种属性。在二维图形上,通过颜色区分密度,一目了然,和其他形式的图表或表格相比,热力图能表达的信息量最高,信息密度最高。
风格化(stylized)是艺术专业中的一个热门的流派(https://baike.baidu.com/item/%E9%A3%8E%E6%A0%BC%E5%8C%96),更贴切的说叫”简笔风“,与之相对应的叫”写实风“。顾名思义,写实风就是尽可能地画出真实的世界,简笔风则是偏向于一种”卡通“风格的绘画。这2种绘画流派有不同的应用场景,在计算机图形学中,简笔风的优势在3D即时渲染领域比较明显:节省渲染算力。我们人脑的视觉中枢在处理简单图形的时候,也会更省力更轻松,这就是人喜欢卡通风格的动画片的原因。所以像热力图这种计算量较高的效果,尽可能地使用简笔风热力图。简笔风要求使用【高对比度】的色块和线条,因此我们选择【RGB配色法】。
RGB配色法使用4种对比度最高的颜色:红黄绿蓝,色温由高到低。其中红色是暖色,蓝色是冷色,黄绿则介于其中,利用这4个颜色实现热力图,既提升了对比度,又兼顾了应景的色温(https://baike.baidu.com/item/%E8%89%B2%E6%B8%A9)。红黄绿蓝4色中按顺序,相邻的两种颜色之间突变接壤。每个像素的【颜色】由当前位置的【温度】决定,而它的温度则由周围的【热力点】共同决定:每个热力点的【热度】和【距离】共同决定了热力点对该像素施加的影响力。首先我们要解决的问题是:温度与颜色如何映射,或者说灰度图与彩图之间如何映射?我们需要设定一个【光谱】。
图中,横轴是温度(scalar),纵轴是颜色(vector4),这是通过UE5的ColorCurve颜色曲线资产实现的。之所以把蓝色设为渐变【淡出】,是因为绝大部分像素都是冷的,现实情况下,往往只有少部分地区会出现热力点,设为淡出,避免了蓝色占满整个场景。好了,我们解决了颜色和温度(非现实单位)的映射,接下来要定义热力点:由坐标(vector2)和热度(scalar)组成的vector3。为了方便实现,我们将整个坐标系约束到归一化的UV空间,因此像素和热力点的坐标都是在0到1之间。
在UE5中实现热力图材质的上层建筑如图所示,其中光谱节点就是上面提到的颜色曲线,其唯一的输入参数CurveTime就是温度值,其输出参数就是最终的像素颜色RGBA。该材质的类型是后期贴花,因为我们这个写实风热力图是简单的二维图形,想要实现三维效果,需要投影到地形上,这样才更生动形象。
那么现在最大的问题是,如何通过【热点数组】算出温度?在UE5的材质中没法直接使用数组,但可以通过纹理图片来存储数组(彩带)!可以在图片中的每个像素上存储一个热点,其RGB代表XYZ。如下图所示,动态地生成分辨率为N列1行的【调色带】图片,传参到材质中的Tex对象,然后通过TexCoord纹理坐标来拾取。以上介绍了许多自定义名词,现在要给他们严格的定义。
● 热点:能够对周围像素施加热力的固定点位,由用户提供,变量heat。
● 热点数组:由热点组成的vector3数组,变量Tex。
● 热点数量:热点数组的长度,变量count。
● 热点坐标:热点的位置,变量heat.xy。
● 热力:热点的固有属性,影响半径,变量heat.z。
● 热度:热点对势力范围内的像素点的影响力,等于heat.z减去两点的距离。
● 像素:热力图上的每一个点,变量UV。
● 温度:像素的固有属性,由所有热度共同决定,变量hot。
● 颜色:像素的固有属性,由温度映射而成。
● 调色盘:温度映射到颜色的映射表。
// 输入:热点数组
// 输出:像素温度
float hot = 0.0;
for (float x = 0.5/X; x < 1; x += 1/X) {
float3 heat = Texture2DSample(Tex, TexSampler, float2(x, 0));
float distance = length(TexCoord - heat.xy);
hot += pow(max(heat.z - distance, 0), 2);
}
return hot;