如何在2D画布上绘制3D对象

时间:2020-06-02 19:35:39

标签: javascript canvas webgl

这是完整的代码,我不得不从一些与问题无关的函数中删除空格,以确保im处于堆栈溢出的30k个字符限制内

const EPSILON = 0.000001;
const mat4 = { rotateZ: function(out, a, rad) { let s = Math.sin(rad); let c = Math.cos(rad); let a00 = a[0]; let a01 = a[1]; let a02 = a[2]; let a03 = a[3]; let a10 = a[4]; let a11 = a[5]; let a12 = a[6]; let a13 = a[7]; if (a !== out) { out[8] = a[8]; out[9] = a[9]; out[10] = a[10]; out[11] = a[11]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; } out[0] = a00 * c + a10 * s; out[1] = a01 * c + a11 * s; out[2] = a02 * c + a12 * s; out[3] = a03 * c + a13 * s; out[4] = a10 * c - a00 * s; out[5] = a11 * c - a01 * s; out[6] = a12 * c - a02 * s; out[7] = a13 * c - a03 * s; return out; }, create: function() { let out = new Float32Array(16); out[0] = 1; out[5] = 1; out[10] = 1; out[15] = 1; return out; }, perspective: function(out, fovy, aspect, near, far) { let f = 1.0 / Math.tan(fovy / 2), nf; out[0] = f / aspect; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = f; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[11] = -1; out[12] = 0; out[13] = 0; out[15] = 0; if (far !== null && far !== Infinity) { nf = 1 / (near - far); out[10] = (far + near) * nf; out[14] = (2 * far * near) * nf; } else { out[10] = -1; out[14] = -2 * near; } return out; }, translate: function(out, a, v) { let x = v[0], y = v[1], z = v[2]; if (a === out) { out[12] = a[0] * x + a[4] * y + a[8] * z + a[12]; out[13] = a[1] * x + a[5] * y + a[9] * z + a[13]; out[14] = a[2] * x + a[6] * y + a[10] * z + a[14]; out[15] = a[3] * x + a[7] * y + a[11] * z + a[15]; return out; } else { let a00, a01, a02, a03; let a10, a11, a12, a13; let a20, a21, a22, a23; a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3]; a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7]; a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11]; out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03; out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13; out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23; out[12] = a00 * x + a10 * y + a20 * z + a[12]; out[13] = a01 * x + a11 * y + a21 * z + a[13]; out[14] = a02 * x + a12 * y + a22 * z + a[14]; out[15] = a03 * x + a13 * y + a23 * z + a[15]; return out; } }, scale: function(out, a, v) { let x = v[0], y = v[1], z = v[2]; out[0] = a[0] * x; out[1] = a[1] * x; out[2] = a[2] * x; out[3] = a[3] * x; out[4] = a[4] * y; out[5] = a[5] * y; out[6] = a[6] * y; out[7] = a[7] * y; out[8] = a[8] * z; out[9] = a[9] * z; out[10] = a[10] * z; out[11] = a[11] * z; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; return out; }, multiply: function(out, a, b) { let a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]; let a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; let a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; let a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; let b0  = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30; out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33; b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7]; out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30; out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33; b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11]; out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30; out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33; b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15]; out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30; out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33; return out; }, lookAt: function(out, eye, center, up) { let x0, x1, x2, y0, y1, y2, z0, z1, z2, len; let eyex = eye[0]; let eyey = eye[1]; let eyez = eye[2]; let upx = up[0]; let upy = up[1]; let upz = up[2]; let centerx = center[0]; let centery = center[1]; let centerz = center[2]; if (Math.abs(eyex - centerx) < EPSILON && Math.abs(eyey - centery) < EPSILON && Math.abs(eyez - centerz) < EPSILON) { return identity(out); } z0 = eyex - centerx; z1 = eyey - centery; z2 = eyez - centerz; len = 1 / Math.hypot(z0, z1, z2); z0 *= len; z1 *= len; z2 *= len; x0 = upy * z2 - upz * z1; x1 = upz * z0 - upx * z2; x2 = upx * z1 - upy * z0; len = Math.hypot(x0, x1, x2); if (!len) { x0 = 0; x1 = 0; x2 = 0; } else { len = 1 / len; x0 *= len; x1 *= len; x2 *= len; } y0 = z1 * x2 - z2 * x1; y1 = z2 * x0 - z0 * x2; y2 = z0 * x1 - z1 * x0; len = Math.hypot(y0, y1, y2); if (!len) { y0 = 0; y1 = 0; y2 = 0; } else { len = 1 / len; y0 *= len; y1 *= len; y2 *= len; } out[0] = x0; out[1] = y0; out[2] = z0; out[3] = 0; out[4] = x1; out[5] = y1; out[6] = z1; out[7] = 0; out[8] = x2; out[9] = y2; out[10] = z2; out[11] = 0; out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez); out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez); out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez); out[15] = 1; return out; }, moveToVec3: function(out, v) { out[12] = v[0]; out[13] = v[1]; out[14] = v[2]; } }; const mat3 = { clone: function(a) { let out = new Float32Array(9); out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7]; out[8] = a[8]; return out; }, create: function() { let out = new Float32Array(9); out[0] = 1; out[4] = 1; out[8] = 1; return out; } }; const vec3 = { multiply: function(out, a, b) { out[0] = a[0] * b[0]; out[1] = a[1] * b[1]; return out; }, create: function() { return new Float32Array(3);; }, copy: function(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; return out; } }; const vec2 = { create: function() { return new Float32Array(2);; }, copy: function(out, a) { out[0] = a[0]; out[1] = a[1]; return out; }, fromValues: function(x, y) { let out = new Float32Array(2); out[0] = x; out[1] = y; return out; }, multiply: function(out, a, b) { out[0] = a[0] * b[0]; out[1] = a[1] * b[1]; return out; }, add: function(out, a, b) { out[0] = a[0] + b[0]; out[1] = a[1] + b[1]; return out; } };
const FRAGMENT_SHADER = ` precision highp float; varying highp vec2 vTextureCoord; varying lowp vec4 vColor; uniform sampler2D uSampler; uniform bool aUseText; void main(void) { if( aUseText ){ gl_FragColor = texture2D(uSampler, vTextureCoord); } else { gl_FragColor = vColor; } } `;
const VERTEX_SHADER = ` attribute vec4 aVertexPosition; attribute vec4 aVertexColor; attribute vec2 aTextureCoord; uniform mat4 uModelViewMatrix; uniform mat4 uProjectionMatrix; uniform mat3 uTextMatrix; uniform float uPointSize; varying lowp vec4 vColor; varying highp vec2 vTextureCoord; void main(void) { gl_PointSize = uPointSize; gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; vColor = aVertexColor; vTextureCoord = (vec3(aTextureCoord, 1)*uTextMatrix).xy; } `;

class WebglEntity {
    constructor() {
        this.matrix = mat4.create();
        this.coords = vec3.create();
    }
    translate(newCoords) {
        const {
            matrix,
            coords
        } = this;
        mat4.translate(matrix, matrix, newCoords);
        vec3.copy(coords, [matrix[12], matrix[13], matrix[14]]);

        return this;
    }
    move(newCoords) {
        const {
            matrix,
            coords
        } = this;
        vec3.copy(coords, newCoords);
        mat4.moveToVec3(matrix, coords);

        return this;
    }
}
class Camera extends WebglEntity {
    constructor(fieldOfView, aspect, zNear, zFar) {
      super();

      this.projection = mat4.perspective(mat4.create(), fieldOfView, aspect, zNear, zFar);

    }
    lookAt(lookAt) {
        const {
            matrix,
            projection,
            coords
        } = this;
        mat4.lookAt(matrix, coords, lookAt, [0, 1, 0]);
        mat4.multiply(matrix, projection, matrix);
        return this;
    }
}
class Rect extends WebglEntity{

  constructor(){

    super();

    this.positionsBuffer = undefined;
    this.fragColorPos = undefined;

    this.strokeColorPos = undefined;
    this.strokePositionBuffer = undefined;

    this.vertexAttribInfo = undefined;
    this.vertextColorAttribInfo = undefined;

    this.vertexCount = undefined;
    this.textureInfo = undefined;


    this.multiTextures = false;

    this.strokeSize = 1;
    this.fillers = {
      fill: false,
      texture: false,
      stroke: false
    };
  }
  setup(matrix, positionsBuffer,  strokePositionBuffer, vertexAttribInfo, vertextColorAttribInfo, vertexCount){

    this.matrix = matrix;

    this.positionsBuffer = positionsBuffer;
    this.strokePositionBuffer = strokePositionBuffer;

    this.vertexAttribInfo = vertexAttribInfo;
    this.vertextColorAttribInfo = vertextColorAttribInfo;

    this.vertexCount = vertexCount;

    return this;
  }

}

class Display{

 constructor(gl, programInfo, zAxis, texture){
   this.gl = gl;
   this.programInfo = programInfo;

   this.canvas = gl.canvas;

   this.currentCamera = new Camera(45 * Math.PI / 180, gl.canvas.width/gl.canvas.height, 0.1, 100.0);

   this.currentCamera.translate([0, 0, zAxis]).lookAt([0, 0, 0]);

   this.zAxis = zAxis;
   this.drawZAxis = 0;

   this.last = {};

   texture.textAttribInfo = {
     numComponents: 2,
     type: gl.FLOAT,
     normalize: false,
     stride: 0,
     offset: 0
   };

   this.texture = texture;
   this.spriteSheets = [];

   const context = texture.context;
   const canvas = texture.canvas;

   this.images = {};

 }


 clear(color){

   const gl = this.gl;

   gl.clearColor(0.1, 0.1, 0.1, 1);

   gl.clearDepth(1.0);
   gl.enable(gl.DEPTH_TEST);
   gl.depthFunc(gl.LEQUAL);

   gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);


 }

 rect(x, y, w, h){

   const {rect, stroke} = this.createRectPos(w, h);

   const square = new Rect();
   square.setup(...this.getRectInfo(x, y, rect, stroke));

   return square;
 }

 fillRect(rect, color){
   const {createStaticDrawBuffer, gl, parseColor} = this;

   rect.fillers.fill = true;

   if(color){
     rect.fragColorPos = createStaticDrawBuffer(gl, [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]);

   }
 }

 createRectPos(w, h){

   const rect = [ w/2,  h/2, -w/2,  h/2, w/2, -h/2, -w/2, -h/2 ];
   const stroke = [ -w/2,  h/2, w/2,  h/2, w/2, -h/2, -w/2, -h/2, ];
   return {rect, stroke};
 }

 getRectInfo(x, y, rect, stroke){
   return this.createSquareBuffer(rect, stroke, [x, y, this.drawZAxis]);
 }

 createStaticDrawBuffer(gl, data){

   const buffer = gl.createBuffer();
   gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
   gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);

   return buffer;
 }

 createSquareBuffer(positions, strokePosition, coords) {
   const {gl, createStaticDrawBuffer} = this;

   const positionsBuffer      = createStaticDrawBuffer(gl, positions);
   const strokePositionBuffer = createStaticDrawBuffer(gl, strokePosition);
   const modelViewMatrix = mat4.create();

   mat4.translate(modelViewMatrix, modelViewMatrix, coords);

   return [modelViewMatrix, positionsBuffer, strokePositionBuffer, this.createAttribInfo(2, gl.FLOAT, false, 0, 0), this.createAttribInfo(4, gl.FLOAT, false, 0, 0), positions.length/2]; }

 createAttribInfo(numComponents, type, normalize, stride, offset){

   return { numComponents, type, normalize, stride, offset};
 }

 enableAttrib(buffer, attrib, gl, {numComponents, type, normalize, stride, offset}){

   gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
   gl.vertexAttribPointer(attrib, numComponents,type,normalize,stride,offset);
   gl.enableVertexAttribArray(attrib);

 }

 drawBuffer(buffer){

   const {gl, drawTexture, enableAttrib, createStaticDrawBuffer, currentCamera, texture: {context, canvas, textAttribInfo}, programInfo: {uniformLocations, program, attribLocations: {vertexPosition, vertexColor, textureCoord}}} = this;

   const cameraMatrix = currentCamera.matrix;

   const {positionsBuffer, fragColorPos, strokeColorPos, strokePositionBuffer, matrix, vertexAttribInfo, vertextColorAttribInfo, vertexCount, fragTextPos, fillers: {fill, stroke, texture}, strokeSize, textureInfo, multiTextures} = buffer;

   gl.uniformMatrix4fv(uniformLocations.projectionMatrix, false, cameraMatrix);
   gl.uniformMatrix4fv(uniformLocations.modelViewMatrix, false, matrix);

   if(fill){

     enableAttrib(positionsBuffer, vertexPosition, gl, vertexAttribInfo);
     enableAttrib(fragColorPos, vertexColor, gl, vertextColorAttribInfo);
     gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexCount);
     gl.disableVertexAttribArray(vertexColor);

   }

 }

 static loadShader(gl, program, type, source) {

   const shader = gl.createShader(type);
   gl.shaderSource(shader, source);
   gl.compileShader(shader);
   gl.attachShader(program, shader);

 }

 static async create(canvas, width, height, zAxis = 6){
   canvas.width  = width;
   canvas.height = height;

   const gl = canvas.getContext("webgl");

   const shaderProgram = gl.createProgram();

   Display.loadShader(gl, shaderProgram, gl.VERTEX_SHADER, VERTEX_SHADER);
   Display.loadShader(gl, shaderProgram, gl.FRAGMENT_SHADER, FRAGMENT_SHADER);

   gl.linkProgram(shaderProgram);

   const programInfo = {
     program: shaderProgram,
     attribLocations: {
       vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
       vertexColor: gl.getAttribLocation(shaderProgram, 'aVertexColor'),
       textureCoord: gl.getAttribLocation(shaderProgram, 'aTextureCoord'),


     },
     uniformLocations: {
       projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
       modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
       textMatrix: gl.getUniformLocation(shaderProgram, 'uTextMatrix'),
       sampler: gl.getUniformLocation(shaderProgram, 'uSampler'),
       useText: gl.getUniformLocation(shaderProgram, 'aUseText'),
       pointSize: gl.getUniformLocation(shaderProgram, 'uPointSize'),
     },
   };

   gl.useProgram(programInfo.program);

   gl.uniform1f(programInfo.uniformLocations.pointSize, 1.0);

   gl.enable(gl.BLEND);
   gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

   const textureBuffer = gl.createTexture();

   gl.activeTexture(gl.TEXTURE0);
   gl.bindTexture(gl.TEXTURE_2D, textureBuffer);
   gl.uniform1i(programInfo.uniformLocations.uSampler, 0);

   const textureCanvas = document.createElement("canvas");

   textureCanvas.width = 0;
   textureCanvas.height = 0;

   let texture = {
       canvas: textureCanvas,
       buffer: textureBuffer,
       context: textureCanvas.getContext("2d"),
     };

   return new Display(gl, programInfo, zAxis, texture);
 }
}

class Engine { constructor(time_step, update, render, allowedSkippedFrames) { this.accumulated_time = 0; this.animation_frame_request = undefined, this.time = undefined, this.time_step = time_step, this.updated = false; this.update = update; this.render = render; this.allowedSkippedFrames = allowedSkippedFrames; this.run = this.run.bind(this); this.end = false; } run(time_stamp) { const { accumulated_time, time, time_step, updated, update, render, allowedSkippedFrames, end } = this; this.accumulated_time += time_stamp - time; this.time = time_stamp; if (accumulated_time > time_stamp * allowedSkippedFrames) { this.accumulated_time = time_stamp; } while (this.accumulated_time >= time_step) { this.accumulated_time -= time_step; update(time_stamp); this.updated = true; } if (updated) { this.updated = false; render(time_stamp); } if (end) { return; } this.animation_frame_request = requestAnimationFrame(this.run); } start() { this.accumulated_time = this.time_step; this.time = performance.now(); this.animation_frame_request = requestAnimationFrame(this.run); } stop() { this.end = true; cancelAnimationFrame(this.animation_frame_request); } }

class Entity extends Rect {

  constructor(){

    super();

    this.velocity = vec2.create();
    this.area = undefined;
    this.mass = 2;

    this.updateFillers = {};
    this.delete = false;
    this.draw = true;
  }

  setup(w, h, ...args){
    this.area = vec2.fromValues(w, h);
    super.setup(...args);

    return this;
  }

  fill(...args){
    this.updateFillers.fill = args;
  }

  update(deltaTime, speed){

    return this;
  }

  move(x, y){

    super.move([x, y, this.coords[2]]);

    return this;

  }

}

class Quixotic{

  constructor(display){

    this.display = display;

    this.engine = undefined;

    this.render = undefined;
    this.update = undefined;
    this.frameRate = undefined;

    this.time = 0; this.speed = 1;
    this.world = {

      objects: {},
      objectsCollisionInfo: {},
      objectsArray: [],
      classesInfo: {}

    };

    this.timePassed = 0;

  }

  createEntity(Class, ...args){
    const display = this.display; const {rect, stroke} = display.createRectPos(5, 5); Class = Class ? Class : Entity; const className = Class.name; if(className !== "Entity" && !Entity.prototype.isPrototypeOf(Class.prototype)){ throw new TypeError("Expected extended class of Entity. Instead got: " + className); } let instance; const {objectsArray, classesInfo, objects} = this.world; const classInfo = classesInfo[className]; if(classInfo){ if(classInfo.args){ instance = new Class(...[...classInfo.args, ...args]); } else { instance = new Class(...args); } const name = classInfo.name; if(Array.isArray(objects[name])){ objects[name].push(instance); instance.name = name; } else { console.warn("Didn't save object in world.objects object, object wouldn't detect collision"); } } else { instance = new Class(...args); } instance.setup(5, 5, ...display.getRectInfo(0, 0, rect, stroke, "#000")); objectsArray.push(instance); return instance; }

  createBackground(objects){
    const buffer = document.createElement("canvas").getContext("2d");

    const bufferRect = this.createEntity();
    let {zAxis, canvas: {width, height}} = this.display;
    zAxis--;
    const halfZ = zAxis/2;
    let {coords: [x, y], area: [w, h]} = objects[objects.length - 1];

    let [mX, mY, mW, mH] = [x, y, w, h];
    for(let i = objects.length-1; i--;){

      const {coords: [_x, _y], area: [_w, _h]} = objects[i];
      x < _x ? _x : x;
      y < _y ? _y : y;

      if(mX < _x){
         mX = _x;
         mW = _w;
      }
      if(mY < _y){
         mY = _y;
         mH = _h;
       }
    }

    buffer.canvas.width = width;
    buffer.canvas.height = height;
    for(let i = objects.length; i--;){

      const {coords: [_x, _y], area: [_w, _h]} = objects[i];
      buffer.fillRect(((_x-halfZ-_w*2)/zAxis+1)*width, ((-_y-halfZ-_h*2)/zAxis+1)*height, _w*2/zAxis*width, _h*2/zAxis*height);
    }

    document.body.appendChild(buffer.canvas)

  }

  buildWorld({objects, classes, tileMap}){

    const world = this.world;

    if(Array.isArray(objects)){
      for(let i = objects.length - 1; i > -1; i --){
        const object = objects[i];
        const {name, array, amount, position, collision, args, area} = object;

        let createClass;

        if(!object.class){
          createClass = Entity;
        }

        const _args = args ? args : [];

        let pos;

        if(position){
            let p = amount;
            if(array){
              const positions = position.positions;
              pos = function(){
                p--;
                return positions[p];
              };
            } else {
              pos = function(){
                return position.position;
              };
            }
          }

        if(array){

          let _array = [];

          for(let j = amount; j--;){

            const instance = this.createEntity(createClass, ..._args);
            instance.name = name;

            if(position){
              instance.move(...pos());
            }

            if(area){

              instance.setSize(area);

            }
            _array.push(instance);
          }
          world.objects[name] = _array;
          world.objectsArray.push(..._array);

        }
      }
    }

    return;

  }

  setup(game){

    const {style: {backgroundColor, backgroundImage, stroke}, world, engine: {frameRate, update, render}, setup} = game; this.buildWorld(world); const {display, entitySystem, world: {objectsArray, objects}} = this; if(backgroundImage){ display.gl.canvas.style.background = `url(${backgroundImage})`; if(repeatX || repeatY){ console.log("not read yet"); } } this.frameRate = frameRate; let lastUpdated = 0; this.update = (time) =>{ let deltaTime = time - lastUpdated; lastUpdated = time; const speed = this.speed; this.timePassed += deltaTime*speed; for(let i = objectsArray.length; i--;){ const object = objectsArray[i]; if(object.delete){ objectsArray.splice(i, 1); } object.update(deltaTime/1000, speed); } update(deltaTime/1000, this); }; let lastRendered = 0; this.render = (timeStamp) => { const deltaTime = timeStamp - lastRendered; lastRendered = timeStamp; if(backgroundColor) display.clear(backgroundColor); const length = objectsArray.length; for(let i = objectsArray.length; i--; ){ const object = objectsArray[length - i - 1]; if(object.draw){ const updateFillers = Object.entries(object.updateFillers); const fillersLength = updateFillers.length; if(fillersLength){ for(let i = fillersLength; i--;){ const [func, args] = updateFillers[fillersLength - i - 1]; display[func + "Rect"](object, ...args); } object.updateFillers = {}; } display.drawBuffer(object); } } const speed = this.speed; const spriteSheets = display.spriteSheets; for(let i = spriteSheets.length; i--;){ spriteSheets[i].update(deltaTime/1000*speed); } render(display, this); }; setup(this, display, this.world); this.engine = new Engine(this.frameRate, this.update, this.render, 3); this.engine.start(); return game;
  }

  static async create({display: {canvas, width, height, zAxis}, homeURL}){

    const display = await Display.create(canvas, width, height, zAxis);

    return new Quixotic(display);
  }

}


const fps = document.querySelector("#fps");
const minLength = innerWidth > innerHeight ? innerHeight : innerWidth;
const game = {

  create: {

    display: {

      canvas: document.querySelector("#canvas"),
      zAxis: 96,
      width: minLength,
      height: minLength,

    },

    homeURL: "/src"
  },

  style: {
    backgroundColor: "#111122"
  },

  world: {
    objects: [
      {
        name: "trees",

        array: true,
        amount: 5,
        position: {
	         type: "set",
	         positions: [ [-37.5, 37.5], [0,0], [-37.5,-37.5], [37.5,-37.5], [37.5,37.5], [10,10], [15,10], [20,10], [25,10], [30,10]]

        }
      }
    ]
  },

  engine: {

    frameRate: 1000/30,

    update: function(deltaTime, engine){
      fps.innerText = 1/deltaTime;
    },

    render: function(display){}
  },

  setup: function(engine, display, {objects: {trees}}){

     trees.forEach(tree => {
       tree.fill("#00ff00")
     })
    engine.createBackground(trees);
  }

};



Quixotic.create(game.create)
  .then(engine => {

    engine.setup(game);
  });
 * {
         box-sizing:border-box;
         margin:0;
         padding:0;
    }
     body {
         background-color: #111c31;
         overflow: hidden;
         align-items:space-around;
         display:grid;
         height:100%;
         width:100%;
    }
     #canvas {
         background-color: #152646;
         /* justify-self: center; */
    }
    #fps {
      position: fixed;
      color: white;
      right: 0;
    }
    canvas {
      position: fixed
    }
   
<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>webgl x 2dCanvas</title>
  </head>
  <body>
    <canvas id="canvas" width="300" height="300"></canvas>
    <p id = "fps"></p>
  </body>
</html>

这里是发生问题的第374行的代码

createBackground(objects){ //method
  const buffer = document.createElement("canvas").getContext("2d");

  const bufferRect = this.createEntity();
  let {zAxis, canvas: {width, height}} = this.display;
  zAxis--; //zAxis is where the camera is at, currently 96, but with webgl the objects have to be 1 point lower, so 95.

  const halfZ = zAxis/2;
  let {coords: [x, y], area: [w, h]} = objects[objects.length - 1];

  let [mX, mY, mW, mH] = [x, y, w, h];
  for(let i = objects.length-1; i--;){

    const {coords: [_x, _y], area: [_w, _h]} = objects[i];
    x < _x ? _x : x;
    y < _y ? _y : y;

    if(mX < _x){
       mX = _x;
       mW = _w;
    }
    if(mY < _y){
       mY = _y;
       mH = _h;
     }
  }

  buffer.canvas.width = ((mX-halfZ+mW*2)/zAxis+1)*width;
  buffer.canvas.height = ((mY-halfZ+mH*2)/zAxis+1)*height;

  for(let i = objects.length; i--;){

    const {coords: [_x, _y], area: [_w, _h]} = objects[i];
    buffer.fillRect(((_x-halfZ-_w*2)/zAxis+1)*width, ((_y-halfZ-_h*2)/zAxis+1)*height, _w*2/zAxis*width, _h*2/zAxis*height);
  }

  document.body.appendChild(buffer.canvas)

}

我有这个功能,可以使用webgl在带有2个向量和矩阵的3d世界中绘制对象,基本上我可以将它们的所有位置和体积绘制到2d画布上,这就是到目前为止我得到的结果

results, black squares not quit covering the green square

绿色正方形是使用webgl绘制的,黑色正方形是在画布渲染2d上绘制的,最终结果应该是覆盖绿色正方形的黑色正方形,但我的数学位置不正确。

完整的代码可以在这里找到 https://github.com/bahaaaldin214/Quixotic-Engine/tree/test

着色器位于src / modules / webgl / shaders

其他信息

相机位置:96,

绿色方块位置:

[
    [-37.5, 37.5], //bottom left
    [0,0], //center
    [-37.5,-37.5],  //top left
    [37.5,-37.5], //bottom right
    [37.5,37.5], //top right
]

1 个答案:

答案 0 :(得分:1)

现在我已经看过代码了。首先,我很糟糕,但我不清楚您应该发布minimal code。有很多不需要的代码。另外,我不确定这是否是您自己的数学库或是否与glmatrix配对。如果是后者,则可以<script src="cdn/to/glmatrix"></script>来使用它。

无论如何,您都是使用透视矩阵和视图矩阵(相机)来定位正方形,因此您需要对2D画布使用相同的数学运算。

const worldViewProjection = mat4.create();
buffer.canvas.width = width;
buffer.canvas.height = height;
for (let i = objects.length; i--;) {
  const {
    coords: [_x, _y],
    area: [_w, _h]
  } = objects[i];
  mat4.multiply(worldViewProjection, this.display.currentCamera.matrix, objects[i].matrix);
  const points = [
    [-_w / 2, -_h / 2, 0],
    [ _w / 2,  _h / 2, 0],
  ].map(p => {
    const ndc = vec3.transformMat4([], p, worldViewProjection);
    return [
      (ndc[0] *  0.5 + 0.5) * width,
      (ndc[1] * -0.5 + 0.5) * height,
    ];
  });
  const ww = points[1][0] - points[0][0];
  const hh = points[1][1] - points[0][1];

  buffer.strokeStyle = 'red';
  buffer.strokeRect(...points[0], ww, hh);
} 

const EPSILON = 0.000001;
const FRAGMENT_SHADER = ` precision highp float; varying highp vec2 vTextureCoord; varying lowp vec4 vColor; uniform sampler2D uSampler; uniform bool aUseText; void main(void) { if( aUseText ){ gl_FragColor = texture2D(uSampler, vTextureCoord); } else { gl_FragColor = vColor; } } `;
const VERTEX_SHADER = ` attribute vec4 aVertexPosition; attribute vec4 aVertexColor; attribute vec2 aTextureCoord; uniform mat4 uModelViewMatrix; uniform mat4 uProjectionMatrix; uniform mat3 uTextMatrix; uniform float uPointSize; varying lowp vec4 vColor; varying highp vec2 vTextureCoord; void main(void) { gl_PointSize = uPointSize; gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; vColor = aVertexColor; vTextureCoord = (vec3(aTextureCoord, 1)*uTextMatrix).xy; } `;

mat4.moveToVec3 = function(out, v) {
  out[12] = v[0];
  out[13] = v[1];
  out[14] = v[2];
};


class WebglEntity {
  constructor() {
    this.matrix = mat4.create();
    this.coords = vec3.create();
  }
  translate(newCoords) {
    const {
      matrix,
      coords
    } = this;
    mat4.translate(matrix, matrix, newCoords);
    vec3.copy(coords, [matrix[12], matrix[13], matrix[14]]);

    return this;
  }
  move(newCoords) {
    const {
      matrix,
      coords
    } = this;
    vec3.copy(coords, newCoords);
    mat4.moveToVec3(matrix, coords);

    return this;
  }
}
class Camera extends WebglEntity {
  constructor(fieldOfView, aspect, zNear, zFar) {
    super();

    this.projection = mat4.perspective(mat4.create(), fieldOfView, aspect, zNear, zFar);

  }
  lookAt(lookAt) {
    const {
      matrix,
      projection,
      coords
    } = this;
    mat4.lookAt(matrix, coords, lookAt, [0, 1, 0]);
    mat4.multiply(matrix, projection, matrix);
    return this;
  }
}
class Rect extends WebglEntity {

  constructor() {

    super();

    this.positionsBuffer = undefined;
    this.fragColorPos = undefined;

    this.strokeColorPos = undefined;
    this.strokePositionBuffer = undefined;

    this.vertexAttribInfo = undefined;
    this.vertextColorAttribInfo = undefined;

    this.vertexCount = undefined;
    this.textureInfo = undefined;


    this.multiTextures = false;

    this.strokeSize = 1;
    this.fillers = {
      fill: false,
      texture: false,
      stroke: false
    };
  }
  setup(matrix, positionsBuffer, strokePositionBuffer, vertexAttribInfo, vertextColorAttribInfo, vertexCount) {

    this.matrix = matrix;

    this.positionsBuffer = positionsBuffer;
    this.strokePositionBuffer = strokePositionBuffer;

    this.vertexAttribInfo = vertexAttribInfo;
    this.vertextColorAttribInfo = vertextColorAttribInfo;

    this.vertexCount = vertexCount;

    return this;
  }

}

class Display {

  constructor(gl, programInfo, zAxis, texture) {
    this.gl = gl;
    this.programInfo = programInfo;

    this.canvas = gl.canvas;

    this.currentCamera = new Camera(45 * Math.PI / 180, gl.canvas.width / gl.canvas.height, 0.1, 100.0);

    this.currentCamera.translate([0, 0, zAxis]).lookAt([0, 0, 0]);

    this.zAxis = zAxis;
    this.drawZAxis = 0;

    this.last = {};

    texture.textAttribInfo = {
      numComponents: 2,
      type: gl.FLOAT,
      normalize: false,
      stride: 0,
      offset: 0
    };

    this.texture = texture;
    this.spriteSheets = [];

    const context = texture.context;
    const canvas = texture.canvas;

    this.images = {};

  }


  clear(color) {

    const gl = this.gl;

    gl.clearColor(0.1, 0.1, 0.1, 1);

    gl.clearDepth(1.0);
    gl.enable(gl.DEPTH_TEST);
    gl.depthFunc(gl.LEQUAL);

    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);


  }

  rect(x, y, w, h) {

    const {
      rect,
      stroke
    } = this.createRectPos(w, h);

    const square = new Rect();
    square.setup(...this.getRectInfo(x, y, rect, stroke));

    return square;
  }

  fillRect(rect, color) {
    const {
      createStaticDrawBuffer,
      gl,
      parseColor
    } = this;

    rect.fillers.fill = true;

    if (color) {
      rect.fragColorPos = createStaticDrawBuffer(gl, [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]);

    }
  }

  createRectPos(w, h) {

    const rect = [w / 2, h / 2, -w / 2, h / 2, w / 2, -h / 2, -w / 2, -h / 2];
    const stroke = [-w / 2, h / 2, w / 2, h / 2, w / 2, -h / 2, -w / 2, -h / 2, ];
    return {
      rect,
      stroke
    };
  }

  getRectInfo(x, y, rect, stroke) {
    return this.createSquareBuffer(rect, stroke, [x, y, this.drawZAxis]);
  }

  createStaticDrawBuffer(gl, data) {

    const buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);

    return buffer;
  }

  createSquareBuffer(positions, strokePosition, coords) {
    const {
      gl,
      createStaticDrawBuffer
    } = this;

    const positionsBuffer = createStaticDrawBuffer(gl, positions);
    const strokePositionBuffer = createStaticDrawBuffer(gl, strokePosition);
    const modelViewMatrix = mat4.create();

    mat4.translate(modelViewMatrix, modelViewMatrix, coords);

    return [modelViewMatrix, positionsBuffer, strokePositionBuffer, this.createAttribInfo(2, gl.FLOAT, false, 0, 0), this.createAttribInfo(4, gl.FLOAT, false, 0, 0), positions.length / 2];
  }

  createAttribInfo(numComponents, type, normalize, stride, offset) {

    return {
      numComponents,
      type,
      normalize,
      stride,
      offset
    };
  }

  enableAttrib(buffer, attrib, gl, {
    numComponents,
    type,
    normalize,
    stride,
    offset
  }) {

    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.vertexAttribPointer(attrib, numComponents, type, normalize, stride, offset);
    gl.enableVertexAttribArray(attrib);

  }

  drawBuffer(buffer) {

    const {
      gl,
      drawTexture,
      enableAttrib,
      createStaticDrawBuffer,
      currentCamera,
      texture: {
        context,
        canvas,
        textAttribInfo
      },
      programInfo: {
        uniformLocations,
        program,
        attribLocations: {
          vertexPosition,
          vertexColor,
          textureCoord
        }
      }
    } = this;

    const cameraMatrix = currentCamera.matrix;

    const {
      positionsBuffer,
      fragColorPos,
      strokeColorPos,
      strokePositionBuffer,
      matrix,
      vertexAttribInfo,
      vertextColorAttribInfo,
      vertexCount,
      fragTextPos,
      fillers: {
        fill,
        stroke,
        texture
      },
      strokeSize,
      textureInfo,
      multiTextures
    } = buffer;

    gl.uniformMatrix4fv(uniformLocations.projectionMatrix, false, cameraMatrix);
    gl.uniformMatrix4fv(uniformLocations.modelViewMatrix, false, matrix);

    if (fill) {

      enableAttrib(positionsBuffer, vertexPosition, gl, vertexAttribInfo);
      enableAttrib(fragColorPos, vertexColor, gl, vertextColorAttribInfo);
      gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexCount);
      gl.disableVertexAttribArray(vertexColor);

    }

  }

  static loadShader(gl, program, type, source) {

    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    gl.attachShader(program, shader);

  }

  static async create(canvas, width, height, zAxis = 6) {
    canvas.width = width;
    canvas.height = height;

    const gl = canvas.getContext("webgl");

    const shaderProgram = gl.createProgram();

    Display.loadShader(gl, shaderProgram, gl.VERTEX_SHADER, VERTEX_SHADER);
    Display.loadShader(gl, shaderProgram, gl.FRAGMENT_SHADER, FRAGMENT_SHADER);

    gl.linkProgram(shaderProgram);

    const programInfo = {
      program: shaderProgram,
      attribLocations: {
        vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
        vertexColor: gl.getAttribLocation(shaderProgram, 'aVertexColor'),
        textureCoord: gl.getAttribLocation(shaderProgram, 'aTextureCoord'),


      },
      uniformLocations: {
        projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
        modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
        textMatrix: gl.getUniformLocation(shaderProgram, 'uTextMatrix'),
        sampler: gl.getUniformLocation(shaderProgram, 'uSampler'),
        useText: gl.getUniformLocation(shaderProgram, 'aUseText'),
        pointSize: gl.getUniformLocation(shaderProgram, 'uPointSize'),
      },
    };

    gl.useProgram(programInfo.program);

    gl.uniform1f(programInfo.uniformLocations.pointSize, 1.0);

    gl.enable(gl.BLEND);
    gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

    const textureBuffer = gl.createTexture();

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, textureBuffer);
    gl.uniform1i(programInfo.uniformLocations.uSampler, 0);

    const textureCanvas = document.createElement("canvas");

    textureCanvas.width = 0;
    textureCanvas.height = 0;

    let texture = {
      canvas: textureCanvas,
      buffer: textureBuffer,
      context: textureCanvas.getContext("2d"),
    };

    return new Display(gl, programInfo, zAxis, texture);
  }
}

class Engine {
  constructor(time_step, update, render, allowedSkippedFrames) {
    this.accumulated_time = 0;
    this.animation_frame_request = undefined, this.time = undefined, this.time_step = time_step, this.updated = false;
    this.update = update;
    this.render = render;
    this.allowedSkippedFrames = allowedSkippedFrames;
    this.run = this.run.bind(this);
    this.end = false;
  }
  run(time_stamp) {
    const {
      accumulated_time,
      time,
      time_step,
      updated,
      update,
      render,
      allowedSkippedFrames,
      end
    } = this;
    this.accumulated_time += time_stamp - time;
    this.time = time_stamp;
    if (accumulated_time > time_stamp * allowedSkippedFrames) {
      this.accumulated_time = time_stamp;
    }
    while (this.accumulated_time >= time_step) {
      this.accumulated_time -= time_step;
      update(time_stamp);
      this.updated = true;
    }
    if (updated) {
      this.updated = false;
      render(time_stamp);
    }
    if (end) {
      return;
    }
    this.animation_frame_request = requestAnimationFrame(this.run);
  }
  start() {
    this.accumulated_time = this.time_step;
    this.time = performance.now();
    this.animation_frame_request = requestAnimationFrame(this.run);
  }
  stop() {
    this.end = true;
    cancelAnimationFrame(this.animation_frame_request);
  }
}

class Entity extends Rect {

  constructor() {

    super();

    this.velocity = vec2.create();
    this.area = undefined;
    this.mass = 2;

    this.updateFillers = {};
    this.delete = false;
    this.draw = true;
  }

  setup(w, h, ...args) {
    this.area = vec2.fromValues(w, h);
    super.setup(...args);

    return this;
  }

  fill(...args) {
    this.updateFillers.fill = args;
  }

  update(deltaTime, speed) {

    return this;
  }

  move(x, y) {

    super.move([x, y, this.coords[2]]);

    return this;

  }

}

class Quixotic {

  constructor(display) {

    this.display = display;

    this.engine = undefined;

    this.render = undefined;
    this.update = undefined;
    this.frameRate = undefined;

    this.time = 0;
    this.speed = 1;
    this.world = {

      objects: {},
      objectsCollisionInfo: {},
      objectsArray: [],
      classesInfo: {}

    };

    this.timePassed = 0;

  }

  createEntity(Class, ...args) {
    const display = this.display;
    const {
      rect,
      stroke
    } = display.createRectPos(5, 5);
    Class = Class ? Class : Entity;
    const className = Class.name;
    if (className !== "Entity" && !Entity.prototype.isPrototypeOf(Class.prototype)) {
      throw new TypeError("Expected extended class of Entity. Instead got: " + className);
    }
    let instance;
    const {
      objectsArray,
      classesInfo,
      objects
    } = this.world;
    const classInfo = classesInfo[className];
    if (classInfo) {
      if (classInfo.args) {
        instance = new Class(...[...classInfo.args, ...args]);
      } else {
        instance = new Class(...args);
      }
      const name = classInfo.name;
      if (Array.isArray(objects[name])) {
        objects[name].push(instance);
        instance.name = name;
      } else {
        console.warn("Didn't save object in world.objects object, object wouldn't detect collision");
      }
    } else {
      instance = new Class(...args);
    }
    instance.setup(5, 5, ...display.getRectInfo(0, 0, rect, stroke, "#000"));
    objectsArray.push(instance);
    return instance;
  }

  createBackground(objects) {
    const buffer = document.createElement("canvas").getContext("2d");

    const bufferRect = this.createEntity();
    let {
      zAxis,
      canvas: {
        width,
        height
      }
    } = this.display;
    zAxis--;
    const halfZ = zAxis / 2;
    let {
      coords: [x, y],
      area: [w, h]
    } = objects[objects.length - 1];

    const worldViewProjection = mat4.create();
    buffer.canvas.width = width;
    buffer.canvas.height = height;
    for (let i = objects.length; i--;) {
      const {
        coords: [_x, _y],
        area: [_w, _h]
      } = objects[i];
      mat4.multiply(worldViewProjection, this.display.currentCamera.matrix, objects[i].matrix);
      const points = [
        [-_w / 2, -_h / 2, 0],
        [_w / 2, _h / 2, 0],
      ].map(p => {
        const ndc = vec3.transformMat4([], p, worldViewProjection);
        return [
          (ndc[0] * 0.5 + 0.5) * width,
          (ndc[1] * -0.5 + 0.5) * height,
        ];
      });
      const ww = points[1][0] - points[0][0];
      const hh = points[1][1] - points[0][1];

      buffer.strokeStyle = 'red';
      buffer.strokeRect(...points[0], ww, hh);
    }
    document.body.appendChild(buffer.canvas)

  }

  buildWorld({
    objects,
    classes,
    tileMap
  }) {

    const world = this.world;

    if (Array.isArray(objects)) {
      for (let i = objects.length - 1; i > -1; i--) {
        const object = objects[i];
        const {
          name,
          array,
          amount,
          position,
          collision,
          args,
          area
        } = object;

        let createClass;

        if (!object.class) {
          createClass = Entity;
        }

        const _args = args ? args : [];

        let pos;

        if (position) {
          let p = amount;
          if (array) {
            const positions = position.positions;
            pos = function() {
              p--;
              return positions[p];
            };
          } else {
            pos = function() {
              return position.position;
            };
          }
        }

        if (array) {

          let _array = [];

          for (let j = amount; j--;) {

            const instance = this.createEntity(createClass, ..._args);
            instance.name = name;

            if (position) {
              instance.move(...pos());
            }

            if (area) {

              instance.setSize(area);

            }
            _array.push(instance);
          }
          world.objects[name] = _array;
          world.objectsArray.push(..._array);

        }
      }
    }

    return;

  }

  setup(game) {

    const {
      style: {
        backgroundColor,
        backgroundImage,
        stroke
      },
      world,
      engine: {
        frameRate,
        update,
        render
      },
      setup
    } = game;
    this.buildWorld(world);
    const {
      display,
      entitySystem,
      world: {
        objectsArray,
        objects
      }
    } = this;
    if (backgroundImage) {
      display.gl.canvas.style.background = `url(${backgroundImage})`;
      if (repeatX || repeatY) {
        console.log("not read yet");
      }
    }
    this.frameRate = frameRate;
    let lastUpdated = 0;
    this.update = (time) => {
      let deltaTime = time - lastUpdated;
      lastUpdated = time;
      const speed = this.speed;
      this.timePassed += deltaTime * speed;
      for (let i = objectsArray.length; i--;) {
        const object = objectsArray[i];
        if (object.delete) {
          objectsArray.splice(i, 1);
        }
        object.update(deltaTime / 1000, speed);
      }
      update(deltaTime / 1000, this);
    };
    let lastRendered = 0;
    this.render = (timeStamp) => {
      const deltaTime = timeStamp - lastRendered;
      lastRendered = timeStamp;
      if (backgroundColor) display.clear(backgroundColor);
      const length = objectsArray.length;
      for (let i = objectsArray.length; i--;) {
        const object = objectsArray[length - i - 1];
        if (object.draw) {
          const updateFillers = Object.entries(object.updateFillers);
          const fillersLength = updateFillers.length;
          if (fillersLength) {
            for (let i = fillersLength; i--;) {
              const [func, args] = updateFillers[fillersLength - i - 1];
              display[func + "Rect"](object, ...args);
            }
            object.updateFillers = {};
          }
          display.drawBuffer(object);
        }
      }
      const speed = this.speed;
      const spriteSheets = display.spriteSheets;
      for (let i = spriteSheets.length; i--;) {
        spriteSheets[i].update(deltaTime / 1000 * speed);
      }
      render(display, this);
    };
    setup(this, display, this.world);
    this.engine = new Engine(this.frameRate, this.update, this.render, 3);
    this.engine.start();
    return game;
  }

  static async create({
    display: {
      canvas,
      width,
      height,
      zAxis
    },
    homeURL
  }) {

    const display = await Display.create(canvas, width, height, zAxis);

    return new Quixotic(display);
  }

}


const fps = document.querySelector("#fps");
const minLength = innerWidth > innerHeight ? innerHeight : innerWidth;
const game = {

  create: {

    display: {

      canvas: document.querySelector("#canvas"),
      zAxis: 96,
      width: minLength,
      height: minLength,

    },

    homeURL: "/src"
  },

  style: {
    backgroundColor: "#111122"
  },

  world: {
    objects: [{
      name: "trees",

      array: true,
      amount: 5,
      position: {
        type: "set",
        positions: [
          [-37.5, 37.5],
          [0, 0],
          [-37.5, -37.5],
          [37.5, -37.5],
          [37.5, 37.5],
          [10, 10],
          [15, 10],
          [20, 10],
          [25, 10],
          [30, 10]
        ]

      }
    }]
  },

  engine: {

    frameRate: 1000 / 30,

    update: function(deltaTime, engine) {
      fps.innerText = 1 / deltaTime;
    },

    render: function(display) {}
  },

  setup: function(engine, display, {
    objects: {
      trees
    }
  }) {

    trees.forEach(tree => {
      tree.fill("#00ff00")
    })
    engine.createBackground(trees);
  }

};



Quixotic.create(game.create)
  .then(engine => {

    engine.setup(game);
  });
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  background-color: #111c31;
  overflow: hidden;
  align-items: space-around;
  display: grid;
  height: 100%;
  width: 100%;
}

#canvas {
  background-color: #152646;
  /* justify-self: center; */
}

#fps {
  position: fixed;
  color: white;
  right: 0;
}

canvas {
  position: fixed
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>
<canvas id="canvas" width="300" height="300"></canvas>
<p id="fps"></p>

注意:该代码仅在未旋转相机的情况下有效,而正方形也未旋转。如果您确实旋转了相机或正方形,则需要像转换WebGL一样,在变换每组3个顶点后用画布2d绘制三角形。