3D区块多重渐变围栏
这里主要用到的就是threejs的shader,至于其他知识点,可以参考json生成3d区域
下面的主要代码:
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
import * as d3geo from 'd3-geo'
import guangzhouJSON from '../assets/json/guangzhou.json'
export default (domId) => {
/* ------------------------------初始化三件套--------------------------------- */
const dom = document.getElementById(domId);
const { innerHeight, innerWidth } = window
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 1, 2000);
camera.position.set(0, 0, 10);
camera.lookAt(scene.position);
const renderer = new THREE.WebGLRenderer({
antialias: true,// 抗锯齿
alpha: false,// 透明度
powerPreference: 'high-performance',// 性能
logarithmicDepthBuffer: true,// 深度缓冲
})
// renderer.setClearColor(0x000000, 0);// 设置背景色
// renderer.clear();// 清除渲染器
renderer.shadowMap.enabled = true;// 开启阴影
renderer.shadowMap.type = THREE.PCFSoftShadowMap;// 阴影类型
renderer.outputEncoding = THREE.sRGBEncoding;// 输出编码
renderer.toneMapping = THREE.ACESFilmicToneMapping;// 色调映射
renderer.toneMappingExposure = 1;// 色调映射曝光
renderer.physicallyCorrectLights = true;// 物理正确灯光
renderer.setPixelRatio(devicePixelRatio);// 设置像素比
renderer.setSize(innerWidth, innerHeight);// 设置渲染器大小
dom.appendChild(renderer.domElement);
// 重置大小
window.addEventListener('resize', () => {
const { innerHeight, innerWidth } = window
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
})
/* ------------------------------初始化工具--------------------------------- */
const controls = new OrbitControls(camera, renderer.domElement) // 相机轨道控制器
controls.enableDamping = true // 是否开启阻尼
controls.dampingFactor = 0.05// 阻尼系数
controls.panSpeed = -1// 平移速度
// const axesHelper = new THREE.AxesHelper(10);
// scene.add(axesHelper);
/* ------------------------------正题--------------------------------- */
// 相机控制器配置
const cameraControl = {
autoCamera: true,// 是否自动旋转
height: 10,// 相机高度
width: 0.5,// 相机宽度
depth: 1,// 相机深度
cameraPosX: 10,// 相机位置x
cameraPosY: 181,// 相机位置y
cameraPosZ: 116,// 相机位置z
autoRotate: false,// 是否自动旋转
rotateSpeed: 2000// 旋转速度
}
let geoFun;// 地理投影函数
let guangzhouData = [];// 地图数据
let shape = null;// 地图形状
let time = 1;// 时间
const group = new THREE.Group();
const edgeMaterial = new THREE.ShaderMaterial({
side: THREE.DoubleSide,
transparent: true,
depthTest: false,
uniforms: {
time: { value: 0.0 },
num: { value: 5.0 },
color1: { value: new THREE.Color('#00FFFF') }
},
vertexShader: `
varying vec2 vUv;
varying vec3 vNormal;
void main() {
vUv=uv;
vNormal=normal;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: `
uniform vec3 color1;
uniform float time;
uniform float num;
varying vec2 vUv;
varying vec3 vNormal;
void main() {
if(vNormal.z==1.0||vNormal.z==-1.0||vUv.y ==0.0){
discard;
}
else{
gl_FragColor = vec4(color1, 1.0-fract((vUv.y-time ) *num) ) ;
}
}`
});
// 初始化地理投影
const initGeo = (size) => {
geoFun = d3geo.geoMercator().scale(size || 100)
}
// 经纬度转像素坐标
const latlng2px = (pos) => {
if (pos[0] >= -180 && pos[0] <= 180 && pos[1] >= -90 && pos[1] <= 90) {
return geoFun(pos);
}
return pos;
};
// 处理地图数据
const processData = () => {
guangzhouData = guangzhouJSON.features[0].geometry.coordinates[0][0];
// 数据 从 经纬度-> 像素坐标-> 三维坐标
guangzhouData = guangzhouData.map(item => {
const two = latlng2px(item);
const three = new THREE.Vector3(two[0], 0, two[1]);
return three;
})
}
// 创建地图块
const createMap = () => {
const extrudeSettings = {
depth: 0.2,// 区块厚度
bevelEnabled: false// 是否使用倒角
};
shape = new THREE.Shape();
shape.moveTo(guangzhouData[0].x, guangzhouData[0].z);// 移动到第一个点
for (let i = 1; i < guangzhouData.length; i++) {
shape.lineTo(guangzhouData[i].x, guangzhouData[i].z);// 连线
}
shape.lineTo(guangzhouData[0].x, guangzhouData[0].z);// 闭合
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
const img = new URL('../assets/images/tex.png', import.meta.url).href;
const text = new THREE.TextureLoader().load(img);
text.wrapS = THREE.RepeatWrapping;// 水平方向
text.wrapT = THREE.RepeatWrapping;// 垂直方向
const material = new THREE.MeshBasicMaterial({
map: text,
color: new THREE.Color('#00FFFF')
})
const mesh = new THREE.Mesh(geometry, material);
mesh.rotateX(Math.PI / 2);// 旋转90度
mesh.position.y = extrudeSettings.depth * 0.5;
group.add(mesh);
}
// 创建边缘线
const createEdge = () => {
const extrudeSettings = {
depth: 1,// 厚度
bevelEnabled: false// 是否使用倒角
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
const mesh = new THREE.Mesh(geometry, edgeMaterial);
mesh.rotation.x = Math.PI * 0.5;
mesh.position.y = extrudeSettings.depth + 0.1;
group.add(mesh);
}
// 设置模型的中心点
const setModeCenter = (object, viewControl) => {
// 如果对象不存在,则返回
if (!object) {
return;
}
if (object.updateMatrixWorld) {
object.updateMatrixWorld();// 更新模型矩阵
}
// 获得包围盒得min和max
let box = new THREE.Box3().setFromObject(object);
let objSize;// 获取包围盒的尺寸
try {
objSize = box.getSize();
} catch (error) {
objSize = new THREE.Vector3(
Math.abs(box.max.x - box.min.x),
Math.abs(box.max.y - box.min.y),
Math.abs(box.max.z - box.min.z)
);
}
// 返回包围盒的中心点
const center = box.getCenter(new THREE.Vector3());
object.position.x += object.position.x - center.x;
object.position.y += object.position.y - center.y;
object.position.z += object.position.z - center.z;
let width = objSize.x;
let height = objSize.y;
let depth = objSize.z;
// 设置相机位置
let centroid = new THREE.Vector3().copy(objSize);
centroid.multiplyScalar(0.5);
if (viewControl.autoCamera) {
camera.position.x =
centroid.x * (viewControl.centerX || 0) + width * (viewControl.width || 0);
camera.position.y =
centroid.y * (viewControl.centerY || 0) + height * (viewControl.height || 0);
camera.position.z =
centroid.z * (viewControl.centerZ || 0) + depth * (viewControl.depth || 0);
} else {
camera.position.set(
viewControl.cameraPosX || 0,
viewControl.cameraPosY || 0,
viewControl.cameraPosZ || 0
);
}
camera.lookAt(0, 0, 0);
}
// 初始化
const init = () => {
initGeo(180)// 初始化地理投影
processData()// 处理地图数据
createMap()// 创建地图块
createEdge()// 创建边缘线
scene.add(group);// 添加到场景中
setModeCenter(group, cameraControl)// 设置模型的中心点
}
init();
/* ------------------------------动画函数--------------------------------- */
const animation = () => {
if (edgeMaterial) {
if (time >= 1.0) {
time = 0.0;
}
time += 0.005;
edgeMaterial.uniforms.time.value = time;
}
controls.update();// 如果不调用,就会很卡
renderer.render(scene, camera);
requestAnimationFrame(animation);
}
animation();
}