素材:

1.CANNON.js文档地址

schteppe/cannon.js @ GitHubhttps://schteppe.github.io/cannon.js/
cannonhttp://schteppe.github.io/cannon.js/docs/

2.CANNON-ES.js文档地址

cannon-esDocumentation for cannon-eshttps://pmndrs.github.io/cannon-es/docs/modules.html

初始代码

import "./style.css";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as dat from "dat.gui";import ky from "kyouka";
import { World } from "cannon-es";
import { Mesh } from "three";/*** Debug*/
const gui = new dat.GUI();/*** Base*/
// Canvas
const canvas = document.querySelector("canvas.webgl");
const textureLoader=new THREE.TextureLoader()// Scene
const scene = new THREE.Scene();
/*** Light*/const ambientLight = new THREE.AmbientLight(0xffffff, 0.7);scene.add(ambientLight);const directionalLight = new THREE.DirectionalLight(0xffffff, 0.2);directionalLight.castShadow = true;directionalLight.shadow.mapSize.set(1024, 1024);directionalLight.shadow.camera.far = 15;directionalLight.shadow.camera.left = -7;directionalLight.shadow.camera.top = 7;directionalLight.shadow.camera.right = 7;directionalLight.shadow.camera.bottom = -7;directionalLight.position.set(5, 5, 5);scene.add(directionalLight);
/*** Objects*/
const sphere=new Mesh(new THREE.SphereBufferGeometry(0.5,20,20),new THREE.MeshStandardMaterial({metalness:0.3,roughness:0.4})
)
sphere.position.y=0.5
sphere.castShadow=true
scene.add(sphere)
const floor=new Mesh(new THREE.PlaneBufferGeometry(10,10),new THREE.MeshStandardMaterial({color: "#777777",metalness: 0.3,roughness: 0.4,})
)
floor.castShadow=true
floor.receiveShadow = true;
floor.rotation.x = -Math.PI * 0.5;
scene.add(floor)
/*** Sizes*/
const sizes = {width: window.innerWidth,height: window.innerHeight,
};window.addEventListener("resize", () => {// Update sizessizes.width = window.innerWidth;sizes.height = window.innerHeight;// Update cameracamera.aspect = sizes.width / sizes.height;camera.updateProjectionMatrix();// Update rendererrenderer.setSize(sizes.width, sizes.height);renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});/*** Camera*/
// Base camera
const camera = new THREE.PerspectiveCamera(75,sizes.width / sizes.height,0.1,100
);
camera.position.set(-3, 3, 3);
scene.add(camera);// Controls
const controls = new OrbitControls(camera, canvas);
controls.enableDamping = true;/*** Renderer*/
const renderer = new THREE.WebGLRenderer({canvas: canvas,
});
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));renderer.shadowMap.enabled=true
// sphereBody.applyLocalForce(new CANNON.Vec3(150, 0, 0));const clock = new THREE.Clock();const tick = () => {const elapsedTime = clock.getElapsedTime();// Update controlscontrols.update();// Renderrenderer.render(scene, camera);// Call tick again on the next framewindow.requestAnimationFrame(tick);
};tick();

1.创建初始化CANNON环境

1) 创建世界

World | cannon-es

gravity gravity: Vec3

Defined in world/World.ts:80

The gravity of the world.

世界的引力。

const world=new CANNON.World()    //创建 cannon世界
world.gravity.set(0,-9.82,0)    //设置重力方向

2) 创建CANNON 球体

Body | cannon-es

const sphereBody=new CANNON.Body({mass:1,position:new CANNON.Vec3(0,3,0),shape:new CANNON.Sphere(0.5)
})

3) 将世界中加入物体

world.addBody(sphere)

4) 设置步进时间

 world.step(1/60,deltaTime,3)

wold.step

step step(dt: number, timeSinceLastCalled?: number, maxSubSteps?: number): void Defined in world/World.ts:470 Step the physics world forward in time. There are two modes. The simple mode is fixed timestepping without interpolation. In this case you only use the first argument. The second case uses interpolation. In that you also provide the time since the function was last used, as well as the maximum fixed timesteps to take.

步骤 step(dt: number,timeSinceLastCalled?:number,maxSubSteps?:编号):

无效 在world/World.ts:470中定义 让物理世界及时向前迈进。 有两种模式。简单模式是无插值的固定时间步长。在这种情况下,您只需使用第一个参数。第二种情况使用插值。因为您还提供了自上次使用该函数以来的时间,以及要使用的最大固定时间步长。

Parameters dt: number The fixed time step size to use. Optional timeSinceLastCalled: number The time elapsed since the function was last called. maxSubSteps: number = 10 Maximum number of fixed steps to take per function call (default: 10). Returns void

dt:        数字 要使用的固定时间步长。
timesinclastcalled:number 自上次调用该函数以来经过的时间。
maxSubSteps: number = 10 每次函数调用要执行的最大固定步骤数(默认值:10)。 返回void

5) 绑定 THREEjs CANNONjs 两个球体位置关联

sphere.position.copy(sphereBody.position)
const scene = new THREE.Scene();
const world=new CANNON.World()
world.gravity.set(0,-9.82,0)    //设置重力方向
const sphereBody=new CANNON.Body({mass:1,position:new CANNON.Vec3(0,3,0),shape:new CANNON.Sphere(0.5)
})
world.addBody(sphereBody)
...
...
...
const tick = () => {const elapsedTime = clock.getElapsedTime();const deltaTime=elapsedTime-oldTime;world.step(1/60,deltaTime,3)sphere.position.copy(sphereBody.position)// Update controlscontrols.update();// Renderrenderer.render(scene, camera);// Call tick again on the next framewindow.requestAnimationFrame(tick);
};

6)创建平台

const floorBody=new CANNON.Body({mass:0,shape:new CANNON.Plane()
})
world.addBody(floorBody)

问题发现小球方向不对了,原因是 CANNON的平台方向不对

6) 反转平台

Quaternion | cannon-es

const floorBody=new CANNON.Body({mass:0,shape:new CANNON.Plane()
})
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(-1,0,0),Math.PI*0.5
)
world.addBody(floorBody)

效果:

7)给小球和平台添加材质

Material | cannon-es

const defaultMat=new CANNON.Material('default')
const defaultContactMaterial=new CANNON.ContactMaterial(defaultMat,defaultMat,{friction:0.6,restitution:0.3
})
world.addContactMaterial(defaultContactMaterial)

给小球和平台也加上材质

const sphereBody=new CANNON.Body({mass:1,position:new CANNON.Vec3(0,3,0),shape:new CANNON.Sphere(0.5),material:defaultContactMaterial
})
world.addBody(sphereBody)const floorBody=new CANNON.Body({mass:0,shape:new CANNON.Plane(),material:defaultContactMaterial
})
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(-1,0,0),Math.PI*0.5)
world.addBody(floorBody)
/**

效果:

8)给一个小球一个方向力

Body | cannon-es

const sphereBody=new CANNON.Body({mass:1,position:new CANNON.Vec3(0,3,0),shape:new CANNON.Sphere(0.5),material:defaultContactMaterial
})
sphereBody.applyLocalForce(new Vec3(150,0,0))
world.addBody(sphereBody)

2.优化创建代码

let objsToUpdate=[]
const sphere=new THREE.SphereBufferGeometry(1,20,20)const sphereMateral=new THREE.MeshStandardMaterial({roughness:0.4,metalness:0.3,})
const createSphere=(r,ps)=>{const mesh=new Mesh(sphere,sphereMateral)mesh.castShadow=truemesh.position.copy(ps)scene.add(mesh)const body=new CANNON.Body({mass:1,position:new Vec3().copy(ps),shape:new Sphere(r),material:defaultContactMaterial})objsToUpdate.push({mesh,body})world.addBody(body)
}
createSphere(0.5,{x:0,y:3,z:0})...
...
...const tick = () => {const elapsedTime = clock.getElapsedTime();const deltaTime=elapsedTime-oldTime;objsToUpdate.forEach(obj=>{obj.mesh.quaternion.copy(obj.body.quaternion)obj.mesh.position.copy(obj.body.position)})world.step(1/60,deltaTime,3)// Update controlscontrols.update();// Renderrenderer.render(scene, camera);// Call tick again on the next framewindow.requestAnimationFrame(tick);
};

效果:

问题 太大了且穿模

2.1) 设置缩放

  mesh.scale.set(r,r,r)//完整部分
const createSphere=(r,ps)=>{const mesh=new Mesh(sphere,sphereMateral)mesh.scale.set(r,r,r)mesh.castShadow=truemesh.position.copy(ps)scene.add(mesh)const body=new CANNON.Body({mass:1,position:new Vec3().copy(ps),shape:new Sphere(r),material:defaultContactMaterial})objsToUpdate.push({mesh,body})world.addBody(body)
}

效果:

3.使用gui 调式创建多个球体

const guiObj={}
guiObj.createSphere=()=>{createSphere(Math.random()*0.5,{x:(Math.random()-0.5)*3,y:3,z:(Math.random()-0.5)*3})
}
gui.add(guiObj,'createSphere')

效果:

3.创建方块

注意 CANNON Box 需要的 是

const box=new THREE.BoxBufferGeometry(1,1,1)
const boxMaterial=new THREE.MeshStandardMaterial({roughness:0.4,metalness:0.3,
})
const createBox=(width,height,depth,position)=>{const mesh=new Mesh(box,boxMaterial)mesh.scale.set(width,height,depth)mesh.castShadow=truemesh.position.copy(position)const body=new CANNON.Body({mass:1,position:new Vec3().copy(position),shape:new CANNON.Box(new CANNON.Vec3(width,height,depth)),material:defaultContactMaterial})objsToUpdate.push({mesh,body})scene.add(mesh)world.addBody(body)
}...
...
...
guiObj.createBox=()=>{createBox(Math.random()*0.5,Math.random()*0.5,Math.random()*0.5,{x:(Math.random()-0.5)*3,y:3,z:(Math.random()-0.5)*3})
}
gui.add(guiObj,'createBox')

效果:

4.碰撞检测性能优化

1.粗测阶段(BroadPhase)

cannon.js会一直测试物体是否与其他物体发生碰撞,这非常消耗CPU性能,这一步成为BroadPhase。当然我们可以选择不同的BroadPhase来更好的提升性能。
NaiveBroadphase(默认) —— 测试所有的刚体相互间的碰撞。
GridBroadphase —— 使用四边形栅格覆盖world,仅针对同一栅格或相邻栅格中的其他刚体进行碰撞测试。
SAPBroadphase(Sweep And Prune) —— 在多个步骤的任意轴上测试刚体。
默认broadphase为NaiveBroadphase,建议切换到SAPBroadphase。
当然如果物体移动速度非常快,最后还是会产生一些bug。
切换到SAPBroadphase只需如下代码

world.broadphase=new CANNON.SAPBroadphase(world)

2.睡眠Sleep

虽然我们使用改进的BroadPhase算法,但所有物体还是都要经过测试,即便是那些不再移动的刚体。
因此我们需要当刚体移动非常非常缓慢以至于看不出其有在移动时,我们说这个刚体进入睡眠,除非有一股力施加在刚体上来唤醒它使其开始移动,否则我们不会进行测试。
只需以下一行代码即可

world.allowSleep=true

当然我们也可以通过Body的sleepSpeedLimit属性或sleepTimeLimit属性来设置刚体进入睡眠模式的条件。
sleepSpeedLimit ——如果速度小于此值,则刚体被视为进入睡眠状态。
sleepTimeLimit —— 如果刚体在这几秒钟内一直处于沉睡,则视为处于睡眠状态。

5.添加碰撞音效

通过collide 来监听 碰撞事件

const audio=new Audio("sounds/hit.mp3")
const playHitSound=()=>{audio.play()
}const createBox=(width,height,depth,position)=>{const mesh=new Mesh(box,boxMaterial)mesh.scale.set(width,height,depth)mesh.castShadow=truemesh.position.copy(position)const body=new CANNON.Body({mass:1,position:new Vec3().copy(position),shape:new CANNON.Box(new CANNON.Vec3(width,height,depth)),material:defaultContactMaterial})body.addEventListener("collide",playHitSound)objsToUpdate.push({mesh,body})scene.add(mesh)world.addBody(body)
}

问题 这样创建的声音很违和都是一样的,

需求力度大的时候有声音

优化音效

碰撞强度可以通过contact属性中的getImpactVelocityAlongNormal()方法获取到

因此我们只要当碰撞强度大于某个值时再触发音效就行了

const playHitSound=(collision)=>{if(collision.contact.getImpactVelocityAlongNormal()>1.5){hitSound.currentTime=0;hitSound.volume=Math.random()hitSound.play();}
}

Rec 0003

5.重置画布

guiObj.restCanvas=()=>{objsToUpdate.forEach(obj=>{scene.remove(obj.mesh)world.removeBody(obj.body)obj.body.removeEventListener("collide",()=>playHitSound())})
}gui.add(guiObj,'restCanvas')

效果:

6.Web Worker

由于JavaScript是单线程模型,即所有任务只能在同一个线程上面完成,前面的任务没有做完,后面的就只能等待,这对于日益增强的计算能力来说不是一件好事。所以在HTML5中引入了Web Worker的概念,来为JavaScript创建多线程环境,将其中一些任务分配给Web Worker运行,二者可以同时运行,互不干扰。Web Worker是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能。

在计算机中做物理运算的是CPU,负责WebGL图形渲染的是GPU。现在我们的所有事情都是在CPU中的同一个线程完成的,所以该线程可能很快就过载,而解决方案就是使用worker。
我们通常把进行物理计算的部分放到worker里面,具体可看这个例子的源码

cannon.js worker example

7.本期完整代码:

import "./style.css";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as dat from "dat.gui";
import * as CANNON from 'cannon-es'
import ky from "kyouka";
import { Mesh } from "three";
import { Sphere, Vec3 } from "cannon-es";/*** Debug*/
const gui = new dat.GUI();
const hitSound=new Audio("sounds/hit.mp3")
const playHitSound=(collision)=>{if(collision.contact.getImpactVelocityAlongNormal()>1.5){hitSound.currentTime=0;hitSound.volume=Math.random()hitSound.play();}
}
/*** Base*/
// Canvas
const canvas = document.querySelector("canvas.webgl");
const textureLoader=new THREE.TextureLoader()
let objsToUpdate=[]
// Scene
const scene = new THREE.Scene();
const world=new CANNON.World()
world.gravity.set(0,-9.82,0)    //设置重力方向
const defaultMat=new CANNON.Material('default')
const defaultContactMaterial=new CANNON.ContactMaterial(defaultMat,defaultMat,{friction:0.1,restitution:0.6
})
world.addContactMaterial(defaultContactMaterial)world.broadphase=new CANNON.SAPBroadphase(world)
world.allowSleep=true
const floorBody=new CANNON.Body({mass:0,shape:new CANNON.Plane(),material:defaultContactMaterial
})
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(-1,0,0),Math.PI*0.5)
world.addBody(floorBody)
/*** Light*/const ambientLight = new THREE.AmbientLight(0xffffff, 0.7);scene.add(ambientLight);const directionalLight = new THREE.DirectionalLight(0xffffff, 0.2);directionalLight.castShadow = true;directionalLight.shadow.mapSize.set(1024, 1024);directionalLight.shadow.camera.far = 15;directionalLight.shadow.camera.left = -7;directionalLight.shadow.camera.top = 7;directionalLight.shadow.camera.right = 7;directionalLight.shadow.camera.bottom = -7;directionalLight.position.set(5, 5, 5);scene.add(directionalLight);
/*** Objects*/const sphere=new THREE.SphereBufferGeometry(1,20,20)const sphereMateral=new THREE.MeshStandardMaterial({roughness:0.4,metalness:0.3,})const box=new THREE.BoxBufferGeometry(1,1,1)const boxMaterial=new THREE.MeshStandardMaterial({roughness:0.4,metalness:0.3,
})
const createBox=(width,height,depth,position)=>{const mesh=new Mesh(box,boxMaterial)mesh.scale.set(width,height,depth)mesh.castShadow=truemesh.position.copy(position)const body=new CANNON.Body({mass:1,position:new Vec3().copy(position),shape:new CANNON.Box(new CANNON.Vec3(width,height,depth)),material:defaultContactMaterial})body.addEventListener("collide",playHitSound)objsToUpdate.push({mesh,body})scene.add(mesh)world.addBody(body)
}
const createSphere=(r,ps)=>{const mesh=new Mesh(sphere,sphereMateral)mesh.scale.set(r,r,r)mesh.castShadow=truemesh.position.copy(ps)scene.add(mesh)const body=new CANNON.Body({mass:1,position:new Vec3().copy(ps),shape:new Sphere(r),material:defaultContactMaterial})objsToUpdate.push({mesh,body})world.addBody(body)
}
createSphere(0.5,{x:0,y:3,z:0})
const floor=new Mesh(new THREE.PlaneBufferGeometry(10,10),new THREE.MeshStandardMaterial({color: "#777777",metalness: 0.3,roughness: 0.4,})
)
floor.castShadow=true
floor.receiveShadow = true;
floor.rotation.x = -Math.PI * 0.5;
scene.add(floor)
/*** Sizes*/
const sizes = {width: window.innerWidth,height: window.innerHeight,
};window.addEventListener("resize", () => {// Update sizessizes.width = window.innerWidth;sizes.height = window.innerHeight;// Update cameracamera.aspect = sizes.width / sizes.height;camera.updateProjectionMatrix();// Update rendererrenderer.setSize(sizes.width, sizes.height);renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});/*** Camera*/
// Base camera
const camera = new THREE.PerspectiveCamera(75,sizes.width / sizes.height,0.1,100
);
camera.position.set(-3, 3, 3);
scene.add(camera);// Controls
const controls = new OrbitControls(camera, canvas);
controls.enableDamping = true;/*** Renderer*/
const renderer = new THREE.WebGLRenderer({canvas: canvas,
});
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));renderer.shadowMap.enabled=true
// sphereBody.applyLocalForce(new CANNON.Vec3(150, 0, 0));const clock = new THREE.Clock();
const oldTime=0;
const tick = () => {const elapsedTime = clock.getElapsedTime();const deltaTime=elapsedTime-oldTime;objsToUpdate.forEach(obj=>{obj.mesh.quaternion.copy(obj.body.quaternion)obj.mesh.position.copy(obj.body.position)})world.step(1/60,deltaTime,3)// Update controlscontrols.update();// Renderrenderer.render(scene, camera);// Call tick again on the next framewindow.requestAnimationFrame(tick);
};tick();const guiObj={}
guiObj.createSphere=()=>{createSphere(Math.random()*0.5,{x:(Math.random()-0.5)*3,y:3,z:(Math.random()-0.5)*3})
}
guiObj.createBox=()=>{createBox(Math.random()*0.5,Math.random()*0.5,Math.random()*0.5,{x:(Math.random()-0.5)*3,y:3,z:(Math.random()-0.5)*3})
}
guiObj.restCanvas=()=>{objsToUpdate.forEach(obj=>{scene.remove(obj.mesh)world.removeBody(obj.body)obj.body.removeEventListener("collide",()=>playHitSound())})
}
gui.add(guiObj,'createSphere')
gui.add(guiObj,'createBox')
gui.add(guiObj,'restCanvas')

ThreeJs 学习之旅(十六)—Physics(物理)相关推荐

  1. 【Unity 3D】学习笔记三十六:物理引擎——刚体

    物理引擎就是游戏中模拟真是的物理效果.如两个物体发生碰撞,物体自由落体等.在unity中使用的是NVIDIA的physX,它渲染的游戏画面很逼真. 刚体 刚体是一个很很中要的组件. 默认情况下,新创的 ...

  2. 线程的堆栈——Windows核心编程学习手札之十六

    线程的堆栈 --Windows核心编程学习手札之十六 系统会在进程的地址空间内保存一些区域,同时也会在进程地址空间内为线程的堆栈保留区域.线程都有自己的堆栈,创建时,系统就保留一个堆栈空间区域,并将相 ...

  3. 我的MYSQL学习心得(十六) 优化

    原文:我的MYSQL学习心得(十六) 优化 我的MYSQL学习心得(十六) 优化 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看 ...

  4. OpenCV学习笔记(十六)——CamShift研究 OpenCV学习笔记(十七)——运动分析和物体跟踪Video OpenCV学习笔记(十八)——图像的各种变换(cvtColor*+)imgproc

    OpenCV学习笔记(十六)--CamShift研究 CamShitf算法,即Continuously Apative Mean-Shift算法,基本思想就是对视频图像的多帧进行MeanShift运算 ...

  5. OpenCV学习笔记(十六):直方图均衡化:equalizeHist()

    OpenCV学习笔记(十六):直方图均匀化:equalizeHist() 参考博客: 直方图均衡化的数学原理 直方图匹配的数学原理 直方图均衡化广泛应用于图像增强中: 直方图均衡化处理的"中 ...

  6. QT学习笔记(十六):setwindowflags的属性总结

    QT学习笔记(十六):setwindowflags的属性总结 此枚举类型用于为小部件指定各种窗口系统属性.它们是不常用的,但在一些情况下是必要的.其中一些标志取决于底层窗口管理器是否支持它们. 主要类 ...

  7. MATLAB学习笔记(十六)

    MATLAB学习笔记(十六) 一.常微分方程数值求解 1.1 常微分方程数值求解的一般概念 1.2 常微分方程数值求解函数 1.3 刚性问题 一.常微分方程数值求解 1.1 常微分方程数值求解的一般概 ...

  8. python数据挖掘学习笔记】十六.逻辑回归LogisticRegression分析鸢尾花数据

    但是很多时候数据是非线性的,所以这篇文章主要讲述逻辑回归及Sklearn机器学习包中的LogisticRegression算法 #2018-03-28 16:57:56 March Wednesday ...

  9. JavaScript学习(八十六)—运算符知识点总结

    JavaScript学习(八十六)-运算符知识点总结 一. 运算符的分类 运算符(operator)也被称为操作符,是用于实现赋值.比较和执行算数运算等功能的符号. JavaScript中常用的运算符 ...

最新文章

  1. Scala学习(二)练习
  2. 职业高中计算机原理,132-浅议职业高中计算机组成原理教法初探
  3. CTFshow php特性 web104
  4. mybatis的延迟加载
  5. ecshop使用php代码,ecshop 修改模板可输出php代码
  6. 正则表达式 正整数_史上最全的正则表达式 (1) -- 校验数字的表达式
  7. java分页插件使用_MyBatis-Plus之分页插件使用
  8. Eclipse中Mybatis的自动提示的配置
  9. mysql服务器的搭建_基于linux的Mysql服务器的搭建
  10. 可能这就是我应用pytest搭建的第一个测试框架吧
  11. 实现C++与C的混合编程
  12. AS函数的一些特殊应用
  13. MATLAB中saveas函数使用
  14. 汽车故障检测仪计算机教程,如何使用汽车故障诊断仪进行汽车维修
  15. matlab安时积分法计算soc,一种带加权的安时积分的SOC估算方法与流程
  16. 隔离出来的“陋室铭”
  17. i7 8700k 安装linux,i7 8700k能装win7吗?i7 8700k搭配华硕B365主板装win7
  18. 微软、IBM对垒大数据
  19. php中baseasset,放映员资料库 ~ 影院之家 | The projectionist database - CineAsset 5.2.11 for Win 下载...
  20. Fiddler抓包工具配置--IE、Chrome

热门文章

  1. 基于风光储能和需求响应的微电网日前经济调度(Python代码实现)
  2. Go实战--golang中使用echo框架中JSONP(labstack/echo)
  3. 喏,你们要的58条 Allegro 使用技巧汇总整理好了
  4. 英语零散笔记Note整理
  5. 【Java SE】多线程
  6. tomcat服务器缓存配置文件,Tomcat7服务器配置Cache-Control和Expires
  7. c语言动态规划算法数塔问题,动态规划之数塔问题...
  8. android如何使用ios14组件,iOS14如何添加和删除小组件 iOS14添加和删除小组件的方法...
  9. 内存(RAM ROM)外存(硬盘 闪存)
  10. HEVC学习笔记(二)整体介绍