canvas可以获取哪些上下文?也就是canvas.getContext()的参数有哪些?(多选)
A. 2d
B. webgl
C. webgl2
D. bitmaprenderer
E. 3d
正确答案:A、B、C、D
bitmaprenderer简绍文档:
https://developer.mozilla.org/zh-CN/docs/Web/API/ImageBitmapRenderingContext
如何使用WebGL?
1.创建 WebGL上下文;
2.创建 WebGL 程序(WebGL Program);
3.将数据存入缓冲区;
4.将缓冲区数据读取到 GPU;
5.GPU 执行 WebGL 程序,输出结果,呈现画面。
WebGL例子:
// 创建 WebGL上下文 // 思考起:如果我们下面这一行代码注释掉会有什么问题吗? const canvas = document.getElementById('canvas'); const gl = canvas.getContext('webgl'); // 1. 使用GLSL语言 const vertex = ` attribute vec2 position; varying vec3 color; void main() { gl_PointSize = 1.0; color = vec3(0.5 + position * 0.5, 0.0); gl_Position = vec4(position * 0.5, 1.0, 1.0); } `; // 渐变:gl_FragColor = vec4(color, 1.0) // 红色:gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 黄色:gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0) const fragment = ` precision mediump float; varying vec3 color; void main() { gl_FragColor = vec4(color, 1.0); } `; // 顶点着色器(Vertex Shader) const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertex); gl.compileShader(vertexShader); // 片元着色器(Fragment Shader) const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragment); gl.compileShader(fragmentShader); // 2. 创建 WebGL 程序(WebGL Program) const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program); const points = new Float32Array([ -1, -1, 0, 1, 1, -1, ]); // 将数据存入缓冲区 const bufferId = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, bufferId); gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW); // 将缓冲区数据读取到GPU const vPosition = gl.getAttribLocation(program, 'position'); gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(vPosition); // GPU 执行 WebGL 程序,输出结果,呈现画面 gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLES, 0, points.length / 2);
顶点着色器(Vertex Shader): 处理顶点的 GPU 程序代码。它可以改变顶点的信息(如顶点的坐标、法线方向、材质等等)
片元着色器(Fragment Shader): 处理光栅化后的像素信息。
原生的WebGL使用起来还是比较困难的,这个时候就是使用框架的时候了,比如集团的Oasis,或者社区的Three.js。
Oasis:https://oasisengine.cn/
Three.js:https://threejs.org/
mrdoob:https://github.com/mrdoob
如何使用three.js?
入门的例子,我们直接使用Three.js官网提供的第一个例子:
import * as THREE from 'three'; // 创建相机 const camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 ); camera.position.z = 1; // 创建场景 const scene = new THREE.Scene(); // 创建正方体 const geometry = new THREE.BoxGeometry( 0.2, 0.2, 0.2 ); const material = new THREE.MeshNormalMaterial(); const mesh = new THREE.Mesh( geometry, material ); scene.add( mesh ); // 渲染器 const renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.setAnimationLoop( animation ); document.body.appendChild( renderer.domElement ); // 动画 function animation( time ) { mesh.rotation.x = time / 2000; mesh.rotation.y = time / 1000; renderer.render( scene, camera ); }
上面有几个比较重要的概念:相机、场景、几何体、网格、材质、渲染器。
Talk is cheap, show me the code.
export default { a: 1, sayHello: () => { console.log('hello'); } };
<script type="module"> import test from './03_es_module.js'; console.log(test.a); test.sayHello(); </script>
思考:官网的例子如果不使用importmap,那么怎么去使用es-module来引入three.js呢?
import * as THREE from '//unpkg.com/three@0.142.0/build/three.module.js';
场景(Scene):相当于是一个容器,可以在它上面添加光线,物体等,最后Three.js把它和相机一起渲染到DOM中。
文档:https://threejs.org/docs/index.html?q=Scene#api/en/scenes/Scene
修改场景颜色:
scene.background = new THREE.Color('orange');
Three.js颜色表示:
// 颜色的关键字 var color = new THREE.Color('orange'); // 默认背景,白色的 注意Three.js渲染的默认背景是黑色的 var color = new THREE.Color(); // 十六进制数字 var color = new THREE.Color( 0xff0000 ); // RGB字符串 var color = new THREE.Color("rgb(255, 0, 0)"); var color = new THREE.Color("rgb(100%, 0%, 0%)"); // HSL字符串 var color = new THREE.Color("hsl(0, 100%, 50%)"); // RGB的值 取值范围0~1 如红色: var color = new THREE.Color( 1, 0, 0 );
文档:https://threejs.org/docs/index.html?q=Camera#api/en/cameras/Camera
Three.js支持两种摄像机透视投影摄像机和正交投影摄像机。
透视投影摄像机(PerspectiveCamera)是最常用的摄像机,他跟我们的眼睛类似,越近的物体看到的越大,越远的物体看到的越小。
PerspectiveCamera的构造方法有4个参数,分别是视场、长宽比、近处距离、远处距离,其中视场表示眼睛看到的度数。
正交投影摄像机(OrthographicCamera)看到相同大小的物体,都是一样大的。其实相当于平行光照射到一个平面上的映射。
OrthographicCamera的构造方法有6个参数,分别是left、right、top、bottom、near、far,即左边、右边、上边、下边、近处和远处的位置,6个值刚好确定了一个长方体,正是投射的长方体。
官方给的这个例子可以帮助我们去理解:https://threejs.org/examples/#webgl_camera
几何体(Geometry)描述了网格的形状。Three.js内置了好多的几何体,比如我们上面用到的BoxGeometry,此外还有PlaneGeometry、CircleGeometry、RingGeometry、SphereGeometry等。
重要的公式:网格(Mesh) = 几何体(Geometry) + 材质(Material)
import * as THREE from 'three'; const camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.01, 100 ); camera.position.x = 0; camera.position.y = 2; camera.position.z = 50; const scene = new THREE.Scene(); // 添加正方体 const cubeGeometry = new THREE.BoxGeometry(4, 4, 4); const cubeMaterial = new THREE.MeshNormalMaterial(); const cube = new THREE.Mesh(cubeGeometry, cubeMaterial); // 正方体位置 cube.position.x = -6; cube.position.y = -6; cube.position.z = 0; // 把正方体添加到场景中 scene.add(cube); // 添加小球 const sphereGeometry = new THREE.SphereGeometry(2, 20, 20); const sphereMaterial = new THREE.MeshNormalMaterial(); const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial); // 小球位置 sphere.position.x = 6; sphere.position.y = -6; sphere.position.z = 0; // 把小球添加到场景中 scene.add(sphere); // 添加一片平地 const planeGeometry = new THREE.PlaneGeometry(30, 30, 100, 100); const planeMaterial = new THREE.MeshNormalMaterial(); const plane = new THREE.Mesh(planeGeometry, planeMaterial); // 由于平地添加后默认是在正前方 所以需要旋转一下 plane.rotation.x = -0.5 * Math.PI; plane.position.y = -10; scene.add(plane); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.setAnimationLoop( animation ); document.body.appendChild( renderer.domElement ); // 看向场景 camera.lookAt(scene.position); function animation(time) { cube.rotation.x = time / 2000; cube.rotation.y = time / 1000; sphere.rotation.x = time / 2000; sphere.rotation.y = time / 1000; renderer.render( scene, camera ); }
在WebGL中只能绘制3种东西,分别是点、线和三角形。
材质描述了几何体的颜色、感光等信息。我们上面用到了MeshNormalMaterial,同样的Three.js也提供了很多材质,如MeshBasicMaterial、MeshDepthMaterial、MeshPhongMaterial等。
const cubeMaterial = new THREE.MeshBasicMaterial({ color: '#ff0000', wireframe: true, opacity: 1, }); const sphereMaterial = new THREE.MeshBasicMaterial({ color: new THREE.Color('yellow'), wireframe: true, opacity: 0.5, });
相如正方体这种也可以给一个数组:
const cubeGeometry = new THREE.BoxGeometry(4, 4, 4); const cubeMaterials = []; cubeMaterials.push(new THREE.MeshBasicMaterial({color: 0xff0000})); cubeMaterials.push(new THREE.MeshBasicMaterial({color: 0x00ff00})); cubeMaterials.push(new THREE.MeshBasicMaterial({color: 0x0000ff})); cubeMaterials.push(new THREE.MeshBasicMaterial({color: 'orange'})); cubeMaterials.push(new THREE.MeshBasicMaterial({color: 'yellow'})); cubeMaterials.push(new THREE.MeshBasicMaterial({color: 'grey'})); const cube = new THREE.Mesh(cubeGeometry, cubeMaterials);
使用图片:
const texture = new THREE.TextureLoader().load('./asserts/lufei.jpeg'); const material = new THREE.MeshBasicMaterial({ map: texture }); // 添加正方体 const cubeGeometry = new THREE.BoxGeometry(8, 8, 8); const cube = new THREE.Mesh(cubeGeometry, material); // 正方体位置 cube.position.x = -6; cube.position.y = -2; cube.position.z = 0; // 把正方体添加到场景中 scene.add(cube); // 添加小球 const sphereGeometry = new THREE.SphereGeometry(5, 20, 20); const sphere = new THREE.Mesh(sphereGeometry, material); // 小球位置 sphere.position.x = 6; sphere.position.y = -2; sphere.position.z = 0; // 把小球添加到场景中 scene.add(sphere);
更多Texture相关文档请看这里:https://threejs.org/docs/index.html?q=Texture#api/en/constants/Textures
为了让3D效果更立体那么必须加入光源,加入光源后不同的物体还会形成阴影。Three.js也支持好几种光源,本节就简绍一下。
Three.js默认是不支持生成阴影的,主要是为了保证性能。这里我们详细以SpotLight(聚光灯光源)来简绍如何使用光源并生成阴影。
使用光源并添加阴影主要分为4个步骤:
1.添加光源并设置可以传播阴影:
// 添加光源 const spotLight = new THREE.SpotLight(0xffffff); spotLight.position.set(0, 10, 0); spotLight.castShadow = true; scene.add(spotLight);
2.使用可以感光的材质。
const cubeMaterial = new THREE.MeshLambertMaterial({color: 0xff0000}); const sphereMaterial = new THREE.MeshLambertMaterial({color: 0x00ff00}); const planeMaterial = new THREE.MeshLambertMaterial({color: 0xdddddd});
3.设置物体传播(产生)阴影或接收阴影:
cube.castShadow = true; sphere.castShadow = true; plane.receiveShadow = true;
4.渲染器开启阴影映射。
renderer.shadowMap.enabled = true;
PointLight(点光源)
PointLight(点光源)是从一个点散发出的光源,点光源不会产生阴影。
const pointLight = new THREE.PointLight("#ffd200"); scene.add(pointLight);
DirectionalLight(直线光)顾名思义是一种平行的直线光源(平行光光源)。平行光光源的光线是平行的,可以产生阴影,所有光的强度都一样。它有一个target属性表示照射到哪个位置上,另外可以使用directionalLight.shadow.camera.left来设置阴影的左边距,同样的也可以设置右边、上边、下边等边距,这样就可以确定一个阴影的范围(为了优化性能)。
// 添加光源 const directionalLight = new THREE.DirectionalLight('#ffffff'); directionalLight.position.set(0, 100, 0); directionalLight.castShadow = true; directionalLight.shadow.mapSize.width = 512; // default directionalLight.shadow.mapSize.height = 512; // default directionalLight.shadow.camera.near = 0.5; directionalLight.shadow.camera.far = 1000; directionalLight.shadow.camera.left = -15; directionalLight.shadow.camera.right = 15; directionalLight.shadow.camera.top = 15; directionalLight.shadow.camera.bottom = -15; scene.add(directionalLight); // 光照指向平地 directionalLight.target = plane;
AmbientLight的作用是给场景添加一种全局的颜色。该光源没有方向,也不产生阴影。如果你需要给场景中添加一种额外的统一的颜色,那么可以考虑使用AmbientLight,比如在上一个例子中添加一种紫色来烘托氛围,那么就可以使用该光源。
AmbientLight主要的作用就是给环境中添加一种颜色,还有一种给环境中添加颜色的光源,就是HemisphereLight。HemisphereLight是一种更加贴近自然的光源,往往用于模拟天空的光源,它的第一个参数表示天空的颜色,第二个参数表示地面(或者环境)的颜色,第三个参数是intensity表示强度。使用方法都差不多,如下:
const ambientLight = new THREE.AmbientLight('#9370DB'); scene.add(ambientLight); const hemisphereLight = new THREE.HemisphereLight('#87ceeb', '#f5deb3', 0.4); scene.add(hemisphereLight);
至此Three.js的基本概念我们都已经讲明白了,我们再回顾一下Three.js的基本物体:相机、场景、几何体、网格、材质、渲染器。
Three.js提供了好多的Geometry,也可以使用自定义Geometry。当然还有一种更简单的添加几何体的办法就是使用已有的模型。Three.js支持很多的模型加载器,使用方式大致相同,以obj模型为例。
let goku = null; new MTLLoader().setPath('./asserts/goku/').load('Goku.mtl', function ( materials ) { materials.preload(); new OBJLoader().setMaterials( materials ).setPath( './asserts/goku/' ).load( 'Goku.obj', function ( object ) { goku = object; goku.position.y = -6; goku.children.forEach(mesh => { mesh.castShadow = true; }); scene.add( goku ); }); });
Three.js提供了好多控制器,使用方式都是一样的,这里主要简绍一下TrackballControls(轨迹器控制器)。
const controls = new TrackballControls( camera, renderer.domElement ); controls.rotateSpeed = 1.0; controls.zoomSpeed = 1.2; controls.panSpeed = 0.8; function animation(time) { controls.update(); renderer.render( scene, camera ); } document.addEventListener('dblclick',() => { controls.reset(); }); function onWindowResize() { const aspect = window.innerWidth / window.innerHeight; camera.aspect = aspect; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); controls.handleResize(); } window.addEventListener( 'resize', onWindowResize );
分享中的代码: