diff --git a/src/engine/components/AtmosphericComponent.ts b/src/engine/components/AtmosphericComponent.ts new file mode 100644 index 00000000..e1230419 --- /dev/null +++ b/src/engine/components/AtmosphericComponent.ts @@ -0,0 +1,98 @@ +import { SkyRenderer } from "../.."; +import { AtmosphericScatteringSky, AtmosphericScatteringSkySetting } from "../textures/AtmosphericScatteringSky"; + +/** + * + * Atmospheric Sky Box Component + * @group Components + */ +export class AtmosphericComponent extends SkyRenderer { + + private _atmosphericScatteringSky: AtmosphericScatteringSky; + + public get sunX() { + return this._atmosphericScatteringSky.setting.sunX; + } + + public set sunX(value) { + this._atmosphericScatteringSky.setting.sunX = value; + this._atmosphericScatteringSky.apply(); + } + + public get sunY() { + return this._atmosphericScatteringSky.setting.sunY; + } + + public set sunY(value) { + this._atmosphericScatteringSky.setting.sunY = value; + this._atmosphericScatteringSky.apply(); + } + + public get eyePos() { + return this._atmosphericScatteringSky.setting.eyePos; + } + + public set eyePos(value) { + this._atmosphericScatteringSky.setting.eyePos = value; + this._atmosphericScatteringSky.apply(); + } + + public get sunRadius() { + return this._atmosphericScatteringSky.setting.sunRadius; + } + + public set sunRadius(value) { + this._atmosphericScatteringSky.setting.sunRadius = value; + this._atmosphericScatteringSky.apply(); + } + + public get sunRadiance() { + return this._atmosphericScatteringSky.setting.sunRadiance; + } + + public set sunRadiance(value) { + this._atmosphericScatteringSky.setting.sunRadiance = value; + this._atmosphericScatteringSky.apply(); + } + + public get sunBrightness() { + return this._atmosphericScatteringSky.setting.sunBrightness; + } + + public set sunBrightness(value) { + this._atmosphericScatteringSky.setting.sunBrightness = value; + this._atmosphericScatteringSky.apply(); + } + + public get displaySun() { + return this._atmosphericScatteringSky.setting.displaySun; + } + + public set displaySun(value) { + this._atmosphericScatteringSky.setting.displaySun = value; + this._atmosphericScatteringSky.apply(); + } + + protected init(): void { + super.init(); + this._atmosphericScatteringSky = new AtmosphericScatteringSky(new AtmosphericScatteringSkySetting()); + } + + protected start(): void { + let scene = this.transform.scene3D; + this.map = this._atmosphericScatteringSky; + scene.envMap = this._atmosphericScatteringSky; + super.start(); + } + + protected onEnable(): void { + + } + + protected onDisable(): void { + + } + + public debug() { + } +} diff --git a/src/engine/components/BillboardComponent.ts b/src/engine/components/BillboardComponent.ts new file mode 100644 index 00000000..fa35f0cc --- /dev/null +++ b/src/engine/components/BillboardComponent.ts @@ -0,0 +1,40 @@ +import { Object3DType } from './gui/data/AssetsInfo'; +import { Camera3D } from '../core/Camera3D'; +import { Object3D } from '../core/entities/Object3D'; +import { Vector3 } from '../math/Vector3'; +import { ComponentBase } from './ComponentBase'; + +export class BillboardComponent extends ComponentBase { + public type: Object3DType; + public camera: Camera3D; + private _cameraDirection: Vector3; + + constructor() { + super(); + this._cameraDirection = new Vector3(); + } + + protected onUpdate() { + super.onUpdate(); + if (this.enable && this.transform.view3D.camera) { + this.updateBillboardMatrix(); + } + } + + private updateBillboardMatrix(): void { + let camera = this.transform.view3D.camera; + this._cameraDirection.copyFrom(camera.transform.back); + if (this.type == Object3DType.BillboardXYZ) { + this._cameraDirection.normalize().multiplyScalar(100); + } else if (this.type == Object3DType.BillboardY) { + this._cameraDirection.y = 0; + this._cameraDirection.normalize().multiplyScalar(100); + } + this.transform.lookAt(Vector3.ZERO, this._cameraDirection, camera.transform.up); + } + + public cloneTo(obj: Object3D) { + let component = obj.addComponent(BillboardComponent); + component.type = this.type; + } +} diff --git a/src/engine/components/ComponentBase.ts b/src/engine/components/ComponentBase.ts new file mode 100644 index 00000000..a1c96092 --- /dev/null +++ b/src/engine/components/ComponentBase.ts @@ -0,0 +1,241 @@ +import { View3D } from "../core/View3D"; +import { Object3D } from "../core/entities/Object3D"; +import { CEventDispatcher } from "../event/CEventDispatcher"; +import { ComponentType, SerializeTag } from "../util/SerializeDefine"; +import { Transform } from "./Transform"; + + +/** + * Components are used to attach functionality to object3D, it has an owner object3D. + * The component can receive update events at each frame. + * @group Components + */ +export class ComponentBase { + /** + * owner object3D + */ + public object3D: Object3D = null; + + /** + * @internal + */ + public eventDispatcher: CEventDispatcher; + + /** + * @internal + */ + public componentType: ComponentType = ComponentType.none; + + /** + * @internal + */ + public serializeTag?: SerializeTag; + + /** + * @internal + */ + protected _enable: boolean = true; + + private __isStart: boolean = false; + + constructor() { + this.eventDispatcher = new CEventDispatcher(); + } + + /** + * Return the Transform component attached to the Object3D. + */ + public get transform(): Transform { + return this.object3D.transform; + } + + + /** + * Enable/disable components. The enabled components can be updated, while the disabled components cannot be updated. + */ + public set enable(value: boolean) { + if (this._enable != value) { + this._enable = value; + if (this._enable) { + this.onEnable(); + } else { + this.onDisable(); + } + } + } + + /** + * Enable/disable components. The enabled components can be updated, while the disabled components cannot be updated. + */ + public get enable(): boolean { + return this._enable; + } + + private __init(param?: any) { + this.init(param); + } + + private __start() { + if (this.transform && this.transform.scene3D && this.__isStart == false) { + this.start(); + this.__isStart = true; + } + if (this.transform && this.transform.scene3D) { + this.onEnable(); + } + let hasUpdate = this.onUpdate.toString().replace(/\s+/g, '').length; + if (hasUpdate > 10) { + this._onUpdate(this.onUpdate.bind(this)); + } + let hasLateUpdate = this.onLateUpdate.toString().replace(/\s+/g, '').length; + if (hasLateUpdate > 14) { + this._onLateUpdate(this.onLateUpdate.bind(this)); + } + let hasBeforeUpdate = this.onBeforeUpdate.toString().replace(/\s+/g, '').length; + if (hasBeforeUpdate > 16) { + this._onBeforeUpdate(this.onBeforeUpdate.bind(this)); + } + let hasCompute = this.onCompute.toString().replace(/\s+/g, '').length; + if (hasCompute > 11) { + this._onCompute(this.onCompute.bind(this)); + } + let hasOnGraphic = this.onGraphic.toString().replace(/\s+/g, '').length; + if (hasOnGraphic > 11) { + this._onGraphic(this.onGraphic.bind(this)); + } + } + + private __stop() { + if (this.transform && this.transform.scene3D) { + this.onDisable(); + } + this._onUpdate(null); + this._onLateUpdate(null); + this._onBeforeUpdate(null); + this._onCompute(null); + this._onGraphic(null); + } + + protected init(param?: any) { } + protected start() { } + protected stop() { } + protected onEnable(view?: View3D) { } + protected onDisable(view?: View3D) { } + protected onUpdate(view?: View3D) { } + protected onLateUpdate(view?: View3D) { } + protected onBeforeUpdate(view?: View3D) { } + protected onCompute(view?: View3D, command?: GPUCommandEncoder) { } + protected onGraphic(view?: View3D) { } + + /** + * + * clone component data to target object3D + * @param obj target object3D + */ + public cloneTo(obj: Object3D) { } + + /** + * internal + * Add update function. Will be executed at every frame update. + * @param call callback + */ + private _onUpdate(call: Function) { + if (call != null) { + ComponentBase.componentsUpdateList.set(this, call); + } else { + ComponentBase.componentsUpdateList.delete(this); + } + } + + /** + * Add a delayed update function. + * @param call callback + */ + private _onLateUpdate(call: Function) { + // if(!this.enable) return; + if (call != null) { + ComponentBase.componentsLateUpdateList.set(this, call); + } else { + ComponentBase.componentsLateUpdateList.delete(this); + } + } + + /** + * The function executed before adding frame updates. + * @param call callback + */ + private _onBeforeUpdate(call: Function) { + // if(!this.enable) return; + if (call != null) { + ComponentBase.componentsBeforeUpdateList.set(this, call); + } else { + ComponentBase.componentsBeforeUpdateList.delete(this); + } + } + + /** + * @internal + * Add individual execution compute capability + * @param call callback + */ + private _onCompute(call: Function) { + if (call != null) { + ComponentBase.componentsComputeList.set(this, call); + } else { + ComponentBase.componentsComputeList.delete(this); + } + } + + /** + * Add individual execution drawing ability + * @param call callback + */ + private _onGraphic(call: Function) { + if (call != null) { + ComponentBase.graphicComponent.set(this, call); + } else { + ComponentBase.graphicComponent.delete(this); + } + } + + /** + * release this component + */ + public destroy() { + this.enable = false; + this.stop(); + this._onBeforeUpdate(null); + this._onUpdate(null); + this._onLateUpdate(null); + } + + /** + * @internal + */ + static componentsUpdateList: Map = new Map(); + + /** + * @internal + */ + static componentsLateUpdateList: Map = new Map(); + + /** + * @internal + */ + static componentsBeforeUpdateList: Map = new Map(); + + /** + * @internal + */ + static componentsComputeList: Map = new Map(); + + /** + * @internal + */ + static waitStartComponent: Map = new Map(); + + /** + * @internal + */ + static graphicComponent: Map = new Map(); +} diff --git a/src/engine/components/Transform.ts b/src/engine/components/Transform.ts new file mode 100644 index 00000000..a416c81b --- /dev/null +++ b/src/engine/components/Transform.ts @@ -0,0 +1,808 @@ +import { ComponentBase } from "./ComponentBase"; +import { Object3D } from "../core/entities/Object3D"; +import { Scene3D } from "../core/Scene3D"; +import { CEvent } from "../event/CEvent"; +import { MathUtil } from "../math/MathUtil"; +import { append, makeMatrix44, Matrix4 } from "../math/Matrix4"; +import { Orientation3D } from "../math/Orientation3D"; +import { Quaternion } from "../math/Quaternion"; +import { Vector3 } from "../math/Vector3"; +import { View3D } from "../core/View3D"; +import { ComponentType } from "../util/SerializeDefine"; + +/** + * The Transform component contains the position, rotation, and scaling of an object in 3D space. + * Each object (Object 3D) has a Transform component + * @group Components + */ +export class Transform extends ComponentBase { + /** + * @internal + */ + public static LIMIT: number = 1; + /** + * @internal + */ + public static COMPONENT_NAME = 'UUTransform'; + /** + * @internal + */ + public static COMPONENT_TYPE = 'Transform'; + /** + * @internal + */ + public static POSITION_ONCHANGE: string = 'POSITION_ONCHANGE'; + + /** + * @internal + */ + public static ROTATION_ONCHANGE: string = 'ROTATION_ONCHANGE'; + /** + * @internal + */ + public static SCALE_ONCHANGE: string = 'SCALE_ONCHANGE'; + /** + * @internal + */ + public static PARENT_ONCHANGE: string = 'PARENT_ONCHANGE'; + /** + * @internal + */ + public static CHILDREN_ONCHANGE: string = 'CHILDREN_ONCHANGE'; + /** + * @internal + */ + public static ADD_ONCHANGE: string = 'ADD_ONCHANGE'; + + /** + * @internal + */ + public eventPositionChange: CEvent = new CEvent(Transform.POSITION_ONCHANGE); + /** + * @internal + */ + public eventRotationChange: CEvent = new CEvent(Transform.ROTATION_ONCHANGE); + /** + * @internal + */ + public eventScaleChange: CEvent = new CEvent(Transform.SCALE_ONCHANGE); + /** + * @internal + */ + public onPositionChange: Function; + /** + * @internal + */ + public onRotationChange: Function; + /** + * @internal + */ + public onScaleChange: Function; + + private _scene3d: Scene3D; + + private _parent: Transform; + + private _localPos: Vector3; + private _localRot: Vector3; + private _localRotQuat: Quaternion; + private _localScale: Vector3; + // public localMatrix: Matrix4; + + private _forward: Vector3 = new Vector3(); + private _back: Vector3 = new Vector3(); + private _right: Vector3 = new Vector3(); + private _left: Vector3 = new Vector3(); + private _up: Vector3 = new Vector3(); + private _down: Vector3 = new Vector3(); + public _worldMatrix: Matrix4; + private _localChange: boolean = true; + + private _targetPos: Vector3; + + public get targetPos(): Vector3 { + return this._targetPos; + } + public set targetPos(value: Vector3) { + this._targetPos = value; + } + + public get parent(): Transform { + return this._parent; + } + + public set parent(value: Transform) { + this._parent = value; + let hasRoot = value ? value.scene3D : null; + if (!hasRoot) { + this.object3D.components.forEach((c) => { + c[`__stop`](); + }); + } else { + this._scene3d = hasRoot; + this.object3D.components.forEach((c) => { + this.object3D[`appendLateStart`](c); + }); + } + + this.object3D.entityChildren.forEach((v) => { + v.transform.parent = this; + }); + } + + public set enable(value: boolean) { + if (this.transform._scene3d && value) { + super.enable = true; + } else { + super.enable = false; + } + this.object3D.entityChildren.forEach((v) => { + v.transform.enable = value; + }); + } + public get enable(): boolean { + return this._enable; + } + + public get scene3D(): Scene3D { + return this._scene3d; + } + + public set scene3D(value: Scene3D) { + this._scene3d = value; + } + + public get view3D(): View3D { + if (this._scene3d && this._scene3d.view) { + return this._scene3d.view; + } + return null; + } + + constructor() { + super(); + this.componentType = ComponentType.transform; + // this.localMatrix = new Matrix4(); + this.worldMatrix = new Matrix4(false); + this._localPos = new Vector3(); + this._localRot = new Vector3(); + this._localRotQuat = new Quaternion(); + this._localScale = new Vector3(1, 1, 1); + } + + awake() { } + + start() { } + + stop() { } + + // update() { } + + // lateUpdate() { } + + /** + * @internal + */ + public notifyLocalChange() { + this._localChange = true; + let entityChildren = this.object3D.entityChildren; + for (let i = 0, len = entityChildren.length; i < len; i++) { + const transform = entityChildren[i].transform; + transform.notifyLocalChange(); + } + } + + public get up(): Vector3 { + this.worldMatrix.transformVector(Vector3.UP, this._up); + return this._up; + } + + public set up(value: Vector3) { + this._up.copyFrom(value); + this.notifyLocalChange(); + + if (this.onRotationChange) { + this.onRotationChange(); + } + + if (this.eventRotationChange) { + this.eventDispatcher.dispatchEvent(this.eventRotationChange); + } + } + + public get down(): Vector3 { + this.worldMatrix.transformVector(Vector3.DOWN, this._down); + return this._down; + } + + public set down(value: Vector3) { + this._down.copyFrom(value); + this.notifyLocalChange(); + + if (this.onRotationChange) { + this.onRotationChange(); + } + + if (this.eventRotationChange) { + this.eventDispatcher.dispatchEvent(this.eventRotationChange); + } + } + + public get forward(): Vector3 { + this.worldMatrix.transformVector(Vector3.FORWARD, this._forward); + return this._forward; + } + + public set forward(value: Vector3) { + this._forward.copyFrom(value); + MathUtil.fromToRotation(Vector3.FORWARD, this._forward, Quaternion.HELP_0); + this.transform.localRotQuat = Quaternion.HELP_0; + this.notifyLocalChange(); + + if (this.onRotationChange) { + this.onRotationChange(); + } + + if (this.eventRotationChange) { + this.eventDispatcher.dispatchEvent(this.eventRotationChange); + } + } + + public get back(): Vector3 { + this.worldMatrix.transformVector(Vector3.BACK, this._back); + return this._back; + } + + public set back(value: Vector3) { + this._back.copyFrom(value); + MathUtil.fromToRotation(Vector3.BACK, this._back, Quaternion.HELP_0); + this.transform.localRotQuat = Quaternion.HELP_0; + this.notifyLocalChange(); + + if (this.onRotationChange) { + this.onRotationChange(); + } + + if (this.eventRotationChange) { + this.eventDispatcher.dispatchEvent(this.eventRotationChange); + } + } + + public get left(): Vector3 { + this.worldMatrix.transformVector(Vector3.neg_X_AXIS, this._left); + return this._left; + } + + public set left(value: Vector3) { + this._left.copyFrom(value); + this.notifyLocalChange(); + + if (this.onRotationChange) { + this.onRotationChange(); + } + + if (this.eventRotationChange) { + this.eventDispatcher.dispatchEvent(this.eventRotationChange); + } + } + + public get right(): Vector3 { + this.worldMatrix.transformVector(Vector3.X_AXIS, this._right); + return this._right; + } + + public set right(value: Vector3) { + this._right.copyFrom(value); + this.notifyLocalChange(); + + if (this.onRotationChange) { + this.onRotationChange(); + } + + if (this.eventRotationChange) { + this.eventDispatcher.dispatchEvent(this.eventRotationChange); + } + } + + /** + * + * 物体相对于父级变换属性,以四元数形式存储 + */ + public get localRotQuat(): Quaternion { + return this._localRotQuat; + } + + public set localRotQuat(value: Quaternion) { + this._localRotQuat = value; + this._localRotQuat.getEulerAngles(this._localRot); + + this.notifyLocalChange(); + + if (this.onRotationChange) { + this.onRotationChange(); + } + + if (this.eventRotationChange) { + this.eventDispatcher.dispatchEvent(this.eventRotationChange); + } + } + + /** + * @private + */ + public notifyChange(): void { + this.notifyLocalChange(); + if (this.onRotationChange) { + this.onRotationChange(); + } + + if (this.eventRotationChange) { + this.eventDispatcher.dispatchEvent(this.eventRotationChange); + } + if (this.eventPositionChange) { + this.eventDispatcher.dispatchEvent(this.eventPositionChange); + } + if (this.eventScaleChange) { + this.eventDispatcher.dispatchEvent(this.eventScaleChange); + } + } + + /** + * @internal + */ + public get worldMatrix(): Matrix4 { + this.updateWorldMatrix(); + return this._worldMatrix; + } + + /** + * @internal + */ + public set worldMatrix(value: Matrix4) { + this._worldMatrix = value; + } + + /** + * + * Update the matrix4 in world space + */ + public updateWorldMatrix(force: boolean = false) { + if (this._localChange || force) { + if (this.parent) { + makeMatrix44(this._localRot, this._localPos, this.localScale, this._worldMatrix); + append(this._worldMatrix, this.parent.worldMatrix, this._worldMatrix); + + // WasmMatrix4.makeMatrix44Append(this._localRot, this._localPos, this.localScale, this._worldMatrix, this._worldMatrix, this.parent.worldMatrix, this._worldMatrix); + this._localChange = false; + } else { + makeMatrix44(this._localRot, this._localPos, this.localScale, this._worldMatrix); + // WasmMatrix4.makeMatrix44(this._localRot, this._localPos, this.localScale, this._worldMatrix); + this._localChange = false; + } + } + } + + public lookTarget(target: Vector3, up: Vector3 = Vector3.UP) { + let worldPosition = this.transform.worldPosition; + this.lookAt(worldPosition, target, up); + } + + /** + * Current object's gaze position (global) (modified by its own global transformation) + * @param pos Own position (global) + * @param target Location of the target (global) + * @param up up direction + */ + public lookAt(pos: Vector3, target: Vector3, up: Vector3 = Vector3.UP) { + this._targetPos = target.clone(); + this._localPos.copyFrom(pos); + this.notifyLocalChange(); + + Matrix4.helpMatrix.lookAt(pos, target, up); + Matrix4.helpMatrix.invert(); + var prs: Vector3[] = Matrix4.helpMatrix.decompose(Orientation3D.QUATERNION); + Quaternion.CALCULATION_QUATERNION.x = prs[1].x; + Quaternion.CALCULATION_QUATERNION.y = prs[1].y; + Quaternion.CALCULATION_QUATERNION.z = prs[1].z; + Quaternion.CALCULATION_QUATERNION.w = prs[1].w; + Quaternion.CALCULATION_QUATERNION.toEulerAngles(this._localRot); + + if (this.eventPositionChange) { + this.eventDispatcher.dispatchEvent(this.eventPositionChange); + } + + if (this.onPositionChange) { + this.onPositionChange(); + } + + if (this.onRotationChange) { + this.onRotationChange(); + } + + if (this.eventRotationChange) { + this.eventDispatcher.dispatchEvent(this.eventRotationChange); + } + } + + destroy(): void { + if (this.parent && this.parent.object3D) { + this.parent.object3D.removeChild(this.object3D); + this.scene3D = null; + this.localPosition = null; + this.localRotQuat = null; + this.localRotation = null; + this.localScale = null; + this._worldMatrix = null; + } + super.destroy(); + } + + + public decomposeFromMatrix(matrix: Matrix4, orientationStyle: string = 'eulerAngles'): this { + let prs = matrix.decompose(orientationStyle); + + let transform = this.transform; + + transform.localRotQuat.copyFrom(prs[1]); + transform.localRotQuat = transform.localRotQuat; + + transform.localPosition.copyFrom(prs[0]); + transform.localPosition = transform.localPosition; + + transform.localScale.copyFrom(prs[2]); + transform.localScale = transform.localScale; + this.updateWorldMatrix(); + return this; + } + + /** + * + * Create a new component, copy the properties of the current component, and add it to the target object. + * @param obj source Object3D + */ + cloneTo(obj: Object3D) { + obj.transform.localPosition.copyFrom(this.localPosition); + obj.transform.localRotation.copyFrom(this.localRotation); + obj.transform.localScale.copyFrom(this.localScale); + } + + public set x(value: number) { + if (this._localPos.x != value) { + this._localPos.x = value; + this.notifyLocalChange(); + + if (this.onPositionChange) { + this.onPositionChange(); + } + + if (this.eventPositionChange) { + this.eventDispatcher.dispatchEvent(this.eventPositionChange); + } + } + } + /** + * + * The position of the object relative to its parent X-axis + */ + public get x(): number { + return this._localPos.x; + } + + public set y(value: number) { + if (this._localPos.y != value) { + this._localPos.y = value; + this.notifyLocalChange(); + + if (this.onPositionChange) { + this.onPositionChange(); + } + + if (this.eventPositionChange) { + this.eventDispatcher.dispatchEvent(this.eventPositionChange); + } + } + } + /** + * + * The position of the object relative to its parent Y-axis + */ + public get y(): number { + return this._localPos.y; + } + + public set z(value: number) { + if (this._localPos.z != value) { + this._localPos.z = value; + this.notifyLocalChange(); + + if (this.onPositionChange) { + this.onPositionChange(); + } + + if (this.eventPositionChange) { + this.eventDispatcher.dispatchEvent(this.eventPositionChange); + } + } + } + /** + * + * The position of the object relative to its parent Y-axis + */ + public get z(): number { + return this._localPos.z; + } + + public set scaleX(value: number) { + if (this._localScale.x != value) { + this._localScale.x = value; + this.notifyLocalChange(); + + if (this.eventScaleChange) { + this.eventDispatcher.dispatchEvent(this.eventScaleChange); + } + } + } + /** + * + * The scale of the object relative to its parent X-axis + */ + public get scaleX(): number { + return this._localScale.x; + } + + public set scaleY(value: number) { + if (this._localScale.y != value) { + this._localScale.y = value; + this.notifyLocalChange(); + + if (this.eventScaleChange) { + this.eventDispatcher.dispatchEvent(this.eventScaleChange); + } + } + } + /** + * + * The scale of the object relative to its parent Y-axis + */ + public get scaleY(): number { + return this._localScale.y; + } + + public set scaleZ(value: number) { + if (this._localScale.z != value) { + this._localScale.z = value; + this.notifyLocalChange(); + + if (this.eventScaleChange) { + this.eventDispatcher.dispatchEvent(this.eventScaleChange); + } + } + } + + /** + * + * The scale of the object relative to its parent Z-axis + */ + public get scaleZ(): number { + return this._localScale.z; + } + + public set rotationX(value: number) { + if (this._localRot.x != value) { + this._localRot.x = value; + this.notifyLocalChange(); + + if (this.onRotationChange) { + this.onRotationChange(); + } + + if (this.eventRotationChange) { + this.eventDispatcher.dispatchEvent(this.eventRotationChange); + } + } + } + /** + * + * The rotation of the object relative to its parent X-axis + */ + public get rotationX(): number { + return this._localRot.x; + } + + public set rotationY(value: number) { + if (this._localRot.y != value) { + this._localRot.y = value; + this.notifyLocalChange(); + + if (this.onRotationChange) { + this.onRotationChange(); + } + + if (this.eventRotationChange) { + this.eventDispatcher.dispatchEvent(this.eventRotationChange); + } + } + } + /** + * + * The rotation of the object relative to its parent Y-axis + */ + public get rotationY(): number { + return this._localRot.y; + } + + public set rotationZ(value: number) { + if (this._localRot.z != value) { + this._localRot.z = value; + this.notifyLocalChange(); + + if (this.onRotationChange) { + this.onRotationChange(); + } + + if (this.eventRotationChange) { + this.eventDispatcher.dispatchEvent(this.eventRotationChange); + } + } + } + /** + * + * The rotation of the object relative to its parent Z-axis + */ + public get rotationZ(): number { + return this._localRot.z; + } + /** + * + * world position + */ + public get worldPosition(): Vector3 { + if (this._localChange) { + this.updateWorldMatrix(); + } + return this._worldMatrix.position; + } + + public set localPosition(v: Vector3) { + this.x = v.x; + this.y = v.y; + this.z = v.z; + this.notifyLocalChange(); + + if (this.onPositionChange) { + this.onPositionChange(); + } + + if (this.eventPositionChange) { + this.eventDispatcher.dispatchEvent(this.eventPositionChange); + } + } + /** + * + * The position of an object relative to its parent + */ + public get localPosition(): Vector3 { + return this._localPos; + } + + public set localRotation(v: Vector3) { + this.rotationX = v.x; + this.rotationY = v.y; + this.rotationZ = v.z; + this.notifyLocalChange(); + + if (this.onRotationChange) { + this.onRotationChange(); + } + + if (this.eventRotationChange) { + this.eventDispatcher.dispatchEvent(this.eventRotationChange); + } + } + + /** + * + * The rotaion vector of an object relative to its parent + */ + public get localRotation(): Vector3 { + return this._localRot; + } + + public set localScale(v: Vector3) { + this.scaleX = v.x; + this.scaleY = v.y; + this.scaleZ = v.z; + this.notifyLocalChange(); + + if (this.eventScaleChange) { + this.eventDispatcher.dispatchEvent(this.eventScaleChange); + } + } + /** + * + * The scale of an object relative to its parent + */ + public get localScale(): Vector3 { + return this._localScale; + } + + // private _rotateAroundAxisX:number = 0 ; + // public set rotateAroundAxisX(value:number){ + // this._rotateAroundAxisX = value; + // this.notifyLocalChange(); + + // let quat = Quaternion.HELP_0 ; + // quat.fromAxisAngle(this.right,value); + // // quat.fromEulerAngles(this._rotateAroundAxisX,this._rotateAroundAxisY,this._rotateAroundAxisZ); + // quat.toEulerAngles(this._localRot); + + // this.notifyLocalChange(); + // if( this.onRotationChange ){ + // this.onRotationChange(); + // } + + // if (this.events_rot) { + // this.events.dispatchEvent(this.events_rot); + // } + // } + + // /** + // * + // */ + // public get rotateAroundAxisX():number { + // return this._rotateAroundAxisX ; + // } + + // private _rotateAroundAxisY:number = 0 ; + // public set rotateAroundAxisY(value:number){ + // this._rotateAroundAxisY = value; + // this.notifyLocalChange(); + + // let quat = Quaternion.HELP_0 ; + // quat.fromEulerAngles(this._rotateAroundAxisX,this._rotateAroundAxisY,this._rotateAroundAxisZ); + // quat.toEulerAngles(this._localRot); + + // this.notifyLocalChange(); + // if( this.onRotationChange ){ + // this.onRotationChange(); + // } + + // if (this.events_rot) { + // this.events.dispatchEvent(this.events_rot); + // } + // } + + // /** + // * + // */ + // public get rotateAroundAxisY():number { + // return this._rotateAroundAxisY ; + // } + + // private _rotateAroundAxisZ:number = 0 ; + // public set rotateAroundAxisZ(value:number){ + // this._rotateAroundAxisZ = value; + // this.notifyLocalChange(); + + // let quat = Quaternion.HELP_0 ; + // quat.fromEulerAngles(this._rotateAroundAxisX,this._rotateAroundAxisY,this._rotateAroundAxisZ); + // quat.toEulerAngles(this._localRot); + + // this.notifyLocalChange(); + // if( this.onRotationChange ){ + // this.onRotationChange(); + // } + + // if (this.events_rot) { + // this.events.dispatchEvent(this.events_rot); + // } + // } + + // /** + // * + // */ + // public get rotateAroundAxisZ():number { + // return this._rotateAroundAxisZ ; + // } + +} diff --git a/src/engine/components/post/PostProcessingComponent.ts b/src/engine/components/post/PostProcessingComponent.ts new file mode 100644 index 00000000..c03500be --- /dev/null +++ b/src/engine/components/post/PostProcessingComponent.ts @@ -0,0 +1,68 @@ +import { Ctor, Engine3D } from "../../.."; +import { PostBase } from "../../gfx/renderJob/post/PostBase"; +import { ComponentBase } from "../ComponentBase"; + +export class PostProcessingComponent extends ComponentBase { + private _postList: Map; + protected init(param?: any): void { + this._postList = new Map(); + } + + protected start(): void { + + } + + protected stop(): void { + + } + + protected onEnable(): void { + this.activePost(); + } + + protected onDisable(): void { + this.unActivePost(); + } + + private activePost() { + let view = this.transform.view3D; + let job = Engine3D.getRenderJob(view); + this._postList.forEach((v) => { + job.addPost(v); + }); + } + + private unActivePost() { + let view = this.transform.view3D; + let job = Engine3D.getRenderJob(view); + this._postList.forEach((v) => { + job.removePost(v); + }); + } + + public addPost(c: Ctor): T { + if (this._postList.has(c.prototype)) return; + let post = new c(); + this._postList.set(c.prototype, post); + if (this._enable) + this.activePost(); + // post.onAttach(this.transform.view3D); + // Engine3D.getRenderJob(this.transform.view3D).addPost(post); + return post; + } + + public removePost(c: Ctor) { + if (!this._postList.has(c.prototype)) return; + let post = this._postList.get(c.prototype); + this._postList.delete(c.prototype); + + let view = this.transform.view3D; + let job = Engine3D.getRenderJob(view); + job.removePost(post); + } + + public getPost(c: Ctor): T { + if (!this._postList.has(c.prototype)) return null; + return this._postList.get(c.prototype) as T; + } +} \ No newline at end of file diff --git a/src/engine/components/renderer/GlobalIlluminationComponent.ts b/src/engine/components/renderer/GlobalIlluminationComponent.ts new file mode 100644 index 00000000..1291d60c --- /dev/null +++ b/src/engine/components/renderer/GlobalIlluminationComponent.ts @@ -0,0 +1,171 @@ +import { Engine3D } from '../../Engine3D'; +import { GlobalBindGroup } from '../../gfx/graphics/webGpu/core/bindGroups/GlobalBindGroup'; +import { EntityCollect } from '../../gfx/renderJob/collect/EntityCollect'; +import { DDGIIrradianceVolume } from '../../gfx/renderJob/passRenderer/ddgi/DDGIIrradianceVolume'; +import { Probe } from '../../gfx/renderJob/passRenderer/ddgi/Probe'; +import { Vector3 } from '../../math/Vector3'; +import { SphereGeometry } from '../../shape/SphereGeometry'; +import { ComponentBase } from '../ComponentBase'; +import { MeshRenderer } from './MeshRenderer'; +import { GIProbeMaterial, GIProbeMaterialType } from '../../materials/GIProbeMaterial'; +import { UnLitMaterial } from '../../materials/UnLitMaterial'; +import { Quaternion } from '../../math/Quaternion'; +import { ComponentType } from '../../util/SerializeDefine'; + +/** + * + * Global Illumination Component + * Use global illumination to achieve more realistic lighting. + * The global illumination system can model the way light reflects or refracts on the surface to other surfaces (indirect lighting), + * rather than limiting that light can only shine from the light source to a certain surface. + * @group Components + */ +export class GlobalIlluminationComponent extends ComponentBase { + public probes: Probe[]; + public volume: DDGIIrradianceVolume; + + private _debugMr: MeshRenderer[] = []; + protected init(): void { + Engine3D.setting.gi.enable = true; + } + + constructor() { + super(); + this.componentType = ComponentType.globalIllumination; + } + + protected start(): void { + this.volume = GlobalBindGroup.getLightEntries(this.transform.scene3D).irradianceVolume; + this.initProbe(); + } + + private initProbe() { + let xCount: number = this.volume.setting.probeXCount; + let yCount: number = this.volume.setting.probeYCount; + let zCount: number = this.volume.setting.probeZCount; + + + let debugGeo = new SphereGeometry(4, 16, 16); + let position: Vector3 = new Vector3(); + this.probes = []; + let unlitMat = new UnLitMaterial(); + for (let x = 0; x < xCount; x++) { + for (let y = 0; y < yCount; y++) { + for (let z = 0; z < zCount; z++) { + let index = x + z * xCount + y * (xCount * zCount); + let probe = new Probe(); + probe.index = index; + + probe.name = `${x}_${y}_${z}`; + let mr = probe.addComponent(MeshRenderer); + mr.material = new GIProbeMaterial(GIProbeMaterialType.CastGI, index); + // mr.material = new GIProbeMaterial(GIProbeMaterialType.CastDepth, index); + // mr.material = unlitMat; + mr.geometry = debugGeo; + mr.castGI = false; + mr.castShadow = false; + + this._debugMr.push(mr); + + this.object3D.addChild(probe); + + this.volume.calcPosition(x, y, z, position); + + probe.x = position.x; + probe.y = position.y; + probe.z = position.z; + + probe.transform.rotationX = 0; + probe.transform.rotationY = 0; + probe.transform.rotationZ = 0; + + this.probes[index] = probe; + + this._debugMr.push(mr); + } + } + } + + for (let i = 0; i < this.probes.length; i++) { + EntityCollect.instance.addGIProbe(this.transform.scene3D, this.probes[i]); + } + + this.object3D.transform.enable = false; + + if (this.volume.setting.debug) { + this.debug(); + } + } + + public debug() { + + } + + private _debugProbeRay(probeIndex: number, array: Float32Array) { + const rayNumber = Engine3D.setting.gi.rayNumber; + let quat = new Quaternion(0.0, -0.7071067811865475, 0.7071067811865475, 0.0); + for (let i = 0; i < rayNumber; i++) { + let ii = probeIndex * rayNumber + i; + let dir = new Vector3( + -array[ii * 4 + 0], + -array[ii * 4 + 1], + -array[ii * 4 + 2], + 0 + ); + quat.transformVector(dir, dir); + let len = array[ii * 4 + 3]; + let id = `showRays${probeIndex}${i}`; + + let start = this.probes[probeIndex].transform.worldPosition.clone(); + let end = dir.scaleBy(len); + end.add(start, end); + + //view.graphic3D.Clear(id); + //view.graphic3D..drawLines(id, [start, end], [new Color(0, 0, 0, 0), new Color(1.0, 1.0, 1.0, 1.0)]); + + } + } + + private changeProbesVolumeData(): void { + this.volume.setVolumeDataChange(); + } + + private changeProbesPosition(): void { + this.volume.setVolumeDataChange(); + let xCount: number = this.volume.setting.probeXCount; + let yCount: number = this.volume.setting.probeYCount; + let zCount: number = this.volume.setting.probeZCount; + let position: Vector3 = new Vector3(); + + for (let x = 0; x < xCount; x++) { + for (let y = 0; y < yCount; y++) { + for (let z = 0; z < zCount; z++) { + let index = x + z * xCount + y * (xCount * zCount); + let probe: Probe = this.probes[index]; + this.volume.calcPosition(x, y, z, position); + + probe.x = position.x; + probe.y = position.y; + probe.z = position.z; + } + } + } + } + + protected onUpdate(): void { + Engine3D.setting.gi.maxDistance = Engine3D.setting.gi.probeSpace * 1.5; + + let camera = this.transform.scene3D.view.camera; + let scale = Vector3.distance(camera.transform.worldPosition, camera.transform.targetPos) / 300; + // console.log(scale); + + if (this._debugMr && this._debugMr.length > 0) { + for (let i = 0; i < this._debugMr.length; i++) { + const debugOBJ = this._debugMr[i].transform; + debugOBJ.scaleX = scale; + debugOBJ.scaleY = scale; + debugOBJ.scaleZ = scale; + } + } + } +} diff --git a/src/engine/components/renderer/InstanceDrawComponent.ts b/src/engine/components/renderer/InstanceDrawComponent.ts new file mode 100644 index 00000000..62d549a5 --- /dev/null +++ b/src/engine/components/renderer/InstanceDrawComponent.ts @@ -0,0 +1,124 @@ +import { GPUContext } from "../../gfx/renderJob/GPUContext"; +import { RTResourceMap } from "../../gfx/renderJob/frame/RTResourceMap"; +import { ClusterLightingRender } from "../../gfx/renderJob/passRenderer/cluster/ClusterLightingRender"; +import { RenderContext } from "../../gfx/renderJob/passRenderer/RenderContext"; +import { RendererPassState } from "../../gfx/renderJob/passRenderer/state/RendererPassState"; +import { RendererType } from "../../gfx/renderJob/passRenderer/state/RendererType"; +import { MeshRenderer } from "./MeshRenderer"; +import { RenderNode } from "./RenderNode"; +import { StorageGPUBuffer } from "../../gfx/graphics/webGpu/core/buffer/StorageGPUBuffer"; +import { View3D } from "../../core/View3D"; + +export class InstanceDrawComponent extends RenderNode { + + private _keyGroup: Map; + private _instanceMatrixBuffer: StorageGPUBuffer; + protected init(param?: any): void { + this._keyGroup = new Map(); + } + + protected start(): void { + + let meshRenders: MeshRenderer[] = []; + this.object3D.getComponents(MeshRenderer, meshRenders, true); + this._instanceMatrixBuffer = new StorageGPUBuffer(meshRenders.length); + this._instanceMatrixBuffer.visibility = GPUShaderStage.VERTEX; + let idArray = new Int32Array(meshRenders.length); + for (let i = 0; i < meshRenders.length; i++) { + const mr = meshRenders[i]; + mr.transform.enable = false; + + idArray[i] = mr.transform.worldMatrix.index; + let key = mr.geometry.uuid; + for (let j = 0; j < mr.materials.length; j++) { + const mat = mr.materials[j]; + key += mat.instanceID; + } + + if (!this._keyGroup.has(key)) { + this._keyGroup.set(key, [mr]); + } else { + this._keyGroup.get(key).push(mr); + } + } + + this.instanceCount = meshRenders.length; + this._instanceMatrixBuffer.setInt32Array("matrixIDs", idArray); + this._instanceMatrixBuffer.apply(); + } + + protected stop(): void { + + } + + public nodeUpdate(view: View3D, passType: RendererType, renderPassState: RendererPassState, clusterLightingRender?: ClusterLightingRender): void { + + + this._keyGroup.forEach((v, k) => { + let renderNode = v[0]; + for (let i = 0; i < renderNode.materials.length; i++) { + let material = renderNode.materials[i]; + let passes = material.renderPasses.get(passType); + if (passes) { + for (let i = 0; i < passes.length; i++) { + const renderShader = passes[i].renderShader;// RenderShader.getShader(passes[i].shaderID); + + renderShader.setDefine("USE_INSTANCEDRAW", true); + renderShader.setStorageBuffer(`instanceDrawID`, this._instanceMatrixBuffer); + } + } + } + renderNode.nodeUpdate(view, passType, renderPassState, clusterLightingRender); + }) + super.nodeUpdate(view, passType, renderPassState, clusterLightingRender); + } + + + public renderPass(view: View3D, passType: RendererType, renderEncoder: RenderContext) { + this._keyGroup.forEach((v, k) => { + let renderNode = v[0]; + // for (let ii = 0; ii < v.length; ii++) { + // const renderNode = v[ii]; + // renderNode.object3D.transform.updateWorldMatrix() + // } + for (let i = 0; i < renderNode.materials.length; i++) { + const material = renderNode.materials[i]; + let passes = material.renderPasses.get(passType); + + if (!passes || passes.length == 0) continue; + + GPUContext.bindGeometryBuffer(renderEncoder.encoder, renderNode.geometry); + let worldMatrix = renderNode.object3D.transform._worldMatrix; + for (let j = 0; j < passes.length; j++) { + if (!passes || passes.length == 0) continue; + let matPass = passes[j]; + if (!matPass.enable) continue; + + for (let jj = passes.length > 1 ? 1 : 0; jj < passes.length; jj++) { + const renderShader = matPass.renderShader; + if (renderShader.shaderState.splitTexture) { + + renderEncoder.endRenderPass(); + RTResourceMap.WriteSplitColorTexture(renderNode.instanceID); + renderEncoder.beginRenderPass(); + + GPUContext.bindCamera(renderEncoder.encoder, view.camera); + GPUContext.bindGeometryBuffer(renderEncoder.encoder, renderNode.geometry); + } + GPUContext.bindPipeline(renderEncoder.encoder, renderShader); + let subGeometries = renderNode.geometry.subGeometries; + for (let k = 0; k < subGeometries.length; k++) { + const subGeometry = subGeometries[k]; + let lodInfos = subGeometry.lodLevels; + let lodInfo = lodInfos[renderNode.lodLevel]; + + GPUContext.drawIndexed(renderEncoder.encoder, lodInfo.indexCount, v.length, lodInfo.indexStart, 0, 0); + } + } + } + } + }) + + } +} + diff --git a/src/engine/components/renderer/MaterialComponent.ts b/src/engine/components/renderer/MaterialComponent.ts new file mode 100644 index 00000000..9c6d672e --- /dev/null +++ b/src/engine/components/renderer/MaterialComponent.ts @@ -0,0 +1,43 @@ +import { Object3D } from '../../core/entities/Object3D'; +import { MaterialBase } from '../../materials/MaterialBase'; +import { ComponentBase } from '../ComponentBase'; +import { MeshComponent } from './MeshComponent'; +import { RenderNode } from './RenderNode'; +/** + * Material component + * @group Components + */ +export class MaterialComponent extends ComponentBase { + private _materials: MaterialBase[]; + constructor() { + super(); + this.materials = []; + } + + public get materials(): MaterialBase[] { + return this._materials; + } + + public set materials(value: MaterialBase[]) { + this._materials = value; + } + + public get material(): MaterialBase { + return this._materials[0]; + } + + public set material(value: MaterialBase) { + this._materials[0] = value; + + let mc = this.object3D.getComponent(MeshComponent); + if (mc && value && mc.geometry) { + let render = this.object3D.addComponent(RenderNode); + // render.initRender(); + } + } + + public cloneTo(obj: Object3D) { + let mc = obj.addComponent(MaterialComponent); + mc.materials.push(...this.materials); + } +} diff --git a/src/engine/components/renderer/MeshComponent.ts b/src/engine/components/renderer/MeshComponent.ts new file mode 100644 index 00000000..79bc30f0 --- /dev/null +++ b/src/engine/components/renderer/MeshComponent.ts @@ -0,0 +1,49 @@ +import { Object3D } from '../../core/entities/Object3D'; +import { GeometryBase } from '../../core/geometry/GeometryBase'; +import { ComponentBase } from '../ComponentBase'; +import { MaterialComponent } from './MaterialComponent'; +import { RenderNode } from './RenderNode'; +/** + * Mesh component + * @group Components + */ +export class MeshComponent extends ComponentBase { + private _geometry: GeometryBase; + public get geometry(): GeometryBase { + return this._geometry; + } + public set geometry(value: GeometryBase) { + this._geometry = value; + this._checkRenderer(); + } + + protected _checkRenderer() { + let mr = this.object3D.getComponent(MaterialComponent); + if (mr && this._geometry && mr.materials.length > 0) { + this.checkRenderer(); + } + } + + protected checkRenderer() { + let render = this.object3D.addComponent(RenderNode); + // render.initRender(); + } + + constructor() { + super(); + } + + protected init() { } + + public cloneTo(obj: Object3D) { + let mc = obj.addComponent(MeshComponent); + mc._geometry = this._geometry; + } + + // public get mesh(): BufferMesh { + // return this._mesh; + // } + // public set mesh(value: BufferMesh) { + // this._mesh = value; + // } +} diff --git a/src/engine/components/renderer/MeshRenderer.ts b/src/engine/components/renderer/MeshRenderer.ts new file mode 100644 index 00000000..bd14c652 --- /dev/null +++ b/src/engine/components/renderer/MeshRenderer.ts @@ -0,0 +1,138 @@ +import { Object3D } from '../../core/entities/Object3D'; +import { GeometryBase } from '../../core/geometry/GeometryBase'; +import { View3D } from '../../core/View3D'; +import { ClusterLightingRender } from '../../gfx/renderJob/passRenderer/cluster/ClusterLightingRender'; +import { RendererMask } from '../../gfx/renderJob/passRenderer/state/RendererMask'; +import { RendererPassState } from '../../gfx/renderJob/passRenderer/state/RendererPassState'; +import { RendererType } from '../../gfx/renderJob/passRenderer/state/RendererType'; +import { MaterialBase } from '../../materials/MaterialBase'; +import { ComponentType } from '../../util/SerializeDefine'; +import { MorphTargetData } from '../anim/morphAnim/MorphTargetData'; +import { RenderNode } from './RenderNode'; + +/** + * The mesh renderer component is a component used to render the mesh + * @group Components + */ +export class MeshRenderer extends RenderNode { + /** + * Enabling this option allows the grid to display any shadows cast on the grid. + */ + public receiveShadow: boolean; + protected morphData: MorphTargetData; + + constructor() { + super(); + this.componentType = ComponentType.meshRenderer; + } + + protected start(): void { } + + protected stop(): void { } + + /** + * The geometry of the mesh determines its shape + */ + public get geometry(): GeometryBase { + return this._geometry; + } + + public set geometry(value: GeometryBase) { + this._geometry = value; + let isMorphTarget = value.morphTargetDictionary != null; + if (isMorphTarget) { + this.morphData ||= new MorphTargetData(); + this.morphData.morphTargetsRelative = value.morphTargetsRelative; + this.morphData.initMorphTarget(value); + } + this.morphData && (this.morphData.enable = isMorphTarget); + if (this.morphData && this.morphData.enable) { + this.addRendererMask(RendererMask.MorphTarget); + } else { + this.removeRendererMask(RendererMask.MorphTarget); + } + + if (this._readyPipeline) { + this.initPipeline(); + } + } + + /** + * material + */ + public get material(): MaterialBase { + return this._materials[0]; + } + + public set material(value: MaterialBase) { + this.materials = [value]; + } + + /** + * Set deformation animation parameters + */ + public setMorphInfluence(key: string, value: number) { + if (this.morphData && this.morphData.enable) { + let index = this._geometry.morphTargetDictionary[key]; + if (index >= 0) { + this.morphData.updateInfluence(index, value); + } + } + } + + public setMorphInfluenceIndex(index: number, value: number) { + if (this.morphData && this.morphData.enable) { + if (index >= 0) { + this.morphData.updateInfluence(index, value); + } + } + } + + + protected onCompute(view: View3D, command: GPUCommandEncoder): void { + if (this.morphData && this.morphData.enable) { + this.morphData.computeMorphTarget(command); + } + } + + /** + * @internal + * @param passType + * @param renderPassState + * @param scene3D + * @param clusterLightingRender + * @param probes + */ + public nodeUpdate(view: View3D, passType: RendererType, renderPassState: RendererPassState, clusterLightingRender?: ClusterLightingRender) { + if (this.morphData && this.morphData.enable) { + for (let i = 0; i < this.materials.length; i++) { + const material = this.materials[i]; + let passes = material.renderPasses.get(passType); + if (passes) { + for (let j = 0; j < passes.length; j++) { + const renderShader = passes[j].renderShader; + this.morphData.applyRenderShader(renderShader); + } + } + } + } + + super.nodeUpdate(view, passType, renderPassState, clusterLightingRender); + } + + cloneTo(obj: Object3D) { + let mr = obj.addComponent(MeshRenderer); + mr.geometry = this.geometry; + mr.material = this.material; + mr.castShadow = this.castShadow; + mr.castGI = this.castGI; + mr.receiveShadow = this.receiveShadow; + mr.rendererMask = this.rendererMask; + } + + drawWireFrame() { + this.object3D.transform.worldPosition; + //view.graphic3D..drawMeshWireframe(`Wireframe_${this.object3D.uuid}`, this.geometry, this.object3D.transform); + } + +} diff --git a/src/engine/components/renderer/RenderNode.ts b/src/engine/components/renderer/RenderNode.ts new file mode 100644 index 00000000..f7e66737 --- /dev/null +++ b/src/engine/components/renderer/RenderNode.ts @@ -0,0 +1,447 @@ +import { GeometryBase } from '../../core/geometry/GeometryBase'; +import { PassGenerate } from '../../gfx/generate/PassGenerate'; +import { ShaderReflection } from '../../gfx/graphics/webGpu/shader/value/ShaderReflectionInfo'; +import { EntityCollect } from '../../gfx/renderJob/collect/EntityCollect'; +import { GPUContext } from '../../gfx/renderJob/GPUContext'; +import { ClusterLightingRender } from '../../gfx/renderJob/passRenderer/cluster/ClusterLightingRender'; +import { RendererType } from '../../gfx/renderJob/passRenderer/state/RendererType'; +import { RendererMaskUtil, RendererMask } from '../../gfx/renderJob/passRenderer/state/RendererMask'; +import { RendererPassState } from '../../gfx/renderJob/passRenderer/state/RendererPassState'; +import { MaterialBase } from '../../materials/MaterialBase'; +import { ComponentBase } from '../ComponentBase'; +import { RenderContext } from '../../gfx/renderJob/passRenderer/RenderContext'; +import { Engine3D } from '../../Engine3D'; +import { View3D } from '../../core/View3D'; +import { GlobalBindGroup } from '../../gfx/graphics/webGpu/core/bindGroups/GlobalBindGroup'; +import { RenderShader } from '../../gfx/graphics/webGpu/shader/RenderShader'; +import { RTResourceMap } from '../../gfx/renderJob/frame/RTResourceMap'; +import { UUID } from '../../util/Global'; +import { ComponentType } from '../../util/SerializeDefine'; +import { IESProfiles } from '../lights/IESProfiles'; + +/** + * @internal + * @group Components + */ +export class RenderNode extends ComponentBase { + public instanceCount: number = 0; + public lodLevel: number = 0; + public alwaysRender: boolean = false; + public renderOrder: number = 0; + public instanceID: string; + public drawType: number = 0; + + protected _geometry: GeometryBase; + protected _materials: MaterialBase[] = []; + protected _castShadow: boolean = true; + protected _castReflection: boolean = false; + protected _castGI: boolean = false; + protected _rendererMask: number = RendererMask.Default; + protected _inRenderer: boolean = false; + protected _readyPipeline: boolean = false; + protected _combineShaderRefection: ShaderReflection; + protected _ignoreEnvMap?: boolean; + protected _ignorePrefilterMap?: boolean; + + constructor() { + super(); + this.componentType = ComponentType.renderNode; + this.rendererMask = RendererMask.Default; + } + + public get geometry(): GeometryBase { + return this._geometry; + } + + public set geometry(value: GeometryBase) { + this._geometry = value; + } + + public addMask(mask: RendererMask) { + this._rendererMask = RendererMaskUtil.addMask(this.rendererMask, mask) + } + + public removeMask(mask: RendererMask) { + this._rendererMask = RendererMaskUtil.removeMask(this.rendererMask, mask) + } + + public hasMask(mask: RendererMask): boolean { + return RendererMaskUtil.hasMask(this.rendererMask, mask) + } + + public get rendererMask(): number { + return this._rendererMask; + } + + public set rendererMask(value: number) { + this._rendererMask = value; + } + + public get materials(): MaterialBase[] { + return this._materials; + } + + public set materials(value: MaterialBase[]) { + this._materials = value; + let transparent = false; + let sort = 0; + for (let i = 0; i < value.length; i++) { + const element = value[i]; + if (element.transparent) { + transparent = true; + sort = sort > element.sort ? sort : element.sort; + } + } + this.renderOrder = transparent ? this.renderOrder : sort; + + if (!this._readyPipeline) { + this.initPipeline(); + } + } + + protected init() { + // this.renderPasses = new Map(); + this.instanceID = UUID(); + } + + public addRendererMask(tag: RendererMask) { + this._rendererMask = RendererMaskUtil.addMask(this._rendererMask, tag); + } + + public removeRendererMask(tag: RendererMask) { + this._rendererMask = RendererMaskUtil.removeMask(this._rendererMask, tag); + } + + protected onEnable(): void { + if (!this._readyPipeline) { + this.initPipeline(); + + + } + EntityCollect.instance.addRenderNode(this.transform.scene3D, this); + } + + + protected onDisable(): void { + EntityCollect.instance.removeRenderNode(this.transform.scene3D, this); + } + + public selfCloneMaterials(key: string): this { + let newMaterials = []; + for (let i = 0, c = this.materials.length; i < c; i++) { + const material = this.materials[i].clone(); + newMaterials.push(material); + } + this.materials = newMaterials; + + this._readyPipeline = false; + this.initPipeline(); + return this; + } + + protected initPipeline() { + if (this._geometry && this._materials.length > 0) { + for (let i = 0; i < this._materials.length; i++) { + const material = this._materials[i]; + let passList = material.addPass(RendererType.COLOR, material); + for (let i = 0; i < passList.length; i++) { + const pass = passList[i]; + let shader = RenderShader.getShader(pass.shaderID); + if (!shader.shaderReflection) { + shader.preCompile(this._geometry); + } + this._geometry.generate(shader.shaderReflection); + } + + this.object3D.bound = this._geometry.bounds; + } + this._readyPipeline = true; + + let transparent = false; + let sort = 0; + for (let i = 0; i < this.materials.length; i++) { + const element = this.materials[i]; + transparent = element.transparent; + if (element.transparent) { + sort = sort > element.sort ? sort : element.sort; + } else { + sort = Math.max(sort - 3000, 0); + } + this.castNeedPass(element.getShader()); + } + this.renderOrder = sort; + + if (this.enable && this.transform && this.transform.scene3D) { + EntityCollect.instance.addRenderNode(this.transform.scene3D, this); + } + } + } + + protected castNeedPass(shader: RenderShader) { + if (this.castGI) { + for (let i = 0; i < this.materials.length; i++) { + const mat = this.materials[i]; + PassGenerate.createGIPass(this, mat); + } + } + + if (this.castShadow) { + for (let i = 0; i < this.materials.length; i++) { + const mat = this.materials[i]; + if (mat.shaderState.castShadow) { + PassGenerate.createShadowPass(this, mat); + } + } + } + + if (this.castReflection) { + for (let i = 0; i < this.materials.length; i++) { + const mat = this.materials[i]; + if (mat.shaderState.castShadow) { + PassGenerate.createShadowPass(this, mat); + } + } + } + + + // add if alpha == 1 + let ignoreDepthPass = RendererMaskUtil.hasMask(this.rendererMask, RendererMask.IgnoreDepthPass); + if (!ignoreDepthPass && Engine3D.setting.render.zPrePass && shader.shaderState.useZ) { + for (let i = 0; i < this.materials.length; i++) { + const mat = this.materials[i]; + PassGenerate.createDepthPass(this, mat); + } + } else { + for (let i = 0; i < this.materials.length; i++) { + const mat = this.materials[i]; + mat.removePass(RendererType.DEPTH, 0); + } + } + } + + public get castShadow(): boolean { + return this._castShadow; + } + + public set castShadow(value: boolean) { + this._castShadow = value; + } + + public get castGI(): boolean { + return this._castGI; + } + + public set castGI(value: boolean) { + this._castGI = value; + } + + public get castReflection(): boolean { + return this._castReflection; + } + + public set castReflection(value: boolean) { + this._castReflection = value; + } + + + + public renderPass(view: View3D, passType: RendererType, renderContext: RenderContext) { + let renderNode = this; + for (let i = 0; i < renderNode.materials.length; i++) { + const material = renderNode.materials[i]; + let passes = material.renderPasses.get(passType); + + if (!passes || passes.length == 0) continue; + + GPUContext.bindGeometryBuffer(renderContext.encoder, renderNode._geometry); + let worldMatrix = renderNode.object3D.transform._worldMatrix; + for (let j = 0; j < passes.length; j++) { + if (!passes || passes.length == 0) continue; + let matPass = passes[j]; + if (!matPass.enable) continue; + + // for (let j = passes.length > 1 ? 1 : 0 ; j < passes.length; j++) { + const renderShader = matPass.renderShader; + if (renderShader.shaderState.splitTexture) { + + renderContext.endRenderPass(); + RTResourceMap.WriteSplitColorTexture(renderNode.instanceID); + renderContext.beginRenderPass(); + + GPUContext.bindCamera(renderContext.encoder, view.camera); + GPUContext.bindGeometryBuffer(renderContext.encoder, renderNode._geometry); + } + GPUContext.bindPipeline(renderContext.encoder, renderShader); + let subGeometries = renderNode._geometry.subGeometries; + for (let k = 0; k < subGeometries.length; k++) { + const subGeometry = subGeometries[k]; + let lodInfos = subGeometry.lodLevels; + let lodInfo = lodInfos[renderNode.lodLevel]; + + if (renderNode.instanceCount > 0) { + GPUContext.drawIndexed(renderContext.encoder, lodInfo.indexCount, renderNode.instanceCount, lodInfo.indexStart, 0, 0); + } else { + GPUContext.drawIndexed(renderContext.encoder, lodInfo.indexCount, 1, lodInfo.indexStart, 0, worldMatrix.index); + } + } + } + } + } + + /** + * render pass at passType + * @param pass + * @param encoder + * @returns + */ + public renderPass2(view: View3D, passType: RendererType, rendererPassState: RendererPassState, clusterLightingRender: ClusterLightingRender, encoder: GPURenderPassEncoder, useBundle: boolean = false) { + if (!this.enable) return; + this.nodeUpdate(view, passType, rendererPassState, clusterLightingRender); + + let node = this; + for (let i = 0; i < this.materials.length; i++) { + const material = this.materials[i]; + let passes = material.renderPasses.get(passType); + if (!passes || passes.length == 0) return; + let matPass = passes[i]; + if (!matPass.enable) continue; + + let worldMatrix = node.object3D.transform._worldMatrix; + if (this.drawType == 2) { + for (let i = 0; i < passes.length; i++) { + let renderShader = passes[i].renderShader; + GPUContext.bindPipeline(encoder, renderShader); + GPUContext.draw(encoder, 6, 1, 0, worldMatrix.index); + } + } else { + GPUContext.bindGeometryBuffer(encoder, node._geometry); + for (let i = 0; i < passes.length; i++) { + let renderShader = passes[i].renderShader; + + GPUContext.bindPipeline(encoder, renderShader); + let subGeometries = node._geometry.subGeometries; + for (let k = 0; k < subGeometries.length; k++) { + const subGeometry = subGeometries[k]; + let lodInfos = subGeometry.lodLevels; + let lodInfo = lodInfos[node.lodLevel]; + GPUContext.drawIndexed(encoder, lodInfo.indexCount, 1, lodInfo.indexStart, 0, worldMatrix.index); + } + } + } + + } + + + } + + public recordRenderPass2(view: View3D, passType: RendererType, rendererPassState: RendererPassState, clusterLightingRender: ClusterLightingRender, encoder: GPURenderPassEncoder, useBundle: boolean = false) { + if (!this.enable) return; + this.nodeUpdate(view, passType, rendererPassState, clusterLightingRender); + + let node = this; + for (let i = 0; i < this.materials.length; i++) { + let material = this.materials[i]; + + let passes = material.renderPasses.get(passType); + if (!passes || passes.length == 0) return; + + let worldMatrix = node.object3D.transform._worldMatrix; + for (let i = 0; i < passes.length; i++) { + const renderShader = passes[i].renderShader; + + GPUContext.bindPipeline(encoder, renderShader); + let subGeometries = node._geometry.subGeometries; + for (let k = 0; k < subGeometries.length; k++) { + const subGeometry = subGeometries[k]; + let lodInfos = subGeometry.lodLevels; + let lodInfo = lodInfos[node.lodLevel]; + GPUContext.drawIndexed(encoder, lodInfo.indexCount, 1, lodInfo.indexStart, 0, worldMatrix.index); + } + } + } + + } + + private noticeShaderChange() { + if (this.enable) { + this.onEnable(); + } + } + + public nodeUpdate(view: View3D, passType: RendererType, renderPassState: RendererPassState, clusterLightingRender?: ClusterLightingRender) { + + let node = this; + for (let i = 0; i < this.materials.length; i++) { + let material = this.materials[i]; + let passes = material.renderPasses.get(passType); + if (passes) { + for (let i = 0; i < passes.length; i++) { + const pass = passes[i];// RenderShader.getShader(passes[i].shaderID); + const renderShader = pass.renderShader;// RenderShader.getShader(passes[i].shaderID); + + if (renderShader.shaderState.splitTexture) { + let splitTexture = RTResourceMap.CreateSplitTexture(this.instanceID); + renderShader.setTexture("splitTexture_Map", splitTexture); + } + + // renderShader.setUniformVector3("center", this.transform.worldPosition); + + // if(scene3D.envMapChange){ + if (!this._ignoreEnvMap) { + renderShader.setTexture(`envMap`, view.scene.envMap); + } + if (!this._ignorePrefilterMap) { + renderShader.setTexture(`prefilterMap`, view.scene.envMap); + } + // } + + if (renderShader.pipeline) { + renderShader.apply(this._geometry, pass, renderPassState, () => this.noticeShaderChange()); + continue; + } + + let bdrflutTex = Engine3D.res.getTexture(`BRDFLUT`); + renderShader.setTexture(`brdflutMap`, bdrflutTex); + + if (Engine3D.getRenderJob(view).shadowMapPassRenderer.depth2DTextureArray) { + renderShader.setTexture(`shadowMap`, Engine3D.getRenderJob(view).shadowMapPassRenderer.depth2DTextureArray); + } + + // let shadowLight = ShadowLights.list; + // if (shadowLight.length) { + renderShader.setTexture(`pointShadowMap`, Engine3D.getRenderJob(view).pointLightShadowRenderer.cubeTextureArray); + // } + + let iesTexture = IESProfiles.iesTexture; + if (iesTexture) { + renderShader.setTexture(`iesTextureArrayMap`, iesTexture); + } + + if (renderPassState.irradianceBuffer && renderPassState.irradianceBuffer.length > 0) { + renderShader.setTexture(`irradianceMap`, renderPassState.irradianceBuffer[0]); + renderShader.setTexture(`irradianceDepthMap`, renderPassState.irradianceBuffer[1]); + } + + let lightUniformEntries = GlobalBindGroup.getLightEntries(view.scene); + if (lightUniformEntries) { + renderShader.setStorageBuffer(`lightBuffer`, lightUniformEntries.storageGPUBuffer); + if (lightUniformEntries.irradianceVolume) { + renderShader.setStructStorageBuffer(`irradianceData`, lightUniformEntries.irradianceVolume.irradianceVolumeBuffer); + } + } + + + if (clusterLightingRender) { + renderShader.setStorageBuffer(`clustersUniform`, clusterLightingRender.clustersUniformBuffer); + renderShader.setStorageBuffer(`lightAssignBuffer`, clusterLightingRender.lightAssignBuffer); + renderShader.setStorageBuffer(`assignTable`, clusterLightingRender.assignTableBuffer); + renderShader.setStorageBuffer(`clusterBuffer`, clusterLightingRender.clusterBuffer); + } + + renderShader.apply(this._geometry, pass, renderPassState); + } + } + } + } + +} diff --git a/src/engine/components/renderer/SkinnedMeshRenderer.ts b/src/engine/components/renderer/SkinnedMeshRenderer.ts new file mode 100644 index 00000000..ea77f59f --- /dev/null +++ b/src/engine/components/renderer/SkinnedMeshRenderer.ts @@ -0,0 +1,121 @@ +import { Object3D } from '../../core/entities/Object3D'; +import { ClusterLightingRender } from '../../gfx/renderJob/passRenderer/cluster/ClusterLightingRender'; +import { RendererType } from '../../gfx/renderJob/passRenderer/state/RendererType'; +import { RendererPassState } from '../../gfx/renderJob/passRenderer/state/RendererPassState'; +import { SkeletonAnimationComponent } from '../SkeletonAnimationComponent'; +import { MeshRenderer } from './MeshRenderer'; +import { RendererMask } from '../../gfx/renderJob/passRenderer/state/RendererMask'; +import { StorageGPUBuffer, View3D } from '../../..'; + +/** + * Skin Mesh Renderer Component + * Renders a deformable mesh. + * Deformable meshes include skin meshes (meshes with bones and bound poses), + * meshes with mixed shapes, and meshes running cloth simulations. + * @group Components + */ +export class SkinnedMeshRenderer extends MeshRenderer { + public skinJointsName: Array; + protected mInverseBindMatrixData: Array; + protected mInverseBindMatrixBuffer: StorageGPUBuffer; + protected mSkeletonAnimation: SkeletonAnimationComponent; + protected mJointIndexTableBuffer: StorageGPUBuffer; + + constructor() { + super(); + this.addRendererMask(RendererMask.SkinnedMesh); + } + + protected start() { + super.start(); + this.skeletonAnimation = this.object3D.getComponent(SkeletonAnimationComponent); + if (!this.skeletonAnimation) { + let comps = this.object3D.parentObject.parentObject.getComponentsInChild(SkeletonAnimationComponent); + if (comps.length > 0) { + this.skeletonAnimation = comps[0]; + } + if (!this.skeletonAnimation) { + this.skeletonAnimation = this.object3D.getComponentFromParent(SkeletonAnimationComponent); + } + } + } + + public get skeletonAnimation(): SkeletonAnimationComponent { + return this.mSkeletonAnimation; + } + + public set skeletonAnimation(value: SkeletonAnimationComponent) { + this.mSkeletonAnimation = value; + if (!value) { + return; + } + + if (!this.mJointIndexTableBuffer) { + let skinJointIndexData = this.mSkeletonAnimation.getJointIndexTable(this.skinJointsName); + this.mJointIndexTableBuffer = new StorageGPUBuffer(skinJointIndexData.length * 4, 0, new Float32Array(skinJointIndexData)); + this.mJointIndexTableBuffer.visibility = GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE; + } + } + + public get skinInverseBindMatrices(): Array { + return this.mInverseBindMatrixData; + } + + public set skinInverseBindMatrices(inverseBindMatrices: Array) { + this.mInverseBindMatrixData = inverseBindMatrices; + var inverseBindMatricesData = new Float32Array(inverseBindMatrices.length * 16); + for (let i = 0; i < inverseBindMatrices.length; i++) { + let index = i * 16; + let mat4x4 = inverseBindMatrices[i]; + inverseBindMatricesData.set(mat4x4, index); + } + this.mInverseBindMatrixBuffer = new StorageGPUBuffer(inverseBindMatricesData.byteLength, 0, inverseBindMatricesData); + this.mInverseBindMatrixBuffer.visibility = GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE; + } + + public get inverseBindMatrixBuffer(): StorageGPUBuffer { + return this.mInverseBindMatrixBuffer; + } + + public get jointIndexTableBuffer(): GPUBuffer { + return this.mJointIndexTableBuffer.buffer; + } + + public cloneTo(obj: Object3D) { + let skinnedMesh = obj.addComponent(SkinnedMeshRenderer); + skinnedMesh.geometry = this.geometry; + skinnedMesh.material = this.material.clone(); + skinnedMesh.castShadow = this.castShadow; + skinnedMesh.castGI = this.castGI; + skinnedMesh.receiveShadow = this.receiveShadow; + skinnedMesh.rendererMask = this.rendererMask; + skinnedMesh.skinJointsName = this.skinJointsName; + skinnedMesh.skinInverseBindMatrices = this.skinInverseBindMatrices; + skinnedMesh.mJointIndexTableBuffer = this.mJointIndexTableBuffer; + } + + /** + * @internal + * @param passType + * @param renderPassState + * @param scene3D + * @param clusterLightingRender + * @param probes + */ + public nodeUpdate(view: View3D, passType: RendererType, renderPassState: RendererPassState, clusterLightingRender?: ClusterLightingRender) { + for (let i = 0; i < this.materials.length; i++) { + const material = this.materials[i]; + let passes = material.renderPasses.get(passType); + if (passes) for (let i = 0; i < passes.length; i++) { + const renderShader = passes[i].renderShader; + if (!renderShader.pipeline) { + renderShader.setStorageBuffer('jointsMatrixIndexTable', this.mSkeletonAnimation.jointMatrixIndexTableBuffer); + renderShader.setStorageBuffer('jointsInverseMatrix', this.mInverseBindMatrixBuffer); + renderShader.setStorageBuffer('jointsIndexMapingTable', this.mJointIndexTableBuffer); + } + } + } + super.nodeUpdate(view, passType, renderPassState, clusterLightingRender); + } + +} diff --git a/src/engine/components/renderer/SkyRenderer.ts b/src/engine/components/renderer/SkyRenderer.ts new file mode 100644 index 00000000..41267069 --- /dev/null +++ b/src/engine/components/renderer/SkyRenderer.ts @@ -0,0 +1,105 @@ + +import { BoundingBox } from '../../core/bound/BoundingBox'; +import { Texture } from '../../gfx/graphics/webGpu/core/texture/Texture'; +import { EntityCollect } from '../../gfx/renderJob/collect/EntityCollect'; +import { SkyMaterial } from '../../materials/SkyMaterial'; +import { Vector3 } from '../../math/Vector3'; +import { MeshRenderer } from './MeshRenderer'; +import { RendererMask } from '../../gfx/renderJob/passRenderer/state/RendererMask'; +import { ClusterLightingRender } from '../../gfx/renderJob/passRenderer/cluster/ClusterLightingRender'; +import { RendererPassState } from '../../gfx/renderJob/passRenderer/state/RendererPassState'; +import { Engine3D } from '../../Engine3D'; +import { View3D } from '../../core/View3D'; +import { RendererType } from '../../gfx/renderJob/passRenderer/state/RendererType'; +import { SphereGeometry } from '../../shape/SphereGeometry'; +import { ComponentType } from '../../util/SerializeDefine'; +/** + * + * Sky Box Renderer Component + * @group Components + */ +export class SkyRenderer extends MeshRenderer { + /** + * The material used in the Sky Box. + */ + public skyMaterial: SkyMaterial; + + constructor() { + super(); + this.componentType = ComponentType.skyRenderer; + this.castShadow = false; + this.castGI = true; + this.addRendererMask(RendererMask.Sky); + this.alwaysRender = true; + } + + protected init(): void { + super.init(); + this.object3D.bound = new BoundingBox(Vector3.ZERO.clone(), Vector3.MAX); + this.geometry = new SphereGeometry(Engine3D.setting.sky.defaultFar, 20, 20); + this.skyMaterial ||= new SkyMaterial(); + } + + protected onEnable(): void { + if (!this._readyPipeline) { + this.initPipeline(); + } else { + this.castNeedPass(this.materials[0].getShader()); + + if (!this._inRenderer && this.transform.scene3D) { + EntityCollect.instance.sky = this; + this._inRenderer = true; + } + } + } + + protected onDisable(): void { + if (this._inRenderer && this.transform.scene3D) { + this._inRenderer = false; + EntityCollect.instance.sky = null; + } + } + + public renderPass2(view: View3D, passType: RendererType, rendererPassState: RendererPassState, clusterLightingRender: ClusterLightingRender, encoder: GPURenderPassEncoder, useBundle: boolean = false) { + this.transform.updateWorldMatrix(); + super.renderPass2(view, passType, rendererPassState, clusterLightingRender, encoder, useBundle); + // this.transform.localPosition = Camera3D.mainCamera.transform.localPosition ; + } + + /** + * set environment texture + */ + public set map(texture: Texture) { + this.skyMaterial.baseMap = texture; + if (this.skyMaterial.name == null) { + this.skyMaterial.name = 'skyMaterial'; + } + this.material = this.skyMaterial; + } + + /** + * get environment texture + */ + public get map(): Texture { + return this.skyMaterial.baseMap; + } + + public get exposure() { + return this.skyMaterial.exposure; + } + + public set exposure(value) { + if (this.skyMaterial) + this.skyMaterial.exposure = value; + } + + public get roughness() { + return this.skyMaterial.roughness; + } + + public set roughness(value) { + if (this.skyMaterial) + this.skyMaterial.roughness = value; + } + +}