既然是使用Unity做可视化三维,那么基本的数据可视化效果还有要有的,之后几篇文章会讲解几种温度图效果的实现方案。
之前文章我们也讲了URP的好处,因为我们的所有效果都是在通用渲染管线下使用的。
效果视频:https://www.bilibili.com/video/BV1eW4y167bj?spm_id_from=333.337.search-card.all.click&vd_source=53e8eab748d74934d8848b24856aaffbhttps://www.bilibili.com/video/BV1eW4y167bj?spm_id_from=333.337.search-card.all.click&vd_source=53e8eab748d74934d8848b24856aaffb
基本逻辑讲解
效果是基于一个平面Mesh渲染的
每一个温度都保存了一个坐标位置和温度信息
在Shader中遍历所有坐标信息,计算当前渲染点的温度值
不同温度值形成不同的高度和颜色
创建Mesh
public static Mesh CreatePlane(Vector2 scaleSize, Vector2 meshSize)
{
float perxlength = scaleSize.x / (meshSize.x - 1);
float perzlength = scaleSize.y / (meshSize.y - 1);
int totalcount = (int)meshSize.x * (int)meshSize.y;
Vector3[] vertexs = new Vector3[totalcount];
Vector3[] normals = new Vector3[totalcount];
Vector2[] uvs = new Vector2[totalcount];
int[] triangles = new int[((int)meshSize.x - 1) * ((int)meshSize.y - 1) * 2 * 3];
int trianglesindex = 0;
for (int i = 0; i < meshSize.y; i++)
{
for (int j = 0; j < meshSize.x; j++)
{
float x = perxlength * j;
float z = perzlength * i;
int index = i * (int)meshSize.x + j;
vertexs[index] = new Vector3(x, 0, z);
normals[index] = Vector3.up;
uvs[index] = new Vector2(j / (meshSize.x - 1), i / (meshSize.y - 1));
if (j != meshSize.x - 1 && i != meshSize.y - 1)
{
triangles[trianglesindex] = j + i * (int)meshSize.x;
trianglesindex++;
triangles[trianglesindex] = j + 1 + (int)meshSize.x + i * (int)meshSize.x;
trianglesindex++;
triangles[trianglesindex] = j + 1 + i * (int)meshSize.x;
trianglesindex++;
triangles[trianglesindex] = j + i * (int)meshSize.x;
trianglesindex++;
triangles[trianglesindex] = j + (int)meshSize.x + i * (int)meshSize.x;
trianglesindex++;
triangles[trianglesindex] = j + 1 + (int)meshSize.x + i * (int)meshSize.x;
trianglesindex++;
}
}
}
Mesh mesh = new Mesh();
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
mesh.vertices = vertexs;
mesh.normals = normals;
mesh.triangles = triangles;
mesh.uv = uvs;
return mesh;
}
创建Mesh的代码比较基础,因为创建的是一个规则的平面,这个需要注意的是Mesh的顶点数,这里需要设置一个合适的数,过大会影响性能,过小温度图的效果会有锯齿(WebGL平台,Windows平台有曲面细分)
温度数据保存
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MY.TemperatureMap
{
public class TemperatureMap : MonoBehaviour
{
public TemperatureCoordinate _temperatureCoordinate;
public List<Vector4> tempratureDatas = new List<Vector4>();
public float maxRefrence = 0.5f;
public float perTempHeight = 0.001f;
public List<float> colorTempLine = new List<float> { 45, 20, 10, 0 };
public List<Color> _colors = new List<Color> { new Color(1, 0.11f, 0, 1), new Color(1, 0.91f, 0.13f, 1), new Color(0.37f, 1, 0.21f , 1), new Color(0.16f, 0.24f, 1 , 0) };
private Material _material;
public MaterialPropertyBlock block;
private MeshRenderer _meshrenderer;
private void Start()
{
_material = GetComponent<MeshRenderer>().material;
UpdateMaterialData(_material);
}
public void UpdateMaterialData(Material mat)
{
if (block == null)
block = new MaterialPropertyBlock();
SetMaterialBaseData(mat);
UpdateTempraturePointDatas(mat);
UpdateTempratureSplitLine(mat);
if (_meshrenderer == null)
_meshrenderer = GetComponent<MeshRenderer>();
_meshrenderer.SetPropertyBlock(block);
}
private void SetMaterialBaseData(Material _material)
{
block.SetFloat("perTempHeight" , perTempHeight);
block.SetFloat("maxRefrence" , maxRefrence);
}
private void UpdateTempraturePointDatas(Material _material)
{
block.SetInt("tempcount" , tempratureDatas.Count);
if(tempratureDatas.Count == 0) return;
if (_temperatureCoordinate == TemperatureCoordinate.Local)
{
List<Vector4> tempDatas = new List<Vector4>();
foreach (var UPPER in tempratureDatas)
{
Vector3 pos = transform.TransformPoint(UPPER);
tempDatas.Add(new Vector4(pos.x, pos.y, pos.z, UPPER.w));
}
block.SetVectorArray("tempData", tempDatas);
}
else
{
block.SetVectorArray("tempData", tempratureDatas);
}
}
private void UpdateTempratureSplitLine(Material _material)
{
block.SetInt("colorcount" , colorTempLine.Count);
if(colorTempLine.Count == 0) return;
block.SetFloatArray("colorLine" , colorTempLine);
List<Vector4> tempDatas = new List<Vector4>();
foreach (var UPPER in _colors)
{
tempDatas.Add(UPPER);
}
block.SetVectorArray("_Colors" , tempDatas);
}
}
[Serializable]
public enum TemperatureCoordinate
{
Local,
World
}
}
这是完整C#代码,可以看到温度数据保存在一个Vector4中,前三位是坐标数据,第四位是温度数据。
颜色层级是可以配置的colorTempLine 保存了温度的分界线,_colors 保存了对应层级的颜色。两个数据的长度必须是一样的,一个层级对应一个颜色,颜色支持透明度。
温度值计算
float GetHeight(float2 vertex)
{
float height = 0;
for(int i = 0;i<tempcount;i++)
{
float deltax = vertex.x - tempData[i].x;
float deltay = vertex.y - tempData[i].z;
float distance = sqrt(deltax * deltax + deltay * deltay);
if(distance > maxRefrence)
continue;
float centerheight = perTempHeight * tempData[i].w;
float releaseDis = 1- (cos(radians(180*(maxRefrence - distance) / maxRefrence)) + 1) / 2;
float nowHeight = centerheight * releaseDis ;
if(nowHeight != 0)
height = nowHeight / (nowHeight + height) * nowHeight + height;
}
return height;
}
这是shader部分的代码,GetHeight计算了当前点的高度
float4 Temp2Color(float temp , float4 color , float _Alpha )
{
for(int i = 0 ; i < colorcount ; i++)
{
if(temp > colorLine[i])
{
if(i == 0)
return float4(_Colors[i].rgb , _Colors[i].a * _Alpha)* color;
else
{
float4 tempcolor = lerp(_Colors[i] ,_Colors[i - 1] , (temp - colorLine[i]) / (colorLine[i - 1] - colorLine[i]));
tempcolor.a = _Alpha * tempcolor.a;
return tempcolor * color;
}
}
}
color.a = 0;
return color;
}
根据温度转换到对应层级的颜色,颜色需要有一定的融合,不然分界明显,效果就不好看了。
结尾
这篇文章做的是最基础的一种温度图效果,这种温度图也可以贴上一些贴图表现出更完美的效果(例如小颗粒点)。还有一些圆饼、柱状类的温度图之后的文章也会讲到。
整体的逻辑还是比较简单,如果看完文章还不会,点赞评论,我发你直接可以使用的插件。
|