1、地球图 - 绘制地球、轮廓流光效果、添加光晕轮廓效果并自转

本文详细介绍了如何使用Three.js库创建一个3D地球模型,包括加载地球纹理,绘制中国轮廓地图,添加流光效果,创建辉光图层以产生光晕轮廓,以及利用TWEEN库实现地球视图拉近和自转动画效果。源码和3D模型提供下载。
摘要由CSDN通过智能技术生成

我们实现标题中的效果基本分为接下来的这些功能点,如果看过“Threejs项目实战 - 省市图”,会对这些功能有一定的了解,特别是开启辉光效果和后续的飞线效果。

地球

绘制地球实体
  1. 加载地球贴图纹理
import earthChartletImage from './images/earth-chartlet.jpg';
let texture = new THREE.TextureLoader()
texture.load( earthChartletImage, ( texture ) => {
  // 在这里会做第2步操作
});
  1. 通过加载好的纹理,创建一个实体,并添加到scene中
import earthChartletImage from './images/earth-chartlet.jpg';

const earthGroup = new THREE.Group();

let texture = new THREE.TextureLoader()
texture.load( earthChartletImage, ( texture ) => {
  // 创建个圆形
  var globeGgeometry = new THREE.SphereGeometry( 5, 100, 100 );
  var globeMaterial = new THREE.MeshStandardMaterial( { map: texture, side:THREE.DoubleSide } );
  var globeMesh = new THREE.Mesh( globeGgeometry, globeMaterial );
  earthGroup.rotation.set(0.5, 2.9, 0.1);
  earthGroup.add( globeMesh );
  scene.add( earthGroup );
});

在这里插入图片描述

地图轮廓

绘制中国轮廓地图
  1. 绘制地图工具方法:createMap.js
import { util } from './util.js';
import * as THREE from 'three';
import * as d3 from 'd3-geo';

class CreateMap {
    drawMap(mapUrl) {
        let mapData = util.decode(mapUrl);
        if (!mapData) {
            console.error('mapData 数据不能是null');
            return;
        }
        // 把经纬度转换成x,y,z 坐标
        mapData.features.forEach(d => {
            d.vector3 = [];
            d.geometry.coordinates.forEach((coordinates, i) => {
                d.vector3[i] = [];
                coordinates.forEach((c, j) => {
                  if (c[0] instanceof Array) {
                      d.vector3[i][j] = [];
                      c.forEach(cinner => {
                        let cp = this.lglt2xyz(cinner);
                        d.vector3[i][j].push(cp);
                      });
                  } else {
                      let cp = this.lglt2xyz(c);
                      d.vector3[i].push(cp);
                  }
                });
            });
        });

        // 绘制地图模型
        let group = new THREE.Group();
        mapData.features.forEach(d => {
            // if (d.properties.name !== '山东省') {
              let g = new THREE.Group(); // 用于存放每个地图模块。||省份
              g.data = d;
              g.name = d.properties.name;
              d.vector3.forEach(points => {
                // 多个面
                if (points[0][0] instanceof Array) {
                    points.forEach(p => {
                        let lineMesh = this.drawLine(p);
                        g.add(lineMesh);
                    });
                } else {
                    // 单个面
                    let lineMesh = this.drawLine(points);
                    g.add(lineMesh);
                }
              });
              group.add(g);
            // }
        });
        // scene.add(group);
        return group
    }

    /**
     * @desc 绘制线条
     * @param {} points
     */
     drawLine(points) {
        let positionsArry = [];
        // points.forEach(d => {
        //   let [x, y, z] = d;
        //   positionsArry.push(x, y, z + 0.01)
        // });
        points.forEach(d => {
          if (d instanceof Array) {
            d.forEach(a => {
              let [x, y, z] = a;
              positionsArry.push(x, y, z)
            })
          } else {
            let [x, y, z] = d;
            positionsArry.push(x, y, z)
          }
        });
        let positions = new Float32Array(positionsArry)
        let lineGeo = new THREE.BufferGeometry();
        lineGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3))
        let material = new THREE.LineBasicMaterial({
          color: 0x3BFA9E,  // 0x3BFA9E  0XF19553
          transparent: true,
          opacity: 1,
          // side: THREE.DoubleSide
        });
        let line = new THREE.Line(lineGeo, material);
        return line;
      }
  

    /**
     * @desc 经纬度转换成墨卡托投影
     * @param {array} 传入经纬度
     * @return array [x,y,z]
     */
    lnglatToMector(lnglat) {
        if (!this.projection) {
          this.projection = d3
            .geoMercator()
            .center([108.904496, 32.668849])
            .scale(80)
            .rotate(Math.PI / 4)
            .translate([0, 0]);
        }
        let [y, x] = this.projection([...lnglat]);
        let z = 0;
        return [x, y, z];
    }

    // threejs自带的经纬度转换
    lglt2xyz(lnglat) {
      let lng = lnglat[0];
      let lat = lnglat[1];
      const theta = ( 90 + lng ) * ( Math.PI / 180 );
      const phi = ( 90 - lat ) * ( Math.PI / 180 );
      return ( new THREE.Vector3() ).setFromSpherical( new THREE.Spherical( 5, phi, theta ) );
    }
}
export const mapUtil = new CreateMap();
  1. 通过工具类创建中国地图轮廓
import { mapUtil } from './createMap.js';

data() {
  return {
    chinaMapUrl: require('./map/china.json'),
  };
},

// 绘制中国地图轮廓
chinaModel = mapUtil.drawMap(this.chinaMapUrl);
earthGroup.add(chinaModel);
  1. 添加地图流光效果,这里我统一集成到了createMap.js中了

createMap.js

import { util } from './util.js';
import * as THREE from 'three';
import * as d3 from 'd3-geo';
import { flyLineUtil } from './createFlyLine.js';

class CreateMap {
    drawMap(mapUrl) {
        let mapData = util.decode(mapUrl);
        if (!mapData) {
            console.error('mapData 数据不能是null');
            return;
        }
        // 把经纬度转换成x,y,z 坐标
        mapData.features.forEach(d => {
            d.vector3 = [];
            d.geometry.coordinates.forEach((coordinates, i) => {
                d.vector3[i] = [];
                coordinates.forEach((c, j) => {
                  if (c[0] instanceof Array) {
                      d.vector3[i][j] = [];
                      c.forEach(cinner => {
                        let cp = this.lglt2xyz(cinner);
                        d.vector3[i][j].push(cp);
                      });
                  } else {
                      let cp = this.lglt2xyz(c);
                      d.vector3[i].push(cp);
                  }
                });
            });
        });

        // 绘制地图模型
        let group = new THREE.Group();
        mapData.features.forEach(d => {
            // if (d.properties.name !== '山东省') {
              let g = new THREE.Group(); // 用于存放每个地图模块。||省份
              g.data = d;
              g.name = d.properties.name;
              d.vector3.forEach(points => {
                // 多个面
                if (points[0][0] instanceof Array) {
                    points.forEach(p => {
                        // let mesh = this.drawModel(p);
                        let lineMesh = this.drawLine(p);
                        // g.add(mesh);
                        g.add(lineMesh);
                    });
                } else {
                    // 单个面
                    // let mesh = this.drawModel(points);
                    let lineMesh = this.drawLine(points);
                    // g.add(mesh);
                    g.add(lineMesh);
                }
              });
              group.add(g);
            // }
        });
        // scene.add(group);
        return group
    }

    /**
     * @desc 绘制线条
     * @param {} points
     */
     drawLine(points) {
        let positionsArry = [];
        // points.forEach(d => {
        //   let [x, y, z] = d;
        //   positionsArry.push(x, y, z + 0.01)
        // });
        points.forEach(d => {
          if (d instanceof Array) {
            d.forEach(a => {
              let [x, y, z] = a;
              positionsArry.push(x, y, z)
            })
          } else {
            let [x, y, z] = d;
            positionsArry.push(x, y, z)
          }
        });
        let positions = new Float32Array(positionsArry)
        let lineGeo = new THREE.BufferGeometry();
        lineGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3))
        let material = new THREE.LineBasicMaterial({
          color: 0x3BFA9E,  // 0x3BFA9E  0XF19553
          transparent: true,
          opacity: 1,
          // side: THREE.DoubleSide
        });
        let line = new THREE.Line(lineGeo, material);
        return line;
      }

      // 画流光
      drawTimeLine(mapUrl) {
        let mapData = util.decode(mapUrl);
        if (!mapData) {
            console.error('mapData 数据不能是null');
            return;
        }

        let group = new THREE.Group();
        mapData.features.forEach( elem => {
          // 新建一个省份容器:用来存放省份对应的模型和轮廓线
          const province = new THREE.Object3D();
          const coordinates = elem.geometry.coordinates;
          coordinates.forEach( multiPolygon => {
            multiPolygon.forEach( polygon => {
              // 这里的坐标要做2次使用:1次用来构建模型,1次用来构建轮廓线
              if (polygon.length > 200) {
                let v3ps = [];
                for (let i = 0; i < polygon.length; i ++) {
                  let pos = this.lglt2xyz( polygon[i] );
                  v3ps.push( pos );
                }
                let curve = new THREE.CatmullRomCurve3( v3ps, false/*是否闭合*/ );
                let color = new THREE.Vector3( 0.5999758518718452, 0.7798940272761521, 0.6181903838257632 );
                let flyLine = flyLineUtil.createFlyLine( curve, {
                  speed: 0.15,
                  // color: randomVec3Color(),
                  color: color,
                  number: 2, //同时跑动的流光数量
                  length: 0.2, //流光线条长度
                  size: 2 //粗细
                }, 5000 );
                province.add( flyLine );
              }
            } );
          } );
          group.add( province );
        } );

        return group;
      }

    /**
     * @desc 经纬度转换成墨卡托投影
     * @param {array} 传入经纬度
     * @return array [x,y,z]
     */
    lnglatToMector(lnglat) {
        if (!this.projection) {
          this.projection = d3
            .geoMercator()
            .center([108.904496, 32.668849])
            .scale(80)
            .rotate(Math.PI / 4)
            .translate([0, 0]);
        }
        let [y, x] = this.projection([...lnglat]);
        let z = 0;
        return [x, y, z];
    }

    // threejs自带的经纬度转换
    lglt2xyz(lnglat) {
      let lng = lnglat[0];
      let lat = lnglat[1];
      const theta = ( 90 + lng ) * ( Math.PI / 180 );
      const phi = ( 90 - lat ) * ( Math.PI / 180 );
      return ( new THREE.Vector3() ).setFromSpherical( new THREE.Spherical( 5, phi, theta ) );
    }
}
export const mapUtil = new CreateMap();

createFlyLine.js

import { Line2 } from "three/examples/jsm/lines/Line2";
import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";

// 顶点着色器
let vertexShader = `    
  varying vec2 vUv;
  attribute float percent;
  uniform float u_time;
  uniform float number;
  uniform float speed;
  uniform float length;
  varying float opacity;
  uniform float size;

  void main()
  {
      vUv = uv;
      vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
      float l = clamp(1.0-length,0.0,1.0);//空白部分长度

      gl_PointSize = clamp(fract(percent*number + l - u_time*number*speed)-l ,0.0,1.) * size * (1./length);

      opacity = gl_PointSize/size;
      gl_Position = projectionMatrix * mvPosition;
  }`;

// 分片着色器
let fragmentShader = `
  #ifdef GL_ES
  precision mediump float;
  #endif

  varying float opacity;
  uniform vec3 color;

  void main(){
      if(opacity <=0.2){
          discard;
      }
      gl_FragColor = vec4(color,1.0);
  }`;

let commonUniforms = {
  u_time: { value: 0.0 },
};


/**
   * @param curve {THREE.Curve} 路径,
   * @param matSetting {Object} 材质配置项
   * @param pointsNumber {Number} 点的个数 越多越细致
   * */
createFlyLine (curve, matSetting, pointsNumber) {
  var points = curve.getPoints( pointsNumber );
  var geometry = new THREE.BufferGeometry().setFromPoints( points );

  let length = points.length;
  var percents = new Float32Array(length);
  for (let i = 0; i < points.length; i+=1){
    percents[i] = (i/length);
  }

  geometry.setAttribute('percent', new THREE.BufferAttribute(percents,1));

  let lineMaterial = this.initLineMaterial(matSetting);

  var flyLine = new THREE.Points( geometry, lineMaterial );
  return flyLine
}

// 首先要写出一个使用fragmentshader生成的material并赋在点上
initLineMaterial(setting){
  let number = setting ? (Number(setting.number) || 1.0) : 1.0;
  let speed = setting ? (Number(setting.speed) || 1.0) : 1.0;
  let length = setting ? (Number(setting.length) || 0.5) : 0.5;
  let size = setting ?(Number(setting.size) || 3.0) : 3.0;
  let color = setting ? setting.color || new THREE.Vector3(0,1,1) : new THREE.Vector3(0,1,1);
  let singleUniforms = {
    u_time: commonUniforms.u_time,
    number: {type: 'f', value:number},
    speed: {type:'f',value:speed},
    length: {type: 'f', value: length},
    size: {type: 'f', value: size},
    color: {type: 'v3', value: color}
  };
  let lineMaterial = new THREE.ShaderMaterial({
    uniforms: singleUniforms,
    vertexShader: vertexShader,
    fragmentShader: fragmentShader,
    transparent: true,
    //blending:THREE.AdditiveBlending,
  });
  return lineMaterial;
}
  1. 通过工具类创建中国地图轮廓流光效果
// 绘制中国轮廓流光
chinaTimeLineModel = mapUtil.drawTimeLine(this.chinaTimeLineUrl);
earthGroup.add(chinaTimeLineModel);

在这里插入图片描述

创建辉光图层

上面的流光效果是要开启辉光图层的,这里在“Threejs项目实战 - 省市图”专栏中有讲解,在这里会再讲一遍
将辉光效果写了一个工具类:createGlow.js

  1. createGlow.js:主要是处理一个创建layer层及创建通道效果
import * as THREE from 'three';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js'
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass';

// 顶点着色器
let hgVertexShader = `
varying vec2 vUv;
void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`

// 分片着色器
let hgShaderMaterials = `
  uniform sampler2D baseTexture;
uniform sampler2D bloomTexture;
varying vec2 vUv;
void main() {
    gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) );
}`

class createGlow {

  // 创建一个 Layer,用于区分辉光物体
  createLayer(num) {
    const layer = new THREE.Layers()
    layer.set(num)
    return layer
  }

  // 辉光效果
  createUnrealBloomPass() {
    const bloomPass = new UnrealBloomPass(
      new THREE.Vector2(window.innerWidth, window.innerHeight),
      1.5,
      0.4,
      0.0
    )
    const params = {
      bloomThreshold: 0,
      bloomStrength: 1, // 辉光强度
      bloomRadius: 0
    }
    bloomPass.threshold = params.bloomThreshold
    bloomPass.strength = params.bloomStrength
    bloomPass.radius = params.bloomRadius
    return bloomPass
  }

  // ShaderPass,着色器pass,自定义程度高,需要编写OpenGL代码
  // 传入bloomComposer
  createShaderPass(bloomComposer) {
    // 着色器材质,自定义shader渲染的材质
    const shaderMaterial = new THREE.ShaderMaterial({
      uniforms: {
        baseTexture: { value: null },
        // 辉光贴图属性设置为传入的bloomComposer,这里就说明了为什么bloomComposer不要渲染到屏幕上
        bloomTexture: { value: bloomComposer.renderTarget2.texture }
      },
      vertexShader: hgVertexShader, // 顶点着色器
      fragmentShader: hgShaderMaterials, // 片元着色器
      defines: {}
    })
    const shaderPass = new ShaderPass(shaderMaterial, 'baseTexture')
    shaderPass.needsSwap = true
    return shaderPass
  }
}

export const glowUtil = new createGlow();
  1. 使用工具类创建辉光图层:以下是具体使用方法
// 辉光图层相关变量
let composer = null;
let outlinePass = null;
let bloomLayer = null;
let bloomComposer = null;
let darkMaterial = null;
let hgMaterials = {}
let bloomIgnore = [];

// 创建辉光图层
createGlowPass() {
  bloomLayer = glowUtil.createLayer(1);
  // 辉光层默认样式
  darkMaterial = new THREE.MeshBasicMaterial({ color: 'black' })// 跟辉光光晕有关的变量
  this.createSawtooth();// 抗锯齿
}

// 抗锯齿,添加辉光层效果
createSawtooth() {
  var renderPass = new RenderPass(scene, camera)
  // 抗锯齿设置
  var effectFXAA = new ShaderPass(FXAAShader)
  effectFXAA.uniforms['resolution'].value.set(
    0.6 / window.innerWidth,
    0.6 / window.innerHeight
  ) // 渲染区域Canvas画布宽高度  不一定是全屏,也可以是区域值
  effectFXAA.renderToScreen = true
  // 我们封装好的 createUnrealBloomPass 函数,用来创建BloomPass(辉光效果)
  const bloomPass = glowUtil.createUnrealBloomPass()
  bloomComposer = new EffectComposer(renderer)
  bloomComposer.renderToScreen = false // 不渲染到屏幕上
  bloomComposer.addPass(renderPass)
  bloomComposer.addPass(bloomPass)// 添加光晕效果
  // bloomComposer.addPass(effectFXAA)// 去掉锯齿

  // 创建自定义的着色器Pass,详细见下
  const shaderPass = glowUtil.createShaderPass(bloomComposer)
  composer = new EffectComposer(renderer)
  composer.addPass(renderPass)

  outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera)
  outlinePass.edgeStrength = 5 // 包围线浓度
  outlinePass.edgeGlow = 0.5 // 边缘线范围
  outlinePass.edgeThickness = 2// 边缘线浓度
  outlinePass.pulsePeriod = 2// 包围线闪烁评率
  outlinePass.visibleEdgeColor.set('#ffffff') // 包围线颜色
  outlinePass.hiddenEdgeColor.set('#190a05')// 被遮挡的边界线颜色
  composer.addPass(outlinePass)
  composer.addPass(shaderPass)
  //composer.addPass(effectFXAA)
}
  
// 隐藏不需要辉光的物体
darkenNonBloomed(obj) {
  if (obj instanceof THREE.Scene) { // 此处忽略Scene,否则场景背景会被影响
    hgMaterials.scene = obj.background
    obj.background = null
    return;
  }
  if (
    obj instanceof THREE.Sprite || // 此处忽略Sprite
    bloomIgnore.includes(obj.type) || obj instanceof THREE.Line ||
    (obj.isMesh && bloomLayer.test(obj.layers) === false) // 判断与辉光是否同层
  ) {
    if(obj.name.indexOf('sd-line') !== -1)
      return;
    hgMaterials[obj.uuid] = obj.material
    obj.material = darkMaterial
  }
}

// 还原非辉光体
restoreMaterial(obj) {
  if (obj instanceof THREE.Scene) {
    obj.background = hgMaterials.scene
    delete hgMaterials.scene
    return;
  }
  if (hgMaterials[obj.uuid]) {
    obj.material = hgMaterials[obj.uuid]
    delete hgMaterials[obj.uuid]
  }
}

render() {
  scene.traverse(this.darkenNonBloomed) // 隐藏不需要辉光的物体
  bloomComposer.render()
  scene.traverse(this.restoreMaterial) // 还原

  // 更新性能插件
  stats.update();
  TWEEN.update();
  renderer.render(scene, camera);
  requestAnimationFrame(this.render);

  // 呼吸灯效果要放到最后渲染,要不然没效果
  if (composer) {
    composer.render();
  }
}

这样上面的飞线就会在辉光图层下显示出发光的效果了

添加地球光晕轮廓

这里我们就通过一个贴图简单的来搞了

  1. 准备一个光晕贴图

在这里插入图片描述

  1. 通过材质添加到刚才的earthGroup上
import earthApertureImage from './images/earth-aperture.png';

var texture = new THREE.TextureLoader().load( earthApertureImage );
var spriteMaterial = new THREE.SpriteMaterial( {
  map: texture,
  transparent: true,
  opacity: 0.5,
  depthWrite: false
} );
var sprite = new THREE.Sprite( spriteMaterial );
sprite.scale.set( 5 * 3, 5 * 3, 1 );
earthGroup.add( sprite );

在这里插入图片描述

TWEEN

TWEEN实现地球视图拉近
  1. scene视图加载时先将摄像头视图定位在远处
createCamera() {
  let element = document.getElementById("container");
  camera = new THREE.PerspectiveCamera(
    45,
    element.clientWidth / element.clientHeight,
    0.1,
    1000
  );
  camera.position.set( 16, 16, 160 ); // 设置相机方向
  // camera.position.set( 1.6, 1.6, 16 ); // 设置相机方向
  camera.lookAt(new THREE.Vector3(0, 0, 0)); // 设置相机方向
  scene.add(camera);
},
  1. 地球视图渲染完毕之后,再将camera和controls设置到实体近处,就能实现地球从远处由小变大的展示到眼前了
import { TWEEN } from "three/examples/jsm/libs/tween.module.min.js";

// 拉进摄像头
this.moveCamera(
  camera.position,
  controls.target,
  { x: 1.6, y: 1.6, z: 16 },
  { x: 0, y: 0.4, z: 0 },
  () => {
    initFlag = true
  }
)

// 移动摄像机
moveCamera(oldP, oldT, newP, newT, callback) {
  let tween = new TWEEN.Tween({
    x1: oldP.x,
    y1: oldP.y,
    z1: oldP.z,
    x2: oldT.x,
    y2: oldT.y,
    z2: oldT.z
  });
  tween.to(
    {
      x1: newP.x,
      y1: newP.y,
      z1: newP.z,
      x2: newT.x,
      y2: newT.y,
      z2: newT.z
    },
    1500
  );
  // 每一帧执行函数 、这个地方就是核心了、每变一帧跟新一次页面元素
  tween.onUpdate((object) => {
    camera.position.set(object.x1, object.y1, object.z1);
    controls.target.x = object.x2;
    controls.target.y = object.y2;
    controls.target.z = object.z2;
    controls.update();
  });

  // 动画完成后的执行函数
  tween.onComplete(() => {
    controls.enabled = true;
    callback && callback();
    // this.tweenCallBack && this.tweenCallBack();
  });

  tween.easing(TWEEN.Easing.Cubic.InOut);
  // 这个函数必须有、这个是启动函数、不加不能启动
  tween.start();
}

在这里插入图片描述

在这里插入图片描述

自转动画

启动地球自转效果
  1. 处理视图拉近之后才执行自转效果
let initFlag = false;

// 拉进摄像头
this.moveCamera(
  camera.position,
  controls.target,
  { x: 1.6, y: 1.6, z: 16 },
  { x: 0, y: 0.4, z: 0 },
  () => {
    initFlag = true
  }
);
  1. 通过动画不断改变y轴实现自转
render() {
  // 地球自转动画
  this.earthRotationAnimation();

  scene.traverse(this.darkenNonBloomed) // 隐藏不需要辉光的物体
  bloomComposer.render()
  scene.traverse(this.restoreMaterial) // 还原

  // 更新性能插件
  // stats.update();
  TWEEN.update();
  renderer.render(scene, camera);
  requestAnimationFrame(this.render);

  // 呼吸灯效果要放到最后渲染,要不然没效果
  if (composer) {
    composer.render();
  }
}

// 地球自转动画
earthRotationAnimation() {
  if (initFlag) {
    earthGroup.rotation.y = earthGroup.rotation.y + 0.002;
  }
},

PS:项目源码及3d模型会在第一篇文章给出下载地址,或者添加wx:z13964122832备注“全球图源码”

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

threejscesium项目实战

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值