WebGL 应用已经比较成熟,在网络中也能找到不少精彩的应用。 本次活动中,应用 WebGL 技术,配合设计师,基于 C4D 软件模型编辑,经过 Blender 进行标准化输出,最终在 Web 端呈现交互。 在此咱们一块儿看一下制做开发流程及不少吸引人的技术细节,以及对于新技术落地应用的主要模式。 主要大纲以下:前端
WebGL于 2011年2月 落地于浏览器,最先是 Chrome9 和 Firefox4。 当时,Google Creative Lab 利用 WebGL 技术进行交互展现的页面开发,体现了 WebGL 的强大力量。git
ROME 3 DREAMS OF BLACKgithub
如今不少主流浏览器对于 WebGL 也都支持了,而且可使用相同的体验,台式机,平板,手机,所以,咱们能够借助浏览器进行基于 WebGL 的跨平台的开发。编程
原理概述 浏览器
![]()
class THREERoot { constructor (wrapper, config) { // 配置解析 // ... CONFIG // 场景初始化 this.scene = new THREE.Scene() const { frustumSize, aspect } = this.config.OrthographicCamera // 摄像机初始化 this.camera = new THREE.OrthographicCamera( -frustumSize * aspect / 2, frustumSize * aspect / 2, frustumSize / 2, -frustumSize / 2, this.config.OrthographicCamera.near, this.config.OrthographicCamera.far ) // 渲染引擎初始化 this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }) // 控制器初始化 this.config.enableControls && (this.controls = new THREE.TrackballControls(this.camera, this.config.controlsDomElement || this.renderer.domElement)) } // 关键帧处理 animate () { this.renderer.render(this.scene, this.camera) this.config.enableControls && this.controls.update() this.camera.updateMatrixWorld() this.camera.updateProjectionMatrix() this.animateCallback && this.animateCallback() this.animateReq = this.requestAnimationFrame.bind(window)(this.animate) } } const gameInit = () => { const { root } = gameConfig const { scene, camera } = root // 游戏背景初始化 scene.background = new THREE.Color(0xa0a0a0) // 辅助坐标轴 const axesHelper = new THREE.AxesHelper(5) scene.add(axesHelper) // 相机位置 camera.up = new THREE.Vector3(0.0, 1.0, 0.0) camera.position.z = gameConfig.cameraRadius // 相机初始坐标 camera.userData = { standardPosition: camera.position.clone(), standardRotation: camera.rotation.clone() } } // 实例化运行 gameConfig.root = new THREERoot() gameInit({ gameConfig }) gameConfig.root.animate() 复制代码
// NOTE Blender 导出模型加载 const loader = new THREE.GLTFLoader() loader.load('./assets/demo.glb', function (gltf) { const textureLoader = new THREE.TextureLoader() // texture 贴图加载,模型加载完成后能够预运行 const texture = textureLoader.load('./assets/demo.png') console.log('模型数据:', gltf) }) 复制代码
gltf.scene.traverse(function (child) { if (child.isMesh) { const positions = child.geometry.attributes.position.array const normals = child.geometry.attributes.normal.array const uvs = child.geometry.attributes.uv.array const indexs = child.geometry.index.array const group = new THREE.Group() for (let i = 0, l = indexs.length; i < l; i += 3) { // 点坐标索引 const points = [ [indexs[i + 0] * 3, indexs[i + 0] * 3 + 1, indexs[i + 0] * 3 + 2], [indexs[i + 1] * 3, indexs[i + 1] * 3 + 1, indexs[i + 1] * 3 + 2], [indexs[i + 2] * 3, indexs[i + 2] * 3 + 1, indexs[i + 2] * 3 + 2] ] // 贴图坐标索引 const coordinates = [ [indexs[i + 0] * 2, indexs[i + 0] * 2 + 1], [indexs[i + 1] * 2, indexs[i + 1] * 2 + 1], [indexs[i + 2] * 2, indexs[i + 2] * 2 + 1] ] // 点坐标 const positionsTraverse = new Float32Array([ positions[ points[0][0] ], positions[ points[0][1] ], positions[ points[0][2] ], positions[ points[1][0] ], positions[ points[1][1] ], positions[ points[1][2] ], positions[ points[2][0] ], positions[ points[2][1] ], positions[ points[2][2] ] ]) // 法线坐标 const normalsTraverse = new Float32Array([ normals[ points[0][0] ], normals[ points[0][1] ], normals[ points[0][2] ], normals[ points[1][0] ], normals[ points[1][1] ], normals[ points[1][2] ], normals[ points[2][0] ], normals[ points[2][1] ], normals[ points[2][2] ] ]) // uv贴图坐标 const uvsTraverse = new Float32Array([ uvs[ coordinates[0][0] ], uvs[ coordinates[0][1] ], uvs[ coordinates[1][0] ], uvs[ coordinates[1][1] ], uvs[ coordinates[2][0] ], uvs[ coordinates[2][1] ] ]) // 顶点着色缓冲对象 const geometry = new THREE.BufferGeometry() geometry.setAttribute('position', new THREE.BufferAttribute(positionsTraverse, 3)) geometry.setAttribute('normal', new THREE.BufferAttribute(normalsTraverse, 3)) geometry.setAttribute('uv', new THREE.BufferAttribute(uvsTraverse, 2)) geometry.setIndex([0, 1, 2]) // 材质对象 const material = new THREE.MeshBasicMaterial( { wireframe: true, color: 0xffaa00 } ) material.side = THREE.DoubleSide const mesh = new THREE.Mesh(geometry, material) // NOTE 这里 的 1 / 100 比例缩放是 blender 导出以后的参数修正 // 相对于 c4d 来讲的话,scale 是 1 mesh.scale.set(gameConfig.scale, gameConfig.scale, gameConfig.scale) group.add(mesh) } group.name = gameConfig.groupName scene.add(group) } }) 复制代码
// NOTE 这里先建立球体进行坐标变换 const phi = Math.acos(-1 + (2 * i) / l) const theta = Math.sqrt(l * Math.PI) * phi const meshGroup = new THREE.Group() meshGroup.name = gameConfig.meshGroupName meshGroup.position.setFromSphericalCoords(2, phi, theta) meshGroup.add(mesh) group.add(meshGroup) 复制代码
// NOTE 更改每一个三角面的中心点 geometry.computeBoundingBox() var center = new THREE.Vector3() geometry.boundingBox.getCenter(center) geometry.center() mesh.position.copy(center) mesh.position.multiplyScalar(gameConfig.scale) mesh.geometry.lookAt(camera.position) 复制代码
root.animateCallback = () => { if (child.isMesh) { if (!child.children.length) { child.lookAt(camera.position) } } }) } 复制代码
// Mesh 回归原来位置 mesh.position.x -= meshGroup.position.x mesh.position.y -= meshGroup.position.y mesh.position.z -= meshGroup.position.z mesh.geometry.lookAt(camera.position) 复制代码
// 纹理顶点着色 const textureVertex = ` #ifdef GL_ES precision mediump float; #endif // attribute float size; varying vec2 vUv; void main() { vUv = uv; vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0); gl_Position = projectionMatrix * modelViewPosition; } ` // 纹理片元着色 const textureFragment = ` #ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; uniform float u_time; uniform vec3 u_position; uniform float instensity; varying vec2 vUv; // u_texture uniform sampler2D u_texture; void main (void) { vec2 uv = gl_FragCoord.xy / u_resolution; vec4 textureColor = texture2D(u_texture, vUv); gl_FragColor = vec4(textureColor.rgb * instensity, textureColor.a); } ` // Material 建立 const material = new THREE.ShaderMaterial({ uniforms: { u_texture: { type: 'sampler2D', value: texture }, u_resolution: new THREE.Uniform(new THREE.Vector2()), instensity: { type: 'f', value: 1.0 } }, fragmentShader: textureFragment, vertexShader: textureVertex }) 复制代码
// 每一个 Mesh 配置随机偏移参数 mesh.userData = { rotationRandom: Math.random() * 2 - 1, positionRandom: Math.random() - 0.5, rotation: camera.rotation.clone() } 复制代码
// 每一个 Mesh 进行随机位置偏移 const { rotationRandom, positionRandom, rotation } = child.userData if (!child.children.length) { // NOTE blender 导出模型 gltf2.0 选项必定要勾选 +Y up child.rotation.z = (rotation.x - Math.sin(x)) * rotationRandom * rotationOffset child.position.z = gameConfig.positionOffset * positionRandom child.lookAt(camera.position) } 复制代码
在完成开发步骤以后,可以使用一些动画库对 Mesh 进行序列帧动画的添加和调试。最终效果以下:markdown
感谢一块儿工做的前端同事李战帮助一块儿进行工程化开发及项目总结的支持,也感谢其余合做方在开发过程当中的支持和配合。网络