实现模型贴图的移动缩放旋转

嵌入式驱动工程师跳槽涨薪难?四维定价模型帮你定位市场身价 在嵌入式开发领域,驱动工程师经常面临技术能力与薪资回报不匹配的困惑。公司内部的绩效评价看重稳定产出,而外部市场则依据稀缺位置定价,两者遵循完全不同的逻辑。理解技术护城河、产品关键路径、影响半径与可替代性这四个维度,是评估自身市场价值的基础。掌握系统级调试、量产稳定性优化等核心技能,并能在薪资谈判中清晰呈现项目对商业结果的影响,才能突破单纯执行层的定价天花板。本文拆解了驱动开发岗位背后的定价机制,帮助嵌入式工程师在跳槽与职业规划中找到高价值增长方向。 阅读详情

技术:threejs+canvas+fabric

效果图:

原理:threejs中没有局部贴图的效果,只能通过map 的方式贴到模型上,所以说换一种方式来实现,通过canvas+fabric来实现图片的移动缩放旋转,然后将整个画布以map 的形式放到模型材质上,实现局部贴图的效果

直接上代码:

<template>
    <div id="c-left">
      <input type="file" @change="handleFileChange" accept=".png" />
      <div id="container"></div>
    </div>
    <div id="c-right">
      <canvas id="canvas" width="512" height="512"></canvas>
    </div>
</template>
  
<script>
import { fabric } from 'fabric'
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
  


// oss上传相关配置
let OSS = require('ali-oss')
let client = new OSS({
    region: 'oss-cn-beijing',
    accessKeyId: 'xxxxx',
    accessKeySecret: 'xxxxx',
    bucket: 'xxxxx'
})

// 设置场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xfffff0);
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambientLight);
const dirLight1 = new THREE.DirectionalLight( 0xffffff, 2.5); 
dirLight1.position.set( 0, 0.5, 1 );
scene.add( dirLight1 );

const dirLight2 = new THREE.DirectionalLight( 0xffffff, 2.5); 
dirLight2.position.set( 0, 0.5, -1 );
scene.add( dirLight2 );

const dirLight3 = new THREE.DirectionalLight( 0xffffff, 2.5 );
dirLight3.position.set( 0, -0.5, 0 );
scene.add( dirLight3 );

const n = 2
// 设置视角
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth/n / window.innerHeight,
  0.1,
  1000
);
camera.position.set(0, 5, 10);
// 随机名称
function generateRandomFileName() {
  const date = new Date().toISOString().replace(/[-:.TZ]/g, '');
  const randomPart = Math.random().toString(36).substr(2, 6);
  return `${date}-${randomPart}`;
}

let selectedImage = null
export default {
  data(){
    return {
      canvas_s:null,
      image_url:null,
    }
  },
  methods:{
    async handleFileChange(event) {
      const file = event.target.files[0];
      if (!file || file.type!== 'image/png') {
          alert('请选择 PNG 格式的图片!');
          return;
      }
      const fileName = generateRandomFileName();
      await client.put(`m2_photos/${fileName}`, file);
      const url = client.signatureUrl(`m2_photos/${fileName}`);
      console.log("url为: ", url);
      this.image_url = url
    },
    init(){
      let flag = {x:false}; 
      // 创建渲染器
      const renderer = new THREE.WebGLRenderer({
          preserveDrawingBuffer: true,
          antialias: true,
      });
      const container = document.getElementById("container");
      container.appendChild(renderer.domElement);
      var s = new fabric.Canvas('canvas');
      s.backgroundColor = 'rgb(100, 255, 255)'; // 设置画布背景
      this.canvas_s = s
      // 创建轨道控制器
      const controls = new OrbitControls(camera, renderer.domElement);
      renderer.shadowMap.enabled = true;
      renderer.shadowMap.type = THREE.PCFSoftShadowMap;
      renderer.outputEncoding = THREE.sRGBEncoding;
      // 开启场景中的阴影贴图
      renderer.shadowMap.enabled = true;
      // 设置控制器阻尼,让控制器更有真实效果,必须在动画循环里调用.update()。
      controls.enableDamping = true;
      
      renderer.setSize(window.innerWidth/n, window.innerHeight);
      
      // 添加坐标系
      const axesHelper = new THREE.AxesHelper(10);
      scene.add(axesHelper);
      
      // 异步添加图片,能够实现图片的任意交互
      fabric.Image.fromURL('xxxxxxx', (oImg)=> {
          oImg.scale(0.1);
          var canvasWidth = s.width;
          var canvasHeight = s.height;
          // 计算图片放置在正中间的位置
          var left = canvasWidth / 2 ;
          var top = canvasHeight / 2 ;
          oImg.set({
              left: left - 80,  
              top: top -40  
          });
          console.log("oImg : ",oImg);
          s.add(oImg);
      }, {crossOrigin: 'anonymous'});

      // 定时任务
      setInterval(()=>{
        if (this.image_url) {
          fabric.Image.fromURL(this.image_url, (oImg)=> {
            oImg.scale(0.1);
            var canvasWidth = s.width;
            var canvasHeight = s.height;
            // 计算图片放置在正中间的位置
            var left = canvasWidth / 2 ;
            var top = canvasHeight / 2 ;
            oImg.set({
                left: left - 80,  
                top: top -40  
            });
            console.log("oImg : ",oImg);
            s.add(oImg);
          }, {crossOrigin: 'anonymous'});
          this.image_url = null
        }
      },1000)

      var texture = new THREE.Texture(document.getElementById("canvas"));
      texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
      const mapTexture = new THREE.TextureLoader().load('/statisc/fabric004.png')
      const loader = new OBJLoader();
      loader.load('模型的位置', (object) => {
          object.traverse((child) => {
              child.material = new THREE.MeshLambertMaterial({ 
                  color:0xffffff,
                  side:THREE.DoubleSide,
                  // transparent:false,
                  // opacity:1,
                  bumpMap:mapTexture,
                  // alphaMap:mapTexture,
                  bumpScale:1,
                  // emissive:0x404040
              });
              child.material.map = texture;
              child.material.map.minFilter = THREE.LinearFilter
              child.material.map.colorSpace = 'srgb'
              console.log("map",child.material.map);
          });
          object.scale.set(0.1, 0.1, 0.1); // 变小一点
          object.position.set(0, -10, 0)
          scene.add(object);
          
          // 新增:为模型添加点击事件监听
          renderer.domElement.addEventListener('click', onModelClick);
      }, () => {
      }, () => {
      });

      // 按键设置
      document.addEventListener('keydown',function (event) {
        if (flag.x) {
          if (event.key === 's') {
            selectedImage.top += 5;
          }else if(event.key === 'a'){
            selectedImage.left -= 5;
          }else if( event.key === 'd'){
            selectedImage.left += 5;
          }else if(event.key === 'w'){
            selectedImage.top -= 5;
          }else if(event.key === 'q'){
            selectedImage.angle -= 5
          }else if(event.key === 'e'){
            selectedImage.angle += 5
          }else if(event.key === '6'){
            selectedImage.scaleX += 0.01
          }else if(event.key === '4'){
            selectedImage.scaleX -= 0.01
          }else if(event.key === '2'){
            selectedImage.scaleY += 0.01
          }else if(event.key === '8'){
            selectedImage.scaleY -= 0.01
          }else if(event.key === '3'){
            selectedImage.scaleY += 0.01
            selectedImage.scaleX += 0.01
          }else if(event.key === '7'){
            selectedImage.scaleY -= 0.01
            selectedImage.scaleX -= 0.01
          }else if(event.key === 'Backspace'){
            s.remove(selectedImage)
          }else if(event.key === 'ArrowUp'){
            s.bringForward(selectedImage)
          }else if(event.key === 'ArrowDown'){
            s.sendBackwards(selectedImage)
          }
          s.renderAll();
        }
      })
      
      const geometry = new THREE.BoxGeometry(1, 1, 1);
      const material = new THREE.MeshBasicMaterial({ map:texture });
      const cube = new THREE.Mesh(geometry, material);
      scene.add(cube);
      
      
      function render() {
          controls.update();
          texture.needsUpdate = true
          renderer.render(scene, camera);
          // 渲染下一帧的时候就会调用render函数
          requestAnimationFrame(render);
      }
      
      render();
      
      var raycaster = new THREE.Raycaster();
      var mouse = new THREE.Vector2();

      // 鼠标点击事件
      function onModelClick(event) {  
        flag.x = false
        event.preventDefault();
        // pos 在场景图像上的位置
        var pos = [event.clientX,event.clientY]
        var rect = container.getBoundingClientRect();
        mouse.x = ((pos[0] - rect.left) / rect.width) *2-1
        mouse.y = -((pos[1] - rect.top) / rect.height) *2+1
        raycaster.setFromCamera(mouse, camera);
        // 通过射线获得场景中的对象
        var intersects = raycaster.intersectObjects(scene.children);
        if (intersects.length > 0 && intersects[0].uv) {
          var uv = intersects[0].uv;
          intersects[0].object.material.map.transformUv(uv)
          // 512表示画布的宽和高都是512
          var x = Math.round(uv.x * rect.width/(1+0.002*(rect.width-512))); 
          var y = Math.round(uv.y * rect.height/(1+0.002*(rect.height-512)));
          const positionOnScene = {x,y}
          selectCanvas(positionOnScene,flag)
        }
        if (!flag.x) {
          s.discardActiveObject();
          s.renderAll();
        }
      }
      // 选中模型中的图片
      function selectCanvas(point,flag) {
        const objects = s.getObjects();
        for (let i = objects.length - 1; i >= 0; i--) {
          const obj = objects[i];
          if (obj.containsPoint(point)) {
            s.setActiveObject(obj);  // 设置图形为选中状态
            flag.x = true;  // 标记有图形被选中
            selectedImage = obj
            s.renderAll();
            break; 
          }
        }
      }
    }
  },
  mounted() {
    this.init();
  },
}
  
  
</script>
  
<style>
#c-left, #c-right {
position: relative;
display: inline-block;
height: 100%;
width: 50%;
}

#c-right {
float: right;
/* display: none; */
}
</style>

我是使用的vue3,同时还包含了oss的图片上传功能以及threejs 的反射效果,当点击模型上的图片时,即可选中图片,并通过wasd移动图片位置,qe旋转,123456789各个位置的缩放,还是很有趣的~

Three 之 three.js (webgl)shader 中 Texture 贴图 uv 坐标的相关简单说明,并简单测试 UV 重复旋转偏移效果 纹理坐标系统 uv其实就是纹理坐标,因为xyz已经被顶点坐标占用了,所以uvw就用来表示纹理坐标。它时候贴图影射到模型表面的依据,把表面的点与平面上的像素对应起来,一般取值在0~1; u:图片在显示器水平的坐标 v:垂直方向 w:垂直于显示器表面 一般情况只是在表面贴图,就涉及不到w,所以常称为uv。 ThreeJS纹理贴图 使用纹理对象贴图 ThreeJS本身做了封装,贴图十分方便,如果大家英文好可以直接去官网,嫌麻烦也可以在中文网站上直接查看教程或对象方法。(⚠️以下非完整代码) ... 阅读详情

相关推荐

OpenGL校园三维漫游程序:键盘鼠标交互+纹理灯光+一键运行

直接双击就能跑的OpenGL三维校园场景,包含教学楼、房屋、围墙、树木、草坪、小路、天空和室内空间,所有模型都用C++手写顶点数据构建,不依赖外部建模软件。每个物体都带真实感纹理贴图,配合动态光源实现明暗过渡与阴影效果。视角控制支持WASD或方向键前后左右移动、鼠标左键拖拽旋转视角、滚轮缩放、右键平移,操作响应灵敏,适合边走边看的沉浸式浏览。项目编译后生成OPENGL1.exe,配套glut32.dll,Windows下无需安装OpenGL环境或额外配置,插上U盘就能演示。源码仅一个opengl.CPP文件

q5r6s7的博客 238

threejs(纹理贴图)

threejs

sqbcsdn的博客 833

vivado实现SDI接口

XAPP1249,用来介绍UHD_SDI的实现。

Huskar_Liu的博客 7076

ThreeJs给物体添加贴图

之前有讲过在threejs场景中绘制一个正方体但是绘制的正方体只能添加一种颜色,导致六个面都是纯色的,那么今天讲怎么给正方体的面添加图片,让绘制的会提更加生动,这里就直接拿之前用过的雨点图片来实践,可能样式会不好看,实际应用中大家选择自己需要的样式图片就可以了。贴图功能顾名思义就是将图片贴在物体上的效果。需要先加载贴图对象...

Baker的博客 1943

UFS Link Startup全流程解析:从硬件初始化到链路稳定的5个关键阶段

本文详细解析了UFS Link Startup的全流程,从硬件初始化到链路稳定的5个关键阶段,包括初始化阶段、数据通道发现、数据通道重新对齐、链路启动收尾和能力交换阶段。通过时序分析、代码实现和实际案例,帮助开发者掌握UFS协议链路启动的技术细节,确保设备与主机之间的可靠通信。

weixin_30687051的博客 430

Qt·DBus快速入门

DBus快速入门

m0_64560763的博客 2273

Three.js杂记(十)——贴图

纹理贴图贴图介绍简单的纹理贴图 贴图介绍 纹理贴图是Threejs一个很重要的内容,游戏、产品720展示、物联网3D可视化等项目程序员加载模型的同时同时需要处理纹理贴图。 简单的纹理贴图

苦作舟的空城机 9678

Kill5.37版本下,使用ARMcomplier6编译__asm 函数报错的问题

Kill5-5.37版本下,使用ARMcomplier6编译__asm 函数报错的问题 问题简要描述: 昨日更新了keil5软件的版本,从5.17更新到了5.37版本,然后编译STM32项目文件时,软件提示编译报错。报错问题如下: Build started: Project: QKsoftware *** Using Compiler 'V6.18', folder: 'E:\MDK-ARM\ARM\ARM\ARMCLANG\Bin' Build target 'QKsoftware' compiling

weixin_43644424的博客 1万+

Threejs贴图材质翻转问题

我们导入一个模型重新赋值一个材质图片,会出现材质上下翻转问题

qq_33298964的博客 1857

TCP报文 Flood攻击原理与防御方式

TCP交互过程中包含SYN、SYN-ACK、ACK、FIN和RST报文,这几类报文也可能会被攻击者利用,海量的攻击报文会导致被攻击目标系统资源耗尽、网络拥塞,无法正常提供服务。接下来我们介绍几种常见的Flood攻击的原理和防御方式。

qq_32044265的博客 4219

PLC的自抗扰控制(ADRC)算法

1、自抗扰控制算法,网上很多文章有所讲解,大家也可以关注韩京清教授的论文和书籍,这里就不赘述了,下面给出梯形图+SCL代码。 1、跟踪微分器 2、线性控制率 3、博图SCL控制代码 4、ADRC控制算法的所有公式汇总 ......

RXXW_Dor的博客 3853

UNITY小白3D坦克大战制作学习笔记1

练习随笔 1.点击模型在弹出的Import Settings 中设置Scale Factor的值,可进行对模型的缩放。 2.贴图可直接托入材质球。

weixin_44587554的博客 1096

使用Python爬虫抓取旅游景点评价数据并进行热度可视化

景点热度通常由景点的评分、评论数以及游客的热情度来决定。高热度的景点通常意味着更高的游客评分和更多的游客评论。热度不仅反映了景点的受欢迎程度,还可以揭示某些景点的潜力和存在的问题。评分:游客对景点的综合评分,通常是0到5分。评论数:景点的评论数量,反映了游客对景点的关注程度。热度变化:在一定时间内,景点的评价和评论数量变化,能够反映景点的热度波动。通过使用Python爬虫技术抓取旅游景点的评价和评论数据,我们能够深入分析景点的热度,并通过数据可视化工具将这些信息展示给用户。

2201_76125261的博客 1282

3D Face HRN开发者案例:将3D Face HRN嵌入微信小程序实现H5端3D建模

本文介绍了如何在星图GPU平台自动化部署3D Face HRN人脸重建模型,并将其集成到微信小程序中实现H5端3D人脸建模。该方案让用户仅需上传一张照片即可快速生成高质量3D人脸模型,适用于虚拟试妆、游戏角色创建等社交娱乐场景,大大降低了移动端3D重建的技术门槛。

weixin_31749299的博客 108

微信小程序 - 解决支付报错 requestPayment:fail access denied (微信支付提示错误requestPayment:拒绝访问失败,真机运行测试或打包编译上线后报错问题)

微信小程序,微信支付,wx.requestPayment,微信小程序支付报错requestPayment:fail access denied,调不起微信,无法唤起支付弹窗,支付失败,支付权限没问题,小程序上线后支付报错,小程序真机测试支付报错,微信小程序苹果ios系统手机无法支付,小程序发布上线后支付报错,本地测试没问题,拒绝访问失败,小程序调起支付API requestPayment:fail:access denied微信开发者工具正常支付,权限没问题,微信公众平台,依旧报错怎么办,检查了appid都

🏆 前端领域优质创作者 1万+

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

VibeCoding 能做到什么程度?我用它做了一座 3D 数字博物馆

当 VibeCoding 不再只是生成简单的网页,而是开始构建完整的 3D 应用,会发生什么?这一次,我通过纯 VibeCoding 的方式,从一句句自然语言描述出发,完成了一座中国古文物数字博物馆。三维展厅、15 件文物、沉浸式交互、热点讲解、相机漫游以及移动端适配,全部由 AI 协助完成。本文将分享这次从 Prompt 到完整 Web 3D 项目的实践过程,以及 VibeCoding 如何改变传统开发方式。

ZHANG_666 320

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

three.js

three.js学习笔记

qq_44331582的博客 252

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

VR开发入门:3D图像的处理过程

https://www.fgba.net/sitemap.xml

august5291的博客 1060

Open3D电商展示终极指南:如何用3D模型提升商品转化率500%

在当今电商竞争激烈的市场中,**3D商品展示**已成为提升转化率的关键技术。Open3D作为一款现代化的3D数据处理库,为电商企业提供了强大的3D模型处理和可视化解决方案。通过Open3D,商家可以创建逼真的3D商品展示,让消费者360度查看产品细节,显著提升购物体验和购买决策速度。本文将为您详细介绍如何利用Open3D实现高效的3D电商展示。 ## 🚀 为什么3D展示能提升电商转化率? 传

gitblog_00004的博客 361

【实现100个unity特效之28】unity中使用ShaderGraph实现一个贴图UV循环移动滚动的指示效果

本文详细介绍了在Unity中实现无限滚动纹理效果的完整教程。通过下载图片资源、配置纹理参数、创建透明无光照ShaderGraph,并添加时间节点控制UV坐标滚动,实现了基础滚动效果。随后逐步添加了旋转控制、缩放调节、颜色变换等功能,并演示了在线渲染器上的应用。最后针对灰色背景问题提供了Alpha Clipping解决方案,推荐阈值设为0.1。教程包含详细的节点连接图和效果演示动图,适合Unity开发者学习ShaderGraph的基础应用和特效实现。

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 2266

opengl-第8章:移动,缩放,旋转

新版本1.8的特性 1. 加入了复合模型Model对象 模型对象直接拥有贴图,绘制数据,Shader复合而成 现在定义的模型可以直接指定贴图,数据,并且有默认的shader,同时添加了两个函数 initAppendOp:指定初始化的一些操作 drawAppendOp:在渲染过程中的一些操作 另外,Model对象直接提供了move,scale,rotate操作 Transform操作 Transform矩阵 在opengl里面,使用Transform矩阵进行变换 所谓的Transform矩阵就是一个4

qq_37080133的博客 1420

Unity&Shader案例篇—旋转、平移和缩放

一、前言      在Unity中通过控制物体的Transform可以很容易的对物体进行旋转、平移和缩放,得到一些简单的动画效果。但是有个不好的地方是,一旦这个物体上有碰撞体的话,会跟环境发生碰撞。我就就想能不能在不麻烦特效动画那边的情况下,自己用Shader去处理一些基于模型顶点或贴图的dong动画效果。如图所示是我完全使用Shader处理处理的顶点的xuan旋转、平移和缩放,另外还有两个是贴...

Just do IT 1万+

【sketchup 2021】草图大师的高级工具使用2【材质贴图应用的基础功能和高级使用与实战演练(给地砖调整尺寸、转贴贴图圆柱为例、投影贴图百叶窗为例】

文章目录不透明度、填充材质材质贴图应用基础功能高级技巧别针蓝色别针【平行四边形别针】红色别针【移动别针】黄色别针【梯形别针】绿色别针【旋转缩放别针】别针位置调整重设镜像设置旋转设置固定图钉隐藏预设图标修改一个面后,其他面同步这个材质给群组或组件调整纹理位置贴图材质远处模糊的设置贴图示例显示屏加图像给窗户添加样式贴图实战演练给户型贴固定尺寸地砖转角贴图【给圆柱贴图为例】投影贴图【给百叶窗贴图为例】山体与全景天、 图层工具、标记管理器、视图与样式截面工具、场景工具、动画播放、相机工具 不透明度、填充材质 因

崔崇鑫的博客,你linux/云运维工作中的百科全书。 8633

h5-Dooring移动端手势支持:滑动、缩放与旋转实现

在移动端H5页面编辑场景中,用户常常面临三大交互难题:内容切换不流畅、元素缩放精度不足、旋转操作体验割裂。h5-Dooring通过深度整合滑动切换(Swipe)、双指缩放(Pinch)和自由旋转(Rotate)三大核心手势,构建了一套完整的移动端交互解决方案。本文将从实现原理、核心代码到性能优化,全面解析这些手势功能的技术细节。 ## 手势系统架构设计 h5-Dooring的手势系统采用分层设计...

gitblog_00187的博客 424

UnityShader学习教程之<实现纹理的缩放平移和旋转,以及用c#代码合并两种贴图并且控制位置>

首先,我们先实现用代码在shader里面实现缩放,平移和旋转! 上代码,首先是我们的shader: Shader "Unlit/贴图的平移缩放" { Properties { _MainTex("Texture", 2D) = "white" {} _UA("旋转中点x",Float) = 0.5 _UB("旋转中点y",Float) = 0.5 _CenterX("平移x",f......

xiongwen_li的博客 1万+

二维平面几何变换(平移、旋转、缩放)实现

本文介绍了基于MFC框架的图形绘制与几何变换实验。实验通过面向对象设计封装图形基类,实现点、线、矩形等基本图形的绘制功能,并支持平移、缩放和旋转三种几何变换操作。详细阐述了图形变换的数学原理,包括平移公式、缩放公式和旋转公式的计算方法。实验采用MFC单文档工程,通过视图类管理图形数组,处理鼠标交互事件,实现图形创建和变换功能。文章还总结了实验过程中的问题解决经验,并提出了功能扩展建议,如增加填充效果、支持三维变换和数据导出等。

因趣斯听—博客 792
上一篇: 微信小程序 canvas 处理图片的缩放移动旋转问题
下一篇: go使用grpc编辑器 windows
天空原海
博客等级 码龄3年 1785粉丝 54原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值