initial commit
This commit is contained in:
commit
e8c3f9862a
193 files changed
+29795
No files matched your search
@@ -0,0 +1,251 @@
|
||||
import { Texture } from './Texture.js';
|
||||
|
||||
const VERTEX_SOURCE = `#version 300 es
|
||||
layout(location = 0) in vec2 aPosition;
|
||||
|
||||
uniform mat4 uProjection;
|
||||
uniform mat4 uModel;
|
||||
|
||||
out vec2 vTexCoord;
|
||||
|
||||
void main() {
|
||||
vTexCoord = aPosition;
|
||||
gl_Position = uProjection * uModel * vec4(aPosition, 0.0, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
const FRAGMENT_SOURCE = `#version 300 es
|
||||
precision mediump float;
|
||||
|
||||
in vec2 vTexCoord;
|
||||
out vec4 outColor;
|
||||
|
||||
uniform sampler2D uSampler;
|
||||
uniform vec4 uColor;
|
||||
|
||||
void main() {
|
||||
outColor = texture(uSampler, vTexCoord) * uColor;
|
||||
}
|
||||
`;
|
||||
|
||||
const QUAD_POSITIONS = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
|
||||
const QUAD_INDICES = new Uint16Array([0, 1, 2, 2, 1, 3]);
|
||||
|
||||
function compileShader(gl, type, source) {
|
||||
const shader = gl.createShader(type);
|
||||
gl.shaderSource(shader, source);
|
||||
gl.compileShader(shader);
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
const message = gl.getShaderInfoLog(shader);
|
||||
gl.deleteShader(shader);
|
||||
throw new Error(`Shader compile error: ${message}`);
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
export class Renderer {
|
||||
constructor(canvas) {
|
||||
this.canvas = canvas;
|
||||
|
||||
const gl = canvas.getContext('webgl2', {
|
||||
alpha: true,
|
||||
premultipliedAlpha: false,
|
||||
antialias: true,
|
||||
});
|
||||
if (!gl) {
|
||||
throw new Error('WebGL2 is not supported by this browser.');
|
||||
}
|
||||
this.gl = gl;
|
||||
|
||||
const vertexShader = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SOURCE);
|
||||
const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SOURCE);
|
||||
|
||||
this.program = gl.createProgram();
|
||||
gl.attachShader(this.program, vertexShader);
|
||||
gl.attachShader(this.program, fragmentShader);
|
||||
gl.linkProgram(this.program);
|
||||
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
|
||||
const message = gl.getProgramInfoLog(this.program);
|
||||
throw new Error(`Program link error: ${message}`);
|
||||
}
|
||||
gl.deleteShader(vertexShader);
|
||||
gl.deleteShader(fragmentShader);
|
||||
|
||||
this.projectionLocation = gl.getUniformLocation(this.program, 'uProjection');
|
||||
this.modelLocation = gl.getUniformLocation(this.program, 'uModel');
|
||||
this.colorLocation = gl.getUniformLocation(this.program, 'uColor');
|
||||
this.samplerLocation = gl.getUniformLocation(this.program, 'uSampler');
|
||||
|
||||
this._centeredProjection = new Float32Array(16);
|
||||
this._modelMatrix = new Float32Array(16);
|
||||
|
||||
this._setupQuad();
|
||||
this.resize(canvas.width, canvas.height);
|
||||
|
||||
gl.enable(gl.BLEND);
|
||||
gl.disable(gl.DEPTH_TEST);
|
||||
gl.disable(gl.CULL_FACE);
|
||||
|
||||
this.setBlendMode('normal');
|
||||
|
||||
gl.useProgram(this.program);
|
||||
gl.uniform1i(this.samplerLocation, 0);
|
||||
}
|
||||
|
||||
setBlendMode(mode) {
|
||||
this._blendMode = mode;
|
||||
const { gl } = this;
|
||||
if (mode === 'additive') {
|
||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
|
||||
} else {
|
||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
}
|
||||
|
||||
clear(r = 0, g = 0, b = 0, a = 0) {
|
||||
const { gl } = this;
|
||||
gl.clearColor(r, g, b, a);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
createTextureFromImage(image) {
|
||||
const texture = new Texture(this.gl, image.width, image.height);
|
||||
texture.uploadImage(image);
|
||||
return texture;
|
||||
}
|
||||
|
||||
get whiteTexture() {
|
||||
if (!this._whiteTexture) {
|
||||
this._whiteTexture = new Texture(this.gl, 1, 1);
|
||||
}
|
||||
return this._whiteTexture;
|
||||
}
|
||||
|
||||
get glowTexture() {
|
||||
if (!this._glowTexture) {
|
||||
this._glowTexture = this._buildRadialTexture(64, (ctx, size) => {
|
||||
const gradient = ctx.createRadialGradient(
|
||||
size / 2, size / 2, 0,
|
||||
size / 2, size / 2, size / 2,
|
||||
);
|
||||
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
|
||||
gradient.addColorStop(0.35, 'rgba(255, 255, 255, 0.6)');
|
||||
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(0, 0, size, size);
|
||||
});
|
||||
}
|
||||
return this._glowTexture;
|
||||
}
|
||||
|
||||
get ringTexture() {
|
||||
if (!this._ringTexture) {
|
||||
this._ringTexture = this._buildRadialTexture(128, (ctx, size) => {
|
||||
ctx.clearRect(0, 0, size, size);
|
||||
ctx.lineWidth = size * 0.06;
|
||||
ctx.strokeStyle = '#fff';
|
||||
ctx.beginPath();
|
||||
ctx.arc(size / 2, size / 2, size * 0.42, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
const gradient = ctx.createRadialGradient(
|
||||
size / 2, size / 2, size * 0.28,
|
||||
size / 2, size / 2, size * 0.52,
|
||||
);
|
||||
gradient.addColorStop(0, 'rgba(255, 255, 255, 0.5)');
|
||||
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.beginPath();
|
||||
ctx.arc(size / 2, size / 2, size * 0.5, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
});
|
||||
}
|
||||
return this._ringTexture;
|
||||
}
|
||||
|
||||
_buildRadialTexture(size, draw) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
const ctx = canvas.getContext('2d');
|
||||
draw(ctx, size);
|
||||
const texture = new Texture(this.gl, size, size);
|
||||
texture.uploadCanvas(canvas);
|
||||
return texture;
|
||||
}
|
||||
|
||||
drawTexture(texture, x, y, width, height, color = [1, 1, 1, 1], angle = 0) {
|
||||
const { gl } = this;
|
||||
|
||||
this._setModelMatrix(x, y, width, height, angle);
|
||||
|
||||
gl.useProgram(this.program);
|
||||
gl.uniformMatrix4fv(this.projectionLocation, false, this._centeredProjection);
|
||||
gl.uniformMatrix4fv(this.modelLocation, false, this._modelMatrix);
|
||||
gl.uniform4f(this.colorLocation, ...color);
|
||||
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture.handle);
|
||||
gl.bindVertexArray(this._quadVao);
|
||||
gl.drawElements(gl.TRIANGLES, QUAD_INDICES.length, gl.UNSIGNED_SHORT, 0);
|
||||
gl.bindVertexArray(null);
|
||||
gl.bindTexture(gl.TEXTURE_2D, null);
|
||||
}
|
||||
|
||||
resize(deviceWidth, deviceHeight, worldWidth = deviceWidth, worldHeight = deviceHeight) {
|
||||
this.canvas.width = deviceWidth;
|
||||
this.canvas.height = deviceHeight;
|
||||
this.gl.viewport(0, 0, deviceWidth, deviceHeight);
|
||||
this._worldWidth = worldWidth;
|
||||
this._worldHeight = worldHeight;
|
||||
this._syncProjection(0, 0, worldWidth, worldHeight);
|
||||
}
|
||||
|
||||
setViewOffset(dx = 0, dy = 0) {
|
||||
this._syncProjection(dx, dy, this._worldWidth, this._worldHeight);
|
||||
}
|
||||
|
||||
_setupQuad() {
|
||||
const { gl } = this;
|
||||
|
||||
this._quadVao = gl.createVertexArray();
|
||||
gl.bindVertexArray(this._quadVao);
|
||||
|
||||
const positionBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, QUAD_POSITIONS, gl.STATIC_DRAW);
|
||||
gl.enableVertexAttribArray(0);
|
||||
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
const indexBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
||||
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, QUAD_INDICES, gl.STATIC_DRAW);
|
||||
|
||||
gl.bindVertexArray(null);
|
||||
}
|
||||
|
||||
_setModelMatrix(x, y, width, height, angle = 0) {
|
||||
const m = this._modelMatrix;
|
||||
if (angle === 0) {
|
||||
m[0] = width; m[1] = 0; m[2] = 0; m[3] = 0;
|
||||
m[4] = 0; m[5] = height; m[6] = 0; m[7] = 0;
|
||||
m[8] = 0; m[9] = 0; m[10] = 1; m[11] = 0;
|
||||
m[12] = x; m[13] = y; m[14] = 0; m[15] = 1;
|
||||
return;
|
||||
}
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
const cx = x + width / 2;
|
||||
const cy = y + height / 2;
|
||||
m[0] = width * cos; m[1] = width * sin; m[2] = 0; m[3] = 0;
|
||||
m[4] = -height * sin; m[5] = height * cos; m[6] = 0; m[7] = 0;
|
||||
m[8] = 0; m[9] = 0; m[10] = 1; m[11] = 0;
|
||||
m[12] = cx; m[13] = cy; m[14] = 0; m[15] = 1;
|
||||
}
|
||||
|
||||
_syncProjection(left, top, width, height) {
|
||||
const m = this._centeredProjection;
|
||||
m[0] = 2 / width; m[1] = 0; m[2] = 0; m[3] = 0;
|
||||
m[4] = 0; m[5] = -2 / height; m[6] = 0; m[7] = 0;
|
||||
m[8] = 0; m[9] = 0; m[10] = 1; m[11] = 0;
|
||||
m[12] = -1 - (2 * left) / width; m[13] = 1 + (2 * top) / height; m[14] = 0; m[15] = 1;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user