From e96a756af375d52f0efd25e4aa1509f969da34f2 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Wed, 9 Feb 2022 16:03:00 +0800 Subject: [PATCH] Add Camera --- package-lock.json | 11 ++ package.json | 1 + source/GLRender/BasicRenderer.ts | 9 + source/GLRender/BasicShader.ts | 4 +- source/GLRender/Camera.ts | 273 +++++++++++++++++++++++++++++ source/GLRender/ClassicRenderer.ts | 3 +- source/GLRender/GLContext.ts | 22 ++- 7 files changed, 314 insertions(+), 9 deletions(-) create mode 100644 source/GLRender/Camera.ts diff --git a/package-lock.json b/package-lock.json index 121190f..17f538b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "GPL", "dependencies": { "@juggle/resize-observer": "^3.3.1", + "gl-matrix": "^3.4.3", "react": "^17.0.2", "react-dom": "^17.0.2" }, @@ -2540,6 +2541,11 @@ "assert-plus": "^1.0.0" } }, + "node_modules/gl-matrix": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==" + }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -8756,6 +8762,11 @@ "assert-plus": "^1.0.0" } }, + "gl-matrix": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==" + }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", diff --git a/package.json b/package.json index 2530b12..417cef3 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ }, "dependencies": { "@juggle/resize-observer": "^3.3.1", + "gl-matrix": "^3.4.3", "react": "^17.0.2", "react-dom": "^17.0.2" } diff --git a/source/GLRender/BasicRenderer.ts b/source/GLRender/BasicRenderer.ts index 661ebd2..027e933 100644 --- a/source/GLRender/BasicRenderer.ts +++ b/source/GLRender/BasicRenderer.ts @@ -2,6 +2,7 @@ import { AbstractRenderer, IRendererParam, IAnyObject } from "@Model/Renderer"; import { EventType } from "@Model/Emitter"; import { GLCanvas, GLCanvasOption } from "./GLCanvas"; import { GLContext } from "./GLContext"; +import { Camera } from "./Camera"; import { Clock } from "@Model/Clock"; interface IRendererOwnParams {} @@ -27,6 +28,11 @@ abstract class BasicRenderer< */ public canvas: GLCanvas; + /** + * 主相机 + */ + public camera: Camera; + /** * 渲染时钟 */ @@ -46,6 +52,9 @@ abstract class BasicRenderer< // 实例化画布对象 this.canvas = new GLCanvas(canvas, this.param); + // 实例化摄像机 + this.camera = new Camera(this.canvas); + // 尝试 webgl2 this.gl = this.canvas.can.getContext("webgl2") as any; if (this.gl) { diff --git a/source/GLRender/BasicShader.ts b/source/GLRender/BasicShader.ts index 172c1fb..c747e80 100644 --- a/source/GLRender/BasicShader.ts +++ b/source/GLRender/BasicShader.ts @@ -21,9 +21,7 @@ interface IBasicsShaderUniform { */ class BasicsShader extends GLShader{ - public onLoad(context: GLContext) { - - super.onLoad(context); + public onLoad() { // 顶点着色 const vertex = ` diff --git a/source/GLRender/Camera.ts b/source/GLRender/Camera.ts new file mode 100644 index 0000000..d1a32df --- /dev/null +++ b/source/GLRender/Camera.ts @@ -0,0 +1,273 @@ +import { vec3, vec2, mat4 } from 'gl-matrix'; +import { GLCanvas } from './GLCanvas'; + +/** + * 摄像机 + */ +class Camera{ + + private canvas: GLCanvas; + + /** + * 视点 + */ + public eye: vec3; + + /** + * 目标 + */ + public target: vec3; + + /** + * 镜头旋转方向 + */ + public up: vec3; + + /** + * 视野大小 + */ + public range: number = Math.PI / 9; + + /** + * 画布宽高比例 + */ + public ratio: number = 1.; + + /** + * 进远平面距离 + */ + public nearFar: vec2; + + /** + * 观察者矩阵 + */ + public viewMat: mat4; + + /** + * 观察者矩阵 + */ + public projectionMat: mat4; + + /** + * 变换矩阵 + */ + public transformMat: mat4; + + /** + * 逆变换矩阵 + */ + public transformNMat: mat4; + + /** + * 构造函数设置初始值 + */ + public constructor(canvas: GLCanvas) { + + this.canvas = canvas; + + // 设置全部参数的初始值 + this.eye = vec3.create(); + this.target = vec3.create(); + this.up = vec3.create(); + this.nearFar = vec2.create(); + this.viewMat = mat4.create(); + + this.projectionMat = mat4.create(); + this.transformMat = mat4.create(); + this.transformNMat = mat4.create(); + + + + // 设置视点初始值 + vec3.set(this.eye, 0., 0., 10.); + + // 设置向上方向 + vec3.set(this.up, 0., 1., 0.); + + // 设置进远平面 + vec2.set(this.nearFar, 0.001, 1000.); + + // 射线追踪临时变量 + this.tempRayP = vec3.create(); + this.tempRayO = vec3.create(); + this.tempRayPoint = vec3.create(); + } + + private tempRayPoint: vec3; + private tempRayP: vec3; + private tempRayO: vec3; + + /** + * 生成变换需要的全部矩阵 + */ + public generateMat(){ + + // 更新 ratio + this.ratio = this.canvas.ratio; + + // 更新观察者矩阵 + mat4.lookAt(this.viewMat, this.eye, this.target, this.up); + + // 更新投影矩阵 + mat4.perspective(this.projectionMat, + this.range, this.ratio, this.nearFar[0], this.nearFar[1]); + + // 更新变换矩阵 + mat4.multiply(this.transformMat, this.projectionMat, this.viewMat); + + // 计算逆矩阵 + mat4.invert(this.transformNMat, this.transformMat); + } + + /** + * X 轴旋转角度 + * [0 - 360) + */ + public angleX:number = 90; + + /** + * Y 轴旋转角度 + * [90 - -90] + */ + public angleY:number = 0; + + /** + * 通过角度设置视点 + */ + public setEyeFromAngle(){ + + // 平移至原点 + vec3.sub(this.eye, this.eye, this.target); + + // 计算视点距离 + let dis = vec3.length(this.eye); + + // 计算方向角 + let anDir = vec3.create(); + + // 设置水平旋转坐标 + let dx = Math.cos(this.angleX * Math.PI / 180); + let dz = Math.sin(this.angleX * Math.PI / 180); + + // 计算垂直旋转坐标 + let dv = Math.cos(this.angleY * Math.PI / 180); + let dy = Math.sin(this.angleY * Math.PI / 180); + + // 合成 + vec3.set(anDir, + dx * dv * dis, + dy * dis, + dz * dv * dis + ); + + // 赋值 + vec3.copy(this.eye, anDir); + + // 平移回视点 + vec3.add(this.eye, this.eye, this.target); + } + + /** + * 控制灵敏度 + */ + public sensitivity:number = .5; + + /** + * 摄像机控制函数 + */ + public ctrl(x:number, y:number) { + + this.angleX += x * this.sensitivity; + this.angleY += y * this.sensitivity; + + if (this.angleX > 360) this.angleX = this.angleX - 360; + if (this.angleX < 0) this.angleX = 360 + this.angleX; + + if (this.angleY > 90) this.angleY = 90; + if (this.angleY < -90) this.angleY = -90; + + this.setEyeFromAngle(); + } + + /** + * 射线追踪 + */ + public rayTrack(x: number, y: number): [vec3, vec3] { + + // 逆变换 + vec3.set(this.tempRayP, x, y, 1); + vec3.transformMat4(this.tempRayP, this.tempRayP, this.transformNMat); + + vec3.set(this.tempRayO, x, y, 0); + vec3.transformMat4(this.tempRayO, this.tempRayO, this.transformNMat); + + vec3.sub(this.tempRayP, this.tempRayP, this.tempRayO); + vec3.normalize(this.tempRayP, this.tempRayP); + + return [this.tempRayO, this.tempRayP]; + } + + /** + * 极限追踪距离 + */ + public EL:number = 1e-5; + + private scaleRay(D: number, d: number, o: vec3, p: vec3): vec3 { + + // 限制 d + if (d < this.EL) d = this.EL; + + let len = D / d; + + this.tempRayPoint[0] = o[0] + p[0] * len; + this.tempRayPoint[1] = o[1] + p[1] * len; + this.tempRayPoint[2] = o[2] + p[2] * len; + + return this.tempRayPoint; + } + + /** + * 计算射线与 XY 平面焦点 + * @param o 射线原点 + * @param p 射线方向 + * @param k 交点距离 + */ + public intersectionLineXYPlant(o: vec3, p: vec3, k: number = 0): vec3 { + + let d = Math.abs(p[2] - k); + let D = Math.abs(o[2] - k); + + return this.scaleRay(D, d, o, p); + } + + /** + * 计算射线与 XZ 平面焦点 + * @param o 射线原点 + * @param p 射线方向 + * @param k 交点距离 + */ + public intersectionLineXZPlant(o: vec3, p: vec3, k:number = 0): vec3 { + + let d = Math.abs(p[1] - k); + let D = Math.abs(o[1] - k); + + return this.scaleRay(D, d, o, p); + } + + /** + * 计算射线与 YZ 平面焦点 + * @param o 射线原点 + * @param p 射线方向 + * @param k 交点距离 + */ + public intersectionLineYZPlant(o: vec3, p: vec3, k: number = 0): vec3 { + + let d = Math.abs(p[0] - k); + let D = Math.abs(o[0] - k); + + return this.scaleRay(D, d, o, p); + } +} + +export default Camera; +export { Camera }; \ No newline at end of file diff --git a/source/GLRender/ClassicRenderer.ts b/source/GLRender/ClassicRenderer.ts index a74bf41..4ed1c85 100644 --- a/source/GLRender/ClassicRenderer.ts +++ b/source/GLRender/ClassicRenderer.ts @@ -11,8 +11,7 @@ class ClassicRenderer extends BasicRenderer<{}, IClassicRendererParams> { // 自动调节分辨率 this.autoResize(); - let shader = new BasicsShader(); - !shader.isLoad && shader.onLoad(this.gl); + let shader = new BasicsShader().bindRenderer(this); // 运行 this.run(); diff --git a/source/GLRender/GLContext.ts b/source/GLRender/GLContext.ts index 2078072..1ea7f6a 100644 --- a/source/GLRender/GLContext.ts +++ b/source/GLRender/GLContext.ts @@ -1,4 +1,5 @@ import { Emitter, EventType } from "@Model/Emitter"; +import BasicRenderer from "./BasicRenderer"; export type GLContext = WebGL2RenderingContext | WebGLRenderingContext; @@ -14,17 +15,30 @@ export abstract class GLContextObject< */ protected gl: GLContext = undefined as any; + /** + * 使用的渲染器 + */ + protected renderer: BasicRenderer = undefined as any; + /** * 是否加载 */ public get isLoad(): boolean { - return !!this.gl; + return !!(this.gl && this.renderer); } + /** + * 初始化 + */ + public bindRenderer(renderer: BasicRenderer): this { + this.renderer = renderer; + this.gl = this.renderer.gl; + this.onLoad(); + return this; + } + /** * 初始化生命周期 */ - public onLoad(context: GLContext): any { - this.gl = context; - }; + abstract onLoad(): any; } \ No newline at end of file