首页 > 其他 > 详细

简易3D热力图

时间:2020-09-27 11:38:40      阅读:61      评论:0      收藏:0      [点我收藏+]

  三维场景里面想要表现出热力图, 最简单的就是投影(Projection)或者叫贴花(Decals)了, 不过最近也有不少通过生成3D热力图的例子, 比如高德接口已经提供了3D热力图 : 

技术分享图片

  

  生成3D热力图的方式按照生产流程来看, 大概有那么几种 : 

  1. 获得的数据是散点数据, 需要我们自己生成一张高度图, 用高度图来生成 Mesh 网格, 然后自己绘制热力图的颜色

  2. 获得的数据直接就是一张热力图, 需要根据热力图来生成高度图, 生成网格后直接用热力图作为贴图即可

  其实都是以生成 Mesh 网格的方式来制作的, 还有纯渲染的比如类似天气, 雾气这样的方式也能做出3D热力图, 不过考虑到可能需要能够点击交互, 使用生成 Mesh 才是最通用的, 并且根据实际需求可以设定分辨率来减少性能损耗, 创建过程中的计算很多可以通过多线程来完成, 使用方便代码简单, 依赖了系统的矩阵转换, 比使用 Shader 来写纯渲染的好多了.

 

  按照散点数据的模式来制作高度图, 首先需要把散点位置转换为相对位置, 比如热力图的中心位于点 A 处, 热力图是一个矩形范围, 那么就可以放一个 GameObject 到该点, 利用 worldToLocalMatrix 把离散点坐标转换为本地坐标, 这样根据矩形空间就能映射到热力图相对位置了, 那么我们把离散点直接像素写入到高度图上去吗? 肯定不是, 因为热力图有一个扩展范围, 至少需要按照某个半径画圆画到高度图上, 这样就得到最初的高度图了, 可是在做成 Mesh 的时候希望它有边缘平滑的效果, 而不是直接 0-1 的剧烈变化, 上面高德地图的边缘平滑基于贝塞尔曲线来的, 我就简单一些直接做几次高斯模糊就行了, 这样就能得到一张边缘平滑的高度图了, 生成 Mesh 之后怎样绘制热力图颜色呢? 最简单的写个 shader 根据世界坐标 y 值进行插值几个颜色就行了.

  离散点绘制到高度图上核心是使用贴图的 _ST 属性, 通过计算修改贴图的这个属性就能实现调整图片位置以及大小的逻辑了, Shader 逻辑为 : 

Shader "Custom/HeightMapCreator"
{
    Properties
    {
        _MainTex("Texture", 2D) = "black"{}
        _Shape ("Shape", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float2 st : TEXCOORD1;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            sampler2D _Shape;
            float4 _Shape_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.st = TRANSFORM_TEX(v.uv, _Shape);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                col = (col + tex2D(_Shape, i.st));
                return col;
            }
            ENDCG
        }
    }
}

  MainTexture 就是我们需要进行迭代的贴图, Shape 就是我们绘制离散点的形状的图片, 通过 _Shape_ST 来调整 Shape 的大小和位置, 这样我们就可以把 Shape 按照坐标计算的位置和自己设定的大小绘制到高度图上了, 当然如果是有很多个离散点的话, 并且都使用同样的 Shape 的话, 这个可以改进为传入 Tiling/Offset 数组的方式实现采样, 要不然每个点都需要进行一次 Blit 操作非常浪费性能...

  基于上面的 Shader 看看坐标转换以及图片合成的过程 : 

    public class PointInfo
    {
        public Vector3 worldPos;
        public float value;

        public static implicit operator Vector3(PointInfo info)
        {
            return info.worldPos;
        }
    }
    public class HeightMapInfo
    {
        public Matrix4x4 worldToLocalMatrix;
        public int worldSize_x;      // total
        public int worldSize_z;      // total

        public int heightMapSize_x;
        public int heightMapSize_z;
        public Vector2 worldToMapScale { get { return new Vector2((float)heightMapSize_x / (float)worldSize_x, (float)heightMapSize_z / (float)worldSize_z); } }

        public Texture2D renderShape;
        public int renderSize_x;
        public int renderSize_y;
        public Vector2 renderShapeScale { get { return new Vector2((float)heightMapSize_x / (float)renderSize_x, (float)heightMapSize_z / (float)renderSize_y); } }
    }

    private static RenderTexture Blit(RenderTexture mainTexture, Material material, HeightMapInfo info, Vector3 worldPos)
    {
        var region = new Rect(-info.worldSize_x * 0.5f, -info.worldSize_z * 0.5f, info.worldSize_x, info.worldSize_z);
        var localPos = info.worldToLocalMatrix.MultiplyPoint3x4(worldPos);  // based on middle-center
        var localPos_xz = new Vector2(localPos.x, localPos.z);
        var renderTarget = RenderTexture.GetTemporary(info.heightMapSize_x, info.heightMapSize_z, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default);

        var offset = localPos_xz - region.min;
        var offset_u = (offset.x / region.width) * -1.0f;
        var offset_v = (offset.y / region.height) * -1.0f;

        var shapeRenderOffset_u = info.renderSize_x * 0.5f / info.heightMapSize_x;
        var shapeRenderOffset_v = info.renderSize_y * 0.5f / info.heightMapSize_z;

        var final_u = offset_u + shapeRenderOffset_u;
        var final_v = offset_v + shapeRenderOffset_v;
        var final_uv = new Vector2(final_u, final_v);

        // offset is scaled
        var final_uv_scaled = Vector2.Scale(final_uv, info.renderShapeScale);
        material.SetTextureOffset("_Shape", final_uv_scaled);

        Graphics.Blit(mainTexture, renderTarget, material);
        RenderTexture.ReleaseTemporary(mainTexture);

        return renderTarget;
    }

  这里按照归一化的过程, 把3D热力图所占的世界空间的大小归一化, 散点位置转换为热力图本地坐标系位置之后, 通过归一化计算就能得到 Shape 的 Offset 了

 

 

  

 

简易3D热力图

原文:https://www.cnblogs.com/tiancaiwrk/p/13737928.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!