├── .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 | };
--------------------------------------------------------------------------------