three.js 实现体模式的热力图混合渲染

three.js 实现体模式的热力图混合渲染

在线链接:https://threehub.cn/#/codeMirror?navigation=ThreeJS&classify=expand&id=heatmapModel

国内站点预览:http://threehub.cn

github地址: https://github.com/z2586300277/three-cesium-examples

在这里插入图片描述

import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'
import * as dat from 'dat.gui'

const box = document.getElementById('box')

const scene = new THREE.Scene()

const camera = new THREE.PerspectiveCamera(75, box.clientWidth / box.clientHeight, 0.1, 1000000)

camera.position.set(200, 200, 200)

const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, logarithmicDepthBuffer: true })

renderer.setSize(box.clientWidth, box.clientHeight)

renderer.setPixelRatio(window.devicePixelRatio * 2)

box.appendChild(renderer.domElement)

const controls = new OrbitControls(camera, renderer.domElement)

controls.enableDamping = true

window.onresize = () => {

    renderer.setSize(box.clientWidth, box.clientHeight)

    camera.aspect = box.clientWidth / box.clientHeight

    camera.updateProjectionMatrix()

}

// 渲染
animate()

function animate() {

    renderer.render(scene, camera)

    controls.update()

    requestAnimationFrame(animate)

}

class InterpolatedGradientMaterial extends THREE.ShaderMaterial {
    constructor({
        dataPoints = [],
        dataValues = [],
        colorLow = new THREE.Color(0x0000FF),
        colorHigh = new THREE.Color(0xFF0000),
        minValue = 0,
        maxValue = 1,
        opacity = 1,
        weightFunction = 'inverse_square',
        smoothstepEdges = { min: 0.0, max: 1.0 }
    }) {
        // 如果数据为空,填充假数据
        if (dataPoints.length === 0 || dataValues.length === 0) {
            console.warn("dataPoints and dataValues are empty. Using default fake data.")
            const fakeDataLength = 10 // 假数据长度
            dataPoints = Array.from({ length: fakeDataLength }, (_, i) => new THREE.Vector3(i, 0, 0)) // 填充简单的假数据
            dataValues = Array.from({ length: fakeDataLength }, (_, i) => i) // 填充简单的假数据
        }

        const vertexShader = /* glsl */`
            varying vec3 vPosition;
            void main() {
                vPosition = position;
                gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
            }
        `

        const fragmentShader = /* glsl */`
            varying vec3 vPosition;
            uniform vec3 dataPoints[DATA_POINTS_LENGTH];
            uniform float dataValues[DATA_POINTS_LENGTH];
            uniform vec3 colorLow;
            uniform vec3 colorHigh;
            uniform float minValue;
            uniform float maxValue;
            uniform int weightFunctionType;
            uniform vec2 smoothstepEdges;
            uniform float opacity;

            vec3 rgb2hsv(vec3 c) {
                vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
                vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
                vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
                float d = q.x - min(q.w, q.y);
                float e = 1.0e-10;
                return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
            }

            vec3 hsv2rgb(vec3 c) {
                vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
                vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
                return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
            }

            float getWeight(float distance) {
                if (weightFunctionType == 0) { // inverse
                    return 1.0 / distance;
                } else if (weightFunctionType == 1) { // inverse square
                    return 1.0 / (distance * distance);
                } else if (weightFunctionType == 2) { // exponential
                    return exp(-distance);
                }
                return 1.0 / (distance * distance); // default to inverse square
            }

            void main() {
                float totalWeight = 0.0;
                float weightedValue = 0.0;
                for(int i = 0; i < DATA_POINTS_LENGTH; i++) {
                    float distance = length(vPosition - dataPoints[i]);
                    float weight = getWeight(distance);
                    totalWeight += weight;
                    weightedValue += dataValues[i] * weight;
                }
                float interpolatedValue = weightedValue / totalWeight;
                float normalizedValue = (interpolatedValue - minValue) / (maxValue - minValue);
                normalizedValue = smoothstep(smoothstepEdges.x, smoothstepEdges.y, normalizedValue);

                vec3 hsvLow = rgb2hsv(colorLow);
                vec3 hsvHigh = rgb2hsv(colorHigh);
                vec3 hsvColor = mix(hsvLow, hsvHigh, normalizedValue);
                vec3 color = hsv2rgb(hsvColor);

                gl_FragColor = vec4(color, opacity);
            }
        `

        super({
            vertexShader,
            fragmentShader: fragmentShader.replace(/DATA_POINTS_LENGTH/g, dataPoints.length.toString()),
            uniforms: {
                dataPoints: { value: dataPoints },
                dataValues: { value: dataValues },
                colorLow: { value: colorLow },
                colorHigh: { value: colorHigh },
                minValue: { value: minValue },
                maxValue: { value: maxValue },
                opacity: { value: opacity },
                weightFunctionType: { value: ['inverse', 'inverse_square', 'exponential'].indexOf(weightFunction) },
                smoothstepEdges: { value: new THREE.Vector2(smoothstepEdges.min, smoothstepEdges.max) }
            },
            transparent: true
        })

        this.dataPointsLength = dataPoints.length
    }

    updateData(dataPoints, dataValues) {
        if (dataPoints.length !== this.dataPointsLength) {
            console.warn('New data length does not match the original length. Shader recompilation required.')
            this.recompileShader(dataPoints.length)
        }

        this.uniforms.dataPoints.value = dataPoints
        this.uniforms.dataValues.value = dataValues
        this.uniformsNeedUpdate = true
    }

    recompileShader(newLength) {
        const newFragmentShader = this.fragmentShader.replace(
            new RegExp(this.dataPointsLength.toString(), 'g'),
            newLength.toString()
        )
        this.fragmentShader = newFragmentShader
        this.needsUpdate = true
        this.dataPointsLength = newLength

        // 重新创建 uniforms
        this.uniforms.dataPoints.value = new Array(newLength).fill(new THREE.Vector3())
        this.uniforms.dataValues.value = new Array(newLength).fill(0)
    }
}


// 加载模型
new OBJLoader().load(

    `https://file.threehub.cn/` + 'files/model/z2586300277.obj',

    (obj) => {

        const box3 = new THREE.Box3().setFromObject(obj)

        const material = setHeat(box3)

        obj.traverse((child) => {

            if (child instanceof THREE.Mesh) {

                child.material = material

            }

        })

        scene.add(obj)

    }

)

function setHeat(box3) {

    // 定义数据点和数据值
    const dataPoints = []
    const dataValues = []

    // 在box3 的范围内随机生成点
    for (let i = 0; i < 20; i++) {

        dataPoints.push(new THREE.Vector3(

            Math.random() * (box3.max.x - box3.min.x) + box3.min.x,

            Math.random() * (box3.max.y - box3.min.y) + box3.min.y,

            Math.random() * (box3.max.z - box3.min.z) + box3.min.z

        ))

        dataValues.push(Math.random())

    }

    // 创建自定义材质
    const material = new InterpolatedGradientMaterial({
        dataPoints: dataPoints,
        dataValues: dataValues,
        colorLow: new THREE.Color(0x0000FF),  // 蓝色
        colorHigh: new THREE.Color(0xFF0000), // 红色
        minValue: 0,
        maxValue: 1,
        opacity: 0.8,
        weightFunction: 'inverse_square',
        smoothstepEdges: { min: 0.0, max: 1.0 }
    });

    // GUI 控制
    const materialFolder = new dat.GUI();

    materialFolder
        .addColor(material.uniforms.colorLow, "value")
        .name("Color Low");
    materialFolder
        .addColor(material.uniforms.colorHigh, "value")
        .name("Color High");
    materialFolder
        .add(material.uniforms.minValue, "value", 0, 1)
        .name("Min Value");
    materialFolder
        .add(material.uniforms.maxValue, "value", 0, 1)
        .name("Max Value");
    materialFolder
        .add(material.uniforms.weightFunctionType, "value", {
            Inverse: 0,
            "Inverse Square": 1,
            Exponential: 2,
        })
        .name("Weight Function")
        .onChange((value) => {
            material.uniforms.weightFunctionType.value = parseInt(value, 10);
        });
    materialFolder
        .add(material.uniforms.smoothstepEdges.value, "x", 0, 1)
        .name("Smoothstep Min");
    materialFolder
        .add(material.uniforms.smoothstepEdges.value, "y", 0, 1)
        .name("Smoothstep Max");

    return material

}

/**
 * 名称: 模型热力图
 * 作者: 优雅永不过时 https://github.com/z2586300277
 * 参考来源:https://github.com/CHENJIAMIAN/InterpolatedGradientMaterial 
 */

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值