├── .gitignore ├── views ├── web3D │ ├── loader │ │ ├── index.js │ │ └── obj.js │ ├── camera │ │ ├── index.js │ │ ├── camera-perspective.js │ │ ├── camera-ortho.js │ │ └── camera.js │ ├── light │ │ ├── index.js │ │ ├── light-ambient.js │ │ ├── light-point.js │ │ ├── light-directional.js │ │ └── light.js │ ├── material │ │ ├── index.js │ │ ├── material-base.js │ │ ├── material-phone.js │ │ └── material.js │ ├── shader │ │ ├── shadow │ │ │ ├── fragment-shader.glsl │ │ │ └── vertex-shader.glsl │ │ ├── index.js │ │ └── normal │ │ │ ├── vertex-shader.glsl │ │ │ └── fragment-shader.glsl │ ├── geometry │ │ ├── index.js │ │ ├── geometry.js │ │ ├── geometry-face.js │ │ ├── geometry-sphare.js │ │ └── geometry-cube.js │ ├── mesh │ │ ├── utlis │ │ │ ├── buffer.js │ │ │ └── shader-data.js │ │ └── index.js │ ├── utils │ │ ├── parse.js │ │ ├── fbo.js │ │ └── math.js │ ├── program.js │ ├── group │ │ └── index.js │ └── index.js └── demos │ ├── view │ ├── imgs │ │ ├── box.jpeg │ │ ├── earth.jpeg │ │ └── sky │ │ │ ├── back.jpg │ │ │ ├── down.jpg │ │ │ ├── front.jpg │ │ │ ├── left.jpg │ │ │ ├── right.jpg │ │ │ └── top.jpg │ ├── index.html │ ├── js │ │ ├── pointer │ │ │ └── index.js │ │ └── keyword │ │ │ └── index.js │ └── index.js │ ├── group │ ├── imgs │ │ └── box.jpeg │ ├── index.html │ └── index.js │ ├── gun │ ├── assets │ │ ├── micai.jpg │ │ └── texture.jpeg │ ├── index.html │ ├── index.js │ └── utils │ │ └── index.js │ ├── shadow │ ├── imgs │ │ └── box.jpeg │ ├── index.html │ ├── js │ │ └── pointer │ │ │ └── index.js │ └── index.js │ ├── animated │ ├── imgs │ │ └── box.jpeg │ ├── index.html │ └── index.js │ ├── texture │ ├── imgs │ │ └── box.jpeg │ ├── index.html │ └── index.js │ ├── point │ ├── geometry-point.js │ ├── index.html │ └── index.js │ ├── cube │ ├── index.html │ └── index.js │ ├── fog │ ├── index.html │ └── index.js │ ├── light │ ├── index.html │ └── index.js │ ├── model │ ├── index.html │ └── index.js │ ├── phone │ ├── index.html │ └── index.js │ └── index.html ├── server └── index.js ├── package.json ├── README.md └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .history 2 | node_modules -------------------------------------------------------------------------------- /views/web3D/loader/index.js: -------------------------------------------------------------------------------- 1 | export * from './obj.js'; -------------------------------------------------------------------------------- /views/demos/view/imgs/box.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhzzcc/web3D/HEAD/views/demos/view/imgs/box.jpeg -------------------------------------------------------------------------------- /views/demos/group/imgs/box.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhzzcc/web3D/HEAD/views/demos/group/imgs/box.jpeg -------------------------------------------------------------------------------- /views/demos/gun/assets/micai.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhzzcc/web3D/HEAD/views/demos/gun/assets/micai.jpg -------------------------------------------------------------------------------- /views/demos/shadow/imgs/box.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhzzcc/web3D/HEAD/views/demos/shadow/imgs/box.jpeg -------------------------------------------------------------------------------- /views/demos/view/imgs/earth.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhzzcc/web3D/HEAD/views/demos/view/imgs/earth.jpeg -------------------------------------------------------------------------------- /views/demos/animated/imgs/box.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhzzcc/web3D/HEAD/views/demos/animated/imgs/box.jpeg -------------------------------------------------------------------------------- /views/demos/gun/assets/texture.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhzzcc/web3D/HEAD/views/demos/gun/assets/texture.jpeg -------------------------------------------------------------------------------- /views/demos/texture/imgs/box.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhzzcc/web3D/HEAD/views/demos/texture/imgs/box.jpeg -------------------------------------------------------------------------------- /views/demos/view/imgs/sky/back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhzzcc/web3D/HEAD/views/demos/view/imgs/sky/back.jpg -------------------------------------------------------------------------------- /views/demos/view/imgs/sky/down.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhzzcc/web3D/HEAD/views/demos/view/imgs/sky/down.jpg -------------------------------------------------------------------------------- /views/demos/view/imgs/sky/front.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhzzcc/web3D/HEAD/views/demos/view/imgs/sky/front.jpg -------------------------------------------------------------------------------- /views/demos/view/imgs/sky/left.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhzzcc/web3D/HEAD/views/demos/view/imgs/sky/left.jpg -------------------------------------------------------------------------------- /views/demos/view/imgs/sky/right.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhzzcc/web3D/HEAD/views/demos/view/imgs/sky/right.jpg -------------------------------------------------------------------------------- /views/demos/view/imgs/sky/top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhzzcc/web3D/HEAD/views/demos/view/imgs/sky/top.jpg -------------------------------------------------------------------------------- /views/web3D/camera/index.js: -------------------------------------------------------------------------------- 1 | export * from './camera.js'; 2 | export * from './camera-perspective.js'; 3 | export * from './camera-ortho.js' -------------------------------------------------------------------------------- /views/web3D/light/index.js: -------------------------------------------------------------------------------- 1 | export * from './light-ambient.js'; 2 | export * from './light-directional.js'; 3 | export * from './light-point.js'; -------------------------------------------------------------------------------- /views/web3D/material/index.js: -------------------------------------------------------------------------------- 1 | export * from './material.js'; 2 | export * from './material-base.js'; 3 | export * from './material-phone.js'; -------------------------------------------------------------------------------- /views/web3D/shader/shadow/fragment-shader.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | void main() { 4 | gl_FragColor = vec4(0, 0, 0, 0); 5 | } -------------------------------------------------------------------------------- /views/web3D/geometry/index.js: -------------------------------------------------------------------------------- 1 | export * from './geometry.js'; 2 | export * from './geometry-face.js'; 3 | export * from './geometry-cube.js'; 4 | export * from './geometry-sphare.js'; -------------------------------------------------------------------------------- /views/web3D/shader/shadow/vertex-shader.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | attribute vec3 position; 3 | attribute vec2 texture; 4 | 5 | uniform mat4 meshMatrix; 6 | uniform mat4 cameraMatrix; 7 | 8 | void main() { 9 | gl_Position = cameraMatrix * meshMatrix * vec4(position, 1.0); 10 | } -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | const app = express(); 4 | 5 | app.use(express.static(path.resolve(__dirname, '../'))) 6 | 7 | 8 | const port = 1234; 9 | app.listen(port, () => { 10 | console.log(`success in localhost:${port}`) 11 | }); -------------------------------------------------------------------------------- /views/web3D/material/material-base.js: -------------------------------------------------------------------------------- 1 | import { Material } from "./material.js"; 2 | 3 | export class MaterialBase extends Material { 4 | constructor(options = {}) { 5 | super(); 6 | const { color = '#fff', image } = options; 7 | color && this.setColor(color); 8 | image && this.setImage(image); 9 | } 10 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webgl", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "serve": "node server/index.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "express": "^4.17.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /views/web3D/light/light-ambient.js: -------------------------------------------------------------------------------- 1 | import { Light } from './light.js'; 2 | export class LightAmbient extends Light { 3 | constructor(options = {}) { 4 | const { 5 | color = [1, 1, 1], 6 | strength = 1 7 | } = options; 8 | super(); 9 | this.setStrength(strength); 10 | this.setColor(color); 11 | } 12 | }; -------------------------------------------------------------------------------- /views/web3D/material/material-phone.js: -------------------------------------------------------------------------------- 1 | import { Material } from "./material.js"; 2 | 3 | export class MaterialPhone extends Material { 4 | constructor(options = {}) { 5 | super(); 6 | const { color = '#fff', image, drawMode } = options; 7 | color && this.setColor(color); 8 | image && this.setImage(image); 9 | drawMode && this.setDrawMode(drawMode); 10 | } 11 | }; -------------------------------------------------------------------------------- /views/web3D/camera/camera-perspective.js: -------------------------------------------------------------------------------- 1 | import { Camera } from './camera.js'; 2 | import { perspective } from '../utils/math.js'; 3 | 4 | export class CameraPerspective extends Camera { 5 | constructor(options = {}) { 6 | super(); 7 | const { fov, aspect, near = 0.1, far = 1000.0 } = options; 8 | this.fov = fov; 9 | this.aspect = aspect; 10 | this.near = near; 11 | this.far = far; 12 | perspective(this.projectMatrix, fov, aspect, near, far); 13 | } 14 | }; -------------------------------------------------------------------------------- /views/web3D/mesh/utlis/buffer.js: -------------------------------------------------------------------------------- 1 | export const createArrayBuffer = (gl, list) => { 2 | const buffer = gl.createBuffer(); 3 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 4 | gl.bufferData(gl.ARRAY_BUFFER, list, gl.STATIC_DRAW); 5 | return buffer; 6 | } 7 | 8 | export const createElementArrayBuffer = (gl, list) => { 9 | const buffer = gl.createBuffer(); 10 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer); 11 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, list, gl.STATIC_DRAW); 12 | return buffer; 13 | } -------------------------------------------------------------------------------- /views/web3D/light/light-point.js: -------------------------------------------------------------------------------- 1 | import { Light } from './light.js'; 2 | export class LightPoint extends Light { 3 | constructor(options = {}) { 4 | const { 5 | position = [0, 0, 0], 6 | color = [1, 1, 1], 7 | strength = 1 8 | } = options; 9 | super(); 10 | this.move({ 11 | x: position[0], 12 | y: position[1], 13 | z: position[2] 14 | }); 15 | this.setStrength(strength); 16 | this.setColor(color); 17 | } 18 | }; -------------------------------------------------------------------------------- /views/web3D/light/light-directional.js: -------------------------------------------------------------------------------- 1 | import { Light } from './light.js'; 2 | export class LightDirectional extends Light { 3 | constructor(options = {}) { 4 | const { 5 | position = [0, 0, 0], 6 | color = [1, 1, 1], 7 | strength = 1 8 | } = options; 9 | super(); 10 | this.move({ 11 | x: position[0], 12 | y: position[1], 13 | z: position[2] 14 | }); 15 | this.setStrength(strength); 16 | this.setColor(color); 17 | } 18 | }; -------------------------------------------------------------------------------- /views/web3D/shader/index.js: -------------------------------------------------------------------------------- 1 | const baseUrl = (location.href.includes('github') ? '/web3D' : '') + '/views/web3D/shader'; 2 | // 获取着色器代码 3 | export const initShader = async (fragmentShaderTextSrc, vertexShaderTextSrc) => { 4 | const [fragmentShaderText, vertexShaderText] = await Promise.all([ 5 | fetch(baseUrl + fragmentShaderTextSrc).then(res => res.text()), 6 | fetch(baseUrl + vertexShaderTextSrc).then(res => res.text()), 7 | ]); 8 | 9 | return [ 10 | fragmentShaderText, 11 | vertexShaderText 12 | ] 13 | }; 14 | -------------------------------------------------------------------------------- /views/web3D/camera/camera-ortho.js: -------------------------------------------------------------------------------- 1 | import { Camera } from './camera.js'; 2 | import { ortho } from '../utils/math.js'; 3 | 4 | export class CameraOrtho extends Camera { 5 | constructor(options = {}) { 6 | super(); 7 | const { left, right, bottom, top, near = 0.1, far = 100.0 } = options; 8 | this.left = left; 9 | this.right = right; 10 | this.bottom = bottom; 11 | this.top = top; 12 | this.near = near; 13 | this.far = far; 14 | ortho(this.projectMatrix, left, right, bottom, top, near, far); 15 | } 16 | }; -------------------------------------------------------------------------------- /views/web3D/utils/parse.js: -------------------------------------------------------------------------------- 1 | export const parseColor = c => { 2 | 3 | 4 | if (Array.isArray(c)) { 5 | return c; 6 | } 7 | 8 | const color = c.split('#')[1]; 9 | let result = []; 10 | if (color.length === 3) { 11 | for (let i = 0; i < 3; i++) { 12 | result.push(+('0x' + color[i] + color[i]) / 255); 13 | } 14 | } 15 | 16 | if (color.length === 6) { 17 | for (let i = 0; i < 6; i += 2) { 18 | result.push(+('0x' + color[i] + color[i+1]) / 255); 19 | } 20 | } 21 | 22 | return result; 23 | }; -------------------------------------------------------------------------------- /views/demos/point/geometry-point.js: -------------------------------------------------------------------------------- 1 | import { Geometry } from '../../web3D/geometry/index.js'; 2 | 3 | 4 | export class GeometryPoint extends Geometry { 5 | constructor() { 6 | super(); 7 | const position = this.computedPosition(); 8 | this.setPosition(position); 9 | } 10 | 11 | computedPosition() { 12 | const position = []; 13 | for (let i = -30; i < 60; i++) { 14 | for (let j = -30; j < 60; j++) { 15 | position.push(i / 3, j / 3, 0); 16 | } 17 | } 18 | return position; 19 | } 20 | }; -------------------------------------------------------------------------------- /views/demos/cube/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /views/demos/fog/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /views/demos/gun/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /views/demos/view/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /views/demos/animated/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /views/demos/group/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /views/demos/light/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /views/demos/model/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /views/demos/phone/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /views/demos/point/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /views/demos/shadow/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /views/demos/texture/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /views/web3D/material/material.js: -------------------------------------------------------------------------------- 1 | // 颜色、纹理图片 2 | export class Material { 3 | constructor() { 4 | this.image = null; 5 | this.color = null; 6 | } 7 | 8 | setColor(color) { 9 | this.color = color; 10 | const canvas = document.createElement('canvas'); 11 | const ctx = canvas.getContext('2d'); 12 | canvas.width = 2; 13 | canvas.height = 2; 14 | ctx.fillStyle = color; 15 | ctx.fillRect(0, 0, 2, 2); 16 | this.setImage(canvas); 17 | } 18 | 19 | setImage(image) { 20 | this.image = image; 21 | } 22 | 23 | getColor() { 24 | return this.color; 25 | } 26 | 27 | getImage() { 28 | return this.image; 29 | } 30 | }; -------------------------------------------------------------------------------- /views/web3D/geometry/geometry.js: -------------------------------------------------------------------------------- 1 | // 存储顶点、法线、纹理、索引 2 | export class Geometry { 3 | constructor() { 4 | this.position = null; 5 | this.normal = null; 6 | this.texture = null; 7 | this.index = null; 8 | } 9 | 10 | setPosition(position) { 11 | this.position = position; 12 | } 13 | 14 | setNormal(normal) { 15 | this.normal = normal; 16 | } 17 | 18 | setTexture(texture) { 19 | this.texture = texture; 20 | } 21 | 22 | setIndex(index) { 23 | this.index = index; 24 | } 25 | 26 | getPosition() { 27 | return this.position; 28 | } 29 | 30 | getNormal() { 31 | return this.normal; 32 | } 33 | 34 | getTexture() { 35 | return this.texture; 36 | } 37 | 38 | getIndex() { 39 | return this.index; 40 | } 41 | }; -------------------------------------------------------------------------------- /views/web3D/light/light.js: -------------------------------------------------------------------------------- 1 | import { parseColor } from '../utils/parse.js'; 2 | export class Light { 3 | constructor() { 4 | this.position = [0, 0, 0]; 5 | this.color = '#fff'; 6 | this.strength = 1; 7 | } 8 | 9 | move({ x, y, z }) { 10 | this.position = [ 11 | this.position[0] + (x || 0), 12 | this.position[1] + (y || 0), 13 | this.position[2] + (z || 0), 14 | ]; 15 | } 16 | 17 | getPosition() { 18 | return this.position; 19 | } 20 | 21 | setColor(color) { 22 | this.color = parseColor(color); 23 | } 24 | 25 | getColor() { 26 | return this.color.map(c => c * Math.min(1, this.strength)); 27 | } 28 | 29 | setStrength(strength) { 30 | this.strength = strength; 31 | } 32 | 33 | getStrength() { 34 | return this.strength; 35 | } 36 | } -------------------------------------------------------------------------------- /views/web3D/shader/normal/vertex-shader.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | attribute vec3 position; 3 | attribute vec3 color; 4 | attribute vec3 normal; 5 | attribute vec2 texture; 6 | 7 | uniform mat4 meshMatrix; 8 | uniform mat4 normalMatrix; 9 | uniform mat4 cameraMatrix; 10 | uniform mat4 shadowLightCameraMatrix; 11 | 12 | uniform float pointSize; 13 | 14 | 15 | varying vec3 vNormal; 16 | varying vec3 vColor; 17 | varying vec3 vPosition; 18 | varying vec2 vTexture; 19 | varying vec4 vPositionFromLight; 20 | varying float vDist; 21 | 22 | 23 | void main() { 24 | vNormal = normalize((normalMatrix * vec4(normal, 1.0)).xyz); 25 | vColor = color; 26 | vPosition = (meshMatrix * vec4(position, 1.0)).xyz; 27 | vTexture = texture; 28 | vPositionFromLight = shadowLightCameraMatrix * meshMatrix * vec4(position, 1.0); 29 | 30 | if (pointSize > 0.0) { 31 | gl_PointSize = pointSize; 32 | } 33 | 34 | gl_Position = cameraMatrix * meshMatrix * vec4(position, 1.0); 35 | vDist = gl_Position.w; 36 | } -------------------------------------------------------------------------------- /views/web3D/utils/fbo.js: -------------------------------------------------------------------------------- 1 | export const initFramebufferObject = (gl, width, height) => { 2 | const size = width; 3 | 4 | const fbo = gl.createFramebuffer() 5 | const depthTexture = gl.createTexture() 6 | 7 | gl.bindTexture(gl.TEXTURE_2D, depthTexture) 8 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) 9 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) 10 | 11 | gl.texImage2D( 12 | gl.TEXTURE_2D, 13 | 0, 14 | gl.DEPTH_COMPONENT, 15 | size, 16 | size, 17 | 0, 18 | gl.DEPTH_COMPONENT, 19 | gl.UNSIGNED_SHORT, 20 | null 21 | ) 22 | 23 | gl.bindFramebuffer(gl.FRAMEBUFFER, fbo) 24 | gl.framebufferTexture2D( 25 | gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, 0 26 | ) 27 | 28 | 29 | const e = gl.checkFramebufferStatus(gl.FRAMEBUFFER) 30 | if (e !== gl.FRAMEBUFFER_COMPLETE) { 31 | console.error('framebuffer not complete', e.toString()) 32 | } 33 | 34 | gl.bindTexture(gl.TEXTURE_2D, null) 35 | gl.bindFramebuffer(gl.FRAMEBUFFER, null) 36 | fbo.texture = depthTexture; 37 | return fbo 38 | } -------------------------------------------------------------------------------- /views/web3D/loader/obj.js: -------------------------------------------------------------------------------- 1 | import { Geometry } from '../geometry/index.js'; 2 | 3 | export const loadObj = async url => { 4 | const data = await fetch(url).then(res => res.text()); 5 | const list = data.split('\n'); 6 | 7 | let position = []; 8 | let normal = []; 9 | let index = []; 10 | for (let i = 0; i < list.length; i++) { 11 | const item = list[i]; 12 | // 顶点 13 | if (/^v /.test(item)) { 14 | const [_, x, y, z] = item.split(/ /); 15 | position.push(+x, +y, +z); 16 | } 17 | // 法线 18 | if (/^vn /.test(item)) { 19 | const [_, x, y, z] = item.split(/ /); 20 | normal.push(+x, +y, +z); 21 | } 22 | // 索引 23 | if (/^f /.test(item)) { 24 | const [_, x, y, z] = item.split(/ /); 25 | index.push(+x.split('//')[0] - 1, +y.split('//')[0] - 1, +z.split('//')[0] - 1); 26 | } 27 | } 28 | 29 | const geometry = new Geometry(); 30 | geometry.setPosition(position); 31 | geometry.setNormal(normal); 32 | geometry.setIndex(index); 33 | 34 | return geometry; 35 | }; -------------------------------------------------------------------------------- /views/web3D/program.js: -------------------------------------------------------------------------------- 1 | const compileShader = (gl, type, source) => { 2 | const shader = gl.createShader(type) 3 | gl.shaderSource(shader, source) 4 | gl.compileShader(shader) 5 | 6 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 7 | console.log('Error compiling shaders', gl.getShaderInfoLog(shader)) 8 | gl.deleteShader(shader) 9 | return null 10 | } 11 | return shader 12 | } 13 | 14 | export const initProgram = (gl, vsSource, fsSource) => { 15 | const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vsSource) 16 | const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fsSource) 17 | 18 | const shaderProgram = gl.createProgram() 19 | gl.attachShader(shaderProgram, vertexShader) 20 | gl.attachShader(shaderProgram, fragmentShader) 21 | gl.linkProgram(shaderProgram) 22 | 23 | if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { 24 | console.log( 25 | 'Error init shader program', gl.getProgramInfoLog(shaderProgram) 26 | ) 27 | return null 28 | } 29 | // gl.useProgram(shaderProgram) 30 | 31 | return shaderProgram 32 | } 33 | -------------------------------------------------------------------------------- /views/web3D/group/index.js: -------------------------------------------------------------------------------- 1 | 2 | export class Group { 3 | constructor() { 4 | this.meshs = []; 5 | this.position = [0, 0, 0]; 6 | this.rotateX = 0; 7 | this.rotateY = 0; 8 | this.rotateZ = 0 9 | this.delta = 0; 10 | } 11 | 12 | add(mesh) { 13 | if (Array.isArray(mesh)) { 14 | mesh.forEach(m => this.meshs.push(m)) 15 | } else { 16 | this.meshs.push(mesh); 17 | } 18 | 19 | } 20 | 21 | move({ x, y, z }) { 22 | this.position = [ 23 | this.position[0] + (x || 0), 24 | this.position[1] + (y || 0), 25 | this.position[2] + (z || 0), 26 | ]; 27 | this.meshs.forEach(mesh => { 28 | mesh.move({ x, y, z }); 29 | }); 30 | } 31 | 32 | rotate({ x, y, z, delta }) { 33 | this.delta += delta; 34 | this.rotateX = x; 35 | this.rotateY = y; 36 | this.rotateZ = z; 37 | this.meshs.forEach(mesh => { 38 | mesh.revolution({ x, y, z, delta }); 39 | }); 40 | } 41 | 42 | getMeshs() { 43 | return this.meshs; 44 | } 45 | }; -------------------------------------------------------------------------------- /views/web3D/camera/camera.js: -------------------------------------------------------------------------------- 1 | import { create, translate, rotate, multiply, lookAt } from '../utils/math.js'; 2 | 3 | export class Camera { 4 | constructor() { 5 | this.position = [0, 0, 0]; 6 | this.view = [0, 0, 0]; 7 | this.cameraMatrix = create(); 8 | this.projectMatrix = create(); 9 | 10 | } 11 | 12 | // 视角 13 | lookAt(x, y, z) { 14 | this.view = [x, y, z]; 15 | } 16 | 17 | getCameraMatrix() { 18 | return this.cameraMatrix; 19 | } 20 | 21 | getView() { 22 | return this.view; 23 | } 24 | 25 | getPosition() { 26 | return this.position; 27 | } 28 | 29 | // 平移 30 | move({ x, y, z }) { 31 | this.position = [ 32 | (x || 0) + this.position[0], 33 | (y || 0) + this.position[1], 34 | (z || 0) + this.position[2] 35 | ]; 36 | } 37 | 38 | 39 | // 更新所有矩阵(draw调用) 40 | computedCameraMatrix() { 41 | const viewMatrix = lookAt([], this.position, this.view, [0, 1, 0]); 42 | const cameraMatrix = multiply([], this.projectMatrix, viewMatrix); 43 | this.cameraMatrix = cameraMatrix; 44 | } 45 | } -------------------------------------------------------------------------------- /views/demos/cube/index.js: -------------------------------------------------------------------------------- 1 | import { Web3D } from '../../web3D/index.js'; 2 | import { CameraPerspective } from '../../web3D/camera/index.js'; 3 | import { GeometryCube } from '../../web3D/geometry/index.js'; 4 | import { MaterialBase } from '../../web3D/material/index.js'; 5 | import { Mesh } from '../../web3D/mesh/index.js'; 6 | import { LightAmbient } from '../../web3D/light/index.js'; 7 | 8 | 9 | const start = async () => { 10 | 11 | const web3D = new Web3D(); 12 | const parentDom = document.querySelector('.web3d'); 13 | const width = parentDom.offsetWidth; 14 | const height = parentDom.offsetHeight; 15 | const web3dDom = await web3D.init({ width, height }); 16 | parentDom.appendChild(web3dDom); 17 | 18 | // 透视投影相机 19 | const camera = new CameraPerspective({ fov: Math.PI / 6, aspect: width / height }); 20 | 21 | 22 | // 箱子 23 | const boxMesh = new Mesh( 24 | new GeometryCube({ l: 1, w: 1, h: 1 }), 25 | new MaterialBase({ color: '#2254f4' }) 26 | ); 27 | 28 | // 环境光 29 | const lightAmbient = new LightAmbient({ color: [1, 1, 1], strength: 0.6 }); 30 | 31 | 32 | web3D.add([ 33 | camera, 34 | boxMesh, 35 | lightAmbient 36 | ]); 37 | 38 | camera.move({ x: 5, y: 5, z: 10 }); 39 | camera.lookAt(0, 0, 0); 40 | 41 | web3D.draw(); 42 | }; 43 | 44 | start(); -------------------------------------------------------------------------------- /views/demos/fog/index.js: -------------------------------------------------------------------------------- 1 | import { Web3D } from '../../web3D/index.js'; 2 | import { CameraPerspective } from '../../web3D/camera/index.js'; 3 | import { GeometryCube } from '../../web3D/geometry/index.js'; 4 | import { MaterialBase } from '../../web3D/material/index.js'; 5 | import { Mesh } from '../../web3D/mesh/index.js'; 6 | import { LightAmbient, LightPoint } from '../../web3D/light/index.js'; 7 | 8 | 9 | const start = async () => { 10 | 11 | const web3D = new Web3D(); 12 | const parentDom = document.querySelector('.web3d'); 13 | const width = parentDom.offsetWidth; 14 | const height = parentDom.offsetHeight; 15 | const web3dDom = await web3D.init({ width, height }); 16 | parentDom.appendChild(web3dDom); 17 | 18 | // 透视投影相机 19 | const camera = new CameraPerspective({ fov: Math.PI / 6, aspect: width / height }); 20 | 21 | // 箱子 22 | const boxMesh = new Mesh( 23 | new GeometryCube({ l: 1, w: 1, h: 1 }), 24 | new MaterialBase({ color: '#2254f4' }), 25 | { 26 | fogColor: '#000', 27 | fogDist: [1, 5.5] 28 | } 29 | ); 30 | 31 | // 环境光 32 | const lightAmbient = new LightAmbient({ color: [1, 1, 1], strength: 0.6 }); 33 | 34 | // 点光源 35 | const lightPoint = new LightPoint({ position: [4, 2, 2], strength: 1.0 }); 36 | 37 | 38 | web3D.add([ 39 | camera, 40 | boxMesh, 41 | lightAmbient, 42 | lightPoint 43 | ]); 44 | 45 | camera.move({ x: 4, y: 2, z: 3 }); 46 | camera.lookAt(0, 0, 0); 47 | 48 | web3D.draw(); 49 | }; 50 | 51 | start(); -------------------------------------------------------------------------------- /views/demos/view/js/pointer/index.js: -------------------------------------------------------------------------------- 1 | const length = (a, b) => { 2 | return Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2)); 3 | } 4 | 5 | export class Pointer { 6 | start(camera) { 7 | const $el = document.body; 8 | this.rx = 0; 9 | this.ry = 0; 10 | 11 | $el.addEventListener('click', () => { 12 | $el.requestPointerLock(); 13 | }); 14 | 15 | const fn = e => this.move(e, camera); 16 | document.addEventListener('pointerlockchange', () => { 17 | if (document.pointerLockElement === $el) { 18 | document.addEventListener("mousemove", fn); 19 | } else { 20 | document.removeEventListener("mousemove", fn); 21 | } 22 | }); 23 | } 24 | 25 | move(e, camera) { 26 | this.rx += e.movementX / 200; 27 | this.ry += -e.movementY / 200; 28 | if (this.ry >= Math.PI / 2) { 29 | this.ry = Math.PI / 2; 30 | } 31 | 32 | if (this.ry <= -Math.PI / 2) { 33 | this.ry = -Math.PI / 2; 34 | 35 | } 36 | const rx = this.rx; 37 | const ry = this.ry; 38 | 39 | const view = camera.getView(); 40 | const position = camera.getPosition(); 41 | const l = length(view, position); 42 | 43 | const x = position[0] + l * Math.sin(rx) * Math.cos(ry); 44 | const y = position[1] + l * Math.sin(ry); 45 | const z = position[2] - l * Math.cos(rx) * Math.cos(ry); 46 | camera.lookAt(x, y, z); 47 | 48 | }; 49 | }; -------------------------------------------------------------------------------- /views/web3D/geometry/geometry-face.js: -------------------------------------------------------------------------------- 1 | import { Geometry } from './geometry.js'; 2 | 3 | export class GeometryFace extends Geometry { 4 | constructor(options = {}) { 5 | super(); 6 | const { l = 1, w = 1 } = options; 7 | const position = this.computedPosition(l, w); 8 | const normal = this.computedNormal(); 9 | const texture = this.computedTexture(); 10 | const index = this.computedIndex(); 11 | this.setPosition(position); 12 | this.setNormal(normal); 13 | this.setTexture(texture); 14 | this.setIndex(index); 15 | } 16 | 17 | computedPosition(l, w) { 18 | const position = [ 19 | // Front face 20 | -l / 2, -w / 2, 0, 21 | l / 2, -w / 2, 0, 22 | l / 2, w / 2, 0, 23 | -l / 2, w / 2, 0, 24 | 25 | ]; 26 | return position; 27 | } 28 | 29 | computedNormal() { 30 | const normal = [ 31 | // Front 32 | 0.0, 0.0, 1.0, 33 | 0.0, 0.0, 1.0, 34 | 0.0, 0.0, 1.0, 35 | 0.0, 0.0, 1.0, 36 | 37 | ]; 38 | return normal; 39 | } 40 | 41 | computedTexture() { 42 | const texture = [ 43 | // Front 44 | 0.0, 0.0, 45 | 1.0, 0.0, 46 | 1.0, 1.0, 47 | 0.0, 1.0, 48 | ]; 49 | return texture; 50 | } 51 | 52 | computedIndex() { 53 | const index = [ 54 | 0, 1, 2, 0, 2, 3, // Front 55 | ]; 56 | return index; 57 | } 58 | 59 | 60 | }; -------------------------------------------------------------------------------- /views/demos/shadow/js/pointer/index.js: -------------------------------------------------------------------------------- 1 | const length = (a, b) => { 2 | return Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2)); 3 | } 4 | 5 | export class Pointer { 6 | start(camera) { 7 | const $el = document.body; 8 | this.rx = 0; 9 | this.ry = 0; 10 | 11 | $el.addEventListener('click', () => { 12 | $el.requestPointerLock(); 13 | }); 14 | 15 | const fn = e => this.move(e, camera); 16 | document.addEventListener('pointerlockchange', () => { 17 | if (document.pointerLockElement === $el) { 18 | document.addEventListener("mousemove", fn); 19 | } else { 20 | document.removeEventListener("mousemove", fn); 21 | } 22 | }); 23 | } 24 | 25 | move(e, camera) { 26 | this.rx += e.movementX / 200; 27 | this.ry += -e.movementY / 200; 28 | if (this.ry >= Math.PI / 2) { 29 | this.ry = Math.PI / 2; 30 | } 31 | 32 | if (this.ry <= -Math.PI / 2) { 33 | this.ry = -Math.PI / 2; 34 | 35 | } 36 | const rx = this.rx; 37 | const ry = this.ry; 38 | 39 | const view = camera.getView(); 40 | const position = camera.getPosition(); 41 | const l = length(view, position); 42 | 43 | const x = position[0] + l * Math.sin(rx) * Math.cos(ry); 44 | const y = position[1] + l * Math.sin(ry); 45 | const z = position[2] - l * Math.cos(rx) * Math.cos(ry); 46 | camera.lookAt(x, y, z); 47 | 48 | }; 49 | }; -------------------------------------------------------------------------------- /views/demos/light/index.js: -------------------------------------------------------------------------------- 1 | import { Web3D } from '../../web3D/index.js'; 2 | import { CameraPerspective } from '../../web3D/camera/index.js'; 3 | import { GeometryCube } from '../../web3D/geometry/index.js'; 4 | import { MaterialBase } from '../../web3D/material/index.js'; 5 | import { Mesh } from '../../web3D/mesh/index.js'; 6 | import { LightAmbient, LightDirectional, LightPoint } from '../../web3D/light/index.js'; 7 | 8 | const start = async () => { 9 | 10 | const web3D = new Web3D(); 11 | const parentDom = document.querySelector('.web3d'); 12 | const width = parentDom.offsetWidth; 13 | const height = parentDom.offsetHeight; 14 | const web3dDom = await web3D.init({ width, height }); 15 | parentDom.appendChild(web3dDom); 16 | 17 | // 透视投影相机 18 | const camera = new CameraPerspective({ fov: Math.PI / 6, aspect: width / height }); 19 | 20 | // 箱子 21 | const boxMesh = new Mesh( 22 | new GeometryCube({ l: 1, w: 1, h: 1 }), 23 | new MaterialBase({ color: '#2254f4' }) 24 | ); 25 | 26 | // 环境光 27 | const lightAmbient = new LightAmbient({ color: '#fff', strength: 0.3 }); 28 | 29 | // 平行光 30 | const lightDirectional = new LightDirectional({ position: [-5, 0, 2], strength: 0.5 }); 31 | 32 | // 点光源 33 | const lightPoint = new LightPoint({ position: [-1, -1, 2], strength: 1.0 }); 34 | 35 | 36 | web3D.add([ 37 | camera, 38 | boxMesh, 39 | lightAmbient, 40 | lightDirectional, 41 | lightPoint 42 | ]) 43 | 44 | camera.move({ x: 5, y: 5, z: 10 }); 45 | camera.lookAt(0, 0, 0); 46 | 47 | web3D.draw(); 48 | 49 | 50 | }; 51 | 52 | start(); -------------------------------------------------------------------------------- /views/demos/phone/index.js: -------------------------------------------------------------------------------- 1 | import { Web3D } from '../../web3D/index.js'; 2 | import { CameraPerspective } from '../../web3D/camera/index.js'; 3 | import { GeometryCube } from '../../web3D/geometry/index.js'; 4 | import { MaterialPhone } from '../../web3D/material/index.js'; 5 | import { Mesh } from '../../web3D/mesh/index.js'; 6 | import { LightAmbient, LightDirectional, LightPoint } from '../../web3D/light/index.js'; 7 | 8 | const start = async () => { 9 | 10 | const web3D = new Web3D(); 11 | const parentDom = document.querySelector('.web3d'); 12 | const width = parentDom.offsetWidth; 13 | const height = parentDom.offsetHeight; 14 | const web3dDom = await web3D.init({ width, height }); 15 | parentDom.appendChild(web3dDom); 16 | 17 | // 透视投影相机 18 | const camera = new CameraPerspective({ fov: Math.PI / 6, aspect: width / height }); 19 | 20 | // 箱子 21 | const boxMesh = new Mesh( 22 | new GeometryCube({ l: 1, w: 1, h: 1 }), 23 | new MaterialPhone({ color: '#2254f4' }) 24 | ); 25 | 26 | // 环境光 27 | const lightAmbient = new LightAmbient({ color: [1, 1, 1], strength: 0.3 }); 28 | 29 | // 平行光 30 | const lightDirectional = new LightDirectional({ position: [-5, 0, 2], strength: 0.5 }); 31 | 32 | // 点光源 33 | const lightPoint = new LightPoint({ position: [-1, -1, 2], strength: 1.0 }); 34 | 35 | 36 | web3D.add([ 37 | camera, 38 | boxMesh, 39 | lightAmbient, 40 | lightDirectional, 41 | lightPoint 42 | ]) 43 | 44 | camera.move({ x: 5, y: 5, z: 10 }); 45 | camera.lookAt(0, 0, 0); 46 | 47 | web3D.draw(); 48 | 49 | 50 | }; 51 | 52 | start(); -------------------------------------------------------------------------------- /views/demos/model/index.js: -------------------------------------------------------------------------------- 1 | import { Web3D } from '../../web3D/index.js'; 2 | import { CameraPerspective } from '../../web3D/camera/index.js'; 3 | // import { GeometryCube } from '../../web3D/geometry/index.js'; 4 | import { MaterialPhone } from '../../web3D/material/index.js'; 5 | import { Mesh } from '../../web3D/mesh/index.js'; 6 | import { LightAmbient, LightDirectional, LightPoint } from '../../web3D/light/index.js'; 7 | import { loadObj } from '../../web3D/loader/obj.js'; 8 | 9 | const start = async () => { 10 | 11 | const web3D = new Web3D(); 12 | const parentDom = document.querySelector('.web3d'); 13 | const width = parentDom.offsetWidth; 14 | const height = parentDom.offsetHeight; 15 | const web3dDom = await web3D.init({ width, height }); 16 | parentDom.appendChild(web3dDom); 17 | 18 | // 透视投影相机 19 | const camera = new CameraPerspective({ fov: Math.PI / 6, aspect: width / height }); 20 | 21 | // 箱子 22 | const geometry = await loadObj('./models/bunny.obj'); 23 | const boxMesh = new Mesh( 24 | geometry, 25 | new MaterialPhone({ color: '#777' }) 26 | ); 27 | 28 | // 环境光 29 | const lightAmbient = new LightAmbient({ color: [1, 1, 1], strength: 0.3 }); 30 | 31 | // 平行光 32 | const lightDirectional = new LightDirectional({ position: [-5, 0, 2], strength: 0.5 }); 33 | 34 | // 点光源 35 | const lightPoint = new LightPoint({ position: [-1, -1, 2], strength: 1.0 }); 36 | 37 | 38 | web3D.add([ 39 | camera, 40 | boxMesh, 41 | lightAmbient, 42 | lightDirectional, 43 | lightPoint 44 | ]) 45 | 46 | camera.move({ x: 0, y: 5, z: 5 }); 47 | camera.lookAt(0, 0, 0); 48 | 49 | const animated = () => { 50 | boxMesh.rotate({ x: 0, y: 1, z: 0, delta: 0.01 }); 51 | web3D.draw(); 52 | requestAnimationFrame(animated); 53 | }; 54 | 55 | animated(); 56 | 57 | 58 | }; 59 | 60 | start(); -------------------------------------------------------------------------------- /views/demos/texture/index.js: -------------------------------------------------------------------------------- 1 | import { Web3D } from '../../web3D/index.js'; 2 | import { CameraPerspective } from '../../web3D/camera/index.js'; 3 | import { GeometryCube } from '../../web3D/geometry/index.js'; 4 | import { MaterialPhone } from '../../web3D/material/index.js'; 5 | import { Mesh } from '../../web3D/mesh/index.js'; 6 | import { LightAmbient, LightDirectional, LightPoint } from '../../web3D/light/index.js'; 7 | 8 | const loadImage = src => { 9 | return new Promise(resolve => { 10 | const image = new Image(); 11 | image.onload = () => { 12 | resolve(image); 13 | }; 14 | image.src = src; 15 | }); 16 | }; 17 | 18 | const start = async () => { 19 | 20 | const web3D = new Web3D(); 21 | const parentDom = document.querySelector('.web3d'); 22 | const width = parentDom.offsetWidth; 23 | const height = parentDom.offsetHeight; 24 | const web3dDom = await web3D.init({ width, height }); 25 | parentDom.appendChild(web3dDom); 26 | 27 | // 透视投影相机 28 | const camera = new CameraPerspective({ fov: Math.PI / 6, aspect: width / height }); 29 | 30 | // 箱子 31 | const boxImage = await loadImage('./imgs/box.jpeg'); 32 | const boxMesh = new Mesh( 33 | new GeometryCube({ l: 1, w: 1, h: 1 }), 34 | new MaterialPhone({ image: boxImage }) 35 | ); 36 | 37 | // 环境光 38 | const lightAmbient = new LightAmbient({ color: [1, 1, 1], strength: 0.3 }); 39 | 40 | // 平行光 41 | const lightDirectional = new LightDirectional({ position: [-5, 0, 2], strength: 0.5 }); 42 | 43 | // 点光源 44 | const lightPoint = new LightPoint({ position: [-1, -1, 2], strength: 1.0 }); 45 | 46 | 47 | web3D.add([ 48 | camera, 49 | boxMesh, 50 | lightAmbient, 51 | lightDirectional, 52 | lightPoint 53 | ]) 54 | 55 | camera.move({ x: 5, y: 5, z: 10 }); 56 | camera.lookAt(0, 0, 0); 57 | 58 | web3D.draw(); 59 | 60 | 61 | }; 62 | 63 | start(); -------------------------------------------------------------------------------- /views/demos/point/index.js: -------------------------------------------------------------------------------- 1 | import { Web3D } from '../../web3D/index.js'; 2 | import { CameraPerspective } from '../../web3D/camera/index.js'; 3 | import { GeometryCube } from '../../web3D/geometry/index.js'; 4 | import { GeometryPoint } from './geometry-point.js'; 5 | import { MaterialBase } from '../../web3D/material/index.js'; 6 | import { Mesh } from '../../web3D/mesh/index.js'; 7 | import { LightAmbient } from '../../web3D/light/index.js'; 8 | 9 | 10 | const start = async () => { 11 | 12 | const web3D = new Web3D(); 13 | const parentDom = document.querySelector('.web3d'); 14 | const width = parentDom.offsetWidth; 15 | const height = parentDom.offsetHeight; 16 | const web3dDom = await web3D.init({ width, height }); 17 | parentDom.appendChild(web3dDom); 18 | 19 | // 透视投影相机 20 | const camera = new CameraPerspective({ fov: Math.PI / 6, aspect: width / height }); 21 | 22 | 23 | // 箱子 24 | const geometry = new GeometryPoint(); 25 | const mesh = new Mesh( 26 | geometry, 27 | new MaterialBase({ color: '#fff' }), 28 | { drawMode: 'POINTS', pointSize: 4 } 29 | ); 30 | 31 | 32 | mesh.rotate({ x: 1, y: 0, z: 0, delta: -Math.PI / 2.5 }); 33 | 34 | 35 | 36 | // 环境光 37 | const lightAmbient = new LightAmbient({ color: [1, 1, 1], strength: 0.6 }); 38 | 39 | 40 | web3D.add([ 41 | camera, 42 | mesh, 43 | lightAmbient 44 | ]); 45 | 46 | camera.move({ x: 0, y: 0, z: 15 }); 47 | camera.lookAt(0, 0, 0); 48 | 49 | 50 | let s = 0 51 | const animated = () => { 52 | s += 0.01; 53 | const position = geometry.getPosition(); 54 | const newPosition = position.map((p, i) => { 55 | if ((i + 1) % 3 === 0) { 56 | return Math.sin(s + i * 2) / 5; 57 | } 58 | return p; 59 | }); 60 | geometry.setPosition(newPosition); 61 | mesh.updateAttributes(); 62 | 63 | web3D.draw(); 64 | requestAnimationFrame(animated); 65 | }; 66 | 67 | animated(); 68 | }; 69 | 70 | start(); -------------------------------------------------------------------------------- /views/web3D/geometry/geometry-sphare.js: -------------------------------------------------------------------------------- 1 | import { Geometry } from './geometry.js'; 2 | 3 | const push = (arr, x) => { arr[arr.length] = x }; 4 | 5 | export class GeometrySphare extends Geometry { 6 | constructor(options = {}) { 7 | super(); 8 | const { center = [ 0, 0, 0 ], radius = 0.5, latBands = 50, longBands = 50 } = options; 9 | const { position, normal, texture, index } = this.computed(center, radius, latBands, longBands); 10 | this.setPosition(position); 11 | this.setNormal(normal); 12 | this.setTexture(texture); 13 | this.setIndex(index); 14 | } 15 | 16 | computed(center, radius, latBands, longBands) { 17 | const position = []; 18 | const normal = []; 19 | const texture = []; 20 | const index = []; 21 | 22 | for (let letNum = 0; letNum <= latBands; letNum++) { 23 | const theta = letNum * Math.PI / latBands; 24 | const sinTheta = Math.sin(theta); 25 | const cosTheta = Math.cos(theta); 26 | 27 | for (let longNumber = 0; longNumber <= longBands; longNumber++) { 28 | const phi = longNumber * 2 * Math.PI / longBands; 29 | const sinPhi = Math.sin(phi); 30 | const cosPhi = Math.cos(phi); 31 | 32 | const x = cosPhi * sinTheta; 33 | const y = cosTheta; 34 | const z = sinPhi * sinTheta; 35 | 36 | const u = 1 - longNumber / longBands; 37 | const v = 1 - letNum / latBands; 38 | 39 | push(position, radius * x + center[0]); 40 | push(position, radius * y + center[1]); 41 | push(position, radius * z + center[2]); 42 | 43 | push(normal, x); 44 | push(normal, y); 45 | push(normal, z); 46 | 47 | push(texture, u); 48 | push(texture, v); 49 | } 50 | } 51 | 52 | for (let letNum = 0; letNum < latBands; letNum++) { 53 | for (let longNum = 0; longNum < longBands; longNum++) { 54 | const first = letNum * (longBands + 1) + longNum; 55 | const second = first + longBands + 1; 56 | 57 | push(index, first); 58 | push(index, second); 59 | push(index, first + 1); 60 | 61 | push(index, second); 62 | push(index, second + 1); 63 | push(index, first + 1); 64 | } 65 | } 66 | 67 | return { 68 | position, 69 | normal, 70 | texture, 71 | index 72 | }; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /views/web3D/mesh/utlis/shader-data.js: -------------------------------------------------------------------------------- 1 | import { createArrayBuffer, createElementArrayBuffer } from './buffer.js'; 2 | 3 | // 返回attribute类型的数据(index也包括进去) 4 | export const getAttributes = (gl, mesh) => { 5 | const geometry = mesh.getGeometry(); 6 | const material = mesh.getMaterial(); 7 | 8 | const position = new Float32Array(geometry.getPosition()); 9 | const normal = new Float32Array(geometry.getNormal() || position); 10 | const texture = new Float32Array(geometry.getTexture() || position.filter((_, i) => (i + 1) % 3 !== 0)); 11 | const index = new Uint16Array(geometry.getIndex()); 12 | // const color = new Float32Array(material.getColor()); 13 | 14 | // let parseColor = color; 15 | // if (color && color.length === 3) { 16 | // parseColor = position.map((_, i) => color[i % 3]); 17 | // } 18 | 19 | 20 | if (!texture.length) { 21 | console.warn('[web3D wraning] no texture'); 22 | } 23 | 24 | return { 25 | position: { 26 | data: position, 27 | type: 'FLOAT', 28 | n: 3, 29 | buffer: createArrayBuffer(gl, position) 30 | }, 31 | normal: { 32 | data: normal, 33 | type: 'FLOAT', 34 | n: 3, 35 | buffer: createArrayBuffer(gl, normal) 36 | }, 37 | // color: { 38 | // data: parseColor, 39 | // type: 'FLOAT', 40 | // n: 3, 41 | // buffer: createArrayBuffer(gl, parseColor) 42 | // }, 43 | index: { 44 | data: index, 45 | buffer: createElementArrayBuffer(gl, index) 46 | }, 47 | texture: { 48 | texture: gl.createTexture(), 49 | data: texture, 50 | type: 'FLOAT', 51 | n: 2, 52 | buffer: createArrayBuffer(gl, texture) 53 | } 54 | }; 55 | }; 56 | 57 | // 返回uniform类型的数据(index也包括进去) 58 | export const getUniforms = mesh => { 59 | return { 60 | }; 61 | }; 62 | 63 | export const getOther = mesh => { 64 | const material = mesh.getMaterial(); 65 | const image = material.getImage(); 66 | return { 67 | image: { 68 | data: image, 69 | } 70 | }; 71 | }; -------------------------------------------------------------------------------- /views/demos/view/js/keyword/index.js: -------------------------------------------------------------------------------- 1 | const normalize = ([x, y, z], l = 100) => { 2 | const len = Math.sqrt(x * x + y * y + z * z) * l; 3 | return [x / len, y / len, z / len]; 4 | }; 5 | 6 | const cross = (a, b) => { 7 | const [x, y, z] = normalize([a[1] * b[2] - b[1] * a[2], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - b[0] * a[1]], 1); 8 | const len = 100; 9 | return [x / len, y / len, z / len]; 10 | }; 11 | 12 | 13 | const sub = (a, b) => { 14 | return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; 15 | } 16 | 17 | export class Keyword { 18 | start(camera) { 19 | let x = 0; 20 | let y = 0; 21 | let z = 0; 22 | let stop = false; 23 | 24 | document.addEventListener('keyup', e => { 25 | stop = true 26 | }); 27 | 28 | document.addEventListener('keydown', e => { 29 | stop = false; 30 | 31 | const fn = () => { 32 | if (stop) return; 33 | const view = camera.getView(); 34 | const view1 = [view[0], 0, view[2]]; 35 | const position = camera.getPosition(); 36 | const [stepX, stepY, stepZ] = normalize(sub(view, position)); 37 | const [stepX1, stepY1, stepZ1] = cross(view, view1); 38 | switch(e.keyCode) { 39 | case 87: 40 | x = stepX; 41 | y = stepY; 42 | z = stepZ; 43 | break; 44 | case 83: 45 | x = -stepX; 46 | y = -stepY; 47 | z = -stepZ; 48 | break; 49 | case 65: 50 | x = stepX1; 51 | y = stepY1; 52 | z = stepZ1; 53 | break; 54 | case 68: 55 | x = -stepX1; 56 | y = -stepY1; 57 | z = -stepZ1; 58 | break; 59 | }; 60 | camera.move({ x, y, z }); 61 | requestAnimationFrame(fn); 62 | }; 63 | fn(); 64 | }); 65 | } 66 | }; -------------------------------------------------------------------------------- /views/demos/animated/index.js: -------------------------------------------------------------------------------- 1 | import { Web3D } from '../../web3D/index.js'; 2 | import { CameraPerspective } from '../../web3D/camera/index.js'; 3 | import { GeometryCube } from '../../web3D/geometry/index.js'; 4 | import { MaterialPhone } from '../../web3D/material/index.js'; 5 | import { Mesh } from '../../web3D/mesh/index.js'; 6 | import { LightAmbient, LightDirectional, LightPoint } from '../../web3D/light/index.js'; 7 | 8 | const loadImage = src => { 9 | return new Promise(resolve => { 10 | const image = new Image(); 11 | image.onload = () => { 12 | resolve(image); 13 | }; 14 | image.src = src; 15 | }); 16 | }; 17 | 18 | const start = async () => { 19 | 20 | const web3D = new Web3D(); 21 | const parentDom = document.querySelector('.web3d'); 22 | const width = parentDom.offsetWidth; 23 | const height = parentDom.offsetHeight; 24 | const web3dDom = await web3D.init({ width, height }); 25 | parentDom.appendChild(web3dDom); 26 | 27 | // 透视投影相机 28 | const camera = new CameraPerspective({ fov: Math.PI / 6, aspect: width / height }); 29 | 30 | // 箱子 31 | const boxImage = await loadImage('./imgs/box.jpeg'); 32 | const boxMesh = new Mesh( 33 | new GeometryCube({ l: 1, w: 1, h: 1 }), 34 | new MaterialPhone({ image: boxImage }) 35 | ); 36 | 37 | // 环境光 38 | const lightAmbient = new LightAmbient({ color: [1, 1, 1], strength: 0.3 }); 39 | 40 | // 平行光 41 | const lightDirectional = new LightDirectional({ position: [-5, 0, 2], strength: 0.5 }); 42 | 43 | // 点光源 44 | const lightPoint = new LightPoint({ position: [-1, -1, 2], strength: 1.0 }); 45 | 46 | 47 | web3D.add([ 48 | camera, 49 | boxMesh, 50 | lightAmbient, 51 | lightDirectional, 52 | lightPoint 53 | ]); 54 | 55 | 56 | camera.move({ x: 5, y: 5, z: 10 }); 57 | camera.lookAt(0, 0, 0); 58 | 59 | let s = -1; 60 | const animated = () => { 61 | boxMesh.rotate({ x: 1, y: 0, z: 0, delta: 0.01 }); 62 | boxMesh.move({ x: -Math.sin(s) / 100, y: 0, z: 0 }); 63 | s += 0.01; 64 | web3D.draw(); 65 | requestAnimationFrame(animated); 66 | }; 67 | 68 | animated(); 69 | }; 70 | 71 | start(); -------------------------------------------------------------------------------- /views/demos/gun/index.js: -------------------------------------------------------------------------------- 1 | import { Web3D } from '../../web3D/index.js'; 2 | import { CameraPerspective } from '../../web3D/camera/index.js'; 3 | import { MaterialPhone } from '../../web3D/material/index.js'; 4 | import { Mesh } from '../../web3D/mesh/index.js'; 5 | import { LightAmbient, LightDirectional, LightPoint } from '../../web3D/light/index.js'; 6 | import { loadObj } from './utils/index.js'; 7 | 8 | const loadImage = src => { 9 | return new Promise(resolve => { 10 | const image = new Image(); 11 | image.onload = () => { 12 | resolve(image); 13 | }; 14 | image.src = src; 15 | }); 16 | }; 17 | 18 | const start = async () => { 19 | 20 | const web3D = new Web3D(); 21 | const parentDom = document.querySelector('.web3d'); 22 | const width = parentDom.offsetWidth; 23 | const height = parentDom.offsetHeight; 24 | const web3dDom = await web3D.init({ width, height }); 25 | parentDom.appendChild(web3dDom); 26 | 27 | // 透视投影相机 28 | const camera = new CameraPerspective({ fov: Math.PI / 6, aspect: width / height }); 29 | 30 | const [m4Geometrys, awmGeometrys] = await Promise.all([ 31 | loadObj('./models/m4a1.obj'), 32 | loadObj('./models/awm.obj') 33 | ]); 34 | 35 | const hupiImage = await loadImage('./assets/texture.jpeg'); 36 | const m4Meshs = m4Geometrys.map(geometry => { 37 | return new Mesh(geometry, new MaterialPhone({ image: hupiImage })); 38 | }); 39 | 40 | const micaiImage = await loadImage('./assets/micai.jpg'); 41 | const awmMeshs = awmGeometrys.map(geometry => { 42 | return new Mesh(geometry, new MaterialPhone({ image: micaiImage })); 43 | }); 44 | 45 | // 环境光 46 | const lightAmbient = new LightAmbient({ color: [1, 1, 1], strength: 0.3 }); 47 | 48 | // 平行光 49 | const lightDirectional = new LightDirectional({ position: [10, 0, 2], strength: 0.5 }); 50 | 51 | // 点光源 52 | const lightPoint = new LightPoint({ position: [10, 10, 2], strength: 1.0 }); 53 | 54 | web3D.add([ 55 | camera, 56 | ...m4Meshs, 57 | ...awmMeshs, 58 | lightAmbient, 59 | lightDirectional, 60 | lightPoint 61 | ]); 62 | 63 | camera.move({ x: 100, y: 50, z: 100 }); 64 | camera.lookAt(-10, 0, 0); 65 | m4Meshs[0].move({ x: 20 }); 66 | 67 | const animated = () => { 68 | // awmMesh.rotate({ x: 0, y: 1, z: 0, delta: 0.01 }); 69 | web3D.draw(); 70 | requestAnimationFrame(animated); 71 | }; 72 | 73 | animated(); 74 | 75 | 76 | }; 77 | 78 | start(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Web3D 2 | 3 | #### webgl打造简单3d渲染框架 4 | 5 | [在线所有示例](https://hhzzcc.github.io/web3D/views/demos)
6 | 7 | #### 运行 8 | ```bash 9 | # 安装依赖 10 | yarn or npm i 11 | 12 | # 运行 13 | yarn serve or npm run serve 14 | 15 | # 打开浏览器输入 16 | localhost:1234/views/demos 17 | 18 | ``` 19 | 20 | #### 代码示例 21 | ```javascript 22 | // index.js 23 | import { Web3D } from '../../web3D/index.js'; 24 | import { CameraPerspective } from '../../web3D/camera/index.js'; 25 | import { GeometryCube } from '../../web3D/geometry/index.js'; 26 | import { MaterialBase } from '../../web3D/material/index.js'; 27 | import { Mesh } from '../../web3D/mesh/index.js'; 28 | import { LightAmbient, LightDirectional, LightPoint } from '../../web3D/light/index.js'; 29 | 30 | const start = async () => { 31 | 32 | const web3D = new Web3D(); 33 | const parentDom = document.querySelector('.web3d'); 34 | const width = parentDom.offsetWidth; 35 | const height = parentDom.offsetHeight; 36 | const web3dDom = await web3D.init({ width, height }); 37 | parentDom.appendChild(web3dDom); 38 | 39 | // 透视投影相机 40 | const camera = new CameraPerspective({ fov: Math.PI / 6, aspect: width / height }); 41 | 42 | // 物体 43 | const boxMesh = new Mesh( 44 | // 正方体 45 | new GeometryCube({ l: 1, w: 1, h: 1 }), 46 | // 普通材质 47 | new MaterialBase({ color: '#2254f4' }) 48 | ); 49 | 50 | // 环境光 51 | const lightAmbient = new LightAmbient({ color: '#fff', strength: 0.3 }); 52 | 53 | // 平行光 54 | const lightDirectional = new LightDirectional({ position: [-5, 0, 2], strength: 0.5 }); 55 | 56 | // 点光源 57 | const lightPoint = new LightPoint({ position: [-1, -1, 2], strength: 1.0 }); 58 | 59 | 60 | // 将相机、物体、光线添加到3d场景中 61 | web3D.add([ 62 | camera, 63 | boxMesh, 64 | lightAmbient, 65 | lightDirectional, 66 | lightPoint 67 | ]) 68 | 69 | // 设置相机位置 70 | camera.move({ x: 5, y: 5, z: 10 }); 71 | camera.lookAt(0, 0, 0); 72 | 73 | // 绘制3d场景 74 | web3D.draw(); 75 | 76 | 77 | }; 78 | 79 | start(); 80 | 81 | ``` 82 | 83 | ```html 84 | 85 | 86 | 87 | 88 | 89 | Document 90 | 104 | 105 | 106 |
107 | 108 | 109 | 110 | ``` 111 | 112 | [运行结果](https://hhzzcc.github.io/web3D/views/demos/phone)
113 | -------------------------------------------------------------------------------- /views/demos/group/index.js: -------------------------------------------------------------------------------- 1 | import { Web3D } from '../../web3D/index.js'; 2 | import { CameraPerspective } from '../../web3D/camera/index.js'; 3 | import { GeometryCube, GeometrySphare } from '../../web3D/geometry/index.js'; 4 | import { MaterialBase } from '../../web3D/material/index.js'; 5 | import { Mesh } from '../../web3D/mesh/index.js'; 6 | import { LightAmbient, LightPoint } from '../../web3D/light/index.js'; 7 | import { Group } from '../../web3D/group/index.js'; 8 | 9 | 10 | const loadImage = src => { 11 | return new Promise(resolve => { 12 | const image = new Image(); 13 | image.onload = () => { 14 | resolve(image); 15 | }; 16 | image.src = src; 17 | }); 18 | }; 19 | 20 | const start = async () => { 21 | 22 | const web3D = new Web3D(); 23 | const parentDom = document.querySelector('.web3d'); 24 | const width = parentDom.offsetWidth; 25 | const height = parentDom.offsetHeight; 26 | const web3dDom = await web3D.init({ width, height }); 27 | parentDom.appendChild(web3dDom); 28 | 29 | // 透视投影相机 30 | const camera = new CameraPerspective({ fov: Math.PI / 6, aspect: width / height }); 31 | const image = await loadImage('./imgs/box.jpeg'); 32 | 33 | // 方块1 34 | const boxMesh1 = new Mesh( 35 | new GeometryCube({ l: 1, w: 1, h: 1 }), 36 | new MaterialBase({ color: '#2254f4' }), 37 | { 38 | isOpenShadow: true 39 | } 40 | ); 41 | boxMesh1.move({ x: -1.5, y: 0, z: 0 }); 42 | 43 | // 球1 44 | const sphare1 = new Mesh( 45 | new GeometrySphare(), 46 | new MaterialBase({ image }), 47 | ); 48 | 49 | 50 | // 方块2 51 | const boxMesh2 = new Mesh( 52 | new GeometryCube({ l: 1, w: 1, h: 1 }), 53 | new MaterialBase({ color: '#2254f4' }), 54 | { drawMode: 'LINE_LOOP' } 55 | ); 56 | boxMesh2.move({ x: 1.5, y: 0, z: 0 }); 57 | 58 | // 球2 59 | const sphare2 = new Mesh( 60 | new GeometrySphare(), 61 | new MaterialBase({ image }), 62 | ); 63 | sphare2.move({ x: 3, y: 0, z: 0 }); 64 | 65 | // 环境光 66 | const lightAmbient = new LightAmbient({ color: [1, 1, 1], strength: 0.6 }); 67 | 68 | // 点光源 69 | const lightPoint = new LightPoint({ position: [0, 10, 2], strength: 1.0 }); 70 | 71 | 72 | const group = new Group(); 73 | group.add([boxMesh1, boxMesh2, sphare1, sphare2]); 74 | 75 | 76 | web3D.add([ 77 | camera, 78 | lightAmbient, 79 | lightPoint, 80 | group 81 | ]) 82 | 83 | camera.move({ x: 0, y: 5, z: 20 }); 84 | camera.lookAt(0, 0, 0); 85 | 86 | 87 | let s = -1; 88 | const animated = () => { 89 | boxMesh1.rotate({ x: 0, y: 1, z: 0, delta: 0.01 }); 90 | boxMesh2.rotate({ x: 1, y: 0, z: 0, delta: 0.01 }); 91 | group.rotate({ x: 1, y: 1, z: 0, delta: 0.01 }); 92 | s += 0.01; 93 | web3D.draw(); 94 | requestAnimationFrame(animated); 95 | }; 96 | 97 | animated(); 98 | }; 99 | 100 | start(); -------------------------------------------------------------------------------- /views/demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | web3D 7 | 49 | 50 | 51 | 54 | 55 | 101 | 102 | -------------------------------------------------------------------------------- /views/web3D/shader/normal/fragment-shader.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | // 归一化后的法线 4 | varying vec3 vNormal; 5 | varying vec3 vColor; 6 | varying vec3 vPosition; 7 | varying vec2 vTexture; 8 | varying vec4 vPositionFromLight; 9 | varying float vDist; 10 | 11 | uniform vec3 cameraPosition; 12 | 13 | uniform int useAmbientLight; 14 | uniform vec3 ambientLightColor; 15 | 16 | uniform int useDirectionalLight; 17 | uniform vec3 directionalLightColor; 18 | uniform vec3 directionalLightPosition; 19 | 20 | uniform int usePointLight; 21 | uniform vec3 pointLightColor; 22 | uniform vec3 pointLightPosition; 23 | 24 | uniform int usePhoneMaterial; 25 | 26 | uniform sampler2D uSampler; 27 | 28 | uniform int useShadow; 29 | uniform sampler2D uShadowSampler; 30 | 31 | uniform int useFog; 32 | uniform vec3 fogColor; 33 | uniform vec2 fogDist; 34 | 35 | 36 | float computeShadow(vec3 lightPosition) { 37 | vec3 projCoords = vPositionFromLight.xyz / vPositionFromLight.w / 2.0 + 0.5; 38 | // float closestDepth = 0.0; 39 | float closestDepth = texture2D(uShadowSampler, projCoords.xy).r; 40 | float currentDepth = projCoords.z; 41 | float bias = max(0.05 * (1.0 - dot(vNormal.xyz, normalize(lightPosition))), 0.005); 42 | // for(float y = -2.0; y <= 2.0; y += 1.0) { 43 | // for(float x = -2.0; x <= 2.0; x += 1.0) { 44 | // closestDepth += texture2D(uShadowSampler, projCoords.xy + vec2(x, y) / 2048.0).r; 45 | // } 46 | // } 47 | float shadow = currentDepth - bias > (closestDepth) ? 0.6 : 0.0; 48 | // disable shadow outside the shadow camera 49 | if (projCoords.z > 1.0) shadow = 0.0; 50 | return 1.0 - shadow; 51 | } 52 | 53 | void main() { 54 | vec3 lightColor = vec3(0, 0, 0); 55 | 56 | // 环境光 57 | if (useAmbientLight == 1) { 58 | vec3 aColor = ambientLightColor; 59 | lightColor += aColor; 60 | } 61 | 62 | // 平行光 63 | if (useDirectionalLight == 1) { 64 | float dDotn = max(dot(normalize(directionalLightPosition), vNormal.xyz), 0.0); 65 | vec3 dColor = directionalLightColor * dDotn; 66 | lightColor += dColor; 67 | } 68 | 69 | // 点光源 70 | if (usePointLight == 1) { 71 | vec3 lightDirection = pointLightPosition - vPosition; 72 | float lDotn = max(dot(normalize(lightDirection), vNormal.xyz), 0.0); 73 | vec3 pColor = pointLightColor * lDotn; 74 | lightColor += pColor; 75 | } 76 | 77 | // 点光源产生镜面高光 78 | if (usePhoneMaterial == 1) { 79 | vec3 reflectDir = reflect(normalize(vPosition - pointLightPosition), vNormal.xyz); 80 | float cDotr = pow(max(dot(normalize(cameraPosition), normalize(reflectDir)), 0.0), 30.0); 81 | vec3 sColor = pointLightColor * cDotr; 82 | lightColor += sColor; 83 | } 84 | 85 | // 纹理 86 | vec4 fragColor = vec4(lightColor, 1.0) * texture2D(uSampler, vTexture); 87 | 88 | // 阴影 89 | if (useShadow == 1) { 90 | float visibility = computeShadow(pointLightPosition); 91 | fragColor = vec4(fragColor.xyz * visibility, fragColor.w); 92 | } 93 | 94 | 95 | // 雾化 96 | if (useFog == 1) { 97 | float fogFacotr = clamp((fogDist.y - vDist) / (fogDist.y, fogDist.x), 0.0, 1.0); 98 | fragColor = vec4(mix(fogColor, vec3(fragColor), fogFacotr), fragColor.w); 99 | } 100 | 101 | gl_FragColor = fragColor; 102 | 103 | } -------------------------------------------------------------------------------- /views/demos/view/index.js: -------------------------------------------------------------------------------- 1 | import { Web3D } from '../../web3D/index.js'; 2 | import { CameraPerspective } from '../../web3D/camera/index.js'; 3 | import { GeometryCube, GeometrySphare, GeometryFace } from '../../web3D/geometry/index.js'; 4 | import { MaterialBase, MaterialPhone } from '../../web3D/material/index.js'; 5 | import { Mesh } from '../../web3D/mesh/index.js'; 6 | import { LightAmbient, LightDirectional, LightPoint } from '../../web3D/light/index.js'; 7 | 8 | import { Pointer } from './js/pointer/index.js'; 9 | import { Keyword } from './js/keyword/index.js'; 10 | 11 | 12 | const loadImage = src => { 13 | return new Promise(resolve => { 14 | const image = new Image(); 15 | image.onload = () => { 16 | resolve(image); 17 | }; 18 | image.src = src; 19 | }); 20 | } 21 | 22 | 23 | const start = async () => { 24 | const web3D = new Web3D(); 25 | const parentDom = document.querySelector('.web3d'); 26 | const width = parentDom.offsetWidth; 27 | const height = parentDom.offsetHeight; 28 | const web3dDom = await web3D.init({ width, height }); 29 | parentDom.appendChild(web3dDom); 30 | 31 | // 透视投影相机 32 | const camera = new CameraPerspective({ fov: Math.PI / 6, aspect: width / height }); 33 | 34 | 35 | // 地球 36 | const earthImage = await loadImage('./imgs/earth.jpeg'); 37 | const earthMesh = new Mesh( 38 | new GeometrySphare({ radius: 1 }), 39 | new MaterialBase({ image: earthImage }) 40 | ); 41 | 42 | const skyBack = await loadImage('./imgs/sky/back.jpg'); 43 | const faceBackMesh = new Mesh( 44 | new GeometryFace({ l: 10, w: 10 }), 45 | new MaterialPhone({ image: skyBack }) 46 | ); 47 | 48 | const skyBottom = await loadImage('./imgs/sky/down.jpg'); 49 | const faceBottomMesh = new Mesh( 50 | new GeometryFace({ l: 10, w: 10 }), 51 | new MaterialPhone({ image: skyBottom }) 52 | ); 53 | 54 | const skyLeft = await loadImage('./imgs/sky/left.jpg'); 55 | const faceLeftMesh = new Mesh( 56 | new GeometryFace({ l: 10, w: 10 }), 57 | new MaterialPhone({ image: skyLeft }) 58 | ); 59 | 60 | // 环境光 61 | const lightAmbient = new LightAmbient({ color: '#fff', strength: 0.3 }); 62 | 63 | // 平行光 64 | const lightDirectional = new LightDirectional({ position: [-5, 0, 2], strength: 0.5 }); 65 | 66 | // 点光源 67 | const lightPoint = new LightPoint({ position: [0, 0, 2], strength: 0.5 }); 68 | 69 | 70 | web3D.add([ 71 | camera, 72 | earthMesh, 73 | faceBackMesh, 74 | faceBottomMesh, 75 | faceLeftMesh, 76 | lightAmbient, 77 | lightDirectional, 78 | lightPoint 79 | ]); 80 | 81 | camera.move({ x: 0, y: 0, z: 10 }); 82 | camera.lookAt(0, 0, 0); 83 | earthMesh.move({ x: 0, y: 0, z: 0 }); 84 | faceBackMesh.move({ x: 0, y: 0, z: -5 }); 85 | 86 | faceBottomMesh.rotate({ x: 1, y: 0, z: 0, delta: -Math.PI / 2 }); 87 | faceBottomMesh.move({ x: 0, y: -5, z: 0 }); 88 | 89 | faceLeftMesh.rotate({ x: 0, y: 1, z: 0, delta: -Math.PI / 2 }); 90 | faceLeftMesh.move({ x: -5, y: 0, z: 0 }); 91 | 92 | const pointer = new Pointer(); 93 | const keyword = new Keyword(); 94 | pointer.start(camera); 95 | keyword.start(camera); 96 | 97 | 98 | const animated = () => { 99 | earthMesh.rotate({ x: 0, y: 1, z: 0, delta: 0.01 }); 100 | web3D.draw(); 101 | requestAnimationFrame(animated); 102 | }; 103 | 104 | animated(); 105 | }; 106 | 107 | start(); -------------------------------------------------------------------------------- /views/demos/shadow/index.js: -------------------------------------------------------------------------------- 1 | import { Web3D } from '../../web3D/index.js'; 2 | import { CameraPerspective } from '../../web3D/camera/index.js'; 3 | import { GeometryCube, GeometrySphare, GeometryFace } from '../../web3D/geometry/index.js'; 4 | import { MaterialBase, MaterialPhone } from '../../web3D/material/index.js'; 5 | import { Mesh } from '../../web3D/mesh/index.js'; 6 | import { LightAmbient, LightPoint } from '../../web3D/light/index.js'; 7 | import { Group } from '../../web3D/group/index.js'; 8 | import { Pointer } from './js/pointer/index.js'; 9 | 10 | const loadImage = src => { 11 | return new Promise(resolve => { 12 | const image = new Image(); 13 | image.onload = () => { 14 | resolve(image); 15 | }; 16 | image.src = src; 17 | }); 18 | }; 19 | 20 | const start = async () => { 21 | 22 | const web3D = new Web3D(); 23 | const parentDom = document.querySelector('.web3d'); 24 | const width = parentDom.offsetWidth; 25 | const height = parentDom.offsetHeight; 26 | const web3dDom = await web3D.init({ width, height }); 27 | parentDom.appendChild(web3dDom); 28 | 29 | // 透视投影相机 30 | const camera = new CameraPerspective({ fov: Math.PI / 6, aspect: width / height }); 31 | const image = await loadImage('./imgs/box.jpeg'); 32 | 33 | // 方块1 34 | const boxMesh1 = new Mesh( 35 | new GeometryCube({ l: 1, w: 1, h: 1 }), 36 | new MaterialBase({ color: '#2254f4' }), 37 | { isOpenShadow: true } 38 | ); 39 | boxMesh1.move({ x: -1.5, y: 0, z: 0 }); 40 | 41 | // 球1 42 | const sphare1 = new Mesh( 43 | new GeometrySphare(), 44 | new MaterialBase({ image }), 45 | { isOpenShadow: true } 46 | ); 47 | 48 | 49 | // 方块2 50 | const boxMesh2 = new Mesh( 51 | new GeometryCube({ l: 1, w: 1, h: 1 }), 52 | new MaterialBase({ color: '#2254f4' }), 53 | { 54 | isOpenShadow: true, 55 | drawMode: 'LINE_LOOP' } 56 | ); 57 | boxMesh2.move({ x: 1.5, y: 0, z: 0 }); 58 | 59 | // 球2 60 | const sphare2 = new Mesh( 61 | new GeometrySphare(), 62 | new MaterialBase({ image }), 63 | { isOpenShadow: true } 64 | ); 65 | sphare2.move({ x: 3, y: 0, z: 0 }); 66 | 67 | const faceMeshBottom = new Mesh( 68 | new GeometryFace({ l: 30, w: 30 }), 69 | new MaterialPhone({ color: '#fff' }) 70 | ); 71 | faceMeshBottom.rotate({ x: 1, y: 0, z: 0, delta: -Math.PI / 2 }); 72 | faceMeshBottom.move({ x: 0, y: -5, z: 0 }); 73 | 74 | const faceMeshBack = new Mesh( 75 | new GeometryFace({ l: 30, w: 30 }), 76 | new MaterialPhone({ color: '#fff' }) 77 | ); 78 | faceMeshBack.move({ x: 0, y: 0, z: -10 }); 79 | 80 | // 环境光 81 | const lightAmbient = new LightAmbient({ color: [1, 1, 1], strength: 0.2 }); 82 | 83 | // 点光源 84 | const lightPoint = new LightPoint({ position: [0, 4, 2], strength: 1.0 }); 85 | 86 | 87 | const group = new Group(); 88 | group.add([boxMesh1, boxMesh2, sphare1, sphare2]); 89 | 90 | web3D.add([ 91 | camera, 92 | lightAmbient, 93 | lightPoint, 94 | group, 95 | faceMeshBottom, 96 | faceMeshBack, 97 | ]) 98 | 99 | camera.move({ x: 0, y: 2, z: 25 }); 100 | camera.lookAt(0, 0, 0); 101 | 102 | const pointer = new Pointer(); 103 | pointer.start(camera); 104 | 105 | const animated = () => { 106 | boxMesh1.rotate({ x: 0, y: 1, z: 0, delta: 0.01 }); 107 | boxMesh2.rotate({ x: 1, y: 0, z: 0, delta: 0.01 }); 108 | group.rotate({ x: 1, y: 1, z: 0, delta: 0.01 }); 109 | web3D.draw(); 110 | requestAnimationFrame(animated); 111 | }; 112 | 113 | animated(); 114 | }; 115 | 116 | start(); -------------------------------------------------------------------------------- /views/web3D/geometry/geometry-cube.js: -------------------------------------------------------------------------------- 1 | import { Geometry } from './geometry.js'; 2 | 3 | export class GeometryCube extends Geometry { 4 | constructor(options = {}) { 5 | super(); 6 | const { l = 1, w = 1, h = 1 } = options; 7 | const position = this.computedPosition(l, w, h); 8 | const normal = this.computedNormal(); 9 | const texture = this.computedTexture(); 10 | const index = this.computedIndex(); 11 | this.setPosition(position); 12 | this.setNormal(normal); 13 | this.setTexture(texture); 14 | this.setIndex(index); 15 | } 16 | 17 | computedPosition(l, w, h) { 18 | const position = [ 19 | // Front face 20 | -l / 2, -w / 2, h / 2, 21 | l / 2, -w / 2, h / 2, 22 | l / 2, w / 2, h / 2, 23 | -l / 2, w / 2, h / 2, 24 | 25 | // Back face 26 | -l / 2, -w / 2, -h / 2, 27 | -l / 2, w / 2, -h / 2, 28 | l / 2, w / 2, -h / 2, 29 | l / 2, -w / 2, -h / 2, 30 | 31 | // Top face 32 | -l / 2, w / 2, -h / 2, 33 | -l / 2, w / 2, h / 2, 34 | l / 2, w / 2, h / 2, 35 | l / 2, w / 2, -h / 2, 36 | 37 | // Bottom face 38 | -l / 2, -w / 2, -h / 2, 39 | l / 2, -w / 2, -h / 2, 40 | l / 2, -w / 2, h / 2, 41 | -l / 2, -w / 2, h / 2, 42 | 43 | // Right face 44 | l / 2, -w / 2, -h / 2, 45 | l / 2, w / 2, -h / 2, 46 | l / 2, w / 2, h / 2, 47 | l / 2, -w / 2, h / 2, 48 | 49 | // Left face 50 | -l / 2, -w / 2, -h / 2, 51 | -l / 2, -w / 2, h / 2, 52 | -l / 2, w / 2, h / 2, 53 | -l / 2, w / 2, -h / 2 54 | ]; 55 | return position; 56 | } 57 | 58 | computedNormal() { 59 | const normal = [ 60 | // Front 61 | 0.0, 0.0, 1.0, 62 | 0.0, 0.0, 1.0, 63 | 0.0, 0.0, 1.0, 64 | 0.0, 0.0, 1.0, 65 | 66 | // Back 67 | 0.0, 0.0, -1.0, 68 | 0.0, 0.0, -1.0, 69 | 0.0, 0.0, -1.0, 70 | 0.0, 0.0, -1.0, 71 | 72 | // Top 73 | 0.0, 1.0, 0.0, 74 | 0.0, 1.0, 0.0, 75 | 0.0, 1.0, 0.0, 76 | 0.0, 1.0, 0.0, 77 | 78 | // Bottom 79 | 0.0, -1.0, 0.0, 80 | 0.0, -1.0, 0.0, 81 | 0.0, -1.0, 0.0, 82 | 0.0, -1.0, 0.0, 83 | 84 | // Right 85 | 1.0, 0.0, 0.0, 86 | 1.0, 0.0, 0.0, 87 | 1.0, 0.0, 0.0, 88 | 1.0, 0.0, 0.0, 89 | 90 | // Left 91 | -1.0, 0.0, 0.0, 92 | -1.0, 0.0, 0.0, 93 | -1.0, 0.0, 0.0, 94 | -1.0, 0.0, 0.0 95 | ]; 96 | return normal; 97 | } 98 | 99 | computedTexture() { 100 | const texture = [ 101 | // Front 102 | 0.0, 0.0, 103 | 1.0, 0.0, 104 | 1.0, 1.0, 105 | 0.0, 1.0, 106 | // Back 107 | 0.0, 0.0, 108 | 1.0, 0.0, 109 | 1.0, 1.0, 110 | 0.0, 1.0, 111 | // Top 112 | 0.0, 0.0, 113 | 1.0, 0.0, 114 | 1.0, 1.0, 115 | 0.0, 1.0, 116 | // Bottom 117 | 0.0, 0.0, 118 | 1.0, 0.0, 119 | 1.0, 1.0, 120 | 0.0, 1.0, 121 | // Right 122 | 0.0, 0.0, 123 | 1.0, 0.0, 124 | 1.0, 1.0, 125 | 0.0, 1.0, 126 | // Left 127 | 0.0, 0.0, 128 | 1.0, 0.0, 129 | 1.0, 1.0, 130 | 0.0, 1.0 131 | ]; 132 | return texture; 133 | } 134 | 135 | computedIndex() { 136 | const index = [ 137 | 0, 1, 2, 0, 2, 3, // Front 138 | 4, 5, 6, 4, 6, 7, // Back 139 | 8, 9, 10, 8, 10, 11, // Top 140 | 12, 13, 14, 12, 14, 15, // Bottom 141 | 16, 17, 18, 16, 18, 19, // Right 142 | 20, 21, 22, 20, 22, 23 // Left 143 | ]; 144 | return index; 145 | } 146 | 147 | 148 | }; -------------------------------------------------------------------------------- /views/web3D/mesh/index.js: -------------------------------------------------------------------------------- 1 | import { getAttributes, getUniforms, getOther } from './utlis/shader-data.js'; 2 | import { create, translate, rotate, invert, transpose, multiply } from '../utils/math.js'; 3 | import { parseColor } from '../utils/parse.js'; 4 | 5 | export class Mesh { 6 | constructor(geometry, material, options = {}) { 7 | const { 8 | isOpenShadow = false, 9 | fogColor, 10 | fogDist, 11 | drawMode = 'TRIANGLES', 12 | drawType = 'drawElements', 13 | pointSize = 1 } = options; 14 | this.isOpenShadow = isOpenShadow; 15 | this.fogColor = fogColor && parseColor(fogColor); 16 | this.fogDist = fogDist; 17 | this.drawMode = drawMode; 18 | this.drawType = drawType; 19 | this.pointSize = pointSize; 20 | this.init(geometry, material); 21 | } 22 | 23 | init(geometry, material) { 24 | this.geometry = geometry; 25 | this.material = material; 26 | 27 | this.position = [0, 0, 0]; 28 | 29 | this.rotateX = 0; 30 | this.rotateY = 0; 31 | this.rotateZ = 0; 32 | this.rotateDelta = 0; 33 | 34 | this.revolutionX = 0; 35 | this.revolutionY = 0; 36 | this.revolutionZ = 0; 37 | this.revolutionDelta = 0; 38 | 39 | // 所有矩阵总和 40 | this.meshMatrix = create(); 41 | // 平移矩阵 42 | this.translateMatrix = create(); 43 | // 自转矩阵 44 | this.rotateMatrix = create(); 45 | // 公转矩阵 46 | this.revolutionMatrix = create(); 47 | // 法线矩阵 48 | this.normalMatrix = create(); 49 | 50 | this.attributes = null; 51 | 52 | this.gl = null; 53 | } 54 | 55 | setFogColor(fogColor) { 56 | this.fogColor = parseColor(fogColor); 57 | } 58 | 59 | setFogDist(fogDist) { 60 | this.fogDist = parseColor(fogDist); 61 | } 62 | 63 | // 设置顶点数据对应shader attribute类型数据,需要传入gl,生成相应buffer 64 | setAttributes(gl) { 65 | if (!this.gl) { 66 | this.gl = gl; 67 | } 68 | this.attributes = getAttributes(gl, this); 69 | } 70 | 71 | getAttributes() { 72 | return this.attributes; 73 | } 74 | 75 | updateAttributes() { 76 | if (this.gl) { 77 | this.attributes = getAttributes(this.gl, this); 78 | } 79 | } 80 | 81 | setGeometry(geometry) { 82 | this.geometry = geometry; 83 | } 84 | 85 | getGeometry() { 86 | return this.geometry; 87 | } 88 | 89 | setMaterial(material) { 90 | this.material = material; 91 | } 92 | 93 | getMaterial() { 94 | return this.material; 95 | } 96 | 97 | getMeshMatrix() { 98 | return this.meshMatrix; 99 | } 100 | 101 | getNormalMatrix() { 102 | return this.normalMatrix; 103 | } 104 | 105 | getPosition() { 106 | return this.position; 107 | } 108 | 109 | // 平移 110 | move({ x, y, z }) { 111 | this.position = [ 112 | (x || 0) + this.position[0], 113 | (y || 0) + this.position[1], 114 | (z || 0) + this.position[2] 115 | ]; 116 | translate(this.translateMatrix, create(), this.position); 117 | } 118 | 119 | // 自转 120 | rotate({ x, y, z, delta }) { 121 | this.rotateX = x; 122 | this.rotateY = y; 123 | this.rotateZ = z; 124 | this.rotateDelta += delta; 125 | rotate(this.rotateMatrix, create(), this.rotateDelta, [this.rotateX, this.rotateY, this.rotateZ]); 126 | } 127 | 128 | // 公转 129 | revolution({ x, y, z, delta }) { 130 | this.revolutionX = x; 131 | this.revolutionY = y; 132 | this.revolutionZ = z; 133 | this.revolutionDelta += delta; 134 | rotate(this.revolutionMatrix, create(), this.revolutionDelta, [this.revolutionX, this.revolutionY, this.revolutionZ]); 135 | } 136 | 137 | // 更新所有矩阵(draw调用) 138 | computedMeshMatrix() { 139 | const meshMatrix = create(); 140 | multiply(meshMatrix, this.translateMatrix, this.rotateMatrix); 141 | multiply(meshMatrix, this.revolutionMatrix, meshMatrix); 142 | invert(this.normalMatrix, meshMatrix); 143 | transpose(this.normalMatrix, this.normalMatrix); 144 | this.meshMatrix = meshMatrix; 145 | } 146 | }; -------------------------------------------------------------------------------- /views/demos/gun/utils/index.js: -------------------------------------------------------------------------------- 1 | import { Geometry } from '../../../web3D/geometry/index.js'; 2 | 3 | // https://webglfundamentals.org/webgl/lessons/webgl-load-obj.html 4 | const parseOBJ = (text) => { 5 | // because indices are base 1 let's just fill in the 0th data 6 | const objPositions = [[0, 0, 0]]; 7 | const objTexcoords = [[0, 0]]; 8 | const objNormals = [[0, 0, 0]]; 9 | 10 | // same order as `f` indices 11 | const objVertexData = [ 12 | objPositions, 13 | objTexcoords, 14 | objNormals, 15 | ]; 16 | 17 | // same order as `f` indices 18 | let webglVertexData = [ 19 | [], // positions 20 | [], // texcoords 21 | [], // normals 22 | [], //index 23 | ]; 24 | 25 | const materialLibs = []; 26 | const geometries = []; 27 | let geometry; 28 | let groups = ['default']; 29 | let material = 'default'; 30 | let object = 'default'; 31 | 32 | const noop = () => {}; 33 | 34 | function newGeometry() { 35 | // If there is an existing geometry and it's 36 | // not empty then start a new one. 37 | if (geometry && geometry.data.position.length) { 38 | geometry = undefined; 39 | } 40 | } 41 | 42 | function setGeometry() { 43 | if (!geometry) { 44 | const position = []; 45 | const texcoord = []; 46 | const normal = []; 47 | const index = []; 48 | webglVertexData = [ 49 | position, 50 | texcoord, 51 | normal, 52 | index, 53 | ]; 54 | geometry = { 55 | object, 56 | groups, 57 | material, 58 | data: { 59 | position, 60 | texcoord, 61 | normal, 62 | index 63 | }, 64 | }; 65 | geometries.push(geometry); 66 | } 67 | } 68 | 69 | const idToIndexMap = {} 70 | let webglIndices = []; 71 | 72 | function addVertex(vert) { 73 | const ptn = vert.split('/'); 74 | // first convert all the indices to positive indices 75 | const indices = ptn.map((objIndexStr, i) => { 76 | if (!objIndexStr) { 77 | return; 78 | } 79 | const objIndex = parseInt(objIndexStr); 80 | return objIndex + (objIndex >= 0 ? 0 : objVertexData[i].length); 81 | }); 82 | // now see that particular combination of position,texcoord,normal 83 | // already exists 84 | const id = indices.join(','); 85 | let vertIndex = idToIndexMap[id]; 86 | if (!vertIndex) { 87 | // No. Add it. 88 | vertIndex = webglVertexData[0].length / 3; 89 | idToIndexMap[id] = vertIndex; 90 | indices.forEach((index, i) => { 91 | if (index !== undefined) { 92 | webglVertexData[i].push(...objVertexData[i][index]); 93 | } 94 | }) 95 | } 96 | webglIndices.push(vertIndex); 97 | } 98 | 99 | const keywords = { 100 | v(parts) { 101 | objPositions.push(parts.map(parseFloat)); 102 | }, 103 | vn(parts) { 104 | objNormals.push(parts.map(parseFloat)); 105 | }, 106 | vt(parts) { 107 | // should check for missing v and extra w? 108 | objTexcoords.push(parts.map(parseFloat)); 109 | }, 110 | f(parts) { 111 | setGeometry(); 112 | const numTriangles = parts.length - 2; 113 | for (let tri = 0; tri < numTriangles; ++tri) { 114 | addVertex(parts[0]); 115 | addVertex(parts[tri + 1]); 116 | addVertex(parts[tri + 2]); 117 | } 118 | geometry.data.index.push(...webglIndices); 119 | webglIndices = []; 120 | }, 121 | s: noop, // smoothing group 122 | mtllib(parts, unparsedArgs) { 123 | // the spec says there can be multiple filenames here 124 | // but many exist with spaces in a single filename 125 | materialLibs.push(unparsedArgs); 126 | }, 127 | usemtl(parts, unparsedArgs) { 128 | material = unparsedArgs; 129 | newGeometry(); 130 | }, 131 | g(parts) { 132 | groups = parts; 133 | newGeometry(); 134 | }, 135 | o(parts, unparsedArgs) { 136 | object = unparsedArgs; 137 | newGeometry(); 138 | }, 139 | }; 140 | 141 | const keywordRE = /(\w*)(?: )*(.*)/; 142 | const lines = text.split('\n'); 143 | for (let lineNo = 0; lineNo < lines.length; ++lineNo) { 144 | const line = lines[lineNo].trim(); 145 | if (line === '' || line.startsWith('#')) { 146 | continue; 147 | } 148 | const m = keywordRE.exec(line); 149 | if (!m) { 150 | continue; 151 | } 152 | const [, keyword, unparsedArgs] = m; 153 | const parts = line.split(/\s+/).slice(1); 154 | const handler = keywords[keyword]; 155 | if (!handler) { 156 | console.warn('unhandled keyword:', keyword); // eslint-disable-line no-console 157 | continue; 158 | } 159 | handler(parts, unparsedArgs); 160 | } 161 | 162 | // remove any arrays that have no entries. 163 | for (const geometry of geometries) { 164 | geometry.data = Object.fromEntries( 165 | Object.entries(geometry.data).filter(([, array]) => array.length > 0)); 166 | } 167 | 168 | return { 169 | geometries, 170 | materialLibs, 171 | }; 172 | }; 173 | 174 | 175 | export const loadObj = async url => { 176 | const data = await fetch(url).then(res => res.text()); 177 | const obj = parseOBJ(data); 178 | const geometries = obj.geometries.map(g => { 179 | const { position, normal, texcoord, index } = g.data; 180 | const geometry = new Geometry(); 181 | geometry.setPosition(position); 182 | geometry.setNormal(normal); 183 | geometry.setTexture(texcoord); 184 | geometry.setIndex(index); 185 | return geometry; 186 | }); 187 | return geometries; 188 | }; -------------------------------------------------------------------------------- /views/web3D/utils/math.js: -------------------------------------------------------------------------------- 1 | // Inline from gl-matrix 2 | // https://github.com/toji/gl-matrix 3 | const ARRAY_TYPE = (typeof Float32Array !== 'undefined') 4 | ? Float32Array : Array 5 | 6 | const EPSILON = 0.000001 7 | 8 | export const create = () => { 9 | let out = new ARRAY_TYPE(16) 10 | if (ARRAY_TYPE !== Float32Array) { 11 | out[1] = 0 12 | out[2] = 0 13 | out[3] = 0 14 | out[4] = 0 15 | out[6] = 0 16 | out[7] = 0 17 | out[8] = 0 18 | out[9] = 0 19 | out[11] = 0 20 | out[12] = 0 21 | out[13] = 0 22 | out[14] = 0 23 | } 24 | out[0] = 1 25 | out[5] = 1 26 | out[10] = 1 27 | out[15] = 1 28 | return out 29 | } 30 | 31 | export const perspective = (out, fovy, aspect, near, far) => { 32 | let f = 1.0 / Math.tan(fovy / 2) 33 | let nf 34 | out[0] = f / aspect 35 | out[1] = 0 36 | out[2] = 0 37 | out[3] = 0 38 | out[4] = 0 39 | out[5] = f 40 | out[6] = 0 41 | out[7] = 0 42 | out[8] = 0 43 | out[9] = 0 44 | out[11] = -1 45 | out[12] = 0 46 | out[13] = 0 47 | out[15] = 0 48 | if (far != null && far !== Infinity) { 49 | nf = 1 / (near - far) 50 | out[10] = (far + near) * nf 51 | out[14] = (2 * far * near) * nf 52 | } else { 53 | out[10] = -1 54 | out[14] = -2 * near 55 | } 56 | return out 57 | } 58 | 59 | export const ortho = (out, left, right, bottom, top, near, far) => { 60 | let lr = 1 / (left - right) 61 | let bt = 1 / (bottom - top) 62 | let nf = 1 / (near - far) 63 | out[0] = -2 * lr 64 | out[1] = 0 65 | out[2] = 0 66 | out[3] = 0 67 | out[4] = 0 68 | out[5] = -2 * bt 69 | out[6] = 0 70 | out[7] = 0 71 | out[8] = 0 72 | out[9] = 0 73 | out[10] = 2 * nf 74 | out[11] = 0 75 | out[12] = (left + right) * lr 76 | out[13] = (top + bottom) * bt 77 | out[14] = (far + near) * nf 78 | out[15] = 1 79 | return out 80 | } 81 | 82 | export const identity = (out) => { 83 | out[0] = 1 84 | out[1] = 0 85 | out[2] = 0 86 | out[3] = 0 87 | out[4] = 0 88 | out[5] = 1 89 | out[6] = 0 90 | out[7] = 0 91 | out[8] = 0 92 | out[9] = 0 93 | out[10] = 1 94 | out[11] = 0 95 | out[12] = 0 96 | out[13] = 0 97 | out[14] = 0 98 | out[15] = 1 99 | return out 100 | } 101 | 102 | export const lookAt = (out, eye, center, up) => { 103 | let x0, x1, x2, y0, y1, y2, z0, z1, z2, len 104 | let eyex = eye[0] 105 | let eyey = eye[1] 106 | let eyez = eye[2] 107 | let upx = up[0] 108 | let upy = up[1] 109 | let upz = up[2] 110 | let centerx = center[0] 111 | let centery = center[1] 112 | let centerz = center[2] 113 | 114 | if (Math.abs(eyex - centerx) < EPSILON && 115 | Math.abs(eyey - centery) < EPSILON && 116 | Math.abs(eyez - centerz) < EPSILON) { 117 | return identity(out) 118 | } 119 | 120 | z0 = eyex - centerx 121 | z1 = eyey - centery 122 | z2 = eyez - centerz 123 | 124 | len = 1 / Math.hypot(z0, z1, z2) 125 | z0 *= len 126 | z1 *= len 127 | z2 *= len 128 | 129 | x0 = upy * z2 - upz * z1 130 | x1 = upz * z0 - upx * z2 131 | x2 = upx * z1 - upy * z0 132 | len = Math.hypot(x0, x1, x2) 133 | if (!len) { 134 | x0 = 0 135 | x1 = 0 136 | x2 = 0 137 | } else { 138 | len = 1 / len 139 | x0 *= len 140 | x1 *= len 141 | x2 *= len 142 | } 143 | 144 | y0 = z1 * x2 - z2 * x1 145 | y1 = z2 * x0 - z0 * x2 146 | y2 = z0 * x1 - z1 * x0 147 | 148 | len = Math.hypot(y0, y1, y2) 149 | if (!len) { 150 | y0 = 0 151 | y1 = 0 152 | y2 = 0 153 | } else { 154 | len = 1 / len 155 | y0 *= len 156 | y1 *= len 157 | y2 *= len 158 | } 159 | 160 | out[0] = x0 161 | out[1] = y0 162 | out[2] = z0 163 | out[3] = 0 164 | out[4] = x1 165 | out[5] = y1 166 | out[6] = z1 167 | out[7] = 0 168 | out[8] = x2 169 | out[9] = y2 170 | out[10] = z2 171 | out[11] = 0 172 | out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez) 173 | out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez) 174 | out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez) 175 | out[15] = 1 176 | 177 | return out 178 | } 179 | 180 | export const multiply = (out, a, b) => { 181 | let a00 = a[0]; let a01 = a[1]; let a02 = a[2]; let a03 = a[3] 182 | let a10 = a[4]; let a11 = a[5]; let a12 = a[6]; let a13 = a[7] 183 | let a20 = a[8]; let a21 = a[9]; let a22 = a[10]; let a23 = a[11] 184 | let a30 = a[12]; let a31 = a[13]; let a32 = a[14]; let a33 = a[15] 185 | 186 | // Cache only the current line of the second matrix 187 | let b0 = b[0]; let b1 = b[1]; let b2 = b[2]; let b3 = b[3] 188 | out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30 189 | out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31 190 | out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32 191 | out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33 192 | 193 | b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7] 194 | out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30 195 | out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31 196 | out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32 197 | out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33 198 | 199 | b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11] 200 | out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30 201 | out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31 202 | out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32 203 | out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33 204 | 205 | b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15] 206 | out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30 207 | out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31 208 | out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32 209 | out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33 210 | return out 211 | } 212 | 213 | export const translate = (out, a, [x, y, z]) => { 214 | let a00, a01, a02, a03 215 | let a10, a11, a12, a13 216 | let a20, a21, a22, a23 217 | 218 | if (a === out) { 219 | out[12] = a[0] * x + a[4] * y + a[8] * z + a[12] 220 | out[13] = a[1] * x + a[5] * y + a[9] * z + a[13] 221 | out[14] = a[2] * x + a[6] * y + a[10] * z + a[14] 222 | out[15] = a[3] * x + a[7] * y + a[11] * z + a[15] 223 | } else { 224 | a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3] 225 | a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7] 226 | a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11] 227 | 228 | out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03 229 | out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13 230 | out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23 231 | 232 | out[12] = a00 * x + a10 * y + a20 * z + a[12] 233 | out[13] = a01 * x + a11 * y + a21 * z + a[13] 234 | out[14] = a02 * x + a12 * y + a22 * z + a[14] 235 | out[15] = a03 * x + a13 * y + a23 * z + a[15] 236 | } 237 | 238 | return out 239 | } 240 | 241 | export const rotate = (out, a, rad, [x, y, z]) => { 242 | let len = Math.sqrt(x * x + y * y + z * z) 243 | let s, c, t 244 | let a00, a01, a02, a03 245 | let a10, a11, a12, a13 246 | let a20, a21, a22, a23 247 | let b00, b01, b02 248 | let b10, b11, b12 249 | let b20, b21, b22 250 | 251 | if (len < EPSILON) { return null } 252 | 253 | len = 1 / len 254 | x *= len 255 | y *= len 256 | z *= len 257 | 258 | s = Math.sin(rad) 259 | c = Math.cos(rad) 260 | t = 1 - c 261 | 262 | a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3] 263 | a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7] 264 | a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11] 265 | 266 | // Construct the elements of the rotation matrix 267 | b00 = x * x * t + c; b01 = y * x * t + z * s; b02 = z * x * t - y * s 268 | b10 = x * y * t - z * s; b11 = y * y * t + c; b12 = z * y * t + x * s 269 | b20 = x * z * t + y * s; b21 = y * z * t - x * s; b22 = z * z * t + c 270 | 271 | // Perform rotation-specific matrix multiplication 272 | out[0] = a00 * b00 + a10 * b01 + a20 * b02 273 | out[1] = a01 * b00 + a11 * b01 + a21 * b02 274 | out[2] = a02 * b00 + a12 * b01 + a22 * b02 275 | out[3] = a03 * b00 + a13 * b01 + a23 * b02 276 | out[4] = a00 * b10 + a10 * b11 + a20 * b12 277 | out[5] = a01 * b10 + a11 * b11 + a21 * b12 278 | out[6] = a02 * b10 + a12 * b11 + a22 * b12 279 | out[7] = a03 * b10 + a13 * b11 + a23 * b12 280 | out[8] = a00 * b20 + a10 * b21 + a20 * b22 281 | out[9] = a01 * b20 + a11 * b21 + a21 * b22 282 | out[10] = a02 * b20 + a12 * b21 + a22 * b22 283 | out[11] = a03 * b20 + a13 * b21 + a23 * b22 284 | 285 | if (a !== out) { // If the source and destination differ, copy the unchanged last row 286 | out[12] = a[12] 287 | out[13] = a[13] 288 | out[14] = a[14] 289 | out[15] = a[15] 290 | } 291 | return out 292 | } 293 | 294 | export function scale (out, a, v) { 295 | let x = v[0]; let y = v[1]; let z = v[2] 296 | 297 | out[0] = a[0] * x 298 | out[1] = a[1] * x 299 | out[2] = a[2] * x 300 | out[3] = a[3] * x 301 | out[4] = a[4] * y 302 | out[5] = a[5] * y 303 | out[6] = a[6] * y 304 | out[7] = a[7] * y 305 | out[8] = a[8] * z 306 | out[9] = a[9] * z 307 | out[10] = a[10] * z 308 | out[11] = a[11] * z 309 | out[12] = a[12] 310 | out[13] = a[13] 311 | out[14] = a[14] 312 | out[15] = a[15] 313 | return out 314 | } 315 | 316 | export const transpose = (out, a) => { 317 | // If we are transposing ourselves we can skip a few steps but have to cache some values 318 | if (out === a) { 319 | let a01 = a[1] 320 | let a02 = a[2] 321 | let a03 = a[3] 322 | let a12 = a[6] 323 | let a13 = a[7] 324 | let a23 = a[11] 325 | 326 | out[1] = a[4] 327 | out[2] = a[8] 328 | out[3] = a[12] 329 | out[4] = a01 330 | out[6] = a[9] 331 | out[7] = a[13] 332 | out[8] = a02 333 | out[9] = a12 334 | out[11] = a[14] 335 | out[12] = a03 336 | out[13] = a13 337 | out[14] = a23 338 | } else { 339 | out[0] = a[0] 340 | out[1] = a[4] 341 | out[2] = a[8] 342 | out[3] = a[12] 343 | out[4] = a[1] 344 | out[5] = a[5] 345 | out[6] = a[9] 346 | out[7] = a[13] 347 | out[8] = a[2] 348 | out[9] = a[6] 349 | out[10] = a[10] 350 | out[11] = a[14] 351 | out[12] = a[3] 352 | out[13] = a[7] 353 | out[14] = a[11] 354 | out[15] = a[15] 355 | } 356 | 357 | return out 358 | } 359 | 360 | export const invert = (out, a) => { 361 | let a00 = a[0] 362 | let a01 = a[1] 363 | let a02 = a[2] 364 | let a03 = a[3] 365 | let a10 = a[4] 366 | let a11 = a[5] 367 | let a12 = a[6] 368 | let a13 = a[7] 369 | let a20 = a[8] 370 | let a21 = a[9] 371 | let a22 = a[10] 372 | let a23 = a[11] 373 | let a30 = a[12] 374 | let a31 = a[13] 375 | let a32 = a[14] 376 | let a33 = a[15] 377 | 378 | let b00 = a00 * a11 - a01 * a10 379 | let b01 = a00 * a12 - a02 * a10 380 | let b02 = a00 * a13 - a03 * a10 381 | let b03 = a01 * a12 - a02 * a11 382 | let b04 = a01 * a13 - a03 * a11 383 | let b05 = a02 * a13 - a03 * a12 384 | let b06 = a20 * a31 - a21 * a30 385 | let b07 = a20 * a32 - a22 * a30 386 | let b08 = a20 * a33 - a23 * a30 387 | let b09 = a21 * a32 - a22 * a31 388 | let b10 = a21 * a33 - a23 * a31 389 | let b11 = a22 * a33 - a23 * a32 390 | 391 | // Calculate the determinant 392 | let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06 393 | 394 | if (!det) { 395 | return null 396 | } 397 | det = 1.0 / det 398 | 399 | out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det 400 | out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det 401 | out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det 402 | out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det 403 | out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det 404 | out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det 405 | out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det 406 | out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det 407 | out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det 408 | out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det 409 | out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det 410 | out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det 411 | out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det 412 | out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det 413 | out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det 414 | out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det 415 | 416 | return out 417 | } 418 | 419 | export const rotateX = (out, a, b, c) => { 420 | let [p, r] = [[], []] 421 | // Translate point to the origin 422 | p[0] = a[0] - b[0] 423 | p[1] = a[1] - b[1] 424 | p[2] = a[2] - b[2] 425 | 426 | // perform rotation 427 | r[0] = p[0] 428 | r[1] = p[1] * Math.cos(c) - p[2] * Math.sin(c) 429 | r[2] = p[1] * Math.sin(c) + p[2] * Math.cos(c) 430 | 431 | // translate to correct position 432 | out[0] = r[0] + b[0] 433 | out[1] = r[1] + b[1] 434 | out[2] = r[2] + b[2] 435 | 436 | return out 437 | } 438 | 439 | export const rotateY = (out, a, b, c) => { 440 | let [p, r] = [[], []] 441 | // Translate point to the origin 442 | p[0] = a[0] - b[0] 443 | p[1] = a[1] - b[1] 444 | p[2] = a[2] - b[2] 445 | 446 | // perform rotation 447 | r[0] = p[2] * Math.sin(c) + p[0] * Math.cos(c) 448 | r[1] = p[1] 449 | r[2] = p[2] * Math.cos(c) - p[0] * Math.sin(c) 450 | 451 | // translate to correct position 452 | out[0] = r[0] + b[0] 453 | out[1] = r[1] + b[1] 454 | out[2] = r[2] + b[2] 455 | 456 | return out 457 | } 458 | 459 | export const rotateZ = (out, a, b, c) => { 460 | let [p, r] = [[], []] 461 | // Translate point to the origin 462 | p[0] = a[0] - b[0] 463 | p[1] = a[1] - b[1] 464 | p[2] = a[2] - b[2] 465 | 466 | // perform rotation 467 | r[0] = p[0] * Math.cos(c) - p[1] * Math.sin(c) 468 | r[1] = p[0] * Math.sin(c) + p[1] * Math.cos(c) 469 | r[2] = p[2] 470 | 471 | // translate to correct position 472 | out[0] = r[0] + b[0] 473 | out[1] = r[1] + b[1] 474 | out[2] = r[2] + b[2] 475 | 476 | return out 477 | } 478 | 479 | export function add (out, a, b) { 480 | out[0] = a[0] + b[0] 481 | out[1] = a[1] + b[1] 482 | out[2] = a[2] + b[2] 483 | return out 484 | } 485 | 486 | export function subtract (out, a, b) { 487 | out[0] = a[0] - b[0] 488 | out[1] = a[1] - b[1] 489 | out[2] = a[2] - b[2] 490 | return out 491 | } 492 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | accepts@~1.3.7: 6 | version "1.3.7" 7 | resolved "https://registry.npm.taobao.org/accepts/download/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" 8 | integrity sha1-UxvHJlF6OytB+FACHGzBXqq1B80= 9 | dependencies: 10 | mime-types "~2.1.24" 11 | negotiator "0.6.2" 12 | 13 | array-flatten@1.1.1: 14 | version "1.1.1" 15 | resolved "https://registry.npm.taobao.org/array-flatten/download/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 16 | integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= 17 | 18 | body-parser@1.19.0: 19 | version "1.19.0" 20 | resolved "https://registry.npm.taobao.org/body-parser/download/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" 21 | integrity sha1-lrJwnlfJxOCab9Zqj9l5hE9p8Io= 22 | dependencies: 23 | bytes "3.1.0" 24 | content-type "~1.0.4" 25 | debug "2.6.9" 26 | depd "~1.1.2" 27 | http-errors "1.7.2" 28 | iconv-lite "0.4.24" 29 | on-finished "~2.3.0" 30 | qs "6.7.0" 31 | raw-body "2.4.0" 32 | type-is "~1.6.17" 33 | 34 | bytes@3.1.0: 35 | version "3.1.0" 36 | resolved "https://registry.npm.taobao.org/bytes/download/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" 37 | integrity sha1-9s95M6Ng4FiPqf3oVlHNx/gF0fY= 38 | 39 | content-disposition@0.5.3: 40 | version "0.5.3" 41 | resolved "https://registry.npm.taobao.org/content-disposition/download/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" 42 | integrity sha1-4TDK9+cnkIfFYWwgB9BIVpiYT70= 43 | dependencies: 44 | safe-buffer "5.1.2" 45 | 46 | content-type@~1.0.4: 47 | version "1.0.4" 48 | resolved "https://registry.npm.taobao.org/content-type/download/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 49 | integrity sha1-4TjMdeBAxyexlm/l5fjJruJW/js= 50 | 51 | cookie-signature@1.0.6: 52 | version "1.0.6" 53 | resolved "https://registry.npm.taobao.org/cookie-signature/download/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 54 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= 55 | 56 | cookie@0.4.0: 57 | version "0.4.0" 58 | resolved "https://registry.npm.taobao.org/cookie/download/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" 59 | integrity sha1-vrQ35wIrO21JAZ0IhmUwPr6cFLo= 60 | 61 | debug@2.6.9: 62 | version "2.6.9" 63 | resolved "https://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 64 | integrity sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8= 65 | dependencies: 66 | ms "2.0.0" 67 | 68 | depd@~1.1.2: 69 | version "1.1.2" 70 | resolved "https://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 71 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= 72 | 73 | destroy@~1.0.4: 74 | version "1.0.4" 75 | resolved "https://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 76 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= 77 | 78 | ee-first@1.1.1: 79 | version "1.1.1" 80 | resolved "https://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 81 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= 82 | 83 | encodeurl@~1.0.2: 84 | version "1.0.2" 85 | resolved "https://registry.npm.taobao.org/encodeurl/download/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 86 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= 87 | 88 | escape-html@~1.0.3: 89 | version "1.0.3" 90 | resolved "https://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 91 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= 92 | 93 | etag@~1.8.1: 94 | version "1.8.1" 95 | resolved "https://registry.npm.taobao.org/etag/download/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 96 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= 97 | 98 | express@^4.17.1: 99 | version "4.17.1" 100 | resolved "https://registry.npm.taobao.org/express/download/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" 101 | integrity sha1-RJH8OGBc9R+GKdOcK10Cb5ikwTQ= 102 | dependencies: 103 | accepts "~1.3.7" 104 | array-flatten "1.1.1" 105 | body-parser "1.19.0" 106 | content-disposition "0.5.3" 107 | content-type "~1.0.4" 108 | cookie "0.4.0" 109 | cookie-signature "1.0.6" 110 | debug "2.6.9" 111 | depd "~1.1.2" 112 | encodeurl "~1.0.2" 113 | escape-html "~1.0.3" 114 | etag "~1.8.1" 115 | finalhandler "~1.1.2" 116 | fresh "0.5.2" 117 | merge-descriptors "1.0.1" 118 | methods "~1.1.2" 119 | on-finished "~2.3.0" 120 | parseurl "~1.3.3" 121 | path-to-regexp "0.1.7" 122 | proxy-addr "~2.0.5" 123 | qs "6.7.0" 124 | range-parser "~1.2.1" 125 | safe-buffer "5.1.2" 126 | send "0.17.1" 127 | serve-static "1.14.1" 128 | setprototypeof "1.1.1" 129 | statuses "~1.5.0" 130 | type-is "~1.6.18" 131 | utils-merge "1.0.1" 132 | vary "~1.1.2" 133 | 134 | finalhandler@~1.1.2: 135 | version "1.1.2" 136 | resolved "https://registry.npm.taobao.org/finalhandler/download/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" 137 | integrity sha1-t+fQAP/RGTjQ/bBTUG9uur6fWH0= 138 | dependencies: 139 | debug "2.6.9" 140 | encodeurl "~1.0.2" 141 | escape-html "~1.0.3" 142 | on-finished "~2.3.0" 143 | parseurl "~1.3.3" 144 | statuses "~1.5.0" 145 | unpipe "~1.0.0" 146 | 147 | forwarded@~0.1.2: 148 | version "0.1.2" 149 | resolved "https://registry.npm.taobao.org/forwarded/download/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 150 | integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= 151 | 152 | fresh@0.5.2: 153 | version "0.5.2" 154 | resolved "https://registry.npm.taobao.org/fresh/download/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 155 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= 156 | 157 | http-errors@1.7.2: 158 | version "1.7.2" 159 | resolved "https://registry.npm.taobao.org/http-errors/download/http-errors-1.7.2.tgz?cache=0&sync_timestamp=1593407676273&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhttp-errors%2Fdownload%2Fhttp-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" 160 | integrity sha1-T1ApzxMjnzEDblsuVSkrz7zIXI8= 161 | dependencies: 162 | depd "~1.1.2" 163 | inherits "2.0.3" 164 | setprototypeof "1.1.1" 165 | statuses ">= 1.5.0 < 2" 166 | toidentifier "1.0.0" 167 | 168 | http-errors@~1.7.2: 169 | version "1.7.3" 170 | resolved "https://registry.npm.taobao.org/http-errors/download/http-errors-1.7.3.tgz?cache=0&sync_timestamp=1593407676273&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhttp-errors%2Fdownload%2Fhttp-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" 171 | integrity sha1-bGGeT5xgMIw4UZSYwU+7EKrOuwY= 172 | dependencies: 173 | depd "~1.1.2" 174 | inherits "2.0.4" 175 | setprototypeof "1.1.1" 176 | statuses ">= 1.5.0 < 2" 177 | toidentifier "1.0.0" 178 | 179 | iconv-lite@0.4.24: 180 | version "0.4.24" 181 | resolved "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 182 | integrity sha1-ICK0sl+93CHS9SSXSkdKr+czkIs= 183 | dependencies: 184 | safer-buffer ">= 2.1.2 < 3" 185 | 186 | inherits@2.0.3: 187 | version "2.0.3" 188 | resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 189 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 190 | 191 | inherits@2.0.4: 192 | version "2.0.4" 193 | resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 194 | integrity sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w= 195 | 196 | ipaddr.js@1.9.1: 197 | version "1.9.1" 198 | resolved "https://registry.npm.taobao.org/ipaddr.js/download/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 199 | integrity sha1-v/OFQ+64mEglB5/zoqjmy9RngbM= 200 | 201 | media-typer@0.3.0: 202 | version "0.3.0" 203 | resolved "https://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 204 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= 205 | 206 | merge-descriptors@1.0.1: 207 | version "1.0.1" 208 | resolved "https://registry.npm.taobao.org/merge-descriptors/download/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 209 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= 210 | 211 | methods@~1.1.2: 212 | version "1.1.2" 213 | resolved "https://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 214 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 215 | 216 | mime-db@1.44.0: 217 | version "1.44.0" 218 | resolved "https://registry.npm.taobao.org/mime-db/download/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" 219 | integrity sha1-+hHF6wrKEzS0Izy01S8QxaYnL5I= 220 | 221 | mime-types@~2.1.24: 222 | version "2.1.27" 223 | resolved "https://registry.npm.taobao.org/mime-types/download/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" 224 | integrity sha1-R5SfmOJ56lMRn1ci4PNOUpvsAJ8= 225 | dependencies: 226 | mime-db "1.44.0" 227 | 228 | mime@1.6.0: 229 | version "1.6.0" 230 | resolved "https://registry.npm.taobao.org/mime/download/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 231 | integrity sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE= 232 | 233 | ms@2.0.0: 234 | version "2.0.0" 235 | resolved "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 236 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 237 | 238 | ms@2.1.1: 239 | version "2.1.1" 240 | resolved "https://registry.npm.taobao.org/ms/download/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" 241 | integrity sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo= 242 | 243 | negotiator@0.6.2: 244 | version "0.6.2" 245 | resolved "https://registry.npm.taobao.org/negotiator/download/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" 246 | integrity sha1-/qz3zPUlp3rpY0Q2pkiD/+yjRvs= 247 | 248 | on-finished@~2.3.0: 249 | version "2.3.0" 250 | resolved "https://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 251 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= 252 | dependencies: 253 | ee-first "1.1.1" 254 | 255 | parseurl@~1.3.3: 256 | version "1.3.3" 257 | resolved "https://registry.npm.taobao.org/parseurl/download/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 258 | integrity sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ= 259 | 260 | path-to-regexp@0.1.7: 261 | version "0.1.7" 262 | resolved "https://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 263 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= 264 | 265 | proxy-addr@~2.0.5: 266 | version "2.0.6" 267 | resolved "https://registry.npm.taobao.org/proxy-addr/download/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" 268 | integrity sha1-/cIzZQVEfT8vLGOO0nLK9hS7sr8= 269 | dependencies: 270 | forwarded "~0.1.2" 271 | ipaddr.js "1.9.1" 272 | 273 | qs@6.7.0: 274 | version "6.7.0" 275 | resolved "https://registry.npm.taobao.org/qs/download/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" 276 | integrity sha1-QdwaAV49WB8WIXdr4xr7KHapsbw= 277 | 278 | range-parser@~1.2.1: 279 | version "1.2.1" 280 | resolved "https://registry.npm.taobao.org/range-parser/download/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 281 | integrity sha1-PPNwI9GZ4cJNGlW4SADC8+ZGgDE= 282 | 283 | raw-body@2.4.0: 284 | version "2.4.0" 285 | resolved "https://registry.npm.taobao.org/raw-body/download/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" 286 | integrity sha1-oc5vucm8NWylLoklarWQWeE9AzI= 287 | dependencies: 288 | bytes "3.1.0" 289 | http-errors "1.7.2" 290 | iconv-lite "0.4.24" 291 | unpipe "1.0.0" 292 | 293 | safe-buffer@5.1.2: 294 | version "5.1.2" 295 | resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 296 | integrity sha1-mR7GnSluAxN0fVm9/St0XDX4go0= 297 | 298 | "safer-buffer@>= 2.1.2 < 3": 299 | version "2.1.2" 300 | resolved "https://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 301 | integrity sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo= 302 | 303 | send@0.17.1: 304 | version "0.17.1" 305 | resolved "https://registry.npm.taobao.org/send/download/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" 306 | integrity sha1-wdiwWfeQD3Rm3Uk4vcROEd2zdsg= 307 | dependencies: 308 | debug "2.6.9" 309 | depd "~1.1.2" 310 | destroy "~1.0.4" 311 | encodeurl "~1.0.2" 312 | escape-html "~1.0.3" 313 | etag "~1.8.1" 314 | fresh "0.5.2" 315 | http-errors "~1.7.2" 316 | mime "1.6.0" 317 | ms "2.1.1" 318 | on-finished "~2.3.0" 319 | range-parser "~1.2.1" 320 | statuses "~1.5.0" 321 | 322 | serve-static@1.14.1: 323 | version "1.14.1" 324 | resolved "https://registry.npm.taobao.org/serve-static/download/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" 325 | integrity sha1-Zm5jbcTwEPfvKZcKiKZ0MgiYsvk= 326 | dependencies: 327 | encodeurl "~1.0.2" 328 | escape-html "~1.0.3" 329 | parseurl "~1.3.3" 330 | send "0.17.1" 331 | 332 | setprototypeof@1.1.1: 333 | version "1.1.1" 334 | resolved "https://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" 335 | integrity sha1-fpWsskqpL1iF4KvvW6ExMw1K5oM= 336 | 337 | "statuses@>= 1.5.0 < 2", statuses@~1.5.0: 338 | version "1.5.0" 339 | resolved "https://registry.npm.taobao.org/statuses/download/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 340 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= 341 | 342 | toidentifier@1.0.0: 343 | version "1.0.0" 344 | resolved "https://registry.npm.taobao.org/toidentifier/download/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" 345 | integrity sha1-fhvjRw8ed5SLxD2Uo8j013UrpVM= 346 | 347 | type-is@~1.6.17, type-is@~1.6.18: 348 | version "1.6.18" 349 | resolved "https://registry.npm.taobao.org/type-is/download/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 350 | integrity sha1-TlUs0F3wlGfcvE73Od6J8s83wTE= 351 | dependencies: 352 | media-typer "0.3.0" 353 | mime-types "~2.1.24" 354 | 355 | unpipe@1.0.0, unpipe@~1.0.0: 356 | version "1.0.0" 357 | resolved "https://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 358 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= 359 | 360 | utils-merge@1.0.1: 361 | version "1.0.1" 362 | resolved "https://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 363 | integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= 364 | 365 | vary@~1.1.2: 366 | version "1.1.2" 367 | resolved "https://registry.npm.taobao.org/vary/download/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 368 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= 369 | -------------------------------------------------------------------------------- /views/web3D/index.js: -------------------------------------------------------------------------------- 1 | import { initShader } from './shader/index.js'; 2 | import { initProgram } from './program.js'; 3 | import { Camera, CameraPerspective, CameraOrtho } from './camera/index.js'; 4 | import { Mesh } from './mesh/index.js'; 5 | import { Group } from './group/index.js'; 6 | import { LightAmbient, LightDirectional, LightPoint } from './light/index.js'; 7 | import { MaterialPhone } from './material/index.js'; 8 | import { initFramebufferObject } from './utils/fbo.js'; 9 | 10 | const SHADOW_WIDHT = 2048; 11 | const SHADOW_HEIGHT = 2048; 12 | export class Web3D { 13 | constructor() {} 14 | 15 | async init(options = {}) { 16 | const { width = 500, height = 500 } = options; 17 | const { canvas, gl, program, shadowProgram } = await this.initWebgl({ width, height }); 18 | this.width = width; 19 | this.height = height; 20 | this.canvas = canvas; 21 | this.gl = gl; 22 | this.gl.getExtension('WEBGL_depth_texture'); 23 | const fbo = initFramebufferObject(this.gl, SHADOW_WIDHT, SHADOW_HEIGHT); 24 | this.fbo = fbo; 25 | this.gl.activeTexture(this.gl.TEXTURE0); 26 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.fbo.texture); 27 | this.program = program; 28 | this.shadowProgram = shadowProgram; 29 | this.meshs = []; 30 | this.camera = null; 31 | this.ambientLight = null; 32 | this.directionalLight = null; 33 | this.pointLight = null; 34 | this.shadowLightCameraMatrix = null; 35 | 36 | return this.canvas; 37 | 38 | // appendDom.appendChild(this.canvas); 39 | } 40 | 41 | async initWebgl ({ width, height }) { 42 | // 获取着色器代码 43 | const [fragmentShaderText, vertexShaderText] = await initShader('/normal/fragment-shader.glsl', '/normal/vertex-shader.glsl'); 44 | const [shadowFragmentShaderText, shadowVertexShaderText] = await initShader('/shadow/fragment-shader.glsl', '/shadow/vertex-shader.glsl'); 45 | const canvas = document.createElement('canvas'); 46 | canvas.width = width; 47 | canvas.height = height; 48 | canvas.id = '__web3d'; 49 | const gl = canvas.getContext('webgl'); 50 | const program = initProgram(gl, vertexShaderText, fragmentShaderText); 51 | const shadowProgram = initProgram(gl, shadowVertexShaderText, shadowFragmentShaderText); 52 | return { 53 | canvas, 54 | gl, 55 | program, 56 | shadowProgram 57 | }; 58 | } 59 | 60 | add(objs) { 61 | const add = obj => { 62 | if(obj instanceof Mesh) { 63 | obj.setAttributes(this.gl); 64 | this.meshs.push(obj); 65 | } 66 | if (obj instanceof Group) { 67 | const meshs = obj.getMeshs(); 68 | meshs.forEach(mesh => { 69 | mesh.setAttributes(this.gl); 70 | this.meshs.push(mesh); 71 | }); 72 | } 73 | if (obj instanceof Camera) { 74 | this.camera = obj; 75 | } 76 | if (obj instanceof LightAmbient) { 77 | this.ambientLight = obj; 78 | } 79 | if (obj instanceof LightDirectional) { 80 | this.directionalLight = obj; 81 | } 82 | if (obj instanceof LightPoint) { 83 | this.pointLight = obj; 84 | } 85 | }; 86 | 87 | if (objs instanceof Array) { 88 | objs.forEach(obj => add(obj)); 89 | } else { 90 | add(objs); 91 | } 92 | } 93 | 94 | updateCamera() { 95 | if (this.camera) { 96 | // 更新计算所有矩阵 97 | this.camera.computedCameraMatrix(); 98 | // 相机变换矩阵 99 | const cameraMatrixLocation = this.gl.getUniformLocation(this.program, 'cameraMatrix'); 100 | const cameraMatrix = this.camera.getCameraMatrix(); 101 | this.gl.uniformMatrix4fv(cameraMatrixLocation, false, cameraMatrix); 102 | 103 | // 相机位置 104 | const cameraPositionLocation = this.gl.getUniformLocation(this.program, 'cameraPosition'); 105 | const position = this.camera.getPosition(); 106 | const cameraPosition = new Float32Array(position); 107 | this.gl.uniform3fv(cameraPositionLocation, cameraPosition); 108 | } 109 | } 110 | 111 | updateLight() { 112 | if (this.ambientLight) { 113 | const color = this.ambientLight.getColor(); 114 | const colorLocation = this.gl.getUniformLocation(this.program, 'ambientLightColor'); 115 | this.gl.uniform3fv(colorLocation, new Float32Array(color)); 116 | } 117 | this.gl.uniform1i( 118 | this.gl.getUniformLocation(this.program, 'useAmbientLight'), 119 | this.ambientLight ? 1 : 0 120 | ); 121 | 122 | if (this.directionalLight) { 123 | const color = this.directionalLight.getColor(); 124 | const colorLocation = this.gl.getUniformLocation(this.program, 'directionalLightColor'); 125 | this.gl.uniform3fv(colorLocation, new Float32Array(color)); 126 | const position = this.directionalLight.getPosition(); 127 | const positionLocation = this.gl.getUniformLocation(this.program, 'directionalLightPosition'); 128 | this.gl.uniform3fv(positionLocation, new Float32Array(position)); 129 | } 130 | this.gl.uniform1i( 131 | this.gl.getUniformLocation(this.program, 'useDirectionalLight'), 132 | this.directionalLight ? 1 : 0 133 | ); 134 | 135 | if (this.pointLight) { 136 | const color = this.pointLight.getColor(); 137 | const colorLocation = this.gl.getUniformLocation(this.program, 'pointLightColor'); 138 | this.gl.uniform3fv(colorLocation, new Float32Array(color)); 139 | const position = this.pointLight.getPosition(); 140 | const positionLocation = this.gl.getUniformLocation(this.program, 'pointLightPosition'); 141 | this.gl.uniform3fv(positionLocation, new Float32Array(position)); 142 | } 143 | this.gl.uniform1i( 144 | this.gl.getUniformLocation(this.program, 'usePointLight'), 145 | this.pointLight ? 1 : 0 146 | ); 147 | } 148 | 149 | updateShadow() { 150 | const program = this.shadowProgram; 151 | let isInitShadow = false; 152 | 153 | const initShadow = () => { 154 | if (isInitShadow) return; 155 | isInitShadow = true; 156 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fbo); 157 | this.gl.viewport(0, 0, SHADOW_WIDHT, SHADOW_HEIGHT); 158 | this.gl.useProgram(program); 159 | this.clear(); 160 | 161 | const camera = new CameraOrtho({ left: -50, right: 50, bottom: -50, top: 50 }); 162 | const nearPlaneLocation = this.gl.getUniformLocation(program, 'nearPlane'); 163 | this.gl.uniform1i(nearPlaneLocation, camera.near); 164 | 165 | const farPlaneLocation = this.gl.getUniformLocation(program, 'farPlane'); 166 | this.gl.uniform1i(farPlaneLocation, camera.far); 167 | 168 | const [x, y, z] = this.pointLight.position; 169 | 170 | camera.move({ x, y, z }); 171 | camera.lookAt(0, 0, 0); 172 | camera.computedCameraMatrix(); 173 | 174 | const cameraMatrixLocation = this.gl.getUniformLocation(program, 'cameraMatrix'); 175 | const cameraMatrix = camera.getCameraMatrix(); 176 | this.gl.uniformMatrix4fv(cameraMatrixLocation, false, cameraMatrix); 177 | this.shadowLightCameraMatrix = cameraMatrix; 178 | }; 179 | 180 | for (let i = 0; i < this.meshs.length; i++) { 181 | const mesh = this.meshs[i]; 182 | if (!mesh.isOpenShadow) { 183 | continue; 184 | } 185 | initShadow(); 186 | // 更新计算所有矩阵 187 | mesh.computedMeshMatrix(); 188 | const attributes = mesh.getAttributes(); 189 | const drawMode = mesh.drawMode; 190 | 191 | // 顶点数据 192 | for (const key in attributes) { 193 | const value = attributes[key]; 194 | if (key === 'normal' || key === 'texture') { 195 | continue; 196 | } 197 | // 顶点索引 198 | else if (key === 'index') { 199 | this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, value.buffer); 200 | } 201 | // 顶点位置、顶点颜色、顶点法线 202 | else if (value) { 203 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, value.buffer); 204 | const location = this.gl.getAttribLocation(program, key); 205 | this.gl.vertexAttribPointer(location, value.n, this.gl[value.type], false, 0, 0); 206 | this.gl.enableVertexAttribArray(location); 207 | } 208 | } 209 | 210 | // 顶点变换矩阵 211 | const meshMatrixLocation = this.gl.getUniformLocation(program, 'meshMatrix'); 212 | const meshMatrix = mesh.getMeshMatrix(); 213 | this.gl.uniformMatrix4fv(meshMatrixLocation, false, meshMatrix); 214 | 215 | this.gl.drawElements(this.gl[drawMode], attributes.index.data.length, this.gl.UNSIGNED_SHORT, 0); 216 | } 217 | 218 | if (isInitShadow) { 219 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); 220 | this.gl.viewport(0, 0, this.width, this.height); 221 | 222 | } 223 | this.gl.useProgram(this.program); 224 | this.clear(); 225 | } 226 | 227 | drawMeshs() { 228 | // 使用阴影 229 | if (this.shadowLightCameraMatrix) { 230 | this.gl.uniform1i(this.program.uShadowSampler, 0); 231 | const shadowLightCameraMatrixLocation = this.gl.getUniformLocation(this.program, 'shadowLightCameraMatrix'); 232 | const shadowLightCameraMatrix = this.shadowLightCameraMatrix; 233 | this.gl.uniformMatrix4fv(shadowLightCameraMatrixLocation, false, shadowLightCameraMatrix); 234 | 235 | const useShadowLocation = this.gl.getUniformLocation(this.program, 'useShadow'); 236 | this.gl.uniform1i(useShadowLocation, 1); 237 | } 238 | 239 | for (let i = 0; i < this.meshs.length; i++) { 240 | const mesh = this.meshs[i]; 241 | // 更新计算所有矩阵 242 | mesh.computedMeshMatrix(); 243 | const attributes = mesh.getAttributes(); 244 | const material = mesh.getMaterial(); 245 | const image = mesh.material.getImage(); 246 | const drawMode = mesh.drawMode; 247 | const drawType = mesh.drawType; 248 | 249 | // 顶点数据 250 | for (const key in attributes) { 251 | const value = attributes[key]; 252 | // 顶点索引 253 | if (key === 'index') { 254 | this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, value.buffer); 255 | } 256 | // 顶点纹理 257 | else if (key === 'texture') { 258 | // 是否开启纹理 259 | const isOpenTexture = !!value.data.length; 260 | if (!isOpenTexture) { 261 | continue; 262 | } 263 | const { texture } = value; 264 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, value.buffer); 265 | const location = this.gl.getAttribLocation(this.program, key); 266 | this.gl.vertexAttribPointer(location, value.n, this.gl[value.type], false, 0, 0); 267 | this.gl.enableVertexAttribArray(location); 268 | const uSamplerLoaction = this.gl.getUniformLocation(this.program, 'uSampler'); 269 | this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, 1); 270 | this.gl.activeTexture(this.gl.TEXTURE1); 271 | this.gl.bindTexture(this.gl.TEXTURE_2D, texture); 272 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST); 273 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST); 274 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); 275 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE); 276 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGB, this.gl.RGB, this.gl.UNSIGNED_BYTE, image); 277 | this.gl.uniform1i(uSamplerLoaction, 1); 278 | } 279 | // 顶点位置、顶点颜色、顶点法线 280 | else if (value) { 281 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, value.buffer); 282 | const location = this.gl.getAttribLocation(this.program, key); 283 | this.gl.vertexAttribPointer(location, value.n, this.gl[value.type], false, 0, 0); 284 | this.gl.enableVertexAttribArray(location); 285 | } 286 | } 287 | 288 | // 顶点变换矩阵 289 | const meshMatrixLocation = this.gl.getUniformLocation(this.program, 'meshMatrix'); 290 | const meshMatrix = mesh.getMeshMatrix(); 291 | this.gl.uniformMatrix4fv(meshMatrixLocation, false, meshMatrix); 292 | 293 | // 法线抵消矩阵 294 | const normalMatrixLocation = this.gl.getUniformLocation(this.program, 'normalMatrix'); 295 | const normalMatrix = mesh.getNormalMatrix(); 296 | this.gl.uniformMatrix4fv(normalMatrixLocation, false, normalMatrix); 297 | 298 | // 使用高光 299 | if (material instanceof MaterialPhone) { 300 | const usePhoneMaterialLocation = this.gl.getUniformLocation(this.program, 'usePhoneMaterial'); 301 | this.gl.uniform1i(usePhoneMaterialLocation, 1); 302 | } 303 | 304 | 305 | // 雾化 306 | if (mesh.fogColor && mesh.fogDist) { 307 | this.gl.uniform1i(this.gl.getUniformLocation(this.program, 'useFog'), 1); 308 | this.gl.uniform3fv(this.gl.getUniformLocation(this.program, 'fogColor'), new Float32Array(mesh.fogColor)); 309 | this.gl.uniform2fv(this.gl.getUniformLocation(this.program, 'fogDist'), new Float32Array(mesh.fogDist)); 310 | } 311 | 312 | // 绘制点 313 | if (drawMode === 'POINTS') { 314 | const pointSizeLocation = this.gl.getUniformLocation(this.program, 'pointSize'); 315 | this.gl.uniform1f(pointSizeLocation, mesh.pointSize); 316 | } 317 | 318 | // 没有索引数据时 319 | if (!attributes.index.data.length || drawType === 'drawArrays') { 320 | this.gl.drawArrays(this.gl[drawMode], 0, attributes.position.data.length / 3); 321 | } 322 | else { 323 | this.gl.drawElements(this.gl[drawMode], attributes.index.data.length, this.gl.UNSIGNED_SHORT, 0); 324 | } 325 | } 326 | } 327 | 328 | clear() { 329 | this.gl.clearColor(0, 0, 0, 1); 330 | this.gl.clearDepth(1); 331 | this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); 332 | this.gl.enable(this.gl.DEPTH_TEST); 333 | } 334 | 335 | draw() { 336 | this.updateShadow(); 337 | this.updateCamera(); 338 | this.updateLight(); 339 | this.drawMeshs(); 340 | return this; 341 | } 342 | }; --------------------------------------------------------------------------------