所以我有这种方法可以在2d世界上的3d世界中绘制所有矩形矩形,我能够将3d对象恰好定位在2d画布上,但是现在我想将2d画布放置在适当的位置的矩形(这样一来就只能用图像绘制一个大矩形,而不是再绘制每个矩形)
这是方法
createBackground(objects) {
const buffer = document.createElement("canvas").getContext("2d");
const bufferRect = this.createEntity();
const display = this.display;
let {
zAxis,
canvas: {
width,
height,
},
currentCamera
} = display;
const cameraMatrix = currentCamera.matrix;
zAxis--;
const halfZ = zAxis / 2;
let { coords: [x, y], area: [w, h] } = objects[objects.length - 1];
const worldViewProjection = mat4.create();
let [bX, bY, lX, lY] = [x+w, y+h, x-w, y-h]; //big x, little x
for(let i = objects.length-1; i--;){
const object = objects[i];
const {coords: [_x, _y], area: [_w, _h]} = objects[i];
if(_x > bX){
bX = _x+_w;
} else if(_x < lX) {
lX = _x-_w;
}
if(_y > bY){
bY = _y+_h;
} else if(_y < lY) {
lY = _y-_h;
}
}
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);
}
bufferRect.move((bX+lX)/2, (bY+lY)/2);
bufferRect.setSize(Math.abs(bX)+Math.abs(lX)-5, Math.abs(bY)+Math.abs(lY)-5);
const texture = display.textureFromImage(buffer.canvas);
bufferRect.attachImage(texture);
}
问题就在这里,我尝试将矩形放置在中间,并将其大小设置为最左边的矩形和最右边的矩形之间以及顶部和底部之间的距离
bufferRect.move((bX+lX)/2, (bY+lY)/2);
bufferRect.setSize(Math.abs(bX)+Math.abs(lX)-5, Math.abs(bY)+Math.abs(lY)-5);
这是完整的代码
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; } `;
function onResize(element, callback) {
let elementHeight = element.height,
elementWidth = element.width;
setInterval(function() {
if (element.height !== elementHeight || element.width !== elementWidth) {
elementHeight = element.height;
elementWidth = element.width;
callback();
}
}, 16);
}
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 Texture {
constructor() {
this.matrix = mat3.create();
this.image = undefined;
this.width = undefined;
this.height = undefined;
this.rotation = 0;
this.y = 0;
this.x = 0;
let onload = function() {};
Object.defineProperty(this, "onload", {
get() {
return onload;
},
set(value) {
if (this.loaded) {
value();
} else {
onload = value;
}
}
});
this.loaded = false;
}
setup(image, y, width, height, matrix, rotation) {
this.image = image;
this.y = y;
this.width = width;
this.height = height;
this.rotation = 0;
this.x = 0;
if (matrix) {
this.matrix = matrix;
if (rotation) {
this.rotation = rotation;
}
}
this.loaded = true;
}
static from(texture) {
const newTexture = new Texture();
const {
image,
y,
width,
height,
matrix,
rotation
} = texture;
newTexture.setup(image, y, width, height, mat3.clone(matrix), rotation);
return newTexture;
}
scale(w, h) {
const matrix = this.matrix;
mat3.scale(matrix, matrix, [w, h]);
}
rotate(rad) {
const matrix = this.matrix;
this.rotation = (this.rotation + rad) % (Math.PI * 2);
mat3.rotate(matrix, matrix, rad);
}
}
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.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;
}
scale(scale) {
const matrix = this.matrix;
mat4.scale(matrix, matrix, scale);
return this;
}
attachImage(newTexture) {
this.fillers.texture = true;
if (this.multiTextures) {
this.textureInfo.push(newTexture);
return;
}
this.textureInfo = newTexture;
this.fillers.TRIANGLE_STRIP = true;
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]);
this.currentCamera.lookAt([0, 0, 0]);
this.zAxis = zAxis;
this.drawZAxis = 0;
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 = {}; /*all the images with their src as their key*/
onResize(texture.canvas, () => {
const images = Object.values(this.images);
for (let i = images.length; i--;) {
const {
image,
y
} = images[i];
context.drawImage(image, 0, y);
}
const internalFormat = gl.RGBA;
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, canvas);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
});
}
setSize(width, height) {
const canvas = this.gl.canvas;
canvas.width = width;
canvas.height = height;
}
clear(color) {
const gl = this.gl;
gl.clearColor(0.1, 0.1, 0.3, 1);
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}
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]);
}
}
textureFromImage(image, texture) {
const {
images,
texture: {
canvas
}
} = this;
texture = texture ? texture : new Texture();
const {
width,
height
} = image;
const y = canvas.height;
if (canvas.width < width) {
canvas.width = width;
}
texture.setup(image, y, width, height, 0);
canvas.height += height;
images[images.length] = texture;
return texture;
}
createRectPos(w, h) {
const rect = [w / 2, h / 2, -w / 2, h / 2, w / 2, -h / 2, -w / 2, -h / 2];
return {
rect,
stroke: undefined
};
}
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);
}
drawTexture(texture, gl, canvas, enableAttrib, createStaticDrawBuffer, textAttribInfo, vertexCount, textureCoord, textMatrix, useText) {
const _width = canvas.width;
const _height = canvas.height;
const {
x,
y,
width,
height,
matrix
} = texture;
const realX = x / _width;
const realWidth = realX + width / _width;
const realHeight = y / _height;
const realY = (y + height) / _height;
const fragTextPos = createStaticDrawBuffer(gl, [realWidth, realHeight, realX, realHeight, realWidth, realY, realX, realY, ]);
gl.uniformMatrix3fv(textMatrix, false, matrix);
enableAttrib(fragTextPos, textureCoord, gl, textAttribInfo);
gl.uniform1f(useText, true);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexCount);
gl.uniform1f(useText, false);
gl.disableVertexAttribArray(textureCoord);
}
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);
}
if (texture) {
const _width = canvas.width;
const _height = canvas.height;
drawTexture(textureInfo, gl, canvas, enableAttrib, createStaticDrawBuffer, textAttribInfo, vertexCount, textureCoord, uniformLocations.textMatrix, uniformLocations.useText);
}
}
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 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;
}
attachImage(image) {
super.attachImage(image);
}
move(x, y) {
super.move([x, y, this.coords[2]]);
return this;
}
setSize(w, h) {
if (typeof w == "object") {
h = w[1];
w = w[0];
}
const area = this.area;
const [_w, _h] = area;
super.scale([w / _w, h / _h, 1]);
area[0] = w;
area[1] = h;
return this;
}
}
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;
update(time_stamp);
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);
}
}
const engineMath = {
randomBetween: function(min, max) {
return min + Math.random() * (max - min);
},
};
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);
let instance = new Entity();
instance.setup(5, 5, ...display.getRectInfo(0, 0, rect, stroke, "#000"));
this.world.objectsArray.push(instance);
return instance;
}
createBackground(objects) {
const buffer = document.createElement("canvas").getContext("2d");
const bufferRect = this.createEntity();
const display = this.display;
let {
zAxis,
canvas: {
width,
height,
},
currentCamera
} = display;
const cameraMatrix = currentCamera.matrix;
zAxis--;
const halfZ = zAxis / 2;
let {
coords: [x, y],
area: [w, h]
} = objects[objects.length - 1];
const worldViewProjection = mat4.create();
let [bX, bY, lX, lY] = [x + w, y + h, x - w, y - h]; //big x, little x
for (let i = objects.length - 1; i--;) {
const object = objects[i];
const {
coords: [_x, _y],
area: [_w, _h]
} = objects[i];
if (_x > bX) {
bX = _x + _w;
} else if (_x < lX) {
lX = _x - _w;
}
if (_y > bY) {
bY = _y + _h;
} else if (_y < lY) {
lY = _y - _h;
}
}
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);
}
bufferRect.move((bX + lX) / 2, (bY + lY) / 2);
bufferRect.setSize(Math.abs(bX) + Math.abs(lX) - 5, Math.abs(bY) + Math.abs(lY) - 5);
const texture = display.textureFromImage(buffer.canvas);
bufferRect.attachImage(texture);
}
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 {
amount,
position,
} = object;
const {
rangeX,
rangeY
} = position;
let _array = [];
for (let j = amount; j--;) {
const instance = this.createEntity();
instance.move(engineMath.randomBetween(...rangeX), engineMath.randomBetween(...rangeY));
_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,
world: {
objectsArray,
objects
}
} = this;
this.frameRate = frameRate;
let lastUpdated = 0;
this.update = (time) => {
let deltaTime = time - lastUpdated;
lastUpdated = time;
const speed = this.speed;
this.timePassed += deltaTime * speed;
update(deltaTime / 1000, this);
};
let lastRendered = 0;
this.render = (timeStamp) => {
if (backgroundColor) display.clear(backgroundColor);
const length = objectsArray.length;
for (let i = 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 j = fillersLength; j--;) {
const [func, args] = updateFillers[fillersLength - j - 1];
display[func + "Rect"](object, ...args);
}
object.updateFillers = {};
}
display.drawBuffer(object);
}
}
render(display, this);
};
setup(this, 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: 90,
width: minLength,
height: minLength,
},
homeURL: "/src"
},
style: {
backgroundColor: "#111122"
},
world: {
objects: [{
name: "trees",
array: true,
amount: 5,
position: {
type: "random",
rangeX: [-37.5, 37.5],
rangeY: [-37.5, 37.5]
}
}]
},
engine: {
frameRate: 1000 / 30,
update: function(deltaTime, engine) {
fps.innerText = 1 / deltaTime;
},
render: function(display) {}
},
setup: function(engine, {
objectsArray
}) {
objectsArray.forEach(tree => {
tree.fill("#00ff00")
})
engine.createBackground(objectsArray);
}
};
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>
答案 0 :(得分:0)
您可以计算到透视投影的仿射坐标。为此,矩形的角通过视图矩阵和投影矩阵进行变换。这似乎工作正常,但是绘制红色矩形时您错过了更改投影和视图矩阵的操作。因此,顶点坐标由投影和视图矩阵两次转换。首先,计算仿射坐标,第二次是在顶点着色器中。
绘制红色矩形时,必须设置与红色矩形的单位匹配的正交投影矩阵,并且视图矩阵必须为Identity matrix。
将红色矩形绘制到坐标系中的平面上,其中大小为5x5的区域映射到视口上。中心在(0,0)。因此,正交投影矩阵为
[2/5,0,0,0, 0,2/5,0,0, 0,0,1,0, 0,0,0,1]
绘制绿色对象后,更改视图矩阵和投影矩阵:
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);
}
const ortho_mat = [2/5,0,0,0, 0,2/5,0,0, 0,0,1,0, 0,0,0,1];
const identity_mat = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1];
gl.uniformMatrix4fv(uniformLocations.projectionMatrix, false, ortho_mat);
gl.uniformMatrix4fv(uniformLocations.modelViewMatrix, false, identity_mat);
// [...]
查看示例:
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; } `;
function onResize(element, callback) {
let elementHeight = element.height,
elementWidth = element.width;
setInterval(function() {
if (element.height !== elementHeight || element.width !== elementWidth) {
elementHeight = element.height;
elementWidth = element.width;
callback();
}
}, 16);
}
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 Texture {
constructor() {
this.matrix = mat3.create();
this.image = undefined;
this.width = undefined;
this.height = undefined;
this.rotation = 0;
this.y = 0;
this.x = 0;
let onload = function() {};
Object.defineProperty(this, "onload", {
get() {
return onload;
},
set(value) {
if (this.loaded) {
value();
} else {
onload = value;
}
}
});
this.loaded = false;
}
setup(image, y, width, height, matrix, rotation) {
this.image = image;
this.y = y;
this.width = width;
this.height = height;
this.rotation = 0;
this.x = 0;
if (matrix) {
this.matrix = matrix;
if (rotation) {
this.rotation = rotation;
}
}
this.loaded = true;
}
static from(texture) {
const newTexture = new Texture();
const {
image,
y,
width,
height,
matrix,
rotation
} = texture;
newTexture.setup(image, y, width, height, mat3.clone(matrix), rotation);
return newTexture;
}
scale(w, h) {
const matrix = this.matrix;
mat3.scale(matrix, matrix, [w, h]);
}
rotate(rad) {
const matrix = this.matrix;
this.rotation = (this.rotation + rad) % (Math.PI * 2);
mat3.rotate(matrix, matrix, rad);
}
}
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.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;
}
scale(scale) {
const matrix = this.matrix;
mat4.scale(matrix, matrix, scale);
return this;
}
attachImage(newTexture) {
this.fillers.texture = true;
if (this.multiTextures) {
this.textureInfo.push(newTexture);
return;
}
this.textureInfo = newTexture;
this.fillers.TRIANGLE_STRIP = true;
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]);
this.currentCamera.lookAt([0, 0, 0]);
this.zAxis = zAxis;
this.drawZAxis = 0;
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 = {}; /*all the images with their src as their key*/
onResize(texture.canvas, () => {
const images = Object.values(this.images);
for (let i = images.length; i--;) {
const {
image,
y
} = images[i];
context.drawImage(image, 0, y);
}
const internalFormat = gl.RGBA;
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, canvas);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
});
}
setSize(width, height) {
const canvas = this.gl.canvas;
canvas.width = width;
canvas.height = height;
}
clear(color) {
const gl = this.gl;
gl.clearColor(0.1, 0.1, 0.3, 1);
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}
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]);
}
}
textureFromImage(image, texture) {
const {
images,
texture: {
canvas
}
} = this;
texture = texture ? texture : new Texture();
const {
width,
height
} = image;
const y = canvas.height;
if (canvas.width < width) {
canvas.width = width;
}
texture.setup(image, y, width, height, 0);
canvas.height += height;
images[images.length] = texture;
return texture;
}
createRectPos(w, h) {
const rect = [w / 2, h / 2, -w / 2, h / 2, w / 2, -h / 2, -w / 2, -h / 2];
return {
rect,
stroke: undefined
};
}
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);
}
drawTexture(texture, gl, canvas, enableAttrib, createStaticDrawBuffer, textAttribInfo, vertexCount, textureCoord, textMatrix, useText) {
const _width = canvas.width;
const _height = canvas.height;
const {
x,
y,
width,
height,
matrix
} = texture;
const realX = x / _width;
const realWidth = realX + width / _width;
const realHeight = y / _height;
const realY = (y + height) / _height;
const fragTextPos = createStaticDrawBuffer(gl, [realWidth, realHeight, realX, realHeight, realWidth, realY, realX, realY, ]);
gl.uniformMatrix3fv(textMatrix, false, matrix);
enableAttrib(fragTextPos, textureCoord, gl, textAttribInfo);
gl.uniform1f(useText, true);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexCount);
gl.uniform1f(useText, false);
gl.disableVertexAttribArray(textureCoord);
}
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);
}
const ortho_mat = [2/5,0,0,0, 0,2/5,0,0, 0,0,1,0, 0,0,0,1];
const identity_mat = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1];
gl.uniformMatrix4fv(uniformLocations.projectionMatrix, false, ortho_mat);
gl.uniformMatrix4fv(uniformLocations.modelViewMatrix, false, identity_mat);
if (texture) {
const _width = canvas.width;
const _height = canvas.height;
drawTexture(textureInfo, gl, canvas, enableAttrib, createStaticDrawBuffer, textAttribInfo, vertexCount, textureCoord, uniformLocations.textMatrix, uniformLocations.useText);
}
}
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 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;
}
attachImage(image) {
super.attachImage(image);
}
move(x, y) {
super.move([x, y, this.coords[2]]);
return this;
}
setSize(w, h) {
if (typeof w == "object") {
h = w[1];
w = w[0];
}
const area = this.area;
const [_w, _h] = area;
super.scale([w / _w, h / _h, 1]);
area[0] = w;
area[1] = h;
return this;
}
}
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;
update(time_stamp);
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);
}
}
const engineMath = {
randomBetween: function(min, max) {
return min + Math.random() * (max - min);
},
};
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);
let instance = new Entity();
instance.setup(5, 5, ...display.getRectInfo(0, 0, rect, stroke, "#000"));
this.world.objectsArray.push(instance);
return instance;
}
createBackground(objects) {
const buffer = document.createElement("canvas").getContext("2d");
const bufferRect = this.createEntity();
const display = this.display;
let {
zAxis,
canvas: {
width,
height,
},
currentCamera
} = display;
const cameraMatrix = currentCamera.matrix;
zAxis--;
const halfZ = zAxis / 2;
let {
coords: [x, y],
area: [w, h]
} = objects[objects.length - 1];
const worldViewProjection = mat4.create();
let [bX, bY, lX, lY] = [x + w, y + h, x - w, y - h]; //big x, little x
for (let i = objects.length - 1; i--;) {
const object = objects[i];
const {
coords: [_x, _y],
area: [_w, _h]
} = objects[i];
if (_x > bX) {
bX = (_x + _w);
} else if (_x < lX) {
lX = _x - _w;
}
if (_y > bY) {
bY = _y + _h;
} else if (_y < lY) {
lY = _y - _h;
}
}
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);
}
bufferRect.move((bX + lX) / 2, (bY + lY) / 2);
bufferRect.setSize(Math.abs(bX) + Math.abs(lX) - 5, Math.abs(bY) + Math.abs(lY) - 5);
const texture = display.textureFromImage(buffer.canvas);
bufferRect.attachImage(texture);
}
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 {
amount,
position,
} = object;
const {
rangeX,
rangeY
} = position;
let _array = [];
for (let j = amount; j--;) {
const instance = this.createEntity();
instance.move(engineMath.randomBetween(...rangeX), engineMath.randomBetween(...rangeY));
_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,
world: {
objectsArray,
objects
}
} = this;
this.frameRate = frameRate;
let lastUpdated = 0;
this.update = (time) => {
let deltaTime = time - lastUpdated;
lastUpdated = time;
const speed = this.speed;
this.timePassed += deltaTime * speed;
update(deltaTime / 1000, this);
};
let lastRendered = 0;
this.render = (timeStamp) => {
if (backgroundColor) display.clear(backgroundColor);
const length = objectsArray.length;
for (let i = 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 j = fillersLength; j--;) {
const [func, args] = updateFillers[fillersLength - j - 1];
display[func + "Rect"](object, ...args);
}
object.updateFillers = {};
}
display.drawBuffer(object);
}
}
render(display, this);
};
setup(this, 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: 90,
width: minLength,
height: minLength,
},
homeURL: "/src"
},
style: {
backgroundColor: "#111122"
},
world: {
objects: [{
name: "trees",
array: true,
amount: 5,
position: {
type: "random",
rangeX: [-37.5, 37.5],
rangeY: [-37.5, 37.5]
}
}]
},
engine: {
frameRate: 1000 / 30,
update: function(deltaTime, engine) {
fps.innerText = 1 / deltaTime;
},
render: function(display) {}
},
setup: function(engine, {
objectsArray
}) {
objectsArray.forEach(tree => {
tree.fill("#00ff00")
})
engine.createBackground(objectsArray);
}
};
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>