1、结果
2、完整C++代码
#include <sstream>
#include <iomanip>
#include <iostream>
#include <vector>
#include <random>
#include <cmath>
#include <functional>
#include <osgViewer/viewer>
#include <osgDB/ReadFile>
#include <osg/Texture3D>
#include <osg/Texture1D>
#include <osgDB/FileUtils>
#include <osg/Billboard>
#include <osg/TexGenNode>
#include <osg/ClipNode>
#include <osgDB/WriteFile>
#include <osg/Point>
#include <osg/ShapeDrawable>
#include <osg/PositionAttitudeTransform>
#include <osg/MatrixTransform>
#include <osgGA/TrackballManipulator>
#include <osg/ComputeBoundsVisitor>
#include <osg/TransferFunction>
#include <array>
#include <osgViewer/ViewerEventHandlers>
#include <osgGA/StateSetManipulator>
#include <osgUtil/SmoothingVisitor>
const std::string DATA_PATH = R"(..\data\)";
const std::string SHADER_PATH = R"(..\shaders\)";
const float RADIUS = 35;//设置热力点的影响半径
struct Point { int x, y; float value; };
// 生成随机数
int getRandomInt(int min, int max) {
return min + std::rand() % (max - min + 1);
}
// 颜色插值(用于热力图渐变)
osg::Vec4 lerpColor(float value) {
value = osg::clampBetween(value, 0.0f, 1.0f); // 确保 value 在 [0, 1] 范围内
osg::Vec4 colors[] = {
osg::Vec4(49/255.0, 54/255.0, 149/255.0, value),
osg::Vec4(69/255.0, 117/255.0, 180/255.0, value),
osg::Vec4(116/255.0, 173/255.0, 209/255.0, value),
osg::Vec4(171/255.0, 217/255.0, 233/255.0, value),
osg::Vec4(224/255.0, 243/255.0, 248/255.0, value),
osg::Vec4(255/255.0, 255/255.0, 191/255.0, value),
osg::Vec4(254/255.0, 224/255.0, 144/255.0, value),
osg::Vec4(253/255.0, 174/255.0, 97/255.0, value),
osg::Vec4(244/255.0, 109/255.0, 67/255.0, value),
osg::Vec4(215/255.0, 48/255.0, 39/255.0, value),
osg::Vec4(165/255.0, 0.0, 38/255.0, value)
};
//osg::Vec4 colors[] = {
// osg::Vec4(50 / 255.0, 136 / 255.0, 189 / 255.0, value),
// osg::Vec4(102 / 255.0, 194 / 255.0, 165 / 255.0, value),
// osg::Vec4(171 / 255.0, 221 / 255.0, 164 / 255.0, value),
// osg::Vec4(230 / 255.0, 245 / 255.0, 152 / 255.0, value),
// osg::Vec4(254 / 255.0, 224 / 255.0, 139 / 255.0, value),
// osg::Vec4(253 / 255.0, 174 / 255.0, 97 / 255.0, value),
// osg::Vec4(244 / 255.0, 109 / 255.0, 67 / 255.0, value),
// osg::Vec4(213 / 255.0, 62 / 255.0, 79 / 255.0, value),
//};
int numColors = sizeof(colors) / sizeof(colors[0]);
float t = value * (numColors - 1); // 乘以 (数量-1)
int index = static_cast<int>(t);
// 处理边界情况,避免越界
if (index >= numColors - 1) {
return colors[numColors - 1]; // 直接返回最后一个颜色
}
t -= index;// 提取 t 的小数部分,作为插值比例
return colors[index] * (1 - t) + colors[index + 1] * t;
}
std::vector<Point> generateData(int width, int height, int pointCount)
{
std::vector<Point> points;
for (int i = 0; i < pointCount; ++i) {
points.push_back({ getRandomInt(10, width - 10), getRandomInt(10, height - 10), getRandomInt(0, 100) / 100.0f });
}
return points;
}
// 生成热力图图像
osg::ref_ptr<osg::Image> generateHeatmap(int width, int height, std::vector<Point> points) {
osg::ref_ptr<osg::Image> image = new osg::Image();
image->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE);
std::vector<unsigned char> buffer(width * height * 4, 0); // 初始化RGBA缓冲区
// 绘制点 (模拟圆形模糊)
for (const auto& p : points) {
for (int dx = -RADIUS; dx <= RADIUS; ++dx) {
for (int dy = -RADIUS; dy <= RADIUS; ++dy) {
int px = p.x + dx;//计算当前像素的x坐标
int py = p.y + dy;//计算当前像素的y坐标
//确保当前像素点在图像范围内
if (px >= 0 && px < width && py >= 0 && py < height) {
//计算该像素点与中心点的归一化距离
float dist = std::sqrt(dx * dx + dy * dy) / RADIUS;
//如果距离在热力点影响范围内
if (dist <= 1.0f) {
//计算当前像素在缓冲区中的索引
int index = (py * width + px) * 4;
//计算当前像素的影响强度
float intensity = (1.0f - dist) * p.value;
//叠加透明度
float oldAlpha = buffer[index + 3] / 255.0f; // 读取背景透明度(归一化到0~1)
float newAlpha = intensity + oldAlpha * (1.0f - intensity); // 计算混合透明度
buffer[index + 3] = static_cast<int>(std::min(255.0f, newAlpha * 255)); // 更新透明度
}
}
}
}
}
// 颜色映射
for (int i = 0; i < width * height; ++i) {
float alpha = buffer[i * 4 + 3] / 255.0f; // 归一化透明度
osg::Vec4 color = lerpColor(alpha);
buffer[i * 4] = static_cast<unsigned char>(color.r() * 255);
buffer[i * 4 + 1] = static_cast<unsigned char>(color.g() * 255);
buffer[i * 4 + 2] = static_cast<unsigned char>(color.b() * 255);
buffer[i * 4 + 3] = static_cast<unsigned char>(color.a() * 255);
}
// 复制数据到 osg::Image
memcpy(image->data(), buffer.data(), buffer.size());
return image;
}
// 生成高度图
osg::ref_ptr<osg::Image> generateHeightmap(int width, int height, std::vector<Point> points)
{
osg::ref_ptr<osg::Image> image = new osg::Image;
image->allocateImage(width, height, 1, GL_LUMINANCE, GL_UNSIGNED_BYTE);
std::vector<float> heightBuffer(width * height, 0.0);//浮点型高度缓存
// 计算高度影响(使用高斯衰减)
for (const auto& p : points)
{
for (int dx = -RADIUS; dx <= RADIUS; ++dx) {
for (int dy = -RADIUS; dy <= RADIUS; ++dy) {
int px = p.x + dx;
int py = p.y + dy;
if (px >= 0 && px < width && py >= 0 && py < height)
{
float distance = std::sqrt(dx * dx + dy * dy);
if (distance <= RADIUS) {
float normalizedDist = distance / RADIUS;
//高斯衰减系数
float falloff = std::exp(-normalizedDist * normalizedDist * 4.0f);
int index = py * width + px;
heightBuffer[index] += falloff * p.value;
}
}
}
}
}
// 归一化处理
float maxHeight = *std::max_element(heightBuffer.begin(), heightBuffer.end());
std::vector<unsigned char> grayBuffer(width * height);
for (int i = 0; i < width * height; ++i)
{
float normalized = heightBuffer[i] / maxHeight;
grayBuffer[i] = static_cast<unsigned char>(normalized * 255);
}
memcpy(image->data(), grayBuffer.data(), grayBuffer.size());
return image;
}
void generateHeatmapTexture()
{
std::vector<Point> data = generateData(1024, 1024, 100);
osg::ref_ptr<osg::Image> heatmap2dImage = generateHeatmap(1024, 1024, data);
osg::ref_ptr<osg::Image> heightmapImage = generateHeightmap(1024, 1024, data);
// 使用OSG保存图像
if (osgDB::writeImageFile(*heatmap2dImage, "heatmap2d.png")) {
std::cout << "Image saved as " << "heatmap_osg.png" << std::endl;
}
else {
std::cerr << "Error saving image." << std::endl;
}
if (osgDB::writeImageFile(*heightmapImage, "heightmap.png")) {
std::cout << "Image saved as " << "heightmap.png" << std::endl;
}
else {
std::cerr << "Error saving image." << std::endl;
}
}
// 创建地形节点
osg::ref_ptr<osg::Node> createTerrain(osg::Image* heightmap, osg::Image* heatmap) {
// 创建几何体
osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry();
osg::ref_ptr<osg::Geode> geode = new osg::Geode();
geode->addDrawable(geometry);
// 创建顶点数组
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array();
const int width = heightmap->s();
const int height = heightmap->t();
const float terrainSize = 200.0f; // 地形物理尺寸
const float heightScale = 50.0f; // 高度缩放系数
// 生成顶点数据
unsigned char* heightData = heightmap->data();
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
// 计算顶点位置(居中)
float xPos = (x - width / 2.0f) * (terrainSize / width);
float yPos = (y - height / 2.0f) * (terrainSize / height);
float zPos = heightData[y * width + x] / 255.0f * heightScale;
vertices->push_back(osg::Vec3(xPos, yPos, zPos));
}
}
// 创建索引数组(三角形带)
osg::ref_ptr<osg::DrawElementsUShort> indices =
new osg::DrawElementsUShort(GL_TRIANGLES);
for (int y = 0; y < height - 1; ++y) {
for (int x = 0; x < width - 1; ++x) {
// 创建两个三角形组成四边形
int i0 = y * width + x;
int i1 = i0 + 1;
int i2 = i0 + width;
int i3 = i2 + 1;
indices->push_back(i1);
indices->push_back(i2);
indices->push_back(i0);
indices->push_back(i3);
indices->push_back(i2);
indices->push_back(i1);
}
}
// 设置几何体数据
geometry->setVertexArray(vertices);
geometry->addPrimitiveSet(indices);
// 自动生成法线
osgUtil::SmoothingVisitor::smooth(*geometry);
// 创建纹理坐标数组
osg::ref_ptr<osg::Vec2Array> texCoords = new osg::Vec2Array();
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
// 归一化纹理坐标 [0,1]
float u = static_cast<float>(x) / (width - 1);
float v = static_cast<float>(y) / (height - 1);
texCoords->push_back(osg::Vec2(u, v));
}
}
geometry->setTexCoordArray(0, texCoords);
// 应用纹理
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D();
texture->setImage(heatmap);
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
osg::StateSet* stateset = geode->getOrCreateStateSet();
stateset->setTextureAttributeAndModes(0, texture);
stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
return geode;
}
osg::Geode* createHeatmap3D() {
const int GRID_SIZE = 256;
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
osg::ref_ptr<osg::Vec2Array> texCoords = new osg::Vec2Array;
for (int y = 0; y < GRID_SIZE; ++y) {
for (int x = 0; x < GRID_SIZE; ++x) {
float u = x / (GRID_SIZE - 1.0f);
float v = y / (GRID_SIZE - 1.0f);
vertices->push_back(osg::Vec3(u * 100 - 50, v * 100 - 50, 0)); // 居中显示
texCoords->push_back(osg::Vec2(u, v));
}
}
// 创建索引
osg::ref_ptr<osg::DrawElementsUInt> indices =
new osg::DrawElementsUInt(GL_TRIANGLES);
for (int y = 0; y < GRID_SIZE - 1; ++y) {
for (int x = 0; x < GRID_SIZE - 1; ++x) {
int i0 = y * GRID_SIZE + x;
int i1 = i0 + 1;
int i2 = i0 + GRID_SIZE;
int i3 = i2 + 1;
indices->push_back(i1);
indices->push_back(i2);
indices->push_back(i0);
indices->push_back(i3);
indices->push_back(i2);
indices->push_back(i1);
}
}
geom->setVertexArray(vertices);
geom->setTexCoordArray(0, texCoords);
geom->addPrimitiveSet(indices);
osgUtil::SmoothingVisitor::smooth(*geom);
osg::Geode* geode = new osg::Geode;
geode->addDrawable(geom);
return geode;
}
osg::Texture2D* createTexture(osg::Image* image) {
osg::Texture2D* tex = new osg::Texture2D;
tex->setImage(image);
tex->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
tex->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
return tex;
}
void setupStateSet(osg::Geode* geode) {
osg::StateSet* ss = geode->getOrCreateStateSet();
// 混合设置
ss->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
ss->setMode(GL_BLEND, osg::StateAttribute::ON);
std::vector<Point> data = generateData(256, 256, 100);
osg::ref_ptr<osg::Image> heatmap2dImage1 = generateHeatmap(256, 256, data);
osg::ref_ptr<osg::Image> heightmapImage1 = generateHeightmap(256, 256, data);
struct Textures {
osg::ref_ptr<osg::Texture2D> current;
osg::ref_ptr<osg::Texture2D> next;
float transition = 0.0f;
bool updating = false;
};
Textures heightTex, heatmapTex;
// 初始纹理
heightTex.current = createTexture(heightmapImage1);
heatmapTex.current = createTexture(heatmap2dImage1);
data = generateData(256, 256, 200);
osg::ref_ptr<osg::Image> heatmap2dImage2 = generateHeatmap(256, 256, data);
osg::ref_ptr<osg::Image> heightmapImage2 = generateHeightmap(256, 256, data);
heightTex.next = createTexture(heightmapImage2);
heatmapTex.next = createTexture(heatmap2dImage2);
osg::Program* program = new osg::Program;
ss->setAttribute(program);
program->addShader(osgDB::readRefShaderFile(osg::Shader::VERTEX, SHADER_PATH + R"(heatmap3d.vert)"));
program->addShader(osgDB::readRefShaderFile(osg::Shader::FRAGMENT, SHADER_PATH + R"(heatmap3d.frag)"));
ss->setAttributeAndModes(program);
program->addBindAttribLocation("texCoord", osg::Drawable::TEXTURE_COORDS_0);
// 绑定纹理单元
ss->setTextureAttribute(0, heightTex.current);
ss->setTextureAttribute(1, heightTex.next);
ss->setTextureAttribute(2, heatmapTex.current);
ss->setTextureAttribute(3, heatmapTex.next);
// Uniform绑定
ss->addUniform(new osg::Uniform("heightMap", 0));
ss->addUniform(new osg::Uniform("nextHeightMap", 1));
ss->addUniform(new osg::Uniform("heatmap", 2));
ss->addUniform(new osg::Uniform("nextHeatmap", 3));
ss->addUniform(new osg::Uniform("transitionProgress", 0.0f));
}
osg::Timer_t startTime;
bool startSimulate = false;
class KeyboardEventHandler : public osgGA::GUIEventHandler {
public:
KeyboardEventHandler(){}
bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override {
if (ea.getEventType() == osgGA::GUIEventAdapter::KEYDOWN) {
switch (ea.getKey()) {
case 't':
if (!startSimulate)
{
startTime = osg::Timer::instance()->tick();
startSimulate = true;
}
return true;
default:
return false;
}
}
return false;
}
private:
};
class TimeUpdateCallback : public osg::NodeCallback
{
public:
TimeUpdateCallback() {}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
if (startSimulate)
{
osg::StateSet* ss = node->getStateSet();
if (ss)
{
float time = osg::Timer::instance()->delta_s(startTime, osg::Timer::instance()->tick()) / 3.0f;
time = osg::clampBetween(time, 0.0f, 1.0f);
if (time == 1.0)
{
std::random_device rd; // 用于获取随机种子
std::mt19937 gen(rd()); // 使用 Mersenne Twister 算法
// 定义一个分布范围 [100, 200]
std::uniform_int_distribution<> dis(100, 200);
std::vector<Point> data = generateData(256, 256, dis(gen));
osg::ref_ptr<osg::Image> heatmap2dImage = generateHeatmap(256, 256, data);
osg::ref_ptr<osg::Image> heightmapImage = generateHeightmap(256, 256, data);
ss->setTextureAttribute(0, ss->getTextureAttribute(1, osg::StateAttribute::TEXTURE));
ss->setTextureAttribute(1, createTexture(heightmapImage));
ss->setTextureAttribute(2, ss->getTextureAttribute(3, osg::StateAttribute::TEXTURE));
ss->setTextureAttribute(3, createTexture(heatmap2dImage));
ss->getUniform("transitionProgress")->set(0.0f);
startTime = osg::Timer::instance()->tick();
}
else
ss->getUniform("transitionProgress")->set(time);
}
traverse(node, nv);
}
}
};
int preview3DHeatmapWithAnimate()
{
osg::Geode* geode = createHeatmap3D();
setupStateSet(geode);
geode->setUpdateCallback(new TimeUpdateCallback());
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
osg::ref_ptr<osg::Group> group = new osg::Group;
group->addChild(geode);
viewer->setSceneData(group);
viewer->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));
viewer->addEventHandler(new osgViewer::StatsHandler());
osg::ref_ptr<KeyboardEventHandler> keyboardHandler = new KeyboardEventHandler;
viewer->addEventHandler(keyboardHandler);
return viewer->run();
}
int preview3DHeatmap()
{
std::vector<Point> data = generateData(256, 256, 100);
osg::ref_ptr<osg::Image> heatmap2dImage = generateHeatmap(256, 256, data);
osg::ref_ptr<osg::Image> heightmapImage = generateHeightmap(256, 256, data);
osg::ref_ptr<osg::Node> node = createTerrain(heightmapImage, heatmap2dImage);
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
osg::ref_ptr<osg::Group> group = new osg::Group;
group->addChild(node);
viewer->setSceneData(group);
viewer->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));
viewer->addEventHandler(new osgViewer::StatsHandler());
return viewer->run();
}
int preview2DHeatmap() {
std::vector<Point> data = generateData(256, 256, 100);
osg::ref_ptr<osg::Image> heatmap2dImage = generateHeatmap(256, 256, data);
// 创建带纹理的四边形几何体
osg::ref_ptr<osg::Geometry> quad = osg::createTexturedQuadGeometry(
osg::Vec3(-1.0f, -1.0f, 0.0f), // 左下角顶点
osg::Vec3(2.0f, 0.0f, 0.0f), // 宽度向量
osg::Vec3(0.0f, 2.0f, 0.0f), // 高度向量
0.0f, 0.0f, 1.0f, 1.0f // 纹理坐标 (左下角到右上角)
);
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
texture->setImage(heatmap2dImage);
osg::ref_ptr<osg::StateSet> stateSet = quad->getOrCreateStateSet();
stateSet->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON);
// 创建Geode并添加几何体
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable(quad);
// 创建场景图根节点
osg::ref_ptr<osg::Group> root = new osg::Group;
root->addChild(geode);
// 创建并设置Viewer
osgViewer::Viewer viewer;
viewer.setSceneData(root);
return viewer.run();
}
int main()
{
//return preview2DHeatmap();
//return preview3DHeatmap();
return preview3DHeatmapWithAnimate();
}
3、着色器代码
//heatmap3d.vert
#version 110
/* GLSL 1.10需要显式声明精度 (OpenGL ES要求) */
#ifdef GL_ES
precision highp float;
#endif
/* 自定义 uniforms */
uniform sampler2D heightMap;
uniform sampler2D nextHeightMap;
uniform float transitionProgress;
attribute vec2 texCoord;
// 输入纹理坐标属性(对应几何体的第0层纹理)
varying vec2 vTexCoord;
void main() {
float x = gl_Vertex.x;
float y = gl_Vertex.y;
vTexCoord = texCoord;
// 使用 texture2D 代替 texture 函数
float currentHeight = texture2D(heightMap, texCoord.xy).r * 50.0;
float nextHeight = texture2D(nextHeightMap, texCoord.xy).r * 50.0;
// 高度插值计算
float finalHeight = mix(currentHeight, nextHeight, transitionProgress);
// 坐标变换
vec4 pos = vec4(x, y, finalHeight, 1.0);
gl_Position = gl_ModelViewProjectionMatrix * pos;
}
//heatmap3d.frag
#version 110
/* GLSL 1.10需要显式声明精度 (OpenGL ES要求) */
#ifdef GL_ES
precision mediump float;
#endif
uniform sampler2D heatmap;
uniform sampler2D nextHeatmap;
uniform float transitionProgress;
varying vec2 vTexCoord;
void main() {
// 使用texture2D替代texture函数
vec4 currentColor = texture2D(heatmap, vTexCoord);
vec4 nextColor = texture2D(nextHeatmap, vTexCoord);
// 使用gl_FragColor替代自定义输出
gl_FragColor = mix(currentColor, nextColor, transitionProgress);
}