'use strict'
/**
* @type {WebGLRenderingContext}
*/
let GL
namespace: {
const container = document.createElement('div')
const canvas = document.createElement('canvas')
canvas.width = 0
canvas.height = 0
canvas.style.width = '100%'
canvas.style.height = '100%'
container.style.position = 'fixed'
container.appendChild(canvas)
document.body.insertBefore(
container, document.body.firstChild,
)
// 侦听WebGL上下文丢失事件
canvas.on('webglcontextlost', function (event) {
event.preventDefault()
setTimeout(() => GL.WEBGL_lose_context.restoreContext())
})
// 侦听WebGL上下文恢复事件
canvas.on('webglcontextrestored', function (event) {
GL.restore()
})
// WebGL上下文选项
const options = {
antialias: false,
alpha: false,
depth: true,
stencil: false,
premultipliedAlpha: false,
preserveDrawingBuffer: false,
desynchronized: false,
}
// 优先使用WebGL2(Win10 DirectX11)
GL = canvas.getContext('webgl2', options)
if (!GL) {
// 回退到WebGL1(Win7 DirectX9以及旧移动设备)
GL = canvas.getContext('webgl', options)
// 获取元素索引 32 位无符号整数扩展
const element_index_uint = GL.getExtension('OES_element_index_uint')
// 获取顶点数组对象扩展
const vertex_array_object = GL.getExtension('OES_vertex_array_object')
GL.createVertexArray = vertex_array_object.createVertexArrayOES.bind(vertex_array_object)
GL.deleteVertexArray = vertex_array_object.deleteVertexArrayOES.bind(vertex_array_object)
GL.isVertexArray = vertex_array_object.isVertexArrayOES.bind(vertex_array_object)
GL.bindVertexArray = vertex_array_object.bindVertexArrayOES.bind(vertex_array_object)
// 获取最小和最大混合模式扩展
const blend_minmax = GL.getExtension('EXT_blend_minmax')
GL.MIN = blend_minmax.MIN_EXT
GL.MAX = blend_minmax.MAX_EXT
// 更新缓冲数据
const prototype = WebGLRenderingContext.prototype
prototype._bufferData = prototype.bufferData
prototype.bufferData = function (target, data, usage, offset, length) {
if (length !== undefined) {
length *= data.BYTES_PER_ELEMENT
data = new Uint8Array(data.buffer, offset, length)
}
return this._bufferData(target, data, usage)
}
// 获取图像像素数据
const pixelCanvas = document.createElement('canvas')
pixelCanvas.width = 0
pixelCanvas.height = 0
const context2d = pixelCanvas.getContext('2d')
prototype.getImageData = function (image) {
pixelCanvas.width = image.width
pixelCanvas.height = image.height
context2d.drawImage(image, 0, 0)
return context2d.getImageData(0, 0, image.width, image.height).data
}
}
// 获取失去上下文扩展
GL.WEBGL_lose_context = GL.getExtension('WEBGL_lose_context')
// 设置画布容器元素
GL.container = container
}
// ******************************** WebGL方法 ********************************
/** WebGL上下文方法 - 初始化 */
GL.initialize = function () {
// 设置初始属性
this.flip = this.flip ?? -1
this.alpha = this.alpha ?? 1
this.blend = this.blend ?? 'normal'
this.matrix = this.matrix ?? new Matrix()
this.width = this.drawingBufferWidth
this.height = this.drawingBufferHeight
this.program = null
this.binding = null
this.masking = false
this.depthTest = false
// 创建环境光对象
this.ambient = {red: -1, green: -1, blue: -1}
// 创建纹理管理器
this.textureManager = this.textureManager ?? new TextureManager()
// 设置最大纹理数量
this.maxTexUnits = 16
// 创建反射光纹理
this.reflectedLightMap = this.reflectedLightMap ?? new Texture({
format: this.RGB,
magFilter: this.LINEAR,
minFilter: this.LINEAR,
})
this.reflectedLightMap.fbo = this.createTextureFBO(this.reflectedLightMap)
this.activeTexture(this.TEXTURE0 + this.maxTexUnits - 1)
this.bindTexture(this.TEXTURE_2D, this.reflectedLightMap.base.glTexture)
this.activeTexture(this.TEXTURE0)
// 创建直射光纹理
this.directLightMap = this.directLightMap ?? new Texture({
format: this.RGB,
magFilter: this.LINEAR,
minFilter: this.LINEAR,
})
this.directLightMap.base.protected = true
this.directLightMap.fbo = this.createTextureFBO(this.directLightMap)
// 创建模板纹理(用来绘制文字)
this.stencilTexture = this.stencilTexture ?? new Texture({format: this.ALPHA})
// 创建遮罩纹理
this.maskTexture = this.maskTexture ?? new Texture({format: this.RGBA})
this.maskTexture.fbo = this.createTextureFBO(this.maskTexture)
// 创建图层数组(用于排序)
this.layers = this.layers ?? new Uint32Array(0x40000)
// 创建零值数组(用完后要确保所有值归零)
this.zeros = this.zeros ?? new Uint32Array(0x40000)
// 创建类型化数组,分成4个区间,每个区间共享数据缓冲区
// 主要用于读写顶点数据,也可以共享使用来减少内存开销
const size = 512 * 512
if (!this.arrays) {
const buffer1 = new ArrayBuffer(size * 96)
const buffer2 = new ArrayBuffer(size * 12)
const buffer3 = new ArrayBuffer(size * 8)
const buffer4 = new ArrayBuffer(size * 40)
this.arrays = {
0: {
uint8: new Uint8Array(buffer1, 0, size * 96),
uint16: new Uint16Array(buffer1, 0, size * 48),
uint32: new Uint32Array(buffer1, 0, size * 24),
float32: new Float32Array(buffer1, 0, size * 24),
float64: new Float64Array(buffer1, 0, size * 12),
},
1: {
uint8: new Uint8Array(buffer2, 0, size * 12),
uint16: new Uint16Array(buffer2, 0, size * 6),
uint32: new Uint32Array(buffer2, 0, size * 3),
float32: new Float32Array(buffer2, 0, size * 3),
float64: new Float64Array(buffer2, 0, size * 1.5),
},
2: {
uint32: new Uint32Array(buffer3, 0, size * 2),
float32: new Float32Array(buffer3, 0, size * 2),
},
3: {
uint32: new Uint32Array(buffer4, 0, size * 10),
float32: new Float32Array(buffer4, 0, size * 10),
},
}
}
// 创建通用帧缓冲区
this.frameBuffer = this.createFramebuffer()
// 创建通用顶点缓冲区
this.vertexBuffer = this.createBuffer()
// 创建通用元素索引缓冲区,刚好够绘制512x512个图块
const indices = this.arrays[0].uint32
for (let i = 0; i < size; i++) {
const ei = i * 6
const vi = i * 4
indices[ei ] = vi
indices[ei + 1] = vi + 1
indices[ei + 2] = vi + 2
indices[ei + 3] = vi
indices[ei + 4] = vi + 2
indices[ei + 5] = vi + 3
}
this.elementBuffer = this.createBuffer()
this.bindBuffer(this.ELEMENT_ARRAY_BUFFER, this.elementBuffer)
this.bufferData(this.ELEMENT_ARRAY_BUFFER, indices, this.STATIC_DRAW, 0, size * 6)
this.bindBuffer(this.ELEMENT_ARRAY_BUFFER, null)
// 创建更新混合模式方法(闭包)
this.updateBlending = this.createBlendingUpdater()
// 创建批量渲染器
this.batchRenderer = new BatchRenderer(this)
// 创建离屏纹理(启用深度缓冲区)
this.offscreen = this.offscreen ?? {
enabled: false,
current: new Texture({format: this.RGB}),
last: new Texture({format: this.RGB}),
}
const {current, last} = this.offscreen
current.fbo = this.createTextureFBO(current)
last.fbo = this.createTextureFBO(last)
// 创建程序对象
this.imageProgram = this.createImageProgram()
this.tileProgram = this.createTileProgram()
this.spriteProgram = this.createSpriteProgram()
this.particleProgram = this.createParticleProgram()
this.lightProgram = this.createLightProgram()
this.graphicProgram = this.createGraphicProgram()
}
// WebGL上下文方法 - 恢复上下文
GL.restore = function () {
const {ambient} = this
this.textureManager.restore()
this.initialize()
this.setAmbientLight(ambient)
this.updateLightTexSize()
}
/**
* WebGL上下文方法 - 创建程序对象
* @param {string} vshader 顶点着色器代码
* @param {string} fshader 片元着色器代码
* @returns {WebGLProgram|null}
*/
GL.createProgramWithShaders = function (vshader, fshader) {
const vertexShader = this.loadShader(this.VERTEX_SHADER, vshader)
const fragmentShader = this.loadShader(this.FRAGMENT_SHADER, fshader)
if (!vertexShader || !fragmentShader) {
return null
}
const program = this.createProgram()
this.attachShader(program, vertexShader)
this.attachShader(program, fragmentShader)
this.linkProgram(program)
if (!this.getProgramParameter(program, this.LINK_STATUS)) {
const error = this.getProgramInfoLog(program)
console.error(`Failed to link program: ${error}`)
this.deleteProgram(program)
this.deleteShader(fragmentShader)
this.deleteShader(vertexShader)
return null
}
return program
}
/**
* WebGL上下文方法 - 加载着色器
* @param {number} type 着色器类型
* @param {string} source 着色器代码
* @returns {WebGLShader|null}
*/
GL.loadShader = function (type, source) {
const shader = this.createShader(type)
this.shaderSource(shader, source)
this.compileShader(shader)
if (!this.getShaderParameter(shader, this.COMPILE_STATUS)) {
const error = this.getShaderInfoLog(shader)
console.error(`Failed to compile shader: ${error}`)
this.deleteShader(shader)
return null
}
return shader
}
/** WebGL上下文方法 - 创建图像程序 */
GL.createImageProgram = function () {
const program = this.createProgramWithShaders(
`
attribute vec2 a_Position;
attribute vec2 a_TexCoord;
attribute float a_Opacity;
uniform float u_Flip;
uniform mat3 u_Matrix;
uniform vec3 u_Ambient;
uniform int u_LightMode;
uniform vec2 u_LightCoord;
uniform vec4 u_LightTexSize;
uniform sampler2D u_LightSampler;
varying vec2 v_TexCoord;
varying vec3 v_LightColor;
varying float v_Opacity;
// 获取光照颜色系数
vec3 getLightColor() {
if (u_LightMode == 0) {
// 光线采样:原始图像
return vec3(1.0, 1.0, 1.0);
}
if (u_LightMode == 1) {
// 光线采样:全局采样
return vec3(
gl_Position.x / u_LightTexSize.x + u_LightTexSize.z,
gl_Position.y / u_LightTexSize.y * u_Flip + u_LightTexSize.w,
-1.0
);
}
if (u_LightMode == 2) {
// 光线采样:锚点采样
vec2 anchorCoord = (u_Matrix * vec3(u_LightCoord, 1.0)).xy;
vec2 lightCoord = vec2(
anchorCoord.x / u_LightTexSize.x + u_LightTexSize.z,
anchorCoord.y / u_LightTexSize.y * u_Flip + u_LightTexSize.w
);
return texture2D(u_LightSampler, lightCoord).rgb;
}
if (u_LightMode == 3) {
// 光线采样:环境光
return u_Ambient;
}
}
void main() {
gl_Position.xyw = u_Matrix * vec3(a_Position, 1.0);
v_TexCoord = a_TexCoord;
v_LightColor = getLightColor();
v_Opacity = a_Opacity;
}
`,
`
precision highp float;
varying vec2 v_TexCoord;
varying vec3 v_LightColor;
uniform vec2 u_Viewport;
uniform int u_Masking;
varying float v_Opacity;
uniform float u_Alpha;
uniform int u_ColorMode;
uniform vec4 u_Color;
uniform vec4 u_Tint;
uniform vec4 u_Repeat;
uniform sampler2D u_Sampler;
uniform sampler2D u_MaskSampler;
uniform sampler2D u_LightSampler;
// 获取光照颜色系数(全局采样)
vec3 getLightColor() {
if (v_LightColor.z != -1.0) return v_LightColor;
return texture2D(u_LightSampler, v_LightColor.xy).rgb;
}
void main() {
if (u_ColorMode == 0) {
// 颜色模式:纹理采样 + 色调
gl_FragColor = texture2D(u_Sampler, fract(v_TexCoord));
if (gl_FragColor.a == 0.0) discard;
gl_FragColor.rgb = gl_FragColor.rgb * (1.0 - u_Tint.a) + u_Tint.rgb +
dot(gl_FragColor.rgb, vec3(0.299, 0.587, 0.114)) * u_Tint.a;
} else if (u_ColorMode == 1) {
// 颜色模式:指定颜色
float alpha = texture2D(u_Sampler, v_TexCoord).a;
if (alpha == 0.0) discard;
gl_FragColor = vec4(u_Color.rgb, u_Color.a * alpha);
} else if (u_ColorMode == 2) {
// 颜色模式:纹理采样 + 切片
vec2 uv = vec2(
mod(v_TexCoord.x - u_Repeat.x, u_Repeat.z) + u_Repeat.x,
mod(v_TexCoord.y - u_Repeat.y, u_Repeat.w) + u_Repeat.y
);
gl_FragColor = texture2D(u_Sampler, uv);
if (gl_FragColor.a == 0.0) discard;
gl_FragColor.rgb = gl_FragColor.rgb * (1.0 - u_Tint.a) + u_Tint.rgb +
dot(gl_FragColor.rgb, vec3(0.299, 0.587, 0.114)) * u_Tint.a;
}
gl_FragColor.rgb *= getLightColor();
gl_FragColor.a *= v_Opacity * u_Alpha;
if (u_Masking == 1) {
vec2 fragCoord = vec2(gl_FragCoord.x, (u_Viewport.y - gl_FragCoord.y));
gl_FragColor.a *= texture2D(u_MaskSampler, fragCoord / u_Viewport).a;
}
}
`,
)
this.useProgram(program)
// 顶点着色器属性
const a_Position = this.getAttribLocation(program, 'a_Position')
const a_TexCoord = this.getAttribLocation(program, 'a_TexCoord')
const a_Opacity = this.getAttribLocation(program, 'a_Opacity')
const u_Flip = this.getUniformLocation(program, 'u_Flip')
const u_Matrix = this.getUniformLocation(program, 'u_Matrix')
const u_Ambient = this.getUniformLocation(program, 'u_Ambient')
const u_LightMode = this.getUniformLocation(program, 'u_LightMode')
const u_LightCoord = this.getUniformLocation(program, 'u_LightCoord')
const u_LightTexSize = this.getUniformLocation(program, 'u_LightTexSize')
// 设置光照采样器指向最后一个纹理
this.uniform1i(this.getUniformLocation(program, 'u_LightSampler'), this.maxTexUnits - 1)
// 片元着色器属性
const u_Viewport = this.getUniformLocation(program, 'u_Viewport')
const u_Masking = this.getUniformLocation(program, 'u_Masking')
const u_Alpha = this.getUniformLocation(program, 'u_Alpha')
const u_ColorMode = this.getUniformLocation(program, 'u_ColorMode')
const u_Color = this.getUniformLocation(program, 'u_Color')
const u_Tint = this.getUniformLocation(program, 'u_Tint')
const u_Repeat = this.getUniformLocation(program, 'u_Repeat')
const u_MaskSampler = this.getUniformLocation(program, 'u_MaskSampler')
// 创建顶点数组对象
const vao = this.createVertexArray()
this.bindVertexArray(vao)
this.enableVertexAttribArray(a_Position)
this.enableVertexAttribArray(a_TexCoord)
this.enableVertexAttribArray(a_Opacity)
this.bindBuffer(this.ARRAY_BUFFER, this.vertexBuffer)
this.vertexAttribPointer(a_Position, 2, this.FLOAT, false, 20, 0)
this.vertexAttribPointer(a_TexCoord, 2, this.FLOAT, false, 20, 8)
this.vertexAttribPointer(a_Opacity, 1, this.FLOAT, false, 20, 16)
this.bindBuffer(this.ELEMENT_ARRAY_BUFFER, this.elementBuffer)
// 创建顶点数组对象 - 属性[110]
vao.a110 = this.createVertexArray()
this.bindVertexArray(vao.a110)
this.enableVertexAttribArray(a_Position)
this.enableVertexAttribArray(a_TexCoord)
this.bindBuffer(this.ARRAY_BUFFER, this.vertexBuffer)
this.vertexAttribPointer(a_Position, 2, this.FLOAT, false, 16, 0)
this.vertexAttribPointer(a_TexCoord, 2, this.FLOAT, false, 16, 8)
this.bindBuffer(this.ELEMENT_ARRAY_BUFFER, this.elementBuffer)
// 使用程序对象
const use = () => {
if (this.program !== program) {
this.program = program
this.useProgram(program)
}
if (program.flip !== this.flip) {
program.flip = this.flip
this.uniform1f(u_Flip, program.flip)
}
if (program.alpha !== this.alpha) {
program.alpha = this.alpha
this.uniform1f(u_Alpha, program.alpha)
}
this.updateMasking()
this.updateBlending()
return program
}
// 保存程序对象
program.use = use
program.vao = vao
program.alpha = 0
program.masking = false
program.a_Position = a_Position
program.a_TexCoord = a_TexCoord
program.a_Opacity = a_Opacity
program.u_Matrix = u_Matrix
program.u_Ambient = u_Ambient
program.u_LightMode = u_LightMode
program.u_LightCoord = u_LightCoord
program.u_LightTexSize = u_LightTexSize
program.u_Viewport = u_Viewport
program.u_Masking = u_Masking
program.u_MaskSampler = u_MaskSampler
program.u_ColorMode = u_ColorMode
program.u_Color = u_Color
program.u_Tint = u_Tint
program.u_Repeat = u_Repeat
return program
}
/** WebGL上下文方法 - 创建图块程序 */
GL.createTileProgram = function () {
const program = this.createProgramWithShaders(
`
attribute vec2 a_Position;
attribute vec2 a_TexCoord;
attribute float a_TexIndex;
uniform float u_Flip;
uniform mat3 u_Matrix;
uniform vec3 u_Ambient;
uniform int u_LightMode;
uniform vec4 u_LightTexSize;
uniform sampler2D u_LightSampler;
varying float v_TexIndex;
varying vec2 v_TexCoord;
varying vec3 v_LightColor;
// 获取光照颜色系数
vec3 getLightColor() {
if (u_LightMode == 0) {
// 光线采样:原始图像
return vec3(1.0, 1.0, 1.0);
}
if (u_LightMode == 1) {
// 光线采样:全局采样
return vec3(
gl_Position.x / u_LightTexSize.x + u_LightTexSize.z,
gl_Position.y / u_LightTexSize.y * u_Flip + u_LightTexSize.w,
-1.0
);
}
if (u_LightMode == 2) {
// 光线采样:环境光
return u_Ambient;
}
}
void main() {
gl_Position.xyw = u_Matrix * vec3(a_Position, 1.0);
v_TexCoord = a_TexCoord;
v_TexIndex = a_TexIndex;
v_LightColor = getLightColor();
}
`,
`
precision highp float;
varying float v_TexIndex;
varying vec2 v_TexCoord;
varying vec3 v_LightColor;
uniform float u_Alpha;
uniform sampler2D u_Samplers[15];
uniform sampler2D u_LightSampler;
// 采样纹理像素颜色(采样器索引,坐标)
// 采样器数组的索引必须使用常量
vec4 sampler(int index, vec2 uv) {
for (int i = 0; i < 15; i++) {
if (i == index) {
return texture2D(u_Samplers[i], uv);
}
}
}
// 获取光照颜色系数(全局采样)
vec3 getLightColor() {
if (v_LightColor.z != -1.0) return v_LightColor;
return texture2D(u_LightSampler, v_LightColor.xy).rgb;
}
void main() {
gl_FragColor = sampler(int(v_TexIndex), v_TexCoord);
if (gl_FragColor.a == 0.0) discard;
gl_FragColor.rgb *= getLightColor();
gl_FragColor.a *= u_Alpha;
}
`,
)
this.useProgram(program)
// 顶点着色器属性
const a_Position = this.getAttribLocation(program, 'a_Position')
const a_TexCoord = this.getAttribLocation(program, 'a_TexCoord')
const a_TexIndex = this.getAttribLocation(program, 'a_TexIndex')
const u_Flip = this.getUniformLocation(program, 'u_Flip')
const u_Matrix = this.getUniformLocation(program, 'u_Matrix')
const u_Ambient = this.getUniformLocation(program, 'u_Ambient')
const u_LightMode = this.getUniformLocation(program, 'u_LightMode')
const u_LightTexSize = this.getUniformLocation(program, 'u_LightTexSize')
// 设置光照采样器指向最后一个纹理
this.uniform1i(this.getUniformLocation(program, 'u_LightSampler'), this.maxTexUnits - 1)
// 片元着色器属性
const u_Alpha = this.getUniformLocation(program, 'u_Alpha')
const u_SamplerLength = this.maxTexUnits - 1
const u_Samplers = []
for (let i = 0; i < u_SamplerLength; i++) {
u_Samplers.push(this.getUniformLocation(program, `u_Samplers[${i}]`))
}
// 创建顶点数组对象
const vao = this.createVertexArray()
this.bindVertexArray(vao)
this.enableVertexAttribArray(a_Position)
this.enableVertexAttribArray(a_TexCoord)
this.enableVertexAttribArray(a_TexIndex)
this.bindBuffer(this.ARRAY_BUFFER, this.vertexBuffer)
this.vertexAttribPointer(a_Position, 2, this.FLOAT, false, 20, 0)
this.vertexAttribPointer(a_TexCoord, 2, this.FLOAT, false, 20, 8)
this.vertexAttribPointer(a_TexIndex, 1, this.FLOAT, false, 20, 16)
this.bindBuffer(this.ELEMENT_ARRAY_BUFFER, this.elementBuffer)
// 使用程序对象
const use = () => {
if (this.program !== program) {
this.program = program
this.useProgram(program)
}
if (program.flip !== this.flip) {
program.flip = this.flip
this.uniform1f(u_Flip, program.flip)
}
if (program.alpha !== this.alpha) {
program.alpha = this.alpha
this.uniform1f(u_Alpha, program.alpha)
}
return program
}
// 保存程序对象
program.use = use
program.vao = vao
program.flip = null
program.alpha = 0
program.samplerNum = 1
program.a_Position = a_Position
program.a_TexCoord = a_TexCoord
program.a_TexIndex = a_TexIndex
program.u_Matrix = u_Matrix
program.u_Ambient = u_Ambient
program.u_LightMode = u_LightMode
program.u_LightTexSize = u_LightTexSize
program.u_Samplers = u_Samplers
return program
}
/** WebGL上下文方法 - 创建精灵程序 */
GL.createSpriteProgram = function () {
const program = this.createProgramWithShaders(
`
attribute vec2 a_Position;
attribute vec2 a_TexCoord;
attribute vec3 a_TexParam;
attribute vec4 a_Tint;
attribute vec2 a_LightCoord;
uniform float u_Flip;
uniform mat3 u_Matrix;
uniform vec4 u_LightTexSize;
uniform sampler2D u_LightSampler;
varying float v_TexIndex;
varying float v_Opacity;
varying vec4 v_Tint;
varying vec2 v_TexCoord;
varying vec3 v_LightColor;
// 获取光照颜色系数
vec3 getLightColor() {
// 参数Z分量是0 = 光线采样:原始图像
if (a_TexParam.z == 0.0) {
return vec3(1.0, 1.0, 1.0);
}
// 参数Z分量是1 = 光线采样:全局采样
if (a_TexParam.z == 1.0) {
return vec3(
gl_Position.x / u_LightTexSize.x + u_LightTexSize.z,
gl_Position.y / u_LightTexSize.y * u_Flip + u_LightTexSize.w,
-1.0
);
}
// 参数Z分量是2 = 光线采样:锚点采样
if (a_TexParam.z == 2.0) {
return texture2D(u_LightSampler, a_LightCoord).rgb;
}
}
void main() {
gl_Position.xyw = u_Matrix * vec3(a_Position, 1.0);
v_TexIndex = a_TexParam.x;
// 不透明度归一化(0~255映射为0~1)
v_Opacity = a_TexParam.y / 255.0;
// 解压缩色调编码(0~510映射为-1~1)
v_Tint = a_Tint / 255.0 - 1.0;
v_TexCoord = a_TexCoord;
v_LightColor = getLightColor();
}
`,
`
precision highp float;
varying float v_TexIndex;
varying float v_Opacity;
varying vec4 v_Tint;
varying vec2 v_TexCoord;
varying vec3 v_LightColor;
uniform float u_Alpha;
uniform sampler2D u_Samplers[15];
uniform sampler2D u_LightSampler;
// 采样纹理像素颜色(采样器索引,坐标)
// 采样器数组的索引必须使用常量
vec4 sampler(int index, vec2 uv) {
for (int i = 0; i < 15; i++) {
if (i == index) {
return texture2D(u_Samplers[i], uv);
}
}
}
// 对颜色使用色调
vec3 tint(vec3 color, vec4 tint) {
return color.rgb * (1.0 - tint.a) + tint.rgb +
dot(color.rgb, vec3(0.299, 0.587, 0.114)) * tint.a;
}
// 获取光照颜色系数(全局采样)
vec3 getLightColor() {
if (v_LightColor.z != -1.0) return v_LightColor;
return texture2D(u_LightSampler, v_LightColor.xy).rgb;
}
void main() {
gl_FragColor = sampler(int(v_TexIndex), v_TexCoord);
if (gl_FragColor.a == 0.0) discard;
gl_FragColor.rgb = tint(gl_FragColor.rgb, v_Tint) * getLightColor();
gl_FragColor.a *= v_Opacity * u_Alpha;
}
`,
)
this.useProgram(program)
// 顶点着色器属性
const a_Position = this.getAttribLocation(program, 'a_Position')
const a_TexCoord = this.getAttribLocation(program, 'a_TexCoord')
const a_TexParam = this.getAttribLocation(program, 'a_TexParam')
const a_Tint = this.getAttribLocation(program, 'a_Tint')
const a_LightCoord = this.getAttribLocation(program, 'a_LightCoord')
const u_Flip = this.getUniformLocation(program, 'u_Flip')
const u_Matrix = this.getUniformLocation(program, 'u_Matrix')
const u_LightTexSize = this.getUniformLocation(program, 'u_LightTexSize')
// 设置光照采样器指向最后一个纹理
this.uniform1i(this.getUniformLocation(program, 'u_LightSampler'), this.maxTexUnits - 1)
// 片元着色器属性
const u_Alpha = this.getUniformLocation(program, 'u_Alpha')
const u_SamplerLength = this.maxTexUnits - 1
const u_Samplers = []
for (let i = 0; i < u_SamplerLength; i++) {
u_Samplers.push(this.getUniformLocation(program, `u_Samplers[${i}]`))
}
// 创建顶点数组对象
const vao = this.createVertexArray()
this.bindVertexArray(vao)
this.enableVertexAttribArray(a_Position)
this.enableVertexAttribArray(a_TexCoord)
this.enableVertexAttribArray(a_TexParam)
this.enableVertexAttribArray(a_Tint)
this.enableVertexAttribArray(a_LightCoord)
this.bindBuffer(this.ARRAY_BUFFER, this.vertexBuffer)
this.vertexAttribPointer(a_Position, 2, this.FLOAT, false, 32, 0)
this.vertexAttribPointer(a_TexCoord, 2, this.FLOAT, false, 32, 8)
this.vertexAttribPointer(a_TexParam, 3, this.UNSIGNED_BYTE, false, 32, 16)
this.vertexAttribPointer(a_Tint, 4, this.UNSIGNED_SHORT, false, 32, 20)
this.vertexAttribPointer(a_LightCoord, 2, this.UNSIGNED_SHORT, true, 32, 28)
this.bindBuffer(this.ELEMENT_ARRAY_BUFFER, this.elementBuffer)
// 使用程序对象
const use = () => {
if (this.program !== program) {
this.program = program
this.useProgram(program)
}
if (program.flip !== this.flip) {
program.flip = this.flip
this.uniform1f(u_Flip, program.flip)
}
if (program.alpha !== this.alpha) {
program.alpha = this.alpha
this.uniform1f(u_Alpha, program.alpha)
}
return program
}
// 保存程序对象
program.use = use
program.vao = vao
program.flip = null
program.alpha = 0
program.samplerNum = 1
program.a_Position = a_Position
program.a_TexCoord = a_TexCoord
program.a_TexParam = a_TexParam
program.a_Tint = a_Tint
program.a_LightCoord = a_LightCoord
program.u_Matrix = u_Matrix
program.u_LightTexSize = u_LightTexSize
program.u_Samplers = u_Samplers
return program
}
/** WebGL上下文方法 - 创建粒子程序 */
GL.createParticleProgram = function () {
const program = this.createProgramWithShaders(
`
attribute vec2 a_Position;
attribute vec2 a_TexCoord;
attribute vec4 a_Color;
uniform float u_Flip;
uniform mat3 u_Matrix;
uniform vec3 u_Ambient;
uniform int u_LightMode;
uniform vec4 u_LightTexSize;
uniform sampler2D u_LightSampler;
varying vec2 v_TexCoord;
varying vec4 v_Color;
varying vec3 v_LightColor;
vec3 getLightColor() {
if (u_LightMode == 0) {
// 光线采样:原始图像
return vec3(1.0, 1.0, 1.0);
}
if (u_LightMode == 1) {
// 光线采样:全局采样
return vec3(
gl_Position.x / u_LightTexSize.x + u_LightTexSize.z,
gl_Position.y / u_LightTexSize.y * u_Flip + u_LightTexSize.w,
-1.0
);
}
if (u_LightMode == 2) {
// 光线采样:环境光
return u_Ambient;
}
}
void main() {
gl_Position.xyw = u_Matrix * vec3(a_Position, 1.0);
v_TexCoord = a_TexCoord;
v_Color = a_Color;
v_LightColor = getLightColor();
}
`,
`
precision highp float;
varying vec2 v_TexCoord;
varying vec4 v_Color;
varying vec3 v_LightColor;
uniform float u_Alpha;
uniform int u_Mode;
uniform vec4 u_Tint;
uniform sampler2D u_Sampler;
uniform sampler2D u_LightSampler;
vec3 getLightColor() {
if (v_LightColor.z != -1.0) return v_LightColor;
return texture2D(u_LightSampler, v_LightColor.xy).rgb;
}
void main() {
if (u_Mode == 0) {
// 颜色模式:指定颜色
float alpha = texture2D(u_Sampler, v_TexCoord).a;
gl_FragColor.a = alpha * v_Color.a * u_Alpha;
if (gl_FragColor.a == 0.0) discard;
gl_FragColor.rgb = v_Color.rgb;
} else if (u_Mode == 1) {
// 颜色模式:纹理采样 + 色调
gl_FragColor = texture2D(u_Sampler, v_TexCoord);
gl_FragColor.a *= v_Color.a * u_Alpha;
if (gl_FragColor.a == 0.0) discard;
gl_FragColor.rgb = gl_FragColor.rgb * (1.0 - u_Tint.a) + u_Tint.rgb +
dot(gl_FragColor.rgb, vec3(0.299, 0.587, 0.114)) * u_Tint.a;
}
gl_FragColor.rgb *= getLightColor();
}
`,
)
this.useProgram(program)
// 顶点着色器属性
const a_Position = this.getAttribLocation(program, 'a_Position')
const a_TexCoord = this.getAttribLocation(program, 'a_TexCoord')
const u_Flip = this.getUniformLocation(program, 'u_Flip')
const a_Color = this.getAttribLocation(program, 'a_Color')
const u_Matrix = this.getUniformLocation(program, 'u_Matrix')
const u_Ambient = this.getUniformLocation(program, 'u_Ambient')
const u_LightMode = this.getUniformLocation(program, 'u_LightMode')
const u_LightTexSize = this.getUniformLocation(program, 'u_LightTexSize')
this.uniform1i(this.getUniformLocation(program, 'u_LightSampler'), this.maxTexUnits - 1)
// 片元着色器属性
const u_Alpha = this.getUniformLocation(program, 'u_Alpha')
const u_Mode = this.getUniformLocation(program, 'u_Mode')
const u_Tint = this.getUniformLocation(program, 'u_Tint')
// 创建顶点数组对象
const vao = this.createVertexArray()
this.bindVertexArray(vao)
this.enableVertexAttribArray(a_Position)
this.enableVertexAttribArray(a_TexCoord)
this.enableVertexAttribArray(a_Color)
this.bindBuffer(this.ARRAY_BUFFER, this.vertexBuffer)
this.vertexAttribPointer(a_Position, 2, this.FLOAT, false, 20, 0)
this.vertexAttribPointer(a_TexCoord, 2, this.FLOAT, false, 20, 8)
this.vertexAttribPointer(a_Color, 4, this.UNSIGNED_BYTE, true, 20, 16)
this.bindBuffer(this.ELEMENT_ARRAY_BUFFER, this.elementBuffer)
// 使用程序对象
const use = () => {
if (this.program !== program) {
this.program = program
this.useProgram(program)
}
if (program.flip !== this.flip) {
program.flip = this.flip
this.uniform1f(u_Flip, program.flip)
}
if (program.alpha !== this.alpha) {
program.alpha = this.alpha
this.uniform1f(u_Alpha, program.alpha)
}
this.updateBlending()
return program
}
// 保存程序对象
program.use = use
program.vao = vao
program.alpha = 0
program.a_Position = a_Position
program.a_TexCoord = a_TexCoord
program.a_Color = a_Color
program.u_Matrix = u_Matrix
program.u_Ambient = u_Ambient
program.u_LightMode = u_LightMode
program.u_LightTexSize = u_LightTexSize
program.u_Mode = u_Mode
program.u_Tint = u_Tint
return program
}
/** WebGL上下文方法 - 创建光源程序 */
GL.createLightProgram = function () {
const program = this.createProgramWithShaders(
`
attribute vec2 a_Position;
attribute vec2 a_LightCoord;
attribute vec4 a_LightColor;
uniform mat3 u_Matrix;
varying vec2 v_LightCoord;
varying vec4 v_LightColor;
void main() {
gl_Position.xyw = u_Matrix * vec3(a_Position, 1.0);
v_LightCoord = a_LightCoord;
v_LightColor = a_LightColor;
}
`,
`
precision highp float;
const float PI = 3.1415926536;
varying vec2 v_LightCoord;
varying vec4 v_LightColor;
uniform int u_LightMode;
uniform sampler2D u_LightSampler;
// 获取光照颜色
vec3 getLightColor() {
if (u_LightMode == 0) {
// 光照模式:点光源
float dist = length(vec2(
(v_LightCoord.x - 0.5),
(v_LightCoord.y - 0.5)
));
// 放弃圆形外面像素
if (dist > 0.5) discard;
// 根据距离和强度来计算光照颜色系数
float angle = dist * PI;
float factor = mix(1.0 - sin(angle), cos(angle), v_LightColor.a);
return v_LightColor.rgb * factor;
}
if (u_LightMode == 1) {
// 光照模式:区域光源
vec4 lightColor = texture2D(u_LightSampler, v_LightCoord);
if (lightColor.a == 0.0) discard;
// 从纹理中采样颜色,与光照颜色相乘
return v_LightColor.rgb * lightColor.rgb * lightColor.a;
}
if (u_LightMode == 2) {
// 光照模式:区域光源(无纹理)
return v_LightColor.rgb;
}
}
void main() {
gl_FragColor = vec4(getLightColor(), 1.0);
}
`,
)
this.useProgram(program)
// 顶点着色器属性
const a_Position = this.getAttribLocation(program, 'a_Position')
const a_LightCoord = this.getAttribLocation(program, 'a_LightCoord')
const a_LightColor = this.getAttribLocation(program, 'a_LightColor')
const u_Matrix = this.getUniformLocation(program, 'u_Matrix')
// 片元着色器属性
const u_LightMode = this.getUniformLocation(program, 'u_LightMode')
// 创建顶点数组对象
const vao = this.createVertexArray()
this.bindVertexArray(vao)
this.enableVertexAttribArray(a_Position)
this.enableVertexAttribArray(a_LightCoord)
this.enableVertexAttribArray(a_LightColor)
this.bindBuffer(this.ARRAY_BUFFER, this.vertexBuffer)
this.vertexAttribPointer(a_Position, 2, this.FLOAT, false, 32, 0)
this.vertexAttribPointer(a_LightCoord, 2, this.FLOAT, false, 32, 8)
this.vertexAttribPointer(a_LightColor, 4, this.FLOAT, false, 32, 16)
this.bindBuffer(this.ELEMENT_ARRAY_BUFFER, this.elementBuffer)
// 创建顶点数组对象 - 属性[110]
vao.a110 = this.createVertexArray()
this.bindVertexArray(vao.a110)
this.enableVertexAttribArray(a_Position)
this.enableVertexAttribArray(a_LightCoord)
this.bindBuffer(this.ARRAY_BUFFER, this.vertexBuffer)
this.vertexAttribPointer(a_Position, 2, this.FLOAT, false, 16, 0)
this.vertexAttribPointer(a_LightCoord, 2, this.FLOAT, false, 16, 8)
this.bindBuffer(this.ELEMENT_ARRAY_BUFFER, this.elementBuffer)
// 使用程序对象
const use = () => {
if (this.program !== program) {
this.program = program
this.useProgram(program)
}
this.updateBlending()
return program
}
// 保存程序对象
program.use = use
program.vao = vao
program.a_Position = a_Position
program.a_LightCoord = a_LightCoord
program.a_LightColor = a_LightColor
program.u_Matrix = u_Matrix
program.u_LightMode = u_LightMode
return program
}
/** WebGL上下文方法 - 创建图形程序 */
GL.createGraphicProgram = function () {
const program = this.createProgramWithShaders(
`
attribute vec2 a_Position;
attribute vec4 a_Color;
uniform mat3 u_Matrix;
varying vec4 v_Color;
void main() {
gl_Position.xyw = u_Matrix * vec3(a_Position, 1.0);
v_Color = a_Color;
}
`,
`
precision highp float;
varying vec4 v_Color;
uniform float u_Alpha;
void main() {
gl_FragColor.rgb = v_Color.rgb;
gl_FragColor.a = v_Color.a * u_Alpha;
}
`,
)
this.useProgram(program)
// 顶点着色器属性
const a_Position = this.getAttribLocation(program, 'a_Position')
const a_Color = this.getAttribLocation(program, 'a_Color')
const u_Matrix = this.getUniformLocation(program, 'u_Matrix')
// 片元着色器属性
const u_Alpha = this.getUniformLocation(program, 'u_Alpha')
// 创建顶点数组对象
const vao = this.createVertexArray()
this.bindVertexArray(vao)
this.enableVertexAttribArray(a_Position)
this.enableVertexAttribArray(a_Color)
this.bindBuffer(this.ARRAY_BUFFER, this.vertexBuffer)
this.vertexAttribPointer(a_Position, 2, this.FLOAT, false, 12, 0)
this.vertexAttribPointer(a_Color, 4, this.UNSIGNED_BYTE, true, 12, 8)
this.bindBuffer(this.ELEMENT_ARRAY_BUFFER, this.elementBuffer)
// 创建顶点数组对象 - 属性[10]
vao.a10 = this.createVertexArray()
this.bindVertexArray(vao.a10)
this.enableVertexAttribArray(a_Position)
this.bindBuffer(this.ARRAY_BUFFER, this.vertexBuffer)
this.vertexAttribPointer(a_Position, 2, this.FLOAT, false, 0, 0)
this.bindBuffer(this.ELEMENT_ARRAY_BUFFER, this.elementBuffer)
// 使用程序对象
const use = () => {
if (this.program !== program) {
this.program = program
this.useProgram(program)
}
if (program.alpha !== this.alpha) {
program.alpha = this.alpha
this.uniform1f(u_Alpha, program.alpha)
}
this.updateBlending()
return program
}
// 保存程序对象
program.use = use
program.vao = vao
program.alpha = 0
program.a_Position = a_Position
program.a_Color = a_Color
program.u_Matrix = u_Matrix
return program
}
/** WebGL上下文方法 - 重置状态 */
GL.reset = function () {
this.blend = 'normal'
this.alpha = 1
this.matrix.reset()
}
// WebGL上下文方法 - 更新遮罩模式
GL.updateMasking = function () {
if (this.program.masking !== this.masking) {
this.program.masking = this.masking
if (this.masking) {
this.uniform1i(this.program.u_Masking, 1)
this.uniform1i(this.program.u_MaskSampler, 1)
this.activeTexture(this.TEXTURE1)
this.bindTexture(this.TEXTURE_2D, this.maskTexture.base.glTexture)
this.activeTexture(this.TEXTURE0)
} else {
this.uniform1i(this.program.u_Masking, 0)
this.uniform1i(this.program.u_MaskSampler, 0)
this.activeTexture(this.TEXTURE1)
this.bindTexture(this.TEXTURE_2D, null)
this.activeTexture(this.TEXTURE0)
}
}
if (this.masking) {
this.uniform2f(this.program.u_Viewport, this.width, this.height)
}
}
/**
* WebGL上下文方法 - 创建混合模式更新器
* @returns {function} 更新混合模式
*/
GL.createBlendingUpdater = function () {
// 开启混合功能
this.enable(this.BLEND)
// 更新器映射表(启用混合时)
const A = {
// 正常模式
normal: () => {
this.blendEquation(this.FUNC_ADD)
this.blendFuncSeparate(this.SRC_ALPHA, this.ONE_MINUS_SRC_ALPHA, this.ONE, this.ZERO)
},
// 滤色模式
screen: () => {
this.blendEquation(this.FUNC_ADD)
this.blendFunc(this.ONE, this.ONE_MINUS_SRC_COLOR)
},
// 加法模式
additive: () => {
this.blendEquation(this.FUNC_ADD)
this.blendFuncSeparate(this.SRC_ALPHA, this.DST_ALPHA, this.ONE, this.ZERO)
},
// 减法模式
subtract: () => {
this.blendEquation(this.FUNC_REVERSE_SUBTRACT)
this.blendFuncSeparate(this.SRC_ALPHA, this.DST_ALPHA, this.ONE, this.ZERO)
},
// 最大值模式
max: () => {
this.blendEquation(this.MAX)
},
// 复制模式
copy: () => {
// 关闭混合功能,切换到B组更新器
this.disable(this.BLEND)
updaters = B
},
}
// 从复制模式切换到其他模式
const resume = () => {
// 开启混合功能,切换到A组更新器
(updaters = A)[blend]()
this.enable(this.BLEND)
}
// 更新器映射表(禁用混合时)
const B = {
normal: resume,
screen: resume,
additive: resume,
subtract: resume,
max: resume,
}
let updaters = A
let blend = ''
// 返回更新混合模式方法
return () => {
if (blend !== this.blend) {
updaters[blend = this.blend]()
}
}
}
/**
* WebGL上下文方法 - 设置环境光: 红[0,255] 绿[0,255] 蓝[0,255]
* @param {{red: number, green: number, blue: number}} ambient 环境光
*/
GL.setAmbientLight = function ({red, green, blue}) {
const ambient = this.ambient
if (ambient.red !== red ||
ambient.green !== green ||
ambient.blue !== blue) {
ambient.red = red
ambient.green = green
ambient.blue = blue
const program = this.program
const r = ambient.red / 255
const g = ambient.green / 255
const b = ambient.blue / 255
// 更新以下GL程序的环境光变量
for (const program of [
this.imageProgram,
this.tileProgram,
this.particleProgram,
]) {
this.useProgram(program)
this.uniform3f(program.u_Ambient, r, g, b)
}
this.useProgram(program)
}
}
/** WebGL上下文方法 - 调整光影纹理 */
GL.resizeLightMap = function () {
const texture = this.reflectedLightMap
const width = this.width
const height = this.height
if (texture.innerWidth !== width ||
texture.innerHeight !== height) {
texture.innerWidth = width
texture.innerHeight = height
if (texture.paddingLeft === undefined) {
const {lightArea} = Data.config
// 首次调用时计算光影纹理最大扩张值(4倍)
texture.paddingLeft = Math.min(lightArea.expansionLeft * 4, 1024)
texture.paddingTop = Math.min(lightArea.expansionTop * 4, 1024)
texture.paddingRight = Math.min(lightArea.expansionRight * 4, 1024)
texture.paddingBottom = Math.min(lightArea.expansionBottom * 4, 1024)
}
const pl = texture.paddingLeft
const pt = texture.paddingTop
const pr = texture.paddingRight
const pb = texture.paddingBottom
const tWidth = width + pl + pr
const tHeight = height + pt + pb
// 重置缩放率(将会重新计算纹理参数)
texture.scale = 0
texture.resize(tWidth, tHeight)
this.bindTexture(this.TEXTURE_2D, null)
this.updateLightTexSize()
}
}
/** WebGL上下文方法 - 更新光照纹理大小 */
GL.updateLightTexSize = function () {
const texture = this.reflectedLightMap
if (texture.width === 0) return
const width = this.drawingBufferWidth
const height = this.drawingBufferHeight
const sizeX = texture.width / width * 2
const sizeY = texture.height / height * 2
const centerX = (texture.paddingLeft + width / 2) / texture.width
const centerY = (texture.paddingTop + height / 2) / texture.height
const program = this.program
// 更新以下GL程序的光照纹理参数
for (const program of [
this.imageProgram,
this.tileProgram,
this.spriteProgram,
this.particleProgram,
]) {
this.useProgram(program)
this.uniform4f(program.u_LightTexSize, sizeX, sizeY, centerX, centerY)
}
this.useProgram(program)
}
/**
* WebGL上下文方法 - 更新纹理采样器数量
* @param {number} samplerNum 采样器数量
*/
GL.updateSamplerNum = function (samplerNum) {
// 在旧版的Chrome中,不重置过期的采样器索引会被警告,因此这个算法被保留了下来
const program = this.program
const lastNum = program.samplerNum
// 如果采样器数量发生了变化
if (lastNum !== samplerNum) {
const u_Samplers = program.u_Samplers
if (lastNum < samplerNum) {
// 如果采样器数量增多,设置新增的采样器索引
for (let i = lastNum; i < samplerNum; i++) {
this.uniform1i(u_Samplers[i], i)
}
} else {
// 如果采样器数量减少,重置过期的采样器索引
for (let i = samplerNum; i < lastNum; i++) {
this.uniform1i(u_Samplers[i], 0)
}
}
program.samplerNum = samplerNum
}
}
/**
* WebGL上下文方法 - 绑定帧缓冲对象
* @param {WebGLFramebuffer} fbo 帧缓冲对象
*/
GL.bindFBO = function (fbo) {
this.binding = fbo
this.flip = 1
this.bindFramebuffer(this.FRAMEBUFFER, fbo)
}
/** WebGL上下文方法 - 解除帧缓冲对象的绑定 */
GL.unbindFBO = function () {
this.binding = null
this.flip = -1
this.bindFramebuffer(this.FRAMEBUFFER, null)
}
/**
* 设置视口大小(单位:像素)
* @param {number} x 视口X
* @param {number} y 视口Y
* @param {number} width 视口宽度
* @param {number} height 视口高度
*/
GL.setViewport = function (x, y, width, height) {
this.width = width
this.height = height
this.viewport(x, y, width, height)
}
/** 重置视口大小 */
GL.resetViewport = function () {
const width = this.drawingBufferWidth
const height = this.drawingBufferHeight
this.width = width
this.height = height
this.viewport(0, 0, width, height)
}
/** WebGL上下文方法 - 激活离屏渲染 */
GL.enableOffscreen = function enableOffscreen() {
const {unbindFBO, resetViewport} = GL
// 离屏模式 - 解除帧缓冲对象的绑定
function offscreenUnbindFBO() {
this.binding = null
this.flip = 1
// 重新绑定到当前离屏纹理的FBO
this.bindFramebuffer(this.FRAMEBUFFER, this.offscreen.current.fbo)
}
// 离屏模式 - 重置窗口大小
function offscreenResetViewport() {
const base = this.offscreen.current.base
const width = base.width
const height = base.height
this.width = width
this.height = height
// 将视口设为当前离屏纹理的大小
this.viewport(0, 0, width, height)
}
return function (enabled) {
const offscreen = this.offscreen
if (offscreen.enabled !== enabled) {
offscreen.enabled = enabled
if (enabled) {
// 如果启用,调整当前离屏纹理大小
const texture = offscreen.current
const width = this.drawingBufferWidth
const height = this.drawingBufferHeight
if (texture.base.width !== width ||
texture.base.height !== height) {
texture.resize(width, height)
}
// 替换成离屏渲染模式下的特定方法
this.unbindFBO = offscreenUnbindFBO
this.resetViewport = offscreenResetViewport
} else {
// 如果禁用,恢复默认的方法
this.unbindFBO = unbindFBO
this.resetViewport = resetViewport
}
this.unbindFBO()
}
}
}()
/** WebGL上下文方法 - 切换离屏纹理 */
GL.switchOffscreen = function () {
const offscreen = this.offscreen
// 如果启用了离屏渲染
if (offscreen.enabled) {
const texture = offscreen.last
const width = this.drawingBufferWidth
const height = this.drawingBufferHeight
// 获取上次离屏纹理,并调整大小
if (texture.base.width !== width ||
texture.base.height !== height) {
texture.resize(width, height)
}
// 交换上次和当前的离屏纹理
offscreen.last = offscreen.current
offscreen.current = texture
// 绑定当前离屏纹理的FBO(offscreenUnbindFBO)
this.unbindFBO()
}
}
/**
* WebGL上下文方法 - 调整画布大小
* @param {number} width 画布宽度
* @param {number} height 画布高度
*/
GL.resize = function (width, height) {
const canvas = this.canvas
// 尽量少的画布缓冲区重置次数
if (canvas.width !== width) {
canvas.width = width
}
if (canvas.height !== height) {
canvas.height = height
}
if (this.width !== width ||
this.height !== height) {
// 更新画布大小参数和视口
this.width = width
this.height = height
this.viewport(0, 0, width, height)
this.maskTexture.resize(width, height)
this.directLightMap.resize(width, height)
}
// 调整光影纹理
this.resizeLightMap()
}
namespace: {
const defTint = new Uint8Array(4)
/**
* WebGL上下文方法 - 绘制图像
* @param {Texture} texture 图像纹理
* @param {number} dx 绘制位置X
* @param {number} dy 绘制位置Y
* @param {number} dw 绘制宽度
* @param {number} dh 绘制高度
* @param {Array<number>} tint 图像色调
*/
GL.drawImage = function (texture, dx, dy, dw, dh, tint = defTint) {
if (!texture.complete) return
const program = this.imageProgram.use()
const vertices = this.arrays[0].float32
const base = texture.base
const sx = texture.x
const sy = texture.y
const sw = texture.width
const sh = texture.height
const tw = base.width
const th = base.height
// 计算变换矩阵
const matrix = Matrix.instance.project(
this.flip,
this.width,
this.height,
).multiply(this.matrix)
// 计算顶点数据
const dl = dx + 0.004
const dt = dy + 0.004
const dr = dl + dw
const db = dt + dh
const sl = sx / tw
const st = sy / th
const sr = (sx + sw) / tw
const sb = (sy + sh) / th
vertices[0] = dl
vertices[1] = dt
vertices[2] = sl
vertices[3] = st
vertices[4] = dl
vertices[5] = db
vertices[6] = sl
vertices[7] = sb
vertices[8] = dr
vertices[9] = db
vertices[10] = sr
vertices[11] = sb
vertices[12] = dr
vertices[13] = dt
vertices[14] = sr
vertices[15] = st
// 色调归一化
const red = tint[0] / 255
const green = tint[1] / 255
const blue = tint[2] / 255
const gray = tint[3] / 255
// 绘制图像
this.bindVertexArray(program.vao.a110)
this.vertexAttrib1f(program.a_Opacity, 1)
this.uniformMatrix3fv(program.u_Matrix, false, matrix)
this.uniform1i(program.u_LightMode, 0)
this.uniform1i(program.u_ColorMode, 0)
this.uniform4f(program.u_Tint, red, green, blue, gray)
this.bufferData(this.ARRAY_BUFFER, vertices, this.STREAM_DRAW, 0, 16)
this.bindTexture(this.TEXTURE_2D, base.glTexture)
this.drawArrays(this.TRIANGLE_FAN, 0, 4)
}
}
/**
* WebGL上下文方法 - 绘制指定颜色的图像
* @param {Texture} texture 图像纹理
* @param {number} dx 绘制位置X
* @param {number} dy 绘制位置Y
* @param {number} dw 绘制宽度
* @param {number} dh 绘制高度
* @param {number} color 整数颜色
*/
GL.drawImageWithColor = function (texture, dx, dy, dw, dh, color) {
if (!texture.complete) return
const program = this.imageProgram.use()
const vertices = this.arrays[0].float32
const base = texture.base
const sx = texture.x
const sy = texture.y
const sw = texture.width
const sh = texture.height
const tw = base.width
const th = base.height
// 计算变换矩阵
const matrix = Matrix.instance.project(
this.flip,
this.width,
this.height,
).multiply(this.matrix)
// 计算顶点数据
const dl = dx + 0.004
const dt = dy + 0.004
const dr = dl + dw
const db = dt + dh
const sl = sx / tw
const st = sy / th
const sr = (sx + sw) / tw
const sb = (sy + sh) / th
vertices[0] = dl
vertices[1] = dt
vertices[2] = sl
vertices[3] = st
vertices[4] = dl
vertices[5] = db
vertices[6] = sl
vertices[7] = sb
vertices[8] = dr
vertices[9] = db
vertices[10] = sr
vertices[11] = sb
vertices[12] = dr
vertices[13] = dt
vertices[14] = sr
vertices[15] = st
// 色调归一化
const red = (color & 0xff) / 255
const green = (color >> 8 & 0xff) / 255
const blue = (color >> 16 & 0xff) / 255
const gray = (color >> 24 & 0xff) / 255
// 绘制图像
this.bindVertexArray(program.vao.a110)
this.vertexAttrib1f(program.a_Opacity, 1)
this.uniformMatrix3fv(program.u_Matrix, false, matrix)
this.uniform1i(program.u_LightMode, 0)
this.uniform1i(program.u_ColorMode, 1)
this.uniform4f(program.u_Color, red, green, blue, gray)
this.bufferData(this.ARRAY_BUFFER, vertices, this.STREAM_DRAW, 0, 16)
this.bindTexture(this.TEXTURE_2D, base.glTexture)
this.drawArrays(this.TRIANGLE_FAN, 0, 4)
}
/**
* WebGL上下文方法 - 绘制切片图像
* @param {Texture} texture 图像纹理
* @param {number} dx 绘制位置X
* @param {number} dy 绘制位置Y
* @param {number} dw 绘制宽度
* @param {number} dh 绘制高度
* @param {Array<number>} clip 图像裁剪区域
* @param {number} border 切片边框宽度
* @param {Array<number>} tint 图像色调
*/
GL.drawSliceImage = function (texture, dx, dy, dw, dh, clip, border, tint) {
if (!texture.complete) return
// 计算变换矩阵
const matrix = Matrix.instance.project(
this.flip,
this.width,
this.height,
).multiply(this.matrix)
.translate(dx + 0.004, dy + 0.004)
// 更新切片数据
const {sliceClip} = texture
if (texture.sliceWidth !== dw ||
texture.sliceHeight !== dh ||
sliceClip[0] !== clip[0] ||
sliceClip[1] !== clip[1] ||
sliceClip[2] !== clip[2] ||
sliceClip[3] !== clip[3] ||
texture.sliceBorder !== border) {
texture.updateSliceData(dw, dh, clip, border)
}
// 色调归一化
const red = tint[0] / 255
const green = tint[1] / 255
const blue = tint[2] / 255
const gray = tint[3] / 255
// 上传数据
const program = this.imageProgram.use()
const vertices = texture.sliceVertices
const thresholds = texture.sliceThresholds
const count = texture.sliceCount
this.bindVertexArray(program.vao.a110)
this.vertexAttrib1f(program.a_Opacity, 1)
this.uniformMatrix3fv(program.u_Matrix, false, matrix)
this.uniform1i(program.u_LightMode, 0)
this.uniform1i(program.u_ColorMode, 2)
this.uniform4f(program.u_Tint, red, green, blue, gray)
this.bufferData(this.ARRAY_BUFFER, vertices, this.STREAM_DRAW, 0, count * 16)
this.bindTexture(this.TEXTURE_2D, texture.base.glTexture)
// 绘制切片
for (let i = 0; i < count; i++) {
const ti = i * 4
const x = thresholds[ti]
const y = thresholds[ti + 1]
const w = thresholds[ti + 2]
const h = thresholds[ti + 3]
this.uniform4f(program.u_Repeat, x, y, w, h)
this.drawArrays(this.TRIANGLE_FAN, i * 4, 4)
}
}
/**
* WebGL上下文方法 - 填充矩形
* @param {number} dx 绘制位置X
* @param {number} dy 绘制位置Y
* @param {number} dw 绘制宽度
* @param {number} dh 绘制高度
* @param {number} color 整数颜色
*/
GL.fillRect = function (dx, dy, dw, dh, color) {
const program = this.graphicProgram.use()
const vertices = this.arrays[0].float32
const colors = this.arrays[0].uint32
// 计算变换矩阵
const matrix = Matrix.instance.project(
this.flip,
this.width,
this.height,
).multiply(this.matrix)
// 计算顶点数据
const dl = dx
const dt = dy
const dr = dx + dw
const db = dy + dh
vertices[0] = dl
vertices[1] = dt
colors [2] = color
vertices[3] = dl
vertices[4] = db
colors [5] = color
vertices[6] = dr
vertices[7] = db
colors [8] = color
vertices[9] = dr
vertices[10] = dt
colors [11] = color
// 绘制图像
this.bindVertexArray(program.vao)
this.uniformMatrix3fv(program.u_Matrix, false, matrix)
this.bufferData(this.ARRAY_BUFFER, vertices, this.STREAM_DRAW, 0, 12)
this.drawArrays(this.TRIANGLE_FAN, 0, 4)
}
/**
* WebGL上下文方法 - 创建普通纹理
* @param {Object} options 选项
* @param {number} [magFilter] 纹理放大过滤器
* @param {number} [minFilter] 纹理缩小过滤器
* @returns {WebGLTexture}
*/
GL.createNormalTexture = function (options = {}) {
const magFilter = options.magFilter ?? this.NEAREST
const minFilter = options.minFilter ?? this.LINEAR
const texture = new BaseTexture()
texture.magFilter = magFilter
texture.minFilter = minFilter
texture.format = options.format ?? GL.RGBA
this.bindTexture(this.TEXTURE_2D, texture.glTexture)
// 设置纹理放大和缩小采样过滤器
this.texParameteri(this.TEXTURE_2D, this.TEXTURE_MAG_FILTER, magFilter)
this.texParameteri(this.TEXTURE_2D, this.TEXTURE_MIN_FILTER, minFilter)
// 设置纹理水平和垂直坐标包装器
this.texParameteri(this.TEXTURE_2D, this.TEXTURE_WRAP_S, this.CLAMP_TO_EDGE)
this.texParameteri(this.TEXTURE_2D, this.TEXTURE_WRAP_T, this.CLAMP_TO_EDGE)
this.textureManager.append(texture)
return texture
}
/**
* WebGL上下文方法 - 创建图像纹理
* @param {string|HTMLImageElement} image 图像文件ID或HTML图像元素
* @param {Object} options 选项
* @param {number} [magFilter] 纹理放大过滤器
* @param {number} [minFilter] 纹理缩小过滤器
* @param {boolean} [sync] 是否开启同步加载
* @returns {WebGLTexture}
*/
GL.createImageTexture = function (image, options = {}) {
const magFilter = options.magFilter ?? this.NEAREST
const minFilter = options.minFilter ?? this.LINEAR
const sync = options.sync ?? false
const guid = image instanceof Image ? image.guid : image
const manager = this.textureManager
let texture = manager.images[guid]
if (!texture) {
texture = new BaseTexture()
texture.guid = guid
texture.image = null
texture.refCount = 0
texture.magFilter = magFilter
texture.minFilter = minFilter
manager.append(texture)
manager.images[guid] = texture
const initialize = image => {
// 如果纹理还在管理器中,并且加载图像成功
if (manager.images[guid] === texture && image) {
texture.image = image
texture.width = image.naturalWidth
texture.height = image.naturalHeight
this.bindTexture(this.TEXTURE_2D, texture.glTexture)
// 设置纹理放大和缩小采样过滤器
this.texParameteri(this.TEXTURE_2D, this.TEXTURE_MAG_FILTER, magFilter)
this.texParameteri(this.TEXTURE_2D, this.TEXTURE_MIN_FILTER, minFilter)
// 设置纹理水平和垂直坐标包装器
this.texParameteri(this.TEXTURE_2D, this.TEXTURE_WRAP_S, this.CLAMP_TO_EDGE)
this.texParameteri(this.TEXTURE_2D, this.TEXTURE_WRAP_T, this.CLAMP_TO_EDGE)
// 上传RGBA格式的图像数据到纹理
this.texImage2D(this.TEXTURE_2D, 0, this.RGBA, this.RGBA, this.UNSIGNED_BYTE, image)
// 执行纹理已加载回调
texture.reply('load')
} else {
// 执行纹理加载错误回调
texture.reply('error')
}
}
image instanceof Image
? initialize(image)
: File.get({
guid: guid,
sync: sync,
type: 'image',
}).then(initialize)
}
texture.refCount++
return texture
}
/**
* WebGL上下文方法 - 创建纹理帧缓冲对象
* @param {Texture} texture 纹理
* @returns {WebGLFramebuffer}
*/
GL.createTextureFBO = function (texture) {
const fbo = this.createFramebuffer()
this.bindFramebuffer(this.FRAMEBUFFER, fbo)
// 绑定纹理到颜色缓冲区
this.framebufferTexture2D(this.FRAMEBUFFER, this.COLOR_ATTACHMENT0, this.TEXTURE_2D, texture.base.glTexture, 0)
// 创建深度模板缓冲区
const depthStencilBuffer = this.createRenderbuffer()
this.bindRenderbuffer(this.RENDERBUFFER, depthStencilBuffer)
this.framebufferRenderbuffer(this.FRAMEBUFFER, this.DEPTH_STENCIL_ATTACHMENT, this.RENDERBUFFER, depthStencilBuffer)
this.renderbufferStorage(this.RENDERBUFFER, this.DEPTH_STENCIL, texture.base.width, texture.base.height)
this.bindRenderbuffer(this.RENDERBUFFER, null)
this.bindFramebuffer(this.FRAMEBUFFER, null)
texture.depthStencilBuffer = depthStencilBuffer
// 重写纹理方法 - 调整大小
texture.resize = (width, height) => {
Texture.prototype.resize.call(texture, width, height)
// 调整深度模板缓冲区大小
this.bindRenderbuffer(this.RENDERBUFFER, depthStencilBuffer)
this.renderbufferStorage(this.RENDERBUFFER, this.DEPTH_STENCIL, width, height)
this.bindRenderbuffer(this.RENDERBUFFER, null)
}
// console.log(this.checkFramebufferStatus(this.FRAMEBUFFER) === this.FRAMEBUFFER_COMPLETE)
// 还需要一个方法来恢复
return fbo
}
// 扩展方法 - 擦除画布
CanvasRenderingContext2D.prototype.clear = function () {
this.clearRect(0, 0, this.canvas.width, this.canvas.height)
}
// 扩展方法 - 调整画布大小
CanvasRenderingContext2D.prototype.resize = function (width, height) {
const canvas = this.canvas
if (canvas.width === width &&
canvas.height === height) {
// 宽高不变时重置画布
canvas.width = width
} else {
// 尽量少的画布缓冲区重置次数
if (canvas.width !== width) {
canvas.width = width
}
if (canvas.height !== height) {
canvas.height = height
}
}
}
// ******************************** 基础纹理类 ********************************
class BaseTexture {
constructor() {
this.glTexture = GL.createTexture()
this.width = 0
this.height = 0
this.format = GL.RGBA
}
// 恢复普通纹理
restoreNormalTexture() {
this.glTexture = GL.createTexture()
const {format, width, height} = this
GL.bindTexture(GL.TEXTURE_2D, this.glTexture)
GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, this.magFilter)
GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, this.minFilter)
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.texImage2D(GL.TEXTURE_2D, 0, format, width, height, 0, format, GL.UNSIGNED_BYTE, null)
}
// 恢复图像纹理
restoreImageTexture() {
this.glTexture = GL.createTexture()
GL.bindTexture(GL.TEXTURE_2D, this.glTexture)
GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, this.magFilter)
GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, this.minFilter)
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.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, GL.RGBA, GL.UNSIGNED_BYTE, this.image)
}
/**
* 基础纹理方法 - 设置加载回调
* @param {string} type 回调事件类型
* @param {function} callback 回调函数
*/
on(type, callback) {
// 如果已加载完成,立即执行回调
let cache = this[BaseTexture.CALLBACK]
if (cache === type) {
callback(this)
return
}
// 首次调用,创建加载回调缓存
if (cache === undefined) {
cache = this[BaseTexture.CALLBACK] =
{load: [], error: []}
}
// 如果未加载完成,添加回调到缓存中
if (typeof cache === 'object') {
cache[type].push(callback)
}
}
/**
* 基础纹理方法 - 执行加载回调
* @param {string} type 回调事件类型
*/
reply(type) {
const cache = this[BaseTexture.CALLBACK]
if (typeof cache === 'object') {
// 调用所有的纹理加载回调
for (const callback of cache[type]) {
callback(this)
}
}
// 将缓存替换为类型名称
this[BaseTexture.CALLBACK] = type
}
static CALLBACK = Symbol('LOAD_CALLBACK')
}
// ******************************** 纹理类 ********************************
class Texture {
complete //:boolean
base //:object
gl //:object
x //:number
y //:number
width //:number
height //:number
/**
* 选项: magFilter, minFilter
* @param {Object} [options] 纹理选项
*/
constructor(options = {}) {
if (new.target !== Texture) {
return
}
// 设置属性
this.complete = true
this.destroyed = false
this.base = GL.createNormalTexture(options)
this.gl = GL
this.x = 0
this.y = 0
this.width = 0
this.height = 0
}
/**
* 裁剪纹理
* @param {number} x 水平位置
* @param {number} y 垂直位置
* @param {number} width 裁剪宽度
* @param {number} height 裁剪高度
* @returns {Texture}
*/
clip(x, y, width, height) {
this.x = x
this.y = y
this.width = width
this.height = height
return this
}
/**
* 擦除纹理中的像素
* @param {number} red 默认红色
* @param {number} green 默认绿色
* @param {number} blue 默认蓝色
* @param {number} alpha 默认不透明度
*/
clear(red = 0, green = 0, blue = 0, alpha = 0) {
const gl = this.gl
gl.bindFBO(gl.frameBuffer)
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.base.glTexture, 0)
gl.clearColor(red, green, blue, alpha)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.unbindFBO()
}
/**
* 调整纹理大小
* @param {number} width 纹理宽度
* @param {number} height 纹理高度
* @returns {Texture}
*/
resize(width, height) {
const {gl, base} = this
const {format} = base
base.width = width
base.height = height
gl.bindTexture(gl.TEXTURE_2D, base.glTexture)
// 此处调整纹理大小后不立即写入数据会被Firefox警告,若只是为了消除警告而上传数据代价很大,不管它
gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, gl.UNSIGNED_BYTE, null)
return this.clip(0, 0, width, height)
}
/**
* 从图像中取样
* @param {HTMLImageElement|HTMLCanvasElement} image HTML图像或画布元素
* @returns {Texture}
*/
fromImage(image) {
const gl = this.gl
const base = this.base
const format = base.format
const width = image.width
const height = image.height
base.image = image
// 上传空图像会被Chromium警告
if (image.width === 0 && image.height === 0) {
return this.resize(0, 0)
}
base.width = width
base.height = height
gl.bindTexture(gl.TEXTURE_2D, base.glTexture)
gl.texImage2D(gl.TEXTURE_2D, 0, format, format, gl.UNSIGNED_BYTE, image)
return this.clip(0, 0, width, height)
}
/**
* 获取图像像素数据
* @param {number} x 水平位置
* @param {number} y 垂直位置
* @param {number} width 裁剪宽度
* @param {number} height 裁剪高度
* @returns {ImageData|null} 图像像素数据
*/
getImageData(x, y, width, height) {
const gl = this.gl
const base = this.base
if (base instanceof BaseTexture) {
const canvas = document.createElement('canvas')
canvas.width = canvas.height = 0
const context = canvas.getContext('2d')
const imageData = context.createImageData(width, height)
const {buffer, length} = imageData.data
const uint8 = new Uint8Array(buffer, 0, length)
gl.bindFramebuffer(gl.FRAMEBUFFER, gl.frameBuffer)
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, base.glTexture, 0)
gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, uint8)
gl.binding ? gl.bindFBO(gl.binding) : gl.unbindFBO()
return imageData
}
return null
}
/**
* 自适应裁剪图像并缩放到指定大小
* 再转换为BASE64编码
* @param {number} width 目标图像宽度
* @param {number} height 目标图像高度
* @returns {string} BASE64
*/
toBase64(width, height) {
const texture = new Texture()
texture.resize(width, height)
const tx = this.x
const ty = this.y
const tw = this.width
const th = this.height
const bw = this.base.width
const bh = this.base.height
let sx, sy, sw, sh
if (width / height >= bw / bh) {
sw = bw
sh = Math.round(bw * height / width)
sx = 0
sy = bh - sh >> 1
} else {
sw = Math.round(bh * width / height)
sh = bh
sx = bw - sw >> 1
sy = 0
}
const gl = this.gl
this.clip(sx, sy, sw, sh)
gl.reset()
gl.bindFBO(gl.frameBuffer)
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture.base.glTexture, 0)
gl.drawImage(this, 0, 0, width, height)
gl.unbindFBO()
this.clip(tx, ty, tw, th)
const imageData = texture.getImageData(0, 0, width, height)
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
const context = canvas.getContext('2d')
context.putImageData(imageData, 0, 0)
texture.destroy()
return canvas.toDataURL()
}
/** 销毁纹理 */
destroy() {
if (!this.destroyed) {
this.destroyed = true
this.complete = false
this.gl.textureManager.delete(this.base)
this.base = null
}
}
}
// ******************************** 图像纹理类 ********************************
class ImageTexture extends Texture {
/**
* 选项: magFilter, minFilter, sync
* @param {string|HTMLImageElement} image 图像文件ID或HTML图像元素
* @param {Object} [options] 纹理选项
*/
constructor(image, options = {}) {
super(options)
// 设置属性
const texture = GL.createImageTexture(image, options)
this.complete = false
this.destroyed = false
this.base = texture
this.gl = GL
this.x = 0
this.y = 0
this.width = 0
this.height = 0
// 设置基础纹理已加载回调
texture.on('load', () => {
if (this.base === texture) {
// 如果没有被销毁,执行包装纹理的回调
this.complete = true
this.width = this.width || texture.width
this.height = this.height || texture.height
this.reply('load')
}
})
// 设置基础纹理加载错误回调
texture.on('error', () => {
this.destroy()
this.reply('error')
})
}
/**
* 更新图像切片数据
* @param {number} width 绘制区域宽度
* @param {number} height 绘制区域高度
* @param {Array<number>} clip 图像裁剪区域
* @param {number} border 切片边框宽度
*/
updateSliceData(width, height, clip, border) {
if (!this.complete) return
const {min, max} = Math
const [cx, cy, cw, ch] = clip
const B = min(border, cw / 2, ch / 2)
const W = max(cw - B * 2, 0)
const H = max(ch - B * 2, 0)
const w = max(width - B * 2, 0)
const h = max(height - B * 2, 0)
let l, r, t, b
if (w > 0) {
l = B
r = B
} else {
l = min(B, width)
r = width - l
}
if (h > 0) {
t = B
b = B
} else {
t = min(B, height)
b = height - t
}
if (!this.sliceClip) {
// 首次调用时创建相关数组
this.sliceClip = new Uint32Array(4)
this.sliceVertices = new Float32Array(9 * 16)
this.sliceThresholds = new Float32Array(9 * 4)
// 绘制切片图像需要使用临近采样
const {gl} = this
this.base.magFilter = gl.NEAREST
this.base.minFilter = gl.NEAREST
gl.bindTexture(gl.TEXTURE_2D, this.base.glTexture)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
}
const bw = this.base.width
const bh = this.base.height
const vertices = this.sliceVertices
const thresholds = this.sliceThresholds
let vi = 0
let ti = 0
// 设置切片的顶点数据
const setVertices = (sx, sy, sw, sh, dx, dy, dw, dh) => {
// 如果是无效顶点数据,返回
if (sw * sh * dw * dh === 0) return
const dl = dx
const dt = dy
const dr = dx + dw
const db = dy + dh
const sl = (cx + sx) / bw
const st = (cy + sy) / bh
const sr = (cx + sx + dw) / bw
const sb = (cy + sy + dh) / bh
vertices[vi ] = dl
vertices[vi + 1] = dt
vertices[vi + 2] = sl
vertices[vi + 3] = st
vertices[vi + 4] = dl
vertices[vi + 5] = db
vertices[vi + 6] = sl
vertices[vi + 7] = sb
vertices[vi + 8] = dr
vertices[vi + 9] = db
vertices[vi + 10] = sr
vertices[vi + 11] = sb
vertices[vi + 12] = dr
vertices[vi + 13] = dt
vertices[vi + 14] = sr
vertices[vi + 15] = st
thresholds[ti ] = sl
thresholds[ti + 1] = st
thresholds[ti + 2] = sw / bw
thresholds[ti + 3] = sh / bh
vi += 16
ti += 4
}
// 创建顶点数据
const BW = B + W
const BH = B + H
const lw = l + w
const th = t + h
setVertices(B, B, W, H, l, t, w, h)
setVertices(0, 0, B, B, 0, 0, l, t)
setVertices(B, 0, W, B, l, 0, w, t)
setVertices(BW, 0, B, B, lw, 0, r, t)
setVertices(0, B, B, H, 0, t, l, h)
setVertices(BW, B, B, H, lw, t, r, h)
setVertices(0, BH, B, B, 0, th, l, b)
setVertices(B, BH, W, B, l, th, w, b)
setVertices(BW, BH, B, B, lw, th, r, b)
this.sliceClip.set(clip)
this.sliceWidth = width
this.sliceHeight = height
this.sliceBorder = border
this.sliceCount = vi / 16
}
/** 销毁图像纹理 */
destroy() {
if (!this.destroyed) {
this.destroyed = true
this.complete = false
this.base.refCount--
this.base = null
}
}
}
// 设置加载回调
ImageTexture.prototype.on = BaseTexture.prototype.on
// 执行加载回调
ImageTexture.prototype.reply = BaseTexture.prototype.reply
// ******************************** 纹理管理器类 ********************************
class TextureManager {
gl //:object
map //:object
images //:object
pointer //:number
count //:number
/** 纹理管理器 */
constructor() {
this.gl = GL
this.map = {}
this.images = {}
this.pointer = 0
this.count = 0
}
/** 更新纹理 */
update() {
const {gl, map, images} = this
for (const texture of Object.values(images)) {
// 删除不再引用的图像纹理
if (texture.refCount === 0) {
gl.deleteTexture(texture.glTexture)
delete images[texture.guid]
delete map[texture.index]
this.count--
if (this.pointer > texture.index) {
this.pointer = texture.index
}
}
}
}
/**
* 添加纹理到管理器中
* @param {Texture} texture 纹理对象
*/
append(texture) {
if (texture.index === undefined) {
// 给纹理分配一个未使用的索引
let i = this.pointer
const map = this.map
while (map[i] !== undefined) {i++}
map[i] = texture
texture.index = i
this.pointer = i + 1
this.count++
}
}
/**
* 从管理器中删除纹理
* @param {Texture} texture 纹理对象
*/
delete(texture) {
const i = texture.index
const {gl, map} = this
gl.deleteTexture(texture.glTexture)
// 通过索引删除映射表中的纹理
if (map[i] === texture) {
delete map[i]
this.count--
if (this.pointer > i) {
this.pointer = i
}
}
}
// 恢复纹理
restore() {
for (const texture of Object.values(this.map)) {
if (texture.onRestore) {
texture.onRestore(texture)
continue
}
if (texture.image !== undefined) {
texture.restoreImageTexture()
} else {
texture.restoreNormalTexture()
}
}
}
}
// ******************************** 批量渲染器 ********************************
class BatchRenderer {
response //:array
setAttrSize //:function
getEndIndex //:function
setBlendMode //:function
bindProgram //:function
unbindProgram //:function
push //:function
draw //:function
/**
* @param {WebGLRenderingContext} gl GL上下文对象
*/
constructor(gl) {
// 初始化上下文
const vertices = gl.arrays[0].float32
const texMap = gl.textureManager.map
const texUnits = gl.maxTexUnits - 1
const queue = new Uint32Array(512 * 512)
const step = texUnits + 3
const samplers = new Int8Array(10000).fill(-1)
const response = new Uint32Array(2)
let attrSize = 0
let queueIndex = 0
let samplerLength = 0
let startIndex = 0
let endIndex = 0
let blendMode = 'normal'
let program = null
// 设置属性大小
const setAttrSize = size => {
attrSize = size
}
// 获取结束索引
const getEndIndex = () => {
return endIndex
}
// 设置混合模式
const setBlendMode = blend => {
// 改变混合模式前,绘制队列中的内容
if (blendMode !== blend) {
draw()
blendMode = blend
}
}
// 绑定GL程序(中途切换程序可恢复)
const bindProgram = () => {
program = gl.program
}
// 解除绑定GL程序
const unbindProgram = () => {
program = null
}
// 推送绘制数据
const push = texIndex => {
// 以纹理索引为键获取采样器索引
let samplerIndex = samplers[texIndex]
// 如果不存在采样器索引,添加一个
if (samplerIndex === -1) {
samplerIndex = samplerLength
// 如果采样器索引已用完
if (samplerIndex === texUnits) {
// 重置采样器索引映射表
for (let i = 0; i < samplerLength; i++) {
samplers[queue[queueIndex + i]] = -1
}
// 获取当前队列尾部索引
const offset = queueIndex + texUnits
// 在队列尾部记录采样器数量、起始索引和结束索引
queue[offset ] = samplerLength
queue[offset + 1] = startIndex
queue[offset + 2] = endIndex
// 调整起始索引和队列索引
startIndex = endIndex
queueIndex += step
// 重置采样器数量和索引
samplerLength = 0
samplerIndex = 0
}
// 设置队列中当前采样器偏移位置为纹理索引
queue[queueIndex + samplerIndex] = texIndex
// 设置采样器映射表:纹理索引 -> 采样器索引
samplers[texIndex] = samplerIndex
// 递增采样器数量
samplerLength += 1
}
// 写入结束索引和采样器索引到返回数组中
response[0] = endIndex
response[1] = samplerIndex
// 结束索引增加4表示跳跃了4个顶点
endIndex += 4
}
// 绘制图像
const draw = () => {
// 如果存在顶点数据
if (endIndex !== 0) {
if (samplerLength !== 0) {
// 重置采样器索引映射表
for (let i = 0; i < samplerLength; i++) {
samplers[queue[queueIndex + i]] = -1
}
// 获取当前队列尾部索引
const offset = queueIndex + texUnits
// 在队列尾部记录采样器数量、起始索引和结束索引
queue[offset ] = samplerLength
queue[offset + 1] = startIndex
queue[offset + 2] = endIndex
// 调整队列索引
queueIndex += step
// 重置采样器数量
samplerLength = 0
}
// 如果绑定了GL程序,恢复GL程序和VAO
if (program !== null && program !== gl.program) {
program.use()
gl.bindVertexArray(program.vao)
}
const vLength = endIndex * attrSize
if (vLength > 0) {
// 上传数据到数组缓冲区(当属性大小为0时,需要手动上传数据)
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STREAM_DRAW, 0, vLength)
}
gl.blend = blendMode
gl.updateBlending()
// 遍历队列的区间
// 每个区间长度是step,前面texUnits个数据是纹理索引
// 后面3个数据分别是采样器数量、起始索引和结束索引
for (let qi = 0; qi < queueIndex; qi += step) {
const offset = qi + step
const length = queue[offset - 3]
const start = queue[offset - 2] * 1.5
const end = queue[offset - 1] * 1.5
for (let si = length - 1; si >= 0; si--) {
// 绑定采样器到对应的GL纹理(通过纹理索引获取)
gl.activeTexture(gl.TEXTURE0 + si)
gl.bindTexture(gl.TEXTURE_2D, texMap[queue[qi + si]].glTexture)
}
// 更新采样器数量并绘制图像
gl.updateSamplerNum(length)
gl.drawElements(gl.TRIANGLES, end - start, gl.UNSIGNED_INT, start * 4)
}
// 重置队列参数
queueIndex = 0
startIndex = 0
endIndex = 0
}
}
// 设置属性
this.response = response
this.setAttrSize = setAttrSize
this.getEndIndex = getEndIndex
this.setBlendMode = setBlendMode
this.bindProgram = bindProgram
this.unbindProgram = unbindProgram
this.push = push
this.draw = draw
}
}
// ******************************** 平面矩阵类 ********************************
class Matrix extends Float32Array {
/** 平面矩阵 */
constructor() {
super(9)
this[0] = 1
this[4] = 1
this[8] = 1
}
/**
* 重置矩阵
* @returns {Matrix}
*/
reset() {
this[0] = 1
this[1] = 0
this[3] = 0
this[4] = 1
this[6] = 0
this[7] = 0
return this
}
/**
* 设置矩阵为目标矩阵的参数
* @param {Matrix} matrix
* @returns {Matrix}
*/
set(matrix) {
this[0] = matrix[0]
this[1] = matrix[1]
this[3] = matrix[3]
this[4] = matrix[4]
this[6] = matrix[6]
this[7] = matrix[7]
return this
}
/**
* 设置矩阵参数
* @param {number} a
* @param {number} b
* @param {number} c
* @param {number} d
* @param {number} e
* @param {number} f
* @returns {Matrix}
*/
set6f(a, b, c, d, e, f) {
this[0] = a
this[1] = b
this[3] = c
this[4] = d
this[6] = e
this[7] = f
return this
}
/**
* 乘以目标矩阵
* @param {Matrix} matrix
* @returns {Matrix}
*/
multiply(matrix) {
const A = this[0]
const B = this[1]
const C = this[3]
const D = this[4]
const E = this[6]
const F = this[7]
const a = matrix[0]
const b = matrix[1]
const c = matrix[3]
const d = matrix[4]
const e = matrix[6]
const f = matrix[7]
this[0] = A * a + C * b
this[1] = B * a + D * b
this[3] = A * c + C * d
this[4] = B * c + D * d
this[6] = A * e + C * f + E
this[7] = B * e + D * f + F
return this
}
/**
* 旋转
* @param {number} angle 旋转角度(弧度)
* @returns {Matrix}
*/
rotate(angle) {
const cos = Math.cos(angle)
const sin = Math.sin(angle)
const a = this[0]
const b = this[1]
const c = this[3]
const d = this[4]
this[0] = a * cos + c * sin
this[1] = b * cos + d * sin
this[3] = c * cos - a * sin
this[4] = d * cos - b * sin
return this
}
/**
* 在指定点旋转
* @param {number} x 旋转位置X
* @param {number} y 旋转位置Y
* @param {number} angle 旋转角度(弧度)
* @returns {Matrix}
*/
rotateAt(x, y, angle) {
const cos = Math.cos(angle)
const sin = Math.sin(angle)
const a = this[0]
const b = this[1]
const c = this[3]
const d = this[4]
this[0] = a * cos + c * sin
this[1] = b * cos + d * sin
this[3] = c * cos - a * sin
this[4] = d * cos - b * sin
this[6] += (a - this[0]) * x + (c - this[3]) * y
this[7] += (b - this[1]) * x + (d - this[4]) * y
return this
}
/**
* 缩放
* @param {number} h 水平缩放系数
* @param {number} v 垂直缩放系数
* @returns {Matrix}
*/
scale(h, v) {
this[0] *= h
this[1] *= h
this[3] *= v
this[4] *= v
return this
}
/**
* 在指定点缩放
* @param {number} x 缩放位置X
* @param {number} y 缩放位置Y
* @param {number} h 水平缩放系数
* @param {number} v 垂直缩放系数
* @returns {Matrix}
*/
scaleAt(x, y, h, v) {
const a = this[0]
const b = this[1]
const c = this[3]
const d = this[4]
this[0] *= h
this[1] *= h
this[3] *= v
this[4] *= v
this[6] += (a - this[0]) * x + (c - this[3]) * y
this[7] += (b - this[1]) * x + (d - this[4]) * y
return this
}
/**
* 平移
* @param {number} x 水平偏移距离
* @param {number} y 垂直偏移距离
* @returns {Matrix}
*/
translate(x, y) {
this[6] += this[0] * x + this[3] * y
this[7] += this[1] * x + this[4] * y
return this
}
/**
* 垂直平移
* @param {number} y 水平偏移距离
* @returns {Matrix}
*/
translateY(y) {
this[6] += this[3] * y
this[7] += this[4] * y
return this
}
/**
* 在指定点倾斜
* @param {number} x 倾斜位置X
* @param {number} y 倾斜位置Y
* @param {number} h 水平倾斜系数
* @param {number} v 垂直倾斜系数
* @returns {Matrix}
*/
skewAt(x, y, h, v) {
const a = this[0]
const b = this[1]
const c = this[3]
const d = this[4]
this[0] = a + c * v
this[1] = b + d * v
this[3] = a * h + c
this[4] = b * h + d
this[6] += (a - this[0]) * x + (c - this[3]) * y
this[7] += (b - this[1]) * x + (d - this[4]) * y
return this
}
/**
* 水平镜像
* @returns {Matrix}
*/
mirrorh() {
this[0] = -this[0]
this[3] = -this[3]
return this
}
/**
* 垂直镜像
* @returns {Matrix}
*/
mirrorv() {
this[1] = -this[1]
this[4] = -this[4]
return this
}
/**
* 投影
* @param {number} flip 是否垂直翻转(-1或1)
* @param {number} width 屏幕宽度
* @param {number} height 屏幕高度
* @returns {Matrix}
*/
project(flip, width, height) {
this[0] = 2 / width
this[1] = 0
this[3] = 0
this[4] = 2 * flip / height
this[6] = -1
this[7] = -flip
return this
}
// 静态 - 平面矩阵实例
static instance = new Matrix()
}
// 初始化WebGL上下文
GL.initialize()