Threejs 给模型设置热力图

最近遇见一个模型uv做不了贴图,但是领导要让做热力图那种,然后我就想到了遍历顶点。然后设置不一样的颜色,但是老是做不成功,不知道什么原因,希望能帮忙看看,有偿
具体需求如图:

img

详细描述一下问题


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script type="x-shader/x-vertex" id="vertexshader">
    #ifdef GL_ES
    precision highp float;
    #endif
    varying vec2 vUv;
    void main() {
 
      // uv 和 顶点变换
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
 
    }
 
  </script>
 
<script type="x-shader/x-fragment" id="fragmentshader">
 
    #ifdef GL_ES
    precision highp float;
    #endif
    varying vec2 vUv;
    uniform sampler2D alphaScaleMap;
    uniform sampler2D paletteMap;
 
    void main() {
      // 温度转为权重alpha 并且 createRadialGradient 渐变的图
      vec4 alphaColor = texture2D(alphaScaleMap, vUv);
 
      // 根据温度转换为的权重alpha,确定改点的颜色 ,paletteMap 指定颜色条形图
      vec4 color = texture2D(paletteMap, vec2(alphaColor.a, 0.0));
      gl_FragColor = vec4(color.r, color.g, color.b, 1.0);
    }
 
  </script>
<script type="importmap">
            {
                "imports": {
                    "three": "../../../build/three.module.js"
                }
            }
        </script>
 
<script type="module">
 
    import * as THREE from '../../../build/three.module.js';
 
    import { OrbitControls } from '../../jsm/controls/OrbitControls.js';
 
    let renderer, scene, camera;
 
    const segments = 45;
    const w = 256;
    const h = 256;
 
    // 随机给出温度值 储存在2维数组
    const getTemperature = () => {
 
        const temperatureArray = new Array();
 
        for ( let i = 0; i < segments; i ++ ) {
 
            temperatureArray[ i ] = parseInt( Math.random() * 25 + 10 );  // 颜色的变化区间 10 - 35
 
        }
 
        return temperatureArray;
 
    };
 
    // 绘制辐射圆
    const drawCircular = ( context, opts ) => {
 
        var { x, y, radius, weight } = opts;
 
        radius = parseInt( radius * weight );
 
        // 创建圆设置填充色
        const rGradient = context.createRadialGradient( x, y, 0, x, y, radius );
        rGradient.addColorStop( 0, 'rgba(255, 0, 0, 1)' );
        rGradient.addColorStop( 1, 'rgba(0, 255, 0, 0)' );
        context.fillStyle = rGradient;
 
        // 设置globalAlpha
        context.globalAlpha = weight;
        context.beginPath();
        context.arc( x, y, radius, 0, 2 * Math.PI );
        context.closePath();
 
        context.fill();
 
    };
 
    // 获得渐变颜色条图
    const getPaletteMap = () => {
 
        //颜色条的颜色分布
        const colorStops = {
            1.0: '#f00',
            0.8: '#e2fa00',
            0.6: '#33f900',
            0.3: '#0349df',
            0.0: '#0f00ff'
        };
 
        //颜色条的大小
        const width = 256, height = 10;
 
        // 创建canvas
        const paletteCanvas = document.createElement( 'canvas' );
        paletteCanvas.width = width;
        paletteCanvas.height = height;
        paletteCanvas.style.position = 'absolute';
        paletteCanvas.style.top = '20px';
        paletteCanvas.style.right = '10px';
        const ctx = paletteCanvas.getContext( '2d' );
 
        // 创建线性渐变色
        const linearGradient = ctx.createLinearGradient( 0, 0, width, 0 );
        for ( const key in colorStops ) {
 
            linearGradient.addColorStop( key, colorStops[ key ] );
 
        }
 
        // 绘制渐变色条
        ctx.fillStyle = linearGradient;
        ctx.fillRect( 0, 0, width, height );
 
        document.body.appendChild( paletteCanvas );
 
        const paletteTexture = new THREE.Texture( paletteCanvas );
        paletteTexture.minFilter = THREE.NearestFilter;
        paletteTexture.needsUpdate = true;
 
        return paletteTexture;
 
    };
 
    // 获取透明度阶梯图
    const getAlphaScaleMap = ( width, height ) => {
 
        const canvas = document.createElement( 'canvas' );
        canvas.width = width;
        canvas.height = height;
 
        const context = canvas.getContext( '2d' );
 
        // 随机生成温度
        const tenperature = getTemperature();
 
        // 绘制透明度阶梯图
        for ( let i = 0; i < segments; i ++ ) {
 
            // 计算出当前温度占标准温度的权值
            const weight = tenperature[ i ] / 25; // 25 是之前颜色的变化区间 10 - 35
 
            drawCircular( context, {
                x: Math.random() * w,
                y: Math.random() * h,
                radius: 50,
                weight: weight
 
            } );
 
        }
 
        // 创建 Three 中的图片
        const tex = new THREE.Texture( canvas );
        tex.minFilter = THREE.NearestFilter;
        tex.needsUpdate = true;
        return tex;
 
    };
 
    init();
    animate();
 
    // 初始化
    function init() {
 
        // 渲染器
        renderer = new THREE.WebGLRenderer( { antialias: true } );
        renderer.setPixelRatio( window.devicePixelRatio );
        renderer.setSize( window.innerWidth, window.innerHeight );
        renderer.setClearColor( '#ccc' );
        document.body.appendChild( renderer.domElement );
 
        // 场景创建
        scene = new THREE.Scene();
 
        // camera 创建设置
        camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
        camera.position.set( 0, 0, 3000 );
 
        scene.add( new THREE.AmbientLight( 0xeef0ff ) );
 
        // 创建热力图渲染的平面几何体
        const heatMapGeo = new THREE.PlaneBufferGeometry( 500, 500 );
 
        // 创建热力图渲染的材质
        const heatMapMaterial = new THREE.ShaderMaterial( {
 
            transparent: true,
            vertexShader: document.getElementById( 'vertexshader' ).textContent,
            fragmentShader: document.getElementById( 'fragmentshader' ).textContent,
            uniforms: {
                'alphaScaleMap': {
                    type: 't',
                    value: getAlphaScaleMap( w, h )
                },
                'paletteMap': {
                    type: 't',
                    value: getPaletteMap()
                },
            }
 
        } );
 
        // 创建热力图Mesh,并显示在 Plane 上
        const heatMapPlane = new THREE.Mesh( heatMapGeo, heatMapMaterial );
        scene.add( heatMapPlane );
 
        const contorl = new OrbitControls( camera, renderer.domElement );
 
        window.addEventListener( 'resize', onWindowResize, false );
 
    }
 
    // 窗口变化的时候,进行 camera 视口的更新
    function onWindowResize() {
 
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
 
        renderer.setSize( window.innerWidth, window.innerHeight );
 
    }
 
    // 动画 update
    function animate() {
 
        requestAnimationFrame( animate );
        renderer.render( scene, camera );
 
    }
 
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>07SimpleHeatMapInThreejsCSDN</title>
    <style>
    </style>
</head>
<body>
 
<div id="container" >
 
</div>
<div>
    <canvas style="height: 900px; width: 100%;" id="heatmap"></canvas>
 
</div>
 
</body>
<script src="heatmap.js"></script>
<script type="importmap">
            {
                "imports": {
                    "three": "../../../build/three.module.js"
                }
            }
        </script>
 
<script type="module">
 
    import * as THREE from 'three';
    import { OrbitControls } from '../../jsm/controls/OrbitControls.js';
 
 
    let renderer, scene, camera;
    let heatmapInstance;
    let texture;
    let points;
    let mesh;
 
    const TemperatureColorStops = {
        1.0: '#f00',
        0.9: '#e2fa00',
        0.6: '#33f900',
        0.3: '#0349df',
        0.0: '#0f00ff'
    };
 
    init();
    animate();
 
    function init() {
 
        initRender();
        initCameraAndScene();
        initLight();
        addPluginHeatmap();
 
        // control 鼠标控制旋转移动
        const contorl = new OrbitControls( camera, renderer.domElement );
 
        // window
        window.addEventListener( 'resize', onWindowResize, false );
 
    }
 
    // 初始化 Renderer
    function initRender() {
 
        const container = document.getElementById( 'container' );
        renderer = new THREE.WebGLRenderer( { antialias: true } );
        renderer.setPixelRatio( window.devicePixelRatio );
        renderer.setSize( container.innerWidth, container.innerHeight );
        renderer.setClearColor( '#ccc' );
        container.appendChild( renderer.domElement );
 
    }
 
    // 初始化场景和相机
    function initCameraAndScene() {
 
        scene = new THREE.Scene();
 
        camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 );
        camera.position.set( 0, 0, 50 );
 
        scene.add( camera );
 
    }
 
    // 光照
    function initLight() {
 
        // 基本的半球光,类似环境光
        const hemiLight = new THREE.HemisphereLight( '#ffffff',
            '#444444', 0.5 );
        hemiLight.position.set( 0, 400, 0 );
        scene.add( hemiLight );
 
        // 添加平行光
        const dirLight = new THREE.DirectionalLight( 0xffffff, 0.8 );
        dirLight.position.set( 0, 200, 100 );
        dirLight.castShadow = true;
        dirLight.shadow.camera.top = 180;
        dirLight.shadow.camera.bottom = - 100;
        dirLight.shadow.camera.left = - 120;
        dirLight.shadow.camera.right = 120;
        scene.add( dirLight );
 
    }
 
    // 窗口更新
    function onWindowResize() {
 
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
 
        renderer.setSize( window.innerWidth, window.innerHeight );
 
    }
 
    function animate() {
 
        requestAnimationFrame( animate );
        renderer.render( scene, camera );
 
    }
 
    // 创建热力图
    function addPluginHeatmap() {
 
        // 创建一个heatmap实例对象
        // “h337” 是heatmap.js全局对象的名称.可以使用它来创建热点图实例
        // 这里直接指定热点图渲染的div了.heatmap支持自定义的样式方案,网页外包接活具体可看官网api
        heatmapInstance = h337.create( {
 
            container: document.getElementById( 'heatmap' ),
 
            //backgroundColor:'red',    // '#121212'    'rgba(0,102,256,0.2)'
            gradient: TemperatureColorStops,
            radius: 50,     // [0,+∞)
            opacity: .5,
            blur: '.8',
 
        } );
 
        setHeatMapData();
 
        // 获取 heatmap
        texture = new THREE.Texture( heatmapInstance._renderer.canvas );
        const material = new THREE.MeshLambertMaterial( {
 
            map: texture,
            transparent: true,
            opacity: 1
 
        } );
 
 
        mesh = new THREE.Mesh( new THREE.PlaneGeometry( 10, 10, 10 ), material );
        scene.add( mesh );
 
 
        // 更新图片
        if ( texture ) {
 
            texture.needsUpdate = true;
 
        }
 
    }
 
    // 设置热力图位置温度数据
    function setHeatMapData() {
 
        //构建一些随机数据点,网页切图价格这里替换成你的业务数据
        points = [];
        let max = 0;
        const width = document.body.clientWidth;
        const height = document.body.clientHeight;
        let len = 500;
        // 随机位置点设置温度值
        while ( len -- ) {
 
            var val = Math.floor( Math.random() * 25 + 10 );
            max = Math.max( max, val );
            var point = {
                x: Math.floor( Math.random() * width ),
                y: Math.floor( Math.random() * height ),
                value: val
            };
            points.push( point );
 
        }
 
        // 准备 heatmap 的数据
        const data = {
            max: max,
 
            data: points
        };
        //因为data是一组数据,web切图报价所以直接setData
        heatmapInstance.setData( data ); //数据绑定还可以使用
 
    }
 
</script>
 
</html>

以下答案由GPT-3.5大模型与博主波罗歌共同编写:
使用遍历顶点的方式给模型设置热力图需要用到以下步骤:

  1. 定义热力图颜色值

首先需要定义一个热力图的颜色值数组,例如:

var colors = [
    0x0000FF, // 最低温度,蓝色
    0x00FFFF, 
    0x00FF00, 
    0xFFFF00, 
    0xFF0000  // 最高温度,红色
];

这里将热力图颜色值从蓝色到红色依次排列。

  1. 遍历顶点

使用 THREE.Geometry 中的 vertices 属性获取模型的顶点数组,并使用 for 循环遍历所有顶点。

例如:

mesh.geometry.vertices.forEach(function(vertex) {
    // 遍历顶点的代码
});
  1. 计算每个顶点的温度值

根据模型的实际需求,可以通过函数计算每个顶点的温度值,例如:

function getTemperature(vertex) {
    // 返回该顶点的温度值,范围在 0 ~ 4 之间
    return Math.floor(Math.random() * 5);
}

在真实应用中,可能需要根据传感器等实际设备来获取每个顶点的温度值。

  1. 根据温度值设置顶点颜色

根据每个顶点的温度值,计算需要显示的颜色,例如

var temperature = getTemperature(vertex); // 获取当前顶点的温度值
var colorIndex = Math.floor(temperature / 1.25); // 计算需要使用的颜色索引,值范围在 0 ~ 4 之间
var color = new THREE.Color(colors[colorIndex]); // 获取对应颜色值,并生成 THREE.Color 对象

这里将温度值分为 5 个等级(0-1.25、1.25-2.5、2.5-3.75、3.75-5),并将每个等级对应一个颜色值,根据当前顶点的温度值计算需要使用的颜色索引并获取对应色值。

  1. 应用顶点颜色

使用 THREE.Geometry 中的 colors 属性设置获取到的颜色值。

mesh.geometry.colors.push(color);

将获取到的颜色值push到颜色数组中。

  1. 设置材质

定义一个新的材质并将颜色数组赋值给材质的 vertexColors 属性,例如:

var material = new THREE.MeshBasicMaterial({
    vertexColors: THREE.VertexColors
});
  1. 绘制模型

将定义好的材质应用到模型上,然后进行绘制即可:

mesh.geometry.colorsNeedUpdate = true; // 标记颜色变化需要更新
mesh.material = material; // 应用新的材质
mesh.geometry.computeVertexNormals(); // 计算面法线向量

完整代码示例:

var colors = [
    0x0000FF, // 最低温度,蓝色
    0x00FFFF, 
    0x00FF00, 
    0xFFFF00, 
    0xFF0000  // 最高温度,红色
];

function getTemperature(vertex) {
    // 返回该顶点的温度值,范围在 0 ~ 4 之间
    return Math.floor(Math.random() * 5);
}

function setHeatMap(mesh) {
    // 遍历所有顶点,并根据温度值设置颜色
    mesh.geometry.vertices.forEach(function(vertex) {
        var temperature = getTemperature(vertex);
        var colorIndex = Math.floor(temperature / 1.25);
        var color = new THREE.Color(colors[colorIndex]);
        mesh.geometry.colors.push(color);
    });

    // 定义新的材质,并将颜色数组赋值给 vertexColors 属性
    var material = new THREE.MeshBasicMaterial({
        vertexColors: THREE.VertexColors
    });

    // 将材质应用到模型,并进行绘制
    mesh.geometry.colorsNeedUpdate = true;
    mesh.material = material;
    mesh.geometry.computeVertexNormals();
}

以上就是使用遍历顶点的方式给模型设置热力图的示例,你可以根据自己的需求进行修改和扩展。
如果我的回答解决了您的问题,请采纳!

以下内容引用CHATGPT、有用望采纳:

可以通过遍历模型的顶点,根据顶点的位置信息设置不同的颜色,来实现热力图的效果。具体思路如下:

  1. 遍历模型的顶点,获取每个顶点的位置信息。

  2. 根据顶点的位置信息计算出对应的热力值(可以根据不同的需求自定义计算公式)。

  3. 根据热力值设置顶点的颜色。

  4. 创建材质对象,将顶点颜色作为材质的vertexColors属性传入,将材质应用到模型上。

下面是一个简单的示例代码:

// 遍历模型顶点,设置热力图
function setHeatMap(model) {
  // 创建顶点颜色数组
  const colors = [];
  const vertices = model.geometry.vertices;
  // 遍历顶点,计算热力值并设置颜色
  for (let i = 0; i < vertices.length; i++) {
    const vertex = vertices[i];
    // 计算热力值,这里简单假设热力值为顶点z坐标的值
    const heatValue = vertex.z;
    // 根据热力值设置颜色,这里简单假设颜色为红色到蓝色的渐变
    const color = new THREE.Color(0x0000ff);
    color.lerp(new THREE.Color(0xff0000), heatValue);
    colors.push(color);
  }
  // 创建材质对象,将顶点颜色作为材质的vertexColors属性传入
  const material = new THREE.MeshBasicMaterial({
    vertexColors: THREE.VertexColors
  });
  // 创建新的几何体,将顶点颜色赋值给新的几何体
  const geometry = new THREE.Geometry();
  geometry.vertices = vertices;
  geometry.colors = colors;
  // 创建新的网格对象,将新的几何体和材质应用到网格对象上
  const mesh = new THREE.Mesh(geometry, material);
  // 移除原有的模型,将新的网格对象添加到场景中
  scene.remove(model);
  scene.add(mesh);
}

需要注意的是,这里示例代码使用的是MeshBasicMaterial,如果需要更高级的材质效果,可以使用其他类型的材质对象。另外,由于这里使用了遍历顶点的方式,对于顶点数较多的模型可能会有一定的性能问题,需要根据具体情况进行优化。

什么模型做不了uv?跟热力图有什么关系,问题描述不清楚。不知道如何帮你。

该回答引用ChatGPT
根据您的问题描述,您的需求是给Three.js中的模型设置热力图,您想要通过遍历顶点来设置不同颜色以实现热力图的效果。具体错误与代码没有提供,因此无法具体回答。

但是,下面给出一份代码作为参考,该代码使用了Three.js中的ShaderMaterial来实现热力图的效果:

javascript
// 创建一个初始的几何体
var geometry = new THREE.BoxGeometry(1, 1, 1);

// 创建一个自定义的着色器
var vertexShader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`;
var fragmentShader = `
uniform vec3 colorA;
uniform vec3 colorB;
varying vec2 vUv;
void main() {
gl_FragColor = vec4( mix( colorA, colorB, vUv.y ), 1.0 );
}
`;
var uniforms = {
colorA: { value: new THREE.Color(0xff0000) },
colorB: { value: new THREE.Color(0x0000ff) }
};
var material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: vertexShader,
fragmentShader: fragmentShader
});

// 创建一个网格对象
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);


上述代码中,我们使用了一个自定义的着色器,在顶点着色器中,我们将uv坐标传递到片元着色器中,并在片元着色器中根据uv坐标的y值来计算颜色,从而实现了热力图的效果。

您可以根据自己的需求进行修改。如果该代码仍然无法解决您的问题,请提供更具体的代码和错误信息。

该回答通过自己思路及引用到GPTᴼᴾᴱᴺᴬᴵ搜索,得到内容具体如下:
要为 Three.js 中的模型设置热力图,可以按照以下步骤进行:

  1. 遍历模型的顶点,并计算每个顶点的热力值。

  2. 根据每个顶点的热力值,为该顶点设置对应的颜色值。

  3. 创建一个 THREE.BufferGeometry 对象,并将每个顶点的位置和颜色值存储到该对象中。

  4. 创建一个 THREE.PointsMaterial 材质,并将其设置为 vertexColors 模式。

  5. 创建一个 THREE.Points 对象,并将上述 BufferGeometry 对象和 PointsMaterial 材质分别设置给该对象。

  6. 最后将该 Points 对象加入场景中即可。

下面是一份完整的代码示例:

// 创建场景和相机
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;

// 创建渲染器
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 加载模型并遍历顶点
var loader = new THREE.OBJLoader();
loader.load('your-model.obj', function (object) {
  object.traverse(function (child) {
    if (child instanceof THREE.Mesh) {
      var geometry = child.geometry;
      var vertices = geometry.vertices;
      var colors = [];
      for (var i = 0; i < vertices.length; i++) {
        // 计算热力值,这里可以根据具体需求自行实现
        var heatValue = calculateHeatValue(vertices[i]);

        // 根据热力值设置对应颜色
        var color = new THREE.Color();
        color.setHSL(0.7 * (1 - heatValue), 1, 0.5);

        // 将颜色值存储到数组中
        colors.push(color.r, color.g, color.b);
      }
    
      // 创建 BufferGeometry 对象并将顶点位置和颜色值存储到该对象中
      var bufferGeometry = new THREE.BufferGeometry();
      bufferGeometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(geometry.vertices.length * 3), 3));
      bufferGeometry.addAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3));
      for (var i = 0; i < geometry.vertices.length; i++) {
        bufferGeometry.attributes.position.setXYZ(i, vertices[i].x, vertices[i].y, vertices[i].z);
      }

      // 创建 PointsMaterial 材质并设置为 vertexColors 模式
      var pointsMaterial = new THREE.PointsMaterial({ size: 0.05, vertexColors: THREE.VertexColors });

      // 创建 Points 对象并将上述 BufferGeometry 对象和 PointsMaterial 材质分别设置给该对象
      var points = new THREE.Points(bufferGeometry, pointsMaterial);

      // 将 Points 对象加入场景中
      scene.add(points);
    }
  });
});

// 渲染循环
function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}
animate();

在上述代码中,我们使用了 Three.js 的 OBJLoader 加载模型,并在遍历模型的顶点时,根据具体需求计算了热力值,并为每个顶点设置了对应的颜色值。我们使用了 THREE.BufferGeometry 对象来存储顶点位置和颜色值,并创建了 THREE.PointsMaterial 材质并将其设置为 vertexColors 模式。最后创建了一个 THREE.Points 对象,并将上述 BufferGeometry 对象和 PointsMaterial 材质分别设置给该对象,然后将该 Points 对象加入场景中。在渲染循环中,我们使用了 Three.js 的 requestAnimationFrame 函数来循环渲染场景和相机,以实现动画效果。

需要注意的是,在计算热力值时,您需要根据具体需求选择合适的算法,并根据算法实现代码。在本示例中,我们使用了简单的线性函数来计算热力值,如下所示:

function calculateHeatValue(vertex) {
  return (vertex.y + 1) / 2; // 将顶点 y 坐标映射到 [0, 1] 范围内
}

您可以根据具体需求修改该函数,以实现更好的效果。

另外,需要注意的是,由于遍历顶点并为每个顶点设置颜色的操作可能比较耗时,因此在处理较大模型时,可能会导致性能问题。为了避免这种情况,您可以考虑使用一些优化策略,例如:只处理模型的部分顶点,或者使用多线程等技术。


如果以上回答对您有所帮助,点击一下采纳该答案~谢谢