繁体   English   中英

如何在二维 canvas 上绘制 3d 对象

[英]How to draw 3d objects on a 2d canvas

这是完整的代码,我不得不从一些与问题无关的函数中删除空格,以确保我在堆栈溢出的 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(wh ,;.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)

}

我有这个 function,它采用在 3d 世界上使用 webgl 绘制的对象,其中包含几个向量和矩阵,基本上我得到了它们的所有位置和体积,以便在 2d ZFCC790C72A86190DE1B549D0 上绘制它们的结果。

结果,黑色方块没有退出覆盖绿色方块

绿色方块是用 webgl 绘制的,黑色方块是在 canvas 渲染 2d 上绘制的,最终结果应该是黑色方块覆盖绿色方块,但我的数学在某个地方。

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

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

其他信息

摄像头 position: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
]

好吧,现在我已经看到了代码。 首先,我的错,但我没有说清楚你应该发布最少的代码 有很多不需要的代码。 另外我不确定这是您自己的数学库还是与 glmatrix 配对。 如果是后者,您只需<script src="cdn/to/glmatrix"></script>即可使用它。

在任何情况下,您都使用透视矩阵和视图矩阵(相机)定位正方形,因此您需要对 2D canvas 使用相同的数学。

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>

注意:代码仅有效,因为相机没有旋转,正方形也没有。 如果您确实旋转了相机或正方形,则需要在转换每组 3 个顶点后使用 canvas 2d 绘制三角形,就像 WebGL 所做的那样。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM