|
@@ -0,0 +1,308 @@
|
|
|
+import { mat4, vec3 } from "gl-matrix";
|
|
|
+
|
|
|
+export type VaoBuffers<T extends string> = { [key in T]: WebGLBuffer };
|
|
|
+export const updateVao = <T extends string>(
|
|
|
+ gl: WebGL2RenderingContext,
|
|
|
+ modal: { [key in T]: ArrayBufferView },
|
|
|
+ pointers: Pointer<T>[],
|
|
|
+ vao: WebGLVertexArrayObject
|
|
|
+) => {
|
|
|
+ gl.bindVertexArray(vao);
|
|
|
+
|
|
|
+ const keys = pointers.map((p) => p.key);
|
|
|
+ const buffers = Object.fromEntries(
|
|
|
+ Object.entries(modal)
|
|
|
+ .filter(([k]) => keys.includes(k as T))
|
|
|
+ .map(([key, val]) => {
|
|
|
+ const buffer = gl.createBuffer();
|
|
|
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
|
+ gl.bufferData(gl.ARRAY_BUFFER, val as ArrayBufferView, gl.STATIC_DRAW);
|
|
|
+ return [key, buffer];
|
|
|
+ })
|
|
|
+ ) as VaoBuffers<T>;
|
|
|
+
|
|
|
+ for (const pointer of pointers) {
|
|
|
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffers[pointer.key]);
|
|
|
+ gl.enableVertexAttribArray(pointer.loc);
|
|
|
+ gl.vertexAttribPointer(
|
|
|
+ pointer.loc,
|
|
|
+ pointer.size,
|
|
|
+ pointer.type,
|
|
|
+ false,
|
|
|
+ pointer.stride,
|
|
|
+ pointer.offset
|
|
|
+ );
|
|
|
+
|
|
|
+ if (pointer.divisor && "divisor" in pointer) {
|
|
|
+ gl.vertexAttribDivisor(pointer.loc, pointer.divisor!);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return buffers;
|
|
|
+};
|
|
|
+
|
|
|
+export type Pointer<T = string> = {
|
|
|
+ loc: number;
|
|
|
+ key: T;
|
|
|
+ size: number;
|
|
|
+ type: number;
|
|
|
+ stride: number;
|
|
|
+ offset: number;
|
|
|
+ divisor?: number;
|
|
|
+};
|
|
|
+export const generateVao = <T extends string>(
|
|
|
+ gl: WebGL2RenderingContext,
|
|
|
+ modal: { [key in T]: ArrayBufferView },
|
|
|
+ pointers: Pointer<T>[]
|
|
|
+) => {
|
|
|
+ const vao = gl.createVertexArray()!;
|
|
|
+ updateVao(gl, modal, pointers, vao);
|
|
|
+
|
|
|
+ if ("includes" in modal) {
|
|
|
+ const eleBuffer = gl.createBuffer();
|
|
|
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, eleBuffer);
|
|
|
+ gl.bufferData(
|
|
|
+ gl.ELEMENT_ARRAY_BUFFER,
|
|
|
+ (modal as any).includes,
|
|
|
+ gl.STATIC_DRAW
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return vao;
|
|
|
+};
|
|
|
+
|
|
|
+export type Uniforms = {
|
|
|
+ [key in string]:
|
|
|
+ | number
|
|
|
+ | number[]
|
|
|
+ | vec3[]
|
|
|
+ | number[][]
|
|
|
+ | Float32Array
|
|
|
+ | Float32Array[]
|
|
|
+ | Uniforms
|
|
|
+ | Uniforms[]
|
|
|
+ | mat4[];
|
|
|
+};
|
|
|
+
|
|
|
+const setUniform = (
|
|
|
+ gl: WebGL2RenderingContext,
|
|
|
+ program: WebGLProgram,
|
|
|
+ key: string,
|
|
|
+ valR: number | number[] | Float32Array
|
|
|
+) => {
|
|
|
+ const val = (
|
|
|
+ valR instanceof Float32Array || Array.isArray(valR) ? valR : [valR]
|
|
|
+ ) as number[];
|
|
|
+
|
|
|
+ const loc = gl.getUniformLocation(program, key);
|
|
|
+
|
|
|
+ if (loc) {
|
|
|
+ try {
|
|
|
+ if (/(mat$)|(mats\[\d+\])/gi.test(key) && (valR as any).length) {
|
|
|
+ (gl as any)[`uniformMatrix${Math.sqrt(val.length)}fv`](loc, false, val);
|
|
|
+ } else if (key.includes("Tex") || key.includes("tex")) {
|
|
|
+ gl.uniform1iv(loc, val);
|
|
|
+ } else if (val.length > 4) {
|
|
|
+ for (let i = 0; i < val.length; i++) {
|
|
|
+ setUniform(gl, program, `${key}[${i}]`, val[i]);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ (gl as any)[`uniform${val.length}fv`](loc, val);
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error(`key in ${key} val in`, val);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // console.log(key, loc);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+export const setUniforms = (
|
|
|
+ gl: WebGL2RenderingContext,
|
|
|
+ program: WebGLProgram,
|
|
|
+ data: Uniforms,
|
|
|
+ prefix = ""
|
|
|
+) => {
|
|
|
+ Object.entries(data).forEach(([k, v]) => {
|
|
|
+ if (
|
|
|
+ v instanceof Float32Array ||
|
|
|
+ Array.isArray(v) ||
|
|
|
+ typeof v !== "object"
|
|
|
+ ) {
|
|
|
+ if (Array.isArray(v) && typeof v[0] === "object") {
|
|
|
+ v.forEach((vi, ndx) => {
|
|
|
+ if (vi instanceof Float32Array) {
|
|
|
+ setUniform(gl, program, prefix + k + `[${ndx}]`, vi);
|
|
|
+ } else {
|
|
|
+ setUniforms(gl, program, vi as Uniforms, prefix + k + `[${ndx}].`);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ setUniform(gl, program, prefix + k, v as number);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ setUniforms(gl, program, v as Uniforms, k + ".");
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+export const generateProgram = (
|
|
|
+ gl: WebGL2RenderingContext,
|
|
|
+ ...shaders: WebGLShader[]
|
|
|
+) => {
|
|
|
+ const program = gl.createProgram();
|
|
|
+ if (!program) throw "gl 无法创建程序";
|
|
|
+ for (const shader of shaders) {
|
|
|
+ gl.attachShader(program, shader);
|
|
|
+ }
|
|
|
+ gl.linkProgram(program);
|
|
|
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
|
+ console.error(gl.getProgramInfoLog(program));
|
|
|
+ throw "程序链接失败";
|
|
|
+ }
|
|
|
+ return program;
|
|
|
+};
|
|
|
+
|
|
|
+const typeNameMap: { [key in string]: string } = {
|
|
|
+ 35633: "顶点着色器",
|
|
|
+ 35632: "片段着色器",
|
|
|
+};
|
|
|
+export const createShader = (
|
|
|
+ gl: WebGL2RenderingContext,
|
|
|
+ type: number,
|
|
|
+ source: string
|
|
|
+) => {
|
|
|
+ const shader = gl.createShader(type);
|
|
|
+ if (!shader) throw `gl 无法创建${typeNameMap[type]}着色器`;
|
|
|
+ gl.shaderSource(shader, source);
|
|
|
+ gl.compileShader(shader);
|
|
|
+
|
|
|
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
|
+ console.error(gl.getShaderInfoLog(shader));
|
|
|
+ throw `${typeNameMap[type]}着色器编译失败`;
|
|
|
+ }
|
|
|
+
|
|
|
+ return shader;
|
|
|
+};
|
|
|
+
|
|
|
+export const createProgram = (
|
|
|
+ gl: WebGL2RenderingContext,
|
|
|
+ vSource: string,
|
|
|
+ fSource: string
|
|
|
+) => {
|
|
|
+ const program = generateProgram(
|
|
|
+ gl,
|
|
|
+ createShader(gl, gl.VERTEX_SHADER, vSource),
|
|
|
+ createShader(gl, gl.FRAGMENT_SHADER, fSource)
|
|
|
+ );
|
|
|
+ return program;
|
|
|
+};
|
|
|
+
|
|
|
+import { ReadonlyVec3, vec2 } from "gl-matrix";
|
|
|
+import { mergeFuns } from ".";
|
|
|
+
|
|
|
+export const createFPSCamera = (
|
|
|
+ mount: HTMLElement,
|
|
|
+ onChange: (viewMat: mat4, eys: vec3, front: vec3) => void,
|
|
|
+ worldUp = vec3.fromValues(0, 1, 0),
|
|
|
+ initEys: ReadonlyVec3 = vec3.fromValues(0, 0, 3),
|
|
|
+ initView: { pitch?: number; yaw?: number } = {},
|
|
|
+ maxDis = Infinity
|
|
|
+) => {
|
|
|
+ const up = vec3.fromValues(0, 1, 0);
|
|
|
+ const eys = vec3.fromValues(initEys[0], initEys[1], initEys[2]);
|
|
|
+ // 向量,前沿方向
|
|
|
+ const front = vec3.fromValues(0, 0, -1);
|
|
|
+ // 俯仰视角
|
|
|
+ let pitch = initView.pitch || 0;
|
|
|
+ // 偏航角
|
|
|
+ let yaw = initView.yaw || -Math.PI / 2;
|
|
|
+
|
|
|
+ const cameraMat = mat4.identity(mat4.create());
|
|
|
+ const updateCameraMat = () => {
|
|
|
+ const target = vec3.add(vec3.create(), eys, front);
|
|
|
+ mat4.lookAt(cameraMat, eys, target, up);
|
|
|
+ onChange(cameraMat, eys, front);
|
|
|
+ };
|
|
|
+ const updateFront = () => {
|
|
|
+ front[0] = Math.cos(pitch) * Math.cos(yaw);
|
|
|
+ front[1] = Math.sin(pitch);
|
|
|
+ front[2] = Math.cos(pitch) * Math.sin(yaw);
|
|
|
+ // console.log(pitch, yaw);
|
|
|
+ vec3.normalize(front, front);
|
|
|
+ vec3.cross(up, vec3.cross(vec3.create(), front, worldUp), front);
|
|
|
+ };
|
|
|
+
|
|
|
+ const start = vec2.create();
|
|
|
+ const mousedownHandler = (ev: MouseEvent) => {
|
|
|
+ start[0] = ev.offsetX;
|
|
|
+ start[1] = ev.offsetY;
|
|
|
+
|
|
|
+ mount.addEventListener("mousemove", mouseMoveHandler);
|
|
|
+ mount.addEventListener("mouseup", mouseUpHandler);
|
|
|
+ };
|
|
|
+
|
|
|
+ const rotatePixelAmount = 1500;
|
|
|
+ const mouseMoveHandler = (ev: MouseEvent) => {
|
|
|
+ const end = vec2.fromValues(ev.offsetX, ev.offsetY);
|
|
|
+ const move = vec2.sub(vec2.create(), end, start);
|
|
|
+ pitch += (move[1] / rotatePixelAmount) * Math.PI;
|
|
|
+ if (pitch > 89) {
|
|
|
+ pitch = 89;
|
|
|
+ } else if (pitch < -89) {
|
|
|
+ pitch = -89;
|
|
|
+ }
|
|
|
+ yaw -= (move[0] / rotatePixelAmount) * Math.PI;
|
|
|
+ start[0] = end[0];
|
|
|
+ start[1] = end[1];
|
|
|
+ updateFront();
|
|
|
+ updateCameraMat();
|
|
|
+ };
|
|
|
+
|
|
|
+ const mouseUpHandler = () => {
|
|
|
+ mount.removeEventListener("mousemove", mouseMoveHandler);
|
|
|
+ mount.removeEventListener("moseup", mouseUpHandler);
|
|
|
+ };
|
|
|
+ const wheelHandler = (ev: WheelEvent) => {
|
|
|
+ const amount = ev.deltaY * -0.01;
|
|
|
+ const move = vec3.scale(vec3.create(), front, amount);
|
|
|
+ const neys = vec3.create();
|
|
|
+ vec3.add(neys, eys, move);
|
|
|
+ if (vec3.length(neys) <= maxDis) {
|
|
|
+ vec3.copy(eys, neys);
|
|
|
+ updateCameraMat();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ mount.addEventListener("mousedown", mousedownHandler);
|
|
|
+ document.addEventListener("wheel", wheelHandler);
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ updateFront();
|
|
|
+ updateCameraMat();
|
|
|
+ });
|
|
|
+
|
|
|
+ return {
|
|
|
+ recovery() {
|
|
|
+ vec3.copy(eys, vec3.fromValues(initEys[0], initEys[1], initEys[2]));
|
|
|
+ pitch = initView.pitch || 0;
|
|
|
+ yaw = initView.yaw || -Math.PI / 2;
|
|
|
+ updateFront();
|
|
|
+ updateCameraMat();
|
|
|
+ },
|
|
|
+ destory: mergeFuns(mouseUpHandler, () =>
|
|
|
+ document.removeEventListener("wheel", wheelHandler)
|
|
|
+ ),
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+export const useTex = (
|
|
|
+ gl: WebGL2RenderingContext,
|
|
|
+ tex: WebGLTexture,
|
|
|
+ target: number = gl.TEXTURE_2D,
|
|
|
+ offset = 0
|
|
|
+) => {
|
|
|
+ gl.activeTexture(gl.TEXTURE0 + offset);
|
|
|
+ gl.bindTexture(target, tex);
|
|
|
+ return offset;
|
|
|
+};
|