From 4167e8383dafa1ee1176b543ebfe4e92013621f0 Mon Sep 17 00:00:00 2001 From: AZhan Date: Tue, 17 Dec 2024 10:52:32 +0800 Subject: [PATCH 01/20] Fix Transform e2e error (#2466) * fix: transform e2e error --- packages/core/src/Transform.ts | 6 +++--- packages/core/src/mesh/SkinnedMeshRenderer.ts | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/core/src/Transform.ts b/packages/core/src/Transform.ts index 981ba1f9ed..21325c8234 100644 --- a/packages/core/src/Transform.ts +++ b/packages/core/src/Transform.ts @@ -580,9 +580,9 @@ export class Transform extends Component { * @internal */ _copyFrom(transform: Transform): void { - this._position.copyFrom(transform._position); - this._rotation.copyFrom(transform._rotation); - this._scale.copyFrom(transform._scale); + this._position.copyFrom(transform.position); + this._rotation.copyFrom(transform.rotation); + this._scale.copyFrom(transform.scale); } protected override _onDestroy(): void { diff --git a/packages/core/src/mesh/SkinnedMeshRenderer.ts b/packages/core/src/mesh/SkinnedMeshRenderer.ts index 599f6f9ee3..75ba7ccaee 100644 --- a/packages/core/src/mesh/SkinnedMeshRenderer.ts +++ b/packages/core/src/mesh/SkinnedMeshRenderer.ts @@ -205,7 +205,12 @@ export class SkinnedMeshRenderer extends MeshRenderer { * @internal */ protected override _updateBounds(worldBounds: BoundingBox): void { - BoundingBox.transform(this._localBounds, this._transformEntity.transform.worldMatrix, worldBounds); + const rootBone = this.skin?.rootBone; + if (rootBone) { + BoundingBox.transform(this._localBounds, this._transformEntity.transform.worldMatrix, worldBounds); + } else { + super._updateBounds(worldBounds); + } } private _checkBlendShapeWeightLength(): void { From 2ec06dfa1f6481ed1bc11cbc3d1d5a1e11d5c9a5 Mon Sep 17 00:00:00 2001 From: zhuxudong Date: Tue, 17 Dec 2024 11:26:29 +0800 Subject: [PATCH 02/20] refactor: migrate shader-shaderlab from entine-toolkit (#2467) --- packages/shader-shaderlab/README.md | 1 + packages/shader-shaderlab/package.json | 36 ++ packages/shader-shaderlab/src/global.d.ts | 9 + packages/shader-shaderlab/src/index.ts | 28 ++ .../src/shaders/BlendShape.glsl | 105 +++++ .../shader-shaderlab/src/shaders/Common.glsl | 101 +++++ .../shader-shaderlab/src/shaders/Fog.glsl | 30 ++ .../shader-shaderlab/src/shaders/Light.glsl | 137 ++++++ .../shader-shaderlab/src/shaders/Normal.glsl | 37 ++ packages/shader-shaderlab/src/shaders/PBR.gs | 78 ++++ .../shader-shaderlab/src/shaders/Shadow.glsl | 179 ++++++++ .../src/shaders/ShadowSampleTent.glsl | 120 +++++ .../shader-shaderlab/src/shaders/Skin.glsl | 47 ++ .../src/shaders/Transform.glsl | 16 + .../shader-shaderlab/src/shaders/index.ts | 31 ++ .../src/shaders/shadingPBR/AttributesPBR.glsl | 76 ++++ .../src/shaders/shadingPBR/BRDF.glsl | 410 ++++++++++++++++++ .../shaders/shadingPBR/ForwardPassPBR.glsl | 110 +++++ .../src/shaders/shadingPBR/FragmentPBR.glsl | 315 ++++++++++++++ .../shaders/shadingPBR/LightDirectPBR.glsl | 162 +++++++ .../shadingPBR/LightIndirectFunctions.glsl | 51 +++ .../shaders/shadingPBR/LightIndirectPBR.glsl | 113 +++++ .../shaders/shadingPBR/ReflectionLobe.glsl | 35 ++ .../src/shaders/shadingPBR/VaryingsPBR.glsl | 37 ++ .../src/shaders/shadingPBR/VertexPBR.glsl | 102 +++++ .../src/shaders/shadingPBR/index.ts | 23 + packages/shader-shaderlab/tsconfig.json | 18 + pnpm-lock.yaml | 10 +- rollup.config.js | 3 +- 29 files changed, 2416 insertions(+), 4 deletions(-) create mode 100644 packages/shader-shaderlab/README.md create mode 100644 packages/shader-shaderlab/package.json create mode 100644 packages/shader-shaderlab/src/global.d.ts create mode 100644 packages/shader-shaderlab/src/index.ts create mode 100644 packages/shader-shaderlab/src/shaders/BlendShape.glsl create mode 100644 packages/shader-shaderlab/src/shaders/Common.glsl create mode 100644 packages/shader-shaderlab/src/shaders/Fog.glsl create mode 100644 packages/shader-shaderlab/src/shaders/Light.glsl create mode 100644 packages/shader-shaderlab/src/shaders/Normal.glsl create mode 100644 packages/shader-shaderlab/src/shaders/PBR.gs create mode 100644 packages/shader-shaderlab/src/shaders/Shadow.glsl create mode 100644 packages/shader-shaderlab/src/shaders/ShadowSampleTent.glsl create mode 100644 packages/shader-shaderlab/src/shaders/Skin.glsl create mode 100644 packages/shader-shaderlab/src/shaders/Transform.glsl create mode 100644 packages/shader-shaderlab/src/shaders/index.ts create mode 100644 packages/shader-shaderlab/src/shaders/shadingPBR/AttributesPBR.glsl create mode 100644 packages/shader-shaderlab/src/shaders/shadingPBR/BRDF.glsl create mode 100644 packages/shader-shaderlab/src/shaders/shadingPBR/ForwardPassPBR.glsl create mode 100644 packages/shader-shaderlab/src/shaders/shadingPBR/FragmentPBR.glsl create mode 100644 packages/shader-shaderlab/src/shaders/shadingPBR/LightDirectPBR.glsl create mode 100644 packages/shader-shaderlab/src/shaders/shadingPBR/LightIndirectFunctions.glsl create mode 100644 packages/shader-shaderlab/src/shaders/shadingPBR/LightIndirectPBR.glsl create mode 100644 packages/shader-shaderlab/src/shaders/shadingPBR/ReflectionLobe.glsl create mode 100644 packages/shader-shaderlab/src/shaders/shadingPBR/VaryingsPBR.glsl create mode 100644 packages/shader-shaderlab/src/shaders/shadingPBR/VertexPBR.glsl create mode 100644 packages/shader-shaderlab/src/shaders/shadingPBR/index.ts create mode 100644 packages/shader-shaderlab/tsconfig.json diff --git a/packages/shader-shaderlab/README.md b/packages/shader-shaderlab/README.md new file mode 100644 index 0000000000..0bbc88fe5f --- /dev/null +++ b/packages/shader-shaderlab/README.md @@ -0,0 +1 @@ +A subpackage of `@galacean/engine`. \ No newline at end of file diff --git a/packages/shader-shaderlab/package.json b/packages/shader-shaderlab/package.json new file mode 100644 index 0000000000..04a3e9060e --- /dev/null +++ b/packages/shader-shaderlab/package.json @@ -0,0 +1,36 @@ +{ + "name": "@galacean/engine-shader-shaderlab", + "version": "1.4.0-alpha.0", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "repository": { + "url": "https://github.com/galacean/engine.git" + }, + "license": "MIT", + "main": "dist/main.js", + "module": "dist/module.js", + "debug": "src/index.ts", + "browser": "dist/browser.js", + "types": "types/index.d.ts", + "scripts": { + "b:types": "tsc" + }, + "umd": { + "name": "Galacean.ShaderShaderLab", + "globals": { + "@galacean/engine": "Galacean" + } + }, + "files": [ + "dist/**/*", + "types/**/*" + ], + "devDependencies": { + "@galacean/engine": "workspace:*" + }, + "peerDependencies": { + "@galacean/engine": "workspace:*" + } +} diff --git a/packages/shader-shaderlab/src/global.d.ts b/packages/shader-shaderlab/src/global.d.ts new file mode 100644 index 0000000000..d0abf1234f --- /dev/null +++ b/packages/shader-shaderlab/src/global.d.ts @@ -0,0 +1,9 @@ +declare module "*.glsl" { + const value: string; + export default value; +} + +declare module "*.gs" { + const value: string; + export default value; +} diff --git a/packages/shader-shaderlab/src/index.ts b/packages/shader-shaderlab/src/index.ts new file mode 100644 index 0000000000..d144ddf44b --- /dev/null +++ b/packages/shader-shaderlab/src/index.ts @@ -0,0 +1,28 @@ +import { Shader, ShaderFactory } from "@galacean/engine"; +import { PBRSource, fragmentList } from "./shaders"; + +let includeRegistered = false; +let shaderRegistered = false; + +export function registerIncludes() { + if (includeRegistered) return; + + for (const sourceFragment of fragmentList) { + ShaderFactory.registerInclude(sourceFragment.includeKey, sourceFragment.source); + } + + includeRegistered = true; +} + +export function registerShader() { + if (shaderRegistered) return; + + Shader.create(PBRSource); + + shaderRegistered = true; +} + +/** + * @internal + */ +export { fragmentList }; diff --git a/packages/shader-shaderlab/src/shaders/BlendShape.glsl b/packages/shader-shaderlab/src/shaders/BlendShape.glsl new file mode 100644 index 0000000000..05a69f0249 --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/BlendShape.glsl @@ -0,0 +1,105 @@ +#ifndef BLENDSHAPE_INCLUDED +#define BLENDSHAPE_INCLUDED + +#ifdef RENDERER_HAS_BLENDSHAPE + #ifdef RENDERER_BLENDSHAPE_USE_TEXTURE + mediump sampler2DArray renderer_BlendShapeTexture; + ivec3 renderer_BlendShapeTextureInfo; + float renderer_BlendShapeWeights[RENDERER_BLENDSHAPE_COUNT]; + + vec3 getBlendShapeVertexElement(int blendShapeIndex, int vertexElementIndex){ + int y = vertexElementIndex / renderer_BlendShapeTextureInfo.y; + int x = vertexElementIndex - y * renderer_BlendShapeTextureInfo.y; + ivec3 uv = ivec3(x, y , blendShapeIndex); + return (texelFetch(renderer_BlendShapeTexture, uv, 0)).xyz; + } + #else + #if defined( RENDERER_BLENDSHAPE_HAS_NORMAL ) && defined( RENDERER_BLENDSHAPE_HAS_TANGENT ) + float renderer_BlendShapeWeights[2]; + #else + #if defined( RENDERER_BLENDSHAPE_HAS_NORMAL ) || defined( RENDERER_BLENDSHAPE_HAS_TANGENT ) + float renderer_BlendShapeWeights[4]; + #else + float renderer_BlendShapeWeights[8]; + #endif + #endif + #endif + + void calculateBlendShape(Attributes attributes, inout vec4 position + #ifdef RENDERER_HAS_NORMAL + ,inout vec3 normal + #ifdef RENDERER_HAS_TANGENT + ,inout vec4 tangent + #endif + #endif + + ){ + #ifdef RENDERER_BLENDSHAPE_USE_TEXTURE + int vertexOffset = gl_VertexID * renderer_BlendShapeTextureInfo.x; + for(int i = 0; i < RENDERER_BLENDSHAPE_COUNT; i++){ + int vertexElementOffset = vertexOffset; + float weight = renderer_BlendShapeWeights[i]; + // Warnning: Multiplying by 0 creates weird precision issues, causing rendering anomalies in Ace2 Android13 + if(weight != 0.0){ + position.xyz += getBlendShapeVertexElement(i, vertexElementOffset) * weight; + + #if defined( RENDERER_HAS_NORMAL ) && defined( RENDERER_BLENDSHAPE_HAS_NORMAL ) + vertexElementOffset += 1; + normal += getBlendShapeVertexElement(i, vertexElementOffset) * weight; + #endif + + #if defined( RENDERER_HAS_TANGENT ) && defined(RENDERER_BLENDSHAPE_HAS_TANGENT) + vertexElementOffset += 1; + tangent.xyz += getBlendShapeVertexElement(i, vertexElementOffset) * weight; + #endif + } + + } + #else + position.xyz += attributes.POSITION_BS0 * renderer_BlendShapeWeights[0]; + position.xyz += attributes.POSITION_BS1 * renderer_BlendShapeWeights[1]; + + #if defined( RENDERER_BLENDSHAPE_HAS_NORMAL ) && defined( RENDERER_BLENDSHAPE_HAS_TANGENT ) + #ifdef RENDERER_HAS_NORMAL + normal += attributes.NORMAL_BS0 * renderer_BlendShapeWeights[0]; + normal += attributes.NORMAL_BS1 * renderer_BlendShapeWeights[1]; + #endif + + #ifdef RENDERER_HAS_TANGENT + tangent.xyz += attributes.TANGENT_BS0 * renderer_BlendShapeWeights[0]; + tangent.xyz += attributes.TANGENT_BS1 * renderer_BlendShapeWeights[1]; + #endif + #else + #if defined( RENDERER_BLENDSHAPE_HAS_NORMAL ) || defined( RENDERER_BLENDSHAPE_HAS_TANGENT ) + position.xyz += attributes.POSITION_BS2 * renderer_BlendShapeWeights[2]; + position.xyz += attributes.POSITION_BS3 * renderer_BlendShapeWeights[3]; + + #if defined( RENDERER_BLENDSHAPE_HAS_NORMAL ) && defined( RENDERER_HAS_NORMAL ) + normal += attributes.NORMAL_BS0 * renderer_BlendShapeWeights[0]; + normal += attributes.NORMAL_BS1 * renderer_BlendShapeWeights[1]; + normal += attributes.NORMAL_BS2 * renderer_BlendShapeWeights[2]; + normal += attributes.NORMAL_BS3 * renderer_BlendShapeWeights[3]; + #endif + + #if defined(RENDERER_BLENDSHAPE_HAS_TANGENT) && defined( RENDERER_HAS_TANGENT ) + tangent.xyz += attributes.TANGENT_BS0 * renderer_BlendShapeWeights[0]; + tangent.xyz += attributes.TANGENT_BS1 * renderer_BlendShapeWeights[1]; + tangent.xyz += attributes.TANGENT_BS2 * renderer_BlendShapeWeights[2]; + tangent.xyz += attributes.TANGENT_BS3 * renderer_BlendShapeWeights[3]; + #endif + #else + position.xyz += attributes.POSITION_BS2 * renderer_BlendShapeWeights[2]; + position.xyz += attributes.POSITION_BS3 * renderer_BlendShapeWeights[3]; + position.xyz += attributes.POSITION_BS4 * renderer_BlendShapeWeights[4]; + position.xyz += attributes.POSITION_BS5 * renderer_BlendShapeWeights[5]; + position.xyz += attributes.POSITION_BS6 * renderer_BlendShapeWeights[6]; + position.xyz += attributes.POSITION_BS7 * renderer_BlendShapeWeights[7]; + #endif + #endif + #endif + } + +#endif + + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/Common.glsl b/packages/shader-shaderlab/src/shaders/Common.glsl new file mode 100644 index 0000000000..46ac04ad5f --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/Common.glsl @@ -0,0 +1,101 @@ +#ifndef COMMON_INCLUDED +#define COMMON_INCLUDED + +#define PI 3.14159265359 +#define RECIPROCAL_PI 0.31830988618 +#define EPSILON 1e-6 +#define LOG2 1.442695 + +#define saturate( a ) clamp( a, 0.0, 1.0 ) + +float pow2(float x ) { + return x * x; +} + +vec4 RGBMToLinear(vec4 value, float maxRange ) { + return vec4( value.rgb * value.a * maxRange, 1.0 ); +} + +vec4 gammaToLinear(vec4 srgbIn){ + return vec4( pow(srgbIn.rgb, vec3(2.2)), srgbIn.a); +} + +vec4 linearToGamma(vec4 linearIn){ + linearIn = max(linearIn, 0.0); + return vec4( pow(linearIn.rgb, vec3(1.0 / 2.2)), linearIn.a); +} + +vec4 camera_DepthBufferParams; + +float remapDepthBufferLinear01(float z){ + return 1.0/ (camera_DepthBufferParams.x * z + camera_DepthBufferParams.y); +} + + +#ifdef GRAPHICS_API_WEBGL2 + #define INVERSE_MAT(mat) inverse(mat) +#else + mat2 inverseMat(mat2 m) { + return mat2(m[1][1],-m[0][1], + -m[1][0], m[0][0]) / (m[0][0]*m[1][1] - m[0][1]*m[1][0]); + } + mat3 inverseMat(mat3 m) { + float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2]; + float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2]; + float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2]; + + float b01 = a22 * a11 - a12 * a21; + float b11 = -a22 * a10 + a12 * a20; + float b21 = a21 * a10 - a11 * a20; + + float det = a00 * b01 + a01 * b11 + a02 * b21; + + return mat3(b01, (-a22 * a01 + a02 * a21), (a12 * a01 - a02 * a11), + b11, (a22 * a00 - a02 * a20), (-a12 * a00 + a02 * a10), + b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)) / det; + } + mat4 inverseMat(mat4 m) { + float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3], + a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3], + a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3], + a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + return mat4( + a11 * b11 - a12 * b10 + a13 * b09, + a02 * b10 - a01 * b11 - a03 * b09, + a31 * b05 - a32 * b04 + a33 * b03, + a22 * b04 - a21 * b05 - a23 * b03, + a12 * b08 - a10 * b11 - a13 * b07, + a00 * b11 - a02 * b08 + a03 * b07, + a32 * b02 - a30 * b05 - a33 * b01, + a20 * b05 - a22 * b02 + a23 * b01, + a10 * b10 - a11 * b08 + a13 * b06, + a01 * b08 - a00 * b10 - a03 * b06, + a30 * b04 - a31 * b02 + a33 * b00, + a21 * b02 - a20 * b04 - a23 * b00, + a11 * b07 - a10 * b09 - a12 * b06, + a00 * b09 - a01 * b07 + a02 * b06, + a31 * b01 - a30 * b03 - a32 * b00, + a20 * b03 - a21 * b01 + a22 * b00) / det; + } + + #define INVERSE_MAT(mat) inverseMat(mat) +#endif + + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/Fog.glsl b/packages/shader-shaderlab/src/shaders/Fog.glsl new file mode 100644 index 0000000000..a9d920c9ad --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/Fog.glsl @@ -0,0 +1,30 @@ +#ifndef FOG_INCLUDED +#define FOG_INCLUDED + +#if SCENE_FOG_MODE != 0 + vec4 scene_FogColor; + vec4 scene_FogParams; // (-1/(end-start), end/(end-start), density/ln(2),density/sprt(ln(2))); + + vec4 fog(vec4 color, vec3 positionVS){ + float fogDepth = length(positionVS); + + #if SCENE_FOG_MODE == 1 + // (end-z) / (end-start) = z * (-1/(end-start)) + (end/(end-start)) + float fogIntensity = clamp(fogDepth * scene_FogParams.x + scene_FogParams.y, 0.0, 1.0); + #elif SCENE_FOG_MODE == 2 + // exp(-z * density) = exp2((-z * density)/ln(2)) = exp2(-z * density/ln(2)) + float fogIntensity = clamp(exp2(-fogDepth * scene_FogParams.z), 0.0, 1.0); + #elif SCENE_FOG_MODE == 3 + // exp(-(z * density)^2) = exp2(-(z * density)^2/ln(2)) = exp2(-(z * density/sprt(ln(2)))^2) + float factor = fogDepth * scene_FogParams.w; + float fogIntensity = clamp(exp2(-factor * factor), 0.0, 1.0); + #endif + + color.rgb = mix(scene_FogColor.rgb, color.rgb, fogIntensity); + + return color; + } +#endif + + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/Light.glsl b/packages/shader-shaderlab/src/shaders/Light.glsl new file mode 100644 index 0000000000..3101ae1cf1 --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/Light.glsl @@ -0,0 +1,137 @@ +#ifndef LIGHT_INCLUDED +#define LIGHT_INCLUDED + + +ivec4 renderer_Layer; +#ifndef GRAPHICS_API_WEBGL2 + bool isBitSet(float value, float mask, float bitIndex){ + return mod(floor(value / pow(2.0, bitIndex)), 2.0) == 1.0 && mod(floor(mask / pow(2.0, bitIndex)), 2.0) == 1.0; + } +#endif + +bool isRendererCulledByLight(ivec2 rendererLayer, ivec2 lightCullingMask){ + #ifdef GRAPHICS_API_WEBGL2 + return !((rendererLayer.x & lightCullingMask.x) != 0 || (rendererLayer.y & lightCullingMask.y) != 0); + #else + for (int i = 0; i < 16; i++) { + if (isBitSet( float(rendererLayer.x), float(lightCullingMask.x), float(i)) || isBitSet( float(rendererLayer.y), float(lightCullingMask.y), float(i))) { + return false; + } + } + return true; + #endif +} + +// Directional light +#ifdef SCENE_DIRECT_LIGHT_COUNT + + struct DirectLight { + vec3 color; + vec3 direction; + }; + + ivec2 scene_DirectLightCullingMask[SCENE_DIRECT_LIGHT_COUNT]; + vec3 scene_DirectLightColor[SCENE_DIRECT_LIGHT_COUNT]; + vec3 scene_DirectLightDirection[SCENE_DIRECT_LIGHT_COUNT]; + + #ifdef GRAPHICS_API_WEBGL2 + DirectLight getDirectLight(int index){ + DirectLight light; + light.color = scene_DirectLightColor[index]; + light.direction = scene_DirectLightDirection[index]; + + return light; + } + #endif + +#endif + + +// Point light +#ifdef SCENE_POINT_LIGHT_COUNT + + struct PointLight { + vec3 color; + vec3 position; + float distance; + }; + + ivec2 scene_PointLightCullingMask[ SCENE_POINT_LIGHT_COUNT ]; + vec3 scene_PointLightColor[ SCENE_POINT_LIGHT_COUNT ]; + vec3 scene_PointLightPosition[ SCENE_POINT_LIGHT_COUNT ]; + float scene_PointLightDistance[ SCENE_POINT_LIGHT_COUNT ]; + + #ifdef GRAPHICS_API_WEBGL2 + PointLight getPointLight(int index){ + PointLight light; + light.color = scene_PointLightColor[index]; + light.position = scene_PointLightPosition[index]; + light.distance = scene_PointLightDistance[index]; + + return light; + } + #endif + +#endif + + +// Spot light +#ifdef SCENE_SPOT_LIGHT_COUNT + + struct SpotLight { + vec3 color; + vec3 position; + vec3 direction; + float distance; + float angleCos; + float penumbraCos; + }; + + ivec2 scene_SpotLightCullingMask[ SCENE_SPOT_LIGHT_COUNT ]; + vec3 scene_SpotLightColor[ SCENE_SPOT_LIGHT_COUNT ]; + vec3 scene_SpotLightPosition[ SCENE_SPOT_LIGHT_COUNT ]; + vec3 scene_SpotLightDirection[ SCENE_SPOT_LIGHT_COUNT ]; + float scene_SpotLightDistance[ SCENE_SPOT_LIGHT_COUNT ]; + float scene_SpotLightAngleCos[ SCENE_SPOT_LIGHT_COUNT ]; + float scene_SpotLightPenumbraCos[ SCENE_SPOT_LIGHT_COUNT ]; + + #ifdef GRAPHICS_API_WEBGL2 + SpotLight getSpotLight(int index){ + SpotLight light; + light.color = scene_SpotLightColor[index]; + light.position = scene_SpotLightPosition[index]; + light.direction = scene_SpotLightDirection[index]; + light.distance = scene_SpotLightDistance[index]; + light.angleCos = scene_SpotLightAngleCos[index]; + light.penumbraCos = scene_SpotLightPenumbraCos[index]; + + return light; + } + #endif + + +#endif + +// Ambient light +struct EnvMapLight { + vec3 diffuse; + float mipMapLevel; + float diffuseIntensity; + float specularIntensity; +}; + + +EnvMapLight scene_EnvMapLight; + +#ifdef SCENE_USE_SH + vec3 scene_EnvSH[9]; +#endif + +#ifdef SCENE_USE_SPECULAR_ENV + samplerCube scene_EnvSpecularSampler; +#endif + + + + +#endif diff --git a/packages/shader-shaderlab/src/shaders/Normal.glsl b/packages/shader-shaderlab/src/shaders/Normal.glsl new file mode 100644 index 0000000000..c077f32aea --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/Normal.glsl @@ -0,0 +1,37 @@ +#ifndef NORMAL_INCLUDED +#define NORMAL_INCLUDED + + +vec3 getNormalByNormalTexture(mat3 tbn, sampler2D normalTexture, float normalIntensity, vec2 uv, bool isFrontFacing){ + vec3 normal = (texture2D(normalTexture, uv)).rgb; + normal = normalize(tbn * ((2.0 * normal - 1.0) * vec3(normalIntensity, normalIntensity, 1.0))); + normal *= float( isFrontFacing ) * 2.0 - 1.0; + + return normal; +} + +mat3 getTBNByDerivatives(vec2 uv, vec3 normal, vec3 position, bool isFrontFacing){ + #ifdef HAS_DERIVATIVES + uv = isFrontFacing? uv: -uv; + // ref: http://www.thetenthplanet.de/archives/1180 + // get edge vectors of the pixel triangle + vec3 dp1 = dFdx(position); + vec3 dp2 = dFdy(position); + vec2 duv1 = dFdx(uv); + vec2 duv2 = dFdy(uv); + // solve the linear system + vec3 dp2perp = cross(dp2, normal); + vec3 dp1perp = cross(normal, dp1); + vec3 tangent = dp2perp * duv1.x + dp1perp * duv2.x; + vec3 bitangent = dp2perp * duv1.y + dp1perp * duv2.y; + // construct a scale-invariant frame + float denom = max( dot(tangent, tangent), dot(bitangent, bitangent) ); + float invmax = (denom == 0.0) ? 0.0 : camera_ProjectionParams.x / sqrt( denom ); + return mat3(tangent * invmax, bitangent * invmax, normal); + #else + return mat3(vec3(0.0), vec3(0.0), normal); + #endif +} + + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/PBR.gs b/packages/shader-shaderlab/src/shaders/PBR.gs new file mode 100644 index 0000000000..8bea8bb27a --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/PBR.gs @@ -0,0 +1,78 @@ +Shader "PBR.gs" { + EditorProperties { + Header("Base"){ + material_IOR("IOR", Range(0, 5, 0.01)) = 1.5; + material_BaseColor("BaseColor", Color) = (1, 1, 1, 1); + material_BaseTexture("BaseTexture", Texture2D); + } + + Header("Metal Roughness") { + material_Metal( "Metal", Range(0,1,0.01) ) = 1; + material_Roughness( "Roughness", Range( 0, 1, 0.01 ) ) = 1; + material_RoughnessMetallicTexture("RoughnessMetallicTexture", Texture2D); + } + + Header("Anisotropy") { + material_AnisotropyInfo("AnisotropyInfo", Vector3) = (1, 0, 0); + material_AnisotropyTexture("AnisotropyTexture", Texture2D); + } + + Header("Normal") { + material_NormalTexture("NormalTexture", Texture2D); + material_NormalIntensity("NormalIntensity", Range(0, 5, 0.01)) = 1; + } + + Header("Emissive") { + material_EmissiveColor("EmissiveColor", Color ) = (0, 0, 0, 1); + material_EmissiveTexture("EmissiveTexture", Texture2D); + } + + Header("Occlusion") { + material_OcclusionTexture("OcclusionTexture", Texture2D); + material_OcclusionIntensity("OcclusionIntensity", Range(0, 5, 0.01)) = 1; + material_OcclusionTextureCoord("OcclusionTextureCoord", Float) = 0; + } + + Header("Clear Coat") { + material_ClearCoat("ClearCoat", Range(0, 1, 0.01)) = 0; + material_ClearCoatTexture("ClearCoatTexture", Texture2D); + material_ClearCoatRoughness("ClearCoatRoughness", Range(0, 1, 0.01)) = 0; + material_ClearCoatRoughnessTexture("ClearCoatRoughnessTexture", Texture2D); + material_ClearCoatNormalTexture("ClearCoatNormalTexture", Texture2D); + } + + Header("Thin Film Iridescence"){ + material_IridescenceInfo("IridescenceInfo", Vector4) = (0, 1.3, 100, 400); + material_IridescenceThicknessTexture("IridescenceThicknessTexture", Texture2D); + material_IridescenceTexture("IridescenceTexture", Texture2D); + } + + Header("Sheen"){ + ui_SheenIntensity("Intensity", Range(0, 1, 0.01)) = 0; + ui_SheenColor("Color", Color ) = (0, 0, 0, 0); + material_SheenRoughness("Roughness", Range(0, 1, 0.01)) = 0; + material_SheenTexture("ColorTexture", Texture2D); + material_SheenRoughnessTexture("RoughnessTexture", Texture2D); + } + + Header("Common") { + material_AlphaCutoff( "AlphaCutoff", Range(0, 1, 0.01) ) = 0; + material_TilingOffset("TilingOffset", Vector4) = (1, 1, 0, 0); + } + } + + SubShader "Default" { + UsePass "pbr/Default/ShadowCaster" + + Pass "Forward Pass" { + Tags { pipelineStage = "Forward"} + + #define IS_METALLIC_WORKFLOW + + VertexShader = PBRVertex; + FragmentShader = PBRFragment; + + #include "ForwardPassPBR.glsl" + } + } + } \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/Shadow.glsl b/packages/shader-shaderlab/src/shaders/Shadow.glsl new file mode 100644 index 0000000000..77823162cf --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/Shadow.glsl @@ -0,0 +1,179 @@ +#ifndef SHADOW_INCLUDED +#define SHADOW_INCLUDED + +#include "Transform.glsl" +#include "Common.glsl" + +#if defined(SCENE_SHADOW_TYPE) && defined(RENDERER_IS_RECEIVE_SHADOWS) + #define NEED_CALCULATE_SHADOWS +#endif + + +#ifdef NEED_CALCULATE_SHADOWS + mat4 scene_ShadowMatrices[SCENE_SHADOW_CASCADED_COUNT + 1]; + vec4 scene_ShadowSplitSpheres[4]; + + mediump int computeCascadeIndex(vec3 positionWS) { + vec3 fromCenter0 = positionWS - scene_ShadowSplitSpheres[0].xyz; + vec3 fromCenter1 = positionWS - scene_ShadowSplitSpheres[1].xyz; + vec3 fromCenter2 = positionWS - scene_ShadowSplitSpheres[2].xyz; + vec3 fromCenter3 = positionWS - scene_ShadowSplitSpheres[3].xyz; + + mediump vec4 comparison = vec4( + (dot(fromCenter0, fromCenter0) < scene_ShadowSplitSpheres[0].w), + (dot(fromCenter1, fromCenter1) < scene_ShadowSplitSpheres[1].w), + (dot(fromCenter2, fromCenter2) < scene_ShadowSplitSpheres[2].w), + (dot(fromCenter3, fromCenter3) < scene_ShadowSplitSpheres[3].w)); + comparison.yzw = clamp(comparison.yzw - comparison.xyz,0.0,1.0);//keep the nearest + mediump vec4 indexCoefficient = vec4(4.0,3.0,2.0,1.0); + mediump int index = 4 - int(dot(comparison, indexCoefficient)); + return index; + } + + vec3 getShadowCoord(vec3 positionWS) { + #if SCENE_SHADOW_CASCADED_COUNT == 1 + mediump int cascadeIndex = 0; + #else + mediump int cascadeIndex = computeCascadeIndex(positionWS); + #endif + + #ifdef GRAPHICS_API_WEBGL2 + mat4 shadowMatrix = scene_ShadowMatrices[cascadeIndex]; + #else + mat4 shadowMatrix; + #if SCENE_SHADOW_CASCADED_COUNT == 4 + if (cascadeIndex == 0) { + shadowMatrix = scene_ShadowMatrices[0]; + } else if (cascadeIndex == 1) { + shadowMatrix = scene_ShadowMatrices[1]; + } else if (cascadeIndex == 2) { + shadowMatrix = scene_ShadowMatrices[2]; + } else if (cascadeIndex == 3) { + shadowMatrix = scene_ShadowMatrices[3]; + } else { + shadowMatrix = scene_ShadowMatrices[4]; + } + #endif + #if SCENE_SHADOW_CASCADED_COUNT == 2 + if (cascadeIndex == 0) { + shadowMatrix = scene_ShadowMatrices[0]; + } else if (cascadeIndex == 1) { + shadowMatrix = scene_ShadowMatrices[1]; + } else { + shadowMatrix = scene_ShadowMatrices[2]; + } + #endif + #if SCENE_SHADOW_CASCADED_COUNT == 1 + if (cascadeIndex == 0) { + shadowMatrix = scene_ShadowMatrices[0]; + } else { + shadowMatrix = scene_ShadowMatrices[1]; + } + #endif + #endif + + vec4 shadowCoord = shadowMatrix * vec4(positionWS, 1.0); + return shadowCoord.xyz; + } +#endif + + +#ifdef NEED_CALCULATE_SHADOWS + // intensity, null, fadeScale, fadeBias + vec4 scene_ShadowInfo; + vec4 scene_ShadowMapSize; + + #ifdef GRAPHICS_API_WEBGL2 + mediump sampler2DShadow scene_ShadowMap; + #define SAMPLE_TEXTURE2D_SHADOW(textureName, coord3) textureLod(textureName, coord3 , 0.0) + #define TEXTURE2D_SHADOW_PARAM(shadowMap) mediump sampler2DShadow shadowMap + #else + sampler2D scene_ShadowMap; + #ifdef ENGINE_NO_DEPTH_TEXTURE + const vec4 bitShift = vec4(1.0, 1.0/256.0, 1.0/(256.0*256.0), 1.0/(256.0*256.0*256.0)); + /** + * Unpack depth value. + */ + float unpack(in vec4 rgbaDepth) { + return dot(rgbaDepth, bitShift); + } + #define SAMPLE_TEXTURE2D_SHADOW(textureName, coord3) (unpack(texture2D(textureName, coord3.xy)) < coord3.z ? 0.0 : 1.0) + #else + #define SAMPLE_TEXTURE2D_SHADOW(textureName, coord3) ((texture2D(textureName, coord3.xy)).r < coord3.z ? 0.0 : 1.0) + #endif + #define TEXTURE2D_SHADOW_PARAM(shadowMap) mediump sampler2D shadowMap + #endif + + #if SCENE_SHADOW_TYPE == 2 + float sampleShadowMapFiltered4(TEXTURE2D_SHADOW_PARAM(shadowMap), vec3 shadowCoord, vec4 shadowMapSize) { + float attenuation; + vec4 attenuation4; + vec2 offset=shadowMapSize.xy/2.0; + vec3 shadowCoord0=shadowCoord + vec3(-offset,0.0); + vec3 shadowCoord1=shadowCoord + vec3(offset.x,-offset.y,0.0); + vec3 shadowCoord2=shadowCoord + vec3(-offset.x,offset.y,0.0); + vec3 shadowCoord3=shadowCoord + vec3(offset,0.0); + attenuation4.x = SAMPLE_TEXTURE2D_SHADOW(shadowMap, shadowCoord0); + attenuation4.y = SAMPLE_TEXTURE2D_SHADOW(shadowMap, shadowCoord1); + attenuation4.z = SAMPLE_TEXTURE2D_SHADOW(shadowMap, shadowCoord2); + attenuation4.w = SAMPLE_TEXTURE2D_SHADOW(shadowMap, shadowCoord3); + attenuation = dot(attenuation4, vec4(0.25)); + return attenuation; + } + #endif + + #if SCENE_SHADOW_TYPE == 3 + #include "ShadowSampleTent.glsl" + + float sampleShadowMapFiltered9(TEXTURE2D_SHADOW_PARAM(shadowMap), vec3 shadowCoord, vec4 shadowmapSize) { + float attenuation; + float fetchesWeights[9]; + vec2 fetchesUV[9]; + sampleShadowComputeSamplesTent5x5(shadowmapSize, shadowCoord.xy, fetchesWeights, fetchesUV); + attenuation = fetchesWeights[0] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3(fetchesUV[0].xy, shadowCoord.z)); + attenuation += fetchesWeights[1] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3(fetchesUV[1].xy, shadowCoord.z)); + attenuation += fetchesWeights[2] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3(fetchesUV[2].xy, shadowCoord.z)); + attenuation += fetchesWeights[3] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3(fetchesUV[3].xy, shadowCoord.z)); + attenuation += fetchesWeights[4] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3(fetchesUV[4].xy, shadowCoord.z)); + attenuation += fetchesWeights[5] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3(fetchesUV[5].xy, shadowCoord.z)); + attenuation += fetchesWeights[6] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3(fetchesUV[6].xy, shadowCoord.z)); + attenuation += fetchesWeights[7] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3(fetchesUV[7].xy, shadowCoord.z)); + attenuation += fetchesWeights[8] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3(fetchesUV[8].xy, shadowCoord.z)); + return attenuation; + } + #endif + + + float getShadowFade(vec3 positionWS){ + vec3 camToPixel = positionWS - camera_Position; + float distanceCamToPixel2 = dot(camToPixel, camToPixel); + return saturate( distanceCamToPixel2 * scene_ShadowInfo.z + scene_ShadowInfo.w ); + } + + + float sampleShadowMap(vec3 positionWS, vec3 shadowCoord) { + float attenuation = 1.0; + if(shadowCoord.z > 0.0 && shadowCoord.z < 1.0) { + #if SCENE_SHADOW_TYPE == 1 + attenuation = SAMPLE_TEXTURE2D_SHADOW(scene_ShadowMap, shadowCoord); + #endif + + #if SCENE_SHADOW_TYPE == 2 + attenuation = sampleShadowMapFiltered4(scene_ShadowMap, shadowCoord, scene_ShadowMapSize); + #endif + + #if SCENE_SHADOW_TYPE == 3 + attenuation = sampleShadowMapFiltered9(scene_ShadowMap, shadowCoord, scene_ShadowMapSize); + #endif + attenuation = mix(1.0, attenuation, scene_ShadowInfo.x); + } + + float shadowFade = getShadowFade(positionWS); + attenuation = mix(1.0, mix(attenuation, 1.0, shadowFade), scene_ShadowInfo.x); + + return attenuation; + } +#endif + + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/ShadowSampleTent.glsl b/packages/shader-shaderlab/src/shaders/ShadowSampleTent.glsl new file mode 100644 index 0000000000..7fc570c72b --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/ShadowSampleTent.glsl @@ -0,0 +1,120 @@ +#ifndef SHADOW_SAMPLE_TENT_INCLUDED +#define SHADOW_SAMPLE_TENT_INCLUDED + +// ------------------------------------------------------------------ +// PCF Filtering Tent Functions +// ------------------------------------------------------------------ + +// Assuming a isoceles right angled triangle of height "triangleHeight" (as drawn below). +// This function return the area of the triangle above the first texel(in Y the first texel). +// +// |\ <-- 45 degree slop isosceles right angled triangle +// | \ +// ---- <-- length of this side is "triangleHeight" +// _ _ _ _ <-- texels +float sampleShadowGetIRTriangleTexelArea(float triangleHeight) { + return triangleHeight - 0.5; +} + +// Assuming a isoceles triangle of 1.5 texels height and 3 texels wide lying on 4 texels. +// This function return the area of the triangle above each of those texels. +// | <-- offset from -0.5 to 0.5, 0 meaning triangle is exactly in the center +// / \ <-- 45 degree slop isosceles triangle (ie tent projected in 2D) +// / \ +// _ _ _ _ <-- texels +// X Y Z W <-- result indices (in computedArea.xyzw and computedAreaUncut.xyzw) +// Top point at (right,top) in a texel,left bottom point at (middle,middle) in a texel,right bottom point at (middle,middle) in a texel. +void sampleShadowGetTexelAreasTent3x3(float offset, out vec4 computedArea, out vec4 computedAreaUncut) { + // Compute the exterior areas,a and h is same. + float a = offset + 0.5; + float offsetSquaredHalved = a * a * 0.5; + computedAreaUncut.x = computedArea.x = offsetSquaredHalved - offset; + computedAreaUncut.w = computedArea.w = offsetSquaredHalved; + + // Compute the middle areas + // For Y : We find the area in Y of as if the left section of the isoceles triangle would + // intersect the axis between Y and Z (ie where offset = 0). + computedAreaUncut.y = sampleShadowGetIRTriangleTexelArea(1.5 - offset); + // This area is superior to the one we are looking for if (offset < 0) thus we need to + // subtract the area of the triangle defined by (0,1.5-offset), (0,1.5+offset), (-offset,1.5). + float clampedOffsetLeft = min(offset,0.0); + float areaOfSmallLeftTriangle = clampedOffsetLeft * clampedOffsetLeft; + computedArea.y = computedAreaUncut.y - areaOfSmallLeftTriangle; + + // We do the same for the Z but with the right part of the isoceles triangle + computedAreaUncut.z = sampleShadowGetIRTriangleTexelArea(1.5 + offset); + float clampedOffsetRight = max(offset,0.0); + float areaOfSmallRightTriangle = clampedOffsetRight * clampedOffsetRight; + computedArea.z = computedAreaUncut.z - areaOfSmallRightTriangle; +} + +// Assuming a isoceles triangle of 2.5 texel height and 5 texels wide lying on 6 texels. +// This function return the weight of each texels area relative to the full triangle area. +// / \ +// _ _ _ _ _ _ <-- texels +// 0 1 2 3 4 5 <-- computed area indices (in texelsWeights[]) +// Top point at (right,top) in a texel,left bottom point at (middle,middle) in a texel,right bottom point at (middle,middle) in a texel. +void sampleShadowGetTexelWeightsTent5x5(float offset, out vec3 texelsWeightsA, out vec3 texelsWeightsB) { + vec4 areaFrom3texelTriangle; + vec4 areaUncutFrom3texelTriangle; + sampleShadowGetTexelAreasTent3x3(offset, areaFrom3texelTriangle, areaUncutFrom3texelTriangle); + + // Triangle slope is 45 degree thus we can almost reuse the result of the 3 texel wide computation. + // the 5 texel wide triangle can be seen as the 3 texel wide one but shifted up by one unit/texel. + // 0.16 is 1/(the triangle area) + texelsWeightsA.x = 0.16 * (areaFrom3texelTriangle.x); + texelsWeightsA.y = 0.16 * (areaUncutFrom3texelTriangle.y); + texelsWeightsA.z = 0.16 * (areaFrom3texelTriangle.y + 1.0); + texelsWeightsB.x = 0.16 * (areaFrom3texelTriangle.z + 1.0); + texelsWeightsB.y = 0.16 * (areaUncutFrom3texelTriangle.z); + texelsWeightsB.z = 0.16 * (areaFrom3texelTriangle.w); +} + +// 5x5 Tent filter (45 degree sloped triangles in U and V) +void sampleShadowComputeSamplesTent5x5(vec4 shadowMapTextureTexelSize, vec2 coord, out float fetchesWeights[9], out vec2 fetchesUV[9]) +{ + // tent base is 5x5 base thus covering from 25 to 36 texels, thus we need 9 bilinear PCF fetches + vec2 tentCenterInTexelSpace = coord.xy * shadowMapTextureTexelSize.zw; + vec2 centerOfFetchesInTexelSpace = floor(tentCenterInTexelSpace + 0.5); + vec2 offsetFromTentCenterToCenterOfFetches = tentCenterInTexelSpace - centerOfFetchesInTexelSpace; + + // find the weight of each texel based on the area of a 45 degree slop tent above each of them. + vec3 texelsWeightsUA, texelsWeightsUB; + vec3 texelsWeightsVA, texelsWeightsVB; + sampleShadowGetTexelWeightsTent5x5(offsetFromTentCenterToCenterOfFetches.x, texelsWeightsUA, texelsWeightsUB); + sampleShadowGetTexelWeightsTent5x5(offsetFromTentCenterToCenterOfFetches.y, texelsWeightsVA, texelsWeightsVB); + + // each fetch will cover a group of 2x2 texels, the weight of each group is the sum of the weights of the texels + vec3 fetchesWeightsU = vec3(texelsWeightsUA.xz, texelsWeightsUB.y) + vec3(texelsWeightsUA.y, texelsWeightsUB.xz); + vec3 fetchesWeightsV = vec3(texelsWeightsVA.xz, texelsWeightsVB.y) + vec3(texelsWeightsVA.y, texelsWeightsVB.xz); + + // move the PCF bilinear fetches to respect texels weights + vec3 fetchesOffsetsU = vec3(texelsWeightsUA.y, texelsWeightsUB.xz) / fetchesWeightsU.xyz + vec3(-2.5,-0.5,1.5); + vec3 fetchesOffsetsV = vec3(texelsWeightsVA.y, texelsWeightsVB.xz) / fetchesWeightsV.xyz + vec3(-2.5,-0.5,1.5); + fetchesOffsetsU *= shadowMapTextureTexelSize.xxx; + fetchesOffsetsV *= shadowMapTextureTexelSize.yyy; + + vec2 bilinearFetchOrigin = centerOfFetchesInTexelSpace * shadowMapTextureTexelSize.xy; + fetchesUV[0] = bilinearFetchOrigin + vec2(fetchesOffsetsU.x, fetchesOffsetsV.x); + fetchesUV[1] = bilinearFetchOrigin + vec2(fetchesOffsetsU.y, fetchesOffsetsV.x); + fetchesUV[2] = bilinearFetchOrigin + vec2(fetchesOffsetsU.z, fetchesOffsetsV.x); + fetchesUV[3] = bilinearFetchOrigin + vec2(fetchesOffsetsU.x, fetchesOffsetsV.y); + fetchesUV[4] = bilinearFetchOrigin + vec2(fetchesOffsetsU.y, fetchesOffsetsV.y); + fetchesUV[5] = bilinearFetchOrigin + vec2(fetchesOffsetsU.z, fetchesOffsetsV.y); + fetchesUV[6] = bilinearFetchOrigin + vec2(fetchesOffsetsU.x, fetchesOffsetsV.z); + fetchesUV[7] = bilinearFetchOrigin + vec2(fetchesOffsetsU.y, fetchesOffsetsV.z); + fetchesUV[8] = bilinearFetchOrigin + vec2(fetchesOffsetsU.z, fetchesOffsetsV.z); + + fetchesWeights[0] = fetchesWeightsU.x * fetchesWeightsV.x; + fetchesWeights[1] = fetchesWeightsU.y * fetchesWeightsV.x; + fetchesWeights[2] = fetchesWeightsU.z * fetchesWeightsV.x; + fetchesWeights[3] = fetchesWeightsU.x * fetchesWeightsV.y; + fetchesWeights[4] = fetchesWeightsU.y * fetchesWeightsV.y; + fetchesWeights[5] = fetchesWeightsU.z * fetchesWeightsV.y; + fetchesWeights[6] = fetchesWeightsU.x * fetchesWeightsV.z; + fetchesWeights[7] = fetchesWeightsU.y * fetchesWeightsV.z; + fetchesWeights[8] = fetchesWeightsU.z * fetchesWeightsV.z; +} + + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/Skin.glsl b/packages/shader-shaderlab/src/shaders/Skin.glsl new file mode 100644 index 0000000000..3f8e1cb1ab --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/Skin.glsl @@ -0,0 +1,47 @@ +#ifndef SKIN_INCLUDED +#define SKIN_INCLUDED + + +#ifdef RENDERER_HAS_SKIN + #ifdef RENDERER_USE_JOINT_TEXTURE + sampler2D renderer_JointSampler; + float renderer_JointCount; + + mat4 getJointMatrix(sampler2D smp, float index){ + float base = index / renderer_JointCount; + float hf = 0.5 / renderer_JointCount; + float v = base + hf; + + vec4 m0 = texture2D(smp, vec2(0.125, v )); + vec4 m1 = texture2D(smp, vec2(0.375, v )); + vec4 m2 = texture2D(smp, vec2(0.625, v )); + vec4 m3 = texture2D(smp, vec2(0.875, v )); + + return mat4(m0, m1, m2, m3); + } + #else + mat4 renderer_JointMatrix[ RENDERER_JOINTS_NUM ]; + #endif + + mat4 getSkinMatrix(Attributes attributes){ + #ifdef RENDERER_USE_JOINT_TEXTURE + mat4 skinMatrix = + attributes.WEIGHTS_0.x * getJointMatrix(renderer_JointSampler, attributes.JOINTS_0.x ) + + attributes.WEIGHTS_0.y * getJointMatrix(renderer_JointSampler, attributes.JOINTS_0.y ) + + attributes.WEIGHTS_0.z * getJointMatrix(renderer_JointSampler, attributes.JOINTS_0.z ) + + attributes.WEIGHTS_0.w * getJointMatrix(renderer_JointSampler, attributes.JOINTS_0.w ); + #else + mat4 skinMatrix = + attributes.WEIGHTS_0.x * renderer_JointMatrix[ int( attributes.JOINTS_0.x ) ] + + attributes.WEIGHTS_0.y * renderer_JointMatrix[ int( attributes.JOINTS_0.y ) ] + + attributes.WEIGHTS_0.z * renderer_JointMatrix[ int( attributes.JOINTS_0.z ) ] + + attributes.WEIGHTS_0.w * renderer_JointMatrix[ int( attributes.JOINTS_0.w ) ]; + #endif + + return skinMatrix; + } + +#endif + + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/Transform.glsl b/packages/shader-shaderlab/src/shaders/Transform.glsl new file mode 100644 index 0000000000..b5d1a52a68 --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/Transform.glsl @@ -0,0 +1,16 @@ +#ifndef TRANSFORM_INCLUDED +#define TRANSFORM_INCLUDED + +mat4 renderer_LocalMat; +mat4 renderer_ModelMat; +mat4 camera_ViewMat; +mat4 camera_ProjMat; +mat4 renderer_MVMat; +mat4 renderer_MVPMat; +mat4 renderer_NormalMat; + +vec3 camera_Position; +vec3 camera_Forward; +vec4 camera_ProjectionParams; + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/index.ts b/packages/shader-shaderlab/src/shaders/index.ts new file mode 100644 index 0000000000..de23d2e392 --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/index.ts @@ -0,0 +1,31 @@ +import BlendShape from "./BlendShape.glsl"; +import Common from "./Common.glsl"; +import Fog from "./Fog.glsl"; +import Light from "./Light.glsl"; +import Normal from "./Normal.glsl"; +import PBRSource from "./PBR.gs"; +import Shadow from "./Shadow.glsl"; +import ShadowSampleTent from "./ShadowSampleTent.glsl"; +import Skin from "./Skin.glsl"; +import Transform from "./Transform.glsl"; +import shadingPBR from "./shadingPBR"; + +interface IShaderFragment { + includeKey: string; + source: string; +} + +const fragmentList: IShaderFragment[] = [ + { source: BlendShape, includeKey: "BlendShape.glsl" }, + { source: Common, includeKey: "Common.glsl" }, + { source: Fog, includeKey: "Fog.glsl" }, + { source: Light, includeKey: "Light.glsl" }, + { source: Normal, includeKey: "Normal.glsl" }, + { source: ShadowSampleTent, includeKey: "ShadowSampleTent.glsl" }, + { source: Shadow, includeKey: "Shadow.glsl" }, + { source: Transform, includeKey: "Transform.glsl" }, + { source: Skin, includeKey: "Skin.glsl" }, + + ...shadingPBR +]; +export { PBRSource, fragmentList }; diff --git a/packages/shader-shaderlab/src/shaders/shadingPBR/AttributesPBR.glsl b/packages/shader-shaderlab/src/shaders/shadingPBR/AttributesPBR.glsl new file mode 100644 index 0000000000..df8b010b90 --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/shadingPBR/AttributesPBR.glsl @@ -0,0 +1,76 @@ +#ifndef ATTRIBUTES_PBR_INCLUDED +#define ATTRIBUTES_PBR_INCLUDED + + +struct Attributes{ + vec3 POSITION; + + #ifdef RENDERER_HAS_BLENDSHAPE + #ifndef RENDERER_BLENDSHAPE_USE_TEXTURE + vec3 POSITION_BS0; + vec3 POSITION_BS1; + #if defined( RENDERER_BLENDSHAPE_HAS_NORMAL ) && defined( RENDERER_BLENDSHAPE_HAS_TANGENT ) + vec3 NORMAL_BS0; + vec3 NORMAL_BS1; + vec3 TANGENT_BS0; + vec3 TANGENT_BS1; + #else + #if defined( RENDERER_BLENDSHAPE_HAS_NORMAL ) || defined( RENDERER_BLENDSHAPE_HAS_TANGENT ) + vec3 POSITION_BS2; + vec3 POSITION_BS3; + + #ifdef RENDERER_BLENDSHAPE_HAS_NORMAL + vec3 NORMAL_BS0; + vec3 NORMAL_BS1; + vec3 NORMAL_BS2; + vec3 NORMAL_BS3; + #endif + + #ifdef RENDERER_BLENDSHAPE_HAS_TANGENT + vec3 TANGENT_BS0; + vec3 TANGENT_BS1; + vec3 TANGENT_BS2; + vec3 TANGENT_BS3; + #endif + + #else + vec3 POSITION_BS2; + vec3 POSITION_BS3; + vec3 POSITION_BS4; + vec3 POSITION_BS5; + vec3 POSITION_BS6; + vec3 POSITION_BS7; + #endif + #endif + #endif + #endif + + + #ifdef RENDERER_HAS_UV + vec2 TEXCOORD_0; + #endif + + #ifdef RENDERER_HAS_UV1 + vec2 TEXCOORD_1; + #endif + + #ifdef RENDERER_HAS_SKIN + vec4 JOINTS_0; + vec4 WEIGHTS_0; + #endif + + #ifdef RENDERER_ENABLE_VERTEXCOLOR + vec4 COLOR_0; + #endif + + #ifdef RENDERER_HAS_NORMAL + vec3 NORMAL; + #endif + + #ifdef RENDERER_HAS_TANGENT + vec4 TANGENT; + #endif +}; + + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/shadingPBR/BRDF.glsl b/packages/shader-shaderlab/src/shaders/shadingPBR/BRDF.glsl new file mode 100644 index 0000000000..c8eb64615c --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/shadingPBR/BRDF.glsl @@ -0,0 +1,410 @@ + +#ifndef BRDF_INCLUDED +#define BRDF_INCLUDED + +#define MIN_PERCEPTUAL_ROUGHNESS 0.045 +#define MIN_ROUGHNESS 0.002025 + +#if defined(RENDERER_HAS_TANGENT) || defined(MATERIAL_ENABLE_ANISOTROPY) || defined(MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE) || defined(MATERIAL_HAS_NORMALTEXTURE) + #define NEED_TANGENT +#endif + +#ifdef MATERIAL_ENABLE_SHEEN + sampler2D scene_prefilteredLUT; +#endif + +struct SurfaceData{ + // common + vec3 albedoColor; + vec3 specularColor; + vec3 emissiveColor; + float metallic; + float roughness; + float diffuseAO; + float specularAO; + float f0; + float opacity; + + // geometry + vec3 position; + vec3 normal; + + #ifdef NEED_TANGENT + vec3 tangent; + vec3 bitangent; + #endif + + vec3 viewDir; + float dotNV; + + // Anisotropy + #ifdef MATERIAL_ENABLE_ANISOTROPY + float anisotropy; + vec3 anisotropicT; + vec3 anisotropicB; + vec3 anisotropicN; + #endif + + // Clear coat + #ifdef MATERIAL_ENABLE_CLEAR_COAT + float clearCoat; + float clearCoatRoughness; + vec3 clearCoatNormal; + float clearCoatDotNV; + #endif + + #ifdef MATERIAL_ENABLE_IRIDESCENCE + float iridesceceIOR; + float iridesceceFactor; + float iridescenceThickness; + #endif + + #ifdef MATERIAL_ENABLE_SHEEN + float sheenRoughness; + vec3 sheenColor; + #endif + +}; + + +struct BRDFData{ + vec3 diffuseColor; + vec3 specularColor; + float roughness; + + #ifdef MATERIAL_ENABLE_CLEAR_COAT + vec3 clearCoatSpecularColor; + float clearCoatRoughness; + #endif + + #ifdef MATERIAL_ENABLE_IRIDESCENCE + vec3 iridescenceSpecularColor; + #endif + + #ifdef MATERIAL_ENABLE_SHEEN + float sheenRoughness; + float sheenScaling; + float approxIBLSheenDG; + #endif + +}; + + +float getAARoughnessFactor(vec3 normal) { + // Kaplanyan 2016, "Stable specular highlights" + // Tokuyoshi 2017, "Error Reduction and Simplification for Shading Anti-Aliasing" + // Tokuyoshi and Kaplanyan 2019, "Improved Geometric Specular Antialiasing" + #ifdef HAS_DERIVATIVES + vec3 dxy = max( abs(dFdx(normal)), abs(dFdy(normal)) ); + return max( max(dxy.x, dxy.y), dxy.z ); + #else + return 0.0; + #endif +} + + +float F_Schlick(float f0, float dotLH) { + return f0 + 0.96 * (pow(1.0 - dotLH, 5.0)); +} + +vec3 F_Schlick(vec3 specularColor, float dotLH ) { + + // Original approximation by Christophe Schlick '94 + // float fresnel = pow( 1.0 - dotLH, 5.0 ); + + // Optimized variant (presented by Epic at SIGGRAPH '13) + // https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + float fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH ); + + return ( 1.0 - specularColor ) * fresnel + specularColor; + +} + +// Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2 +// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf +float G_GGX_SmithCorrelated(float alpha, float dotNL, float dotNV ) { + + float a2 = pow2( alpha ); + + // dotNL and dotNV are explicitly swapped. This is not a mistake. + float gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) ); + float gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) ); + + return 0.5 / max( gv + gl, EPSILON ); + +} + +#ifdef MATERIAL_ENABLE_ANISOTROPY + // Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs" + // Heitz http://jcgt.org/published/0003/02/03/paper.pdf + float G_GGX_SmithCorrelated_Anisotropic(float at, float ab, float ToV, float BoV, float ToL, float BoL, float NoV, float NoL) { + float lambdaV = NoL * length(vec3(at * ToV, ab * BoV, NoV)); + float lambdaL = NoV * length(vec3(at * ToL, ab * BoL, NoL)); + return 0.5 / max(lambdaV + lambdaL, EPSILON); + } +#endif + +// Microfacet Models for Refraction through Rough Surfaces - equation (33) +// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html +// alpha is "roughness squared" in Disney’s reparameterization +float D_GGX(float alpha, float dotNH ) { + + float a2 = pow2( alpha ); + + float denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0; // avoid alpha = 0 with dotNH = 1 + + return RECIPROCAL_PI * a2 / pow2( denom ); + +} + +#ifdef MATERIAL_ENABLE_ANISOTROPY + // GGX Distribution Anisotropic + // https://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf Addenda + float D_GGX_Anisotropic(float at, float ab, float ToH, float BoH, float NoH) { + float a2 = at * ab; + highp vec3 d = vec3(ab * ToH, at * BoH, a2 * NoH); + highp float d2 = dot(d, d); + float b2 = a2 / d2; + return a2 * b2 * b2 * RECIPROCAL_PI; + } +#endif + +float DG_GGX(float alpha, float dotNV, float dotNL, float dotNH) { + float D = D_GGX( alpha, dotNH ); + float G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV ); + return G * D; +} + +#ifdef MATERIAL_ENABLE_ANISOTROPY + float DG_GGX_anisotropic(vec3 h, vec3 l, SurfaceData surfaceData, float alpha, float dotNV, float dotNL, float dotNH) { + vec3 t = surfaceData.anisotropicT; + vec3 b = surfaceData.anisotropicB; + vec3 v = surfaceData.viewDir; + + float dotTV = dot(t, v); + float dotBV = dot(b, v); + float dotTL = dot(t, l); + float dotBL = dot(b, l); + float dotTH = dot(t, h); + float dotBH = dot(b, h); + + // Aniso parameter remapping + // https://blog.selfshadow.com/publications/s2017-shading-course/imageworks/s2017_pbs_imageworks_slides_v2.pdf page 24 + float at = max(alpha * (1.0 + surfaceData.anisotropy), MIN_ROUGHNESS); + float ab = max(alpha * (1.0 - surfaceData.anisotropy), MIN_ROUGHNESS); + + // specular anisotropic BRDF + float D = D_GGX_Anisotropic(at, ab, dotTH, dotBH, dotNH); + float G = G_GGX_SmithCorrelated_Anisotropic(at, ab, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL); + + return G * D; + } +#endif + +// GGX Distribution, Schlick Fresnel, GGX-Smith Visibility +vec3 BRDF_Specular_GGX(vec3 incidentDirection, SurfaceData surfaceData, BRDFData brdfData, vec3 normal, vec3 specularColor, float roughness ) { + + float alpha = pow2( roughness ); // UE4's roughness + + vec3 halfDir = normalize( incidentDirection + surfaceData.viewDir ); + + float dotNL = saturate( dot( normal, incidentDirection ) ); + float dotNV = saturate( dot( normal, surfaceData.viewDir ) ); + float dotNH = saturate( dot( normal, halfDir ) ); + float dotLH = saturate( dot( incidentDirection, halfDir ) ); + + vec3 F = F_Schlick( specularColor, dotLH ); + #ifdef MATERIAL_ENABLE_IRIDESCENCE + F = mix(F, brdfData.iridescenceSpecularColor, surfaceData.iridesceceFactor); + #endif + + + #ifdef MATERIAL_ENABLE_ANISOTROPY + float GD = DG_GGX_anisotropic(halfDir, incidentDirection, surfaceData, alpha, dotNV, dotNL, dotNH); + #else + float GD = DG_GGX(alpha, dotNV, dotNL, dotNH); + #endif + return F * GD; +} + +vec3 BRDF_Diffuse_Lambert(vec3 diffuseColor) { + return RECIPROCAL_PI * diffuseColor; +} + +#ifdef MATERIAL_ENABLE_IRIDESCENCE + vec3 iorToFresnel0(vec3 transmittedIOR, float incidentIOR) { + return pow((transmittedIOR - incidentIOR) / (transmittedIOR + incidentIOR),vec3(2.0)); + } + + float iorToFresnel0(float transmittedIOR, float incidentIOR) { + return pow((transmittedIOR - incidentIOR) / (transmittedIOR + incidentIOR),2.0); + } + + // Assume air interface for top + // Note: We don't handle the case fresnel0 == 1 + vec3 fresnelToIOR(vec3 f0){ + vec3 sqrtF0 = sqrt(f0); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); + } + + // Fresnel equations for dielectric/dielectric interfaces. + // Ref: https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html + // Evaluation XYZ sensitivity curves in Fourier space + vec3 evalSensitivity(float opd, vec3 shift){ + // Use Gaussian fits, given by 3 parameters: val, pos and var + float phase = 2.0 * PI * opd * 1.0e-9; + const vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13); + const vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06); + const vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09); + vec3 xyz = val * sqrt(2.0 * PI * var) * cos(pos * phase + shift) * exp(-var * pow2(phase)); + xyz.x += 9.7470e-14 * sqrt(2.0 * PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(-4.5282e+09 * pow2(phase)); + xyz /= 1.0685e-7; + // XYZ to RGB color space + const mat3 XYZ_TO_RGB = mat3( 3.2404542, -0.9692660, 0.0556434, + -1.5371385, 1.8760108, -0.2040259, + -0.4985314, 0.0415560, 1.0572252); + vec3 rgb = XYZ_TO_RGB * xyz; + return rgb; + } + + vec3 evalIridescenceSpecular(float outsideIOR, float dotNV, float thinIOR, vec3 baseF0,float iridescenceThickness){ + vec3 iridescence = vec3(1.0); + // Force iridescenceIOR -> outsideIOR when thinFilmThickness -> 0.0 + float iridescenceIOR = mix( outsideIOR, thinIOR, smoothstep( 0.0, 0.03, iridescenceThickness ) ); + // Evaluate the cosTheta on the base layer (Snell law) + float sinTheta2Sq = pow( outsideIOR / iridescenceIOR, 2.0) * (1.0 - pow( dotNV, 2.0)); + float cosTheta2Sq = 1.0 - sinTheta2Sq; + // Handle total internal reflection + if (cosTheta2Sq < 0.0) { + return iridescence; + } + float cosTheta2 = sqrt(cosTheta2Sq); + + // First interface + float f0 = iorToFresnel0(iridescenceIOR, outsideIOR); + float reflectance = F_Schlick(f0, dotNV); + float t121 = 1.0 - reflectance; + float phi12 = 0.0; + // iridescenceIOR has limited greater than 1.0 + // if (iridescenceIOR < outsideIOR) {phi12 = PI;} + float phi21 = PI - phi12; + + // Second interface + vec3 baseIOR = fresnelToIOR(clamp(baseF0, 0.0, 0.9999)); // guard against 1.0 + vec3 r1 = iorToFresnel0(baseIOR, iridescenceIOR); + vec3 r23 = F_Schlick(r1, cosTheta2); + vec3 phi23 =vec3(0.0); + if (baseIOR[0] < iridescenceIOR) {phi23[0] = PI;} + if (baseIOR[1] < iridescenceIOR) {phi23[1] = PI;} + if (baseIOR[2] < iridescenceIOR) {phi23[2] = PI;} + + // Phase shift + float opd = 2.0 * iridescenceIOR * iridescenceThickness * cosTheta2; + vec3 phi = vec3(phi21) + phi23; + + // Compound terms + vec3 r123 = clamp(reflectance * r23, 1e-5, 0.9999); + vec3 sr123 = sqrt(r123); + vec3 rs = pow2(t121) * r23 / (vec3(1.0) - r123); + // Reflectance term for m = 0 (DC term amplitude) + vec3 c0 = reflectance + rs; + iridescence = c0; + // Reflectance term for m > 0 (pairs of diracs) + vec3 cm = rs - t121; + for (int m = 1; m <= 2; ++m) { + cm *= sr123; + vec3 sm = 2.0 * evalSensitivity(float(m) * opd, float(m) * phi); + iridescence += cm * sm; + } + return iridescence = max(iridescence, vec3(0.0)); + } +#endif + +#ifdef MATERIAL_ENABLE_SHEEN + // http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf + float D_Charlie(float roughness, float dotNH) { + float invAlpha = 1.0 / roughness; + float cos2h = dotNH * dotNH; + float sin2h = max(1.0 - cos2h, 0.0078125); // 2^(-14/2), so sin2h^2 > 0 in fp16 + return (2.0 + invAlpha) * pow(sin2h, invAlpha * 0.5) / (2.0 * PI); + } + + // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886". + float V_Neubelt(float NoV, float NoL) { + return saturate(1.0 / (4.0 * (NoL + NoV - NoL * NoV))); + } + + vec3 sheenBRDF(vec3 incidentDirection, SurfaceData surfaceData, vec3 sheenColor, float sheenRoughness) { + vec3 halfDir = normalize(incidentDirection + surfaceData.viewDir); + float dotNL = saturate(dot(surfaceData.normal, incidentDirection)); + float dotNH = saturate(dot(surfaceData.normal, halfDir)); + float D = D_Charlie(sheenRoughness, dotNH); + float V = V_Neubelt(surfaceData.dotNV, dotNL); + vec3 F = sheenColor; + return D * V * F; + } + + float prefilteredSheenDFG(float dotNV, float sheenRoughness) { + #ifdef HAS_TEX_LOD + return texture2DLodEXT(scene_prefilteredLUT, vec2(dotNV, sheenRoughness), 0.0).b; + #else + return texture2D(scene_prefilteredLUT, vec2(dotNV, sheenRoughness),0.0).b; + #endif + } +#endif + +// ------------------------Indirect Specular------------------------ +// ref: https://www.unrealengine.com/blog/physically-based-shading-on-mobile - environmentBRDF for GGX on mobile +vec3 envBRDFApprox(vec3 specularColor, float roughness, float dotNV ) { + + const vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 ); + + const vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 ); + + vec4 r = roughness * c0 + c1; + + float a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y; + + vec2 AB = vec2( -1.04, 1.04 ) * a004 + r.zw; + + return specularColor * AB.x + AB.y; + +} + + +void initBRDFData(SurfaceData surfaceData, out BRDFData brdfData){ + vec3 albedoColor = surfaceData.albedoColor; + vec3 specularColor = surfaceData.specularColor; + float metallic = surfaceData.metallic; + float roughness = surfaceData.roughness; + float f0 = surfaceData.f0; + + #ifdef IS_METALLIC_WORKFLOW + brdfData.diffuseColor = albedoColor * ( 1.0 - metallic ); + brdfData.specularColor = mix( vec3(f0), albedoColor, metallic ); + #else + float specularStrength = max( max( specularColor.r, specularColor.g ), specularColor.b ); + brdfData.diffuseColor = albedoColor * ( 1.0 - specularStrength ); + brdfData.specularColor = specularColor; + #endif + + brdfData.roughness = max(MIN_PERCEPTUAL_ROUGHNESS, min(roughness + getAARoughnessFactor(surfaceData.normal), 1.0)); + + #ifdef MATERIAL_ENABLE_CLEAR_COAT + brdfData.clearCoatRoughness = max(MIN_PERCEPTUAL_ROUGHNESS, min(surfaceData.clearCoatRoughness + getAARoughnessFactor(surfaceData.clearCoatNormal), 1.0)); + brdfData.clearCoatSpecularColor = vec3(0.04); + #endif + + #ifdef MATERIAL_ENABLE_IRIDESCENCE + float topIOR = 1.0; + brdfData.iridescenceSpecularColor = evalIridescenceSpecular(topIOR, surfaceData.dotNV, surfaceData.iridesceceIOR, brdfData.specularColor, surfaceData.iridescenceThickness); + #endif + + #ifdef MATERIAL_ENABLE_SHEEN + brdfData.sheenRoughness = max(MIN_PERCEPTUAL_ROUGHNESS, min(surfaceData.sheenRoughness + getAARoughnessFactor(surfaceData.normal), 1.0)); + brdfData.approxIBLSheenDG = prefilteredSheenDFG(surfaceData.dotNV, brdfData.sheenRoughness); + brdfData.sheenScaling = 1.0 - brdfData.approxIBLSheenDG * max(max(surfaceData.sheenColor.r, surfaceData.sheenColor.g), surfaceData.sheenColor.b); + #endif +} + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/shadingPBR/ForwardPassPBR.glsl b/packages/shader-shaderlab/src/shaders/shadingPBR/ForwardPassPBR.glsl new file mode 100644 index 0000000000..8717574808 --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/shadingPBR/ForwardPassPBR.glsl @@ -0,0 +1,110 @@ +#ifndef FORWARD_PASS_PBR_INCLUDED +#define FORWARD_PASS_PBR_INCLUDED + +#include "Common.glsl" +#include "Fog.glsl" + +#include "AttributesPBR.glsl" +#include "VaryingsPBR.glsl" +#include "LightDirectPBR.glsl" +#include "LightIndirectPBR.glsl" + +#include "VertexPBR.glsl" +#include "FragmentPBR.glsl" + + +Varyings PBRVertex(Attributes attributes) { + Varyings varyings; + + varyings.uv = getUV0(attributes); + #ifdef RENDERER_HAS_UV1 + varyings.uv1 = attributes.TEXCOORD_1; + #endif + + #ifdef RENDERER_ENABLE_VERTEXCOLOR + varyings.vertexColor = attributes.COLOR_0; + #endif + + + VertexInputs vertexInputs = getVertexInputs(attributes); + + // positionWS + varyings.positionWS = vertexInputs.positionWS; + + // positionVS + #if SCENE_FOG_MODE != 0 + varyings.positionVS = vertexInputs.positionVS; + #endif + + // normalWS、tangentWS、bitangentWS + #ifdef RENDERER_HAS_NORMAL + varyings.normalWS = vertexInputs.normalWS; + #ifdef RENDERER_HAS_TANGENT + varyings.tangentWS = vertexInputs.tangentWS; + varyings.bitangentWS = vertexInputs.bitangentWS; + #endif + #endif + + // ShadowCoord + #if defined(NEED_CALCULATE_SHADOWS) && (SCENE_SHADOW_CASCADED_COUNT == 1) + varyings.shadowCoord = getShadowCoord(vertexInputs.positionWS); + #endif + + gl_Position = renderer_MVPMat * vertexInputs.positionOS; + + return varyings; +} + + +void PBRFragment(Varyings varyings) { + BRDFData brdfData; + + // Get aoUV + vec2 aoUV = varyings.uv; + #if defined(MATERIAL_HAS_OCCLUSION_TEXTURE) && defined(RENDERER_HAS_UV1) + if(material_OcclusionTextureCoord == 1.0){ + aoUV = varyings.uv1; + } + #endif + + SurfaceData surfaceData = getSurfaceData(varyings, aoUV, gl_FrontFacing); + + // Can modify surfaceData here + initBRDFData(surfaceData, brdfData); + + vec4 color = vec4(0, 0, 0, surfaceData.opacity); + + // Get shadow attenuation + float shadowAttenuation = 1.0; + #if defined(SCENE_DIRECT_LIGHT_COUNT) && defined(NEED_CALCULATE_SHADOWS) + #if SCENE_SHADOW_CASCADED_COUNT == 1 + vec3 shadowCoord = varyings.shadowCoord; + #else + vec3 shadowCoord = getShadowCoord(varyings.positionWS); + #endif + shadowAttenuation *= sampleShadowMap(varyings.positionWS, shadowCoord); + #endif + + // Evaluate direct lighting + evaluateDirectRadiance(varyings, surfaceData, brdfData, shadowAttenuation, color.rgb); + + // IBL + evaluateIBL(varyings, surfaceData, brdfData, color.rgb); + + // Emissive + color.rgb += surfaceData.emissiveColor; + + + #if SCENE_FOG_MODE != 0 + color = fog(color, varyings.positionVS); + #endif + + #ifndef ENGINE_IS_COLORSPACE_GAMMA + color = linearToGamma(color); + #endif + + gl_FragColor = color; +} + + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/shadingPBR/FragmentPBR.glsl b/packages/shader-shaderlab/src/shaders/shadingPBR/FragmentPBR.glsl new file mode 100644 index 0000000000..0ec9afe48c --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/shadingPBR/FragmentPBR.glsl @@ -0,0 +1,315 @@ +#ifndef MATERIAL_INPUT_PBR_INCLUDED +#define MATERIAL_INPUT_PBR_INCLUDED + +#include "Normal.glsl" + +float material_AlphaCutoff; +vec4 material_BaseColor; +float material_Metal; +float material_Roughness; +float material_IOR; +vec3 material_PBRSpecularColor; +float material_Glossiness; +vec3 material_EmissiveColor; +float material_NormalIntensity; +float material_OcclusionIntensity; +float material_OcclusionTextureCoord; + +#ifdef MATERIAL_ENABLE_CLEAR_COAT + float material_ClearCoat; + float material_ClearCoatRoughness; + + #ifdef MATERIAL_HAS_CLEAR_COAT_TEXTURE + sampler2D material_ClearCoatTexture; + #endif + + #ifdef MATERIAL_HAS_CLEAR_COAT_ROUGHNESS_TEXTURE + sampler2D material_ClearCoatRoughnessTexture; + #endif + + #ifdef MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE + sampler2D material_ClearCoatNormalTexture; + #endif +#endif + +#ifdef MATERIAL_ENABLE_ANISOTROPY + vec3 material_AnisotropyInfo; + #ifdef MATERIAL_HAS_ANISOTROPY_TEXTURE + sampler2D material_AnisotropyTexture; + #endif +#endif + +#ifdef MATERIAL_ENABLE_IRIDESCENCE + vec4 material_IridescenceInfo; + #ifdef MATERIAL_HAS_IRIDESCENCE_THICKNESS_TEXTURE + sampler2D material_IridescenceThicknessTexture; + #endif + + #ifdef MATERIAL_HAS_IRIDESCENCE_TEXTURE + sampler2D material_IridescenceTexture; + #endif +#endif + +#ifdef MATERIAL_ENABLE_SHEEN + float material_SheenRoughness; + vec3 material_SheenColor; + #ifdef MATERIAL_HAS_SHEEN_TEXTURE + sampler2D material_SheenTexture; + #endif + + #ifdef MATERIAL_HAS_SHEEN_ROUGHNESS_TEXTURE + sampler2D material_SheenRoughnessTexture; + #endif +#endif + +// Texture +#ifdef MATERIAL_HAS_BASETEXTURE + sampler2D material_BaseTexture; +#endif + +#ifdef MATERIAL_HAS_NORMALTEXTURE + sampler2D material_NormalTexture; +#endif + +#ifdef MATERIAL_HAS_EMISSIVETEXTURE + sampler2D material_EmissiveTexture; +#endif + +#ifdef MATERIAL_HAS_ROUGHNESS_METALLIC_TEXTURE + sampler2D material_RoughnessMetallicTexture; +#endif + + +#ifdef MATERIAL_HAS_SPECULAR_GLOSSINESS_TEXTURE + sampler2D material_SpecularGlossinessTexture; +#endif + +#ifdef MATERIAL_HAS_OCCLUSION_TEXTURE + sampler2D material_OcclusionTexture; +#endif + + +#ifdef MATERIAL_ENABLE_ANISOTROPY + // Aniso Bent Normals + // Mc Alley https://www.gdcvault.com/play/1022235/Rendering-the-World-of-Far + vec3 getAnisotropicBentNormal(SurfaceData surfaceData) { + vec3 anisotropyDirection = (surfaceData.anisotropy >= 0.0) ? surfaceData.anisotropicB : surfaceData.anisotropicT; + vec3 anisotropicTangent = cross(anisotropyDirection, surfaceData.viewDir); + vec3 anisotropicNormal = cross(anisotropicTangent, anisotropyDirection); + // reduce stretching for (roughness < 0.2), refer to https://advances.realtimerendering.com/s2018/Siggraph%202018%20HDRP%20talk_with%20notes.pdf 80 + vec3 bentNormal = normalize( mix(surfaceData.normal, anisotropicNormal, abs(surfaceData.anisotropy) * saturate( 5.0 * surfaceData.roughness)) ); + + return bentNormal; + } +#endif + + +SurfaceData getSurfaceData(Varyings v, vec2 aoUV, bool isFrontFacing){ + SurfaceData surfaceData; + + vec2 uv = v.uv; + + // common + vec4 baseColor = material_BaseColor; + float metallic = material_Metal; + float roughness = material_Roughness; + vec3 specularColor = material_PBRSpecularColor; + float glossiness = material_Glossiness; + float f0 = pow2( (material_IOR - 1.0) / (material_IOR + 1.0) ); + vec3 emissiveRadiance = material_EmissiveColor; + + #ifdef MATERIAL_HAS_BASETEXTURE + vec4 baseTextureColor = texture2D(material_BaseTexture, uv); + #ifndef ENGINE_IS_COLORSPACE_GAMMA + baseTextureColor = gammaToLinear(baseTextureColor); + #endif + baseColor *= baseTextureColor; + #endif + + #ifdef RENDERER_ENABLE_VERTEXCOLOR + baseColor *= v.vertexColor; + #endif + + + #ifdef MATERIAL_IS_ALPHA_CUTOFF + if( baseColor.a < material_AlphaCutoff ) { + discard; + } + #endif + + #ifdef MATERIAL_HAS_ROUGHNESS_METALLIC_TEXTURE + vec4 metalRoughMapColor = texture2D( material_RoughnessMetallicTexture, uv ); + roughness *= metalRoughMapColor.g; + metallic *= metalRoughMapColor.b; + #endif + + #ifdef MATERIAL_HAS_SPECULAR_GLOSSINESS_TEXTURE + vec4 specularGlossinessColor = texture2D(material_SpecularGlossinessTexture, uv ); + #ifndef ENGINE_IS_COLORSPACE_GAMMA + specularGlossinessColor = gammaToLinear(specularGlossinessColor); + #endif + specularColor *= specularGlossinessColor.rgb; + glossiness *= specularGlossinessColor.a; + roughness = 1.0 - glossiness; + #endif + + #ifdef MATERIAL_HAS_EMISSIVETEXTURE + vec4 emissiveColor = texture2D(material_EmissiveTexture, uv); + #ifndef ENGINE_IS_COLORSPACE_GAMMA + emissiveColor = gammaToLinear(emissiveColor); + #endif + emissiveRadiance *= emissiveColor.rgb; + #endif + + surfaceData.albedoColor = baseColor.rgb; + surfaceData.specularColor = specularColor; + surfaceData.emissiveColor = emissiveRadiance; + surfaceData.metallic = metallic; + surfaceData.roughness = roughness; + surfaceData.f0 = f0; + + #ifdef MATERIAL_IS_TRANSPARENT + surfaceData.opacity = baseColor.a; + #else + surfaceData.opacity = 1.0; + #endif + + + // Geometry + surfaceData.position = v.positionWS; + + #ifdef CAMERA_ORTHOGRAPHIC + surfaceData.viewDir = -camera_Forward; + #else + surfaceData.viewDir = normalize(camera_Position - v.positionWS); + #endif + + // Normal + #ifdef RENDERER_HAS_NORMAL + vec3 normal = normalize(v.normalWS); + #elif defined(HAS_DERIVATIVES) + vec3 pos_dx = dFdx(v.positionWS); + vec3 pos_dy = dFdy(v.positionWS); + vec3 normal = normalize( cross(pos_dx, pos_dy) ); + normal *= camera_ProjectionParams.x; + #else + vec3 normal = vec3(0, 0, 1); + #endif + + normal *= float( isFrontFacing ) * 2.0 - 1.0; + surfaceData.normal = normal; + + // Tangent + #ifdef NEED_TANGENT + #if defined(RENDERER_HAS_NORMAL) && defined(RENDERER_HAS_TANGENT) + surfaceData.tangent = v.tangentWS; + surfaceData.bitangent = v.bitangentWS; + mat3 tbn = mat3(v.tangentWS, v.bitangentWS, v.normalWS); + #else + mat3 tbn = getTBNByDerivatives(uv, normal, v.positionWS, isFrontFacing); + surfaceData.tangent = tbn[0]; + surfaceData.bitangent = tbn[1]; + #endif + + #ifdef MATERIAL_HAS_NORMALTEXTURE + surfaceData.normal = getNormalByNormalTexture(tbn, material_NormalTexture, material_NormalIntensity, uv, isFrontFacing); + #endif + #endif + + surfaceData.dotNV = saturate( dot(surfaceData.normal, surfaceData.viewDir) ); + + // Clear Coat + #ifdef MATERIAL_ENABLE_CLEAR_COAT + #ifdef MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE + surfaceData.clearCoatNormal = getNormalByNormalTexture(mat3(surfaceData.tangent, surfaceData.bitangent, surfaceData.normal), material_ClearCoatNormalTexture, material_NormalIntensity, uv, isFrontFacing); + #else + surfaceData.clearCoatNormal = normal; + #endif + surfaceData.clearCoatDotNV = saturate( dot(surfaceData.clearCoatNormal, surfaceData.viewDir) ); + + surfaceData.clearCoat = material_ClearCoat; + surfaceData.clearCoatRoughness = material_ClearCoatRoughness; + + #ifdef MATERIAL_HAS_CLEAR_COAT_TEXTURE + surfaceData.clearCoat *= (texture2D( material_ClearCoatTexture, uv )).r; + #endif + + #ifdef MATERIAL_HAS_CLEAR_COAT_ROUGHNESS_TEXTURE + surfaceData.clearCoatRoughness *= (texture2D( material_ClearCoatRoughnessTexture, uv )).g; + #endif + + surfaceData.clearCoat = saturate( surfaceData.clearCoat ); + surfaceData.clearCoatRoughness = max(MIN_PERCEPTUAL_ROUGHNESS, min(surfaceData.clearCoatRoughness + getAARoughnessFactor(surfaceData.clearCoatNormal), 1.0)); + #endif + + // Anisotropy + #ifdef MATERIAL_ENABLE_ANISOTROPY + float anisotropy = material_AnisotropyInfo.z; + vec3 anisotropicDirection = vec3(material_AnisotropyInfo.xy, 0.0); + #ifdef MATERIAL_HAS_ANISOTROPY_TEXTURE + vec3 anisotropyTextureInfo = (texture2D( material_AnisotropyTexture, uv )).rgb; + anisotropy *= anisotropyTextureInfo.b; + anisotropicDirection.xy *= anisotropyTextureInfo.rg * 2.0 - 1.0; + #endif + + surfaceData.anisotropy = anisotropy; + surfaceData.anisotropicT = normalize(mat3(surfaceData.tangent, surfaceData.bitangent, surfaceData.normal) * anisotropicDirection); + surfaceData.anisotropicB = normalize(cross(surfaceData.normal, surfaceData.anisotropicT)); + surfaceData.anisotropicN = getAnisotropicBentNormal(surfaceData); + #endif + + //Iridescence + #ifdef MATERIAL_ENABLE_IRIDESCENCE + surfaceData.iridesceceFactor = material_IridescenceInfo.x; + surfaceData.iridesceceIOR = material_IridescenceInfo.y; + + #ifdef MATERIAL_HAS_IRIDESCENCE_THICKNESS_TEXTURE + float iridescenceThicknessWeight = texture2D( material_IridescenceThicknessTexture, uv).g; + surfaceData.iridescenceThickness = mix(material_IridescenceInfo.z, material_IridescenceInfo.w, iridescenceThicknessWeight); + #else + surfaceData.iridescenceThickness = material_IridescenceInfo.w; + #endif + + #ifdef MATERIAL_HAS_IRIDESCENCE_TEXTURE + surfaceData.iridesceceFactor *= texture2D( material_IridescenceTexture, uv).r; + #endif + #endif + + #ifdef MATERIAL_ENABLE_SHEEN + vec3 sheenColor = material_SheenColor; + #ifdef MATERIAL_HAS_SHEEN_TEXTURE + vec4 sheenTextureColor = texture2D(material_SheenTexture, uv); + #ifndef ENGINE_IS_COLORSPACE_GAMMA + sheenTextureColor = gammaToLinear(sheenTextureColor); + #endif + sheenColor *= sheenTextureColor.rgb; + #endif + surfaceData.sheenColor = sheenColor; + + surfaceData.sheenRoughness = material_SheenRoughness; + #ifdef MATERIAL_HAS_SHEEN_ROUGHNESS_TEXTURE + surfaceData.sheenRoughness *= texture2D(material_SheenRoughnessTexture, uv).a; + #endif + #endif + + // AO + float diffuseAO = 1.0; + float specularAO = 1.0; + + #ifdef MATERIAL_HAS_OCCLUSION_TEXTURE + diffuseAO = ((texture2D(material_OcclusionTexture, aoUV)).r - 1.0) * material_OcclusionIntensity + 1.0; + #endif + + #if defined(MATERIAL_HAS_OCCLUSION_TEXTURE) && defined(SCENE_USE_SPECULAR_ENV) + specularAO = saturate( pow( surfaceData.dotNV + diffuseAO, exp2( - 16.0 * surfaceData.roughness - 1.0 ) ) - 1.0 + diffuseAO ); + #endif + + surfaceData.diffuseAO = diffuseAO; + surfaceData.specularAO = specularAO; + + return surfaceData; +} + + + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/shadingPBR/LightDirectPBR.glsl b/packages/shader-shaderlab/src/shaders/shadingPBR/LightDirectPBR.glsl new file mode 100644 index 0000000000..f8ad6e77a4 --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/shadingPBR/LightDirectPBR.glsl @@ -0,0 +1,162 @@ + +#ifndef LIGHT_DIRECT_PBR_INCLUDED +#define LIGHT_DIRECT_PBR_INCLUDED + +#ifndef FUNCTION_SURFACE_SHADING + #define FUNCTION_SURFACE_SHADING surfaceShading +#endif +#ifndef FUNCTION_DIFFUSE_LOBE + #define FUNCTION_DIFFUSE_LOBE diffuseLobe +#endif +#ifndef FUNCTION_SPECULAR_LOBE + #define FUNCTION_SPECULAR_LOBE specularLobe +#endif +#ifndef FUNCTION_CLEAR_COAT_LOBE + #define FUNCTION_CLEAR_COAT_LOBE clearCoatLobe +#endif +#ifndef FUNCTION_SHEEN_LOBE + #define FUNCTION_SHEEN_LOBE sheenLobe +#endif + +#include "BRDF.glsl" +#include "Light.glsl" +#include "ReflectionLobe.glsl" + +void surfaceShading(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, vec3 incidentDirection, vec3 lightColor, inout vec3 color) { + + vec3 diffuseColor = vec3(0); + vec3 specularColor = vec3(0); + float dotNL = saturate( dot( surfaceData.normal, incidentDirection ) ); + vec3 irradiance = dotNL * lightColor * PI; + + // ClearCoat Lobe + float attenuation = FUNCTION_CLEAR_COAT_LOBE(varyings, surfaceData, brdfData, incidentDirection, lightColor, specularColor); + + vec3 attenuationIrradiance = attenuation * irradiance; + // Diffuse Lobe + FUNCTION_DIFFUSE_LOBE(varyings, surfaceData, brdfData, attenuationIrradiance, diffuseColor); + // Specular Lobe + FUNCTION_SPECULAR_LOBE(varyings, surfaceData, brdfData, incidentDirection, attenuationIrradiance, specularColor); + // Sheen Lobe + FUNCTION_SHEEN_LOBE(varyings, surfaceData, brdfData, incidentDirection, attenuationIrradiance, diffuseColor, specularColor); + + color += diffuseColor + specularColor; + +} + +#ifdef SCENE_DIRECT_LIGHT_COUNT + + void addDirectionalDirectLightRadiance(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, DirectLight directionalLight, inout vec3 color) { + vec3 lightColor = directionalLight.color; + vec3 direction = -directionalLight.direction; + + FUNCTION_SURFACE_SHADING(varyings, surfaceData, brdfData, direction, lightColor, color); + + } + +#endif + +#ifdef SCENE_POINT_LIGHT_COUNT + + void addPointDirectLightRadiance(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, PointLight pointLight, inout vec3 color) { + vec3 lVector = pointLight.position - surfaceData.position; + vec3 direction = normalize( lVector ); + float lightDistance = length( lVector ); + + vec3 lightColor = pointLight.color; + lightColor *= clamp(1.0 - pow(lightDistance/pointLight.distance, 4.0), 0.0, 1.0); + + FUNCTION_SURFACE_SHADING(varyings, surfaceData, brdfData, direction, lightColor, color); + } + +#endif + +#ifdef SCENE_SPOT_LIGHT_COUNT + + void addSpotDirectLightRadiance(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, SpotLight spotLight, inout vec3 color) { + + vec3 lVector = spotLight.position - surfaceData.position; + vec3 direction = normalize( lVector ); + float lightDistance = length( lVector ); + float angleCos = dot( direction, -spotLight.direction ); + + float spotEffect = smoothstep( spotLight.penumbraCos, spotLight.angleCos, angleCos ); + float decayEffect = clamp(1.0 - pow(lightDistance/spotLight.distance, 4.0), 0.0, 1.0); + + vec3 lightColor = spotLight.color; + lightColor *= spotEffect * decayEffect; + + FUNCTION_SURFACE_SHADING(varyings, surfaceData, brdfData, direction, lightColor, color); + + } + + +#endif + +void evaluateDirectRadiance(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, float shadowAttenuation, inout vec3 color){ + #ifdef SCENE_DIRECT_LIGHT_COUNT + + for ( int i = 0; i < SCENE_DIRECT_LIGHT_COUNT; i ++ ) { + // warning: use `continue` syntax may trigger flickering bug in safri 16.1. + if(!isRendererCulledByLight(renderer_Layer.xy, scene_DirectLightCullingMask[i])){ + #ifdef GRAPHICS_API_WEBGL2 + DirectLight directionalLight = getDirectLight(i); + #else + DirectLight directionalLight; + directionalLight.color = scene_DirectLightColor[i]; + directionalLight.direction = scene_DirectLightDirection[i]; + #endif + + #ifdef NEED_CALCULATE_SHADOWS + if (i == 0) { // Sun light index is always 0 + directionalLight.color *= shadowAttenuation; + } + #endif + addDirectionalDirectLightRadiance(varyings, surfaceData, brdfData, directionalLight, color ); + } + } + + #endif + + #ifdef SCENE_POINT_LIGHT_COUNT + + for ( int i = 0; i < SCENE_POINT_LIGHT_COUNT; i ++ ) { + if(!isRendererCulledByLight(renderer_Layer.xy, scene_PointLightCullingMask[i])){ + #ifdef GRAPHICS_API_WEBGL2 + PointLight pointLight = getPointLight(i); + #else + PointLight pointLight; + pointLight.color = scene_PointLightColor[i]; + pointLight.position = scene_PointLightPosition[i]; + pointLight.distance = scene_PointLightDistance[i]; + #endif + addPointDirectLightRadiance(varyings, surfaceData, brdfData, pointLight, color ); + } + } + + #endif + + #ifdef SCENE_SPOT_LIGHT_COUNT + + for ( int i = 0; i < SCENE_SPOT_LIGHT_COUNT; i ++ ) { + if(!isRendererCulledByLight(renderer_Layer.xy, scene_SpotLightCullingMask[i])){ + #ifdef GRAPHICS_API_WEBGL2 + SpotLight spotLight = getSpotLight(i); + #else + SpotLight spotLight; + spotLight.color = scene_SpotLightColor[i]; + spotLight.position = scene_SpotLightPosition[i]; + spotLight.direction = scene_SpotLightDirection[i]; + spotLight.distance = scene_SpotLightDistance[i]; + spotLight.angleCos = scene_SpotLightAngleCos[i]; + spotLight.penumbraCos = scene_SpotLightPenumbraCos[i]; + #endif + addSpotDirectLightRadiance( varyings, surfaceData, brdfData, spotLight, color ); + } + } + + #endif +} + + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/shadingPBR/LightIndirectFunctions.glsl b/packages/shader-shaderlab/src/shaders/shadingPBR/LightIndirectFunctions.glsl new file mode 100644 index 0000000000..a658834937 --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/shadingPBR/LightIndirectFunctions.glsl @@ -0,0 +1,51 @@ +#ifndef LIGHT_INDIRECT_FUNCTIONS_INCLUDED +#define LIGHT_INDIRECT_FUNCTIONS_INCLUDED +#include "Light.glsl" + +vec3 getReflectedVector(SurfaceData surfaceData, vec3 n) { + #ifdef MATERIAL_ENABLE_ANISOTROPY + vec3 r = reflect(-surfaceData.viewDir, surfaceData.anisotropicN); + #else + vec3 r = reflect(-surfaceData.viewDir, n); + #endif + + return r; +} + +float getSpecularMIPLevel(float roughness, int maxMIPLevel ) { + return roughness * float(maxMIPLevel); +} + +// sh need be pre-scaled in CPU. +vec3 getLightProbeRadiance(SurfaceData surfaceData, vec3 normal, float roughness) { + + #ifndef SCENE_USE_SPECULAR_ENV + return vec3(0); + #else + vec3 reflectVec = getReflectedVector(surfaceData, normal); + reflectVec.x = -reflectVec.x; // TextureCube is left-hand,so x need inverse + + float specularMIPLevel = getSpecularMIPLevel(roughness, int(scene_EnvMapLight.mipMapLevel) ); + + #ifdef HAS_TEX_LOD + vec4 envMapColor = textureCubeLodEXT( scene_EnvSpecularSampler, reflectVec, specularMIPLevel ); + #else + vec4 envMapColor = textureCube( scene_EnvSpecularSampler, reflectVec, specularMIPLevel ); + #endif + + #ifdef SCENE_IS_DECODE_ENV_RGBM + envMapColor.rgb = (RGBMToLinear(envMapColor, 5.0)).rgb; + #ifdef ENGINE_IS_COLORSPACE_GAMMA + envMapColor = linearToGamma(envMapColor); + #endif + #else + #ifndef ENGINE_IS_COLORSPACE_GAMMA + envMapColor = gammaToLinear(envMapColor); + #endif + #endif + + return envMapColor.rgb * scene_EnvMapLight.specularIntensity; + + #endif +} +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/shadingPBR/LightIndirectPBR.glsl b/packages/shader-shaderlab/src/shaders/shadingPBR/LightIndirectPBR.glsl new file mode 100644 index 0000000000..e0f5780b58 --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/shadingPBR/LightIndirectPBR.glsl @@ -0,0 +1,113 @@ + +#ifndef LIGHT_INDIRECT_PBR_INCLUDED +#define LIGHT_INDIRECT_PBR_INCLUDED + +#ifndef FUNCTION_DIFFUSE_IBL + #define FUNCTION_DIFFUSE_IBL evaluateDiffuseIBL +#endif +#ifndef FUNCTION_SPECULAR_IBL + #define FUNCTION_SPECULAR_IBL evaluateSpecularIBL +#endif +#ifndef FUNCTION_CLEAR_COAT_IBL + #define FUNCTION_CLEAR_COAT_IBL evaluateClearCoatIBL +#endif +#ifndef FUNCTION_SHEEN_IBL + #define FUNCTION_SHEEN_IBL evaluateSheenIBL +#endif +#include "BRDF.glsl" +#include "Light.glsl" +#include "LightIndirectFunctions.glsl" + +// ------------------------Diffuse------------------------ + +// sh need be pre-scaled in CPU. +vec3 getLightProbeIrradiance(vec3 sh[9], vec3 normal){ + normal.x = -normal.x; + vec3 result = sh[0] + + + sh[1] * (normal.y) + + sh[2] * (normal.z) + + sh[3] * (normal.x) + + + sh[4] * (normal.y * normal.x) + + sh[5] * (normal.y * normal.z) + + sh[6] * (3.0 * normal.z * normal.z - 1.0) + + sh[7] * (normal.z * normal.x) + + sh[8] * (normal.x * normal.x - normal.y * normal.y); + + return max(result, vec3(0.0)); + +} + + +void evaluateDiffuseIBL(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, inout vec3 diffuseColor){ + #ifdef SCENE_USE_SH + vec3 irradiance = getLightProbeIrradiance(scene_EnvSH, surfaceData.normal); + #ifdef ENGINE_IS_COLORSPACE_GAMMA + irradiance = (linearToGamma(vec4(irradiance, 1.0))).rgb; + #endif + irradiance *= scene_EnvMapLight.diffuseIntensity; + #else + vec3 irradiance = scene_EnvMapLight.diffuse * scene_EnvMapLight.diffuseIntensity; + irradiance *= PI; + #endif + + diffuseColor += surfaceData.diffuseAO * irradiance * BRDF_Diffuse_Lambert( brdfData.diffuseColor ); +} + +float evaluateClearCoatIBL(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, inout vec3 specularColor){ + float radianceAttenuation = 1.0; + + #ifdef MATERIAL_ENABLE_CLEAR_COAT + vec3 clearCoatRadiance = getLightProbeRadiance(surfaceData, surfaceData.clearCoatNormal, brdfData.clearCoatRoughness); + specularColor += surfaceData.specularAO * clearCoatRadiance * surfaceData.clearCoat * envBRDFApprox(brdfData.clearCoatSpecularColor, brdfData.clearCoatRoughness, surfaceData.clearCoatDotNV); + radianceAttenuation -= surfaceData.clearCoat * F_Schlick(0.04, surfaceData.clearCoatDotNV); + #endif + + return radianceAttenuation; +} + +void evaluateSpecularIBL(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, float radianceAttenuation, inout vec3 outSpecularColor){ + vec3 radiance = getLightProbeRadiance(surfaceData, surfaceData.normal, brdfData.roughness); + + #ifdef MATERIAL_ENABLE_IRIDESCENCE + vec3 speculaColor = mix(brdfData.specularColor, brdfData.iridescenceSpecularColor, surfaceData.iridesceceFactor); + #else + vec3 speculaColor = brdfData.specularColor; + #endif + + outSpecularColor += surfaceData.specularAO * radianceAttenuation * radiance * envBRDFApprox(speculaColor, brdfData.roughness, surfaceData.dotNV); +} + +void evaluateSheenIBL(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, float radianceAttenuation, inout vec3 diffuseColor, inout vec3 specularColor){ + #ifdef MATERIAL_ENABLE_SHEEN + diffuseColor *= brdfData.sheenScaling; + specularColor *= brdfData.sheenScaling; + + vec3 reflectance = surfaceData.specularAO * radianceAttenuation * brdfData.approxIBLSheenDG * surfaceData.sheenColor; + specularColor += reflectance; + #endif +} + +void evaluateIBL(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, inout vec3 color){ + vec3 diffuseColor = vec3(0); + vec3 specularColor = vec3(0); + + // IBL diffuse + FUNCTION_DIFFUSE_IBL(varyings, surfaceData, brdfData, diffuseColor); + + // IBL ClearCoat + float radianceAttenuation = FUNCTION_CLEAR_COAT_IBL(varyings, surfaceData, brdfData, specularColor); + + // IBL specular + FUNCTION_SPECULAR_IBL(varyings, surfaceData, brdfData, radianceAttenuation, specularColor); + + // IBL sheen + FUNCTION_SHEEN_IBL(varyings, surfaceData, brdfData, radianceAttenuation, diffuseColor, specularColor); + + color += diffuseColor + specularColor; + +} + + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/shadingPBR/ReflectionLobe.glsl b/packages/shader-shaderlab/src/shaders/shadingPBR/ReflectionLobe.glsl new file mode 100644 index 0000000000..e13d6062bb --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/shadingPBR/ReflectionLobe.glsl @@ -0,0 +1,35 @@ +#ifndef REFLECTION_LOBE_INCLUDED +#define REFLECTION_LOBE_INCLUDED + +void diffuseLobe(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, vec3 attenuationIrradiance, inout vec3 diffuseColor){ + diffuseColor += attenuationIrradiance * BRDF_Diffuse_Lambert( brdfData.diffuseColor ); +} + +void specularLobe(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, vec3 incidentDirection, vec3 attenuationIrradiance, inout vec3 specularColor){ + specularColor += attenuationIrradiance * BRDF_Specular_GGX( incidentDirection, surfaceData, brdfData, surfaceData.normal, brdfData.specularColor, brdfData.roughness); +} + +void sheenLobe(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, vec3 incidentDirection, vec3 attenuationIrradiance, inout vec3 diffuseColor, inout vec3 specularColor){ + #ifdef MATERIAL_ENABLE_SHEEN + diffuseColor *= brdfData.sheenScaling; + specularColor *= brdfData.sheenScaling; + + specularColor += attenuationIrradiance * sheenBRDF(incidentDirection, surfaceData, surfaceData.sheenColor, brdfData.sheenRoughness); + #endif +} + +float clearCoatLobe(Varyings varyings, SurfaceData surfaceData, BRDFData brdfData, vec3 incidentDirection, vec3 color, inout vec3 specularColor){ + float attenuation = 1.0; + + #ifdef MATERIAL_ENABLE_CLEAR_COAT + float clearCoatDotNL = saturate( dot( surfaceData.clearCoatNormal, incidentDirection ) ); + vec3 clearCoatIrradiance = clearCoatDotNL * color; + + specularColor += surfaceData.clearCoat * clearCoatIrradiance * BRDF_Specular_GGX( incidentDirection, surfaceData, brdfData, surfaceData.clearCoatNormal, brdfData.clearCoatSpecularColor, brdfData.clearCoatRoughness ); + attenuation -= surfaceData.clearCoat * F_Schlick(0.04, surfaceData.clearCoatDotNV); + #endif + + return attenuation; +} + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/shadingPBR/VaryingsPBR.glsl b/packages/shader-shaderlab/src/shaders/shadingPBR/VaryingsPBR.glsl new file mode 100644 index 0000000000..6e11b24b4c --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/shadingPBR/VaryingsPBR.glsl @@ -0,0 +1,37 @@ +#ifndef VARYINGS_PBR_INCLUDED +#define VARYINGS_PBR_INCLUDED + +#include "Shadow.glsl" + +struct Varyings{ + vec2 uv; + #ifdef RENDERER_HAS_UV1 + vec2 uv1; + #endif + + #ifdef RENDERER_ENABLE_VERTEXCOLOR + vec4 vertexColor; + #endif + + vec3 positionWS; + + #if SCENE_FOG_MODE != 0 + vec3 positionVS; + #endif + + #ifdef RENDERER_HAS_NORMAL + vec3 normalWS; + #ifdef RENDERER_HAS_TANGENT + vec3 tangentWS; + vec3 bitangentWS; + #endif + #endif + + + #if defined(NEED_CALCULATE_SHADOWS) && (SCENE_SHADOW_CASCADED_COUNT == 1) + vec3 shadowCoord; + #endif +}; + + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/shadingPBR/VertexPBR.glsl b/packages/shader-shaderlab/src/shaders/shadingPBR/VertexPBR.glsl new file mode 100644 index 0000000000..b35eb0332a --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/shadingPBR/VertexPBR.glsl @@ -0,0 +1,102 @@ +#ifndef VERTEX_INCLUDE +#define VERTEX_INCLUDE + +#include "Transform.glsl" +#include "Skin.glsl" +#include "BlendShape.glsl" +#include "Shadow.glsl" + + +struct VertexInputs{ + vec4 positionOS; + vec3 positionWS; + + #if SCENE_FOG_MODE != 0 + vec3 positionVS; + #endif + + #ifdef RENDERER_HAS_NORMAL + vec3 normalWS; + #ifdef RENDERER_HAS_TANGENT + vec3 tangentWS; + vec3 bitangentWS; + #endif + #endif +}; + +vec4 material_TilingOffset; +vec2 getUV0(Attributes attributes){ + vec2 uv0 = vec2(0); + + #ifdef RENDERER_HAS_UV + uv0 = attributes.TEXCOORD_0; + #endif + + return uv0 * material_TilingOffset.xy + material_TilingOffset.zw; +} + +VertexInputs getVertexInputs(Attributes attributes){ + VertexInputs inputs; + vec4 position = vec4(attributes.POSITION, 1.0); + + #ifdef RENDERER_HAS_NORMAL + vec3 normal = vec3( attributes.NORMAL ); + #ifdef RENDERER_HAS_TANGENT + vec4 tangent = vec4( attributes.TANGENT ); + #endif + #endif + + + // BlendShape + #ifdef RENDERER_HAS_BLENDSHAPE + calculateBlendShape(attributes, position + #ifdef RENDERER_HAS_NORMAL + ,normal + #ifdef RENDERER_HAS_TANGENT + ,tangent + #endif + #endif + ); + #endif + + // Skin + #ifdef RENDERER_HAS_SKIN + mat4 skinMatrix = getSkinMatrix(attributes); + position = skinMatrix * position; + + #if defined(RENDERER_HAS_NORMAL) + mat3 skinNormalMatrix = INVERSE_MAT(mat3(skinMatrix)); + normal = normal * skinNormalMatrix; + #ifdef RENDERER_HAS_TANGENT + tangent.xyz = tangent.xyz * skinNormalMatrix; + #endif + #endif + #endif + + // TBN world space + #ifdef RENDERER_HAS_NORMAL + inputs.normalWS = normalize( mat3(renderer_NormalMat) * normal ); + + #ifdef RENDERER_HAS_TANGENT + vec3 tangentWS = normalize( mat3(renderer_NormalMat) * tangent.xyz ); + vec3 bitangentWS = cross( inputs.normalWS, tangentWS ) * tangent.w; + + inputs.tangentWS = tangentWS; + inputs.bitangentWS = bitangentWS; + #endif + #endif + + + inputs.positionOS = position; + vec4 positionWS = renderer_ModelMat * position; + inputs.positionWS = positionWS.xyz / positionWS.w; + + #if SCENE_FOG_MODE != 0 + vec4 positionVS = renderer_MVMat * position; + inputs.positionVS = positionVS.xyz / positionVS.w; + #endif + + return inputs; +} + +#endif \ No newline at end of file diff --git a/packages/shader-shaderlab/src/shaders/shadingPBR/index.ts b/packages/shader-shaderlab/src/shaders/shadingPBR/index.ts new file mode 100644 index 0000000000..2f34850456 --- /dev/null +++ b/packages/shader-shaderlab/src/shaders/shadingPBR/index.ts @@ -0,0 +1,23 @@ +import AttributesPBR from "./AttributesPBR.glsl"; +import BRDF from "./BRDF.glsl"; +import ForwardPassPBR from "./ForwardPassPBR.glsl"; +import FragmentPBR from "./FragmentPBR.glsl"; +import LightDirectPBR from "./LightDirectPBR.glsl"; +import LightIndirectFunctions from "./LightIndirectFunctions.glsl"; +import LightIndirectPBR from "./LightIndirectPBR.glsl"; +import ReflectionLobe from "./ReflectionLobe.glsl"; +import VaryingsPBR from "./VaryingsPBR.glsl"; +import VertexPBR from "./VertexPBR.glsl"; + +export default [ + { source: ForwardPassPBR, includeKey: "ForwardPassPBR.glsl" }, + { source: AttributesPBR, includeKey: "AttributesPBR.glsl" }, + { source: VaryingsPBR, includeKey: "VaryingsPBR.glsl" }, + { source: FragmentPBR, includeKey: "FragmentPBR.glsl" }, + { source: LightDirectPBR, includeKey: "LightDirectPBR.glsl" }, + { source: LightIndirectPBR, includeKey: "LightIndirectPBR.glsl" }, + { source: VertexPBR, includeKey: "VertexPBR.glsl" }, + { source: BRDF, includeKey: "BRDF.glsl" }, + { source: LightIndirectFunctions, includeKey: "LightIndirectFunctions.glsl" }, + { source: ReflectionLobe, includeKey: "ReflectionLobe.glsl" } +]; diff --git a/packages/shader-shaderlab/tsconfig.json b/packages/shader-shaderlab/tsconfig.json new file mode 100644 index 0000000000..588b0055c3 --- /dev/null +++ b/packages/shader-shaderlab/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "declaration": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "declarationDir": "types", + "emitDeclarationOnly": true, + "noImplicitOverride": true, + "sourceMap": true, + "incremental": false, + "skipLibCheck": true, + "stripInternal": true + }, + "include": ["src/**/*"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e5f8db82b6..736f18b0ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,7 +46,7 @@ importers: version: 6.21.0(eslint@8.57.1)(typescript@5.6.3) '@vitest/coverage-v8': specifier: 2.1.3 - version: 2.1.3(@vitest/browser@2.1.3(@types/node@18.19.64)(@vitest/spy@2.1.3)(playwright@1.48.2)(typescript@5.6.3)(vite@5.4.11(@types/node@18.19.64)(sass@1.81.0))(vitest@2.1.3))(vitest@2.1.3(@types/node@18.19.64)(@vitest/browser@2.1.3)(msw@2.6.5(@types/node@18.19.64)(typescript@5.6.3))(sass@1.81.0)) + version: 2.1.3(@vitest/browser@2.1.3)(vitest@2.1.3) bumpp: specifier: ^9.5.2 version: 9.8.1(magicast@0.3.5) @@ -247,6 +247,12 @@ importers: specifier: workspace:* version: link:../design + packages/shader-shaderlab: + devDependencies: + '@galacean/engine': + specifier: workspace:* + version: link:../galacean + packages/xr: devDependencies: '@galacean/engine': @@ -5335,7 +5341,7 @@ snapshots: - utf-8-validate - vite - '@vitest/coverage-v8@2.1.3(@vitest/browser@2.1.3(@types/node@18.19.64)(@vitest/spy@2.1.3)(playwright@1.48.2)(typescript@5.6.3)(vite@5.4.11(@types/node@18.19.64)(sass@1.81.0))(vitest@2.1.3))(vitest@2.1.3(@types/node@18.19.64)(@vitest/browser@2.1.3)(msw@2.6.5(@types/node@18.19.64)(typescript@5.6.3))(sass@1.81.0))': + '@vitest/coverage-v8@2.1.3(@vitest/browser@2.1.3)(vitest@2.1.3)': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 diff --git a/rollup.config.js b/rollup.config.js index c253121a51..4ef87b8cc7 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -28,14 +28,13 @@ const shaderLabPkg = pkgs.find((item) => item.pkgJson.name === "@galacean/engine pkgs.push({ ...shaderLabPkg, verboseMode: true }); // toGlobalName - const extensions = [".js", ".jsx", ".ts", ".tsx"]; const mainFields = NODE_ENV === "development" ? ["debug", "module", "main"] : undefined; const commonPlugins = [ resolve({ extensions, preferBuiltins: true, mainFields }), glslify({ - include: [/\.glsl$/] + include: [/\.(glsl|gs)$/] }), swc( defineRollupSwcOption({ From 24abef3835d08d7e9d0658d75d303a75fa228a9f Mon Sep 17 00:00:00 2001 From: GuoLei1990 Date: Tue, 17 Dec 2024 18:02:28 +0800 Subject: [PATCH 03/20] chore: release v1.4.0-alpha.1 --- e2e/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader-shaderlab/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 8c14a1756d..6ded5f85c8 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "1.4.0-alpha.0", + "version": "1.4.0-alpha.1", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/package.json b/package.json index 0aa6402923..2c97e24bcb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "1.4.0-alpha.0", + "version": "1.4.0-alpha.1", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index e4e543a0de..3c86b6862d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "1.4.0-alpha.0", + "version": "1.4.0-alpha.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index ec1cb79509..de84cbee13 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "1.4.0-alpha.0", + "version": "1.4.0-alpha.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index 638219210d..303c26359a 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "1.4.0-alpha.0", + "version": "1.4.0-alpha.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index 4d2febcc9e..3c50e2a940 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "1.4.0-alpha.0", + "version": "1.4.0-alpha.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index 27b5fa2053..607d204353 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "1.4.0-alpha.0", + "version": "1.4.0-alpha.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index 1fa5fb121c..60f7fee0f6 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "1.4.0-alpha.0", + "version": "1.4.0-alpha.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index 9e57f342ff..e97326536e 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "1.4.0-alpha.0", + "version": "1.4.0-alpha.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index f52cd84a7f..999c4a2bf6 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "1.4.0-alpha.0", + "version": "1.4.0-alpha.1", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index a0c988d481..98a75b186d 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader-lab", - "version": "1.4.0-alpha.0", + "version": "1.4.0-alpha.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader-shaderlab/package.json b/packages/shader-shaderlab/package.json index 04a3e9060e..daf6594bbb 100644 --- a/packages/shader-shaderlab/package.json +++ b/packages/shader-shaderlab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader-shaderlab", - "version": "1.4.0-alpha.0", + "version": "1.4.0-alpha.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index 95ba11989a..2736f7514d 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "1.4.0-alpha.0", + "version": "1.4.0-alpha.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index a9b080a3cd..e726dc35e3 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "1.4.0-alpha.0", + "version": "1.4.0-alpha.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index ff6a458f72..fbbc9f27f4 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "1.4.0-alpha.0", + "version": "1.4.0-alpha.1", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From aa07cd890583c772d23137afab16609b23dd0760 Mon Sep 17 00:00:00 2001 From: SwayYan Date: Wed, 18 Dec 2024 16:14:40 +0800 Subject: [PATCH 04/20] Disable glsl compress for non-mini package (#2469) * feat: disable glsl compress for non mini package --- rollup.config.js | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/rollup.config.js b/rollup.config.js index 4ef87b8cc7..278ec5cfdd 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -31,11 +31,14 @@ pkgs.push({ ...shaderLabPkg, verboseMode: true }); const extensions = [".js", ".jsx", ".ts", ".tsx"]; const mainFields = NODE_ENV === "development" ? ["debug", "module", "main"] : undefined; +const glslifyPlugin = glslify({ + include: [/\.(glsl|gs)$/], + compress: false +}); + const commonPlugins = [ resolve({ extensions, preferBuiltins: true, mainFields }), - glslify({ - include: [/\.(glsl|gs)$/] - }), + glslifyPlugin, swc( defineRollupSwcOption({ include: /\.[mc]?[jt]sx?$/, @@ -81,20 +84,23 @@ function config({ location, pkgJson, verboseMode }) { const umdConfig = pkgJson.umd; let file = path.join(location, "dist", "browser.js"); + if (compress) { + const glslifyPluginIdx = curPlugins.findIndex((item) => item === glslifyPlugin); + curPlugins.splice( + glslifyPluginIdx, + 1, + glslify({ + include: [/\.(glsl|gs)$/], + compress: true + }) + ); + curPlugins.push(minify({ sourceMap: true })); + } + if (verboseMode) { - if (compress) { - curPlugins.push(minify({ sourceMap: true })); - file = path.join(location, "dist", "browser.verbose.min.js"); - } else { - file = path.join(location, "dist", "browser.verbose.js"); - } + file = path.join(location, "dist", compress ? "browser.verbose.min.js" : "browser.verbose.js"); } else { - if (compress) { - curPlugins.push(minify({ sourceMap: true })); - file = path.join(location, "dist", "browser.min.js"); - } else { - file = path.join(location, "dist", "browser.js"); - } + file = path.join(location, "dist", compress ? "browser.min.js" : "browser.js"); } const umdExternal = Object.keys(umdConfig.globals ?? {}); From 66011dd9cf8cb92051e70362f50174b8319b0774 Mon Sep 17 00:00:00 2001 From: luzhuang <364439895@qq.com> Date: Wed, 18 Dec 2024 18:39:37 +0800 Subject: [PATCH 05/20] Support get contact points and get closest point on shape from a point (#2458) * feat: support get contract points and get closest point on shape from a point --- .../core/src/physics/CharacterController.ts | 3 +- packages/core/src/physics/Collision.ts | 42 +++ packages/core/src/physics/ContactPoint.ts | 15 + packages/core/src/physics/PhysicsScene.ts | 35 +- packages/core/src/physics/index.ts | 2 + .../core/src/physics/shape/ColliderShape.ts | 26 +- .../src/physics/shape/PlaneColliderShape.ts | 6 + packages/design/src/physics/ICollision.ts | 37 +++ packages/design/src/physics/IPhysics.ts | 7 +- packages/design/src/physics/index.ts | 1 + .../src/physics/shape/IColliderShape.ts | 9 +- .../physics-lite/src/LiteDynamicCollider.ts | 40 +-- packages/physics-lite/src/LitePhysics.ts | 7 +- packages/physics-lite/src/LitePhysicsScene.ts | 14 +- .../src/shape/LiteBoxColliderShape.ts | 48 ++- .../src/shape/LiteColliderShape.ts | 21 +- .../src/shape/LiteSphereColliderShape.ts | 25 +- packages/physics-physx/src/PhysXPhysics.ts | 9 +- .../physics-physx/src/PhysXPhysicsScene.ts | 32 +- .../src/shape/PhysXColliderShape.ts | 16 +- tests/src/core/physics/ColliderShape.test.ts | 308 +++++++++++++++++- tests/src/core/physics/Collision.test.ts | 69 ++++ 22 files changed, 692 insertions(+), 80 deletions(-) create mode 100644 packages/core/src/physics/ContactPoint.ts create mode 100644 packages/design/src/physics/ICollision.ts create mode 100644 tests/src/core/physics/Collision.test.ts diff --git a/packages/core/src/physics/CharacterController.ts b/packages/core/src/physics/CharacterController.ts index a4d1b49ccc..d96eae2048 100644 --- a/packages/core/src/physics/CharacterController.ts +++ b/packages/core/src/physics/CharacterController.ts @@ -5,7 +5,7 @@ import { Collider } from "./Collider"; import { PhysicsScene } from "./PhysicsScene"; import { ControllerNonWalkableMode } from "./enums/ControllerNonWalkableMode"; import { ColliderShape } from "./shape"; -import { deepClone } from "../clone/CloneManager"; +import { deepClone, ignoreClone } from "../clone/CloneManager"; /** * The character controllers. @@ -166,6 +166,7 @@ export class CharacterController extends Collider { (this._nativeCollider).getWorldPosition(this.entity.transform.worldPosition); } + @ignoreClone private _setUpDirection(): void { (this._nativeCollider).setUpDirection(this._upDirection); } diff --git a/packages/core/src/physics/Collision.ts b/packages/core/src/physics/Collision.ts index db6497bf10..5638a35980 100644 --- a/packages/core/src/physics/Collision.ts +++ b/packages/core/src/physics/Collision.ts @@ -1,5 +1,47 @@ +import { ContactPoint } from "./ContactPoint"; import { ColliderShape } from "./shape"; +import { ICollision } from "@galacean/engine-design"; +/** + * Collision information between two shapes when they collide. + */ export class Collision { + /** @internal */ + _nativeCollision: ICollision; + + /** The target shape be collided. */ shape: ColliderShape; + + /** + * Count of contact points. + */ + get contactCount(): number { + return this._nativeCollision.contactCount; + } + + /** + * Get contact points. + * @param outContacts - The result of contact points + * @returns The actual count of contact points + * + * @remarks To optimize performance, the engine does not modify the length of the array you pass. + * You need to obtain the actual number of contact points from the function's return value. + */ + getContacts(outContacts: ContactPoint[]): number { + const nativeCollision = this._nativeCollision; + const factor = nativeCollision.shape0Id < nativeCollision.shape1Id ? 1 : -1; + + const nativeContactPoints = nativeCollision.getContacts(); + const length = nativeContactPoints.size(); + for (let i = 0; i < length; i++) { + const nativeContractPoint = nativeContactPoints.get(i); + + const contact = (outContacts[i] ||= new ContactPoint()); + contact.position.copyFrom(nativeContractPoint.position); + contact.normal.copyFrom(nativeContractPoint.normal).scale(factor); + contact.impulse.copyFrom(nativeContractPoint.impulse).scale(factor); + contact.separation = nativeContractPoint.separation; + } + return length; + } } diff --git a/packages/core/src/physics/ContactPoint.ts b/packages/core/src/physics/ContactPoint.ts new file mode 100644 index 0000000000..ac24ab3ae8 --- /dev/null +++ b/packages/core/src/physics/ContactPoint.ts @@ -0,0 +1,15 @@ +import { Vector3 } from "@galacean/engine-math"; + +/** + * Describes a contact point where the collision occurs. + */ +export class ContactPoint { + /** The position of the contact point between the shapes, in world space. */ + readonly position = new Vector3(); + /** The normal of the contacting surfaces at the contact point. The normal direction points from the second shape to the first shape. */ + readonly normal = new Vector3(); + /** The impulse applied at the contact point, in world space. Divide by the simulation time step to get a force value. */ + readonly impulse = new Vector3(); + /** The separation of the shapes at the contact point. A negative separation denotes a penetration. */ + separation: number; +} diff --git a/packages/core/src/physics/PhysicsScene.ts b/packages/core/src/physics/PhysicsScene.ts index 251532dad7..dea373a767 100644 --- a/packages/core/src/physics/PhysicsScene.ts +++ b/packages/core/src/physics/PhysicsScene.ts @@ -1,4 +1,4 @@ -import { ICharacterController, ICollider, IPhysics, IPhysicsScene } from "@galacean/engine-design"; +import { ICharacterController, ICollider, IPhysics, IPhysicsScene, ICollision } from "@galacean/engine-design"; import { MathUtil, Ray, Vector3 } from "@galacean/engine-math"; import { Layer } from "../Layer"; import { Scene } from "../Scene"; @@ -28,14 +28,16 @@ export class PhysicsScene { private _gravity: Vector3 = new Vector3(0, -9.81, 0); private _nativePhysicsScene: IPhysicsScene; - private _onContactEnter = (obj1: number, obj2: number) => { + private _onContactEnter = (nativeCollision: ICollision) => { const physicalObjectsMap = Engine._physicalObjectsMap; - const shape1 = physicalObjectsMap[obj1]; - const shape2 = physicalObjectsMap[obj2]; + const { shape0Id, shape1Id } = nativeCollision; + const shape1 = physicalObjectsMap[shape0Id]; + const shape2 = physicalObjectsMap[shape1Id]; + const collision = PhysicsScene._collision; + collision._nativeCollision = nativeCollision; shape1.collider.entity._scripts.forEach( (element: Script) => { - let collision = PhysicsScene._collision; collision.shape = shape2; element.onCollisionEnter(collision); }, @@ -46,7 +48,6 @@ export class PhysicsScene { shape2.collider.entity._scripts.forEach( (element: Script) => { - let collision = PhysicsScene._collision; collision.shape = shape1; element.onCollisionEnter(collision); }, @@ -56,14 +57,16 @@ export class PhysicsScene { ); }; - private _onContactExit = (obj1: number, obj2: number) => { + private _onContactExit = (nativeCollision: ICollision) => { const physicalObjectsMap = Engine._physicalObjectsMap; - const shape1 = physicalObjectsMap[obj1]; - const shape2 = physicalObjectsMap[obj2]; + const { shape0Id, shape1Id } = nativeCollision; + const shape1 = physicalObjectsMap[shape0Id]; + const shape2 = physicalObjectsMap[shape1Id]; + const collision = PhysicsScene._collision; + collision._nativeCollision = nativeCollision; shape1.collider.entity._scripts.forEach( (element: Script) => { - let collision = PhysicsScene._collision; collision.shape = shape2; element.onCollisionExit(collision); }, @@ -74,7 +77,6 @@ export class PhysicsScene { shape2.collider.entity._scripts.forEach( (element: Script) => { - let collision = PhysicsScene._collision; collision.shape = shape1; element.onCollisionExit(collision); }, @@ -83,14 +85,16 @@ export class PhysicsScene { } ); }; - private _onContactStay = (obj1: number, obj2: number) => { + private _onContactStay = (nativeCollision: ICollision) => { const physicalObjectsMap = Engine._physicalObjectsMap; - const shape1 = physicalObjectsMap[obj1]; - const shape2 = physicalObjectsMap[obj2]; + const { shape0Id, shape1Id } = nativeCollision; + const shape1 = physicalObjectsMap[shape0Id]; + const shape2 = physicalObjectsMap[shape1Id]; + const collision = PhysicsScene._collision; + collision._nativeCollision = nativeCollision; shape1.collider.entity._scripts.forEach( (element: Script) => { - let collision = PhysicsScene._collision; collision.shape = shape2; element.onCollisionStay(collision); }, @@ -101,7 +105,6 @@ export class PhysicsScene { shape2.collider.entity._scripts.forEach( (element: Script) => { - let collision = PhysicsScene._collision; collision.shape = shape1; element.onCollisionStay(collision); }, diff --git a/packages/core/src/physics/index.ts b/packages/core/src/physics/index.ts index b7fbe00d86..537bd367d6 100644 --- a/packages/core/src/physics/index.ts +++ b/packages/core/src/physics/index.ts @@ -5,6 +5,8 @@ export { HitResult } from "./HitResult"; export { PhysicsMaterial } from "./PhysicsMaterial"; export { PhysicsScene } from "./PhysicsScene"; export { StaticCollider } from "./StaticCollider"; +export { Collision } from "./Collision"; +export { ContactPoint } from "./ContactPoint"; export * from "./enums"; export * from "./joint"; export * from "./shape"; diff --git a/packages/core/src/physics/shape/ColliderShape.ts b/packages/core/src/physics/shape/ColliderShape.ts index 79e6dc4239..7701561635 100644 --- a/packages/core/src/physics/shape/ColliderShape.ts +++ b/packages/core/src/physics/shape/ColliderShape.ts @@ -80,7 +80,7 @@ export abstract class ColliderShape implements ICustomClone { } /** - * The local rotation of this ColliderShape. + * The local rotation of this ColliderShape, in radians. */ get rotation(): Vector3 { return this._rotation; @@ -133,6 +133,30 @@ export abstract class ColliderShape implements ICustomClone { Engine._physicalObjectsMap[this._id] = this; } + /** + * Get the distance and the closest point on the shape from a point. + * @param point - Location in world space you want to find the closest point to + * @param outClosestPoint - The closest point on the shape in world space + * @returns The distance between the point and the shape + */ + getClosestPoint(point: Vector3, outClosestPoint: Vector3): number { + const collider = this._collider; + if (collider.enabled === false || collider.entity._isActiveInHierarchy === false) { + console.warn("The collider is not active in scene."); + return -1; + } + + const res = this._nativeShape.pointDistance(point); + const distance = res.w; + if (distance > 0) { + outClosestPoint.set(res.x, res.y, res.z); + } else { + outClosestPoint.copyFrom(point); + } + + return Math.sqrt(distance); + } + /** * @internal */ diff --git a/packages/core/src/physics/shape/PlaneColliderShape.ts b/packages/core/src/physics/shape/PlaneColliderShape.ts index 151ec3d10a..82e7f77a58 100644 --- a/packages/core/src/physics/shape/PlaneColliderShape.ts +++ b/packages/core/src/physics/shape/PlaneColliderShape.ts @@ -1,3 +1,4 @@ +import { Vector3 } from "@galacean/engine-math"; import { PhysicsScene } from "../PhysicsScene"; import { ColliderShape } from "./ColliderShape"; @@ -9,4 +10,9 @@ export class PlaneColliderShape extends ColliderShape { super(); this._nativeShape = PhysicsScene._nativePhysics.createPlaneColliderShape(this._id, this._material._nativeMaterial); } + + override getClosestPoint(point: Vector3, closestPoint: Vector3): number { + console.error("PlaneColliderShape is not support getClosestPoint"); + return -1; + } } diff --git a/packages/design/src/physics/ICollision.ts b/packages/design/src/physics/ICollision.ts new file mode 100644 index 0000000000..c0dd650797 --- /dev/null +++ b/packages/design/src/physics/ICollision.ts @@ -0,0 +1,37 @@ +/** + * Interface of collision. + */ +export interface ICollision { + /** The unique id of the first collider. */ + shape0Id: number; + /** The unique id of the second collider. */ + shape1Id: number; + /** Count of contact points. */ + contactCount: number; + /** Get contact points. */ + getContacts(): VectorContactPairPoint; +} + +interface VectorContactPairPoint { + size(): number; + get(index: number): IContactPoint; +} + +interface IContactPoint { + position: { + x: number; + y: number; + z: number; + }; + normal: { + x: number; + y: number; + z: number; + }; + impulse: { + x: number; + y: number; + z: number; + }; + separation: number; +} diff --git a/packages/design/src/physics/IPhysics.ts b/packages/design/src/physics/IPhysics.ts index 80f7aab202..4ddaa242fc 100644 --- a/packages/design/src/physics/IPhysics.ts +++ b/packages/design/src/physics/IPhysics.ts @@ -8,6 +8,7 @@ import { IPhysicsScene } from "./IPhysicsScene"; import { IStaticCollider } from "./IStaticCollider"; import { IFixedJoint, IHingeJoint, ISpringJoint } from "./joints"; import { IBoxColliderShape, ICapsuleColliderShape, IPlaneColliderShape, ISphereColliderShape } from "./shape"; +import { ICollision } from "./ICollision"; /** * The interface of physics creation. @@ -36,9 +37,9 @@ export interface IPhysics { */ createPhysicsScene( physicsManager: IPhysicsManager, - onContactEnter?: (obj1: number, obj2: number) => void, - onContactExit?: (obj1: number, obj2: number) => void, - onContactStay?: (obj1: number, obj2: number) => void, + onContactEnter?: (collision: ICollision) => void, + onContactExit?: (collision: ICollision) => void, + onContactStay?: (collision: ICollision) => void, onTriggerEnter?: (obj1: number, obj2: number) => void, onTriggerExit?: (obj1: number, obj2: number) => void, onTriggerStay?: (obj1: number, obj2: number) => void diff --git a/packages/design/src/physics/index.ts b/packages/design/src/physics/index.ts index abe6ec7c8f..88404cfb8b 100644 --- a/packages/design/src/physics/index.ts +++ b/packages/design/src/physics/index.ts @@ -6,5 +6,6 @@ export type { IPhysicsMaterial } from "./IPhysicsMaterial"; export type { IPhysicsScene } from "./IPhysicsScene"; export type { IPhysicsManager } from "./IPhysicsManager"; export type { IStaticCollider } from "./IStaticCollider"; +export type { ICollision } from "./ICollision"; export * from "./joints/index"; export * from "./shape/index"; diff --git a/packages/design/src/physics/shape/IColliderShape.ts b/packages/design/src/physics/shape/IColliderShape.ts index 0320a208fe..dd3df38eda 100644 --- a/packages/design/src/physics/shape/IColliderShape.ts +++ b/packages/design/src/physics/shape/IColliderShape.ts @@ -1,4 +1,4 @@ -import { Vector3 } from "@galacean/engine-math"; +import { Quaternion, Vector3, Vector4 } from "@galacean/engine-math"; import { IPhysicsMaterial } from "../IPhysicsMaterial"; /** @@ -41,6 +41,13 @@ export interface IColliderShape { */ setIsTrigger(value: boolean): void; + /** + * Get the distance between a point and the shape. + * @param point - Location in world space you want to find the closest point to + * @returns The x, y, and z components of the Vector4 represent the closest point on the shape in world space, + * and the w component represents the distance between the point and the shape + */ + pointDistance(point: Vector3): Vector4; /** * Decrements the reference count of a shape and releases it if the new reference count is zero. */ diff --git a/packages/physics-lite/src/LiteDynamicCollider.ts b/packages/physics-lite/src/LiteDynamicCollider.ts index 2848398797..0660cb18a2 100644 --- a/packages/physics-lite/src/LiteDynamicCollider.ts +++ b/packages/physics-lite/src/LiteDynamicCollider.ts @@ -1,6 +1,6 @@ import { LiteCollider } from "./LiteCollider"; import { IDynamicCollider } from "@galacean/engine-design"; -import { Quaternion, Vector3 } from "@galacean/engine"; +import { Logger, Quaternion, Vector3 } from "@galacean/engine"; /** * A dynamic collider can act with self-defined movement or physical force @@ -23,14 +23,14 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide * {@inheritDoc IDynamicCollider.getInertiaTensor } */ getInertiaTensor(out: Vector3): Vector3 { - console.error("Physics-lite don't support getInertiaTensor. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support getInertiaTensor. Use Physics-PhysX instead!"); return out; } /** * {@inheritDoc IDynamicCollider.getCenterOfMass } */ getCenterOfMass(out: Vector3): Vector3 { - console.error("Physics-lite don't support getCenterOfMass. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support getCenterOfMass. Use Physics-PhysX instead!"); return out; } @@ -38,7 +38,7 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide * {@inheritDoc IDynamicCollider.setMassAndUpdateInertia } */ setMassAndUpdateInertia(mass: number): void { - console.error("Physics-lite don't support setMassAndUpdateInertia. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setMassAndUpdateInertia. Use Physics-PhysX instead!"); } /** @@ -87,14 +87,14 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide * {@inheritDoc IDynamicCollider.setAngularDamping } */ setAngularDamping(value: number): void { - console.error("Physics-lite don't support setAngularDamping. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setAngularDamping. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.getAngularVelocity } */ getAngularVelocity(out: Vector3): Vector3 { - console.error("Physics-lite don't support getAngularVelocity. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support getAngularVelocity. Use Physics-PhysX instead!"); return out; } @@ -102,42 +102,42 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide * {@inheritDoc IDynamicCollider.setAngularVelocity } */ setAngularVelocity(value: Vector3): void { - console.error("Physics-lite don't support setAngularVelocity. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setAngularVelocity. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setCenterOfMass } */ setCenterOfMass(value: Vector3): void { - console.error("Physics-lite don't support setCenterOfMass. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setCenterOfMass. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setCollisionDetectionMode } */ setCollisionDetectionMode(value: number): void { - console.error("Physics-lite don't support setCollisionDetectionMode. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setCollisionDetectionMode. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setConstraints } */ setConstraints(flags: number): void { - console.error("Physics-lite don't support setConstraints. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setConstraints. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setInertiaTensor } */ setInertiaTensor(value: Vector3): void { - console.error("Physics-lite don't support setInertiaTensor. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setInertiaTensor. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setIsKinematic } */ setIsKinematic(value: boolean): void { - console.error("Physics-lite don't support setIsKinematic. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setIsKinematic. Use Physics-PhysX instead!"); } /** @@ -150,14 +150,14 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide * {@inheritDoc IDynamicCollider.setLinearDamping } */ setLinearDamping(value: number): void { - console.error("Physics-lite don't support setLinearDamping. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setLinearDamping. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.getLinearVelocity } */ getLinearVelocity(out: Vector3): Vector3 { - console.error("Physics-lite don't support getLinearVelocity. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support getLinearVelocity. Use Physics-PhysX instead!"); return out; } @@ -165,21 +165,21 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide * {@inheritDoc IDynamicCollider.setLinearVelocity } */ setLinearVelocity(value: Vector3): void { - console.error("Physics-lite don't support setLinearVelocity. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setLinearVelocity. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setMass } */ setMass(value: number): void { - console.error("Physics-lite don't support setMass. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setMass. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setMaxAngularVelocity } */ setMaxAngularVelocity(value: number): void { - console.error("Physics-lite don't support setMaxAngularVelocity. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setMaxAngularVelocity. Use Physics-PhysX instead!"); } /** @@ -193,21 +193,21 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide * {@inheritDoc IDynamicCollider.setMaxDepenetrationVelocity } */ setMaxDepenetrationVelocity(value: number): void { - console.error("Physics-lite don't support setMaxDepenetrationVelocity. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setMaxDepenetrationVelocity. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setSleepThreshold } */ setSleepThreshold(value: number): void { - console.error("Physics-lite don't support setSleepThreshold. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setSleepThreshold. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setSolverIterations } */ setSolverIterations(value: number): void { - console.error("Physics-lite don't support setSolverIterations. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setSolverIterations. Use Physics-PhysX instead!"); } /** diff --git a/packages/physics-lite/src/LitePhysics.ts b/packages/physics-lite/src/LitePhysics.ts index 5422c6a126..b666f857f0 100644 --- a/packages/physics-lite/src/LitePhysics.ts +++ b/packages/physics-lite/src/LitePhysics.ts @@ -3,6 +3,7 @@ import { IBoxColliderShape, ICapsuleColliderShape, ICharacterController, + ICollision, IDynamicCollider, IFixedJoint, IHingeJoint, @@ -43,9 +44,9 @@ export class LitePhysics implements IPhysics { */ createPhysicsScene( physicsManager: LitePhysicsManager, - onContactBegin?: (obj1: number, obj2: number) => void, - onContactEnd?: (obj1: number, obj2: number) => void, - onContactPersist?: (obj1: number, obj2: number) => void, + onContactBegin?: (collision: ICollision) => void, + onContactEnd?: (collision: ICollision) => void, + onContactPersist?: (collision: ICollision) => void, onTriggerBegin?: (obj1: number, obj2: number) => void, onTriggerEnd?: (obj1: number, obj2: number) => void, onTriggerPersist?: (obj1: number, obj2: number) => void diff --git a/packages/physics-lite/src/LitePhysicsScene.ts b/packages/physics-lite/src/LitePhysicsScene.ts index d1732f150d..13e1bb11ca 100644 --- a/packages/physics-lite/src/LitePhysicsScene.ts +++ b/packages/physics-lite/src/LitePhysicsScene.ts @@ -1,5 +1,5 @@ import { BoundingBox, BoundingSphere, CollisionUtil, DisorderedArray, Ray, Vector3 } from "@galacean/engine"; -import { ICharacterController, IPhysicsScene } from "@galacean/engine-design"; +import { ICharacterController, ICollision, IPhysicsScene } from "@galacean/engine-design"; import { LiteCollider } from "./LiteCollider"; import { LiteDynamicCollider } from "./LiteDynamicCollider"; import { LiteHitResult } from "./LiteHitResult"; @@ -17,9 +17,9 @@ export class LitePhysicsScene implements IPhysicsScene { private static _currentHit: LiteHitResult = new LiteHitResult(); private static _hitResult: LiteHitResult = new LiteHitResult(); - private readonly _onContactEnter?: (obj1: number, obj2: number) => void; - private readonly _onContactExit?: (obj1: number, obj2: number) => void; - private readonly _onContactStay?: (obj1: number, obj2: number) => void; + private readonly _onContactEnter?: (collision: ICollision) => void; + private readonly _onContactExit?: (collision: ICollision) => void; + private readonly _onContactStay?: (collision: ICollision) => void; private readonly _onTriggerEnter?: (obj1: number, obj2: number) => void; private readonly _onTriggerExit?: (obj1: number, obj2: number) => void; private readonly _onTriggerStay?: (obj1: number, obj2: number) => void; @@ -34,9 +34,9 @@ export class LitePhysicsScene implements IPhysicsScene { private _eventPool: TriggerEvent[] = []; constructor( - onContactEnter?: (obj1: number, obj2: number) => void, - onContactExit?: (obj1: number, obj2: number) => void, - onContactStay?: (obj1: number, obj2: number) => void, + onContactEnter?: (collision: ICollision) => void, + onContactExit?: (collision: ICollision) => void, + onContactStay?: (collision: ICollision) => void, onTriggerEnter?: (obj1: number, obj2: number) => void, onTriggerExit?: (obj1: number, obj2: number) => void, onTriggerStay?: (obj1: number, obj2: number) => void diff --git a/packages/physics-lite/src/shape/LiteBoxColliderShape.ts b/packages/physics-lite/src/shape/LiteBoxColliderShape.ts index 39a09b624b..af41c8305e 100644 --- a/packages/physics-lite/src/shape/LiteBoxColliderShape.ts +++ b/packages/physics-lite/src/shape/LiteBoxColliderShape.ts @@ -1,4 +1,4 @@ -import { BoundingBox, Ray, Vector3 } from "@galacean/engine"; +import { BoundingBox, Matrix, Quaternion, Ray, Vector3, Vector4 } from "@galacean/engine"; import { IBoxColliderShape } from "@galacean/engine-design"; import { LiteHitResult } from "../LiteHitResult"; import { LitePhysicsMaterial } from "../LitePhysicsMaterial"; @@ -9,6 +9,8 @@ import { LiteColliderShape } from "./LiteColliderShape"; */ export class LiteBoxColliderShape extends LiteColliderShape implements IBoxColliderShape { private static _tempBox: BoundingBox = new BoundingBox(); + private static _tempMatrix: Matrix = new Matrix(); + private static _tempInvMatrix: Matrix = new Matrix(); private _halfSize: Vector3 = new Vector3(); private _sizeScale: Vector3 = new Vector3(1, 1, 1); @@ -55,6 +57,50 @@ export class LiteBoxColliderShape extends LiteColliderShape implements IBoxColli this._setBondingBox(); } + /** + * {@inheritDoc IColliderShape.pointDistance } + */ + override pointDistance(point: Vector3): Vector4 { + const position = LiteColliderShape._tempPos; + const rotation = LiteColliderShape._tempRot; + this._transform.worldMatrix.decompose(position, rotation, LiteColliderShape._tempScale); + const { position: shapePosition } = this._transform; + const m = LiteBoxColliderShape._tempMatrix; + const invM = LiteBoxColliderShape._tempInvMatrix; + const p = LiteColliderShape._tempPoint; + const scale = this._sizeScale; + const boundingBox = LiteBoxColliderShape._tempBox; + + const { _boxMin, _boxMax } = this; + p.copyFrom(_boxMin); + p.subtract(shapePosition); + p.divide(scale); + boundingBox.min.copyFrom(p); + p.copyFrom(_boxMax); + p.subtract(shapePosition); + p.divide(scale); + boundingBox.max.copyFrom(p); + + Matrix.affineTransformation(scale, rotation, position, m); + Matrix.invert(m, invM); + Vector3.transformCoordinate(point, invM, p); + const min = boundingBox.min; + const max = boundingBox.max; + p.x = Math.max(min.x, Math.min(p.x, max.x)); + p.y = Math.max(min.y, Math.min(p.y, max.y)); + p.z = Math.max(min.z, Math.min(p.z, max.z)); + Vector3.transformCoordinate(p, m, p); + + const res = LiteColliderShape._tempVector4; + if (Vector3.equals(p, point)) { + res.set(point.x, point.y, point.z, 0); + } else { + res.set(p.x, p.y, p.z, Vector3.distanceSquared(p, point)); + } + + return res; + } + /** * @internal */ diff --git a/packages/physics-lite/src/shape/LiteColliderShape.ts b/packages/physics-lite/src/shape/LiteColliderShape.ts index c1f2b42d97..a205753972 100644 --- a/packages/physics-lite/src/shape/LiteColliderShape.ts +++ b/packages/physics-lite/src/shape/LiteColliderShape.ts @@ -1,4 +1,4 @@ -import { Matrix, Ray, Vector3 } from "@galacean/engine"; +import { Matrix, Quaternion, Ray, Vector3, Vector4 } from "@galacean/engine"; import { IColliderShape, IPhysicsMaterial } from "@galacean/engine-design"; import { LiteCollider } from "../LiteCollider"; import { LiteHitResult } from "../LiteHitResult"; @@ -9,8 +9,13 @@ import { LiteUpdateFlag } from "../LiteUpdateFlag"; * Abstract class for collider shapes. */ export abstract class LiteColliderShape implements IColliderShape { + protected static _tempPos = new Vector3(); + protected static _tempRot = new Quaternion(); + protected static _tempScale = new Vector3(); + protected static _tempPoint = new Vector3(); + protected static _tempVector4 = new Vector4(); + private static _ray = new Ray(); - private static _tempPoint = new Vector3(); /** @internal */ _id: number; @@ -27,6 +32,8 @@ export abstract class LiteColliderShape implements IColliderShape { /** @internal */ _inverseWorldMatFlag: LiteUpdateFlag; + private _rotation: Vector3 = new Vector3(); + protected constructor() { this._transform.owner = this; this._inverseWorldMatFlag = this._transform.registerWorldChangeFlag(); @@ -36,7 +43,10 @@ export abstract class LiteColliderShape implements IColliderShape { * {@inheritDoc IColliderShape.setRotation } */ setRotation(rotation: Vector3): void { - console.log("Physics-lite don't support setRotation. Use Physics-PhysX instead!"); + if (rotation !== this._rotation) { + this._rotation.copyFrom(rotation); + Quaternion.rotationEuler(rotation.x, rotation.y, rotation.z, this._transform.rotationQuaternion); + } } /** @@ -85,6 +95,11 @@ export abstract class LiteColliderShape implements IColliderShape { console.log("Physics-lite don't support setIsTrigger. Use Physics-PhysX instead!"); } + /** + * {@inheritDoc IColliderShape.pointDistance } + */ + abstract pointDistance(point: Vector3): Vector4; + /** * {@inheritDoc IColliderShape.destroy } */ diff --git a/packages/physics-lite/src/shape/LiteSphereColliderShape.ts b/packages/physics-lite/src/shape/LiteSphereColliderShape.ts index 50052823a5..9bc4a42cd9 100644 --- a/packages/physics-lite/src/shape/LiteSphereColliderShape.ts +++ b/packages/physics-lite/src/shape/LiteSphereColliderShape.ts @@ -1,6 +1,6 @@ import { ISphereColliderShape } from "@galacean/engine-design"; import { LiteColliderShape } from "./LiteColliderShape"; -import { BoundingSphere, Quaternion, Ray, Vector3 } from "@galacean/engine"; +import { BoundingSphere, Quaternion, Ray, Vector3, Vector4 } from "@galacean/engine"; import { LiteHitResult } from "../LiteHitResult"; import { LitePhysicsMaterial } from "../LitePhysicsMaterial"; @@ -44,6 +44,29 @@ export class LiteSphereColliderShape extends LiteColliderShape implements ISpher this._maxScale = Math.max(Math.abs(scale.x), Math.abs(scale.y), Math.abs(scale.z)); } + /** + * {@inheritDoc IColliderShape.pointDistance } + */ + override pointDistance(point: Vector3): Vector4 { + const position = LiteColliderShape._tempPos; + this._transform.worldMatrix.decompose(position, LiteColliderShape._tempRot, LiteColliderShape._tempScale); + const p = LiteColliderShape._tempPoint; + Vector3.subtract(point, position, p); + const direction = p.normalize(); + + Vector3.scale(direction, this.worldRadius, p); + p.add(position); + + const res = LiteColliderShape._tempVector4; + if (Vector3.equals(p, point)) { + res.set(point.x, point.y, point.z, 0); + } else { + res.set(p.x, p.y, p.z, Vector3.distanceSquared(p, point)); + } + + return res; + } + /** * @internal */ diff --git a/packages/physics-physx/src/PhysXPhysics.ts b/packages/physics-physx/src/PhysXPhysics.ts index f061405803..9f3c68b714 100644 --- a/packages/physics-physx/src/PhysXPhysics.ts +++ b/packages/physics-physx/src/PhysXPhysics.ts @@ -3,6 +3,7 @@ import { IBoxColliderShape, ICapsuleColliderShape, ICharacterController, + ICollision, IDynamicCollider, IFixedJoint, IHingeJoint, @@ -91,7 +92,7 @@ export class PhysXPhysics implements IPhysics { if (runtimeMode == PhysXRuntimeMode.JavaScript) { script.src = `https://mdn.alipayobjects.com/rms/afts/file/A*rnDeR58NNGoAAAAAAAAAAAAAARQnAQ/physx.release.js.js`; } else if (runtimeMode == PhysXRuntimeMode.WebAssembly) { - script.src = `https://mdn.alipayobjects.com/rms/afts/file/A*rP-bRKBDf0YAAAAAAAAAAAAAARQnAQ/physx.release.js`; + script.src = `https://mdn.alipayobjects.com/rms/afts/file/A*04GyRKeSJw4AAAAAAAAAAAAAARQnAQ/physx.release.js`; } }); @@ -139,9 +140,9 @@ export class PhysXPhysics implements IPhysics { */ createPhysicsScene( physicsManager: PhysXPhysicsManager, - onContactBegin?: (obj1: number, obj2: number) => void, - onContactEnd?: (obj1: number, obj2: number) => void, - onContactStay?: (obj1: number, obj2: number) => void, + onContactBegin?: (collision: ICollision) => void, + onContactEnd?: (collision: ICollision) => void, + onContactStay?: (collision: ICollision) => void, onTriggerBegin?: (obj1: number, obj2: number) => void, onTriggerEnd?: (obj1: number, obj2: number) => void, onTriggerStay?: (obj1: number, obj2: number) => void diff --git a/packages/physics-physx/src/PhysXPhysicsScene.ts b/packages/physics-physx/src/PhysXPhysicsScene.ts index 805001bb91..8535227224 100644 --- a/packages/physics-physx/src/PhysXPhysicsScene.ts +++ b/packages/physics-physx/src/PhysXPhysicsScene.ts @@ -1,5 +1,5 @@ import { Ray, Vector3, DisorderedArray } from "@galacean/engine"; -import { IPhysicsScene } from "@galacean/engine-design"; +import { ICollision, IPhysicsScene } from "@galacean/engine-design"; import { PhysXCharacterController } from "./PhysXCharacterController"; import { PhysXCollider } from "./PhysXCollider"; import { PhysXPhysics } from "./PhysXPhysics"; @@ -22,12 +22,12 @@ export class PhysXPhysicsScene implements IPhysicsScene { private _pxScene: any; - private readonly _onContactEnter?: (obj1: number, obj2: number) => void; - private readonly _onContactExit?: (obj1: number, obj2: number) => void; - private readonly _onContactStay?: (obj1: number, obj2: number) => void; - private readonly _onTriggerEnter?: (obj1: number, obj2: number) => void; - private readonly _onTriggerExit?: (obj1: number, obj2: number) => void; - private readonly _onTriggerStay?: (obj1: number, obj2: number) => void; + private readonly _onContactEnter?: (collision: ICollision) => void; + private readonly _onContactExit?: (collision: ICollision) => void; + private readonly _onContactStay?: (collision: ICollision) => void; + private readonly _onTriggerEnter?: (index1: number, index2: number) => void; + private readonly _onTriggerExit?: (index1: number, index2: number) => void; + private readonly _onTriggerStay?: (index1: number, index2: number) => void; private _currentEvents: DisorderedArray = new DisorderedArray(); @@ -36,9 +36,9 @@ export class PhysXPhysicsScene implements IPhysicsScene { constructor( physXPhysics: PhysXPhysics, physicsManager: PhysXPhysicsManager, - onContactEnter?: (obj1: number, obj2: number) => void, - onContactExit?: (obj1: number, obj2: number) => void, - onContactStay?: (obj1: number, obj2: number) => void, + onContactEnter?: (collision: ICollision) => void, + onContactExit?: (collision: ICollision) => void, + onContactStay?: (collision: ICollision) => void, onTriggerEnter?: (obj1: number, obj2: number) => void, onTriggerExit?: (obj1: number, obj2: number) => void, onTriggerStay?: (obj1: number, obj2: number) => void @@ -60,14 +60,14 @@ export class PhysXPhysicsScene implements IPhysicsScene { this._onTriggerStay = onTriggerStay; const triggerCallback = { - onContactBegin: (index1, index2) => { - this._onContactEnter(index1, index2); + onContactBegin: (collision) => { + this._onContactEnter(collision); }, - onContactEnd: (index1, index2) => { - this._onContactExit(index1, index2); + onContactEnd: (collision) => { + this._onContactExit(collision); }, - onContactPersist: (index1, index2) => { - this._onContactStay(index1, index2); + onContactPersist: (collision) => { + this._onContactStay(collision); }, onTriggerBegin: (index1, index2) => { const event = index1 < index2 ? this._getTrigger(index1, index2) : this._getTrigger(index2, index1); diff --git a/packages/physics-physx/src/shape/PhysXColliderShape.ts b/packages/physics-physx/src/shape/PhysXColliderShape.ts index 170bba49e1..a2565ade4d 100644 --- a/packages/physics-physx/src/shape/PhysXColliderShape.ts +++ b/packages/physics-physx/src/shape/PhysXColliderShape.ts @@ -1,4 +1,4 @@ -import { Quaternion, Vector3, DisorderedArray } from "@galacean/engine"; +import { Quaternion, Vector3, DisorderedArray, Vector4 } from "@galacean/engine"; import { IColliderShape } from "@galacean/engine-design"; import { PhysXCharacterController } from "../PhysXCharacterController"; import { PhysXPhysics } from "../PhysXPhysics"; @@ -20,6 +20,7 @@ export enum ShapeFlag { * Abstract class for collider shapes. */ export abstract class PhysXColliderShape implements IColliderShape { + protected static _tempVector4 = new Vector4(); static readonly halfSqrt: number = 0.70710678118655; static transform = { translation: new Vector3(), @@ -58,7 +59,7 @@ export abstract class PhysXColliderShape implements IColliderShape { */ setRotation(value: Vector3): void { this._rotation = value; - Quaternion.rotationYawPitchRoll(value.x, value.y, value.z, this._physXRotation); + Quaternion.rotationYawPitchRoll(value.y, value.x, value.z, this._physXRotation); this._axis && Quaternion.multiply(this._physXRotation, this._axis, this._physXRotation); this._physXRotation.normalize(); this._setLocalPose(); @@ -125,6 +126,17 @@ export abstract class PhysXColliderShape implements IColliderShape { this._setShapeFlags(this._shapeFlags); } + /** + * {@inheritDoc IColliderShape.pointDistance } + */ + pointDistance(point: Vector3): Vector4 { + const info = this._pxGeometry.pointDistance(this._pxShape.getGlobalPose(), point); + const closestPoint = info.closestPoint; + const res = PhysXColliderShape._tempVector4; + res.set(closestPoint.x, closestPoint.y, closestPoint.z, info.distance); + return res; + } + /** * {@inheritDoc IColliderShape.destroy } */ diff --git a/tests/src/core/physics/ColliderShape.test.ts b/tests/src/core/physics/ColliderShape.test.ts index ed1a650344..12fd2fc17f 100644 --- a/tests/src/core/physics/ColliderShape.test.ts +++ b/tests/src/core/physics/ColliderShape.test.ts @@ -12,22 +12,33 @@ import { Vector3 } from "@galacean/engine-math"; import { WebGLEngine } from "@galacean/engine-rhi-webgl"; import { PhysXPhysics } from "@galacean/engine-physics-physx"; import { describe, beforeAll, beforeEach, expect, it } from "vitest"; +import { LitePhysics } from "@galacean/engine-physics-lite"; -describe("ColliderShape", () => { +describe("ColliderShape PhysX", () => { let dynamicCollider: DynamicCollider; + function formatValue(value: number) { + return Math.round(value * 10000) / 10000; + } + beforeAll(async () => { const engine = await WebGLEngine.create({ canvas: document.createElement("canvas"), physics: new PhysXPhysics() }); engine.run(); const scene = engine.sceneManager.activeScene; + scene.physics.gravity = new Vector3(0, 0, 0); const root = scene.createRootEntity("root"); const roleEntity = root.createChild("role"); + dynamicCollider = roleEntity.addComponent(DynamicCollider); }); beforeEach(() => { + const entity = dynamicCollider.entity; + entity.transform.setPosition(0, 0, 0); + entity.transform.setScale(1, 1, 1); + entity.transform.setRotation(0, 0, 0); dynamicCollider.clearShapes(); }); @@ -240,6 +251,83 @@ describe("ColliderShape", () => { expect(sphereShape.rotation).to.deep.include({ x: 40, y: -182, z: 720 }); }); + it("boxShape getClosestPoint", () => { + const boxShape = new BoxColliderShape(); + boxShape.size.set(1, 2, 3); + boxShape.position.set(2, 3, 4); + boxShape.rotation.set(23, 45, 12); + dynamicCollider.addShape(boxShape); + const entity = dynamicCollider.entity; + const engine = entity.engine; + entity.transform.setPosition(2, 3, 5); + entity.transform.setScale(3, 4, 5); + entity.transform.setRotation(13, -45, 38); + + const point = new Vector3(-9, 7, 6); + const closestPoint = new Vector3(); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + let distance = boxShape.getClosestPoint(point, closestPoint); + expect(formatValue(distance)).to.eq(10.492); + expect(formatValue(closestPoint.x)).to.eq(-16.0876); + expect(formatValue(closestPoint.y)).to.eq(10.7095); + expect(formatValue(closestPoint.z)).to.eq(12.7889); + + entity.transform.setScale(1, 1, 1); + entity.transform.setRotation(0, 0, 0); + + point.set(4, 6, 9); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + distance = boxShape.getClosestPoint(point, closestPoint); + expect(distance).to.eq(0); + expect(formatValue(closestPoint.x)).to.eq(4); + expect(formatValue(closestPoint.y)).to.eq(6); + expect(formatValue(closestPoint.z)).to.eq(9); + }); + + it("sphereShape getClosestPoint", () => { + const sphereShape = new SphereColliderShape(); + sphereShape.radius = 2; + sphereShape.position.set(2, 3, 4); + dynamicCollider.addShape(sphereShape); + const entity = dynamicCollider.entity; + const engine = entity.engine; + entity.transform.setPosition(2, 3, 5); + entity.transform.setScale(3, 4, 5); + entity.transform.setRotation(13, -45, 38); + + const point = new Vector3(14, 8, 10); + const closestPoint = new Vector3(); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + const distance = sphereShape.getClosestPoint(point, closestPoint); + expect(formatValue(distance)).to.eq(21.2571); + expect(formatValue(closestPoint.x)).to.eq(-6.2337); + expect(formatValue(closestPoint.y)).to.eq(10.2538); + expect(formatValue(closestPoint.z)).to.eq(16.1142); + + entity.transform.setScale(1, 1, 1); + entity.transform.setRotation(0, 0, 0); + point.set(4, 6, 9); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + const distance2 = sphereShape.getClosestPoint(point, closestPoint); + expect(distance2).to.eq(0); + expect(closestPoint).to.deep.include({ x: 4, y: 6, z: 9 }); + }); + + it("getClosestPoint with collider disabled", () => { + const sphereShape = new BoxColliderShape(); + dynamicCollider.addShape(sphereShape); + dynamicCollider.enabled = false; + + const point = new Vector3(2, 0, 0); + const closestPoint = new Vector3(); + const distance = sphereShape.getClosestPoint(point, closestPoint); + expect(distance).to.eq(-1); + }); + it("clone", () => { // SphereColliderShape const sphereShape = new SphereColliderShape(); @@ -270,3 +358,221 @@ describe("ColliderShape", () => { expect((newCollider3.shapes[0] as CapsuleColliderShape).height).to.eq(3); }); }); + +describe("ColliderShape Lite", () => { + let dynamicCollider: DynamicCollider; + + function formatValue(value: number) { + return Math.round(value * 10000) / 10000; + } + + beforeAll(async () => { + const engine = await WebGLEngine.create({ canvas: document.createElement("canvas"), physics: new LitePhysics() }); + engine.run(); + + const scene = engine.sceneManager.activeScene; + const root = scene.createRootEntity("root"); + + const roleEntity = root.createChild("role"); + + dynamicCollider = roleEntity.addComponent(DynamicCollider); + }); + + beforeEach(() => { + const entity = dynamicCollider.entity; + entity.transform.setPosition(0, 0, 0); + entity.transform.setScale(1, 1, 1); + entity.transform.setRotation(0, 0, 0); + dynamicCollider.clearShapes(); + }); + + it("BoxColliderShape", () => { + const boxShape = new BoxColliderShape(); + dynamicCollider.addShape(boxShape); + + // Test that set size works correctly. + boxShape.size = new Vector3(1, 2, 3); + expect(boxShape.size).to.deep.include({ x: 1, y: 2, z: 3 }); + + // Test that set trigger works correctly. + boxShape.isTrigger = true; + expect(boxShape.isTrigger).to.eq(true); + + // Test that set contactOffset works correctly. + let contactOffset = boxShape.contactOffset; + boxShape.contactOffset = contactOffset; + expect(boxShape.contactOffset).to.eq(contactOffset); + + contactOffset = 2.4; + boxShape.contactOffset = contactOffset; + expect(boxShape.contactOffset).to.eq(contactOffset); + + contactOffset = 0; + boxShape.contactOffset = contactOffset; + expect(boxShape.contactOffset).to.eq(contactOffset); + + contactOffset = 2.7; + boxShape.contactOffset = contactOffset; + expect(boxShape.contactOffset).to.eq(contactOffset); + + // Test that set material works correctly. + const material = new PhysicsMaterial(); + boxShape.material = material; + expect(boxShape.material).to.eq(material); + + // Test that set position works correctly. + boxShape.position = new Vector3(1, 2, -1); + expect(boxShape.position).to.deep.include({ x: 1, y: 2, z: -1 }); + + // Test that set rotation works correctly. + boxShape.rotation = new Vector3(40, -182, 720); + expect(boxShape.rotation).to.deep.include({ x: 40, y: -182, z: 720 }); + }); + + it("SphereColliderShape", () => { + const sphereShape = new SphereColliderShape(); + dynamicCollider.addShape(sphereShape); + + // Test that set radius works correctly. + let radius = sphereShape.radius; + expect(sphereShape.radius).to.eq(radius); + + radius *= 0.5; + sphereShape.radius = radius; + expect(sphereShape.radius).to.eq(radius); + + radius *= 4; + sphereShape.radius = radius; + expect(sphereShape.radius).to.eq(radius); + + // Test that set trigger works correctly. + sphereShape.isTrigger = true; + expect(sphereShape.isTrigger).to.eq(true); + + // Test that set contactOffset works correctly. + let contactOffset = sphereShape.contactOffset; + sphereShape.contactOffset = contactOffset; + expect(sphereShape.contactOffset).to.eq(contactOffset); + + contactOffset = 2.4; + sphereShape.contactOffset = contactOffset; + expect(sphereShape.contactOffset).to.eq(contactOffset); + + contactOffset = 0; + sphereShape.contactOffset = contactOffset; + expect(sphereShape.contactOffset).to.eq(contactOffset); + + contactOffset = 2.7; + sphereShape.contactOffset = contactOffset; + expect(sphereShape.contactOffset).to.eq(contactOffset); + + // Test that set material works correctly. + const material = new PhysicsMaterial(); + sphereShape.material = material; + expect(sphereShape.material).to.eq(material); + + // Test that set position works correctly. + sphereShape.position = new Vector3(1, 2, -1); + expect(sphereShape.position).to.deep.include({ x: 1, y: 2, z: -1 }); + + // Test that set rotation works correctly. + sphereShape.rotation = new Vector3(40, -182, 720); + expect(sphereShape.rotation).to.deep.include({ x: 40, y: -182, z: 720 }); + }); + + it("boxShape getClosestPoint", () => { + const boxShape = new BoxColliderShape(); + boxShape.size.set(1, 2, 3); + boxShape.position.set(2, 3, 4); + boxShape.rotation.set(23, 45, 12); + dynamicCollider.addShape(boxShape); + const entity = dynamicCollider.entity; + const engine = entity.engine; + entity.transform.setPosition(2, 3, 5); + entity.transform.setScale(3, 4, 5); + entity.transform.setRotation(13, -45, 38); + + const point = new Vector3(-9, 7, 6); + const closestPoint = new Vector3(); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + let distance = boxShape.getClosestPoint(point, closestPoint); + expect(formatValue(distance)).to.eq(10.492); + expect(formatValue(closestPoint.x)).to.eq(-16.0876); + expect(formatValue(closestPoint.y)).to.eq(10.7095); + expect(formatValue(closestPoint.z)).to.eq(12.7889); + + entity.transform.setScale(1, 1, 1); + entity.transform.setRotation(0, 0, 0); + + point.set(4, 6, 9); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + distance = boxShape.getClosestPoint(point, closestPoint); + expect(distance).to.eq(0); + expect(formatValue(closestPoint.x)).to.eq(4); + expect(formatValue(closestPoint.y)).to.eq(6); + expect(formatValue(closestPoint.z)).to.eq(9); + }); + + it("sphereShape getClosestPoint", () => { + const sphereShape = new SphereColliderShape(); + sphereShape.radius = 2; + sphereShape.position.set(2, 3, 4); + dynamicCollider.addShape(sphereShape); + const entity = dynamicCollider.entity; + const engine = entity.engine; + entity.transform.setPosition(2, 3, 5); + entity.transform.setScale(3, 4, 5); + entity.transform.setRotation(13, -45, 38); + + const point = new Vector3(14, 8, 10); + const closestPoint = new Vector3(); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + const distance = sphereShape.getClosestPoint(point, closestPoint); + expect(formatValue(distance)).to.eq(21.2571); + expect(formatValue(closestPoint.x)).to.eq(-6.2337); + expect(formatValue(closestPoint.y)).to.eq(10.2538); + expect(formatValue(closestPoint.z)).to.eq(16.1142); + + entity.transform.setScale(1, 1, 1); + entity.transform.setRotation(0, 0, 0); + point.set(4, 6, 9); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + const distance2 = sphereShape.getClosestPoint(point, closestPoint); + expect(distance2).to.eq(0); + expect(closestPoint).to.deep.include({ x: 4, y: 6, z: 9 }); + }); + + it("getClosestPoint with collider disabled", () => { + const sphereShape = new BoxColliderShape(); + dynamicCollider.addShape(sphereShape); + dynamicCollider.enabled = false; + + const point = new Vector3(2, 0, 0); + const closestPoint = new Vector3(); + const distance = sphereShape.getClosestPoint(point, closestPoint); + expect(distance).to.eq(-1); + }); + + it("clone", () => { + // SphereColliderShape + const sphereShape = new SphereColliderShape(); + sphereShape.radius = 2; + dynamicCollider.addShape(sphereShape); + const newCollider = dynamicCollider.entity.clone().getComponent(DynamicCollider); + expect(newCollider.shapes.length).to.eq(1); + expect((newCollider.shapes[0] as SphereColliderShape).radius).to.eq(2); + + // BoxColliderShape + dynamicCollider.clearShapes(); + const boxShape = new BoxColliderShape(); + boxShape.size = new Vector3(1, 2, 3); + dynamicCollider.addShape(boxShape); + const newCollider2 = dynamicCollider.entity.clone().getComponent(DynamicCollider); + expect(newCollider2.shapes.length).to.eq(1); + expect((newCollider2.shapes[0] as BoxColliderShape).size).to.deep.include({ x: 1, y: 2, z: 3 }); + }); +}); diff --git a/tests/src/core/physics/Collision.test.ts b/tests/src/core/physics/Collision.test.ts new file mode 100644 index 0000000000..63110340ec --- /dev/null +++ b/tests/src/core/physics/Collision.test.ts @@ -0,0 +1,69 @@ +import { BoxColliderShape, DynamicCollider, Entity, Engine, Script, StaticCollider } from "@galacean/engine-core"; +import { Vector3 } from "@galacean/engine-math"; +import { PhysXPhysics } from "@galacean/engine-physics-physx"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { Collision } from "packages/core/types/physics/Collision"; +import { describe, beforeAll, beforeEach, expect, it } from "vitest"; + +describe("Collision", function () { + let rootEntity: Entity; + let engine: Engine; + + function addBox(cubeSize: Vector3, type: typeof DynamicCollider | typeof StaticCollider, pos: Vector3) { + const boxEntity = rootEntity.createChild("BoxEntity"); + boxEntity.transform.setPosition(pos.x, pos.y, pos.z); + + const physicsBox = new BoxColliderShape(); + physicsBox.material.dynamicFriction = 0; + physicsBox.material.staticFriction = 0; + physicsBox.size = cubeSize; + const boxCollider = boxEntity.addComponent(type); + boxCollider.addShape(physicsBox); + return boxEntity; + } + + function formatValue(value: number) { + return Math.round(value * 100000) / 100000; + } + + beforeAll(async function () { + engine = await WebGLEngine.create({ canvas: document.createElement("canvas"), physics: new PhysXPhysics() }); + + rootEntity = engine.sceneManager.activeScene.createRootEntity("root"); + }); + + beforeEach(function () { + rootEntity.clearChildren(); + engine.sceneManager.activeScene.physics.gravity = new Vector3(0, -9.81, 0); + }); + + it("collision info", function () { + engine.sceneManager.activeScene.physics.gravity = new Vector3(0, 0, 0); + const box1 = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(-3, 0, 0)); + const box2 = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(0, 0, 0)); + + return new Promise((done) => { + box1.addComponent( + class extends Script { + onCollisionEnter(other: Collision): void { + expect(other.shape).toBe(box2.getComponent(DynamicCollider).shapes[0]); + expect(other.contactCount).toBe(4); + const contacts = []; + other.getContacts(contacts); + expect(contacts.length).toBe(4); + expect(formatValue(contacts[0].position.x)).toBe(-0.27778); + expect(formatValue(contacts[0].separation)).toBe(-0.22222); + expect(formatValue(contacts[0].normal.x)).toBe(-1); + expect(formatValue(contacts[0].impulse.x)).toBe(-2.93748); + + done(); + } + } + ); + + box1.getComponent(DynamicCollider).applyForce(new Vector3(1000, 0, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + }); + }); +}); From 6fd705094fe805e3d1b1e64795491fcdbe941d0c Mon Sep 17 00:00:00 2001 From: luzhuang <364439895@qq.com> Date: Thu, 19 Dec 2024 10:55:36 +0800 Subject: [PATCH 06/20] Fix closest point error when point internal in lite physics (#2471) * fix: lite closest point error when point internal --- .../physics-lite/src/shape/LiteSphereColliderShape.ts | 8 +++++--- tests/src/core/physics/ColliderShape.test.ts | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/physics-lite/src/shape/LiteSphereColliderShape.ts b/packages/physics-lite/src/shape/LiteSphereColliderShape.ts index 9bc4a42cd9..6d25da3002 100644 --- a/packages/physics-lite/src/shape/LiteSphereColliderShape.ts +++ b/packages/physics-lite/src/shape/LiteSphereColliderShape.ts @@ -49,19 +49,21 @@ export class LiteSphereColliderShape extends LiteColliderShape implements ISpher */ override pointDistance(point: Vector3): Vector4 { const position = LiteColliderShape._tempPos; + const worldRadius = this.worldRadius; this._transform.worldMatrix.decompose(position, LiteColliderShape._tempRot, LiteColliderShape._tempScale); const p = LiteColliderShape._tempPoint; Vector3.subtract(point, position, p); const direction = p.normalize(); - Vector3.scale(direction, this.worldRadius, p); + Vector3.scale(direction, worldRadius, p); p.add(position); const res = LiteColliderShape._tempVector4; - if (Vector3.equals(p, point)) { + const distanceSquared = Vector3.distanceSquared(p, point); + if (distanceSquared <= worldRadius * worldRadius) { res.set(point.x, point.y, point.z, 0); } else { - res.set(p.x, p.y, p.z, Vector3.distanceSquared(p, point)); + res.set(p.x, p.y, p.z, distanceSquared); } return res; diff --git a/tests/src/core/physics/ColliderShape.test.ts b/tests/src/core/physics/ColliderShape.test.ts index 12fd2fc17f..7cb098db9d 100644 --- a/tests/src/core/physics/ColliderShape.test.ts +++ b/tests/src/core/physics/ColliderShape.test.ts @@ -538,12 +538,12 @@ describe("ColliderShape Lite", () => { entity.transform.setScale(1, 1, 1); entity.transform.setRotation(0, 0, 0); - point.set(4, 6, 9); + point.set(3, 6, 9); // @ts-ignore engine.sceneManager.activeScene.physics._update(1 / 60); const distance2 = sphereShape.getClosestPoint(point, closestPoint); expect(distance2).to.eq(0); - expect(closestPoint).to.deep.include({ x: 4, y: 6, z: 9 }); + expect(closestPoint).to.deep.include({ x: 3, y: 6, z: 9 }); }); it("getClosestPoint with collider disabled", () => { From 4b41340a753df79548737748004a7e6c225997e0 Mon Sep 17 00:00:00 2001 From: luzhuang <364439895@qq.com> Date: Fri, 20 Dec 2024 14:24:16 +0800 Subject: [PATCH 07/20] Fix closest point error in lite physics (#2472) * fix: lite closest point error when point internal --- packages/physics-lite/src/shape/LiteSphereColliderShape.ts | 6 ++++-- tests/src/core/physics/ColliderShape.test.ts | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/physics-lite/src/shape/LiteSphereColliderShape.ts b/packages/physics-lite/src/shape/LiteSphereColliderShape.ts index 6d25da3002..4110974684 100644 --- a/packages/physics-lite/src/shape/LiteSphereColliderShape.ts +++ b/packages/physics-lite/src/shape/LiteSphereColliderShape.ts @@ -1,6 +1,6 @@ import { ISphereColliderShape } from "@galacean/engine-design"; import { LiteColliderShape } from "./LiteColliderShape"; -import { BoundingSphere, Quaternion, Ray, Vector3, Vector4 } from "@galacean/engine"; +import { BoundingSphere, Ray, Vector3, Vector4 } from "@galacean/engine"; import { LiteHitResult } from "../LiteHitResult"; import { LitePhysicsMaterial } from "../LitePhysicsMaterial"; @@ -53,6 +53,7 @@ export class LiteSphereColliderShape extends LiteColliderShape implements ISpher this._transform.worldMatrix.decompose(position, LiteColliderShape._tempRot, LiteColliderShape._tempScale); const p = LiteColliderShape._tempPoint; Vector3.subtract(point, position, p); + const distanceFromCenter = p.lengthSquared(); const direction = p.normalize(); Vector3.scale(direction, worldRadius, p); @@ -60,7 +61,8 @@ export class LiteSphereColliderShape extends LiteColliderShape implements ISpher const res = LiteColliderShape._tempVector4; const distanceSquared = Vector3.distanceSquared(p, point); - if (distanceSquared <= worldRadius * worldRadius) { + + if (distanceFromCenter <= worldRadius * worldRadius) { res.set(point.x, point.y, point.z, 0); } else { res.set(p.x, p.y, p.z, distanceSquared); diff --git a/tests/src/core/physics/ColliderShape.test.ts b/tests/src/core/physics/ColliderShape.test.ts index 7cb098db9d..6ea29d627d 100644 --- a/tests/src/core/physics/ColliderShape.test.ts +++ b/tests/src/core/physics/ColliderShape.test.ts @@ -544,6 +544,13 @@ describe("ColliderShape Lite", () => { const distance2 = sphereShape.getClosestPoint(point, closestPoint); expect(distance2).to.eq(0); expect(closestPoint).to.deep.include({ x: 3, y: 6, z: 9 }); + + point.set(8, 6, 9); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + const distance3 = sphereShape.getClosestPoint(point, closestPoint); + expect(distance3).to.eq(2); + expect(closestPoint).to.deep.include({ x: 6, y: 6, z: 9 }); }); it("getClosestPoint with collider disabled", () => { From 30b986c424ca831d3addb67f2cdc7904ba4f0fd1 Mon Sep 17 00:00:00 2001 From: zhuxudong Date: Fri, 20 Dec 2024 18:34:40 +0800 Subject: [PATCH 08/20] Open post process pipeline and support local mode (#2401) * feat: open post process pipeline and support local mode --- e2e/case/multi-camera-no-clear.ts | 17 +- e2e/case/multi-scene-clear.ts | 18 +- e2e/case/multi-scene-no-clear.ts | 17 +- e2e/case/postProcess-HDR-bloom-ACES.ts | 20 +- e2e/case/postProcess-HDR-bloom-neutral.ts | 20 +- e2e/case/postProcess-LDR-bloom-neutral.ts | 20 +- e2e/case/postProcess-customPass.ts | 88 ++++ e2e/config.ts | 5 + .../PostProcess_postProcess-customPass.jpg | 3 + packages/core/src/Camera.ts | 7 +- packages/core/src/Component.ts | 2 + packages/core/src/Engine.ts | 85 ++++ .../src/RenderPipeline/BasicRenderPipeline.ts | 41 +- packages/core/src/RenderPipeline/Blitter.ts | 90 ++++ .../src/RenderPipeline/OpaqueTexturePass.ts | 3 +- .../core/src/RenderPipeline/PipelineUtils.ts | 85 ---- packages/core/src/RenderPipeline/index.ts | 3 +- packages/core/src/Scene.ts | 7 +- packages/core/src/postProcess/PostProcess.ts | 130 ++++++ .../core/src/postProcess/PostProcessEffect.ts | 68 ++++ .../postProcess/PostProcessEffectParameter.ts | 95 +++++ .../src/postProcess/PostProcessManager.ts | 277 +++++++++++-- .../core/src/postProcess/PostProcessPass.ts | 81 ++++ .../src/postProcess/PostProcessUberPass.ts | 277 +++++++++++++ .../src/postProcess/effects/BloomEffect.ts | 383 +++--------------- .../postProcess/effects/TonemappingEffect.ts | 48 +-- packages/core/src/postProcess/index.ts | 34 +- .../core/src/shaderlib/extra/Blit.fs.glsl | 5 +- packages/loader/src/SceneLoader.ts | 26 +- .../src/core/postProcess/PostProcess.test.ts | 379 +++++++++++++++++ .../core/postProcess/PostProcessPass.test.ts | 120 ++++++ 31 files changed, 1823 insertions(+), 631 deletions(-) create mode 100644 e2e/case/postProcess-customPass.ts create mode 100644 e2e/fixtures/originImage/PostProcess_postProcess-customPass.jpg create mode 100644 packages/core/src/RenderPipeline/Blitter.ts create mode 100644 packages/core/src/postProcess/PostProcess.ts create mode 100644 packages/core/src/postProcess/PostProcessEffect.ts create mode 100644 packages/core/src/postProcess/PostProcessEffectParameter.ts create mode 100644 packages/core/src/postProcess/PostProcessPass.ts create mode 100644 packages/core/src/postProcess/PostProcessUberPass.ts create mode 100644 tests/src/core/postProcess/PostProcess.test.ts create mode 100644 tests/src/core/postProcess/PostProcessPass.test.ts diff --git a/e2e/case/multi-camera-no-clear.ts b/e2e/case/multi-camera-no-clear.ts index e605f6c101..1735c31954 100644 --- a/e2e/case/multi-camera-no-clear.ts +++ b/e2e/case/multi-camera-no-clear.ts @@ -4,6 +4,7 @@ */ import { BlinnPhongMaterial, + BloomEffect, Camera, CameraClearFlags, Color, @@ -11,8 +12,10 @@ import { Layer, Logger, MeshRenderer, + PostProcess, PrimitiveMesh, Scene, + TonemappingEffect, WebGLEngine, WebGLMode } from "@galacean/engine"; @@ -67,15 +70,13 @@ function initFirstScene(engine: Engine): Scene { camera2.clearFlags = CameraClearFlags.None; camera2.msaaSamples = 1; - // @ts-ignore - const bloomEffect = scene._postProcessManager._bloomEffect; - // @ts-ignore - const tonemappingEffect = scene._postProcessManager._tonemappingEffect; + const globalPostProcessEntity = scene.createRootEntity(); + const postProcess = globalPostProcessEntity.addComponent(PostProcess); + const bloomEffect = postProcess.addEffect(BloomEffect); + postProcess.addEffect(TonemappingEffect); - bloomEffect.enabled = true; - tonemappingEffect.enabled = true; - bloomEffect.threshold = 0.1; - bloomEffect.intensity = 2; + bloomEffect.threshold.value = 0.1; + bloomEffect.intensity.value = 2; cameraEntity.transform.setPosition(0, 0, 20); // Create cube diff --git a/e2e/case/multi-scene-clear.ts b/e2e/case/multi-scene-clear.ts index c54438a949..770ff91496 100644 --- a/e2e/case/multi-scene-clear.ts +++ b/e2e/case/multi-scene-clear.ts @@ -4,14 +4,16 @@ */ import { BlinnPhongMaterial, + BloomEffect, Camera, - CameraClearFlags, Color, Engine, Logger, MeshRenderer, + PostProcess, PrimitiveMesh, Scene, + TonemappingEffect, WebGLEngine } from "@galacean/engine"; import { initScreenshot, updateForE2E } from "./.mockForE2E"; @@ -60,15 +62,13 @@ function initSecondScene(engine: Engine): Camera { camera.enablePostProcess = true; // camera.clearFlags = CameraClearFlags.None; - // @ts-ignore - const bloomEffect = scene._postProcessManager._bloomEffect; - // @ts-ignore - const tonemappingEffect = scene._postProcessManager._tonemappingEffect; + const globalPostProcessEntity = scene.createRootEntity(); + const postProcess = globalPostProcessEntity.addComponent(PostProcess); + const bloomEffect = postProcess.addEffect(BloomEffect); + postProcess.addEffect(TonemappingEffect); - bloomEffect.enabled = true; - tonemappingEffect.enabled = true; - bloomEffect.threshold = 0.1; - bloomEffect.intensity = 2; + bloomEffect.threshold.value = 0.1; + bloomEffect.intensity.value = 2; cameraEntity.transform.setPosition(0, 0, 20); // Create cube diff --git a/e2e/case/multi-scene-no-clear.ts b/e2e/case/multi-scene-no-clear.ts index 91985fa839..b8c67e6e4a 100644 --- a/e2e/case/multi-scene-no-clear.ts +++ b/e2e/case/multi-scene-no-clear.ts @@ -4,14 +4,17 @@ */ import { BlinnPhongMaterial, + BloomEffect, Camera, CameraClearFlags, Color, Engine, Logger, MeshRenderer, + PostProcess, PrimitiveMesh, Scene, + TonemappingEffect, WebGLEngine, WebGLMode } from "@galacean/engine"; @@ -66,15 +69,13 @@ function initSecondScene(engine: Engine): Camera { camera.enablePostProcess = true; camera.clearFlags = CameraClearFlags.None; - // @ts-ignore - const bloomEffect = scene._postProcessManager._bloomEffect; - // @ts-ignore - const tonemappingEffect = scene._postProcessManager._tonemappingEffect; + const globalPostProcessEntity = scene.createRootEntity(); + const postProcess = globalPostProcessEntity.addComponent(PostProcess); + const bloomEffect = postProcess.addEffect(BloomEffect); + postProcess.addEffect(TonemappingEffect); - bloomEffect.enabled = true; - tonemappingEffect.enabled = true; - bloomEffect.threshold = 0.1; - bloomEffect.intensity = 2; + bloomEffect.threshold.value = 0.1; + bloomEffect.intensity.value = 2; cameraEntity.transform.setPosition(0, 0, 20); // Create cube diff --git a/e2e/case/postProcess-HDR-bloom-ACES.ts b/e2e/case/postProcess-HDR-bloom-ACES.ts index 4ba5483863..3228bc0e7c 100644 --- a/e2e/case/postProcess-HDR-bloom-ACES.ts +++ b/e2e/case/postProcess-HDR-bloom-ACES.ts @@ -2,7 +2,7 @@ * @title Bloom + HDR + ACES Tonemapping * @category PostProcess */ -import { Camera, TonemappingMode } from "@galacean/engine"; +import { BloomEffect, Camera, PostProcess, TonemappingEffect, TonemappingMode } from "@galacean/engine"; import { initPostProcessEnv } from "./.initPostProcessEnv"; initPostProcessEnv((camera: Camera, resArray) => { @@ -11,15 +11,15 @@ initPostProcessEnv((camera: Camera, resArray) => { camera.enablePostProcess = true; camera.enableHDR = true; - // @ts-ignore - const bloomEffect = scene._postProcessManager._bloomEffect; - // @ts-ignore - const tonemappingEffect = scene._postProcessManager._tonemappingEffect; - bloomEffect.enabled = true; - tonemappingEffect.enabled = true; + const globalPostProcessEntity = scene.createRootEntity(); + const postProcess = globalPostProcessEntity.addComponent(PostProcess); + const bloomEffect = postProcess.addEffect(BloomEffect); + const tonemappingEffect = postProcess.addEffect(TonemappingEffect); + tonemappingEffect.mode.value = TonemappingMode.ACES; - bloomEffect.threshold = 0.5; - bloomEffect.dirtTexture = dirtTexture; - tonemappingEffect.mode = TonemappingMode.ACES; + bloomEffect.threshold.value = 0.5; + bloomEffect.intensity.value = 1; + bloomEffect.dirtTexture.value = dirtTexture; + tonemappingEffect.mode.value = TonemappingMode.ACES; }); diff --git a/e2e/case/postProcess-HDR-bloom-neutral.ts b/e2e/case/postProcess-HDR-bloom-neutral.ts index 5fd897feae..fe152ce4cf 100644 --- a/e2e/case/postProcess-HDR-bloom-neutral.ts +++ b/e2e/case/postProcess-HDR-bloom-neutral.ts @@ -2,7 +2,7 @@ * @title Bloom + HDR + Neutral Tonemapping * @category PostProcess */ -import { Camera, TonemappingMode } from "@galacean/engine"; +import { BloomEffect, Camera, PostProcess, TonemappingEffect, TonemappingMode } from "@galacean/engine"; import { initPostProcessEnv } from "./.initPostProcessEnv"; initPostProcessEnv((camera: Camera, resArray) => { @@ -11,15 +11,15 @@ initPostProcessEnv((camera: Camera, resArray) => { camera.enablePostProcess = true; camera.enableHDR = true; - // @ts-ignore - const bloomEffect = scene._postProcessManager._bloomEffect; - // @ts-ignore - const tonemappingEffect = scene._postProcessManager._tonemappingEffect; - bloomEffect.enabled = true; - tonemappingEffect.enabled = true; + const globalPostProcessEntity = scene.createRootEntity(); + const postProcess = globalPostProcessEntity.addComponent(PostProcess); + const bloomEffect = postProcess.addEffect(BloomEffect); + const tonemappingEffect = postProcess.addEffect(TonemappingEffect); + tonemappingEffect.mode.value = TonemappingMode.ACES; - bloomEffect.threshold = 0.5; - bloomEffect.dirtTexture = dirtTexture; - tonemappingEffect.mode = TonemappingMode.Neutral; + bloomEffect.threshold.value = 0.5; + bloomEffect.intensity.value = 1; + bloomEffect.dirtTexture.value = dirtTexture; + tonemappingEffect.mode.value = TonemappingMode.Neutral; }); diff --git a/e2e/case/postProcess-LDR-bloom-neutral.ts b/e2e/case/postProcess-LDR-bloom-neutral.ts index 730e32d742..872b8d5e9c 100644 --- a/e2e/case/postProcess-LDR-bloom-neutral.ts +++ b/e2e/case/postProcess-LDR-bloom-neutral.ts @@ -2,7 +2,7 @@ * @title Bloom + LDR + Neutral Tonemapping * @category PostProcess */ -import { Camera, TonemappingMode } from "@galacean/engine"; +import { BloomEffect, Camera, PostProcess, TonemappingEffect, TonemappingMode } from "@galacean/engine"; import { initPostProcessEnv } from "./.initPostProcessEnv"; initPostProcessEnv((camera: Camera, resArray) => { @@ -11,15 +11,15 @@ initPostProcessEnv((camera: Camera, resArray) => { camera.enablePostProcess = true; camera.enableHDR = false; - // @ts-ignore - const bloomEffect = scene._postProcessManager._bloomEffect; - // @ts-ignore - const tonemappingEffect = scene._postProcessManager._tonemappingEffect; - bloomEffect.enabled = true; - tonemappingEffect.enabled = true; + const globalPostProcessEntity = scene.createRootEntity(); + const postProcess = globalPostProcessEntity.addComponent(PostProcess); + const bloomEffect = postProcess.addEffect(BloomEffect); + const tonemappingEffect = postProcess.addEffect(TonemappingEffect); + tonemappingEffect.mode.value = TonemappingMode.ACES; - bloomEffect.threshold = 0.5; - bloomEffect.dirtTexture = dirtTexture; - tonemappingEffect.mode = TonemappingMode.Neutral; + bloomEffect.threshold.value = 0.5; + bloomEffect.intensity.value = 1; + bloomEffect.dirtTexture.value = dirtTexture; + tonemappingEffect.mode.value = TonemappingMode.Neutral; }); diff --git a/e2e/case/postProcess-customPass.ts b/e2e/case/postProcess-customPass.ts new file mode 100644 index 0000000000..102142159f --- /dev/null +++ b/e2e/case/postProcess-customPass.ts @@ -0,0 +1,88 @@ +/** + * @title Custom post process pass + * @category PostProcess + */ +import { + Blitter, + BloomEffect, + Camera, + Engine, + Material, + PostProcess, + PostProcessPass, + PostProcessPassEvent, + RenderTarget, + Shader, + Texture2D, + TonemappingEffect, + TonemappingMode +} from "@galacean/engine"; +import { initPostProcessEnv } from "./.initPostProcessEnv"; + +const customShader = Shader.create( + "Custom Post Process", + ` + attribute vec4 POSITION_UV; +varying vec2 v_uv; + +void main() { + gl_Position = vec4(POSITION_UV.xy, 0.0, 1.0); + v_uv = POSITION_UV.zw; +} + `, + ` + varying vec2 v_uv; + uniform sampler2D renderer_BlitTexture; + + void main(){ + gl_FragColor = texture2D(renderer_BlitTexture, v_uv).rrra; + } + ` +); + +class CustomPass extends PostProcessPass { + private _blitMaterial: Material; + + set intensity(value) { + this._blitMaterial.shaderData.setFloat("intensity", value); + } + + constructor(engine: Engine) { + super(engine); + this.event = PostProcessPassEvent.AfterUber; + this._blitMaterial = new Material(this.engine, customShader); + + const depthState = this._blitMaterial.renderState.depthState; + + depthState.enabled = false; + depthState.writeEnabled = false; + } + + onRender(_, srcTexture: Texture2D, dst: RenderTarget): void { + const engine = this.engine; + Blitter.blitTexture(engine, srcTexture, dst, undefined, undefined, this._blitMaterial, 0); + } +} + +initPostProcessEnv((camera: Camera, resArray) => { + const [_, __, dirtTexture] = resArray; + const scene = camera.scene; + const engine = scene.engine; + + camera.enablePostProcess = true; + camera.enableHDR = true; + + const globalPostProcessEntity = scene.createRootEntity(); + const postProcess = globalPostProcessEntity.addComponent(PostProcess); + const bloomEffect = postProcess.addEffect(BloomEffect); + const tonemappingEffect = postProcess.addEffect(TonemappingEffect); + tonemappingEffect.mode.value = TonemappingMode.ACES; + + bloomEffect.threshold.value = 0.5; + bloomEffect.intensity.value = 1; + bloomEffect.dirtTexture.value = dirtTexture; + tonemappingEffect.mode.value = TonemappingMode.Neutral; + + const customPass = new CustomPass(engine); + engine.addPostProcessPass(customPass); +}); diff --git a/e2e/config.ts b/e2e/config.ts index 75317f9dc6..5aabc5b725 100644 --- a/e2e/config.ts +++ b/e2e/config.ts @@ -216,6 +216,11 @@ export const E2E_CONFIG = { category: "PostProcess", caseFileName: "postProcess-LDR-bloom-neutral", threshold: 0.2 + }, + customPass: { + category: "PostProcess", + caseFileName: "postProcess-customPass", + threshold: 0.2 } }, SpriteMask: { diff --git a/e2e/fixtures/originImage/PostProcess_postProcess-customPass.jpg b/e2e/fixtures/originImage/PostProcess_postProcess-customPass.jpg new file mode 100644 index 0000000000..06c06c7ca6 --- /dev/null +++ b/e2e/fixtures/originImage/PostProcess_postProcess-customPass.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05fe68a45c0c58edec99ce566927b5b0d425ce13fa56376ae814ce31d75dfa2c +size 203480 diff --git a/packages/core/src/Camera.ts b/packages/core/src/Camera.ts index ea86aaf362..6069f6c44e 100644 --- a/packages/core/src/Camera.ts +++ b/packages/core/src/Camera.ts @@ -65,6 +65,11 @@ export class Camera extends Component { */ cullingMask: Layer = Layer.Everything; + /** + * Determines which PostProcess to use. + */ + postProcessMask: Layer = Layer.Everything; + /** * Depth texture mode. * If `DepthTextureMode.PrePass` is used, the depth texture can be accessed in the shader using `camera_DepthTexture`. @@ -164,7 +169,7 @@ export class Camera extends Component { */ get independentCanvasEnabled(): boolean { // Uber pass need internal RT - if (this.enablePostProcess && this.scene._postProcessManager.hasActiveEffect) { + if (this.enablePostProcess && this.scene.postProcessManager._isValid()) { return true; } diff --git a/packages/core/src/Component.ts b/packages/core/src/Component.ts index 0615805153..b3af438d07 100644 --- a/packages/core/src/Component.ts +++ b/packages/core/src/Component.ts @@ -12,6 +12,7 @@ export class Component extends EngineObject { /** @internal */ @ignoreClone _entity: Entity; + /** @internal */ @ignoreClone _awoken: boolean = false; @@ -21,6 +22,7 @@ export class Component extends EngineObject { @ignoreClone private _phasedActive: boolean = false; + @assignmentClone private _enabled: boolean = true; diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index 5341bfdf40..c015b7613d 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -29,6 +29,8 @@ import { Material } from "./material/Material"; import { ParticleBufferUtils } from "./particle/ParticleBufferUtils"; import { PhysicsScene } from "./physics/PhysicsScene"; import { ColliderShape } from "./physics/shape/ColliderShape"; +import { PostProcessPass } from "./postProcess/PostProcessPass"; +import { PostProcessUberPass } from "./postProcess/PostProcessUberPass"; import { Shader } from "./shader/Shader"; import { ShaderMacro } from "./shader/ShaderMacro"; import { ShaderMacroCollection } from "./shader/ShaderMacroCollection"; @@ -106,6 +108,9 @@ export class Engine extends EventDispatcher { /** @internal @todo: temporary solution */ _macroCollection: ShaderMacroCollection = new ShaderMacroCollection(); + /** @internal */ + _postProcessPassNeedRefresh = false; + /** @internal */ protected _canvas: Canvas; @@ -125,6 +130,8 @@ export class Engine extends EventDispatcher { private _waitingDestroy: boolean = false; private _isDeviceLost: boolean = false; private _waitingGC: boolean = false; + private _postProcessPasses = new Array(); + private _activePostProcessPasses = new Array(); private _animate = () => { if (this._vSyncCount) { @@ -210,6 +217,13 @@ export class Engine extends EventDispatcher { this._targetFrameInterval = 1000 / value; } + /** + * All post process passes. + */ + get postProcessPasses(): ReadonlyArray { + return this._postProcessPasses; + } + /** * Indicates whether the engine is destroyed. */ @@ -261,6 +275,9 @@ export class Engine extends EventDispatcher { this._basicResources = new BasicResources(this); this._particleBufferUtils = new ParticleBufferUtils(this); + + const uberPass = new PostProcessUberPass(this); + this.addPostProcessPass(uberPass); } /** @@ -315,6 +332,7 @@ export class Engine extends EventDispatcher { const { inputManager, _physicsInitialized: physicsInitialized } = this; inputManager._update(); + this._refreshActivePostProcessPasses(); const scenes = this._sceneManager._scenes.getLoopArray(); const sceneCount = scenes.length; @@ -407,6 +425,69 @@ export class Engine extends EventDispatcher { this._hardwareRenderer.forceRestoreDevice(); } + /** + * Add a post process pass. + * @param pass - Post process pass to add + */ + addPostProcessPass(pass: PostProcessPass): void { + if (pass.engine !== this) { + throw "The pass is not belong to this engine."; + } + + const passes = this._postProcessPasses; + if (passes.indexOf(pass) === -1) { + passes.push(pass); + pass.isActive && (this._postProcessPassNeedRefresh = true); + } + } + + /** + * @internal + */ + _removePostProcessPass(pass: PostProcessPass): void { + const passes = this._postProcessPasses; + const index = passes.indexOf(pass); + if (index !== -1) { + passes.splice(index, 1); + + pass.isActive && (this._postProcessPassNeedRefresh = true); + } + } + + /** + * @internal + */ + _refreshActivePostProcessPasses(): void { + if (this._postProcessPassNeedRefresh) { + this._postProcessPassNeedRefresh = false; + + const postProcessPasses = this._postProcessPasses; + const activePostProcesses = this._activePostProcessPasses; + activePostProcesses.length = 0; + + // Filter + for (let i = 0, n = postProcessPasses.length; i < n; i++) { + const pass = postProcessPasses[i]; + if (pass.isActive) { + activePostProcesses.push(pass); + } + } + + // Sort + if (activePostProcesses.length) { + activePostProcesses.sort((a, b) => a.event - b.event); + } + } + } + + /** + * @internal + */ + _getActivePostProcessPasses(): ReadonlyArray { + this._refreshActivePostProcessPasses(); + return this._activePostProcessPasses; + } + private _destroy(): void { this._sceneManager._destroyAllScene(); @@ -498,6 +579,10 @@ export class Engine extends EventDispatcher { (camera: Camera) => { const componentsManager = scene._componentsManager; componentsManager.callCameraOnBeginRender(camera); + + // Update post process manager + scene.postProcessManager._update(camera); + camera.render(); componentsManager.callCameraOnEndRender(camera); diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index 9537acdf7e..b0657b867e 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -1,4 +1,4 @@ -import { Vector2 } from "@galacean/engine-math"; +import { Vector2, Vector4 } from "@galacean/engine-math"; import { Background } from "../Background"; import { Camera } from "../Camera"; import { Logger } from "../base/Logger"; @@ -21,6 +21,7 @@ import { TextureFormat, TextureWrapMode } from "../texture"; +import { Blitter } from "./Blitter"; import { CullingResults } from "./CullingResults"; import { DepthOnlyPass } from "./DepthOnlyPass"; import { OpaqueTexturePass } from "./OpaqueTexturePass"; @@ -47,6 +48,7 @@ export class BasicRenderPipeline { private _grabTexture: Texture2D; private _canUseBlitFrameBuffer = false; private _shouldGrabColor = false; + private _sourceScaleOffset = new Vector4(1, 1, 0, 0); /** * Create a basic render pipeline. @@ -226,7 +228,11 @@ export class BasicRenderPipeline { // Copy RT's color buffer to grab texture rhi.copyRenderTargetToSubTexture(camera.renderTarget, this._grabTexture, camera.viewport); // Then blit grab texture to internal RT's color buffer - PipelineUtils.blitTexture( + const sourceScaleOffset = this._sourceScaleOffset; + sourceScaleOffset.y = camera.renderTarget ? 1 : -1; + sourceScaleOffset.w = camera.renderTarget ? 0 : 1; + // `uv.y = 1.0 - uv.y` if grab from screen + Blitter.blitTexture( engine, this._grabTexture, internalColorTarget, @@ -234,8 +240,7 @@ export class BasicRenderPipeline { undefined, undefined, undefined, - // Only flip Y axis in webgl context - !camera.renderTarget + sourceScaleOffset ); } else { rhi.clearRenderTarget(engine, CameraClearFlags.All, color); @@ -281,20 +286,22 @@ export class BasicRenderPipeline { // Revert stencil buffer generated by mask maskManager.clearMask(context, PipelineStage.Forward); - const postProcessManager = scene._postProcessManager; + const postProcessManager = scene.postProcessManager; const cameraRenderTarget = camera.renderTarget; - if (camera.enablePostProcess && postProcessManager.hasActiveEffect) { - postProcessManager._render(context, internalColorTarget, cameraRenderTarget); - } else if (internalColorTarget) { - internalColorTarget._blitRenderTarget(); - - PipelineUtils.blitTexture( - engine, - internalColorTarget.getColorTexture(0), - cameraRenderTarget, - 0, - camera.viewport - ); + if (camera.enablePostProcess && postProcessManager._isValid()) { + postProcessManager._render(camera, internalColorTarget, cameraRenderTarget); + } else { + postProcessManager._releaseSwapRenderTarget(); + if (internalColorTarget) { + internalColorTarget._blitRenderTarget(); + Blitter.blitTexture( + engine, + internalColorTarget.getColorTexture(0), + cameraRenderTarget, + 0, + camera.viewport + ); + } } cameraRenderTarget?._blitRenderTarget(); diff --git a/packages/core/src/RenderPipeline/Blitter.ts b/packages/core/src/RenderPipeline/Blitter.ts new file mode 100644 index 0000000000..96cf482556 --- /dev/null +++ b/packages/core/src/RenderPipeline/Blitter.ts @@ -0,0 +1,90 @@ +import { Vector4 } from "@galacean/engine-math"; +import { Engine } from "../Engine"; +import { Material } from "../material"; +import { ShaderProperty } from "../shader"; +import { Shader } from "../shader/Shader"; +import { ShaderData } from "../shader/ShaderData"; +import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; +import { ShaderDataGroup } from "../shader/enums/ShaderDataGroup"; +import { RenderTarget, Texture2D } from "../texture"; +import { PipelineUtils } from "./PipelineUtils"; + +/** + * A helper class to blit texture to destination render target. + */ +export class Blitter { + private static _blitTextureProperty = ShaderProperty.getByName("renderer_BlitTexture"); + private static _blitMipLevelProperty = ShaderProperty.getByName("renderer_BlitMipLevel"); + private static _blitTexelSizeProperty = ShaderProperty.getByName("renderer_texelSize"); // x: 1/width, y: 1/height, z: width, w: height + private static _sourceScaleOffsetProperty = ShaderProperty.getByName("renderer_SourceScaleOffset"); + + private static _rendererShaderData = new ShaderData(ShaderDataGroup.Renderer); + private static _texelSize = new Vector4(); + private static _defaultScaleOffset = new Vector4(1, 1, 0, 0); + + /** + * Blit texture to destination render target using a triangle. + * @param engine - Engine + * @param source - Source texture + * @param destination - Destination render target + * @param mipLevel - Mip level to blit + * @param viewport - Viewport + * @param material - The material to use when blit + * @param passIndex - Pass index to use of the provided material + * @param flipYOfSource - Whether flip Y axis of source texture + */ + static blitTexture( + engine: Engine, + source: Texture2D, + destination: RenderTarget | null, + mipLevel: number = 0, + viewport: Vector4 = PipelineUtils.defaultViewport, + material: Material = null, + passIndex = 0, + sourceScaleOffset?: Vector4 + ): void { + const basicResources = engine._basicResources; + const blitMesh = destination ? basicResources.flipYBlitMesh : basicResources.blitMesh; + const blitMaterial = material || basicResources.blitMaterial; + const rhi = engine._hardwareRenderer; + const context = engine._renderContext; + + // We not use projection matrix when blit, but we must modify flipProjection to make front face correct + context.flipProjection = !!destination; + + rhi.activeRenderTarget(destination, viewport, context.flipProjection, 0); + + const rendererShaderData = Blitter._rendererShaderData; + + rendererShaderData.setTexture(Blitter._blitTextureProperty, source); + rendererShaderData.setFloat(Blitter._blitMipLevelProperty, mipLevel); + Blitter._texelSize.set(1 / source.width, 1 / source.height, source.width, source.height); + rendererShaderData.setVector4(Blitter._blitTexelSizeProperty, Blitter._texelSize); + rendererShaderData.setVector4(Blitter._sourceScaleOffsetProperty, sourceScaleOffset ?? Blitter._defaultScaleOffset); + + const pass = blitMaterial.shader.subShaders[0].passes[passIndex]; + const compileMacros = Shader._compileMacros; + + ShaderMacroCollection.unionCollection( + context.camera._globalShaderMacro, + blitMaterial.shaderData._macroCollection, + compileMacros + ); + const program = pass._getShaderProgram(engine, compileMacros); + + program.bind(); + program.groupingOtherUniformBlock(); + program.uploadAll(program.rendererUniformBlock, rendererShaderData); + program.uploadAll(program.materialUniformBlock, blitMaterial.shaderData); + program.uploadUnGroupTextures(); + + (pass._renderState || blitMaterial.renderState)._applyStates( + engine, + false, + pass._renderStateDataMap, + blitMaterial.shaderData + ); + + rhi.drawPrimitive(blitMesh._primitive, blitMesh.subMesh, program); + } +} diff --git a/packages/core/src/RenderPipeline/OpaqueTexturePass.ts b/packages/core/src/RenderPipeline/OpaqueTexturePass.ts index aa7df415c5..36fa9a7568 100644 --- a/packages/core/src/RenderPipeline/OpaqueTexturePass.ts +++ b/packages/core/src/RenderPipeline/OpaqueTexturePass.ts @@ -4,6 +4,7 @@ import { Downsampling } from "../enums/Downsampling"; import { Texture, Texture2D, TextureFilterMode } from "../texture"; import { RenderTarget } from "../texture/RenderTarget"; import { TextureWrapMode } from "../texture/enums/TextureWrapMode"; +import { Blitter } from "./Blitter"; import { PipelinePass } from "./PipelinePass"; import { PipelineUtils } from "./PipelineUtils"; import { RenderContext } from "./RenderContext"; @@ -46,7 +47,7 @@ export class OpaqueTexturePass extends PipelinePass { } override onRender(context: RenderContext): void { - PipelineUtils.blitTexture(this.engine, this._cameraColorTexture, this._renderTarget); + Blitter.blitTexture(this.engine, this._cameraColorTexture, this._renderTarget); context.camera.shaderData.setTexture(Camera._cameraOpaqueTextureProperty, this._renderTarget.getColorTexture(0)); } } diff --git a/packages/core/src/RenderPipeline/PipelineUtils.ts b/packages/core/src/RenderPipeline/PipelineUtils.ts index edc6d60c3e..1af885b9d4 100644 --- a/packages/core/src/RenderPipeline/PipelineUtils.ts +++ b/packages/core/src/RenderPipeline/PipelineUtils.ts @@ -1,25 +1,11 @@ import { Vector4 } from "@galacean/engine-math"; import { Engine } from "../Engine"; -import { Material } from "../material"; -import { ShaderMacro, ShaderProperty } from "../shader"; -import { Shader } from "../shader/Shader"; -import { ShaderData } from "../shader/ShaderData"; -import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; -import { ShaderDataGroup } from "../shader/enums/ShaderDataGroup"; import { RenderTarget, Texture2D, TextureFilterMode, TextureFormat, TextureWrapMode } from "../texture"; /** * @internal */ export class PipelineUtils { - private static _blitTextureProperty = ShaderProperty.getByName("renderer_BlitTexture"); - private static _blitMipLevelProperty = ShaderProperty.getByName("renderer_BlitMipLevel"); - private static _blitTexelSizeProperty = ShaderProperty.getByName("renderer_texelSize"); // x: 1/width, y: 1/height, z: width, w: height - private static _flipYTextureMacro = ShaderMacro.getByName("renderer_FlipYBlitTexture"); - - private static _rendererShaderData = new ShaderData(ShaderDataGroup.Renderer); - private static _texelSize = new Vector4(); - static readonly defaultViewport = new Vector4(0, 0, 1, 1); /** @@ -141,75 +127,4 @@ export class PipelineUtils { return currentRenderTarget; } - - /** - * Blit texture to destination render target using a triangle. - * @param engine - Engine - * @param source - Source texture - * @param destination - Destination render target - * @param mipLevel - Mip level to blit - * @param viewport - Viewport - * @param material - The material to use when blitting - * @param passIndex - Pass index to use of the provided material - * @param flipYOfSource - Whether flip Y axis of source texture - */ - static blitTexture( - engine: Engine, - source: Texture2D, - destination: RenderTarget | null, - mipLevel: number = 0, - viewport: Vector4 = PipelineUtils.defaultViewport, - material: Material = null, - passIndex = 0, - flipYOfSource = false - ): void { - const basicResources = engine._basicResources; - const blitMesh = destination ? basicResources.flipYBlitMesh : basicResources.blitMesh; - const blitMaterial = material || basicResources.blitMaterial; - const rhi = engine._hardwareRenderer; - const context = engine._renderContext; - - // We not use projection matrix when blit, but we must modify flipProjection to make front face correct - context.flipProjection = !!destination; - - rhi.activeRenderTarget(destination, viewport, context.flipProjection, 0); - - const rendererShaderData = PipelineUtils._rendererShaderData; - - rendererShaderData.setTexture(PipelineUtils._blitTextureProperty, source); - rendererShaderData.setFloat(PipelineUtils._blitMipLevelProperty, mipLevel); - PipelineUtils._texelSize.set(1 / source.width, 1 / source.height, source.width, source.height); - rendererShaderData.setVector4(PipelineUtils._blitTexelSizeProperty, PipelineUtils._texelSize); - if (flipYOfSource) { - rendererShaderData.enableMacro(PipelineUtils._flipYTextureMacro); - } else { - rendererShaderData.disableMacro(PipelineUtils._flipYTextureMacro); - } - - const pass = blitMaterial.shader.subShaders[0].passes[passIndex]; - const compileMacros = Shader._compileMacros; - - ShaderMacroCollection.unionCollection( - context.camera._globalShaderMacro, - rendererShaderData._macroCollection, - compileMacros - ); - ShaderMacroCollection.unionCollection(compileMacros, blitMaterial.shaderData._macroCollection, compileMacros); - const program = pass._getShaderProgram(engine, compileMacros); - - program.bind(); - program.groupingOtherUniformBlock(); - program.uploadAll(program.rendererUniformBlock, rendererShaderData); - program.uploadAll(program.materialUniformBlock, blitMaterial.shaderData); - program.uploadUnGroupTextures(); - - (pass._renderState || blitMaterial.renderState)._applyStates( - engine, - false, - pass._renderStateDataMap, - blitMaterial.shaderData - ); - - rhi.drawPrimitive(blitMesh._primitive, blitMesh.subMesh, program); - } } diff --git a/packages/core/src/RenderPipeline/index.ts b/packages/core/src/RenderPipeline/index.ts index 72da2434f5..272dc766e7 100644 --- a/packages/core/src/RenderPipeline/index.ts +++ b/packages/core/src/RenderPipeline/index.ts @@ -1,3 +1,4 @@ export { BasicRenderPipeline } from "./BasicRenderPipeline"; -export { RenderQueue } from "./RenderQueue"; +export { Blitter } from "./Blitter"; export { PipelineStage } from "./enums/PipelineStage"; +export { RenderQueue } from "./RenderQueue"; diff --git a/packages/core/src/Scene.ts b/packages/core/src/Scene.ts index fade2c1c0b..08dfbab743 100644 --- a/packages/core/src/Scene.ts +++ b/packages/core/src/Scene.ts @@ -12,7 +12,7 @@ import { DirectLight } from "./lighting"; import { AmbientLight } from "./lighting/AmbientLight"; import { LightManager } from "./lighting/LightManager"; import { PhysicsScene } from "./physics/PhysicsScene"; -import { _PostProcessManager } from "./postProcess"; +import { PostProcessManager } from "./postProcess"; import { ShaderProperty } from "./shader"; import { ShaderData } from "./shader/ShaderData"; import { ShaderMacroCollection } from "./shader/ShaderMacroCollection"; @@ -50,6 +50,9 @@ export class Scene extends EngineObject { */ shadowFadeBorder: number = 0.1; + /** Post process manager. */ + readonly postProcessManager = new PostProcessManager(this); + /* @internal */ _lightManager: LightManager = new LightManager(); /* @internal */ @@ -64,8 +67,6 @@ export class Scene extends EngineObject { _globalShaderMacro: ShaderMacroCollection = new ShaderMacroCollection(); /** @internal */ _rootEntities: Entity[] = []; - /** @internal */ - _postProcessManager = new _PostProcessManager(this); private _background: Background = new Background(this._engine); private _shaderData: ShaderData = new ShaderData(ShaderDataGroup.Scene); diff --git a/packages/core/src/postProcess/PostProcess.ts b/packages/core/src/postProcess/PostProcess.ts new file mode 100644 index 0000000000..d385c7bb5f --- /dev/null +++ b/packages/core/src/postProcess/PostProcess.ts @@ -0,0 +1,130 @@ +import { Logger } from "../base"; +import { deepClone } from "../clone/CloneManager"; +import { Component } from "../Component"; +import { Layer } from "../Layer"; +import { PostProcessEffect } from "./PostProcessEffect"; + +/** + * Post Process component can be used for global or local post-processing. + */ +export class PostProcess extends Component { + /** + * The layer to which the PostProcess belongs. + */ + layer = Layer.Layer0; + + /** + * The outer distance to start blending from, only takes effect when the `isGlobal` is false. + */ + blendDistance = 0; + + /** @internal */ + @deepClone + _effects: PostProcessEffect[] = []; + + private _priority = 0; + private _isGlobal = true; + + /** + * Whether the PostProcess is global. + * @remarks + * Specifies whether to apply the PostProcess to the entire Scene or in Colliders. + * Only support local PostProcess in physics enabled Scenes. + */ + get isGlobal(): boolean { + return this._isGlobal; + } + + set isGlobal(value: boolean) { + if (value !== this._isGlobal) { + if (!value && !this.engine._physicsInitialized) { + Logger.warn("Only support local PostProcess in physics enabled Scenes."); + return; + } + + this._isGlobal = value; + } + } + + /** + * A value which determines which PostProcess is being used when PostProcess have an equal amount of influence on the Scene. + * @remarks + * PostProcess with a higher priority will override lower ones. + */ + get priority(): number { + return this._priority; + } + + set priority(value: number) { + this._priority = value; + if (this.scene) { + this.scene.postProcessManager._postProcessNeedSorting = true; + } + } + + /** + * Get the PostProcessEffect by type. + * @param type - The type of PostProcessEffect + * @returns The PostProcessEffect found + */ + getEffect(type: T): InstanceType { + const effects = this._effects; + const length = effects.length; + + for (let i = 0; i < length; i++) { + const effect = effects[i] as InstanceType; + if (effect instanceof type) { + return effect; + } + } + } + + /** + * Add a PostProcessEffect to the PostProcess. + * @remarks Only one effect of the same type can be added to the PostProcess. + * @param type - The type of PostProcessEffect + * @returns The PostProcessEffect added + */ + addEffect(type: T): InstanceType { + if (this.getEffect(type)) { + Logger.error(`effect "${type.name}" already exists in the PostProcess.`); + return; + } + + const effect = new type() as InstanceType; + this._effects.push(effect); + return effect; + } + + /** + * Remove a PostProcessEffect from the PostProcess. + * @param type - The type of PostProcessEffect + * @returns The PostProcessEffect removed + */ + removeEffect(type: T): InstanceType { + const effects = this._effects; + const length = effects.length; + + for (let i = 0; i < length; i++) { + const effect = effects[i] as InstanceType; + if (effect instanceof type) { + effects.splice(i, 1); + return effect; + } + } + } + + /** + * @inheritdoc + */ + override _onEnableInScene() { + this.scene.postProcessManager._addPostProcess(this); + } + + /** + * @inheritdoc + */ + override _onDisableInScene() { + this.scene.postProcessManager._removePostProcess(this); + } +} diff --git a/packages/core/src/postProcess/PostProcessEffect.ts b/packages/core/src/postProcess/PostProcessEffect.ts new file mode 100644 index 0000000000..a2a29d382a --- /dev/null +++ b/packages/core/src/postProcess/PostProcessEffect.ts @@ -0,0 +1,68 @@ +import { PostProcessEffectParameter } from "./PostProcessEffectParameter"; + +/** + * The base class for post process effect. + */ +export class PostProcessEffect { + private _enabled = true; + private _parameters: PostProcessEffectParameter[] = []; + private _parameterInitialized = false; + + /** + * Indicates whether the post process effect is enabled. + */ + get enabled(): boolean { + return this._enabled; + } + + set enabled(value: boolean) { + if (value === this._enabled) { + return; + } + + this._enabled = value; + } + + /** + * Whether the post process effect is valid. + * @remarks + * This method can be overridden to control the effect's real validity. + */ + isValid(): boolean { + return this._enabled; + } + + /** + * @internal + */ + _lerp(to: PostProcessEffect, factor: number): void { + const parameters = this._getParameters(); + const toParameters = to._getParameters(); + + for (let i = 0, n = parameters.length; i < n; i++) { + const toParameter = toParameters[i]; + if (toParameter.enabled) { + parameters[i]._lerp(toParameter.value, factor); + } + } + } + + /** + * Get all parameters of the post process effect. + * @remarks + * Only get the parameters that are initialized in the constructor. + * It will don't take effect if you add a new parameter after the post process effect is created, such as `effect.** = new PostProcessEffectParameter(1)` + */ + private _getParameters(): PostProcessEffectParameter[] { + if (!this._parameterInitialized) { + this._parameterInitialized = true; + for (let key in this) { + const value = this[key]; + if (value instanceof PostProcessEffectParameter) { + this._parameters.push(value); + } + } + } + return this._parameters; + } +} diff --git a/packages/core/src/postProcess/PostProcessEffectParameter.ts b/packages/core/src/postProcess/PostProcessEffectParameter.ts new file mode 100644 index 0000000000..5f13d60755 --- /dev/null +++ b/packages/core/src/postProcess/PostProcessEffectParameter.ts @@ -0,0 +1,95 @@ +import { Color, MathUtil, Vector2, Vector3, Vector4 } from "@galacean/engine-math"; +import { Texture } from "../texture/Texture"; + +/** + * Represents a parameter of a post process effect. + * @remarks + * The parameter will be mixed to a final value and be used in post process manager. + */ +export class PostProcessEffectParameter { + /** + * Whether the parameter is enabled. + */ + enabled = true; + + private _value: T; + private _needLerp = false; + private _min?: number; + private _max?: number; + + /** + * The value of the parameter. + */ + get value(): T { + return this._value; + } + + set value(value: T) { + if (value?.constructor === Number) { + this._value = (MathUtil.clamp(value, this._min, this._max)); + } else { + this._value = value; + } + } + + constructor(value: Exclude, needLerp?: boolean); + constructor(value: Exclude, needLerp?: boolean); + constructor( + value: Exclude, + min?: number, + max?: number, + needLerp?: boolean + ); + + constructor(value: T, needLerpOrMin?: boolean | number, max?: number, needLerp?: boolean) { + if (typeof value === "number") { + if (typeof needLerpOrMin === "boolean") { + this._needLerp = needLerpOrMin; + this._min = Number.NEGATIVE_INFINITY; + this._max = Number.POSITIVE_INFINITY; + } else if (typeof needLerpOrMin === "number") { + this._min = needLerpOrMin; + this._max = max ?? Number.POSITIVE_INFINITY; + this._needLerp = needLerp ?? false; + } else if (needLerpOrMin == undefined) { + this._min = Number.NEGATIVE_INFINITY; + this._max = Number.POSITIVE_INFINITY; + } + } else { + this._needLerp = needLerpOrMin ?? false; + } + + this.value = value; + } + + /** + * @internal + */ + _lerp(to: T, factor: number) { + if (this._needLerp) { + switch (this.value?.constructor) { + case Number: + this.value = (MathUtil.lerp(this.value, to, factor)); + break; + case Color: + Color.lerp(this.value, to, factor, this.value); + break; + case Vector2: + Vector2.lerp(this.value, to, factor, this.value); + break; + case Vector3: + Vector3.lerp(this.value, to, factor, this.value); + break; + case Vector4: + Vector4.lerp(this.value, to, factor, this.value); + break; + default: + if (factor > 0) { + this.value = to; + } + } + } else if (factor > 0) { + this.value = to; + } + } +} diff --git a/packages/core/src/postProcess/PostProcessManager.ts b/packages/core/src/postProcess/PostProcessManager.ts index 46819d7319..88e3af86cf 100644 --- a/packages/core/src/postProcess/PostProcessManager.ts +++ b/packages/core/src/postProcess/PostProcessManager.ts @@ -1,73 +1,270 @@ +import { Vector3 } from "@galacean/engine-math"; +import { Camera } from "../Camera"; import { PipelineUtils } from "../RenderPipeline/PipelineUtils"; -import { RenderContext } from "../RenderPipeline/RenderContext"; import { Scene } from "../Scene"; +import { Logger } from "../base/Logger"; import { Material } from "../material"; -import { Shader } from "../shader"; -import { RenderTarget, Texture2D } from "../texture"; -import { BloomEffect, TonemappingEffect } from "./effects"; +import { Collider } from "../physics"; +import { RenderTarget, Texture2D, TextureFilterMode, TextureFormat, TextureWrapMode } from "../texture"; +import { PostProcess } from "./PostProcess"; +import { PostProcessEffect } from "./PostProcessEffect"; /** - * @internal + * A global manager of the PostProcess. */ -export class _PostProcessManager { - static readonly UBER_SHADER_NAME = "UberPost"; - - /** - * Whether the post process manager is active. - */ - isActive = true; +export class PostProcessManager { + private static _tempColliders: Collider[] = []; + private static _tempVector3 = new Vector3(); /** @internal */ _uberMaterial: Material; /** @internal */ - _bloomEffect: BloomEffect; + _postProcessNeedSorting = false; /** @internal */ - _tonemappingEffect: TonemappingEffect; + _postProcessPassNeedSorting = false; + + private _activePostProcesses: PostProcess[] = []; + private _swapRenderTarget: RenderTarget; + private _srcRenderTarget: RenderTarget; + private _destRenderTarget: RenderTarget; + private _currentSourceRenderTarget: RenderTarget; + private _currentDestRenderTarget: RenderTarget; + private _blendEffectMap = new Map(); + private _defaultEffectMap = new Map(); + private _remainActivePassCount = 0; /** - * Whether has active post process effect. + * Create a PostProcessManager. + * @param scene - Scene to which the current PostProcessManager belongs */ - get hasActiveEffect(): boolean { - return this.isActive && (this._bloomEffect.enabled || this._tonemappingEffect.enabled); + constructor(public readonly scene: Scene) {} + + /** + * @internal + * Whether has any valid post process pass. + */ + _isValid(): boolean { + const scene = this.scene; + const engine = scene.engine; + + const activePasses = engine._getActivePostProcessPasses(); + return activePasses.some((pass) => pass.isValid(scene.postProcessManager)); } /** - * Create a PostProcessManager. - * @param scene - Scene to which the current PostProcessManager belongs + * @internal */ - constructor(public readonly scene: Scene) { - const uberShader = Shader.find(_PostProcessManager.UBER_SHADER_NAME); - const uberMaterial = new Material(scene.engine, uberShader); - const depthState = uberMaterial.renderState.depthState; + _update(camera: Camera): void { + // Start by resetting post process effect instance to default values + this._resetDefaultValue(); + + // Sort post process + this._sortActivePostProcess(); + const activePostProcesses = this._activePostProcesses; + + for (let i = 0, n = activePostProcesses.length; i < n; i++) { + const postProcess = activePostProcesses[i]; + + if (!(camera.postProcessMask & postProcess.layer)) { + continue; + } - depthState.enabled = false; - depthState.writeEnabled = false; + const isGlobal = postProcess.isGlobal; + let interpFactor = 1; // Global default value + if (!isGlobal) { + const currentColliders = PostProcessManager._tempColliders; + const cameraPosition = camera.entity.transform.worldPosition; + const blendDistance = postProcess.blendDistance; - const bloomEffect = new BloomEffect(uberMaterial); - const tonemappingEffect = new TonemappingEffect(uberMaterial); + let hasColliderShape = false; + // Find closest distance to current postProcess, 0 means it's inside it + let closestDistance = Number.POSITIVE_INFINITY; - this._uberMaterial = uberMaterial; - this._bloomEffect = bloomEffect; - this._tonemappingEffect = tonemappingEffect; + postProcess.entity.getComponents(Collider, currentColliders); + for (let i = 0; i < currentColliders.length; i++) { + const collider = currentColliders[i]; + if (!collider.enabled) { + continue; + } + const shapes = collider.shapes; + for (let j = 0; j < shapes.length; j++) { + const currentShape = shapes[j]; + hasColliderShape = true; + + const distance = currentShape.getClosestPoint(cameraPosition, PostProcessManager._tempVector3); + if (distance < closestDistance) { + closestDistance = distance; + } + } + } + + if (!hasColliderShape) { + Logger.warn( + `No collider shape found in the entity:"${postProcess.entity.name}", the local mode of post process will not take effect.` + ); + continue; + } + + // Post process has no influence, ignore it + if (closestDistance > blendDistance) { + continue; + } + + if (blendDistance > 0) { + interpFactor = 1 - closestDistance / blendDistance; + } + } + + const effects = postProcess._effects; + for (let j = 0; j < effects.length; j++) { + const effect = effects[j]; + if (!effect.enabled) { + continue; + } + const PostConstructor = effect.constructor as typeof PostProcessEffect; + + let blendEffect = this._blendEffectMap.get(PostConstructor); + if (!blendEffect) { + blendEffect = new PostConstructor(); + this._blendEffectMap.set(PostConstructor, blendEffect); + } + + blendEffect._lerp(effect, interpFactor); + blendEffect.enabled = true; + } + } } /** * @internal */ - _render(context: RenderContext, srcTarget: RenderTarget, destTarget: RenderTarget): void { - const camera = context.camera; - const engine = camera.engine; + _addPostProcess(postProcess: PostProcess): void { + this._activePostProcesses.push(postProcess); + this._postProcessNeedSorting = true; + } + + /** + * @internal + */ + _removePostProcess(postProcess: PostProcess): void { + const index = this._activePostProcesses.indexOf(postProcess); + if (index >= 0) { + this._activePostProcesses.splice(index, 1); + this._postProcessNeedSorting = true; + } + } + + /** + * Get the blend effect by type. + * @remarks + * The blend effect is a post process effect that is used to blend all result of the effects by the type. + * @param type - The type of PostProcessEffect + * @returns The PostProcessEffect instance found + */ + getBlendEffect(type: T): InstanceType { + return this._blendEffectMap.get(type) as InstanceType; + } + + /** + * @internal + */ + _render(camera: Camera, srcRenderTarget: RenderTarget, destRenderTarget: RenderTarget): void { + const engine = this.scene.engine; + this._srcRenderTarget = srcRenderTarget; + this._destRenderTarget = destRenderTarget; // Should blit to resolve the MSAA - srcTarget._blitRenderTarget(); - const srcTexture = srcTarget.getColorTexture(); - const bloomEffect = this._bloomEffect; + srcRenderTarget._blitRenderTarget(); + + const activePasses = engine._getActivePostProcessPasses(); + this._remainActivePassCount = activePasses.length; + this._initSwapRenderTarget(camera); + + for (let i = 0, n = activePasses.length; i < n; i++) { + const pass = activePasses[i]; + pass.onRender(camera, this._getCurrentSourceTexture(), this._currentDestRenderTarget); + this._remainActivePassCount--; + this._swapRT(); + } + } + + /** + * @internal + */ + _releaseSwapRenderTarget(): void { + const swapRenderTarget = this._swapRenderTarget; + if (swapRenderTarget) { + swapRenderTarget.getColorTexture(0)?.destroy(true); + swapRenderTarget.destroy(true); + this._swapRenderTarget = null; + } + } + + private _sortActivePostProcess(): void { + if (this._postProcessNeedSorting) { + const postProcesses = this._activePostProcesses; + if (postProcesses.length) { + postProcesses.sort((a, b) => a.priority - b.priority); + } + this._postProcessNeedSorting = false; + } + } + + private _resetDefaultValue(): void { + this._blendEffectMap.forEach((blendEffect, typeofBlendEffect) => { + let defaultEffect = this._defaultEffectMap.get(typeofBlendEffect); + + if (!defaultEffect) { + defaultEffect = new typeofBlendEffect(); + this._defaultEffectMap.set(typeofBlendEffect, defaultEffect); + } + + // Reset effectInstance's value by defaultEffect + blendEffect._lerp(defaultEffect, 1); + blendEffect.enabled = false; + }); + } - if (bloomEffect.enabled) { - bloomEffect.onRender(context, srcTexture); + private _initSwapRenderTarget(camera: Camera): void { + if (this._remainActivePassCount > 1) { + const viewport = camera.pixelViewport; + const swapRenderTarget = PipelineUtils.recreateRenderTargetIfNeeded( + this.scene.engine, + this._swapRenderTarget, + viewport.width, + viewport.height, + camera._getInternalColorTextureFormat(), + TextureFormat.Depth24Stencil8, + false, + false, + 1, + TextureWrapMode.Clamp, + TextureFilterMode.Bilinear + ); + + this._swapRenderTarget = swapRenderTarget; + this._currentDestRenderTarget = this._swapRenderTarget; + } else { + this._currentDestRenderTarget = this._destRenderTarget; + } + + this._currentSourceRenderTarget = this._srcRenderTarget; + } + + private _swapRT(): void { + const currentSourceRenderTarget = this._currentSourceRenderTarget; + const currentDestRenderTarget = this._currentDestRenderTarget; + + this._currentSourceRenderTarget = currentDestRenderTarget; + + if (this._remainActivePassCount > 1) { + this._currentDestRenderTarget = currentSourceRenderTarget; + } else { + this._currentDestRenderTarget = this._destRenderTarget; } + } - // Done with Uber, blit it - PipelineUtils.blitTexture(engine, srcTexture, destTarget, 0, camera.viewport, this._uberMaterial); + private _getCurrentSourceTexture(): Texture2D { + return this._currentSourceRenderTarget.getColorTexture(0) as Texture2D; } } diff --git a/packages/core/src/postProcess/PostProcessPass.ts b/packages/core/src/postProcess/PostProcessPass.ts new file mode 100644 index 0000000000..f05e272c9b --- /dev/null +++ b/packages/core/src/postProcess/PostProcessPass.ts @@ -0,0 +1,81 @@ +import { EngineObject } from "../base"; +import { Camera } from "../Camera"; +import { RenderTarget, Texture2D } from "../texture"; +import { PostProcessManager } from "./PostProcessManager"; + +/** + * Controls when the post process pass executes. + */ +export enum PostProcessPassEvent { + /** Before the uber pass. */ + BeforeUber = 0, + + /** After the uber pass. */ + AfterUber = 100 +} + +/** + * Post process pass. + */ +export abstract class PostProcessPass extends EngineObject { + private _event = PostProcessPassEvent.AfterUber; + private _isActive = true; + + /** + * When the post process pass is rendered. + * @remarks + * Users can also inject pass events in a specific point by doing PostProcessPassEvent + offset. + */ + get event(): PostProcessPassEvent { + return this._event; + } + + set event(value: PostProcessPassEvent) { + if (value !== this._event) { + this._event = value; + if (this._isActive) { + this.engine._postProcessPassNeedRefresh = true; + } + } + } + + /** + * Whether the post process pass is active. + */ + get isActive(): boolean { + return this._isActive; + } + + set isActive(value: boolean) { + if (value !== this._isActive) { + this._isActive = value; + this.engine._postProcessPassNeedRefresh = true; + } + } + + /** + * Whether the post process pass is valid in current post process manager. + * @remarks + * This method can be overridden to control the pass's real validity. + * @param postProcessManager - The post process manager + */ + isValid(postProcessManager: PostProcessManager): boolean { + return this._isActive; + } + + /** + * Execute the post process pass if it is active. + * @param camera - The camera used to render + * @param srcTexture - The source texture from last render target + * @param destTarget - The destination render target + */ + abstract onRender(camera: Camera, srcTexture: Texture2D, destTarget: RenderTarget): void; + + /** + * @inheritdoc + */ + override _onDestroy() { + super._onDestroy(); + this.engine._removePostProcessPass(this); + } +} diff --git a/packages/core/src/postProcess/PostProcessUberPass.ts b/packages/core/src/postProcess/PostProcessUberPass.ts new file mode 100644 index 0000000000..9df998a62b --- /dev/null +++ b/packages/core/src/postProcess/PostProcessUberPass.ts @@ -0,0 +1,277 @@ +import { Color, MathUtil, Vector4 } from "@galacean/engine-math"; +import { Camera } from "../Camera"; +import { Engine } from "../Engine"; +import { Material } from "../material"; +import { Blitter } from "../RenderPipeline/Blitter"; +import { PipelineUtils } from "../RenderPipeline/PipelineUtils"; +import { Shader } from "../shader"; +import { ShaderLib } from "../shaderlib"; +import blitVs from "../shaderlib/extra/Blit.vs.glsl"; +import { RenderTarget, Texture2D, TextureFilterMode, TextureWrapMode } from "../texture"; +import { BloomDownScaleMode, BloomEffect, TonemappingEffect } from "./effects"; +import { PostProcessPass, PostProcessPassEvent } from "./PostProcessPass"; +import Filtering from "./shaders/Filtering.glsl"; +import PostCommon from "./shaders/PostCommon.glsl"; +import ACESTonemapping from "./shaders/Tonemapping/ACES/ACESTonemapping.glsl"; +import ColorTransform from "./shaders/Tonemapping/ACES/ColorTransform.glsl"; +import ODT from "./shaders/Tonemapping/ACES/ODT.glsl"; +import RRT from "./shaders/Tonemapping/ACES/RRT.glsl"; +import Tonescale from "./shaders/Tonemapping/ACES/Tonescale.glsl"; +import NeutralTonemapping from "./shaders/Tonemapping/NeutralTonemapping.glsl"; +import UberPost from "./shaders/UberPost.glsl"; +import { PostProcessManager } from "./PostProcessManager"; + +export class PostProcessUberPass extends PostProcessPass { + static readonly UBER_SHADER_NAME = "UberPost"; + + private _uberMaterial: Material; + + // Bloom + private _bloomMaterial: Material; + private _mipDownRT: RenderTarget[] = []; + private _mipUpRT: RenderTarget[] = []; + + constructor(engine: Engine) { + super(engine); + this.event = PostProcessPassEvent.AfterUber - 1; + + // Uber Material + const uberMaterial = new Material(engine, Shader.find(PostProcessUberPass.UBER_SHADER_NAME)); + const uberDepthState = uberMaterial.renderState.depthState; + uberDepthState.enabled = false; + uberDepthState.writeEnabled = false; + this._uberMaterial = uberMaterial; + + // Bloom Material + const bloomMaterial = new Material(engine, Shader.find(BloomEffect.SHADER_NAME)); + const bloomDepthState = bloomMaterial.renderState.depthState; + bloomDepthState.enabled = false; + bloomDepthState.writeEnabled = false; + this._bloomMaterial = bloomMaterial; + + // ShaderData initialization + const bloomShaderData = bloomMaterial.shaderData; + const uberShaderData = uberMaterial.shaderData; + bloomShaderData.setVector4(BloomEffect._bloomParams, new Vector4()); + bloomShaderData.setVector4(BloomEffect._lowMipTexelSizeProp, new Vector4()); + uberShaderData.setVector4(BloomEffect._bloomIntensityParams, new Vector4()); + uberShaderData.setVector4(BloomEffect._dirtTilingOffsetProp, new Vector4()); + uberShaderData.setColor(BloomEffect._tintProp, new Color()); + } + + /** @inheritdoc */ + override isValid(postProcessManager: PostProcessManager): boolean { + if (!this.isActive) { + return false; + } + + const bloomBlend = postProcessManager.getBlendEffect(BloomEffect); + const tonemappingBlend = postProcessManager.getBlendEffect(TonemappingEffect); + return bloomBlend?.isValid() || tonemappingBlend?.isValid(); + } + + /** + * @inheritdoc + */ + onRender(camera: Camera, srcTexture: Texture2D, destTarget: RenderTarget): void { + const postProcessManager = camera.scene.postProcessManager; + const uberShaderData = this._uberMaterial.shaderData; + const bloomBlend = postProcessManager.getBlendEffect(BloomEffect); + const tonemappingBlend = postProcessManager.getBlendEffect(TonemappingEffect); + + if (bloomBlend?.isValid()) { + this._setupBloom(bloomBlend, camera, srcTexture); + uberShaderData.enableMacro(BloomEffect._enableMacro); + } else { + uberShaderData.disableMacro(BloomEffect._enableMacro); + this._releaseBloomRenderTargets(); + } + + if (tonemappingBlend?.isValid()) { + uberShaderData.enableMacro("TONEMAPPING_MODE", tonemappingBlend.mode.value.toString()); + uberShaderData.enableMacro(TonemappingEffect._enableMacro); + } else { + uberShaderData.disableMacro(TonemappingEffect._enableMacro); + } + + Blitter.blitTexture(camera.engine, srcTexture, destTarget, 0, camera.viewport, this._uberMaterial, undefined); + } + + /** + * @inheritdoc + */ + override _onDestroy() { + super._onDestroy(); + this._releaseBloomRenderTargets(); + this._uberMaterial.destroy(); + this._bloomMaterial.destroy(); + } + + private _setupBloom(bloomBlend: BloomEffect, camera: Camera, srcTexture: Texture2D) { + const engine = camera.engine; + const bloomMaterial = this._bloomMaterial; + const bloomShaderData = bloomMaterial.shaderData; + const uberMaterial = this._uberMaterial; + const uberShaderData = uberMaterial.shaderData; + const { downScale, threshold, scatter, intensity, tint, highQualityFiltering, dirtTexture, dirtIntensity } = + bloomBlend; + + // Update shaderData + const thresholdLinear = Color.gammaToLinearSpace(threshold.value); + const thresholdKnee = thresholdLinear * 0.5; // Hardcoded soft knee + const bloomParams = bloomShaderData.getVector4(BloomEffect._bloomParams); + const scatterLerp = MathUtil.lerp(0.05, 0.95, scatter.value); + bloomParams.x = threshold.value; + bloomParams.y = thresholdKnee; + bloomParams.z = scatterLerp; + const bloomIntensityParams = uberShaderData.getVector4(BloomEffect._bloomIntensityParams); + bloomIntensityParams.x = intensity.value; + bloomIntensityParams.y = dirtIntensity.value; + const tintParam = uberShaderData.getColor(BloomEffect._tintProp); + tintParam.copyFrom(tint.value); + if (highQualityFiltering.value) { + bloomShaderData.enableMacro(BloomEffect._hqMacro); + uberShaderData.enableMacro(BloomEffect._hqMacro); + } else { + bloomShaderData.disableMacro(BloomEffect._hqMacro); + uberShaderData.disableMacro(BloomEffect._hqMacro); + } + uberShaderData.setTexture(BloomEffect._dirtTextureProp, dirtTexture.value); + if (dirtTexture) { + uberShaderData.enableMacro(BloomEffect._dirtMacro); + } else { + uberShaderData.disableMacro(BloomEffect._dirtMacro); + } + + // Determine the iteration count + const downRes = downScale.value === BloomDownScaleMode.Half ? 1 : 2; + const pixelViewport = camera.pixelViewport; + const tw = pixelViewport.width >> downRes; + const th = pixelViewport.height >> downRes; + const maxSize = Math.max(tw, th); + const iterations = Math.floor(Math.log2(maxSize) - 1); + const mipCount = Math.min(Math.max(iterations, 1), BloomEffect._maxIterations); + + // Prefilter + const internalColorTextureFormat = camera._getInternalColorTextureFormat(); + let mipWidth = tw, + mipHeight = th; + for (let i = 0; i < mipCount; i++) { + this._mipUpRT[i] = PipelineUtils.recreateRenderTargetIfNeeded( + engine, + this._mipUpRT[i], + mipWidth, + mipHeight, + internalColorTextureFormat, + null, + false, + false, + 1, + TextureWrapMode.Clamp, + TextureFilterMode.Bilinear + ); + this._mipDownRT[i] = PipelineUtils.recreateRenderTargetIfNeeded( + engine, + this._mipDownRT[i], + mipWidth, + mipHeight, + internalColorTextureFormat, + null, + false, + false, + 1, + TextureWrapMode.Clamp, + TextureFilterMode.Bilinear + ); + mipWidth = Math.max(1, Math.floor(mipWidth / 2)); + mipHeight = Math.max(1, Math.floor(mipHeight / 2)); + } + Blitter.blitTexture(engine, srcTexture, this._mipDownRT[0], undefined, undefined, bloomMaterial, 0); + + // Down sample - gaussian pyramid + let lastDown = this._mipDownRT[0]; + for (let i = 1; i < mipCount; i++) { + // Classic two pass gaussian blur - use mipUp as a temporary target + // First pass does 2x downsampling + 9-tap gaussian + // Second pass does 9-tap gaussian using a 5-tap filter + bilinear filtering + Blitter.blitTexture( + engine, + lastDown.getColorTexture(0), + this._mipUpRT[i], + undefined, + undefined, + bloomMaterial, + 1 + ); + Blitter.blitTexture( + engine, + this._mipUpRT[i].getColorTexture(0), + this._mipDownRT[i], + undefined, + undefined, + bloomMaterial, + 2 + ); + lastDown = this._mipDownRT[i]; + } + + // Up sample (bilinear by default, HQ filtering does bicubic instead + for (let i = mipCount - 2; i >= 0; i--) { + const lowMip = i == mipCount - 2 ? this._mipDownRT[i + 1] : this._mipUpRT[i + 1]; + const highMip = this._mipDownRT[i]; + const dst = this._mipUpRT[i]; + bloomShaderData.setTexture(BloomEffect._lowMipTextureProp, lowMip.getColorTexture(0)); + if (highQualityFiltering) { + const texelSizeLow = bloomShaderData.getVector4(BloomEffect._lowMipTexelSizeProp); + texelSizeLow.set(1 / lowMip.width, 1 / lowMip.height, lowMip.width, lowMip.height); + } + Blitter.blitTexture(engine, highMip.getColorTexture(0), dst, undefined, undefined, bloomMaterial, 3); + } + + // Setup bloom on uber + if (dirtTexture.value) { + const dirtTilingOffset = uberShaderData.getVector4(BloomEffect._dirtTilingOffsetProp); + const dirtRatio = dirtTexture.value.width / dirtTexture.value.height; + const screenRatio = camera.aspectRatio; + if (dirtRatio > screenRatio) { + dirtTilingOffset.set(screenRatio / dirtRatio, 1, (1 - dirtTilingOffset.x) * 0.5, 0); + } else if (dirtRatio < screenRatio) { + dirtTilingOffset.set(1, dirtRatio / screenRatio, 0, (1 - dirtTilingOffset.y) * 0.5); + } else { + dirtTilingOffset.set(1, 1, 0, 0); + } + } + uberShaderData.setTexture(BloomEffect._bloomTextureProp, this._mipUpRT[0].getColorTexture(0)); + } + + private _releaseBloomRenderTargets(): void { + const length = this._mipDownRT.length; + for (let i = 0; i < length; i++) { + const downRT = this._mipDownRT[i]; + const upRT = this._mipUpRT[i]; + if (downRT) { + downRT.getColorTexture(0).destroy(true); + downRT.destroy(true); + } + if (upRT) { + upRT.getColorTexture(0).destroy(true); + upRT.destroy(true); + } + } + this._mipDownRT.length = 0; + this._mipUpRT.length = 0; + } +} + +Object.assign(ShaderLib, { + PostCommon, + Filtering, + ODT, + RRT, + Tonescale, + ColorTransform, + NeutralTonemapping, + ACESTonemapping +}); + +Shader.create(PostProcessUberPass.UBER_SHADER_NAME, blitVs, UberPost); diff --git a/packages/core/src/postProcess/effects/BloomEffect.ts b/packages/core/src/postProcess/effects/BloomEffect.ts index e79b563131..8c764b9c6e 100644 --- a/packages/core/src/postProcess/effects/BloomEffect.ts +++ b/packages/core/src/postProcess/effects/BloomEffect.ts @@ -1,12 +1,10 @@ -import { Color, MathUtil, Vector4 } from "@galacean/engine-math"; -import { Camera } from "../../Camera"; -import { PipelineUtils } from "../../RenderPipeline/PipelineUtils"; -import { RenderContext } from "../../RenderPipeline/RenderContext"; -import { Material } from "../../material"; +import { Color } from "@galacean/engine-math"; import { Shader, ShaderMacro, ShaderPass, ShaderProperty } from "../../shader"; import blitVs from "../../shaderlib/extra/Blit.vs.glsl"; -import { RenderTarget, Texture2D, TextureFilterMode, TextureWrapMode } from "../../texture"; +import { Texture2D } from "../../texture"; +import { PostProcessEffect } from "../PostProcessEffect"; +import { PostProcessEffectParameter } from "../PostProcessEffectParameter"; import fragBlurH from "../shaders/Bloom/BloomBlurH.glsl"; import fragBlurV from "../shaders/Bloom/BloomBlurV.glsl"; import fragPrefilter from "../shaders/Bloom/BloomPrefilter.glsl"; @@ -16,371 +14,88 @@ import fragUpsample from "../shaders/Bloom/BloomUpsample.glsl"; * This controls the size of the bloom texture. */ export enum BloomDownScaleMode { - /** - * Use this to select half size as the starting resolution. - */ + /** Use this to select half size as the starting resolution. */ Half, - /** - * Use this to select quarter size as the starting resolution. - */ + /** Use this to select quarter size as the starting resolution. */ Quarter } -export class BloomEffect { +export class BloomEffect extends PostProcessEffect { static readonly SHADER_NAME = "PostProcessEffect Bloom"; // Bloom shader properties - private static _hqMacro: ShaderMacro = ShaderMacro.getByName("BLOOM_HQ"); - private static _dirtMacro: ShaderMacro = ShaderMacro.getByName("BLOOM_DIRT"); - private static _bloomParams = ShaderProperty.getByName("material_BloomParams"); // x: threshold (linear), y: threshold knee, z: scatter - private static _lowMipTextureProp = ShaderProperty.getByName("material_lowMipTexture"); - private static _lowMipTexelSizeProp = ShaderProperty.getByName("material_lowMipTexelSize"); // x: 1/width, y: 1/height, z: width, w: height + /** @internal */ + static _maxIterations = 6; + /** @internal */ + static _hqMacro: ShaderMacro = ShaderMacro.getByName("BLOOM_HQ"); + /** @internal */ + static _dirtMacro: ShaderMacro = ShaderMacro.getByName("BLOOM_DIRT"); + /** @internal */ + static _bloomParams = ShaderProperty.getByName("material_BloomParams"); // x: threshold (linear), y: threshold knee, z: scatter + /** @internal */ + static _lowMipTextureProp = ShaderProperty.getByName("material_lowMipTexture"); + /** @internal */ + static _lowMipTexelSizeProp = ShaderProperty.getByName("material_lowMipTexelSize"); // x: 1/width, y: 1/height, z: width, w: height // Uber shader properties - private static _enableMacro: ShaderMacro = ShaderMacro.getByName("ENABLE_EFFECT_BLOOM"); - private static _bloomTextureProp = ShaderProperty.getByName("material_BloomTexture"); - private static _dirtTextureProp = ShaderProperty.getByName("material_BloomDirtTexture"); - private static _tintProp = ShaderProperty.getByName("material_BloomTint"); - private static _bloomIntensityParams = ShaderProperty.getByName("material_BloomIntensityParams"); // x: bloom intensity, y: dirt intensity - private static _dirtTilingOffsetProp = ShaderProperty.getByName("material_BloomDirtTilingOffset"); - - private _bloomMaterial: Material; - private _threshold: number; - private _scatter: number; - private _highQualityFiltering = false; + /** @internal */ + static _enableMacro: ShaderMacro = ShaderMacro.getByName("ENABLE_EFFECT_BLOOM"); + /** @internal */ + static _bloomTextureProp = ShaderProperty.getByName("material_BloomTexture"); + /** @internal */ + static _dirtTextureProp = ShaderProperty.getByName("material_BloomDirtTexture"); + /** @internal */ + static _tintProp = ShaderProperty.getByName("material_BloomTint"); + /** @internal */ + static _bloomIntensityParams = ShaderProperty.getByName("material_BloomIntensityParams"); // x: bloom intensity, y: dirt intensity + /** @internal */ + static _dirtTilingOffsetProp = ShaderProperty.getByName("material_BloomDirtTilingOffset"); - private _mipDownRT: RenderTarget[] = []; - private _mipUpRT: RenderTarget[] = []; - private _maxIterations = 6; - private _enabled = false; + /** + * Controls whether to use bicubic sampling instead of bilinear sampling for the upSampling passes. + * @remarks This is slightly more expensive but helps getting smoother visuals. + */ + highQualityFiltering = new PostProcessEffectParameter(false); /** * Controls the starting resolution that this effect begins processing. */ - downScale = BloomDownScaleMode.Half; + downScale = new PostProcessEffectParameter(BloomDownScaleMode.Half); /** - * Indicates whether the post process effect is enabled. + * Specifies a Texture to add smudges or dust to the bloom effect. */ - get enabled(): boolean { - return this._enabled; - } - - set enabled(value: boolean) { - if (value !== this._enabled) { - this._enabled = value; - if (value) { - this._uberMaterial.shaderData.enableMacro(BloomEffect._enableMacro); - } else { - this._uberMaterial.shaderData.disableMacro(BloomEffect._enableMacro); - this._releaseRenderTargets(); - } - } - } + dirtTexture = new PostProcessEffectParameter(null); /** * Set the level of brightness to filter out pixels under this level. * @remarks This value is expressed in gamma-space. */ - - get threshold(): number { - return this._threshold; - } - - set threshold(value: number) { - value = Math.max(0, value); - - if (value !== this._threshold) { - this._threshold = value; - const threshold = Color.gammaToLinearSpace(value); - const thresholdKnee = threshold * 0.5; // Hardcoded soft knee - const params = this._bloomMaterial.shaderData.getVector4(BloomEffect._bloomParams); - params.x = threshold; - params.y = thresholdKnee; - } - } + threshold = new PostProcessEffectParameter(0.9, 0, Number.POSITIVE_INFINITY, true); /** * Controls the radius of the bloom effect. */ - get scatter(): number { - return this._scatter; - } - - set scatter(value: number) { - value = MathUtil.clamp(value, 0, 1); - - if (value !== this._scatter) { - this._scatter = value; - const params = this._bloomMaterial.shaderData.getVector4(BloomEffect._bloomParams); - const scatter = MathUtil.lerp(0.05, 0.95, value); - params.z = scatter; - } - } + scatter = new PostProcessEffectParameter(0.7, 0, 1, true); /** * Controls the strength of the bloom effect. */ - get intensity(): number { - return this._uberMaterial.shaderData.getVector4(BloomEffect._bloomIntensityParams).x; - } - - set intensity(value: number) { - value = Math.max(0, value); - - this._uberMaterial.shaderData.getVector4(BloomEffect._bloomIntensityParams).x = value; - } - - /** - * Specifies the tint of the bloom effect. - */ - get tint(): Color { - return this._uberMaterial.shaderData.getColor(BloomEffect._tintProp); - } - - set tint(value: Color) { - const tint = this._uberMaterial.shaderData.getColor(BloomEffect._tintProp); - if (value !== tint) { - tint.copyFrom(value); - } - } + intensity = new PostProcessEffectParameter(0, 0, Number.POSITIVE_INFINITY, true); /** - * Controls whether to use bicubic sampling instead of bilinear sampling for the upSampling passes. - * @remarks This is slightly more expensive but helps getting smoother visuals. + * Controls the strength of the lens dirt. */ - - get highQualityFiltering(): boolean { - return this._highQualityFiltering; - } - - set highQualityFiltering(value: boolean) { - if (value !== this._highQualityFiltering) { - this._highQualityFiltering = value; - if (value) { - this._bloomMaterial.shaderData.enableMacro(BloomEffect._hqMacro); - this._uberMaterial.shaderData.enableMacro(BloomEffect._hqMacro); - } else { - this._bloomMaterial.shaderData.disableMacro(BloomEffect._hqMacro); - this._uberMaterial.shaderData.disableMacro(BloomEffect._hqMacro); - } - } - } + dirtIntensity = new PostProcessEffectParameter(0, 0, Number.POSITIVE_INFINITY, true); /** - * Specifies a Texture to add smudges or dust to the bloom effect. - */ - get dirtTexture(): Texture2D { - return this._uberMaterial.shaderData.getTexture(BloomEffect._dirtTextureProp); - } - - set dirtTexture(value: Texture2D) { - this._uberMaterial.shaderData.setTexture(BloomEffect._dirtTextureProp, value); - if (value) { - this._uberMaterial.shaderData.enableMacro(BloomEffect._dirtMacro); - } else { - this._uberMaterial.shaderData.disableMacro(BloomEffect._dirtMacro); - } - } - - /** - * Controls the strength of the lens dirt. + * Specifies the tint of the bloom effect. */ - get dirtIntensity(): number { - return this._uberMaterial.shaderData.getVector4(BloomEffect._bloomIntensityParams).y; - } - - set dirtIntensity(value: number) { - value = Math.max(0, value); - - this._uberMaterial.shaderData.getVector4(BloomEffect._bloomIntensityParams).y = value; - } - - constructor(private _uberMaterial: Material) { - const engine = _uberMaterial.engine; - const material = new Material(engine, Shader.find(BloomEffect.SHADER_NAME)); - const depthState = material.renderState.depthState; - - depthState.enabled = false; - depthState.writeEnabled = false; - - const bloomShaderData = material.shaderData; - const uberShaderData = _uberMaterial.shaderData; - bloomShaderData.setVector4(BloomEffect._bloomParams, new Vector4()); - bloomShaderData.setVector4(BloomEffect._lowMipTexelSizeProp, new Vector4()); - - uberShaderData.setVector4(BloomEffect._bloomIntensityParams, new Vector4(1, 1, 0, 0)); - uberShaderData.setVector4(BloomEffect._dirtTilingOffsetProp, new Vector4()); - uberShaderData.setColor(BloomEffect._tintProp, new Color(1, 1, 1, 1)); - - this._bloomMaterial = material; - this.threshold = 0.9; - this.scatter = 0.7; - this.intensity = 1; - this.dirtIntensity = 1; - } - - onRender(context: RenderContext, srcTexture: Texture2D): void { - const camera = context.camera; - const downRes = this.downScale === BloomDownScaleMode.Half ? 1 : 2; - const pixelViewport = camera.pixelViewport; - const tw = pixelViewport.width >> downRes; - const th = pixelViewport.height >> downRes; - - // Determine the iteration count - const mipCount = this._calculateMipCount(tw, th); - - // Prefilter - this._prefilter(camera, srcTexture, tw, th, mipCount); - // Down sample - gaussian pyramid - this._downsample(mipCount); - // Up sample (bilinear by default, HQ filtering does bicubic instead - this._upsample(mipCount); - // Setup bloom on uber - this._setupUber(camera); - } - - private _calculateMipCount(tw: number, th: number): number { - const maxSize = Math.max(tw, th); - const iterations = Math.floor(Math.log2(maxSize) - 1); - return Math.min(Math.max(iterations, 1), this._maxIterations); - } - - private _prefilter(camera: Camera, srcTexture: Texture2D, tw: number, th: number, mipCount: number): void { - const engine = this._uberMaterial.engine; - const internalColorTextureFormat = camera._getInternalColorTextureFormat(); - let mipWidth = tw, - mipHeight = th; - - for (let i = 0; i < mipCount; i++) { - this._mipUpRT[i] = PipelineUtils.recreateRenderTargetIfNeeded( - engine, - this._mipUpRT[i], - mipWidth, - mipHeight, - internalColorTextureFormat, - null, - false, - false, - 1, - TextureWrapMode.Clamp, - TextureFilterMode.Bilinear - ); - this._mipDownRT[i] = PipelineUtils.recreateRenderTargetIfNeeded( - engine, - this._mipDownRT[i], - mipWidth, - mipHeight, - internalColorTextureFormat, - null, - false, - false, - 1, - TextureWrapMode.Clamp, - TextureFilterMode.Bilinear - ); - - mipWidth = Math.max(1, Math.floor(mipWidth / 2)); - mipHeight = Math.max(1, Math.floor(mipHeight / 2)); - } - - PipelineUtils.blitTexture(engine, srcTexture, this._mipDownRT[0], undefined, undefined, this._bloomMaterial, 0); - } - - private _downsample(mipCount: number): void { - const material = this._bloomMaterial; - const engine = material.engine; - let lastDown = this._mipDownRT[0]; - - // Down sample - gaussian pyramid - for (let i = 1; i < mipCount; i++) { - // Classic two pass gaussian blur - use mipUp as a temporary target - // First pass does 2x downsampling + 9-tap gaussian - // Second pass does 9-tap gaussian using a 5-tap filter + bilinear filtering - PipelineUtils.blitTexture( - engine, - lastDown.getColorTexture(0), - this._mipUpRT[i], - undefined, - undefined, - material, - 1 - ); - PipelineUtils.blitTexture( - engine, - this._mipUpRT[i].getColorTexture(0), - this._mipDownRT[i], - undefined, - undefined, - material, - 2 - ); - - lastDown = this._mipDownRT[i]; - } - } - - private _upsample(mipCount: number): void { - const material = this._bloomMaterial; - const engine = material.engine; - const shaderData = material.shaderData; - - // Up sample (bilinear by default, HQ filtering does bicubic instead - for (let i = mipCount - 2; i >= 0; i--) { - const lowMip = i == mipCount - 2 ? this._mipDownRT[i + 1] : this._mipUpRT[i + 1]; - const highMip = this._mipDownRT[i]; - const dst = this._mipUpRT[i]; - - shaderData.setTexture(BloomEffect._lowMipTextureProp, lowMip.getColorTexture(0)); - if (this.highQualityFiltering) { - const texelSizeLow = shaderData.getVector4(BloomEffect._lowMipTexelSizeProp); - texelSizeLow.set(1 / lowMip.width, 1 / lowMip.height, lowMip.width, lowMip.height); - } - - PipelineUtils.blitTexture(engine, highMip.getColorTexture(0), dst, undefined, undefined, material, 3); - } - } - - private _setupUber(camera: Camera): void { - const shaderData = this._uberMaterial.shaderData; - const dirtTexture = this.dirtTexture; - - if (dirtTexture) { - const dirtTilingOffset = shaderData.getVector4(BloomEffect._dirtTilingOffsetProp); - const dirtRatio = dirtTexture.width / dirtTexture.height; - const screenRatio = camera.aspectRatio; - if (dirtRatio > screenRatio) { - dirtTilingOffset.set(screenRatio / dirtRatio, 1, (1 - dirtTilingOffset.x) * 0.5, 0); - } else if (dirtRatio < screenRatio) { - dirtTilingOffset.set(1, dirtRatio / screenRatio, 0, (1 - dirtTilingOffset.y) * 0.5); - } else { - dirtTilingOffset.set(1, 1, 0, 0); - } - } - - shaderData.setTexture(BloomEffect._bloomTextureProp, this._mipUpRT[0].getColorTexture(0)); - } - - private _releaseRenderTargets(): void { - const length = this._mipDownRT.length; - - for (let i = 0; i < length; i++) { - const downRT = this._mipDownRT[i]; - const upRT = this._mipUpRT[i]; - - if (downRT) { - downRT.getColorTexture(0).destroy(true); - downRT.destroy(true); - } - - if (upRT) { - upRT.getColorTexture(0).destroy(true); - upRT.destroy(true); - } - } + tint = new PostProcessEffectParameter(new Color(1, 1, 1, 1), true); - this._mipDownRT.length = 0; - this._mipUpRT.length = 0; + /** @inheritdoc */ + override isValid(): boolean { + return this.enabled && this.intensity.value > 0; } } diff --git a/packages/core/src/postProcess/effects/TonemappingEffect.ts b/packages/core/src/postProcess/effects/TonemappingEffect.ts index 1153c59740..e66d18d3cb 100644 --- a/packages/core/src/postProcess/effects/TonemappingEffect.ts +++ b/packages/core/src/postProcess/effects/TonemappingEffect.ts @@ -1,18 +1,19 @@ -import { Material } from "../../material"; import { ShaderMacro } from "../../shader"; +import { PostProcessEffect } from "../PostProcessEffect"; +import { PostProcessEffectParameter } from "../PostProcessEffectParameter"; /** * Options to select a tonemapping algorithm to use. */ export enum TonemappingMode { /** - * Neutral tonemapper + * Neutral tonemapper. * @remarks Use this option if you only want range-remapping with minimal impact on color hue and saturation. */ Neutral, /** - * ACES Filmic reference tonemapper (custom approximation) + * ACES Filmic reference tonemapper (custom approximation). * @remarks * Use this option to apply a close approximation of the reference ACES tonemapper for a more filmic look. * It is more contrasted than Neutral and has an effect on actual color hue and saturation. @@ -20,45 +21,12 @@ export enum TonemappingMode { ACES } -export class TonemappingEffect { - private static _enableMacro: ShaderMacro = ShaderMacro.getByName("ENABLE_EFFECT_TONEMAPPING"); - - private _mode: TonemappingMode; - private _enabled = false; - - /** - * Indicates whether the post process effect is enabled. - */ - get enabled(): boolean { - return this._enabled; - } - - set enabled(value: boolean) { - if (value !== this._enabled) { - this._enabled = value; - if (value) { - this._uberMaterial.shaderData.enableMacro(TonemappingEffect._enableMacro); - } else { - this._uberMaterial.shaderData.disableMacro(TonemappingEffect._enableMacro); - } - } - } +export class TonemappingEffect extends PostProcessEffect { + /** @internal */ + static _enableMacro: ShaderMacro = ShaderMacro.getByName("ENABLE_EFFECT_TONEMAPPING"); /** * Use this to select a tonemapping algorithm to use. */ - get mode(): TonemappingMode { - return this._mode; - } - - set mode(value: TonemappingMode) { - if (value !== this._mode) { - this._mode = value; - this._uberMaterial.shaderData.enableMacro("TONEMAPPING_MODE", value.toString()); - } - } - - constructor(private _uberMaterial: Material) { - this.mode = TonemappingMode.Neutral; - } + mode = new PostProcessEffectParameter(TonemappingMode.Neutral, false); } diff --git a/packages/core/src/postProcess/index.ts b/packages/core/src/postProcess/index.ts index cfde36dd59..4882605f93 100644 --- a/packages/core/src/postProcess/index.ts +++ b/packages/core/src/postProcess/index.ts @@ -1,30 +1,8 @@ -import { Shader } from "../shader"; -import { ShaderLib } from "../shaderlib"; -import { _PostProcessManager } from "./PostProcessManager"; - -import blitVs from "../shaderlib/extra/Blit.vs.glsl"; -import Filtering from "./shaders/Filtering.glsl"; -import PostCommon from "./shaders/PostCommon.glsl"; -import ACESTonemapping from "./shaders/Tonemapping/ACES/ACESTonemapping.glsl"; -import ColorTransform from "./shaders/Tonemapping/ACES/ColorTransform.glsl"; -import ODT from "./shaders/Tonemapping/ACES/ODT.glsl"; -import RRT from "./shaders/Tonemapping/ACES/RRT.glsl"; -import Tonescale from "./shaders/Tonemapping/ACES/Tonescale.glsl"; -import NeutralTonemapping from "./shaders/Tonemapping/NeutralTonemapping.glsl"; -import UberPost from "./shaders/UberPost.glsl"; - export * from "./effects"; -export { _PostProcessManager }; - -Object.assign(ShaderLib, { - PostCommon, - Filtering, - ODT, - RRT, - Tonescale, - ColorTransform, - NeutralTonemapping, - ACESTonemapping -}); -Shader.create(_PostProcessManager.UBER_SHADER_NAME, blitVs, UberPost); +export { PostProcess } from "./PostProcess"; +export { PostProcessEffect } from "./PostProcessEffect"; +export { PostProcessEffectParameter } from "./PostProcessEffectParameter"; +export { PostProcessManager } from "./PostProcessManager"; +export { PostProcessPass, PostProcessPassEvent } from "./PostProcessPass"; +export { PostProcessUberPass } from "./PostProcessUberPass"; diff --git a/packages/core/src/shaderlib/extra/Blit.fs.glsl b/packages/core/src/shaderlib/extra/Blit.fs.glsl index 78f439796e..4ae8868cb5 100644 --- a/packages/core/src/shaderlib/extra/Blit.fs.glsl +++ b/packages/core/src/shaderlib/extra/Blit.fs.glsl @@ -3,14 +3,13 @@ uniform mediump sampler2D renderer_BlitTexture; uniform float renderer_BlitMipLevel; #endif +uniform vec4 renderer_SourceScaleOffset; varying vec2 v_uv; void main() { vec2 uv = v_uv; - #ifdef renderer_FlipYBlitTexture - uv.y = 1.0 - uv.y; - #endif + uv = uv * renderer_SourceScaleOffset.xy + renderer_SourceScaleOffset.zw; #ifdef HAS_TEX_LOD gl_FragColor = texture2DLodEXT( renderer_BlitTexture, uv, renderer_BlitMipLevel ); diff --git a/packages/loader/src/SceneLoader.ts b/packages/loader/src/SceneLoader.ts index 7d191991d3..e9ea7eb41a 100644 --- a/packages/loader/src/SceneLoader.ts +++ b/packages/loader/src/SceneLoader.ts @@ -129,29 +129,9 @@ class SceneLoader extends Loader { // Post Process const postProcessData = data.scene.postProcess; if (postProcessData) { - // @ts-ignore - const postProcessManager = scene._postProcessManager; - const bloomEffect = postProcessManager._bloomEffect as BloomEffect; - const tonemappingEffect = postProcessManager._tonemappingEffect as TonemappingEffect; - - postProcessManager.isActive = postProcessData.isActive; - bloomEffect.enabled = postProcessData.bloom.enabled; - bloomEffect.downScale = postProcessData.bloom.downScale; - bloomEffect.threshold = postProcessData.bloom.threshold; - bloomEffect.scatter = postProcessData.bloom.scatter; - bloomEffect.intensity = postProcessData.bloom.intensity; - bloomEffect.tint.copyFrom(postProcessData.bloom.tint); - bloomEffect.dirtIntensity = postProcessData.bloom.dirtIntensity; - tonemappingEffect.enabled = postProcessData.tonemapping.enabled; - tonemappingEffect.mode = postProcessData.tonemapping.mode; - if (postProcessData.bloom.dirtTexture) { - // @ts-ignore - // prettier-ignore - const dirtTexturePromise = resourceManager.getResourceByRef(postProcessData.bloom.dirtTexture).then((texture) => { - bloomEffect.dirtTexture = texture; - }); - promises.push(dirtTexturePromise); - } + Logger.warn( + "Post Process is not supported in scene yet, please add PostProcess component in entity instead." + ); } return Promise.all(promises).then(() => { diff --git a/tests/src/core/postProcess/PostProcess.test.ts b/tests/src/core/postProcess/PostProcess.test.ts new file mode 100644 index 0000000000..ab45e24453 --- /dev/null +++ b/tests/src/core/postProcess/PostProcess.test.ts @@ -0,0 +1,379 @@ +import { Color } from "@galacean/engine"; +import { + BloomDownScaleMode, + BloomEffect, + Camera, + Engine, + Entity, + PostProcess, + PostProcessEffect, + PostProcessEffectParameter, + PostProcessPass, + RenderTarget, + Scene, + SphereColliderShape, + StaticCollider, + Texture2D, + TonemappingEffect +} from "@galacean/engine-core"; +import { MathUtil } from "@galacean/engine-math"; +import { PhysXPhysics } from "@galacean/engine-physics-physx"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +class CustomPass extends PostProcessPass { + onRender(camera: Camera, srcTexture: Texture2D, destTarget: RenderTarget): void {} +} + +export class CustomEffect extends PostProcessEffect { + intensity = new PostProcessEffectParameter(0, 0, 1, true); +} + +describe("PostProcess", () => { + let engine: Engine; + let scene: Scene; + let uberPass: PostProcessPass = null; + let postEntity: Entity = null; + let camera: Camera; + + beforeAll(async function () { + engine = await WebGLEngine.create({ canvas: document.createElement("canvas") }); + scene = engine.sceneManager.scenes[0]; + const passes = engine.postProcessPasses; + uberPass = passes[0]; + const cameraEntity = scene.createRootEntity("camera"); + camera = cameraEntity.addComponent(Camera); + camera.enablePostProcess = true; + }); + + afterAll(() => { + engine.destroy(); + }); + + beforeEach(() => { + postEntity = scene.createRootEntity("post-process"); + }); + + afterEach(() => { + postEntity.destroy(); + }); + + it("Post Process isActive", () => { + const ppManager = scene.postProcessManager; + const pp = postEntity.addComponent(PostProcess); + + engine.update(); + // @ts-ignore + expect(ppManager._isValid()).to.false; + + // Test effect + const bloomEffect = pp.addEffect(BloomEffect); + engine.update(); + // @ts-ignore + expect(ppManager._isValid()).to.false; + + bloomEffect.enabled = false; + engine.update(); + // @ts-ignore + expect(ppManager._isValid()).to.false; + + bloomEffect.enabled = true; + engine.update(); + // @ts-ignore + expect(ppManager._isValid()).to.false; + + bloomEffect.intensity.value = 1; + engine.update(); + // @ts-ignore + expect(ppManager._isValid()).to.true; + + // Test PostProcess disable + pp.enabled = false; + engine.update(); + // @ts-ignore + expect(ppManager._isValid()).to.false; + + pp.enabled = true; + engine.update(); + // @ts-ignore + expect(ppManager._isValid()).to.true; + + // Test pass isActive + uberPass.isActive = false; + engine.update(); + // @ts-ignore + expect(ppManager._isValid()).to.false; + + uberPass.isActive = true; + engine.update(); + // @ts-ignore + expect(ppManager._isValid()).to.true; + + // Test effect remove + const removedBloomEffect = pp.removeEffect(BloomEffect); + expect(removedBloomEffect).to.instanceOf(BloomEffect); + engine.update(); + // @ts-ignore + expect(ppManager._isValid()).to.false; + + // Test component destroy + pp.destroy(); + engine.update(); + // @ts-ignore + expect(ppManager._isValid()).to.false; + + // Test entity destroy + { + const pp = postEntity.addComponent(PostProcess); + pp.addEffect(TonemappingEffect); + engine.update(); + // @ts-ignore + expect(ppManager._isValid()).to.true; + + postEntity.destroy(); + engine.update(); + // @ts-ignore + expect(ppManager._isValid()).to.false; + } + }); + + it("Post Process Effect", () => { + const pp = postEntity.addComponent(PostProcess); + + expect(pp.getEffect(BloomEffect)).to.undefined; + // @ts-ignore + expect(pp._effects.length).to.equal(0); + + const bloomEffect = pp.addEffect(BloomEffect); + expect(bloomEffect).to.instanceOf(BloomEffect); + + const bloomEffectRepeat = pp.addEffect(BloomEffect); + expect(bloomEffectRepeat).to.undefined; + + // @ts-ignore + expect(pp._effects.length).to.equal(1); + expect(pp.removeEffect(TonemappingEffect)).to.undefined; + + // Test Bloom parameters + expect(bloomEffect.highQualityFiltering.value).to.false; + expect(bloomEffect.downScale.value).to.equal(BloomDownScaleMode.Half); + expect(bloomEffect.dirtTexture.value).to.null; + expect(bloomEffect.threshold.value).to.equal(0.9); + expect(bloomEffect.scatter.value).to.equal(0.7); + expect(bloomEffect.intensity.value).to.equal(0); + expect(bloomEffect.dirtIntensity.value).to.equal(0); + expect(bloomEffect.tint.value).to.include(new Color(1, 1, 1, 1)); + + // Test remove effect + const removedBloomEffect = pp.removeEffect(BloomEffect); + expect(removedBloomEffect).to.instanceOf(BloomEffect); + expect(removedBloomEffect).to.equal(bloomEffect); + // @ts-ignore + expect(pp._effects.length).to.equal(0); + }); + + it("Post Process", () => { + const ppManager = scene.postProcessManager; + + // @ts-ignore + const activePostProcesses = ppManager._activePostProcesses; + + expect(activePostProcesses.length).to.equal(0); + + const pp1 = postEntity.addComponent(PostProcess); + const pp2 = postEntity.addComponent(PostProcess); + pp1.addEffect(BloomEffect); + + expect(pp1.priority).to.equal(0); + expect(activePostProcesses.length).to.equal(2); + expect(activePostProcesses[1] === pp2).to.true; + + // Test priority + pp1.priority = 10; + engine.update(); + expect(activePostProcesses[1] === pp1).to.true; + + pp1.enabled = false; + expect(activePostProcesses.length).to.equal(1); + + pp1.enabled = true; + expect(activePostProcesses.length).to.equal(2); + }); + + it("Custom effect", () => { + const pp = postEntity.addComponent(PostProcess); + const customEffect = pp.addEffect(CustomEffect); + + expect(customEffect).to.instanceOf(CustomEffect); + expect(customEffect.intensity.value).to.equal(0); + + // Clamp + customEffect.intensity.value = 2; + expect(customEffect.intensity.value).to.equal(1); + + customEffect.intensity.value = -2; + expect(customEffect.intensity.value).to.equal(0); + + // isValid + expect(customEffect.isValid()).to.true; + customEffect.enabled = false; + expect(customEffect.isValid()).to.false; + }); + + it("Post process effect parameter", () => { + { + const p1 = new PostProcessEffectParameter(1); + const p2 = new PostProcessEffectParameter(2, 0, 1); + const p3 = new PostProcessEffectParameter(-2, 0, 1); + const p4 = new PostProcessEffectParameter(10, 0); + const p5 = new PostProcessEffectParameter(-10, 0); + const p6 = new PostProcessEffectParameter(0.5, 0, 1, true); + + expect(p1.value).to.equal(1); + expect(p2.value).to.equal(1); + expect(p3.value).to.equal(0); + expect(p4.value).to.equal(10); + expect(p5.value).to.equal(0); + expect(p6.value).to.equal(0.5); + } + + { + const p1 = new PostProcessEffectParameter(false); + const p2 = new PostProcessEffectParameter(true); + const p3 = new PostProcessEffectParameter(true, true); + + expect(p1.value).to.equal(false); + expect(p2.value).to.equal(true); + expect(p3.value).to.equal(true); + } + }); + + it("Global mode", () => { + const ppManager = scene.postProcessManager; + const pp1 = postEntity.addComponent(PostProcess); + const pp2 = postEntity.addComponent(PostProcess); + const bloom1 = pp1.addEffect(BloomEffect); + const bloom2 = pp2.addEffect(BloomEffect); + + engine.update(); + + const bloomBlend = ppManager.getBlendEffect(BloomEffect); + expect(bloomBlend).to.instanceOf(BloomEffect); + expect(bloomBlend.intensity.value).to.equal(0); + + bloom2.intensity.value = 10; + engine.update(); + expect(bloomBlend.intensity.value).to.equal(10); + + pp1.priority = 10; + engine.update(); + expect(bloomBlend.intensity.value).to.equal(0); + + pp1.enabled = false; + engine.update(); + expect(bloomBlend.intensity.value).to.equal(10); + + pp1.enabled = true; + engine.update(); + expect(bloomBlend.intensity.value).to.equal(0); + + pp2.priority = 20; + engine.update(); + expect(bloomBlend.intensity.value).to.equal(10); + }); + + it("Local mode", async () => { + const pp1 = postEntity.addComponent(PostProcess); + expect(pp1.blendDistance).to.equal(0); + expect(pp1.isGlobal).to.equal(true); + + pp1.isGlobal = false; + // Only support local PostProcess in physics enabled Scenes. + expect(pp1.isGlobal).to.equal(true); + + { + const engine = await WebGLEngine.create({ + canvas: document.createElement("canvas"), + physics: new PhysXPhysics() + // physics: new LitePhysics() + }); + const scene = engine.sceneManager.scenes[0]; + const passes = engine.postProcessPasses; + const ppManager = scene.postProcessManager; + uberPass = passes[0]; + const cameraEntity = scene.createRootEntity("camera"); + const camera = cameraEntity.addComponent(Camera); + camera.enablePostProcess = true; + const postEntity = scene.createRootEntity("post-process"); + + const pp1 = postEntity.addComponent(PostProcess); + const pp2 = postEntity.addComponent(PostProcess); + const bloom1 = pp1.addEffect(BloomEffect); + const bloom2 = pp2.addEffect(BloomEffect); + const intensity2 = 10; + + pp2.priority = 10; + bloom2.intensity.value = intensity2; + + engine.update(); + const bloomBlend = ppManager.getBlendEffect(BloomEffect); + + expect(bloomBlend.intensity.value).to.equal(10); + + // Local mode + const radius = 5; + pp2.isGlobal = false; + pp2.blendDistance = radius; + const collider = postEntity.addComponent(StaticCollider); + const physicsBox = new SphereColliderShape(); + physicsBox.radius = radius; + collider.addShape(physicsBox); + + // Inside + cameraEntity.transform.position.set(radius / 2, 0, 0); + engine.update(); + expect(bloomBlend.intensity.value).to.equal(intensity2); + + // Edge + cameraEntity.transform.position.set(radius, 0, 0); + engine.update(); + expect(bloomBlend.intensity.value).to.equal(intensity2); + + // Outer half in blend distance + cameraEntity.transform.position.set(radius + pp2.blendDistance / 2, 0, 0); + engine.update(); + expect(bloomBlend.intensity.value).to.equal(intensity2 / 2); + + // Outside over blend distance + cameraEntity.transform.position.set(radius + pp2.blendDistance, 0, 0); + engine.update(); + expect(bloomBlend.intensity.value).to.equal(0); + + // Blend with local and global + const intensity1 = 1; + bloom1.intensity.value = intensity1; + + // Inside + cameraEntity.transform.position.set(radius / 2, 0, 0); + engine.update(); + expect(bloomBlend.intensity.value).to.equal(MathUtil.lerp(intensity1, intensity2, 1)); + + // Edge + cameraEntity.transform.position.set(radius, 0, 0); + engine.update(); + expect(bloomBlend.intensity.value).to.equal(MathUtil.lerp(intensity1, intensity2, 1)); + + // Outer half in blend distance + cameraEntity.transform.position.set(radius + pp2.blendDistance / 2, 0, 0); + engine.update(); + expect(bloomBlend.intensity.value).to.equal(MathUtil.lerp(intensity1, intensity2, 0.5)); + + // Outside over blend distance + cameraEntity.transform.position.set(radius + pp2.blendDistance, 0, 0); + engine.update(); + expect(bloomBlend.intensity.value).to.equal(MathUtil.lerp(intensity1, intensity2, 0)); + + engine.destroy(); + } + }); +}); diff --git a/tests/src/core/postProcess/PostProcessPass.test.ts b/tests/src/core/postProcess/PostProcessPass.test.ts new file mode 100644 index 0000000000..3f40d1fed8 --- /dev/null +++ b/tests/src/core/postProcess/PostProcessPass.test.ts @@ -0,0 +1,120 @@ +import { + Camera, + Engine, + PostProcessPass, + PostProcessPassEvent, + PostProcessUberPass, + RenderTarget, + Scene, + Texture2D +} from "@galacean/engine-core"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; + +class CustomPass extends PostProcessPass { + onRender(camera: Camera, srcTexture: Texture2D, destTarget: RenderTarget): void {} +} + +describe("PostProcessPass", () => { + let engine: Engine; + let engine2: Engine; + let scene1: Scene; + let scene2: Scene; + + beforeAll(async function () { + engine = await WebGLEngine.create({ canvas: document.createElement("canvas") }); + engine2 = await WebGLEngine.create({ canvas: document.createElement("canvas") }); + scene1 = engine.sceneManager.scenes[0]; + scene2 = new Scene(engine); + engine.sceneManager.addScene(scene2); + engine.run(); + }); + + afterAll(() => { + engine.destroy(); + engine2.destroy(); + }); + + it("internal uber pass", () => { + const passes1 = engine.postProcessPasses; + const passes2 = engine2.postProcessPasses; + const uberPass1 = passes1[0]; + const uberPass2 = passes2[0]; + + expect(passes1.length).to.eq(1); + expect(uberPass1).to.instanceOf(PostProcessUberPass); + expect(passes2.length).to.eq(1); + expect(uberPass2).to.instanceOf(PostProcessUberPass); + }); + + it("can't cross engine", () => { + const passes = engine.postProcessPasses; + const uberPass = passes[0]; + + expect(() => { + engine2.addPostProcessPass(uberPass); + }).toThrowError(); + }); + + it("add pass", () => { + const passes = engine.postProcessPasses; + const customPass = new CustomPass(engine); + const uberPass = passes[0]; + + // Add same pass should not work + engine.addPostProcessPass(uberPass); + expect(passes.length).to.eq(1); + + // Add custom pass + engine.addPostProcessPass(customPass); + expect(passes.length).to.eq(2); + + // Set isActive + customPass.isActive = false; + expect(passes.length).to.eq(2); + + // Destroy + customPass.destroy(); + expect(passes.length).to.eq(1); + }); + + it("active pass", () => { + const customPass = new CustomPass(engine); + + // @ts-ignore + expect(engine._getActivePostProcessPasses().length).to.eq(1); + engine.addPostProcessPass(customPass); + // @ts-ignore + expect(engine._getActivePostProcessPasses().length).to.eq(2); + + customPass.isActive = false; + // @ts-ignore + expect(engine._getActivePostProcessPasses().length).to.eq(1); + + customPass.isActive = true; + // @ts-ignore + expect(engine._getActivePostProcessPasses().length).to.eq(2); + + customPass.isActive = false; + // @ts-ignore + expect(engine._getActivePostProcessPasses().length).to.eq(1); + }); + + it("pass event", () => { + const uberPass = engine.postProcessPasses[0]; + const customPass = new CustomPass(engine); + engine.addPostProcessPass(customPass); + + expect(customPass.event).to.eq(PostProcessPassEvent.AfterUber); + // @ts-ignore + expect(engine._getActivePostProcessPasses()[0] === uberPass).to.be.true; + + customPass.event = PostProcessPassEvent.BeforeUber; + // @ts-ignore + expect(engine._getActivePostProcessPasses()[0] === customPass).to.be.true; + + customPass.destroy(); + // @ts-ignore + expect(engine._getActivePostProcessPasses()[0] === uberPass).to.be.true; + }); +}); From 590ebaabb5cc935a508e70647fe0ad7962972534 Mon Sep 17 00:00:00 2001 From: Kbscript Date: Tue, 24 Dec 2024 10:52:34 +0800 Subject: [PATCH 09/20] feat: update release action --- .github/workflows/release.yml | 88 +++++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 10d206a09b..3b7f04514b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,8 @@ jobs: contents: write id-token: write steps: - - uses: actions/checkout@v4 + - name: Checkout Engine repo + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -32,10 +33,77 @@ jobs: registry-url: https://registry.npmjs.org/ cache: pnpm - - name: Build + - name: Build Engine run: pnpm b:all - - name: Release current monorepo + - name: Cache Engine Package + uses: actions/cache@v3 + with: + path: ./packages/galacean + key: ${{ runner.os }}-engine-${{ github.sha }} + + - name: Checkout Sub-repositories + run: | + git clone https://github.com/galacean/engine-toolkit.git + git clone https://github.com/galacean/engine-lottie.git + git clone https://github.com/galacean/engine-spine.git + + - name: Install and Link Engine and Build for Toolkit + working-directory: ./engine-toolkit + run: | + pnpm install + pnpm link ../packages/galacean + pnpm b:all + + - name: Install and Link Engine and Build for Lottie + working-directory: ./engine-lottie + run: | + pnpm install --ignore-workspace + pnpm link ../packages/galacean + pnpm build + + - name: Install and Link Engine and Build for Spine + working-directory: ./engine-spine + run: | + pnpm install --ignore-workspace + pnpm link ../packages/galacean + pnpm build + + - name: Ensure Dist Directory Exists + run: mkdir -p ${{ github.workspace }}/platform-adapter/dist + + # Create a mock package.json to specify the path and version of the adapter build result when syncing with the CDN later. + # name is set to @galacean/engine-platform-adapter-release to avoid conflicts with the real package.json + # version is set to the version of the engine package + - name: Create package.json for platform-adapter + run: | + VERSION=$(jq -r '.version' ${{ github.workspace }}/packages/galacean/package.json) + echo "{\"name\": \"@galacean/engine-platform-adapter-release\", \"version\": \"$VERSION\"}" > ${{ github.workspace }}/platform-adapter/package.json + cat ${{ github.workspace }}/platform-adapter/package.json + + - name: Bundle polyfill and engine + uses: galacean/platform-adapter@main + env: + ADAPTER_BUNDLE_SETTINGS: | + { + "polyfill": true, + "engine": [ + "${{ github.workspace }}/packages/galacean/dist/module.js", + "${{ github.workspace }}/packages/xr/dist/module.js", + "${{ github.workspace }}/packages/shader-lab/dist/module.js", + "${{ github.workspace }}/packages/physics-lite/dist/module.js", + "${{ github.workspace }}/packages/physics-physx/dist/module.js", + "${{ github.workspace }}/engine-lottie/dist/module.js", + "${{ github.workspace }}/engine-spine/dist/module.js", + "${{ github.workspace }}/engine-toolkit/galacean-engine-toolkit/dist/module.js" + ], + "jsWASMLoader": [ + "${{ github.workspace }}/packages/physics-physx/libs/physx.release.js" + ], + "outputDir": "${{ github.workspace }}/platform-adapter/dist" + } + + - name: Release engine packages and Sync to CDN uses: galacean/publish@v0.2.0 env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} @@ -43,3 +111,17 @@ jobs: OASISBE_UPLOAD_URL: https://oasisbe.alipay.com/api/file/no-auth/crypto/upload OASISBE_REQUEST_HEADER: ${{secrets.OASISBE_REQUEST_HEADER}} OASISBE_PUBLIC_KEY: ${{secrets.OASISBE_PUBLIC_KEY}} + + - name: Sync Platform Adapter to CDN + uses: galacean/publish@v0.2.0 + with: + publish: false + packages: | + platform-adapter + + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + NPM_CONFIG_PROVENANCE: true + OASISBE_UPLOAD_URL: https://oasisbe.alipay.com/api/file/no-auth/crypto/upload + OASISBE_REQUEST_HEADER: ${{secrets.OASISBE_REQUEST_HEADER}} + OASISBE_PUBLIC_KEY: ${{secrets.OASISBE_PUBLIC_KEY}} From 3076ff6c27d65d7a24915743c6ee631204be8527 Mon Sep 17 00:00:00 2001 From: Bo Kou Date: Tue, 24 Dec 2024 12:07:49 +0800 Subject: [PATCH 10/20] Update release.yml --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3b7f04514b..c9c68a1eff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -95,7 +95,7 @@ jobs: "${{ github.workspace }}/packages/physics-physx/dist/module.js", "${{ github.workspace }}/engine-lottie/dist/module.js", "${{ github.workspace }}/engine-spine/dist/module.js", - "${{ github.workspace }}/engine-toolkit/galacean-engine-toolkit/dist/module.js" + "${{ github.workspace }}/engine-toolkit/packages/galacean-engine-toolkit/dist/es/index.js" ], "jsWASMLoader": [ "${{ github.workspace }}/packages/physics-physx/libs/physx.release.js" From cbe310e394170c7f9adce59f0ace6c0aa9bffaab Mon Sep 17 00:00:00 2001 From: Kbscript Date: Tue, 24 Dec 2024 12:43:11 +0800 Subject: [PATCH 11/20] chore: tweak release action --- .github/workflows/release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c9c68a1eff..c640a4d537 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -102,9 +102,13 @@ jobs: ], "outputDir": "${{ github.workspace }}/platform-adapter/dist" } + + - name: Print Adapter Bundle + run: ls -l ${{ github.workspace }}/platform-adapter/dist - name: Release engine packages and Sync to CDN uses: galacean/publish@v0.2.0 + if: success() || failure() env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} NPM_CONFIG_PROVENANCE: true @@ -118,7 +122,6 @@ jobs: publish: false packages: | platform-adapter - env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} NPM_CONFIG_PROVENANCE: true From 77db3794430c593cc2ffb527ac1726b9d08501d2 Mon Sep 17 00:00:00 2001 From: Kbscript Date: Tue, 24 Dec 2024 12:58:41 +0800 Subject: [PATCH 12/20] chore: tweak release action --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c640a4d537..a82f765829 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -118,6 +118,7 @@ jobs: - name: Sync Platform Adapter to CDN uses: galacean/publish@v0.2.0 + if: success() || failure() with: publish: false packages: | From 81d6c4c6c303b010a38d29b89f4846fae074669b Mon Sep 17 00:00:00 2001 From: Kbscript Date: Tue, 24 Dec 2024 13:28:42 +0800 Subject: [PATCH 13/20] chore: use main branch of galacena/publish to build --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a82f765829..be593fa664 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -107,7 +107,7 @@ jobs: run: ls -l ${{ github.workspace }}/platform-adapter/dist - name: Release engine packages and Sync to CDN - uses: galacean/publish@v0.2.0 + uses: galacean/publish@main if: success() || failure() env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} @@ -117,7 +117,7 @@ jobs: OASISBE_PUBLIC_KEY: ${{secrets.OASISBE_PUBLIC_KEY}} - name: Sync Platform Adapter to CDN - uses: galacean/publish@v0.2.0 + uses: galacean/publish@main if: success() || failure() with: publish: false From afddc218b6a3be3b9018f8efcd2e7f1aeff13d6c Mon Sep 17 00:00:00 2001 From: Kbscript Date: Tue, 24 Dec 2024 15:42:50 +0800 Subject: [PATCH 14/20] chore: modify adapter bundler setting --- .github/workflows/release.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index be593fa664..c760ca046a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -88,14 +88,14 @@ jobs: { "polyfill": true, "engine": [ - "${{ github.workspace }}/packages/galacean/dist/module.js", - "${{ github.workspace }}/packages/xr/dist/module.js", - "${{ github.workspace }}/packages/shader-lab/dist/module.js", - "${{ github.workspace }}/packages/physics-lite/dist/module.js", - "${{ github.workspace }}/packages/physics-physx/dist/module.js", - "${{ github.workspace }}/engine-lottie/dist/module.js", - "${{ github.workspace }}/engine-spine/dist/module.js", - "${{ github.workspace }}/engine-toolkit/packages/galacean-engine-toolkit/dist/es/index.js" + "${{ github.workspace }}/packages/galacean/dist/browser.js", + "${{ github.workspace }}/packages/xr/dist/browser.js", + "${{ github.workspace }}/packages/shader-lab/dist/browser.js", + "${{ github.workspace }}/packages/physics-lite/dist/browser.js", + "${{ github.workspace }}/packages/physics-physx/dist/browser.js", + "${{ github.workspace }}/engine-lottie/dist/browser.js", + "${{ github.workspace }}/engine-spine/dist/browser.js", + "${{ github.workspace }}/engine-toolkit/packages/galacean-engine-toolkit/dist/umd/browser.js" ], "jsWASMLoader": [ "${{ github.workspace }}/packages/physics-physx/libs/physx.release.js" @@ -104,7 +104,7 @@ jobs: } - name: Print Adapter Bundle - run: ls -l ${{ github.workspace }}/platform-adapter/dist + run: find ${{ github.workspace }}/platform-adapter -type f - name: Release engine packages and Sync to CDN uses: galacean/publish@main From 07186da9e363c246049b7f3f6b51f8d6d0ccc34d Mon Sep 17 00:00:00 2001 From: SwayYan Date: Tue, 24 Dec 2024 18:20:44 +0800 Subject: [PATCH 15/20] ShaderLab support MRT (#2452) * feat: add mrt syntax --- e2e/case/shaderLab-mrt.ts | 113 +++ e2e/config.ts | 5 + .../originImage/Material_shaderLab-mrt.jpg | 3 + packages/shader-lab/src/GSError.ts | 3 +- packages/shader-lab/src/ParserUtils.ts | 8 +- packages/shader-lab/src/ShaderLabUtils.ts | 6 +- .../shader-lab/src/codeGen/CodeGenVisitor.ts | 78 +- packages/shader-lab/src/codeGen/GLES100.ts | 50 +- packages/shader-lab/src/codeGen/GLES300.ts | 98 ++- .../shader-lab/src/codeGen/GLESVisitor.ts | 65 +- .../shader-lab/src/codeGen/VisitorContext.ts | 38 +- packages/shader-lab/src/common/BaseScanner.ts | 2 +- .../shader-lab/src/common/BaseSymbolTable.ts | 44 +- packages/shader-lab/src/common/Keywords.ts | 3 + packages/shader-lab/src/common/types.ts | 5 +- .../src/contentParser/ContentSymbolTable.ts | 33 + .../src/contentParser/ShaderContentParser.ts | 47 +- .../src/contentParser/SymbolTable.ts | 12 - packages/shader-lab/src/lalr/CFG.ts | 472 ++++++------ packages/shader-lab/src/lalr/LALR1.ts | 22 +- packages/shader-lab/src/lalr/Production.ts | 6 +- packages/shader-lab/src/lalr/StateItem.ts | 4 +- packages/shader-lab/src/lalr/Utils.ts | 21 +- packages/shader-lab/src/lalr/types.ts | 4 +- packages/shader-lab/src/lexer/Lexer.ts | 1 - packages/shader-lab/src/parser/AST.ts | 696 +++++------------- packages/shader-lab/src/parser/Grammar.ts | 16 +- .../shader-lab/src/parser/GrammarSymbol.ts | 5 +- .../shader-lab/src/parser/SemanticAnalyzer.ts | 49 +- packages/shader-lab/src/parser/ShaderInfo.ts | 4 +- .../src/parser/ShaderTargetParser.ts | 4 +- packages/shader-lab/src/parser/TargetParser.y | 8 +- .../src/parser/builtin/variables.ts | 3 +- .../src/parser/symbolTable/SymbolInfo.ts | 6 +- .../src/parser/symbolTable/SymbolTable.ts | 29 - .../parser/symbolTable/TargetSymbolTable.ts | 74 ++ .../src/parser/symbolTable/index.ts | 2 +- packages/shader-lab/src/parser/types.ts | 33 +- tests/src/shader-lab/ShaderLab.test.ts | 29 +- .../src/shader-lab/shaders/mrt-error1.shader | 37 + .../src/shader-lab/shaders/mrt-normal.shader | 42 ++ .../src/shader-lab/shaders/mrt-struct.shader | 50 ++ tests/vitest.config.ts | 3 +- 43 files changed, 1224 insertions(+), 1009 deletions(-) create mode 100644 e2e/case/shaderLab-mrt.ts create mode 100644 e2e/fixtures/originImage/Material_shaderLab-mrt.jpg create mode 100644 packages/shader-lab/src/contentParser/ContentSymbolTable.ts delete mode 100644 packages/shader-lab/src/contentParser/SymbolTable.ts delete mode 100644 packages/shader-lab/src/parser/symbolTable/SymbolTable.ts create mode 100644 packages/shader-lab/src/parser/symbolTable/TargetSymbolTable.ts create mode 100644 tests/src/shader-lab/shaders/mrt-error1.shader create mode 100644 tests/src/shader-lab/shaders/mrt-normal.shader create mode 100644 tests/src/shader-lab/shaders/mrt-struct.shader diff --git a/e2e/case/shaderLab-mrt.ts b/e2e/case/shaderLab-mrt.ts new file mode 100644 index 0000000000..c46e78b133 --- /dev/null +++ b/e2e/case/shaderLab-mrt.ts @@ -0,0 +1,113 @@ +/** + * @title ShaderLab MRT + * @category Material + */ + +import { Camera, Color, Logger, Material, MeshRenderer, PrimitiveMesh, Shader, WebGLEngine } from "@galacean/engine"; +import { ShaderLab } from "@galacean/engine-shader-lab"; +import { initScreenshot, updateForE2E } from "./.mockForE2E"; + +const shaderLab = new ShaderLab(); + +const shaderSource = `Shader "/custom.gs" { + SubShader "Default" { + UsePass "pbr/Default/ShadowCaster" + + Pass "Pass0" { + struct Attributes { + vec3 POSITION; + vec2 TEXCOORD_0; + vec4 JOINTS_0; + vec4 WEIGHTS_0; + }; + + struct Varyings { + vec2 uv; + }; + + mat4 renderer_MVPMat; + + vec4 material_BaseColor; + + + VertexShader = vert; + FragmentShader = frag; + + Varyings vert(Attributes attr) { + Varyings v; + + vec4 position = vec4(attr.POSITION, 1.0); + + // Skin + #ifdef RENDERER_HAS_SKIN + mat4 skinMatrix = getSkinMatrix(attr); + position = skinMatrix * position; + #endif + + gl_Position = renderer_MVPMat * position; + v.uv = attr.TEXCOORD_0; + + return v; + } + + struct mrt { + layout(location = 0) vec4 fragColor0; + layout(location = 1) vec4 fragColor1; + }; + + mrt frag(Varyings v) { + mrt o; + + vec4 baseColor = material_BaseColor; + + #ifdef MATERIAL_HAS_BASETEXTURE + vec4 textureColor = texture2D(material_BaseTexture, v.uv); + #ifndef ENGINE_IS_COLORSPACE_GAMMA + textureColor = gammaToLinear(textureColor); + #endif + baseColor *= textureColor; + #endif + + #ifdef MATERIAL_IS_ALPHA_CUTOFF + if( baseColor.a < material_AlphaCutoff ) { + discard; + } + #endif + + o.fragColor0 = baseColor; + o.fragColor1 = baseColor; + + return o; + } + } + } + }`; + +Logger.enable(); +WebGLEngine.create({ canvas: "canvas", shaderLab }).then((engine) => { + engine.canvas.resizeByClientSize(); + + const shader = Shader.create(shaderSource); + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity(); + + // camera + const cameraEntity = rootEntity.createChild("cameraNode"); + cameraEntity.transform.setPosition(0, 0, 5); + const camera = cameraEntity.addComponent(Camera); + + // sphere + { + const sphere = rootEntity.createChild("sphere"); + sphere.transform.position.x = -1; + const renderer = sphere.addComponent(MeshRenderer); + renderer.mesh = PrimitiveMesh.createSphere(engine); + const material = new Material(engine, shader); + material.shaderData.setColor("material_BaseColor", new Color(1, 0, 0, 0.2)); + renderer.setMaterial(material); + } + + updateForE2E(engine); + + initScreenshot(engine, camera); +}); diff --git a/e2e/config.ts b/e2e/config.ts index 5aabc5b725..8c342d813f 100644 --- a/e2e/config.ts +++ b/e2e/config.ts @@ -110,6 +110,11 @@ export const E2E_CONFIG = { caseFileName: "material-shaderLab", threshold: 0.2 }, + shaderLabMRT: { + category: "Material", + caseFileName: "shaderLab-mrt", + threshold: 0.2 + }, shaderReplacement: { category: "Material", caseFileName: "material-shaderReplacement", diff --git a/e2e/fixtures/originImage/Material_shaderLab-mrt.jpg b/e2e/fixtures/originImage/Material_shaderLab-mrt.jpg new file mode 100644 index 0000000000..4621abc35a --- /dev/null +++ b/e2e/fixtures/originImage/Material_shaderLab-mrt.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:233b89b0d3bf85389142f721bc072d86f4efcc42b8df9567efb6ccd041269c70 +size 23846 diff --git a/packages/shader-lab/src/GSError.ts b/packages/shader-lab/src/GSError.ts index cbce38d1f4..9073591300 100644 --- a/packages/shader-lab/src/GSError.ts +++ b/packages/shader-lab/src/GSError.ts @@ -47,7 +47,8 @@ export class GSError extends Error { if (i === start.line) { remarkStart = start.column; paddingLength += start.column; - } else if (i === end.line) { + } + if (i === end.line) { remarkEnd = end.column; } const remarkLength = Math.max(remarkEnd - remarkStart, 1); diff --git a/packages/shader-lab/src/ParserUtils.ts b/packages/shader-lab/src/ParserUtils.ts index a269ef3199..03e93ab4a9 100644 --- a/packages/shader-lab/src/ParserUtils.ts +++ b/packages/shader-lab/src/ParserUtils.ts @@ -1,4 +1,4 @@ -import { ENonTerminal, GrammarSymbol } from "./parser/GrammarSymbol"; +import { NoneTerminal, GrammarSymbol } from "./parser/GrammarSymbol"; import { BaseToken as Token } from "./common/BaseToken"; import { EKeyword, ETokenType, GalaceanDataType } from "./common"; import { TreeNode } from "./parser/AST"; @@ -7,7 +7,7 @@ import State from "./lalr/State"; // #endif export class ParserUtils { - static unwrapNodeByType(node: TreeNode, type: ENonTerminal): T | undefined { + static unwrapNodeByType(node: TreeNode, type: NoneTerminal): T | undefined { const child = node.children[0]; if (child instanceof Token) return; if (child.nt === type) return child as T; @@ -30,12 +30,12 @@ export class ParserUtils { if (this.isTerminal(sm)) { return ETokenType[sm] ?? EKeyword[sm]; } - return ENonTerminal[sm]; + return NoneTerminal[sm]; } // #endif static isTerminal(sm: GrammarSymbol) { - return sm < ENonTerminal.START; + return sm < NoneTerminal.START; } /** diff --git a/packages/shader-lab/src/ShaderLabUtils.ts b/packages/shader-lab/src/ShaderLabUtils.ts index f4d411ef13..230e190494 100644 --- a/packages/shader-lab/src/ShaderLabUtils.ts +++ b/packages/shader-lab/src/ShaderLabUtils.ts @@ -1,4 +1,4 @@ -import { ClearableObjectPool, IPoolElement } from "@galacean/engine"; +import { ClearableObjectPool, IPoolElement, Logger } from "@galacean/engine"; import { GSErrorName } from "./GSError"; import { ShaderRange } from "./common/ShaderRange"; import { ShaderPosition } from "./common/ShaderPosition"; @@ -27,11 +27,11 @@ export class ShaderLabUtils { source: string, location: ShaderRange | ShaderPosition, file?: string - ): Error { + ): Error | undefined { // #if _VERBOSE return new GSError(errorName, message, location, source, file); // #else - return new Error(`[${errorName}]: ${message}`); + Logger.error(message); // #endif } } diff --git a/packages/shader-lab/src/codeGen/CodeGenVisitor.ts b/packages/shader-lab/src/codeGen/CodeGenVisitor.ts index cd696e61d5..fe940e1b1b 100644 --- a/packages/shader-lab/src/codeGen/CodeGenVisitor.ts +++ b/packages/shader-lab/src/codeGen/CodeGenVisitor.ts @@ -1,4 +1,4 @@ -import { ENonTerminal } from "../parser/GrammarSymbol"; +import { NoneTerminal } from "../parser/GrammarSymbol"; import { BaseToken as Token } from "../common/BaseToken"; import { EKeyword, ShaderPosition, ShaderRange } from "../common"; import { ASTNode, TreeNode } from "../parser/AST"; @@ -11,10 +11,12 @@ import { GSErrorName } from "../GSError"; // #if _VERBOSE import { GSError } from "../GSError"; // #endif -import { ShaderLabUtils } from "../ShaderLabUtils"; import { Logger, ReturnableObjectPool } from "@galacean/engine"; import { TempArray } from "../TempArray"; +export const V3_GL_FragColor = "GS_glFragColor"; +export const V3_GL_FragData = "GS_glFragData"; + /** * @internal * The code generator @@ -23,6 +25,10 @@ export abstract class CodeGenVisitor { // #if _VERBOSE readonly errors: Error[] = []; // #endif + + abstract getFragDataCodeGen(index: string | number): string; + abstract getReferencedMRTPropText(index: string | number, ident: string): string; + protected static _tmpArrayPool = new ReturnableObjectPool(TempArray, 10); defaultCodeGen(children: NodeChild[]) { @@ -41,12 +47,13 @@ export abstract class CodeGenVisitor { } visitPostfixExpression(node: ASTNode.PostfixExpression) { - if (node.children.length === 3) { - const context = VisitorContext.context; - - const postExpr = node.children[0] as ASTNode.PostfixExpression; + const children = node.children; + const derivationLength = children.length; + const context = VisitorContext.context; - const prop = node.children[2]; + if (derivationLength === 3) { + const postExpr = children[0] as ASTNode.PostfixExpression; + const prop = children[2]; if (prop instanceof Token) { if (context.isAttributeStruct(postExpr.type)) { @@ -65,12 +72,36 @@ export abstract class CodeGenVisitor { } // #endif return prop.lexeme; + } else if (context.isMRTStruct(postExpr.type)) { + const error = context.referenceMRTProp(prop); + // #if _VERBOSE + if (error) { + this.errors.push(error); + } + // #endif + return prop.lexeme; } return `${postExpr.codeGen(this)}.${prop.lexeme}`; } else { return `${postExpr.codeGen(this)}.${prop.codeGen(this)}`; } + } else if (derivationLength === 4) { + const identNode = children[0] as ASTNode.PostfixExpression; + const indexNode = children[2] as ASTNode.Expression; + const identLexeme = identNode.codeGen(this); + const indexLexeme = indexNode.codeGen(this); + if (identLexeme === "gl_FragData") { + // #if _VERBOSE + if (context._referencedVaryingList[V3_GL_FragColor]) { + this._reportError(identNode.location, "cannot use both gl_FragData and gl_FragColor"); + } + // #endif + const mrtLexeme = this.getFragDataCodeGen(indexLexeme); + context._referencedMRTList[mrtLexeme] = this.getReferencedMRTPropText(indexLexeme, mrtLexeme); + return mrtLexeme; + } + return `${identLexeme}[${indexLexeme}]`; } return this.defaultCodeGen(node.children); } @@ -110,7 +141,7 @@ export abstract class CodeGenVisitor { visitStatementList(node: ASTNode.StatementList): string { const children = node.children as TreeNode[]; - if (node.children.length === 1) { + if (children.length === 1) { return children[0].codeGen(this); } else { return `${children[0].codeGen(this)}\n${children[1].codeGen(this)}`; @@ -126,22 +157,24 @@ export abstract class CodeGenVisitor { } visitGlobalVariableDeclaration(node: ASTNode.VariableDeclaration): string { - const fullType = node.children[0]; + const children = node.children; + const fullType = children[0]; if (fullType instanceof ASTNode.FullySpecifiedType && fullType.typeSpecifier.isCustom) { VisitorContext.context.referenceGlobal(fullType.type, ESymbolType.STRUCT); } - return this.defaultCodeGen(node.children); + return this.defaultCodeGen(children); } visitDeclaration(node: ASTNode.Declaration): string { - const child = node.children[0]; - if ( - child instanceof ASTNode.InitDeclaratorList && - child.typeInfo.typeLexeme === VisitorContext.context.varyingStruct?.ident?.lexeme - ) { - return ""; + const { context } = VisitorContext; + const children = node.children; + const child = children[0]; + + if (child instanceof ASTNode.InitDeclaratorList) { + const typeLexeme = child.typeInfo.typeLexeme; + if (context.isVaryingStruct(typeLexeme) || context.isMRTStruct(typeLexeme)) return ""; } - return this.defaultCodeGen(node.children); + return this.defaultCodeGen(children); } visitFunctionProtoType(node: ASTNode.FunctionProtoType): string { @@ -174,24 +207,25 @@ export abstract class CodeGenVisitor { } visitJumpStatement(node: ASTNode.JumpStatement): string { - const cmd = node.children[0] as Token; + const children = node.children; + const cmd = children[0] as Token; if (cmd.type === EKeyword.RETURN) { - const expr = node.children[1]; + const expr = children[1]; if (expr instanceof ASTNode.Expression) { const returnVar = ParserUtils.unwrapNodeByType( expr, - ENonTerminal.variable_identifier + NoneTerminal.variable_identifier ); if (returnVar?.typeInfo === VisitorContext.context.varyingStruct?.ident?.lexeme) { return ""; } - const returnFnCall = ParserUtils.unwrapNodeByType(expr, ENonTerminal.function_call); + const returnFnCall = ParserUtils.unwrapNodeByType(expr, NoneTerminal.function_call); if (returnFnCall?.type === VisitorContext.context.varyingStruct?.ident?.lexeme) { return `${expr.codeGen(this)};`; } } } - return this.defaultCodeGen(node.children); + return this.defaultCodeGen(children); } visitFunctionIdentifier(node: ASTNode.FunctionIdentifier): string { diff --git a/packages/shader-lab/src/codeGen/GLES100.ts b/packages/shader-lab/src/codeGen/GLES100.ts index fdfd225f71..9243493363 100644 --- a/packages/shader-lab/src/codeGen/GLES100.ts +++ b/packages/shader-lab/src/codeGen/GLES100.ts @@ -1,3 +1,5 @@ +import { BaseToken } from "../common/BaseToken"; +import { ASTNode } from "../parser/AST"; import { GLESVisitor } from "./GLESVisitor"; import { VisitorContext } from "./VisitorContext"; import { ICodeSegment } from "./types"; @@ -21,25 +23,57 @@ export class GLES100Visitor extends GLESVisitor { return this._singleton; } - override getAttributeDeclare(): ICodeSegment[] { - const ret: ICodeSegment[] = []; + override getFragDataCodeGen(index: string | number): string { + return `gl_FragData[${index}]`; + } + + override getReferencedMRTPropText(index: string | number, ident: string): string { + return ""; + } + + override getAttributeDeclare(out: ICodeSegment[]): void { for (const item of Object.values(VisitorContext.context._referencedAttributeList)) { - ret.push({ + out.push({ text: `attribute ${item.typeInfo.typeLexeme} ${item.ident.lexeme};`, index: item.ident.location.start.index }); } - return ret; } - override getVaryingDeclare(): ICodeSegment[] { - const ret: ICodeSegment[] = []; + override getVaryingDeclare(out: ICodeSegment[]): void { for (const item of Object.values(VisitorContext.context._referencedVaryingList)) { - ret.push({ + out.push({ text: `varying ${item.typeInfo.typeLexeme} ${item.ident.lexeme};`, index: item.ident.location.start.index }); } - return ret; + } + + override getMRTDeclare(out: ICodeSegment[]): void { + return; + } + + override visitPostfixExpression(node: ASTNode.PostfixExpression): string { + const { children } = node; + const postExpr = children[0]; + const { context } = VisitorContext; + if (postExpr instanceof ASTNode.PostfixExpression && context.isMRTStruct(postExpr.type)) { + const propReferenced = children[2] as BaseToken; + const prop = context.mrtStruct!.propList.find((item) => item.ident.lexeme === propReferenced.lexeme); + if (!prop) { + this._reportError(propReferenced.location, `not found mrt property: ${propReferenced.lexeme}`); + return ""; + } + return `gl_FragData[${prop.mrtIndex!}]`; + } + return super.visitPostfixExpression(node); + } + + override visitJumpStatement(node: ASTNode.JumpStatement): string { + if (node.isFragReturnStatement) { + const expression = node.children[1] as ASTNode.Expression; + return `gl_FragColor = ${expression.codeGen(this)}`; + } + return super.visitJumpStatement(node); } } diff --git a/packages/shader-lab/src/codeGen/GLES300.ts b/packages/shader-lab/src/codeGen/GLES300.ts index 31576ab866..536a594706 100644 --- a/packages/shader-lab/src/codeGen/GLES300.ts +++ b/packages/shader-lab/src/codeGen/GLES300.ts @@ -1,14 +1,13 @@ -import { ASTNode } from "../parser/AST"; +import { ASTNode, TreeNode } from "../parser/AST"; import { SymbolType } from "../parser/types"; import { BaseToken as Token } from "../common/BaseToken"; -import { EKeyword, ETokenType, ShaderPosition } from "../common"; +import { EKeyword, ETokenType } from "../common"; import { GLESVisitor } from "./GLESVisitor"; import { EShaderStage } from "../common/Enums"; import { ICodeSegment } from "./types"; import { VisitorContext } from "./VisitorContext"; import { ShaderLab } from "../ShaderLab"; - -const V3_GL_FragColor = "GS_glFragColor"; +import { V3_GL_FragColor, V3_GL_FragData } from "./CodeGenVisitor"; export class GLES300Visitor extends GLESVisitor { override _versionText: string = "#version 300 es"; @@ -21,35 +20,58 @@ export class GLES300Visitor extends GLESVisitor { return this._singleton; } - override getAttributeDeclare(): ICodeSegment[] { - const ret: ICodeSegment[] = []; + override getFragDataCodeGen(index: string | number): string { + return `${V3_GL_FragData}_${index}`; + } + + override getReferencedMRTPropText(index: string | number, ident: string): string { + return `layout(location = ${index}) out vec4 ${ident};`; + } + + override getAttributeDeclare(out: ICodeSegment[]): void { for (const item of Object.values(VisitorContext.context._referencedAttributeList)) { - ret.push({ + out.push({ text: `in ${item.typeInfo.typeLexeme} ${item.ident.lexeme};`, index: item.ident.location.start.index }); } - return ret; } - override getVaryingDeclare(): ICodeSegment[] { - const ret: ICodeSegment[] = []; + override getVaryingDeclare(out: ICodeSegment[]): void { const qualifier = VisitorContext.context.stage === EShaderStage.FRAGMENT ? "in" : "out"; const values = Object.values(VisitorContext.context._referencedVaryingList); for (let i = 0; i < values.length; i++) { const item = values[i]; - ret.push({ + out.push({ text: `${item.qualifier ?? qualifier} ${item.typeInfo.typeLexeme} ${item.ident.lexeme};`, index: item.ident.location.start.index }); } - return ret; + } + + override getMRTDeclare(out: ICodeSegment[]): void { + const referencedMRTList = VisitorContext.context._referencedMRTList; + for (let ident in referencedMRTList) { + const info = referencedMRTList[ident]; + if (typeof info === "string") { + out.push({ + text: info, + index: Number.MAX_SAFE_INTEGER + }); + } else { + out.push({ + text: this.getReferencedMRTPropText(info.mrtIndex, ident), + index: info.ident.location.start.index + }); + } + } } override visitFunctionIdentifier(node: ASTNode.FunctionIdentifier): string { - const typeSpecifier = node.children[0] as ASTNode.TypeSpecifier; + const children = node.children; + const typeSpecifier = children[0] as ASTNode.TypeSpecifier; if (typeSpecifier.children.length !== 1) { - return this.defaultCodeGen(node.children); + return this.defaultCodeGen(children); } let ident = node.lexeme; if (node.ident === "texture2D" || node.ident === "textureCube") { @@ -81,19 +103,47 @@ export class GLES300Visitor extends GLESVisitor { } override visitVariableIdentifier(node: ASTNode.VariableIdentifier): string { - if (VisitorContext.context.stage === EShaderStage.FRAGMENT && node.lexeme === "gl_FragColor") { - if (!VisitorContext.context._referencedVaryingList[V3_GL_FragColor]) { - const token = Token.pool.get(); - token.set(ETokenType.ID, V3_GL_FragColor, ShaderLab.createPosition(0, 0, 0)); - VisitorContext.context._referencedVaryingList[V3_GL_FragColor] = { - ident: token, - typeInfo: new SymbolType(EKeyword.VEC4, "vec4"), - qualifier: "out", - astNode: node - }; + const { context } = VisitorContext; + if (context.stage === EShaderStage.FRAGMENT && node.lexeme === "gl_FragColor") { + // #if _VERBOSE + if (context._referencedMRTList["gl_FragData"]) { + this._reportError(node.location, "cannot use both gl_FragData and gl_FragColor"); } + if (context.mrtStruct) { + this._reportError(node.location, "gl_FragColor cannot be used with MRT (Multiple Render Targets)."); + } + // #endif + this._registerFragColorVariable(node); return V3_GL_FragColor; } return super.visitVariableIdentifier(node); } + + override visitJumpStatement(node: ASTNode.JumpStatement): string { + if (node.isFragReturnStatement) { + const { mrtStruct } = VisitorContext.context; + if (mrtStruct) { + return ""; + } + this._registerFragColorVariable(node); + + const expression = node.children[1] as ASTNode.Expression; + return `${V3_GL_FragColor} = ${expression.codeGen(this)};`; + } + return super.visitJumpStatement(node); + } + + private _registerFragColorVariable(node: TreeNode) { + const { _referencedVaryingList } = VisitorContext.context; + if (!_referencedVaryingList[V3_GL_FragColor]) { + const token = Token.pool.get(); + token.set(ETokenType.ID, V3_GL_FragColor, ShaderLab.createPosition(0, 0, 0)); + _referencedVaryingList[V3_GL_FragColor] = { + ident: token, + typeInfo: new SymbolType(EKeyword.VEC4, "vec4"), + qualifier: "out", + astNode: node + }; + } + } } diff --git a/packages/shader-lab/src/codeGen/GLESVisitor.ts b/packages/shader-lab/src/codeGen/GLESVisitor.ts index e3923dcf3e..e3a771992e 100644 --- a/packages/shader-lab/src/codeGen/GLESVisitor.ts +++ b/packages/shader-lab/src/codeGen/GLESVisitor.ts @@ -24,9 +24,11 @@ const defaultPrecision = ` export abstract class GLESVisitor extends CodeGenVisitor { protected _versionText: string = ""; protected _extensions: string = ""; + private _globalCodeArray: ICodeSegment[] = []; - abstract getAttributeDeclare(): ICodeSegment[]; - abstract getVaryingDeclare(): ICodeSegment[]; + abstract getAttributeDeclare(out: ICodeSegment[]): void; + abstract getVaryingDeclare(out: ICodeSegment[]): void; + abstract getMRTDeclare(out: ICodeSegment[]): void; visitShaderProgram(node: ASTNode.GLShaderProgram, vertexEntry: string, fragmentEntry: string): IShaderInfo { // #if _VERBOSE @@ -43,7 +45,7 @@ export abstract class GLESVisitor extends CodeGenVisitor { vertexMain(entry: string, data: ShaderData): string { const { symbolTable } = data; - const fnSymbol = symbolTable.lookup({ ident: entry, symbolType: ESymbolType.FN }); + const fnSymbol = symbolTable.lookup(entry, ESymbolType.FN); if (!fnSymbol?.astNode) throw `no entry function found: ${entry}`; const fnNode = fnSymbol.astNode; @@ -51,24 +53,21 @@ export abstract class GLESVisitor extends CodeGenVisitor { const returnType = fnNode.protoType.returnType; if (typeof returnType.type === "string") { - const varyStruct = symbolTable.lookup({ ident: returnType.type, symbolType: ESymbolType.STRUCT }); + const varyStruct = symbolTable.lookup(returnType.type, ESymbolType.STRUCT); if (!varyStruct) { this._reportError(returnType.location, `invalid varying struct: ${returnType.type}`); } else { VisitorContext.context.varyingStruct = varyStruct.astNode; } } else if (returnType.type !== EKeyword.VOID) { - this._reportError(returnType.location, "main entry can only return struct."); + this._reportError(returnType.location, "vertex main entry can only return struct or void."); } const paramList = fnNode.protoType.parameterList; if (paramList?.length) { for (const paramInfo of paramList) { if (typeof paramInfo.typeInfo.type === "string") { - const structSymbol = symbolTable.lookup({ - ident: paramInfo.typeInfo.type, - symbolType: ESymbolType.STRUCT - }); + const structSymbol = symbolTable.lookup(paramInfo.typeInfo.type, ESymbolType.STRUCT); if (!structSymbol) { this._reportError(paramInfo.astNode.location, `Not found attribute struct "${paramInfo.typeInfo.type}".`); continue; @@ -84,12 +83,15 @@ export abstract class GLESVisitor extends CodeGenVisitor { } const statements = fnNode.statements.codeGen(this); - const globalText = this._getGlobalText(data); - const attributeDeclare = this.getAttributeDeclare(); - const varyingDeclare = this.getVaryingDeclare(); + const { _globalCodeArray: globalCodeArray } = this; + globalCodeArray.length = 0; - const globalCode = [...globalText, ...attributeDeclare, ...varyingDeclare] + this._getGlobalText(data, globalCodeArray); + this.getAttributeDeclare(globalCodeArray); + this.getVaryingDeclare(globalCodeArray); + + const globalCode = globalCodeArray .sort((a, b) => a.index - b.index) .map((item) => item.text) .join("\n"); @@ -101,27 +103,50 @@ export abstract class GLESVisitor extends CodeGenVisitor { private _fragmentMain(entry: string, data: ShaderData): string { const { symbolTable } = data; - const fnSymbol = symbolTable.lookup({ ident: entry, symbolType: ESymbolType.FN }); + const fnSymbol = symbolTable.lookup(entry, ESymbolType.FN); if (!fnSymbol?.astNode) throw `no entry function found: ${entry}`; const fnNode = fnSymbol.astNode; - VisitorContext.context.stage = EShaderStage.FRAGMENT; + const { returnStatement } = fnNode; + if (returnStatement) { + returnStatement.isFragReturnStatement = true; + } + + const { context } = VisitorContext; + context.stage = EShaderStage.FRAGMENT; + + const { type: returnDataType, location: returnLocation } = fnNode.protoType.returnType; + if (typeof returnDataType === "string") { + const mrtStruct = symbolTable.lookup(returnDataType, ESymbolType.STRUCT); + if (!mrtStruct) { + this._reportError(returnLocation, `invalid mrt struct: ${returnDataType}`); + } else { + context.mrtStruct = mrtStruct.astNode; + } + } else if (returnDataType !== EKeyword.VOID && returnDataType !== EKeyword.VEC4) { + this._reportError(returnLocation, "fragment main entry can only return struct or vec4."); + } + const statements = fnNode.statements.codeGen(this); - const globalText = this._getGlobalText(data); - const varyingDeclare = this.getVaryingDeclare(); + const { _globalCodeArray: globalCodeArray } = this; + globalCodeArray.length = 0; - const globalCode = [...globalText, ...varyingDeclare] + this._getGlobalText(data, globalCodeArray); + this.getVaryingDeclare(globalCodeArray); + this.getMRTDeclare(globalCodeArray); + + const globalCode = globalCodeArray .sort((a, b) => a.index - b.index) .map((item) => item.text) .join("\n"); - VisitorContext.context.reset(); + context.reset(); return `${this._versionText}\n${this._extensions}\n${defaultPrecision}\n${globalCode}\n\nvoid main() ${statements}`; } private _getGlobalText( data: ShaderData, - textList: ICodeSegment[] = [], + textList: ICodeSegment[], lastLength: number = 0, _serialized: Set = new Set() ): ICodeSegment[] { diff --git a/packages/shader-lab/src/codeGen/VisitorContext.ts b/packages/shader-lab/src/codeGen/VisitorContext.ts index 6a177d12b7..5a91e97076 100644 --- a/packages/shader-lab/src/codeGen/VisitorContext.ts +++ b/packages/shader-lab/src/codeGen/VisitorContext.ts @@ -1,14 +1,11 @@ import { EShaderStage } from "../common/Enums"; import { ASTNode } from "../parser/AST"; -import { ESymbolType, SymbolTable, SymbolInfo } from "../parser/symbolTable"; -import { IParamInfo } from "../parser/types"; +import { ESymbolType, TargetSymbolTable, SymbolInfo } from "../parser/symbolTable"; +import { IParamInfo, StructProp } from "../parser/types"; import { GSErrorName } from "../GSError"; import { BaseToken } from "../common/BaseToken"; import { ShaderLab } from "../ShaderLab"; import { ShaderLabUtils } from "../ShaderLabUtils"; -// #if _VERBOSE -import { GSError } from "../GSError"; -// #endif /** @internal */ export class VisitorContext { @@ -27,16 +24,18 @@ export class VisitorContext { attributeList: IParamInfo[] = []; attributeStructs: ASTNode.StructSpecifier[] = []; varyingStruct?: ASTNode.StructSpecifier; + mrtStruct?: ASTNode.StructSpecifier; stage: EShaderStage; _referencedAttributeList: Record = Object.create(null); _referencedGlobals: Record = Object.create(null); _referencedVaryingList: Record = Object.create(null); + _referencedMRTList: Record = Object.create(null); _curFn?: ASTNode.FunctionProtoType; - _passSymbolTable: SymbolTable; + _passSymbolTable: TargetSymbolTable; private constructor() {} @@ -50,6 +49,8 @@ export class VisitorContext { this._referencedAttributeList = Object.create(null); this._referencedGlobals = Object.create(null); this._referencedVaryingList = Object.create(null); + this._referencedMRTList = Object.create(null); + this.mrtStruct = undefined; } isAttributeStruct(type: string) { @@ -60,7 +61,11 @@ export class VisitorContext { return this.varyingStruct?.ident?.lexeme === type; } - referenceAttribute(ident: BaseToken): Error { + isMRTStruct(type: string) { + return this.mrtStruct?.ident?.lexeme === type; + } + + referenceAttribute(ident: BaseToken): Error | void { if (this._referencedAttributeList[ident.lexeme]) return; const prop = this.attributeList.find((item) => item.ident.lexeme === ident.lexeme); @@ -75,7 +80,7 @@ export class VisitorContext { this._referencedAttributeList[ident.lexeme] = prop; } - referenceVarying(ident: BaseToken): Error | undefined { + referenceVarying(ident: BaseToken): Error | void { if (this._referencedVaryingList[ident.lexeme]) return; const prop = this.varyingStruct?.propList.find((item) => item.ident.lexeme === ident.lexeme); @@ -90,6 +95,21 @@ export class VisitorContext { this._referencedVaryingList[ident.lexeme] = prop; } + referenceMRTProp(ident: BaseToken): Error | void { + if (this._referencedMRTList[ident.lexeme]) return; + + const prop = this.mrtStruct?.propList.find((item) => item.ident.lexeme === ident.lexeme); + if (!prop) { + return ShaderLabUtils.createGSError( + `referenced mrt not found: ${ident.lexeme}`, + GSErrorName.CompilationError, + ShaderLab._processingPassText, + ident.location + ); + } + this._referencedMRTList[ident.lexeme] = prop; + } + referenceGlobal(ident: string, type: ESymbolType) { if (this._referencedGlobals[ident]) return; @@ -101,7 +121,7 @@ export class VisitorContext { } return; } - const sm = this.passSymbolTable.lookup({ ident, symbolType: type }); + const sm = this._passSymbolTable.lookup(ident, type); if (sm) { this._referencedGlobals[ident] = sm; } diff --git a/packages/shader-lab/src/common/BaseScanner.ts b/packages/shader-lab/src/common/BaseScanner.ts index 40e232c834..685d55b25e 100644 --- a/packages/shader-lab/src/common/BaseScanner.ts +++ b/packages/shader-lab/src/common/BaseScanner.ts @@ -148,7 +148,7 @@ export default class BaseScanner { throwError(pos: ShaderPosition | ShaderRange, ...msgs: any[]) { const error = ShaderLabUtils.createGSError(msgs.join(" "), GSErrorName.ScannerError, this._source, pos); // #if _VERBOSE - Logger.error(error.toString()); + Logger.error(error!.toString()); // #endif throw error; } diff --git a/packages/shader-lab/src/common/BaseSymbolTable.ts b/packages/shader-lab/src/common/BaseSymbolTable.ts index ded560c672..96ae17bfa1 100644 --- a/packages/shader-lab/src/common/BaseSymbolTable.ts +++ b/packages/shader-lab/src/common/BaseSymbolTable.ts @@ -1,5 +1,4 @@ import { Logger } from "@galacean/engine"; -import { GalaceanDataType } from "./types"; export interface IBaseSymbol { readonly ident: string; @@ -11,60 +10,29 @@ export interface IBaseSymbol { export abstract class BaseSymbolTable { protected _table: Map = new Map(); - /** - * Check the equality of two symbol. - */ - abstract symbolEqualCheck(exist: T, newSymbol: T): boolean; - - insert(sm: T) { - const entry = this._table.get(sm.ident) ?? []; - for (let i = 0; i < entry.length; i++) { - if (this.symbolEqualCheck(entry[i], sm)) { - Logger.warn("replace symbol:", sm.ident); - entry[i] = sm; - return; - } - } - entry.push(sm); - this._table.set(sm.ident, entry); - } - - lookup(sm: T & { signature?: GalaceanDataType[] }): R { - const entry = this._table.get(sm.ident) ?? []; - for (const item of entry) { - if (this.symbolEqualCheck(item, sm)) return item as unknown as R; - } - } + abstract insert(sm: T): void; } export class SymbolTableStack> { - private _stack: T[] = []; + stack: T[] = []; get _scope() { - return this._stack[this._stack.length - 1]; + return this.stack[this.stack.length - 1]; } newScope(scope: T) { - this._stack.push(scope); + this.stack.push(scope); } clear() { - this._stack.length = 0; + this.stack.length = 0; } dropScope() { - this._stack.pop(); + this.stack.pop(); } insert(sm: S) { this._scope.insert(sm); } - - lookup(sm: S & { signature?: GalaceanDataType[] }) { - for (let i = this._stack.length - 1; i >= 0; i--) { - const scope = this._stack[i]; - const ret = scope.lookup(sm); - if (ret) return ret; - } - } } diff --git a/packages/shader-lab/src/common/Keywords.ts b/packages/shader-lab/src/common/Keywords.ts index 0ab7b47fa5..c584d29ea9 100644 --- a/packages/shader-lab/src/common/Keywords.ts +++ b/packages/shader-lab/src/common/Keywords.ts @@ -27,6 +27,7 @@ export enum EKeyword { VEC2, VEC3, VEC4, + VEC4_ARRAY, MAT2, MAT3, MAT4, @@ -56,6 +57,8 @@ export enum EKeyword { U_SAMPLER_CUBE, U_SAMPLER2D_ARRAY, STRUCT, + LAYOUT, + LOCATION, VOID, TRUE, FALSE, diff --git a/packages/shader-lab/src/common/types.ts b/packages/shader-lab/src/common/types.ts index 508fafb630..353cc8d179 100644 --- a/packages/shader-lab/src/common/types.ts +++ b/packages/shader-lab/src/common/types.ts @@ -63,7 +63,9 @@ export const KeywordTable = new Map([ ["flat", EKeyword.FLAT], ["smooth", EKeyword.SMOOTH], ["noperspective", EKeyword.NOPERSPECTIVE], - ["centroid", EKeyword.CENTROID] + ["centroid", EKeyword.CENTROID], + ["layout", EKeyword.LAYOUT], + ["location", EKeyword.LOCATION] ]); export enum ETokenType { @@ -209,6 +211,7 @@ export type GalaceanDataType = | EKeyword.U_SAMPLER3D | EKeyword.U_SAMPLER_CUBE | EKeyword.U_SAMPLER2D_ARRAY + | EKeyword.VEC4_ARRAY | typeof TypeAny | string; diff --git a/packages/shader-lab/src/contentParser/ContentSymbolTable.ts b/packages/shader-lab/src/contentParser/ContentSymbolTable.ts new file mode 100644 index 0000000000..31fa6cb6ac --- /dev/null +++ b/packages/shader-lab/src/contentParser/ContentSymbolTable.ts @@ -0,0 +1,33 @@ +import { Logger } from "@galacean/engine"; +import { TokenType } from "../common"; +import { BaseSymbolTable, IBaseSymbol } from "../common/BaseSymbolTable"; + +export interface ISymbol extends IBaseSymbol { + type: number; + value?: any; +} + +export default class ContentSymbolTable extends BaseSymbolTable { + override insert(sm: ISymbol): void { + const entry = this._table.get(sm.ident) ?? []; + for (let i = 0; i < entry.length; i++) { + if (entry[i].type === sm.type) { + Logger.warn("replace symbol:", sm.ident); + entry[i] = sm; + return; + } + } + entry.push(sm); + this._table.set(sm.ident, entry); + } + + lookup(ident: string, type: TokenType): ISymbol | undefined { + const entry = this._table.get(ident); + if (entry) { + for (let length = entry.length, i = 0; i < length; i++) { + const item = entry[i]; + if (item.type === type) return item; + } + } + } +} diff --git a/packages/shader-lab/src/contentParser/ShaderContentParser.ts b/packages/shader-lab/src/contentParser/ShaderContentParser.ts index 06bfc6cc95..70d41731a2 100644 --- a/packages/shader-lab/src/contentParser/ShaderContentParser.ts +++ b/packages/shader-lab/src/contentParser/ShaderContentParser.ts @@ -1,10 +1,10 @@ import { SymbolTableStack } from "../common/BaseSymbolTable"; import { BaseToken } from "../common/BaseToken"; -import { EKeyword, ETokenType } from "../common"; +import { EKeyword, ETokenType, TokenType } from "../common"; import { ShaderPosition } from "../common"; import { KeywordMap } from "./KeywordMap"; import Scanner from "./Scanner"; -import SymbolTable, { ISymbol } from "./SymbolTable"; +import ContentSymbolTable, { ISymbol } from "./ContentSymbolTable"; import { RenderStateDataKey, Color, @@ -56,19 +56,11 @@ export class ShaderContentParser { static _errors: GSError[] = []; - private static _isRenderStateDeclarator(token: BaseToken) { - return RenderStateType.includes(token.type); - } - - private static _isEngineType(token: BaseToken) { - return EngineType.includes(token.type); - } - - private static _symbolTable: SymbolTableStack = new SymbolTableStack(); + private static _symbolTableStack: SymbolTableStack = new SymbolTableStack(); static reset() { this._errors.length = 0; - this._symbolTable.clear(); + this._symbolTableStack.clear(); this._newScope(); } @@ -113,6 +105,23 @@ export class ShaderContentParser { return ret; } + private static _isRenderStateDeclarator(token: BaseToken) { + return RenderStateType.includes(token.type); + } + + private static _isEngineType(token: BaseToken) { + return EngineType.includes(token.type); + } + + private static _lookupSymbolByType(ident: string, type: TokenType): ISymbol | undefined { + const stack = ShaderContentParser._symbolTableStack.stack; + for (let length = stack.length, i = length - 1; i >= 0; i--) { + const symbolTable = stack[i]; + const ret = symbolTable.lookup(ident, type); + if (ret) return ret; + } + } + private static _parseShaderStatements(ret: IShaderContent, scanner: Scanner) { let braceLevel = 1; let start = scanner.getCurPosition(); @@ -147,7 +156,7 @@ export class ShaderContentParser { braceLevel -= 1; if (braceLevel === 0) { this._addGlobalStatement(ret, scanner, start, word.lexeme.length); - this._symbolTable.dropScope(); + this._symbolTableStack.dropScope(); return; } } @@ -183,7 +192,7 @@ export class ShaderContentParser { } else if (ident.lexeme === "=") { const variable = scanner.scanToken(); scanner.scanText(";"); - const sm = this._symbolTable.lookup({ type: stateToken.type, ident: variable.lexeme }); + const sm = ShaderContentParser._lookupSymbolByType(variable.lexeme, stateToken.type); if (!sm?.value) { const error = ShaderLabUtils.createGSError( `Invalid "${stateToken.lexeme}" variable: ${variable.lexeme}`, @@ -204,7 +213,7 @@ export class ShaderContentParser { const renderState = this._parseRenderStatePropList(stateToken.lexeme, scanner); if (isDeclaration) { - this._symbolTable.insert({ ident: ident.lexeme, type: stateToken.type, value: renderState }); + this._symbolTableStack.insert({ ident: ident.lexeme, type: stateToken.type, value: renderState }); } else { Object.assign(ret.renderStates.constantMap, renderState.constantMap); Object.assign(ret.renderStates.variableMap, renderState.variableMap); @@ -214,16 +223,16 @@ export class ShaderContentParser { private static _parseVariableDeclaration(type: number, scanner: Scanner) { const token = scanner.scanToken(); scanner.scanText(";"); - this._symbolTable.insert({ type: token.type, ident: token.lexeme }); + this._symbolTableStack.insert({ type: token.type, ident: token.lexeme }); } private static _newScope() { - const symbolTable = new SymbolTable(); - this._symbolTable.newScope(symbolTable); + const symbolTable = new ContentSymbolTable(); + this._symbolTableStack.newScope(symbolTable); } private static _dropScope() { - this._symbolTable.dropScope(); + this._symbolTableStack.dropScope(); } private static _parseRenderStatePropList(state: string, scanner: Scanner): IRenderStates { diff --git a/packages/shader-lab/src/contentParser/SymbolTable.ts b/packages/shader-lab/src/contentParser/SymbolTable.ts deleted file mode 100644 index 4445123d37..0000000000 --- a/packages/shader-lab/src/contentParser/SymbolTable.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { BaseSymbolTable, IBaseSymbol } from "../common/BaseSymbolTable"; - -export interface ISymbol extends IBaseSymbol { - type: number; - value?: any; -} - -export default class SymbolTable extends BaseSymbolTable { - override symbolEqualCheck(s1: ISymbol, s2: ISymbol): boolean { - return s1.type === s2.type; - } -} diff --git a/packages/shader-lab/src/lalr/CFG.ts b/packages/shader-lab/src/lalr/CFG.ts index 01ff210182..577ccd946a 100644 --- a/packages/shader-lab/src/lalr/CFG.ts +++ b/packages/shader-lab/src/lalr/CFG.ts @@ -1,7 +1,7 @@ // Context Free Grammar of Galacean ShaderLab import { Grammar } from "../parser/Grammar"; -import { ENonTerminal, GrammarSymbol } from "../parser/GrammarSymbol"; +import { NoneTerminal, GrammarSymbol } from "../parser/GrammarSymbol"; import GrammarUtils from "./Utils"; import { EKeyword, ETokenType } from "../common"; import SematicAnalyzer, { TranslationRule } from "../parser/SemanticAnalyzer"; @@ -9,30 +9,30 @@ import { ASTNode } from "../parser/AST"; const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ...GrammarUtils.createProductionWithOptions( - ENonTerminal.gs_shader_program, - [[ENonTerminal.global_declaration], [ENonTerminal.gs_shader_program, ENonTerminal.global_declaration]], + NoneTerminal.gs_shader_program, + [[NoneTerminal.global_declaration], [NoneTerminal.gs_shader_program, NoneTerminal.global_declaration]], ASTNode.GLShaderProgram.pool ), - ...GrammarUtils.createProductionWithOptions(ENonTerminal.global_declaration, [ - [ENonTerminal.precision_specifier], - [ENonTerminal.variable_declaration], - [ENonTerminal.struct_specifier], - [ENonTerminal.function_definition] + ...GrammarUtils.createProductionWithOptions(NoneTerminal.global_declaration, [ + [NoneTerminal.precision_specifier], + [NoneTerminal.variable_declaration], + [NoneTerminal.struct_specifier], + [NoneTerminal.function_definition] ]), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.variable_declaration, + NoneTerminal.variable_declaration, [ [EKeyword.GS_RenderQueueType, ETokenType.ID, ETokenType.SEMICOLON], - [ENonTerminal.fully_specified_type, ETokenType.ID, ETokenType.SEMICOLON], - [ENonTerminal.fully_specified_type, ETokenType.ID, ENonTerminal.array_specifier, ETokenType.SEMICOLON] + [NoneTerminal.fully_specified_type, ETokenType.ID, ETokenType.SEMICOLON], + [NoneTerminal.fully_specified_type, ETokenType.ID, NoneTerminal.array_specifier, ETokenType.SEMICOLON] ], ASTNode.VariableDeclaration.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.ext_builtin_type_specifier_nonarray, + NoneTerminal.ext_builtin_type_specifier_nonarray, [ [EKeyword.VOID], [EKeyword.FLOAT], @@ -80,46 +80,46 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.type_specifier_nonarray, - [[ETokenType.ID], [ENonTerminal.ext_builtin_type_specifier_nonarray]], + NoneTerminal.type_specifier_nonarray, + [[ETokenType.ID], [NoneTerminal.ext_builtin_type_specifier_nonarray]], ASTNode.TypeSpecifierNonArray.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.fully_specified_type, - [[ENonTerminal.type_specifier], [ENonTerminal.type_qualifier, ENonTerminal.type_specifier]], + NoneTerminal.fully_specified_type, + [[NoneTerminal.type_specifier], [NoneTerminal.type_qualifier, NoneTerminal.type_specifier]], ASTNode.FullySpecifiedType.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.type_specifier, + NoneTerminal.type_specifier, [ - [ENonTerminal.type_specifier_nonarray], - [ENonTerminal.ext_builtin_type_specifier_nonarray, ENonTerminal.array_specifier] + [NoneTerminal.type_specifier_nonarray], + [NoneTerminal.ext_builtin_type_specifier_nonarray, NoneTerminal.array_specifier] ], ASTNode.TypeSpecifier.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.type_qualifier, - [[ENonTerminal.single_type_qualifier], [ENonTerminal.type_qualifier, ENonTerminal.single_type_qualifier]], + NoneTerminal.type_qualifier, + [[NoneTerminal.single_type_qualifier], [NoneTerminal.type_qualifier, NoneTerminal.single_type_qualifier]], ASTNode.TypeQualifier.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.single_type_qualifier, + NoneTerminal.single_type_qualifier, [ - [ENonTerminal.storage_qualifier], - [ENonTerminal.precision_qualifier], - [ENonTerminal.interpolation_qualifier], - [ENonTerminal.invariant_qualifier], + [NoneTerminal.storage_qualifier], + [NoneTerminal.precision_qualifier], + [NoneTerminal.interpolation_qualifier], + [NoneTerminal.invariant_qualifier], [EKeyword.PRECISE] ], ASTNode.SingleTypeQualifier.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.storage_qualifier, + NoneTerminal.storage_qualifier, [[EKeyword.CONST], [EKeyword.IN], [EKeyword.INOUT], [EKeyword.OUT], [EKeyword.CENTROID]], // #if _VERBOSE ASTNode.StorageQualifier.pool @@ -127,7 +127,7 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.interpolation_qualifier, + NoneTerminal.interpolation_qualifier, [[EKeyword.SMOOTH], [EKeyword.FLAT]], // #if _VERBOSE ASTNode.InterpolationQualifier.pool @@ -135,7 +135,7 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.invariant_qualifier, + NoneTerminal.invariant_qualifier, [[EKeyword.INVARIANT]], // #if _VERBOSE ASTNode.InvariantQualifier.pool @@ -143,7 +143,7 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.precision_qualifier, + NoneTerminal.precision_qualifier, [[EKeyword.HIGHP], [EKeyword.MEDIUMP], [EKeyword.LOWP]], // #if _VERBOSE ASTNode.PrecisionQualifier.pool @@ -151,20 +151,20 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.struct_specifier, + NoneTerminal.struct_specifier, [ [ EKeyword.STRUCT, ETokenType.ID, ETokenType.LEFT_BRACE, - ENonTerminal.struct_declaration_list, + NoneTerminal.struct_declaration_list, ETokenType.RIGHT_BRACE, ETokenType.SEMICOLON ], [ EKeyword.STRUCT, ETokenType.LEFT_BRACE, - ENonTerminal.struct_declaration_list, + NoneTerminal.struct_declaration_list, ETokenType.RIGHT_BRACE, ETokenType.SEMICOLON ] @@ -173,84 +173,100 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.struct_declaration_list, - [[ENonTerminal.struct_declaration], [ENonTerminal.struct_declaration_list, ENonTerminal.struct_declaration]], + NoneTerminal.struct_declaration_list, + [[NoneTerminal.struct_declaration], [NoneTerminal.struct_declaration_list, NoneTerminal.struct_declaration]], ASTNode.StructDeclarationList.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.struct_declaration, + NoneTerminal.struct_declaration, [ - [ENonTerminal.type_specifier, ENonTerminal.struct_declarator_list, ETokenType.SEMICOLON], + [NoneTerminal.type_specifier, NoneTerminal.struct_declarator_list, ETokenType.SEMICOLON], [ - ENonTerminal.type_qualifier, - ENonTerminal.type_specifier, - ENonTerminal.struct_declarator_list, + NoneTerminal.type_qualifier, + NoneTerminal.type_specifier, + NoneTerminal.struct_declarator_list, ETokenType.SEMICOLON - ] + ], + [NoneTerminal.layout_qualifier, NoneTerminal.type_specifier, NoneTerminal.struct_declarator, ETokenType.SEMICOLON] ], ASTNode.StructDeclaration.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.struct_declarator_list, + NoneTerminal.layout_qualifier, + [ + [ + EKeyword.LAYOUT, + ETokenType.LEFT_PAREN, + EKeyword.LOCATION, + ETokenType.EQUAL, + ETokenType.INT_CONSTANT, + ETokenType.RIGHT_PAREN + ] + ], + ASTNode.LayoutQualifier.pool + ), + + ...GrammarUtils.createProductionWithOptions( + NoneTerminal.struct_declarator_list, [ - [ENonTerminal.struct_declarator], - [ENonTerminal.struct_declarator_list, ETokenType.COMMA, ENonTerminal.struct_declarator] + [NoneTerminal.struct_declarator], + [NoneTerminal.struct_declarator_list, ETokenType.COMMA, NoneTerminal.struct_declarator] ], ASTNode.StructDeclaratorList.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.struct_declarator, - [[ETokenType.ID], [ETokenType.ID, ENonTerminal.array_specifier]], + NoneTerminal.struct_declarator, + [[ETokenType.ID], [ETokenType.ID, NoneTerminal.array_specifier]], ASTNode.StructDeclarator.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.array_specifier, + NoneTerminal.array_specifier, [ [ETokenType.LEFT_BRACKET, ETokenType.RIGHT_BRACKET], - [ETokenType.LEFT_BRACKET, ENonTerminal.integer_constant_expression, ETokenType.RIGHT_BRACKET] + [ETokenType.LEFT_BRACKET, NoneTerminal.integer_constant_expression, ETokenType.RIGHT_BRACKET] ], ASTNode.ArraySpecifier.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.integer_constant_expression_operator, + NoneTerminal.integer_constant_expression_operator, [[ETokenType.PLUS], [ETokenType.DASH], [ETokenType.STAR], [ETokenType.SLASH], [ETokenType.PERCENT]], ASTNode.IntegerConstantExpressionOperator.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.integer_constant_expression, + NoneTerminal.integer_constant_expression, [ - [ENonTerminal.variable_identifier], + [NoneTerminal.variable_identifier], [ETokenType.INT_CONSTANT], [ - ENonTerminal.integer_constant_expression, - ENonTerminal.integer_constant_expression_operator, + NoneTerminal.integer_constant_expression, + NoneTerminal.integer_constant_expression_operator, ETokenType.INT_CONSTANT ], [ - ENonTerminal.integer_constant_expression, - ENonTerminal.integer_constant_expression_operator, - ENonTerminal.variable_identifier + NoneTerminal.integer_constant_expression, + NoneTerminal.integer_constant_expression_operator, + NoneTerminal.variable_identifier ] ], ASTNode.IntegerConstantExpression.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.conditional_expression, + NoneTerminal.conditional_expression, [ - [ENonTerminal.logical_or_expression], + [NoneTerminal.logical_or_expression], [ - ENonTerminal.logical_or_expression, + NoneTerminal.logical_or_expression, ETokenType.QUESTION, - ENonTerminal.expression, + NoneTerminal.expression, ETokenType.COLON, - ENonTerminal.assignment_expression + NoneTerminal.assignment_expression ] ], // #if _VERBOSE @@ -259,10 +275,10 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.logical_or_expression, + NoneTerminal.logical_or_expression, [ - [ENonTerminal.logical_xor_expression], - [ENonTerminal.logical_or_expression, ETokenType.OR_OP, ENonTerminal.logical_xor_expression] + [NoneTerminal.logical_xor_expression], + [NoneTerminal.logical_or_expression, ETokenType.OR_OP, NoneTerminal.logical_xor_expression] ], // #if _VERBOSE ASTNode.LogicalOrExpression.pool @@ -270,10 +286,10 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.logical_xor_expression, + NoneTerminal.logical_xor_expression, [ - [ENonTerminal.logical_and_expression], - [ENonTerminal.logical_xor_expression, ETokenType.XOR_OP, ENonTerminal.logical_and_expression] + [NoneTerminal.logical_and_expression], + [NoneTerminal.logical_xor_expression, ETokenType.XOR_OP, NoneTerminal.logical_and_expression] ], // #if _VERBOSE ASTNode.LogicalXorExpression.pool @@ -281,10 +297,10 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.logical_and_expression, + NoneTerminal.logical_and_expression, [ - [ENonTerminal.inclusive_or_expression], - [ENonTerminal.logical_and_expression, ETokenType.AND_OP, ENonTerminal.inclusive_or_expression] + [NoneTerminal.inclusive_or_expression], + [NoneTerminal.logical_and_expression, ETokenType.AND_OP, NoneTerminal.inclusive_or_expression] ], // #if _VERBOSE ASTNode.LogicalAndExpression.pool @@ -292,10 +308,10 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.inclusive_or_expression, + NoneTerminal.inclusive_or_expression, [ - [ENonTerminal.exclusive_or_expression], - [ENonTerminal.inclusive_or_expression, ETokenType.VERTICAL_BAR, ENonTerminal.exclusive_or_expression] + [NoneTerminal.exclusive_or_expression], + [NoneTerminal.inclusive_or_expression, ETokenType.VERTICAL_BAR, NoneTerminal.exclusive_or_expression] ], // #if _VERBOSE ASTNode.InclusiveOrExpression.pool @@ -303,10 +319,10 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.exclusive_or_expression, + NoneTerminal.exclusive_or_expression, [ - [ENonTerminal.and_expression], - [ENonTerminal.exclusive_or_expression, ETokenType.CARET, ENonTerminal.and_expression] + [NoneTerminal.and_expression], + [NoneTerminal.exclusive_or_expression, ETokenType.CARET, NoneTerminal.and_expression] ], // #if _VERBOSE ASTNode.ExclusiveOrExpression.pool @@ -314,10 +330,10 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.and_expression, + NoneTerminal.and_expression, [ - [ENonTerminal.equality_expression], - [ENonTerminal.and_expression, ETokenType.AMPERSAND, ENonTerminal.equality_expression] + [NoneTerminal.equality_expression], + [NoneTerminal.and_expression, ETokenType.AMPERSAND, NoneTerminal.equality_expression] ], // #if _VERBOSE ASTNode.AndExpression.pool @@ -325,11 +341,11 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.equality_expression, + NoneTerminal.equality_expression, [ - [ENonTerminal.relational_expression], - [ENonTerminal.equality_expression, ETokenType.EQ_OP, ENonTerminal.relational_expression], - [ENonTerminal.equality_expression, ETokenType.NE_OP, ENonTerminal.relational_expression] + [NoneTerminal.relational_expression], + [NoneTerminal.equality_expression, ETokenType.EQ_OP, NoneTerminal.relational_expression], + [NoneTerminal.equality_expression, ETokenType.NE_OP, NoneTerminal.relational_expression] ], // #if _VERBOSE ASTNode.EqualityExpression.pool @@ -337,13 +353,13 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.relational_expression, + NoneTerminal.relational_expression, [ - [ENonTerminal.shift_expression], - [ENonTerminal.relational_expression, ETokenType.LEFT_ANGLE, ENonTerminal.shift_expression], - [ENonTerminal.relational_expression, ETokenType.RIGHT_ANGLE, ENonTerminal.shift_expression], - [ENonTerminal.relational_expression, ETokenType.LE_OP, ENonTerminal.shift_expression], - [ENonTerminal.relational_expression, ETokenType.GE_OP, ENonTerminal.shift_expression] + [NoneTerminal.shift_expression], + [NoneTerminal.relational_expression, ETokenType.LEFT_ANGLE, NoneTerminal.shift_expression], + [NoneTerminal.relational_expression, ETokenType.RIGHT_ANGLE, NoneTerminal.shift_expression], + [NoneTerminal.relational_expression, ETokenType.LE_OP, NoneTerminal.shift_expression], + [NoneTerminal.relational_expression, ETokenType.GE_OP, NoneTerminal.shift_expression] ], // #if _VERBOSE ASTNode.RelationalExpression.pool @@ -351,11 +367,11 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.shift_expression, + NoneTerminal.shift_expression, [ - [ENonTerminal.additive_expression], - [ENonTerminal.shift_expression, ETokenType.LEFT_OP, ENonTerminal.additive_expression], - [ENonTerminal.shift_expression, ETokenType.RIGHT_OP, ENonTerminal.additive_expression] + [NoneTerminal.additive_expression], + [NoneTerminal.shift_expression, ETokenType.LEFT_OP, NoneTerminal.additive_expression], + [NoneTerminal.shift_expression, ETokenType.RIGHT_OP, NoneTerminal.additive_expression] ], // #if _VERBOSE ASTNode.ShiftExpression.pool @@ -363,11 +379,11 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.additive_expression, + NoneTerminal.additive_expression, [ - [ENonTerminal.multiplicative_expression], - [ENonTerminal.additive_expression, ETokenType.PLUS, ENonTerminal.multiplicative_expression], - [ENonTerminal.additive_expression, ETokenType.DASH, ENonTerminal.multiplicative_expression] + [NoneTerminal.multiplicative_expression], + [NoneTerminal.additive_expression, ETokenType.PLUS, NoneTerminal.multiplicative_expression], + [NoneTerminal.additive_expression, ETokenType.DASH, NoneTerminal.multiplicative_expression] ], // #if _VERBOSE ASTNode.AdditiveExpression.pool @@ -375,12 +391,12 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.multiplicative_expression, + NoneTerminal.multiplicative_expression, [ - [ENonTerminal.unary_expression], - [ENonTerminal.multiplicative_expression, ETokenType.STAR, ENonTerminal.unary_expression], - [ENonTerminal.multiplicative_expression, ETokenType.SLASH, ENonTerminal.unary_expression], - [ENonTerminal.multiplicative_expression, ETokenType.PERCENT, ENonTerminal.unary_expression] + [NoneTerminal.unary_expression], + [NoneTerminal.multiplicative_expression, ETokenType.STAR, NoneTerminal.unary_expression], + [NoneTerminal.multiplicative_expression, ETokenType.SLASH, NoneTerminal.unary_expression], + [NoneTerminal.multiplicative_expression, ETokenType.PERCENT, NoneTerminal.unary_expression] ], // #if _VERBOSE ASTNode.MultiplicativeExpression.pool @@ -388,12 +404,12 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.unary_expression, + NoneTerminal.unary_expression, [ - [ENonTerminal.postfix_expression], - [ETokenType.INC_OP, ENonTerminal.unary_expression], - [ETokenType.DEC_OP, ENonTerminal.unary_expression], - [ENonTerminal.unary_operator, ENonTerminal.unary_expression] + [NoneTerminal.postfix_expression], + [ETokenType.INC_OP, NoneTerminal.unary_expression], + [ETokenType.DEC_OP, NoneTerminal.unary_expression], + [NoneTerminal.unary_operator, NoneTerminal.unary_expression] ], // #if _VERBOSE ASTNode.UnaryExpression.pool @@ -401,7 +417,7 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.unary_operator, + NoneTerminal.unary_operator, [[ETokenType.PLUS], [ETokenType.DASH], [ETokenType.BANG], [ETokenType.TILDE]], // #if _VERBOSE ASTNode.UnaryOperator.pool @@ -409,52 +425,52 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.postfix_expression, + NoneTerminal.postfix_expression, [ - [ENonTerminal.primary_expression], - [ENonTerminal.postfix_expression, ETokenType.LEFT_BRACKET, ENonTerminal.expression, ETokenType.RIGHT_BRACKET], - [ENonTerminal.function_call], - [ENonTerminal.postfix_expression, ETokenType.DOT, ETokenType.ID], - [ENonTerminal.postfix_expression, ETokenType.DOT, ENonTerminal.function_call], - [ENonTerminal.postfix_expression, ETokenType.INC_OP], - [ENonTerminal.postfix_expression, ETokenType.DEC_OP] + [NoneTerminal.primary_expression], + [NoneTerminal.postfix_expression, ETokenType.LEFT_BRACKET, NoneTerminal.expression, ETokenType.RIGHT_BRACKET], + [NoneTerminal.function_call], + [NoneTerminal.postfix_expression, ETokenType.DOT, ETokenType.ID], + [NoneTerminal.postfix_expression, ETokenType.DOT, NoneTerminal.function_call], + [NoneTerminal.postfix_expression, ETokenType.INC_OP], + [NoneTerminal.postfix_expression, ETokenType.DEC_OP] ], ASTNode.PostfixExpression.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.primary_expression, + NoneTerminal.primary_expression, [ - [ENonTerminal.variable_identifier], + [NoneTerminal.variable_identifier], [ETokenType.INT_CONSTANT], [ETokenType.FLOAT_CONSTANT], [EKeyword.TRUE], [EKeyword.FALSE], - [ETokenType.LEFT_PAREN, ENonTerminal.expression, ETokenType.RIGHT_PAREN] + [ETokenType.LEFT_PAREN, NoneTerminal.expression, ETokenType.RIGHT_PAREN] ], ASTNode.PrimaryExpression.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.expression, + NoneTerminal.expression, [ - [ENonTerminal.assignment_expression], - [ENonTerminal.expression, ETokenType.COMMA, ENonTerminal.assignment_expression] + [NoneTerminal.assignment_expression], + [NoneTerminal.expression, ETokenType.COMMA, NoneTerminal.assignment_expression] ], ASTNode.Expression.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.assignment_expression, + NoneTerminal.assignment_expression, [ - [ENonTerminal.conditional_expression], - [ENonTerminal.unary_expression, ENonTerminal.assignment_operator, ENonTerminal.assignment_expression] + [NoneTerminal.conditional_expression], + [NoneTerminal.unary_expression, NoneTerminal.assignment_operator, NoneTerminal.assignment_expression] ], ASTNode.AssignmentExpression.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.assignment_operator, + NoneTerminal.assignment_operator, [ [ETokenType.EQUAL], [ETokenType.MUL_ASSIGN], @@ -474,117 +490,117 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.function_call, - [[ENonTerminal.function_call_generic]], + NoneTerminal.function_call, + [[NoneTerminal.function_call_generic]], ASTNode.FunctionCall.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.function_call_generic, + NoneTerminal.function_call_generic, [ [ - ENonTerminal.function_identifier, + NoneTerminal.function_identifier, ETokenType.LEFT_PAREN, - ENonTerminal.function_call_parameter_list, + NoneTerminal.function_call_parameter_list, ETokenType.RIGHT_PAREN ], - [ENonTerminal.function_identifier, ETokenType.LEFT_PAREN, ETokenType.RIGHT_PAREN], - [ENonTerminal.function_identifier, EKeyword.VOID, ETokenType.RIGHT_PAREN] + [NoneTerminal.function_identifier, ETokenType.LEFT_PAREN, ETokenType.RIGHT_PAREN], + [NoneTerminal.function_identifier, EKeyword.VOID, ETokenType.RIGHT_PAREN] ], ASTNode.FunctionCallGeneric.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.function_call_parameter_list, + NoneTerminal.function_call_parameter_list, [ - [ENonTerminal.assignment_expression], - [ENonTerminal.function_call_parameter_list, ETokenType.COMMA, ENonTerminal.assignment_expression] + [NoneTerminal.assignment_expression], + [NoneTerminal.function_call_parameter_list, ETokenType.COMMA, NoneTerminal.assignment_expression] ], ASTNode.FunctionCallParameterList.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.function_identifier, - [[ENonTerminal.type_specifier]], + NoneTerminal.function_identifier, + [[NoneTerminal.type_specifier]], ASTNode.FunctionIdentifier.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.function_definition, - [[ENonTerminal.function_prototype, ENonTerminal.compound_statement_no_scope]], + NoneTerminal.function_definition, + [[NoneTerminal.function_prototype, NoneTerminal.compound_statement_no_scope]], ASTNode.FunctionDefinition.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.function_prototype, - [[ENonTerminal.function_declarator, ETokenType.RIGHT_PAREN]], + NoneTerminal.function_prototype, + [[NoneTerminal.function_declarator, ETokenType.RIGHT_PAREN]], ASTNode.FunctionProtoType.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.function_declarator, - [[ENonTerminal.function_header], [ENonTerminal.function_header, ENonTerminal.function_parameter_list]], + NoneTerminal.function_declarator, + [[NoneTerminal.function_header], [NoneTerminal.function_header, NoneTerminal.function_parameter_list]], ASTNode.FunctionDeclarator.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.function_header, - [[ENonTerminal.fully_specified_type, ETokenType.ID, ETokenType.LEFT_PAREN]], + NoneTerminal.function_header, + [[NoneTerminal.fully_specified_type, ETokenType.ID, ETokenType.LEFT_PAREN]], ASTNode.FunctionHeader.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.function_parameter_list, + NoneTerminal.function_parameter_list, [ - [ENonTerminal.parameter_declaration], - [ENonTerminal.function_parameter_list, ETokenType.COMMA, ENonTerminal.parameter_declaration] + [NoneTerminal.parameter_declaration], + [NoneTerminal.function_parameter_list, ETokenType.COMMA, NoneTerminal.parameter_declaration] ], ASTNode.FunctionParameterList.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.parameter_declaration, - [[ENonTerminal.type_qualifier, ENonTerminal.parameter_declarator], [ENonTerminal.parameter_declarator]], + NoneTerminal.parameter_declaration, + [[NoneTerminal.type_qualifier, NoneTerminal.parameter_declarator], [NoneTerminal.parameter_declarator]], ASTNode.ParameterDeclaration.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.parameter_declarator, + NoneTerminal.parameter_declarator, [ - [ENonTerminal.type_specifier, ETokenType.ID], - [ENonTerminal.type_specifier, ETokenType.ID, ENonTerminal.array_specifier] + [NoneTerminal.type_specifier, ETokenType.ID], + [NoneTerminal.type_specifier, ETokenType.ID, NoneTerminal.array_specifier] ], ASTNode.ParameterDeclarator.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.statement_list, - [[ENonTerminal.statement], [ENonTerminal.statement_list, ENonTerminal.statement]], + NoneTerminal.statement_list, + [[NoneTerminal.statement], [NoneTerminal.statement_list, NoneTerminal.statement]], ASTNode.StatementList.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.statement, - [[ENonTerminal.compound_statement], [ENonTerminal.simple_statement]], + NoneTerminal.statement, + [[NoneTerminal.compound_statement], [NoneTerminal.simple_statement]], // #if _VERBOSE ASTNode.Statement.pool // #endif ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.compound_statement_no_scope, + NoneTerminal.compound_statement_no_scope, [ [ETokenType.LEFT_BRACE, ETokenType.RIGHT_BRACE], - [ETokenType.LEFT_BRACE, ENonTerminal.statement_list, ETokenType.RIGHT_BRACE] + [ETokenType.LEFT_BRACE, NoneTerminal.statement_list, ETokenType.RIGHT_BRACE] ], ASTNode.CompoundStatementNoScope.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.compound_statement, + NoneTerminal.compound_statement, [ [ETokenType.LEFT_BRACE, ETokenType.RIGHT_BRACE], - [ENonTerminal.scope_brace, ENonTerminal.statement_list, ENonTerminal.scope_end_brace] + [NoneTerminal.scope_brace, NoneTerminal.statement_list, NoneTerminal.scope_end_brace] ], // #if _VERBOSE ASTNode.CompoundStatement.pool @@ -592,13 +608,13 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.simple_statement, + NoneTerminal.simple_statement, [ - [ENonTerminal.declaration], - [ENonTerminal.expression_statement], - [ENonTerminal.selection_statement], - [ENonTerminal.iteration_statement], - [ENonTerminal.jump_statement] + [NoneTerminal.declaration], + [NoneTerminal.expression_statement], + [NoneTerminal.selection_statement], + [NoneTerminal.iteration_statement], + [NoneTerminal.jump_statement] ], // #if _VERBOSE ASTNode.SimpleStatement.pool @@ -606,72 +622,72 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.declaration, + NoneTerminal.declaration, [ - [ENonTerminal.function_prototype, ETokenType.SEMICOLON], - [ENonTerminal.init_declarator_list, ETokenType.SEMICOLON], + [NoneTerminal.function_prototype, ETokenType.SEMICOLON], + [NoneTerminal.init_declarator_list, ETokenType.SEMICOLON], [ EKeyword.PRECISION, - ENonTerminal.precision_qualifier, - ENonTerminal.ext_builtin_type_specifier_nonarray, + NoneTerminal.precision_qualifier, + NoneTerminal.ext_builtin_type_specifier_nonarray, ETokenType.SEMICOLON ], - [ENonTerminal.type_qualifier, ETokenType.ID, ETokenType.SEMICOLON], - [ENonTerminal.type_qualifier, ETokenType.ID, ENonTerminal.identifier_list, ETokenType.SEMICOLON] + [NoneTerminal.type_qualifier, ETokenType.ID, ETokenType.SEMICOLON], + [NoneTerminal.type_qualifier, ETokenType.ID, NoneTerminal.identifier_list, ETokenType.SEMICOLON] ], ASTNode.Declaration.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.identifier_list, + NoneTerminal.identifier_list, [ [ETokenType.COMMA, ETokenType.ID], - [ENonTerminal.identifier_list, ETokenType.COMMA, ETokenType.ID] + [NoneTerminal.identifier_list, ETokenType.COMMA, ETokenType.ID] ], ASTNode.IdentifierList.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.init_declarator_list, + NoneTerminal.init_declarator_list, [ - [ENonTerminal.single_declaration], - [ENonTerminal.init_declarator_list, ETokenType.COMMA, ETokenType.ID], - [ENonTerminal.init_declarator_list, ETokenType.COMMA, ETokenType.ID, ENonTerminal.array_specifier], + [NoneTerminal.single_declaration], + [NoneTerminal.init_declarator_list, ETokenType.COMMA, ETokenType.ID], + [NoneTerminal.init_declarator_list, ETokenType.COMMA, ETokenType.ID, NoneTerminal.array_specifier], [ - ENonTerminal.init_declarator_list, + NoneTerminal.init_declarator_list, ETokenType.COMMA, ETokenType.ID, - ENonTerminal.array_specifier, + NoneTerminal.array_specifier, ETokenType.EQUAL, - ENonTerminal.initializer + NoneTerminal.initializer ], - [ENonTerminal.init_declarator_list, ETokenType.COMMA, ETokenType.ID, ETokenType.EQUAL, ENonTerminal.initializer] + [NoneTerminal.init_declarator_list, ETokenType.COMMA, ETokenType.ID, ETokenType.EQUAL, NoneTerminal.initializer] ], ASTNode.InitDeclaratorList.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.single_declaration, + NoneTerminal.single_declaration, [ - [ENonTerminal.fully_specified_type, ETokenType.ID], - [ENonTerminal.fully_specified_type, ETokenType.ID, ENonTerminal.array_specifier], + [NoneTerminal.fully_specified_type, ETokenType.ID], + [NoneTerminal.fully_specified_type, ETokenType.ID, NoneTerminal.array_specifier], [ - ENonTerminal.fully_specified_type, + NoneTerminal.fully_specified_type, ETokenType.ID, - ENonTerminal.array_specifier, + NoneTerminal.array_specifier, ETokenType.EQUAL, - ENonTerminal.initializer + NoneTerminal.initializer ], - [ENonTerminal.fully_specified_type, ETokenType.ID, ETokenType.EQUAL, ENonTerminal.initializer] + [NoneTerminal.fully_specified_type, ETokenType.ID, ETokenType.EQUAL, NoneTerminal.initializer] ], ASTNode.SingleDeclaration.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.initializer, + NoneTerminal.initializer, [ - [ENonTerminal.assignment_expression], - [ETokenType.LEFT_BRACE, ENonTerminal.initializer_list, ETokenType.RIGHT_BRACE] + [NoneTerminal.assignment_expression], + [ETokenType.LEFT_BRACE, NoneTerminal.initializer_list, ETokenType.RIGHT_BRACE] ], // #if _VERBOSE ASTNode.Initializer.pool @@ -679,16 +695,16 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.initializer_list, - [[ENonTerminal.initializer], [ENonTerminal.initializer_list, ETokenType.COMMA, ENonTerminal.initializer]], + NoneTerminal.initializer_list, + [[NoneTerminal.initializer], [NoneTerminal.initializer_list, ETokenType.COMMA, NoneTerminal.initializer]], // #if _VERBOSE ASTNode.InitializerList.pool // #endif ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.expression_statement, - [[ETokenType.SEMICOLON], [ENonTerminal.expression, ETokenType.SEMICOLON]], + NoneTerminal.expression_statement, + [[ETokenType.SEMICOLON], [NoneTerminal.expression, ETokenType.SEMICOLON]], // #if _VERBOSE ASTNode.ExpressionStatement.pool // #endif @@ -696,17 +712,17 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ // dangling else ambiguity ...GrammarUtils.createProductionWithOptions( - ENonTerminal.selection_statement, + NoneTerminal.selection_statement, [ - [EKeyword.IF, ETokenType.LEFT_PAREN, ENonTerminal.expression, ETokenType.RIGHT_PAREN, ENonTerminal.statement], + [EKeyword.IF, ETokenType.LEFT_PAREN, NoneTerminal.expression, ETokenType.RIGHT_PAREN, NoneTerminal.statement], [ EKeyword.IF, ETokenType.LEFT_PAREN, - ENonTerminal.expression, + NoneTerminal.expression, ETokenType.RIGHT_PAREN, - ENonTerminal.statement, + NoneTerminal.statement, EKeyword.ELSE, - ENonTerminal.statement + NoneTerminal.statement ] ], // #if _VERBOSE @@ -715,16 +731,16 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.iteration_statement, + NoneTerminal.iteration_statement, [ - [EKeyword.WHILE, ETokenType.LEFT_PAREN, ENonTerminal.condition, ETokenType.RIGHT_PAREN, ENonTerminal.statement], + [EKeyword.WHILE, ETokenType.LEFT_PAREN, NoneTerminal.condition, ETokenType.RIGHT_PAREN, NoneTerminal.statement], [ EKeyword.FOR, ETokenType.LEFT_PAREN, - ENonTerminal.for_init_statement, - ENonTerminal.for_rest_statement, + NoneTerminal.for_init_statement, + NoneTerminal.for_rest_statement, ETokenType.RIGHT_PAREN, - ENonTerminal.statement + NoneTerminal.statement ] ], // #if _VERBOSE @@ -733,12 +749,12 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.precision_specifier, + NoneTerminal.precision_specifier, [ [ EKeyword.PRECISION, - ENonTerminal.precision_qualifier, - ENonTerminal.ext_builtin_type_specifier_nonarray, + NoneTerminal.precision_qualifier, + NoneTerminal.ext_builtin_type_specifier_nonarray, ETokenType.SEMICOLON ] ], @@ -746,18 +762,18 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.for_init_statement, - [[ENonTerminal.expression_statement], [ENonTerminal.declaration]], + NoneTerminal.for_init_statement, + [[NoneTerminal.expression_statement], [NoneTerminal.declaration]], // #if _VERBOSE ASTNode.ForInitStatement.pool // #endif ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.condition, + NoneTerminal.condition, [ - [ENonTerminal.expression], - [ENonTerminal.fully_specified_type, ETokenType.ID, ETokenType.EQUAL, ENonTerminal.initializer] + [NoneTerminal.expression], + [NoneTerminal.fully_specified_type, ETokenType.ID, ETokenType.EQUAL, NoneTerminal.initializer] ], // #if _VERBOSE ASTNode.Condition.pool @@ -765,10 +781,10 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.for_rest_statement, + NoneTerminal.for_rest_statement, [ - [ENonTerminal.conditionopt, ETokenType.SEMICOLON], - [ENonTerminal.conditionopt, ETokenType.SEMICOLON, ENonTerminal.expression] + [NoneTerminal.conditionopt, ETokenType.SEMICOLON], + [NoneTerminal.conditionopt, ETokenType.SEMICOLON, NoneTerminal.expression] ], // #if _VERBOSE ASTNode.ForRestStatement.pool @@ -776,39 +792,39 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.conditionopt, - [[ETokenType.EPSILON], [ENonTerminal.condition]], + NoneTerminal.conditionopt, + [[ETokenType.EPSILON], [NoneTerminal.condition]], // #if _VERBOSE ASTNode.ConditionOpt.pool // #endif ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.jump_statement, + NoneTerminal.jump_statement, [ [EKeyword.CONTINUE, ETokenType.SEMICOLON], [EKeyword.BREAK, ETokenType.SEMICOLON], [EKeyword.RETURN, ETokenType.SEMICOLON], - [EKeyword.RETURN, ENonTerminal.expression, ETokenType.SEMICOLON], + [EKeyword.RETURN, NoneTerminal.expression, ETokenType.SEMICOLON], [EKeyword.DISCARD, ETokenType.SEMICOLON] ], ASTNode.JumpStatement.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.scope_brace, + NoneTerminal.scope_brace, [[ETokenType.LEFT_BRACE]], ASTNode.ScopeBrace.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.scope_end_brace, + NoneTerminal.scope_end_brace, [[ETokenType.RIGHT_BRACE]], ASTNode.ScopeEndBrace.pool ), ...GrammarUtils.createProductionWithOptions( - ENonTerminal.variable_identifier, + NoneTerminal.variable_identifier, [[ETokenType.ID]], ASTNode.VariableIdentifier.pool ) @@ -816,7 +832,7 @@ const productionAndRules: [GrammarSymbol[], TranslationRule | undefined][] = [ const createGrammar = () => Grammar.create( - ENonTerminal.gs_shader_program, + NoneTerminal.gs_shader_program, productionAndRules.map((item) => item[0]) ); diff --git a/packages/shader-lab/src/lalr/LALR1.ts b/packages/shader-lab/src/lalr/LALR1.ts index b83eb78f60..31a3bafe12 100644 --- a/packages/shader-lab/src/lalr/LALR1.ts +++ b/packages/shader-lab/src/lalr/LALR1.ts @@ -1,5 +1,5 @@ import { Grammar } from "../parser/Grammar"; -import { ENonTerminal, GrammarSymbol, Terminal } from "../parser/GrammarSymbol"; +import { NoneTerminal, GrammarSymbol, Terminal } from "../parser/GrammarSymbol"; import State from "./State"; import StateItem from "./StateItem"; import GrammarUtils from "./Utils"; @@ -11,15 +11,15 @@ import { ActionInfo, ActionTable, EAction, GotoTable, StateActionTable, StateGot * The [LALR1](https://web.stanford.edu/class/archive/cs/cs143/cs143.1128/handouts/140%20LALR%20Parsing.pdf) Parser generator */ export class LALR1 { - readonly firstSetMap: Map> = new Map(); - readonly followSetMap: Map> = new Map(); + readonly firstSetMap: Map> = new Map(); + readonly followSetMap: Map> = new Map(); readonly actionTable: StateActionTable = new Map(); readonly gotoTable: StateGotoTable = new Map(); private grammar: Grammar; /** For circle detect */ - private _firstSetNTStack: ENonTerminal[] = []; + private _firstSetNTStack: NoneTerminal[] = []; constructor(grammar: Grammar) { this.grammar = grammar; @@ -58,7 +58,7 @@ export class LALR1 { private _extendStateItem(state: State, item: StateItem) { if (GrammarUtils.isTerminal(item.curSymbol)) return; - const productionList = this.grammar.getProductionList(item.curSymbol); + const productionList = this.grammar.getProductionList(item.curSymbol); if (item.nextSymbol) { let newLookaheadSet = new Set(); @@ -72,7 +72,7 @@ export class LALR1 { terminalExist = true; break; } - lastFirstSet = this.firstSetMap.get(nextSymbol)!; + lastFirstSet = this.firstSetMap.get(nextSymbol)!; for (const t of lastFirstSet) { newLookaheadSet.add(t); } @@ -115,7 +115,7 @@ export class LALR1 { for (const stateItem of state.items) { if (stateItem.canReduce()) { let action: ActionInfo; - if (stateItem.production.goal !== ENonTerminal.START) { + if (stateItem.production.goal !== NoneTerminal.START) { action = { action: EAction.Reduce, target: stateItem.production.id @@ -144,7 +144,7 @@ export class LALR1 { target: newState.id }); } else { - stateGotoTable.set(gs, newState.id); + stateGotoTable.set(gs, newState.id); } newStates.add(newState); @@ -181,7 +181,7 @@ export class LALR1 { } } - private _computeFirstSetForNT(NT: ENonTerminal) { + private _computeFirstSetForNT(NT: NoneTerminal) { // circle detect const idx = this._firstSetNTStack.findIndex((item) => item === NT); if (idx !== -1) { @@ -209,12 +209,12 @@ export class LALR1 { break; } - const succeedFirstSet = this._computeFirstSetForNT(gs); + const succeedFirstSet = this._computeFirstSetForNT(gs); for (const item of succeedFirstSet) { if (item !== ETokenType.EPSILON) firstSet.add(item); } - if (!this.grammar.isNullableNT(gs)) break; + if (!this.grammar.isNullableNT(gs)) break; } if (i === production.derivation.length) firstSet.add(ETokenType.EPSILON); } diff --git a/packages/shader-lab/src/lalr/Production.ts b/packages/shader-lab/src/lalr/Production.ts index f6c6d32087..848a7ccb8f 100644 --- a/packages/shader-lab/src/lalr/Production.ts +++ b/packages/shader-lab/src/lalr/Production.ts @@ -1,14 +1,14 @@ -import { ENonTerminal, GrammarSymbol } from "../parser/GrammarSymbol"; +import { NoneTerminal, GrammarSymbol } from "../parser/GrammarSymbol"; export default class Production { private static _id = 0; static pool: Map = new Map(); - readonly goal: ENonTerminal; + readonly goal: NoneTerminal; readonly derivation: GrammarSymbol[]; readonly id: number; - constructor(goal: ENonTerminal, derivation: GrammarSymbol[]) { + constructor(goal: NoneTerminal, derivation: GrammarSymbol[]) { this.goal = goal; this.derivation = derivation; this.id = Production._id++; diff --git a/packages/shader-lab/src/lalr/StateItem.ts b/packages/shader-lab/src/lalr/StateItem.ts index ccf6bbb845..c065e7d1c3 100644 --- a/packages/shader-lab/src/lalr/StateItem.ts +++ b/packages/shader-lab/src/lalr/StateItem.ts @@ -1,5 +1,5 @@ import { ETokenType } from "../common"; -import { ENonTerminal, Terminal } from "../parser/GrammarSymbol"; +import { NoneTerminal, Terminal } from "../parser/GrammarSymbol"; import Production from "./Production"; import GrammarUtils from "./Utils"; @@ -70,7 +70,7 @@ export default class StateItem { const coreItem = this.production.derivation.map((item) => GrammarUtils.toString(item)); coreItem[this.position] = "." + (coreItem[this.position] ?? ""); - return `${ENonTerminal[this.production.goal]} :=> ${coreItem.join("|")} ;${Array.from(this.lookaheadSet) + return `${NoneTerminal[this.production.goal]} :=> ${coreItem.join("|")} ;${Array.from(this.lookaheadSet) .map((item) => GrammarUtils.toString(item)) .join("/")}`; } diff --git a/packages/shader-lab/src/lalr/Utils.ts b/packages/shader-lab/src/lalr/Utils.ts index 8eb9492fef..25066cbe09 100644 --- a/packages/shader-lab/src/lalr/Utils.ts +++ b/packages/shader-lab/src/lalr/Utils.ts @@ -1,7 +1,7 @@ import { EKeyword, ETokenType, ShaderRange } from "../common"; import { ASTNode, TreeNode } from "../parser/AST"; import { TranslationRule } from "../parser/SemanticAnalyzer"; -import { ENonTerminal, GrammarSymbol } from "../parser/GrammarSymbol"; +import { NoneTerminal, GrammarSymbol } from "../parser/GrammarSymbol"; import Production from "./Production"; import { ActionInfo, EAction } from "./types"; import { ShaderLab } from "../ShaderLab"; @@ -10,18 +10,18 @@ import { NodeChild } from "../parser/types"; export default class GrammarUtils { static isTerminal(sm: GrammarSymbol) { - return sm < ENonTerminal.START; + return sm < NoneTerminal.START; } static toString(sm: GrammarSymbol) { if (this.isTerminal(sm)) { return ETokenType[sm] ?? EKeyword[sm]; } - return ENonTerminal[sm]; + return NoneTerminal[sm]; } static createProductionWithOptions( - goal: ENonTerminal, + goal: NoneTerminal, options: GrammarSymbol[][], /** the ast node */ astTypePool?: ClearableObjectPool< @@ -44,17 +44,6 @@ export default class GrammarUtils { return ret; } - static createProductionOptions(common: GrammarSymbol[], position: number, opts: GrammarSymbol[][]) { - const ret: GrammarSymbol[][] = []; - for (const opt of opts) { - const list = common.slice(0, position); - list.push(...opt); - list.push(...common.slice(position)); - ret.push(list); - } - return ret; - } - static addMapSetItem(map: Map>, k: K, v: T) { const set = map.get(k) ?? new Set(); set.add(v); @@ -81,7 +70,7 @@ export default class GrammarUtils { static printProduction(production: Production) { const deriv = production.derivation.map((gs) => GrammarUtils.toString(gs)).join("|"); - return `${ENonTerminal[production.goal]} :=> ${deriv}`; + return `${NoneTerminal[production.goal]} :=> ${deriv}`; } // #endif } diff --git a/packages/shader-lab/src/lalr/types.ts b/packages/shader-lab/src/lalr/types.ts index f980cfd39f..7a5c37513a 100644 --- a/packages/shader-lab/src/lalr/types.ts +++ b/packages/shader-lab/src/lalr/types.ts @@ -1,9 +1,9 @@ -import { ENonTerminal, Terminal } from "../parser/GrammarSymbol"; +import { NoneTerminal, Terminal } from "../parser/GrammarSymbol"; export type StateActionTable = Map; export type ActionTable = Map; export type StateGotoTable = Map; -export type GotoTable = Map; +export type GotoTable = Map; export enum EAction { Shift = 0, diff --git a/packages/shader-lab/src/lexer/Lexer.ts b/packages/shader-lab/src/lexer/Lexer.ts index ef03d6d192..b62889538d 100644 --- a/packages/shader-lab/src/lexer/Lexer.ts +++ b/packages/shader-lab/src/lexer/Lexer.ts @@ -1,4 +1,3 @@ -import { ShaderRange, ShaderPosition } from "../common"; import { ETokenType, KeywordTable } from "../common"; import { EOF, BaseToken } from "../common/BaseToken"; import LexerUtils from "./Utils"; diff --git a/packages/shader-lab/src/parser/AST.ts b/packages/shader-lab/src/parser/AST.ts index f01ffbdce6..ec968e6966 100644 --- a/packages/shader-lab/src/parser/AST.ts +++ b/packages/shader-lab/src/parser/AST.ts @@ -1,21 +1,30 @@ // #if _VERBOSE import { BuiltinFunction, BuiltinVariable, NonGenericGalaceanType } from "./builtin"; // #endif +import { ClearableObjectPool, IPoolElement } from "@galacean/engine"; import { CodeGenVisitor } from "../codeGen"; -import { ENonTerminal } from "./GrammarSymbol"; -import { BaseToken as Token } from "../common/BaseToken"; -import { EKeyword, ETokenType, TokenType, ShaderRange, GalaceanDataType, TypeAny } from "../common"; +import { EKeyword, ETokenType, GalaceanDataType, ShaderRange, TokenType, TypeAny } from "../common"; +import { BaseToken, BaseToken as Token } from "../common/BaseToken"; +import { ParserUtils } from "../ParserUtils"; +import { ShaderLabUtils } from "../ShaderLabUtils"; +import { NoneTerminal } from "./GrammarSymbol"; import SematicAnalyzer from "./SemanticAnalyzer"; import { ShaderData } from "./ShaderInfo"; import { ESymbolType, FnSymbol, StructSymbol, VarSymbol } from "./symbolTable"; -import { ParserUtils } from "../ParserUtils"; import { IParamInfo, NodeChild, StructProp, SymbolType } from "./types"; -import { ClearableObjectPool, IPoolElement } from "@galacean/engine"; -import { ShaderLabUtils } from "../ShaderLabUtils"; + +function ASTNodeDecorator(nonTerminal: NoneTerminal) { + return function (ASTNode: T) { + ASTNode.prototype.nt = nonTerminal; + (ASTNode).pool = ShaderLabUtils.createObjectPool(ASTNode); + }; +} export abstract class TreeNode implements IPoolElement { + static pool: ClearableObjectPool void }>; + /** The non-terminal in grammar. */ - nt: ENonTerminal; + nt: NoneTerminal; private _children: NodeChild[]; private _location: ShaderRange; @@ -27,12 +36,14 @@ export abstract class TreeNode implements IPoolElement { return this._location; } - set(loc: ShaderRange, children: NodeChild[], nt: ENonTerminal) { - this.nt = nt; + set(loc: ShaderRange, children: NodeChild[]): void { this._location = loc; this._children = children; + this.init(); } + init() {} + dispose(): void {} // Visitor pattern interface for code generation @@ -62,52 +73,36 @@ export namespace ASTNode { sa.semanticStack.push(node); } - export class TrivialNode extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(TrivialNode); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal._ignore); - } - } + @ASTNodeDecorator(NoneTerminal._ignore) + export class TrivialNode extends TreeNode {} + @ASTNodeDecorator(NoneTerminal.scope_brace) export class ScopeBrace extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(ScopeBrace); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.scope_brace); - } - override semanticAnalyze(sa: SematicAnalyzer): void { sa.newScope(); } } + @ASTNodeDecorator(NoneTerminal.scope_end_brace) export class ScopeEndBrace extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(ScopeEndBrace); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.scope_end_brace); - } - override semanticAnalyze(sa: SematicAnalyzer): void { sa.dropScope(); } } + @ASTNodeDecorator(NoneTerminal.jump_statement) export class JumpStatement extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(JumpStatement); + isFragReturnStatement: boolean; - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.jump_statement); + override init(): void { + this.isFragReturnStatement = false; } - // #if _VERBOSE override semanticAnalyze(sa: SematicAnalyzer): void { if (ASTNode._unwrapToken(this.children![0]).type === EKeyword.RETURN) { - // TODO: check the equality of function return type declared and this type. + sa.curFunctionInfo.returnStatement = this; } } - // #endif override codeGen(visitor: CodeGenVisitor): string { return visitor.visitJumpStatement(this); @@ -115,61 +110,26 @@ export namespace ASTNode { } // #if _VERBOSE - export class ConditionOpt extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(ConditionOpt); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.conditionopt); - } - } - - export class ForRestStatement extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(ForRestStatement); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.for_rest_statement); - } - } - - export class Condition extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(Condition); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.condition); - } - } - - export class ForInitStatement extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(ForInitStatement); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.for_init_statement); - } - } + @ASTNodeDecorator(NoneTerminal.conditionopt) + export class ConditionOpt extends TreeNode {} - export class IterationStatement extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(IterationStatement); + @ASTNodeDecorator(NoneTerminal.for_rest_statement) + export class ForRestStatement extends TreeNode {} - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.iteration_statement); - } - } + @ASTNodeDecorator(NoneTerminal.condition) + export class Condition extends TreeNode {} - export class SelectionStatement extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(SelectionStatement); + @ASTNodeDecorator(NoneTerminal.for_init_statement) + export class ForInitStatement extends TreeNode {} - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.selection_statement); - } - } + @ASTNodeDecorator(NoneTerminal.iteration_statement) + export class IterationStatement extends TreeNode {} - export class ExpressionStatement extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(ExpressionStatement); + @ASTNodeDecorator(NoneTerminal.selection_statement) + export class SelectionStatement extends TreeNode {} - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.expression_statement); - } - } + @ASTNodeDecorator(NoneTerminal.expression_statement) + export class ExpressionStatement extends TreeNode {} // #endif export abstract class ExpressionAstNode extends TreeNode { @@ -181,33 +141,22 @@ export namespace ASTNode { return this._type ?? TypeAny; } - override set(loc: ShaderRange, children: NodeChild[], nt: ENonTerminal) { - super.set(loc, children, nt); + override init(): void { this._type = undefined; } } // #if _VERBOSE + @ASTNodeDecorator(NoneTerminal.initializer_list) export class InitializerList extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(InitializerList); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.initializer_list); - } - override semanticAnalyze(sa: SematicAnalyzer): void { const init = this.children[0] as Initializer | InitializerList; this.type = init.type; } } + @ASTNodeDecorator(NoneTerminal.initializer) export class Initializer extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(Initializer); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.initializer); - } - override semanticAnalyze(sa: SematicAnalyzer): void { if (this.children.length === 1) { this.type = (this.children[0]).type; @@ -218,14 +167,12 @@ export namespace ASTNode { } // #endif + @ASTNodeDecorator(NoneTerminal.single_declaration) export class SingleDeclaration extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(SingleDeclaration); - typeSpecifier: TypeSpecifier; arraySpecifier?: ArraySpecifier; - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.single_declaration); + override init(): void { this.typeSpecifier = undefined; this.arraySpecifier = undefined; } @@ -249,7 +196,7 @@ export namespace ASTNode { sm = new VarSymbol(id.lexeme, symbolType, false, initializer); } - sa.symbolTable.insert(sm); + sa.symbolTableStack.insert(sm); } override codeGen(visitor: CodeGenVisitor): string { @@ -257,9 +204,8 @@ export namespace ASTNode { } } + @ASTNodeDecorator(NoneTerminal.fully_specified_type) export class FullySpecifiedType extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(FullySpecifiedType); - get qualifierList() { if (this.children.length > 1) { return (this.children[0]).qualifierList; @@ -273,21 +219,12 @@ export namespace ASTNode { get type() { return this.typeSpecifier.type; } - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.fully_specified_type); - } } + @ASTNodeDecorator(NoneTerminal.type_qualifier) export class TypeQualifier extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(TypeQualifier); - qualifierList: EKeyword[]; - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.type_qualifier); - } - override semanticAnalyze(sa: SematicAnalyzer): void { if (this.children.length > 1) { this.qualifierList = [ @@ -300,16 +237,11 @@ export namespace ASTNode { } } + @ASTNodeDecorator(NoneTerminal.single_type_qualifier) export class SingleTypeQualifier extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(SingleTypeQualifier); - qualifier: EKeyword; lexeme: string; - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.single_type_qualifier); - } - override semanticAnalyze(sa: SematicAnalyzer): void { const child = this.children[0]; if (child instanceof Token) { @@ -329,49 +261,24 @@ export namespace ASTNode { get lexeme(): string { return (this.children[0]).lexeme; } - - override set(loc: ShaderRange, children: NodeChild[], nt: ENonTerminal) { - super.set(loc, children, nt); - } } // #if _VERBOSE - export class StorageQualifier extends BasicTypeQualifier { - static pool = ShaderLabUtils.createObjectPool(StorageQualifier); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.storage_qualifier); - } - } - - export class PrecisionQualifier extends BasicTypeQualifier { - static pool = ShaderLabUtils.createObjectPool(PrecisionQualifier); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.precision_qualifier); - } - } - - export class InterpolationQualifier extends BasicTypeQualifier { - static pool = ShaderLabUtils.createObjectPool(InterpolationQualifier); + @ASTNodeDecorator(NoneTerminal.storage_qualifier) + export class StorageQualifier extends BasicTypeQualifier {} - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.interpolation_qualifier); - } - } + @ASTNodeDecorator(NoneTerminal.precision_qualifier) + export class PrecisionQualifier extends BasicTypeQualifier {} - export class InvariantQualifier extends BasicTypeQualifier { - static pool = ShaderLabUtils.createObjectPool(InvariantQualifier); + @ASTNodeDecorator(NoneTerminal.interpolation_qualifier) + export class InterpolationQualifier extends BasicTypeQualifier {} - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.invariant_qualifier); - } - } + @ASTNodeDecorator(NoneTerminal.invariant_qualifier) + export class InvariantQualifier extends BasicTypeQualifier {} // #endif + @ASTNodeDecorator(NoneTerminal.type_specifier) export class TypeSpecifier extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(TypeSpecifier); - get type(): GalaceanDataType { return (this.children![0] as TypeSpecifierNonArray).type; } @@ -385,37 +292,23 @@ export namespace ASTNode { get isCustom() { return typeof this.type === "string"; } - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.type_specifier); - } } + @ASTNodeDecorator(NoneTerminal.array_specifier) export class ArraySpecifier extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(ArraySpecifier); - get size(): number | undefined { const integerConstantExpr = this.children[1] as IntegerConstantExpression; return integerConstantExpr.value; } - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.array_specifier); - } } + @ASTNodeDecorator(NoneTerminal.integer_constant_expression_operator) export class IntegerConstantExpressionOperator extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(IntegerConstantExpressionOperator); - compute: (a: number, b: number) => number; get lexeme(): string { return (this.children[0] as Token).lexeme; } - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.integer_constant_expression_operator); - } - override semanticAnalyze(sa: SematicAnalyzer): void { const operator = this.children[0] as Token; switch (operator.type) { @@ -435,17 +328,16 @@ export namespace ASTNode { this.compute = (a, b) => a % b; break; default: - sa.error(operator.location, `not implemented operator ${operator.lexeme}`); + sa.reportError(operator.location, `not implemented operator ${operator.lexeme}`); } } } + @ASTNodeDecorator(NoneTerminal.integer_constant_expression) export class IntegerConstantExpression extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(IntegerConstantExpression); - value?: number; - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.integer_constant_expression); + + override init(): void { this.value = undefined; } @@ -459,10 +351,10 @@ export namespace ASTNode { else { const id = child as VariableIdentifier; if (!id.symbolInfo) { - sa.error(id.location, "Undeclared symbol:", id.lexeme); + sa.reportError(id.location, `Undeclared symbol: ${id.lexeme}`); } if (!ParserUtils.typeCompatible(EKeyword.INT, id.typeInfo)) { - sa.error(id.location, "Invalid integer."); + sa.reportError(id.location, "Invalid integer."); return; } } @@ -471,14 +363,13 @@ export namespace ASTNode { } } + @ASTNodeDecorator(NoneTerminal.type_specifier_nonarray) export class TypeSpecifierNonArray extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(TypeSpecifierNonArray); - type: GalaceanDataType; lexeme: string; - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.type_specifier_nonarray); - const tt = children[0]; + + override init(): void { + const tt = this.children[0]; if (tt instanceof Token) { this.type = tt.lexeme; this.lexeme = tt.lexeme; @@ -489,23 +380,20 @@ export namespace ASTNode { } } + @ASTNodeDecorator(NoneTerminal.ext_builtin_type_specifier_nonarray) export class ExtBuiltinTypeSpecifierNonArray extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(ExtBuiltinTypeSpecifierNonArray); - type: TokenType; lexeme: string; - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.ext_builtin_type_specifier_nonarray); + override init(): void { const token = this.children[0] as Token; this.type = token.type; this.lexeme = token.lexeme; } } + @ASTNodeDecorator(NoneTerminal.init_declarator_list) export class InitDeclaratorList extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(InitDeclaratorList); - get typeInfo(): SymbolType { if (this.children.length === 1) { const singleDecl = this.children[0] as SingleDeclaration; @@ -520,62 +408,47 @@ export namespace ASTNode { return initDeclList.typeInfo; } - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.init_declarator_list); - } - override semanticAnalyze(sa: SematicAnalyzer): void { let sm: VarSymbol; if (this.children.length === 3 || this.children.length === 5) { const id = this.children[2] as Token; sm = new VarSymbol(id.lexeme, this.typeInfo, false, this); - sa.symbolTable.insert(sm); + sa.symbolTableStack.insert(sm); } else if (this.children.length === 4 || this.children.length === 6) { const typeInfo = this.typeInfo; const arraySpecifier = this.children[3] as ArraySpecifier; // #if _VERBOSE if (typeInfo.arraySpecifier && arraySpecifier) { - sa.error(arraySpecifier.location, "Array of array is not supported."); + sa.reportError(arraySpecifier.location, "Array of array is not supported."); } // #endif typeInfo.arraySpecifier = arraySpecifier; const id = this.children[2] as Token; sm = new VarSymbol(id.lexeme, typeInfo, false, this); - sa.symbolTable.insert(sm); + sa.symbolTableStack.insert(sm); } } } + @ASTNodeDecorator(NoneTerminal.identifier_list) export class IdentifierList extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(IdentifierList); - get idList(): Token[] { if (this.children.length === 2) { return [this.children[1] as Token]; } return [...(this.children[0]).idList, this.children[2] as Token]; } - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.identifier_list); - } } + @ASTNodeDecorator(NoneTerminal.declaration) export class Declaration extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(Declaration); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.declaration); - } - override codeGen(visitor: CodeGenVisitor): string { return visitor.visitDeclaration(this); } } + @ASTNodeDecorator(NoneTerminal.function_prototype) export class FunctionProtoType extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(FunctionProtoType); - private get declarator() { return this.children[0] as FunctionDeclarator; } @@ -596,18 +469,13 @@ export namespace ASTNode { return this.declarator.paramSig; } - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.function_prototype); - } - override codeGen(visitor: CodeGenVisitor): string { return visitor.visitFunctionProtoType(this); } } + @ASTNodeDecorator(NoneTerminal.function_declarator) export class FunctionDeclarator extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(FunctionDeclarator); - private get header() { return this.children[0] as FunctionHeader; } @@ -632,14 +500,14 @@ export namespace ASTNode { return this.parameterList?.paramSig; } - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.function_declarator); + override semanticAnalyze(sa: SematicAnalyzer): void { + sa.curFunctionInfo.returnStatement = null; + sa.curFunctionInfo.header = this; } } + @ASTNodeDecorator(NoneTerminal.function_header) export class FunctionHeader extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(FunctionHeader); - get ident() { return this.children[1] as Token; } @@ -647,10 +515,6 @@ export namespace ASTNode { return this.children[0] as FullySpecifiedType; } - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.function_header); - } - override semanticAnalyze(sa: SematicAnalyzer): void { sa.newScope(); } @@ -660,9 +524,8 @@ export namespace ASTNode { } } + @ASTNodeDecorator(NoneTerminal.function_parameter_list) export class FunctionParameterList extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(FunctionParameterList); - get parameterInfoList(): IParamInfo[] { if (this.children.length === 1) { const decl = this.children[0] as ParameterDeclaration; @@ -684,18 +547,13 @@ export namespace ASTNode { } } - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.function_parameter_list); - } - override codeGen(visitor: CodeGenVisitor): string { return visitor.visitFunctionParameterList(this); } } + @ASTNodeDecorator(NoneTerminal.parameter_declaration) export class ParameterDeclaration extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(ParameterDeclaration); - get typeQualifier() { if (this.children.length === 2) return this.children[0] as TypeQualifier; } @@ -713,10 +571,6 @@ export namespace ASTNode { return this.parameterDeclarator.ident; } - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.parameter_declaration); - } - override semanticAnalyze(sa: SematicAnalyzer): void { let declarator: ParameterDeclarator; if (this.children.length === 1) { @@ -725,13 +579,12 @@ export namespace ASTNode { declarator = this.children[1] as ParameterDeclarator; } const varSymbol = new VarSymbol(declarator.ident.lexeme, declarator.typeInfo, false, this); - sa.symbolTable.insert(varSymbol); + sa.symbolTableStack.insert(varSymbol); } } + @ASTNodeDecorator(NoneTerminal.parameter_declarator) export class ParameterDeclarator extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(ParameterDeclarator); - get ident() { return this.children[1] as Token; } @@ -741,62 +594,38 @@ export namespace ASTNode { const arraySpecifier = this.children[2] as ArraySpecifier; return new SymbolType(typeSpecifier.type, typeSpecifier.lexeme, arraySpecifier); } - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.parameter_declarator); - } } // #if _VERBOSE - export class SimpleStatement extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(SimpleStatement); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.simple_statement); - } - } + @ASTNodeDecorator(NoneTerminal.simple_statement) + export class SimpleStatement extends TreeNode {} - export class CompoundStatement extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(CompoundStatement); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.compound_statement); - } - } + @ASTNodeDecorator(NoneTerminal.compound_statement) + export class CompoundStatement extends TreeNode {} // #endif - export class CompoundStatementNoScope extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(CompoundStatementNoScope); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.compound_statement_no_scope); - } - } + @ASTNodeDecorator(NoneTerminal.compound_statement_no_scope) + export class CompoundStatementNoScope extends TreeNode {} // #if _VERBOSE - export class Statement extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(Statement); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.statement); - } - } + @ASTNodeDecorator(NoneTerminal.statement) + export class Statement extends TreeNode {} // #endif + @ASTNodeDecorator(NoneTerminal.statement_list) export class StatementList extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(StatementList); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.statement_list); - } - override codeGen(visitor: CodeGenVisitor): string { return visitor.visitStatementList(this); } } + @ASTNodeDecorator(NoneTerminal.function_definition) export class FunctionDefinition extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(FunctionDefinition); + private _returnStatement?: ASTNode.JumpStatement; + + get returnStatement(): ASTNode.JumpStatement | undefined { + return this._returnStatement; + } get protoType() { return this.children[0] as FunctionProtoType; @@ -806,14 +635,30 @@ export namespace ASTNode { return this.children[1] as CompoundStatementNoScope; } - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.function_definition); + override init(): void { + this._returnStatement = undefined; } override semanticAnalyze(sa: SematicAnalyzer): void { sa.dropScope(); const sm = new FnSymbol(this.protoType.ident.lexeme, this); - sa.symbolTable.insert(sm); + sa.symbolTableStack.insert(sm); + + const { curFunctionInfo } = sa; + const { header, returnStatement } = curFunctionInfo; + if (header.returnType.type === EKeyword.VOID) { + if (returnStatement) { + sa.reportError(header.returnType.location, "Return in void function."); + } + } else { + if (!returnStatement) { + sa.reportError(header.returnType.location, `No return statement found.`); + } else { + this._returnStatement = returnStatement; + } + } + curFunctionInfo.header = undefined; + curFunctionInfo.returnStatement = undefined; } override codeGen(visitor: CodeGenVisitor): string { @@ -821,13 +666,8 @@ export namespace ASTNode { } } + @ASTNodeDecorator(NoneTerminal.function_call) export class FunctionCall extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(FunctionCall); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.function_call); - } - override semanticAnalyze(sa: SematicAnalyzer): void { this.type = (this.children[0] as FunctionCallGeneric).type; } @@ -837,13 +677,12 @@ export namespace ASTNode { } } + @ASTNodeDecorator(NoneTerminal.function_call_generic) export class FunctionCallGeneric extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(FunctionCallGeneric); - fnSymbol: FnSymbol | StructSymbol | undefined; - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.function_call_generic); + override init(): void { + super.init(); this.fnSymbol = undefined; } @@ -869,10 +708,10 @@ export namespace ASTNode { } // #endif - const fnSymbol = sa.symbolTable.lookup({ ident: fnIdent, symbolType: ESymbolType.FN, signature: paramSig }); + const fnSymbol = sa.lookupSymbolBy(fnIdent, ESymbolType.FN, paramSig); if (!fnSymbol) { // #if _VERBOSE - sa.error(this.location, "No overload function type found: ", functionIdentifier.ident); + sa.reportError(this.location, `No overload function type found: ${functionIdentifier.ident}`); // #endif return; } @@ -882,9 +721,8 @@ export namespace ASTNode { } } + @ASTNodeDecorator(NoneTerminal.function_call_parameter_list) export class FunctionCallParameterList extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(FunctionCallParameterList); - get paramSig(): GalaceanDataType[] | undefined { if (this.children.length === 1) { const expr = this.children[0] as AssignmentExpression; @@ -911,27 +749,17 @@ export namespace ASTNode { return list.paramNodes.concat([decl]); } } - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.function_call_parameter_list); - } } + @ASTNodeDecorator(NoneTerminal.precision_specifier) export class PrecisionSpecifier extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(PrecisionSpecifier); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.precision_specifier); - } - override semanticAnalyze(sa: SematicAnalyzer): void { sa.shaderData.globalPrecisions.push(this); } } + @ASTNodeDecorator(NoneTerminal.function_identifier) export class FunctionIdentifier extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(FunctionIdentifier); - get ident() { const ty = this.children[0] as TypeSpecifier; return ty.type; @@ -946,10 +774,6 @@ export namespace ASTNode { return typeof this.ident !== "string"; } - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.function_identifier); - } - override semanticAnalyze(sa: SematicAnalyzer): void {} override codeGen(visitor: CodeGenVisitor): string { @@ -957,13 +781,8 @@ export namespace ASTNode { } } + @ASTNodeDecorator(NoneTerminal.assignment_expression) export class AssignmentExpression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(AssignmentExpression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.assignment_expression); - } - // #if _VERBOSE override semanticAnalyze(sa: SematicAnalyzer): void { if (this.children.length === 1) { @@ -978,22 +797,12 @@ export namespace ASTNode { } // #if _VERBOSE - export class AssignmentOperator extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(AssignmentOperator); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.assignment_operator); - } - } + @ASTNodeDecorator(NoneTerminal.assignment_operator) + export class AssignmentOperator extends TreeNode {} // #endif + @ASTNodeDecorator(NoneTerminal.expression) export class Expression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(Expression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.expression); - } - // #if _VERBOSE override semanticAnalyze(sa: SematicAnalyzer): void { if (this.children.length === 1) { @@ -1007,13 +816,8 @@ export namespace ASTNode { // #endif } + @ASTNodeDecorator(NoneTerminal.primary_expression) export class PrimaryExpression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(PrimaryExpression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.primary_expression); - } - override semanticAnalyze(sa: SematicAnalyzer): void { if (this.children.length === 1) { const id = this.children[0]; @@ -1040,11 +844,10 @@ export namespace ASTNode { } } + @ASTNodeDecorator(NoneTerminal.postfix_expression) export class PostfixExpression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(PostfixExpression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.postfix_expression); + override init(): void { + super.init(); if (this.children.length === 1) { const child = this.children[0] as PrimaryExpression | FunctionCall; this.type = child.type; @@ -1057,32 +860,20 @@ export namespace ASTNode { } // #if _VERBOSE - export class UnaryOperator extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(UnaryOperator); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.unary_operator); - } - } - // #endif + @ASTNodeDecorator(NoneTerminal.unary_operator) + export class UnaryOperator extends TreeNode {} - // #if _VERBOSE + @ASTNodeDecorator(NoneTerminal.unary_expression) export class UnaryExpression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(UnaryExpression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.unary_expression); + override init(): void { this.type = (this.children[0] as PostfixExpression).type; } } - // #endif - // #if _VERBOSE + @ASTNodeDecorator(NoneTerminal.multiplicative_expression) export class MultiplicativeExpression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(MultiplicativeExpression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.multiplicative_expression); + override init(): void { + super.init(); if (this.children.length === 1) { this.type = (this.children[0] as UnaryExpression).type; } else { @@ -1094,14 +885,11 @@ export namespace ASTNode { } } } - // #endif - // #if _VERBOSE + @ASTNodeDecorator(NoneTerminal.additive_expression) export class AdditiveExpression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(AdditiveExpression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.additive_expression); + override init(): void { + super.init(); if (this.children.length === 1) { this.type = (this.children[0] as MultiplicativeExpression).type; } else { @@ -1113,31 +901,17 @@ export namespace ASTNode { } } } - // #endif - // #if _VERBOSE + @ASTNodeDecorator(NoneTerminal.shift_expression) export class ShiftExpression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(ShiftExpression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.shift_expression); - } - override semanticAnalyze(sa: SematicAnalyzer): void { const expr = this.children[0] as ExpressionAstNode; this.type = expr.type; } } - // #endif - // #if _VERBOSE + @ASTNodeDecorator(NoneTerminal.relational_expression) export class RelationalExpression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(RelationalExpression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.relational_expression); - } - override semanticAnalyze(sa: SematicAnalyzer): void { if (this.children.length === 1) { this.type = (this.children[0]).type; @@ -1146,16 +920,9 @@ export namespace ASTNode { } } } - // #endif - // #if _VERBOSE + @ASTNodeDecorator(NoneTerminal.equality_expression) export class EqualityExpression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(EqualityExpression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.equality_expression); - } - override semanticAnalyze(sa: SematicAnalyzer): void { if (this.children.length === 1) { this.type = (this.children[0]).type; @@ -1164,16 +931,9 @@ export namespace ASTNode { } } } - // #endif - // #if _VERBOSE + @ASTNodeDecorator(NoneTerminal.and_expression) export class AndExpression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(AndExpression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.and_expression); - } - override semanticAnalyze(sa: SematicAnalyzer): void { if (this.children.length === 1) { this.type = (this.children[0]).type; @@ -1182,16 +942,9 @@ export namespace ASTNode { } } } - // #endif - // #if _VERBOSE + @ASTNodeDecorator(NoneTerminal.exclusive_or_expression) export class ExclusiveOrExpression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(ExclusiveOrExpression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.exclusive_or_expression); - } - override semanticAnalyze(sa: SematicAnalyzer): void { if (this.children.length === 1) { this.type = (this.children[0]).type; @@ -1200,16 +953,9 @@ export namespace ASTNode { } } } - // #endif - // #if _VERBOSE + @ASTNodeDecorator(NoneTerminal.inclusive_or_expression) export class InclusiveOrExpression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(InclusiveOrExpression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.inclusive_or_expression); - } - override semanticAnalyze(sa: SematicAnalyzer): void { if (this.children.length === 1) { this.type = (this.children[0]).type; @@ -1218,16 +964,9 @@ export namespace ASTNode { } } } - // #endif - // #if _VERBOSE + @ASTNodeDecorator(NoneTerminal.logical_and_expression) export class LogicalAndExpression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(LogicalAndExpression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.logical_and_expression); - } - override semanticAnalyze(sa: SematicAnalyzer): void { if (this.children.length === 1) { this.type = (this.children[0]).type; @@ -1236,16 +975,9 @@ export namespace ASTNode { } } } - // #endif - // #if _VERBOSE + @ASTNodeDecorator(NoneTerminal.logical_xor_expression) export class LogicalXorExpression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(LogicalXorExpression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.logical_xor_expression); - } - override semanticAnalyze(sa: SematicAnalyzer): void { if (this.children.length === 1) { this.type = (this.children[0]).type; @@ -1254,16 +986,9 @@ export namespace ASTNode { } } } - // #endif - // #if _VERBOSE + @ASTNodeDecorator(NoneTerminal.logical_or_expression) export class LogicalOrExpression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(LogicalOrExpression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.logical_or_expression); - } - override semanticAnalyze(sa: SematicAnalyzer): void { if (this.children.length === 1) { this.type = (this.children[0]).type; @@ -1272,16 +997,9 @@ export namespace ASTNode { } } } - // #endif - // #if _VERBOSE + @ASTNodeDecorator(NoneTerminal.conditional_expression) export class ConditionalExpression extends ExpressionAstNode { - static pool = ShaderLabUtils.createObjectPool(ConditionalExpression); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.conditional_expression); - } - override semanticAnalyze(sa: SematicAnalyzer): void { if (this.children.length === 1) { this.type = (this.children[0]).type; @@ -1290,9 +1008,8 @@ export namespace ASTNode { } // #endif + @ASTNodeDecorator(NoneTerminal.struct_specifier) export class StructSpecifier extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(StructSpecifier); - ident?: Token; get propList(): StructProp[] { @@ -1300,21 +1017,16 @@ export namespace ASTNode { return declList.propList; } - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.struct_specifier); - } - override semanticAnalyze(sa: SematicAnalyzer): void { if (this.children.length === 6) { this.ident = this.children[1] as Token; - sa.symbolTable.insert(new StructSymbol(this.ident.lexeme, this)); + sa.symbolTableStack.insert(new StructSymbol(this.ident.lexeme, this)); } } } + @ASTNodeDecorator(NoneTerminal.struct_declaration_list) export class StructDeclarationList extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(StructDeclarationList); - get propList(): StructProp[] { if (this.children.length === 1) { return (this.children[0]).propList; @@ -1323,15 +1035,10 @@ export namespace ASTNode { const decl = this.children[1] as StructDeclaration; return [list.propList, decl.propList].flat(); } - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.struct_declaration_list); - } } + @ASTNodeDecorator(NoneTerminal.struct_declaration) export class StructDeclaration extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(StructDeclaration); - get typeSpecifier() { if (this.children.length === 3) { return this.children[0] as TypeSpecifier; @@ -1348,23 +1055,38 @@ export namespace ASTNode { get propList(): StructProp[] { const ret: StructProp[] = []; - for (let i = 0; i < this.declaratorList.declaratorList.length; i++) { - const declarator = this.declaratorList.declaratorList[i]; - const typeInfo = new SymbolType(this.typeSpecifier.type, this.typeSpecifier.lexeme, declarator.arraySpecifier); - const prop = new StructProp(typeInfo, declarator.ident); + const firstChild = this.children[0]; + if (firstChild instanceof LayoutQualifier) { + const typeSpecifier = this.children[1] as TypeSpecifier; + const declarator = this.children[2] as StructDeclarator; + const typeInfo = new SymbolType(typeSpecifier.type, typeSpecifier.lexeme); + const prop = new StructProp(typeInfo, declarator.ident, firstChild.index); ret.push(prop); + } else { + for (let i = 0; i < this.declaratorList.declaratorList.length; i++) { + const declarator = this.declaratorList.declaratorList[i]; + const typeInfo = new SymbolType( + this.typeSpecifier.type, + this.typeSpecifier.lexeme, + declarator.arraySpecifier + ); + const prop = new StructProp(typeInfo, declarator.ident); + ret.push(prop); + } } return ret; } + } - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.struct_declaration); + @ASTNodeDecorator(NoneTerminal.layout_qualifier) + export class LayoutQualifier extends TreeNode { + get index(): number { + return Number((this.children[4]).lexeme); } } + @ASTNodeDecorator(NoneTerminal.struct_declarator_list) export class StructDeclaratorList extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(StructDeclaratorList); - get declaratorList(): StructDeclarator[] { if (this.children.length === 1) { return [this.children[0] as StructDeclarator]; @@ -1373,15 +1095,10 @@ export namespace ASTNode { return [...list.declaratorList, this.children[1]]; } } - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.struct_declarator_list); - } } + @ASTNodeDecorator(NoneTerminal.struct_declarator) export class StructDeclarator extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(StructDeclarator); - get ident() { return this.children[0] as Token; } @@ -1389,26 +1106,17 @@ export namespace ASTNode { get arraySpecifier(): ArraySpecifier | undefined { return this.children[1] as ArraySpecifier; } - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.struct_declarator); - } } + @ASTNodeDecorator(NoneTerminal.variable_declaration) export class VariableDeclaration extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(VariableDeclaration); - - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.variable_declaration); - } - override semanticAnalyze(sa: SematicAnalyzer): void { const type = this.children[0] as FullySpecifiedType; const ident = this.children[1] as Token; let sm: VarSymbol; sm = new VarSymbol(ident.lexeme, new SymbolType(type.type, type.typeSpecifier.lexeme), true, this); - sa.symbolTable.insert(sm); + sa.symbolTableStack.insert(sm); } override codeGen(visitor: CodeGenVisitor): string { @@ -1416,9 +1124,8 @@ export namespace ASTNode { } } + @ASTNodeDecorator(NoneTerminal.variable_identifier) export class VariableIdentifier extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(VariableIdentifier); - symbolInfo: | VarSymbol // #if _VERBOSE @@ -1435,10 +1142,6 @@ export namespace ASTNode { return this.symbolInfo?.type; } - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.variable_identifier); - } - override semanticAnalyze(sa: SematicAnalyzer): void { const token = this.children[0] as Token; @@ -1450,10 +1153,10 @@ export namespace ASTNode { } // #endif - this.symbolInfo = sa.symbolTable.lookup({ ident: token.lexeme, symbolType: ESymbolType.VAR }) as VarSymbol; + this.symbolInfo = sa.lookupSymbolBy(token.lexeme, ESymbolType.VAR) as VarSymbol; // #if _VERBOSE if (!this.symbolInfo) { - sa.error(this.location, "undeclared identifier:", token.lexeme); + sa.reportError(this.location, `undeclared identifier: ${token.lexeme}`); } // #endif } @@ -1463,18 +1166,13 @@ export namespace ASTNode { } } + @ASTNodeDecorator(NoneTerminal.gs_shader_program) export class GLShaderProgram extends TreeNode { - static pool = ShaderLabUtils.createObjectPool(GLShaderProgram); - shaderData: ShaderData; - override set(loc: ShaderRange, children: NodeChild[]) { - super.set(loc, children, ENonTerminal.gs_shader_program); - } - override semanticAnalyze(sa: SematicAnalyzer): void { this.shaderData = sa.shaderData; - this.shaderData.symbolTable = sa.symbolTable._scope; + this.shaderData.symbolTable = sa.symbolTableStack._scope; } } } diff --git a/packages/shader-lab/src/parser/Grammar.ts b/packages/shader-lab/src/parser/Grammar.ts index 5222c03bc5..bd53837970 100644 --- a/packages/shader-lab/src/parser/Grammar.ts +++ b/packages/shader-lab/src/parser/Grammar.ts @@ -1,30 +1,30 @@ import { ETokenType } from "../common"; -import { ENonTerminal, GrammarSymbol } from "./GrammarSymbol"; +import { NoneTerminal, GrammarSymbol } from "./GrammarSymbol"; import Production from "../lalr/Production"; export class Grammar { readonly productions: Production[]; - readonly startSymbol: ENonTerminal; + readonly startSymbol: NoneTerminal; - static create(start: ENonTerminal, productions: GrammarSymbol[][]) { + static create(start: NoneTerminal, productions: GrammarSymbol[][]) { const _ps = productions.map((gsl) => { - return new Production(gsl[0], gsl.slice(1)); + return new Production(gsl[0], gsl.slice(1)); }); return new Grammar(start, _ps); } - constructor(start: ENonTerminal, productions: Production[]) { + constructor(start: NoneTerminal, productions: Production[]) { this.startSymbol = start; - productions.unshift(new Production(ENonTerminal.START, [start])); + productions.unshift(new Production(NoneTerminal.START, [start])); this.productions = productions; } - getProductionList(nonTerminal: ENonTerminal) { + getProductionList(nonTerminal: NoneTerminal) { return this.productions.filter((item) => item.goal === nonTerminal); } - isNullableNT(NT: ENonTerminal) { + isNullableNT(NT: NoneTerminal) { return this.productions.find((item) => item.goal === NT && item.derivation[0] === ETokenType.EPSILON); } diff --git a/packages/shader-lab/src/parser/GrammarSymbol.ts b/packages/shader-lab/src/parser/GrammarSymbol.ts index 0dd3789a18..208d00a171 100644 --- a/packages/shader-lab/src/parser/GrammarSymbol.ts +++ b/packages/shader-lab/src/parser/GrammarSymbol.ts @@ -2,7 +2,7 @@ import { TokenType } from "../common"; export type Terminal = TokenType; -export enum ENonTerminal { +export enum NoneTerminal { START = 2000, // galacean gs_shader_program, @@ -72,6 +72,7 @@ export enum ENonTerminal { struct_specifier, struct_declaration_list, struct_declaration, + layout_qualifier, struct_declarator_list, struct_declarator, identifier_list, @@ -107,6 +108,6 @@ export enum ENonTerminal { _ignore } -export type GrammarSymbol = Terminal | ENonTerminal; +export type GrammarSymbol = Terminal | NoneTerminal; export type Derivation = GrammarSymbol[]; diff --git a/packages/shader-lab/src/parser/SemanticAnalyzer.ts b/packages/shader-lab/src/parser/SemanticAnalyzer.ts index b078287d97..0028c9e895 100644 --- a/packages/shader-lab/src/parser/SemanticAnalyzer.ts +++ b/packages/shader-lab/src/parser/SemanticAnalyzer.ts @@ -1,18 +1,22 @@ import { ShaderRange } from "../common"; -import { TreeNode } from "./AST"; +import { ASTNode, TreeNode } from "./AST"; import { GSErrorName } from "../GSError"; import { ShaderData } from "./ShaderInfo"; -import { SymbolInfo, SymbolTable } from "../parser/symbolTable"; +import { ESymbolType, SymbolInfo, TargetSymbolTable } from "../parser/symbolTable"; import { NodeChild } from "./types"; import { SymbolTableStack } from "../common/BaseSymbolTable"; import { ShaderLab } from "../ShaderLab"; +import { NonGenericGalaceanType } from "./builtin"; // #if _VERBOSE import { GSError } from "../GSError"; +// #else +import { Logger } from "@galacean/engine"; // #endif export type TranslationRule = (sa: SematicAnalyzer, ...tokens: NodeChild[]) => T; /** + * @internal * The semantic analyzer of `ShaderLab` compiler. * - Build symbol table * - Static analysis @@ -20,19 +24,22 @@ export type TranslationRule = (sa: SematicAnalyzer, ...tokens: NodeChil export default class SematicAnalyzer { semanticStack: TreeNode[] = []; acceptRule?: TranslationRule = undefined; - symbolTable: SymbolTableStack = new SymbolTableStack(); + symbolTableStack: SymbolTableStack = new SymbolTableStack(); + curFunctionInfo: { + header?: ASTNode.FunctionDeclarator; + returnStatement?: ASTNode.JumpStatement; + } = {}; private _shaderData = new ShaderData(); + private _translationRuleTable: Map = new Map(); // #if _VERBOSE - readonly errors: GSError[] = []; + readonly errors: Error[] = []; // #endif get shaderData() { return this._shaderData; } - private _translationRuleTable: Map = new Map(); - constructor() { this.newScope(); } @@ -40,7 +47,7 @@ export default class SematicAnalyzer { reset() { this.semanticStack.length = 0; this._shaderData = new ShaderData(); - this.symbolTable.clear(); + this.symbolTableStack.clear(); this.newScope(); // #if _VERBOSE this.errors.length = 0; @@ -48,12 +55,12 @@ export default class SematicAnalyzer { } newScope() { - const scope = new SymbolTable(); - this.symbolTable.newScope(scope); + const scope = new TargetSymbolTable(); + this.symbolTableStack.newScope(scope); } dropScope() { - return this.symbolTable.dropScope(); + return this.symbolTableStack.dropScope(); } addTranslationRule(pid: number, rule: TranslationRule) { @@ -64,13 +71,25 @@ export default class SematicAnalyzer { return this._translationRuleTable.get(pid); } - error(loc: ShaderRange, ...param: any[]) { + lookupSymbolBy( + ident: string, + symbolType: ESymbolType, + signature?: NonGenericGalaceanType[], + astNode?: ASTNode.FunctionDefinition + ): SymbolInfo | undefined { + const stack = this.symbolTableStack.stack; + for (let length = stack.length, i = length - 1; i >= 0; i--) { + const symbolTable = stack[i]; + const ret = symbolTable.lookup(ident, symbolType, signature, astNode); + if (ret) return ret; + } + } + + reportError(loc: ShaderRange, message: string): void { // #if _VERBOSE - const err = new GSError(GSErrorName.CompilationError, param.join(""), loc, ShaderLab._processingPassText); - this.errors.push(err); - return err; + this.errors.push(new GSError(GSErrorName.CompilationError, message, loc, ShaderLab._processingPassText)); // #else - throw new Error(param.join("")); + Logger.error(message); // #endif } } diff --git a/packages/shader-lab/src/parser/ShaderInfo.ts b/packages/shader-lab/src/parser/ShaderInfo.ts index dacd0814b9..2954f6671c 100644 --- a/packages/shader-lab/src/parser/ShaderInfo.ts +++ b/packages/shader-lab/src/parser/ShaderInfo.ts @@ -1,8 +1,8 @@ import { ASTNode } from "./AST"; -import { SymbolTable } from "../parser/symbolTable"; +import { TargetSymbolTable } from "../parser/symbolTable"; export class ShaderData { - symbolTable: SymbolTable; + symbolTable: TargetSymbolTable; vertexMain: ASTNode.FunctionDefinition; fragmentMain: ASTNode.FunctionDefinition; diff --git a/packages/shader-lab/src/parser/ShaderTargetParser.ts b/packages/shader-lab/src/parser/ShaderTargetParser.ts index 2bbb3da72c..15af0c1d91 100644 --- a/packages/shader-lab/src/parser/ShaderTargetParser.ts +++ b/packages/shader-lab/src/parser/ShaderTargetParser.ts @@ -1,5 +1,5 @@ import { Grammar } from "./Grammar"; -import { ENonTerminal, GrammarSymbol } from "./GrammarSymbol"; +import { NoneTerminal, GrammarSymbol } from "./GrammarSymbol"; import { BaseToken } from "../common/BaseToken"; import { ETokenType } from "../common"; import { EAction, StateActionTable, StateGotoTable } from "../lalr/types"; @@ -131,7 +131,7 @@ export class ShaderTargetParser { private _printStack(nextToken: BaseToken) { let str = ""; for (let i = 0; i < this._traceBackStack.length - 1; i++) { - const state = this._traceBackStack[i++]; + const state = this._traceBackStack[i++]; const token = this._traceBackStack[i]; str += `State${state} - ${(token).lexeme ?? ParserUtils.toString(token as GrammarSymbol)}; `; } diff --git a/packages/shader-lab/src/parser/TargetParser.y b/packages/shader-lab/src/parser/TargetParser.y index 17c9b00013..6dd10e5af1 100644 --- a/packages/shader-lab/src/parser/TargetParser.y +++ b/packages/shader-lab/src/parser/TargetParser.y @@ -28,6 +28,8 @@ %token PRECISION %token INVARIANT +%token layout +%token location %token or %token xor @@ -106,9 +108,13 @@ struct_declaration_list: struct_declaration: type_specifier struct_declarator_list ';' - | type_qualifier type_specifier struct_declaration_list ';' + | type_qualifier type_specifier struct_declarator_list ';' + | layout_qualifier type_specifier struct_declarator ';' ; +layout_qualifier: + layout '(' location '=' INT_CONSTANT ')' + struct_declarator_list: struct_declarator | struct_declarator_list ',' struct_declarator diff --git a/packages/shader-lab/src/parser/builtin/variables.ts b/packages/shader-lab/src/parser/builtin/variables.ts index 88724d6d8f..6621610704 100644 --- a/packages/shader-lab/src/parser/builtin/variables.ts +++ b/packages/shader-lab/src/parser/builtin/variables.ts @@ -1,4 +1,4 @@ -import { EKeyword, GalaceanDataType } from "../../common"; +import { EKeyword, GalaceanDataType, TypeAny } from "../../common"; import { EShaderStage } from "../../common/Enums"; export const BuiltinVariableTable: Map = new Map(); @@ -34,6 +34,7 @@ BuiltinVariable.createVariable("gl_FrontFacing", EKeyword.BOOL, EShaderStage.FRA BuiltinVariable.createVariable("gl_FragDepth", EKeyword.FLOAT, EShaderStage.FRAGMENT); BuiltinVariable.createVariable("gl_PointCoord", EKeyword.VEC2, EShaderStage.FRAGMENT); BuiltinVariable.createVariable("gl_FragColor", EKeyword.VEC4, EShaderStage.FRAGMENT); +BuiltinVariable.createVariable("gl_FragData", EKeyword.VEC4_ARRAY, EShaderStage.FRAGMENT); BuiltinVariable.createVariable("gl_MaxVertexAttribs", EKeyword.INT); BuiltinVariable.createVariable("gl_MaxVertexUniformVectors", EKeyword.INT); diff --git a/packages/shader-lab/src/parser/symbolTable/SymbolInfo.ts b/packages/shader-lab/src/parser/symbolTable/SymbolInfo.ts index b58bbdb327..e7c4152d47 100644 --- a/packages/shader-lab/src/parser/symbolTable/SymbolInfo.ts +++ b/packages/shader-lab/src/parser/symbolTable/SymbolInfo.ts @@ -1,4 +1,5 @@ import { IBaseSymbol } from "../../common/BaseSymbolTable"; +import { GalaceanDataType } from "../../common/types"; import { ASTNode } from "../AST"; import { SymbolDataType } from "./SymbolDataType"; @@ -8,7 +9,7 @@ export enum ESymbolType { STRUCT } -type SymbolAstNode = +export type SymbolAstNode = | ASTNode.Initializer | ASTNode.StructSpecifier | ASTNode.FunctionDefinition @@ -21,6 +22,7 @@ export class SymbolInfo implements IBaseSymbol { public readonly ident: string, public readonly symbolType: ESymbolType, public readonly astNode?: SymbolAstNode, - public readonly dataType?: SymbolDataType + public readonly dataType?: SymbolDataType, + public readonly signature?: GalaceanDataType[] ) {} } diff --git a/packages/shader-lab/src/parser/symbolTable/SymbolTable.ts b/packages/shader-lab/src/parser/symbolTable/SymbolTable.ts deleted file mode 100644 index 6908d6874b..0000000000 --- a/packages/shader-lab/src/parser/symbolTable/SymbolTable.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { GalaceanDataType, TypeAny } from "../../common"; -import { BaseSymbolTable } from "../../common/BaseSymbolTable"; -import { ASTNode } from "../AST"; -import { FnSymbol } from "./FnSymbol"; -import { ESymbolType, SymbolInfo } from "./SymbolInfo"; - -export class SymbolTable extends BaseSymbolTable { - override symbolEqualCheck(exist: SymbolInfo, newSymbol: SymbolInfo & { signature?: GalaceanDataType[] }): boolean { - if (exist.symbolType !== newSymbol.symbolType) return false; - if (newSymbol.symbolType === ESymbolType.FN) { - if (!newSymbol.astNode && !newSymbol.signature) return true; - - const existParams = (exist.astNode as ASTNode.FunctionDefinition).protoType.paramSig; - const newSymbolParams = - newSymbol.signature ?? (newSymbol.astNode as ASTNode.FunctionDefinition).protoType.paramSig; - if (existParams.length !== newSymbolParams.length) return false; - for (let i = 0; i < existParams.length; i++) { - if (existParams[i] === TypeAny || newSymbolParams[i] === TypeAny) continue; - if (existParams[i] !== newSymbolParams[i]) return false; - } - } - return true; - } - - getAllFnSymbols(fnIdent: string): FnSymbol[] { - const entries = this._table.get(fnIdent) ?? []; - return entries.filter((item) => item.symbolType === ESymbolType.FN) as FnSymbol[]; - } -} diff --git a/packages/shader-lab/src/parser/symbolTable/TargetSymbolTable.ts b/packages/shader-lab/src/parser/symbolTable/TargetSymbolTable.ts new file mode 100644 index 0000000000..729f6a7020 --- /dev/null +++ b/packages/shader-lab/src/parser/symbolTable/TargetSymbolTable.ts @@ -0,0 +1,74 @@ +import { Logger } from "@galacean/engine"; +import { GalaceanDataType, TypeAny } from "../../common"; +import { BaseSymbolTable } from "../../common/BaseSymbolTable"; +import { ASTNode } from "../AST"; +import { FnSymbol } from "./FnSymbol"; +import { StructSymbol } from "./StructSymbol"; +import { ESymbolType, SymbolAstNode, SymbolInfo } from "./SymbolInfo"; +import { VarSymbol } from "./VarSymbol"; + +export class TargetSymbolTable extends BaseSymbolTable { + override insert(sm: SymbolInfo): void { + const entry = this._table.get(sm.ident) ?? []; + for (let i = 0; i < entry.length; i++) { + if (this._compareWith(entry[i], sm.symbolType, sm.signature, sm.astNode)) { + Logger.warn("replace symbol:", sm.ident); + entry[i] = sm; + return; + } + } + entry.push(sm); + this._table.set(sm.ident, entry); + } + + lookup( + ident: string, + symbolType: T, + signature?: GalaceanDataType[], + astNode?: ASTNode.FunctionDefinition + ): T extends ESymbolType.FN + ? FnSymbol + : T extends ESymbolType.STRUCT + ? StructSymbol + : T extends ESymbolType.VAR + ? VarSymbol + : SymbolInfo { + const entry = this._table.get(ident); + if (entry) { + for (let length = entry.length, i = 0; i < length; i++) { + const item = entry[i]; + if (this._compareWith(item, symbolType, signature, astNode)) return item; + } + } + } + + getAllFnSymbols(fnIdent: string): FnSymbol[] { + const entries = this._table.get(fnIdent) ?? []; + return entries.filter((item) => item.symbolType === ESymbolType.FN) as FnSymbol[]; + } + + private _compareWith( + item: SymbolInfo, + symbolType: ESymbolType, + signature?: GalaceanDataType[], + astNode?: SymbolAstNode + ): boolean { + if (item.symbolType !== symbolType) return false; + if (item.symbolType === ESymbolType.FN) { + if (!astNode && !signature) return true; + + const params = (item.astNode).protoType.paramSig; + const comparedParams = signature ?? (astNode).protoType.paramSig; + const length = params.length; + if (length !== comparedParams.length) return false; + for (let i = 0; i < length; i++) { + const t1 = params[i], + t2 = comparedParams[i]; + if (t1 === TypeAny || t2 === TypeAny) continue; + if (t1 !== t2) return false; + } + return true; + } + return true; + } +} diff --git a/packages/shader-lab/src/parser/symbolTable/index.ts b/packages/shader-lab/src/parser/symbolTable/index.ts index 97193e552e..b4d44fa849 100644 --- a/packages/shader-lab/src/parser/symbolTable/index.ts +++ b/packages/shader-lab/src/parser/symbolTable/index.ts @@ -2,5 +2,5 @@ export * from "./FnSymbol"; export * from "./StructSymbol"; export * from "./SymbolDataType"; export * from "./SymbolInfo"; -export * from "./SymbolTable"; +export * from "./TargetSymbolTable"; export * from "./VarSymbol"; diff --git a/packages/shader-lab/src/parser/types.ts b/packages/shader-lab/src/parser/types.ts index 525b260bdf..f72b0777b1 100644 --- a/packages/shader-lab/src/parser/types.ts +++ b/packages/shader-lab/src/parser/types.ts @@ -1,33 +1,26 @@ -import { ENonTerminal } from "./GrammarSymbol"; +import { NoneTerminal } from "./GrammarSymbol"; import { BaseToken } from "../common/BaseToken"; import { GalaceanDataType, ShaderRange } from "../common"; import { ASTNode, TreeNode } from "./AST"; -export type TraceStackItem = ENonTerminal | BaseToken; +export type TraceStackItem = NoneTerminal | BaseToken; export class SymbolType { - type: GalaceanDataType; - arraySpecifier?: ASTNode.ArraySpecifier; - typeLexeme: string; - - constructor(type: GalaceanDataType, typeLexeme: string, arraySpecifier?: ASTNode.ArraySpecifier) { - this.type = type; - this.arraySpecifier = arraySpecifier; - this.typeLexeme = typeLexeme; - } + constructor( + public type: GalaceanDataType, + public typeLexeme: string, + public arraySpecifier?: ASTNode.ArraySpecifier + ) {} } export class StructProp implements IParamInfo { - typeInfo: SymbolType; - ident: BaseToken; - astNode: ASTNode.StructDeclarator; - - constructor(type: SymbolType, ident: BaseToken) { - this.typeInfo = type; - this.ident = ident; - } + constructor( + public typeInfo: SymbolType, + public ident: BaseToken, + public mrtIndex?: number + ) {} } export type NodeChild = TreeNode | BaseToken; -export type IParamInfo = { ident: BaseToken; typeInfo: SymbolType; astNode: TreeNode }; +export type IParamInfo = { ident: BaseToken; typeInfo: SymbolType; astNode?: TreeNode }; diff --git a/tests/src/shader-lab/ShaderLab.test.ts b/tests/src/shader-lab/ShaderLab.test.ts index 08b367ba52..3544e0f2fe 100644 --- a/tests/src/shader-lab/ShaderLab.test.ts +++ b/tests/src/shader-lab/ShaderLab.test.ts @@ -1,4 +1,10 @@ -import { BlendOperation, CompareFunction, CullMode, RenderStateDataKey } from "@galacean/engine-core"; +import { + BlendOperation, + CompareFunction, + CullMode, + RenderStateDataKey, + ShaderPlatformTarget +} from "@galacean/engine-core"; import { Color } from "@galacean/engine-math"; import { ShaderLab as ShaderLabVerbose, GSError } from "@galacean/engine-shader-lab/verbose"; import { ShaderLab as ShaderLabRelease } from "@galacean/engine-shader-lab"; @@ -256,4 +262,25 @@ describe("ShaderLab", () => { console.log(err.toString()); } }); + + it("mrt-normal", async () => { + const shaderSource = await readFile("./shaders/mrt-normal.shader"); + glslValidate(shaderSource, shaderLabVerbose, {}); + glslValidate(shaderSource, shaderLabRelease, {}); + }); + + it("mrt-struct", async () => { + const shaderSource = await readFile("./shaders/mrt-struct.shader"); + glslValidate(shaderSource, shaderLabVerbose, {}); + glslValidate(shaderSource, shaderLabVerbose, {}); + }); + + it("mrt-error1", async () => { + const shaderSource = await readFile("./shaders/mrt-error1.shader"); + shaderParse.bind(shaderLabVerbose)(shaderSource, [], ShaderPlatformTarget.GLES300); + const errors = shaderLabVerbose.errors; + expect(errors.length).to.eq(1); + expect(errors[0]).to.be.a.instanceOf(GSError); + expect(errors[0].toString()).include("cannot use both gl_FragData and gl_FragColor"); + }); }); diff --git a/tests/src/shader-lab/shaders/mrt-error1.shader b/tests/src/shader-lab/shaders/mrt-error1.shader new file mode 100644 index 0000000000..7fe361fd86 --- /dev/null +++ b/tests/src/shader-lab/shaders/mrt-error1.shader @@ -0,0 +1,37 @@ +Shader "Triangle" { + SubShader "Default" { + Pass "0" { + mat4 renderer_MVPMat; + vec3 u_color; + + struct a2v { + vec4 POSITION; + }; + + struct v2f { + vec3 v_color; + }; + + v2f vert(a2v v) { + v2f o; + + gl_Position = renderer_MVPMat * v.POSITION; + o.v_color = u_color; + return o; + } + + struct MRT { + layout(location = 0) vec4 fragColor0; + layout(location = 1) vec4 fragColor1; + }; + + void frag(v2f i) { + gl_FragColor = vec4(1.); + gl_FragData[0] = vec4(0.3); + } + + VertexShader = vert; + FragmentShader = frag; + } + } +} \ No newline at end of file diff --git a/tests/src/shader-lab/shaders/mrt-normal.shader b/tests/src/shader-lab/shaders/mrt-normal.shader new file mode 100644 index 0000000000..ac1aac40e5 --- /dev/null +++ b/tests/src/shader-lab/shaders/mrt-normal.shader @@ -0,0 +1,42 @@ +Shader "/custom.gs" { + + SubShader "Default" { + + Pass "Pass0" { + struct Attributes { + vec3 POSITION; + vec2 TEXCOORD_0; + vec4 JOINTS_0; + vec4 WEIGHTS_0; + }; + + struct Varyings { + vec2 uv; + }; + + vec4 material_BaseColor; + mat4 renderer_MVPMat; + + VertexShader = vert; + FragmentShader = frag; + + Varyings vert(Attributes attr) { + Varyings v; + + vec4 position = vec4(attr.POSITION, 1.0); + + gl_Position = renderer_MVPMat * position; + v.uv = attr.TEXCOORD_0; + + return v; + } + + void frag(Varyings v) { + vec4 baseColor = material_BaseColor; + + gl_FragData[0] = baseColor; + gl_FragData[1] = baseColor; + } + } + } +} \ No newline at end of file diff --git a/tests/src/shader-lab/shaders/mrt-struct.shader b/tests/src/shader-lab/shaders/mrt-struct.shader new file mode 100644 index 0000000000..7f3886ecde --- /dev/null +++ b/tests/src/shader-lab/shaders/mrt-struct.shader @@ -0,0 +1,50 @@ +Shader "/custom.gs" { + + SubShader "Default" { + + Pass "Pass0" { + struct Attributes { + vec3 POSITION; + vec2 TEXCOORD_0; + vec4 JOINTS_0; + vec4 WEIGHTS_0; + }; + + struct Varyings { + vec2 uv; + }; + + vec4 material_BaseColor; + mat4 renderer_MVPMat; + + VertexShader = vert; + FragmentShader = frag; + + Varyings vert(Attributes attr) { + Varyings v; + + vec4 position = vec4(attr.POSITION, 1.0); + + gl_Position = renderer_MVPMat * position; + v.uv = attr.TEXCOORD_0; + + return v; + } + + struct mrt { + layout(location = 0) vec4 fragColor0; + layout(location = 1) vec4 fragColor1; + }; + + mrt frag(Varyings v) { + mrt o; + + vec4 baseColor = material_BaseColor; + + o.fragColor0 = baseColor; + o.fragColor1 = baseColor; + return o; + } + } + } +} \ No newline at end of file diff --git a/tests/vitest.config.ts b/tests/vitest.config.ts index 7014792762..6a87b0e5b7 100644 --- a/tests/vitest.config.ts +++ b/tests/vitest.config.ts @@ -19,7 +19,8 @@ export default defineProject({ launch: { args: ["--use-gl=egl", "--ignore-gpu-blocklist", "--use-gl=angle"] } - } + }, + headless: true } } }); From 2850a718d59d445515cb3dca552c292607480995 Mon Sep 17 00:00:00 2001 From: Kbscript Date: Tue, 24 Dec 2024 18:22:23 +0800 Subject: [PATCH 16/20] feat: fix adapter temp path --- .github/workflows/release.yml | 39 +++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c760ca046a..d11691e771 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -69,7 +69,28 @@ jobs: pnpm link ../packages/galacean pnpm build - - name: Ensure Dist Directory Exists + - name: Structure Temp Directory + run: | + mkdir -p ${{ github.workspace }}/temp + mkdir -p ${{ github.workspace }}/temp/@galacean/engine + cp -r ${{ github.workspace }}/packages/galacean/dist ${{ github.workspace }}/temp/@galacean/engine + mkdir -p ${{ github.workspace }}/temp/@galacean/engine-xr + cp -r ${{ github.workspace }}/packages/xr/dist ${{ github.workspace }}/temp/@galacean/engine-xr + mkdir -p ${{ github.workspace }}/temp/@galacean/engine-shader-lab + cp -r ${{ github.workspace }}/packages/shader-lab/dist ${{ github.workspace }}/temp/@galacean/engine-shader-lab + mkdir -p ${{ github.workspace }}/temp/@galacean/engine-physics-lite + cp -r ${{ github.workspace }}/packages/physics-lite/dist ${{ github.workspace }}/temp/@galacean/engine-physics-lite + mkdir -p ${{ github.workspace }}/temp/@galacean/engine-physics-physx + cp -r ${{ github.workspace }}/packages/physics-physx/dist ${{ github.workspace }}/temp/@galacean/engine-physics-physx + mkdir -p ${{ github.workspace }}/temp/@galacean/engine-toolkit + cp -r ${{ github.workspace }}/engine-toolkit/packages/galacean-engine-toolkit/dist/umd ${{ github.workspace }}/temp/@galacean/engine-toolkit + mkdir -p ${{ github.workspace }}/temp/@galacean/engine-lottie + cp -r ${{ github.workspace }}/engine-lottie/dist ${{ github.workspace }}/temp/@galacean/engine-lottie + mkdir -p ${{ github.workspace }}/temp/@galacean/engine-spine + cp -r ${{ github.workspace }}/engine-spine/dist ${{ github.workspace }}/temp/@galacean/engine-spine + find ${{ github.workspace }}/temp + + - name: Ensure Adapter Directory Exists run: mkdir -p ${{ github.workspace }}/platform-adapter/dist # Create a mock package.json to specify the path and version of the adapter build result when syncing with the CDN later. @@ -88,14 +109,14 @@ jobs: { "polyfill": true, "engine": [ - "${{ github.workspace }}/packages/galacean/dist/browser.js", - "${{ github.workspace }}/packages/xr/dist/browser.js", - "${{ github.workspace }}/packages/shader-lab/dist/browser.js", - "${{ github.workspace }}/packages/physics-lite/dist/browser.js", - "${{ github.workspace }}/packages/physics-physx/dist/browser.js", - "${{ github.workspace }}/engine-lottie/dist/browser.js", - "${{ github.workspace }}/engine-spine/dist/browser.js", - "${{ github.workspace }}/engine-toolkit/packages/galacean-engine-toolkit/dist/umd/browser.js" + "${{ github.workspace }}/temp/@galacean/engine/browsers.js", + "${{ github.workspace }}/temp/@galacean/engine-xr/browsers.js", + "${{ github.workspace }}/temp/@galacean/engine-shader-lab/browsers.js", + "${{ github.workspace }}/temp/@galacean/engine-physics-lite/browsers.js", + "${{ github.workspace }}/temp/@galacean/engine-physics-physx/browsers.js", + "${{ github.workspace }}/temp/@galacean/engine-toolkit/browsers.js", + "${{ github.workspace }}/temp/@galacean/engine-lottie/browsers.js", + "${{ github.workspace }}/temp/@galacean/engine-spine/browsers.js" ], "jsWASMLoader": [ "${{ github.workspace }}/packages/physics-physx/libs/physx.release.js" From a4a6953d10f9b61b345373074023d5869fb73a51 Mon Sep 17 00:00:00 2001 From: Kbscript Date: Tue, 24 Dec 2024 18:29:03 +0800 Subject: [PATCH 17/20] feat: adapter path --- .github/workflows/release.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d11691e771..c3b7a0b92b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -109,14 +109,14 @@ jobs: { "polyfill": true, "engine": [ - "${{ github.workspace }}/temp/@galacean/engine/browsers.js", - "${{ github.workspace }}/temp/@galacean/engine-xr/browsers.js", - "${{ github.workspace }}/temp/@galacean/engine-shader-lab/browsers.js", - "${{ github.workspace }}/temp/@galacean/engine-physics-lite/browsers.js", - "${{ github.workspace }}/temp/@galacean/engine-physics-physx/browsers.js", - "${{ github.workspace }}/temp/@galacean/engine-toolkit/browsers.js", - "${{ github.workspace }}/temp/@galacean/engine-lottie/browsers.js", - "${{ github.workspace }}/temp/@galacean/engine-spine/browsers.js" + "${{ github.workspace }}/temp/@galacean/engine/dist/browsers.js", + "${{ github.workspace }}/temp/@galacean/engine-xr/dist/browsers.js", + "${{ github.workspace }}/temp/@galacean/engine-shader-lab/dist/browsers.js", + "${{ github.workspace }}/temp/@galacean/engine-physics-lite/dist/browsers.js", + "${{ github.workspace }}/temp/@galacean/engine-physics-physx/dist/browsers.js", + "${{ github.workspace }}/temp/@galacean/engine-toolkit/dist/browsers.js", + "${{ github.workspace }}/temp/@galacean/engine-lottie/dist/browsers.js", + "${{ github.workspace }}/temp/@galacean/engine-spine/dist/browsers.js" ], "jsWASMLoader": [ "${{ github.workspace }}/packages/physics-physx/libs/physx.release.js" From 970a02d02c74c5f2913106bea34f8660ad9eab2f Mon Sep 17 00:00:00 2001 From: Kbscript Date: Tue, 24 Dec 2024 18:34:41 +0800 Subject: [PATCH 18/20] fix: adapter bundle setting --- .github/workflows/release.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c3b7a0b92b..ae103e69e0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -109,14 +109,14 @@ jobs: { "polyfill": true, "engine": [ - "${{ github.workspace }}/temp/@galacean/engine/dist/browsers.js", - "${{ github.workspace }}/temp/@galacean/engine-xr/dist/browsers.js", - "${{ github.workspace }}/temp/@galacean/engine-shader-lab/dist/browsers.js", - "${{ github.workspace }}/temp/@galacean/engine-physics-lite/dist/browsers.js", - "${{ github.workspace }}/temp/@galacean/engine-physics-physx/dist/browsers.js", - "${{ github.workspace }}/temp/@galacean/engine-toolkit/dist/browsers.js", - "${{ github.workspace }}/temp/@galacean/engine-lottie/dist/browsers.js", - "${{ github.workspace }}/temp/@galacean/engine-spine/dist/browsers.js" + "${{ github.workspace }}/temp/@galacean/engine/dist/browser.js", + "${{ github.workspace }}/temp/@galacean/engine-xr/dist/browser.js", + "${{ github.workspace }}/temp/@galacean/engine-shader-lab/dist/browser.js", + "${{ github.workspace }}/temp/@galacean/engine-physics-lite/dist/browser.js", + "${{ github.workspace }}/temp/@galacean/engine-physics-physx/dist/browser.js", + "${{ github.workspace }}/temp/@galacean/engine-toolkit/dist/browser.js", + "${{ github.workspace }}/temp/@galacean/engine-lottie/dist/browser.js", + "${{ github.workspace }}/temp/@galacean/engine-spine/dist/browser.js" ], "jsWASMLoader": [ "${{ github.workspace }}/packages/physics-physx/libs/physx.release.js" From f5351a9c590f13fb93d3788fa5b96786a2047e38 Mon Sep 17 00:00:00 2001 From: Kbscript Date: Tue, 24 Dec 2024 18:41:12 +0800 Subject: [PATCH 19/20] fix: adapter bundle setting --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ae103e69e0..e991de9566 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -114,7 +114,7 @@ jobs: "${{ github.workspace }}/temp/@galacean/engine-shader-lab/dist/browser.js", "${{ github.workspace }}/temp/@galacean/engine-physics-lite/dist/browser.js", "${{ github.workspace }}/temp/@galacean/engine-physics-physx/dist/browser.js", - "${{ github.workspace }}/temp/@galacean/engine-toolkit/dist/browser.js", + "${{ github.workspace }}/temp/@galacean/engine-toolkit/umd/browser.js", "${{ github.workspace }}/temp/@galacean/engine-lottie/dist/browser.js", "${{ github.workspace }}/temp/@galacean/engine-spine/dist/browser.js" ], From 90c32285d1ed025713c314a71b99f3875b0902df Mon Sep 17 00:00:00 2001 From: luzhuang <364439895@qq.com> Date: Tue, 24 Dec 2024 21:49:12 +0800 Subject: [PATCH 20/20] Add physicsMaterial loader and joint component parser and change radians to degrees in physics (#2475) * feat: add physicsMaterial loader and joint component parser and change radians to degrees in physics --- packages/core/src/asset/AssetType.ts | 4 +- .../core/src/physics/CharacterController.ts | 6 +-- packages/core/src/physics/DynamicCollider.ts | 21 ++++++++-- packages/core/src/physics/joint/Joint.ts | 37 +++++++++++------- .../core/src/physics/joint/JointLimits.ts | 16 ++++---- packages/core/src/physics/joint/JointMotor.ts | 24 ++++++++---- .../core/src/physics/shape/ColliderShape.ts | 7 +++- .../src/physics/ICharacterController.ts | 2 +- .../design/src/physics/IDynamicCollider.ts | 5 +++ packages/loader/src/PhysicsMaterialLoader.ts | 33 ++++++++++++++++ packages/loader/src/index.ts | 1 + .../resources/parser/HierarchyParser.ts | 8 +++- .../resources/parser/ParserContext.ts | 38 ++++++++++++++----- .../resources/parser/ReflectionParser.ts | 25 +++++++++--- .../resources/schema/BasicSchema.ts | 5 +++ .../physics-lite/src/LiteDynamicCollider.ts | 7 ++++ .../src/shape/LiteColliderShape.ts | 17 ++++++--- .../src/PhysXCharacterController.ts | 2 +- .../physics-physx/src/PhysXDynamicCollider.ts | 24 ++++++++++-- .../src/joint/PhysXHingeJoint.ts | 13 +++++-- .../src/shape/PhysXCapsuleColliderShape.ts | 2 +- .../src/shape/PhysXColliderShape.ts | 12 ++++-- .../core/physics/CharacterController.test.ts | 11 +++--- tests/src/core/physics/ColliderShape.test.ts | 16 ++++---- .../src/core/physics/DynamicCollider.test.ts | 25 +++++++++--- tests/src/core/physics/HingeJoint.test.ts | 34 ++++++++--------- tests/src/core/physics/Joint.test.ts | 12 +++--- 27 files changed, 290 insertions(+), 117 deletions(-) create mode 100644 packages/loader/src/PhysicsMaterialLoader.ts diff --git a/packages/core/src/asset/AssetType.ts b/packages/core/src/asset/AssetType.ts index d1e4725256..a176d32732 100644 --- a/packages/core/src/asset/AssetType.ts +++ b/packages/core/src/asset/AssetType.ts @@ -60,5 +60,7 @@ export enum AssetType { /** AudioClip, include ogg, wav and mp3. */ Audio = "Audio", /** Project asset. */ - Project = "project" + Project = "project", + /** PhysicsMaterial. */ + PhysicsMaterial = "PhysicsMaterial" } diff --git a/packages/core/src/physics/CharacterController.ts b/packages/core/src/physics/CharacterController.ts index d96eae2048..2cef370d3a 100644 --- a/packages/core/src/physics/CharacterController.ts +++ b/packages/core/src/physics/CharacterController.ts @@ -15,7 +15,7 @@ export class CharacterController extends Collider { private _nonWalkableMode: ControllerNonWalkableMode = ControllerNonWalkableMode.PreventClimbing; @deepClone private _upDirection = new Vector3(0, 1, 0); - private _slopeLimit = 0.707; + private _slopeLimit = 45; /** * The step offset for the controller, the value must be greater than or equal to 0. @@ -61,8 +61,8 @@ export class CharacterController extends Collider { } /** - * The slope limit for the controller, the value is the cosine value of the maximum slope angle. - * @defaultValue 0.707(the cosine value of 45 degrees) + * The slope limit in degrees for the controller, the value is the cosine value of the maximum slope angle. + * @defaultValue 45 degrees */ get slopeLimit(): number { return this._slopeLimit; diff --git a/packages/core/src/physics/DynamicCollider.ts b/packages/core/src/physics/DynamicCollider.ts index 26a8384529..83f6db4b46 100644 --- a/packages/core/src/physics/DynamicCollider.ts +++ b/packages/core/src/physics/DynamicCollider.ts @@ -20,9 +20,10 @@ export class DynamicCollider extends Collider { private _centerOfMass = new Vector3(); @ignoreClone private _inertiaTensor = new Vector3(1, 1, 1); - private _maxAngularVelocity = 100; + private _maxAngularVelocity = 18000 / Math.PI; private _maxDepenetrationVelocity = 1.0000000331813535e32; private _solverIterations = 4; + private _useGravity = true; private _isKinematic = false; private _constraints: DynamicColliderConstraints = 0; private _collisionDetectionMode: CollisionDetectionMode = CollisionDetectionMode.Discrete; @@ -77,7 +78,7 @@ export class DynamicCollider extends Collider { } /** - * The angular velocity vector of the dynamic collider measured in radians per second. + * The angular velocity vector of the dynamic collider measured in degrees per second. */ get angularVelocity(): Vector3 { //@ts-ignore @@ -185,7 +186,7 @@ export class DynamicCollider extends Collider { } /** - * The maximum angular velocity of the collider measured in radians per second. (Default 7) range { 0, infinity }. + * The maximum angular velocity of the collider measured in degrees per second. */ get maxAngularVelocity(): number { return this._maxAngularVelocity; @@ -240,6 +241,20 @@ export class DynamicCollider extends Collider { } } + /** + * Controls whether gravity affects the dynamic collider. + */ + get useGravity(): boolean { + return this._useGravity; + } + + set useGravity(value: boolean) { + if (this._useGravity !== value) { + this._useGravity = value; + (this._nativeCollider).setUseGravity(value); + } + } + /** * Controls whether physics affects the dynamic collider. */ diff --git a/packages/core/src/physics/joint/Joint.ts b/packages/core/src/physics/joint/Joint.ts index d86fae052c..54ab5dc983 100644 --- a/packages/core/src/physics/joint/Joint.ts +++ b/packages/core/src/physics/joint/Joint.ts @@ -6,12 +6,12 @@ import { dependentComponents, DependentMode } from "../../ComponentsDependencies import { Entity } from "../../Entity"; import { Collider } from "../Collider"; import { TransformModifyFlags } from "../../Transform"; - +import { DynamicCollider } from "../DynamicCollider"; /** * A base class providing common functionality for joints. * @decorator `@dependentComponents(Collider, DependentMode.CheckOnly)` */ -@dependentComponents(Collider, DependentMode.CheckOnly) +@dependentComponents(DynamicCollider, DependentMode.AutoAdd) export abstract class Joint extends Component { private static _tempVector3 = new Vector3(); @@ -24,6 +24,8 @@ export abstract class Joint extends Component { private _force = Infinity; private _torque = Infinity; private _automaticConnectedAnchor = true; + @ignoreClone + private _updateConnectedActualAnchor: Function; /** * The connected collider. @@ -37,7 +39,7 @@ export abstract class Joint extends Component { this._connectedColliderInfo.collider?.entity._updateFlagManager.removeListener(this._onConnectedTransformChanged); value?.entity._updateFlagManager.addListener(this._onConnectedTransformChanged); this._connectedColliderInfo.collider = value; - this._nativeJoint?.setConnectedCollider(value._nativeCollider); + this._nativeJoint?.setConnectedCollider(value?._nativeCollider); if (this._automaticConnectedAnchor) { this._calculateConnectedAnchor(); } else { @@ -47,8 +49,7 @@ export abstract class Joint extends Component { } /** - * The connected anchor position. - * @remarks If connectedCollider is set, this anchor is relative offset, or the anchor is world position. + * The anchor position. */ get anchor(): Vector3 { return this._colliderInfo.anchor; @@ -66,13 +67,22 @@ export abstract class Joint extends Component { /** * The connected anchor position. * @remarks If connectedCollider is set, this anchor is relative offset, or the anchor is world position. + * The connectedAnchor is automatically calculated, if you want to set it manually, please set automaticConnectedAnchor to false */ get connectedAnchor(): Vector3 { - return this._connectedColliderInfo.anchor; + const connectedColliderAnchor = this._connectedColliderInfo.anchor; + if (this._automaticConnectedAnchor) { + //@ts-ignore + connectedColliderAnchor._onValueChanged = null; + this._calculateConnectedAnchor(); + //@ts-ignore + connectedColliderAnchor._onValueChanged = this._updateConnectedActualAnchor; + } + return connectedColliderAnchor; } set connectedAnchor(value: Vector3) { - if (this.automaticConnectedAnchor) { + if (this._automaticConnectedAnchor) { console.warn("Cannot set connectedAnchor when automaticConnectedAnchor is true."); return; } @@ -96,7 +106,7 @@ export abstract class Joint extends Component { } /** - * The scale to apply to the inverse mass of collider 0 for resolving this constraint. + * The scale to apply to the mass of collider 0 for resolving this constraint. */ get connectedMassScale(): number { return this._connectedColliderInfo.massScale; @@ -110,7 +120,7 @@ export abstract class Joint extends Component { } /** - * The scale to apply to the inverse mass of collider 1 for resolving this constraint. + * The scale to apply to the mass of collider 1 for resolving this constraint. */ get massScale(): number { return this._colliderInfo.massScale; @@ -124,7 +134,7 @@ export abstract class Joint extends Component { } /** - * The scale to apply to the inverse inertia of collider0 for resolving this constraint. + * The scale to apply to the inertia of collider0 for resolving this constraint. */ get connectedInertiaScale(): number { return this._connectedColliderInfo.inertiaScale; @@ -138,7 +148,7 @@ export abstract class Joint extends Component { } /** - * The scale to apply to the inverse inertia of collider1 for resolving this constraint. + * The scale to apply to the inertia of collider1 for resolving this constraint. */ get inertiaScale(): number { return this._colliderInfo.inertiaScale; @@ -183,8 +193,10 @@ export abstract class Joint extends Component { super(entity); //@ts-ignore this._colliderInfo.anchor._onValueChanged = this._updateActualAnchor.bind(this, AnchorOwner.Self); + this._updateConnectedActualAnchor = this._updateActualAnchor.bind(this, AnchorOwner.Connected); //@ts-ignore - this._connectedColliderInfo.anchor._onValueChanged = this._updateActualAnchor.bind(this, AnchorOwner.Connected); + this._connectedColliderInfo.anchor._onValueChanged = this._updateConnectedActualAnchor; + this._onSelfTransformChanged = this._onSelfTransformChanged.bind(this); this._onConnectedTransformChanged = this._onConnectedTransformChanged.bind(this); // @ts-ignore @@ -261,7 +273,6 @@ export abstract class Joint extends Component { } } - @ignoreClone private _updateActualAnchor(flag: AnchorOwner): void { if (flag & AnchorOwner.Self) { const worldScale = this.entity.transform.lossyWorldScale; diff --git a/packages/core/src/physics/joint/JointLimits.ts b/packages/core/src/physics/joint/JointLimits.ts index 40758d63d1..3d7ff811be 100644 --- a/packages/core/src/physics/joint/JointLimits.ts +++ b/packages/core/src/physics/joint/JointLimits.ts @@ -16,34 +16,34 @@ export class JointLimits { private _damping = 0; /** - * The upper angular limit (in radians) of the joint. + * The upper angular limit (in degrees) of the joint. */ get max(): number { return this._max; } set max(value: number) { - if (value < this._min) { - throw new Error("Max limit must be greater than min limit"); - } if (this._max !== value) { + if (value < this._min) { + this._min = value; + } this._max = value; this._updateFlagManager.dispatch(); } } /** - * The lower angular limit (in radians) of the joint. + * The lower angular limit (in degrees) of the joint. */ get min(): number { return this._min; } set min(value: number) { - if (value > this._max) { - throw new Error("Min limit must be less than max limit"); - } if (this._min !== value) { + if (value > this._max) { + this._max = value; + } this._min = value; this._updateFlagManager.dispatch(); } diff --git a/packages/core/src/physics/joint/JointMotor.ts b/packages/core/src/physics/joint/JointMotor.ts index ef36d31f90..0f381c1a6c 100644 --- a/packages/core/src/physics/joint/JointMotor.ts +++ b/packages/core/src/physics/joint/JointMotor.ts @@ -22,8 +22,10 @@ export class JointMotor { } set targetVelocity(value: number) { - this._targetVelocity = value; - this._updateFlagManager.dispatch(); + if (this._targetVelocity !== value) { + this._targetVelocity = value; + this._updateFlagManager.dispatch(); + } } /** @@ -34,8 +36,10 @@ export class JointMotor { } set forceLimit(value: number) { - this._forceLimit = value; - this._updateFlagManager.dispatch(); + if (this._forceLimit !== value) { + this._forceLimit = value; + this._updateFlagManager.dispatch(); + } } /** @@ -46,8 +50,10 @@ export class JointMotor { } set gearRatio(value: number) { - this._gearRatio = value; - this._updateFlagManager.dispatch(); + if (this._gearRatio !== value) { + this._gearRatio = value; + this._updateFlagManager.dispatch(); + } } /** @@ -58,7 +64,9 @@ export class JointMotor { } set freeSpin(value: boolean) { - this._freeSpin = value; - this._updateFlagManager.dispatch(); + if (this._freeSpin !== value) { + this._freeSpin = value; + this._updateFlagManager.dispatch(); + } } } diff --git a/packages/core/src/physics/shape/ColliderShape.ts b/packages/core/src/physics/shape/ColliderShape.ts index 7701561635..f056670906 100644 --- a/packages/core/src/physics/shape/ColliderShape.ts +++ b/packages/core/src/physics/shape/ColliderShape.ts @@ -66,13 +66,16 @@ export abstract class ColliderShape implements ICustomClone { } /** - * Physical material. + * Physical material, material can't be null. */ get material(): PhysicsMaterial { return this._material; } set material(value: PhysicsMaterial) { + if (!value) { + throw new Error("The physics material of the shape can't be null."); + } if (this._material !== value) { this._material = value; this._nativeShape.setMaterial(value._nativeMaterial); @@ -80,7 +83,7 @@ export abstract class ColliderShape implements ICustomClone { } /** - * The local rotation of this ColliderShape, in radians. + * The local rotation of this ColliderShape, in degrees. */ get rotation(): Vector3 { return this._rotation; diff --git a/packages/design/src/physics/ICharacterController.ts b/packages/design/src/physics/ICharacterController.ts index 03aff94e0b..80cf3bae82 100644 --- a/packages/design/src/physics/ICharacterController.ts +++ b/packages/design/src/physics/ICharacterController.ts @@ -44,7 +44,7 @@ export interface ICharacterController extends ICollider { setUpDirection(up: Vector3): void; /** - * Sets the slope limit. + * Sets the slope limit in degrees. * @param slopeLimit The slope limit for the controller. */ setSlopeLimit(slopeLimit: number): void; diff --git a/packages/design/src/physics/IDynamicCollider.ts b/packages/design/src/physics/IDynamicCollider.ts index c1fbdf5068..f4c31fda6b 100644 --- a/packages/design/src/physics/IDynamicCollider.ts +++ b/packages/design/src/physics/IDynamicCollider.ts @@ -132,6 +132,11 @@ export interface IDynamicCollider extends ICollider { */ setCollisionDetectionMode(value: number): void; + /** + * Whether the collider is affected by gravity. + */ + setUseGravity(value: boolean): void; + /** * Controls whether physics affects the dynamic collider. * @param value - is or not diff --git a/packages/loader/src/PhysicsMaterialLoader.ts b/packages/loader/src/PhysicsMaterialLoader.ts new file mode 100644 index 0000000000..334e60a618 --- /dev/null +++ b/packages/loader/src/PhysicsMaterialLoader.ts @@ -0,0 +1,33 @@ +import { + resourceLoader, + Loader, + AssetPromise, + AssetType, + LoadItem, + ResourceManager, + PhysicsMaterial +} from "@galacean/engine-core"; + +@resourceLoader(AssetType.PhysicsMaterial, ["mesh"]) +class PhysicsMaterialLoader extends Loader { + load(item: LoadItem, resourceManager: ResourceManager): AssetPromise { + return ( + resourceManager + // @ts-ignore + ._request(item.url, { + ...item, + type: "json" + }) + .then((data) => { + const physicsMaterial = new PhysicsMaterial(); + physicsMaterial.bounciness = data.bounciness; + physicsMaterial.dynamicFriction = data.dynamicFriction; + physicsMaterial.staticFriction = data.staticFriction; + physicsMaterial.bounceCombine = data.bounceCombine; + physicsMaterial.frictionCombine = data.frictionCombine; + + return physicsMaterial; + }) + ); + } +} diff --git a/packages/loader/src/index.ts b/packages/loader/src/index.ts index 9316c4dcfd..7b7ec39494 100644 --- a/packages/loader/src/index.ts +++ b/packages/loader/src/index.ts @@ -22,6 +22,7 @@ import "./AudioLoader"; import "./ktx2/KTX2Loader"; import "./ShaderLoader"; import "./ShaderChunkLoader"; +import "./PhysicsMaterialLoader"; export { GLTFLoader } from "./GLTFLoader"; export type { GLTFParams } from "./GLTFLoader"; diff --git a/packages/loader/src/resource-deserialize/resources/parser/HierarchyParser.ts b/packages/loader/src/resource-deserialize/resources/parser/HierarchyParser.ts index b427f44103..2c6bdaa241 100644 --- a/packages/loader/src/resource-deserialize/resources/parser/HierarchyParser.ts +++ b/packages/loader/src/resource-deserialize/resources/parser/HierarchyParser.ts @@ -84,7 +84,6 @@ export abstract class HierarchyParser { const entitiesConfig = this.data.entities; const entityMap = this.context.entityMap; - const components = this.context.components; const promises = []; for (let i = 0, l = entitiesConfig.length; i < l; i++) { @@ -94,11 +93,16 @@ export abstract class HierarchyParser resolve(null)); + } + return Promise.all(promises); } diff --git a/packages/loader/src/resource-deserialize/resources/parser/ParserContext.ts b/packages/loader/src/resource-deserialize/resources/parser/ParserContext.ts index a91dcc8920..70251d5b85 100644 --- a/packages/loader/src/resource-deserialize/resources/parser/ParserContext.ts +++ b/packages/loader/src/resource-deserialize/resources/parser/ParserContext.ts @@ -1,16 +1,12 @@ import { Component, Engine, EngineObject, Entity, ReferResource, ResourceManager, Scene } from "@galacean/engine-core"; -import type { IEntity, IHierarchyFile } from "../schema"; +import type { IComponentRef, IEntity, IHierarchyFile } from "../schema"; export enum ParserType { Prefab, Scene } /** - * Parser context - * @export - * @class ParserContext - * @template T - * @template I + * @internal */ export class ParserContext { entityMap: Map = new Map(); @@ -18,6 +14,7 @@ export class ParserContext { components: Map = new Map(); rootIds: string[] = []; strippedIds: string[] = []; + componentWaitingMap: Map = new Map(); readonly resourceManager: ResourceManager; @@ -29,10 +26,31 @@ export class ParserContext { this.resourceManager = engine.resourceManager; } - /** - * Destroy the context. - * @memberof ParserContext - */ + addComponent(id: string, component: Component) { + this.components.set(id, component); + const waitingList = this.componentWaitingMap.get(id); + if (waitingList?.length) { + waitingList.forEach((resolve) => resolve(component)); + this.componentWaitingMap.delete(id); + } + } + + getComponentByRef(ref: IComponentRef): Promise { + return new Promise((resolve, reject) => { + const component = this.components.get(ref.componentId); + if (component) { + resolve(component); + } else { + const resolves = this.componentWaitingMap.get(ref.componentId); + if (resolves) { + resolves.push(resolve); + } else { + this.componentWaitingMap.set(ref.componentId, [resolve]); + } + } + }); + } + clear() { this.entityMap.clear(); this.components.clear(); diff --git a/packages/loader/src/resource-deserialize/resources/parser/ReflectionParser.ts b/packages/loader/src/resource-deserialize/resources/parser/ReflectionParser.ts index 3655569f51..47ad86b98d 100644 --- a/packages/loader/src/resource-deserialize/resources/parser/ReflectionParser.ts +++ b/packages/loader/src/resource-deserialize/resources/parser/ReflectionParser.ts @@ -1,5 +1,14 @@ -import { EngineObject, Entity, Loader, ReferResource } from "@galacean/engine-core"; -import type { IAssetRef, IBasicType, IClassObject, IEntity, IEntityRef, IHierarchyFile, IRefEntity } from "../schema"; +import { EngineObject, Entity, Loader } from "@galacean/engine-core"; +import type { + IAssetRef, + IBasicType, + IClassObject, + IEntity, + IEntityRef, + IComponentRef, + IHierarchyFile, + IRefEntity +} from "../schema"; import { ParserContext, ParserType } from "./ParserContext"; export class ReflectionParser { @@ -85,6 +94,8 @@ export class ReflectionParser { } return resource; }); + } else if (ReflectionParser._isComponentRef(value)) { + return this._context.getComponentByRef(value); } else if (ReflectionParser._isEntityRef(value)) { // entity reference return Promise.resolve(this._context.entityMap.get(value.entityId)); @@ -144,14 +155,18 @@ export class ReflectionParser { } private static _isClass(value: any): value is IClassObject { - return value["class"] != undefined; + return value["class"] !== undefined; } private static _isAssetRef(value: any): value is IAssetRef { - return value["refId"] != undefined; + return value["refId"] !== undefined; } private static _isEntityRef(value: any): value is IEntityRef { - return value["entityId"] != undefined; + return value["entityId"] !== undefined; + } + + private static _isComponentRef(value: any): value is IComponentRef { + return value["ownerId"] !== undefined && value["componentId"] !== undefined; } } diff --git a/packages/loader/src/resource-deserialize/resources/schema/BasicSchema.ts b/packages/loader/src/resource-deserialize/resources/schema/BasicSchema.ts index 1cce358d62..8243c46936 100644 --- a/packages/loader/src/resource-deserialize/resources/schema/BasicSchema.ts +++ b/packages/loader/src/resource-deserialize/resources/schema/BasicSchema.ts @@ -92,3 +92,8 @@ export type IBasicType = export type IAssetRef = { key?: string; refId: string }; export type IEntityRef = { entityId: string }; + +export type IComponentRef = { + ownerId: string; + componentId: string; +}; diff --git a/packages/physics-lite/src/LiteDynamicCollider.ts b/packages/physics-lite/src/LiteDynamicCollider.ts index 0660cb18a2..68768a0af7 100644 --- a/packages/physics-lite/src/LiteDynamicCollider.ts +++ b/packages/physics-lite/src/LiteDynamicCollider.ts @@ -133,6 +133,13 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide Logger.error("Physics-lite don't support setInertiaTensor. Use Physics-PhysX instead!"); } + /** + * {@inheritDoc IDynamicCollider.setUseGravity } + */ + setUseGravity(value: boolean): void { + throw "Physics-lite don't support setUseGravity. Use Physics-PhysX instead!"; + } + /** * {@inheritDoc IDynamicCollider.setIsKinematic } */ diff --git a/packages/physics-lite/src/shape/LiteColliderShape.ts b/packages/physics-lite/src/shape/LiteColliderShape.ts index a205753972..295241dfed 100644 --- a/packages/physics-lite/src/shape/LiteColliderShape.ts +++ b/packages/physics-lite/src/shape/LiteColliderShape.ts @@ -1,4 +1,4 @@ -import { Matrix, Quaternion, Ray, Vector3, Vector4 } from "@galacean/engine"; +import { MathUtil, Matrix, Quaternion, Ray, Vector3, Vector4 } from "@galacean/engine"; import { IColliderShape, IPhysicsMaterial } from "@galacean/engine-design"; import { LiteCollider } from "../LiteCollider"; import { LiteHitResult } from "../LiteHitResult"; @@ -43,10 +43,17 @@ export abstract class LiteColliderShape implements IColliderShape { * {@inheritDoc IColliderShape.setRotation } */ setRotation(rotation: Vector3): void { - if (rotation !== this._rotation) { - this._rotation.copyFrom(rotation); - Quaternion.rotationEuler(rotation.x, rotation.y, rotation.z, this._transform.rotationQuaternion); - } + const rotationInRadians = this._rotation.set( + MathUtil.degreeToRadian(rotation.x), + MathUtil.degreeToRadian(rotation.y), + MathUtil.degreeToRadian(rotation.z) + ); + Quaternion.rotationEuler( + rotationInRadians.x, + rotationInRadians.y, + rotationInRadians.z, + this._transform.rotationQuaternion + ); } /** diff --git a/packages/physics-physx/src/PhysXCharacterController.ts b/packages/physics-physx/src/PhysXCharacterController.ts index d5a266c8fa..69c1b99400 100644 --- a/packages/physics-physx/src/PhysXCharacterController.ts +++ b/packages/physics-physx/src/PhysXCharacterController.ts @@ -81,7 +81,7 @@ export class PhysXCharacterController implements ICharacterController { * {@inheritDoc ICharacterController.setSlopeLimit } */ setSlopeLimit(slopeLimit: number): void { - this._pxController?.setSlopeLimit(slopeLimit); + this._pxController?.setSlopeLimit(Math.cos((slopeLimit * Math.PI) / 180)); } /** diff --git a/packages/physics-physx/src/PhysXDynamicCollider.ts b/packages/physics-physx/src/PhysXDynamicCollider.ts index e2083e268a..89fe20e01d 100644 --- a/packages/physics-physx/src/PhysXDynamicCollider.ts +++ b/packages/physics-physx/src/PhysXDynamicCollider.ts @@ -1,5 +1,5 @@ import { IDynamicCollider } from "@galacean/engine-design"; -import { Quaternion, Vector3 } from "@galacean/engine"; +import { MathUtil, Quaternion, Vector3 } from "@galacean/engine"; import { PhysXCollider } from "./PhysXCollider"; import { PhysXPhysics } from "./PhysXPhysics"; @@ -78,14 +78,23 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli */ getAngularVelocity(out: Vector3): Vector3 { const velocity = this._pxActor.getAngularVelocity(); - return out.set(velocity.x, velocity.y, velocity.z); + return out.set( + MathUtil.radianToDegree(velocity.x), + MathUtil.radianToDegree(velocity.y), + MathUtil.radianToDegree(velocity.z) + ); } /** * {@inheritDoc IDynamicCollider.setAngularVelocity } */ setAngularVelocity(value: Vector3): void { - this._pxActor.setAngularVelocity(value, true); + PhysXDynamicCollider._tempTranslation.set( + MathUtil.degreeToRadian(value.x), + MathUtil.degreeToRadian(value.y), + MathUtil.degreeToRadian(value.z) + ); + this._pxActor.setAngularVelocity(PhysXDynamicCollider._tempTranslation, true); } /** @@ -136,7 +145,7 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli * {@inheritDoc IDynamicCollider.setMaxAngularVelocity } */ setMaxAngularVelocity(value: number): void { - this._pxActor.setMaxAngularVelocity(value); + this._pxActor.setMaxAngularVelocity(MathUtil.degreeToRadian(value)); } /** @@ -194,6 +203,13 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli } } + /** + * {@inheritDoc IDynamicCollider.setUseGravity } + */ + setUseGravity(value: boolean): void { + this._pxActor.setActorFlag(this._physXPhysics._physX.PxActorFlag.eDISABLE_GRAVITY, !value); + } + /** * {@inheritDoc IDynamicCollider.setIsKinematic } */ diff --git a/packages/physics-physx/src/joint/PhysXHingeJoint.ts b/packages/physics-physx/src/joint/PhysXHingeJoint.ts index e90b5b6870..7addf168dc 100644 --- a/packages/physics-physx/src/joint/PhysXHingeJoint.ts +++ b/packages/physics-physx/src/joint/PhysXHingeJoint.ts @@ -1,5 +1,5 @@ import { IHingeJoint } from "@galacean/engine-design"; -import { Quaternion, Vector3 } from "@galacean/engine"; +import { MathUtil, Quaternion, Vector3 } from "@galacean/engine"; import { PhysXCollider } from "../PhysXCollider"; import { PhysXPhysics } from "../PhysXPhysics"; import { PhysXJoint } from "./PhysXJoint"; @@ -56,7 +56,7 @@ export class PhysXHingeJoint extends PhysXJoint implements IHingeJoint { * {@inheritDoc IHingeJoint.getAngle } */ getAngle(): number { - return this._pxJoint.getAngle(); + return MathUtil.radianToDegree(this._pxJoint.getAngle()); } /** @@ -70,14 +70,19 @@ export class PhysXHingeJoint extends PhysXJoint implements IHingeJoint { * {@inheritDoc IHingeJoint.setHardLimitCone } */ setHardLimit(lowerLimit: number, upperLimit: number, contactDist: number): void { - this._pxJoint.setHardLimit(lowerLimit, upperLimit, contactDist); + this._pxJoint.setHardLimit(MathUtil.degreeToRadian(lowerLimit), MathUtil.degreeToRadian(upperLimit), contactDist); } /** * {@inheritDoc IHingeJoint.setHardLimitCone } */ setSoftLimit(lowerLimit: number, upperLimit: number, stiffness: number, damping: number): void { - this._pxJoint.setSoftLimit(lowerLimit, upperLimit, stiffness, damping); + this._pxJoint.setSoftLimit( + MathUtil.degreeToRadian(lowerLimit), + MathUtil.degreeToRadian(upperLimit), + stiffness, + damping + ); } /** diff --git a/packages/physics-physx/src/shape/PhysXCapsuleColliderShape.ts b/packages/physics-physx/src/shape/PhysXCapsuleColliderShape.ts index 8d1a75706c..f140cfd65e 100644 --- a/packages/physics-physx/src/shape/PhysXCapsuleColliderShape.ts +++ b/packages/physics-physx/src/shape/PhysXCapsuleColliderShape.ts @@ -104,7 +104,7 @@ export class PhysXCapsuleColliderShape extends PhysXColliderShape implements ICa break; } if (rotation) { - Quaternion.rotationYawPitchRoll(rotation.x, rotation.y, rotation.z, physXRotation); + Quaternion.rotationYawPitchRoll(rotation.y, rotation.x, rotation.z, physXRotation); Quaternion.multiply(physXRotation, axis, physXRotation); } else { physXRotation.copyFrom(axis); diff --git a/packages/physics-physx/src/shape/PhysXColliderShape.ts b/packages/physics-physx/src/shape/PhysXColliderShape.ts index a2565ade4d..676c6dc5f3 100644 --- a/packages/physics-physx/src/shape/PhysXColliderShape.ts +++ b/packages/physics-physx/src/shape/PhysXColliderShape.ts @@ -1,4 +1,4 @@ -import { Quaternion, Vector3, DisorderedArray, Vector4 } from "@galacean/engine"; +import { Quaternion, Vector3, DisorderedArray, Vector4, MathUtil } from "@galacean/engine"; import { IColliderShape } from "@galacean/engine-design"; import { PhysXCharacterController } from "../PhysXCharacterController"; import { PhysXPhysics } from "../PhysXPhysics"; @@ -35,7 +35,7 @@ export abstract class PhysXColliderShape implements IColliderShape { protected _physXPhysics: PhysXPhysics; protected _worldScale: Vector3 = new Vector3(1, 1, 1); protected _position: Vector3 = new Vector3(); - protected _rotation: Vector3 = null; + protected _rotation: Vector3 = new Vector3(); protected _axis: Quaternion = null; protected _physXRotation: Quaternion = new Quaternion(); @@ -58,8 +58,12 @@ export abstract class PhysXColliderShape implements IColliderShape { * {@inheritDoc IColliderShape.setRotation } */ setRotation(value: Vector3): void { - this._rotation = value; - Quaternion.rotationYawPitchRoll(value.y, value.x, value.z, this._physXRotation); + const rotation = this._rotation.set( + MathUtil.degreeToRadian(value.x), + MathUtil.degreeToRadian(value.y), + MathUtil.degreeToRadian(value.z) + ); + Quaternion.rotationYawPitchRoll(rotation.y, rotation.x, rotation.z, this._physXRotation); this._axis && Quaternion.multiply(this._physXRotation, this._axis, this._physXRotation); this._physXRotation.normalize(); this._setLocalPose(); diff --git a/tests/src/core/physics/CharacterController.test.ts b/tests/src/core/physics/CharacterController.test.ts index fd7870fc3a..636ed920b2 100644 --- a/tests/src/core/physics/CharacterController.test.ts +++ b/tests/src/core/physics/CharacterController.test.ts @@ -100,7 +100,7 @@ describe("CharacterController", function () { expect(controller.stepOffset).eq(0.5); expect(controller.nonWalkableMode).eq(ControllerNonWalkableMode.PreventClimbing); expect(controller.upDirection).deep.include({ x: 0, y: 1, z: 0 }); - expect(controller.slopeLimit).eq(0.707); + expect(controller.slopeLimit).eq(45); }); it("addShape and removeShape", () => { @@ -179,8 +179,8 @@ describe("CharacterController", function () { const { fixedTimeStep } = engine.sceneManager.activeScene.physics; const moveScript = roleEntity.getComponent(MoveScript); const controller = roleEntity.getComponent(CharacterController); - expect(controller.slopeLimit).eq(0.707); - controller.slopeLimit = 1; + expect(controller.slopeLimit).eq(45); + controller.slopeLimit = 0; const slope = addPlane(new Vector3(0, 0, 2), new Quaternion().rotateX(-Math.PI / 4)); moveScript.moveTo(new Vector3(0, 0, 3), 50); // @ts-ignore @@ -194,7 +194,7 @@ describe("CharacterController", function () { const { fixedTimeStep } = engine.sceneManager.activeScene.physics; const moveScript = roleEntity.getComponent(MoveScript); const controller = roleEntity.getComponent(CharacterController); - expect(controller.slopeLimit).eq(0.707); + expect(controller.slopeLimit).eq(45); const slope = addPlane(new Vector3(0, 0, 2), new Quaternion().rotateX(-Math.PI / 4)); moveScript.moveTo(new Vector3(0, 0, 3), 50); // @ts-ignore @@ -295,7 +295,7 @@ describe("CharacterController", function () { const { fixedTimeStep } = engine.sceneManager.activeScene.physics; const moveScript = newRole.getComponent(MoveScript); const controller = newRole.getComponent(CharacterController); - expect(controller.slopeLimit).eq(0.707); + expect(controller.slopeLimit).eq(45); const slope = addPlane(new Vector3(0, 0, 2), new Quaternion().rotateX(-Math.PI / 4)); moveScript.moveTo(new Vector3(0, 0, 3), 50); // @ts-ignore @@ -308,7 +308,6 @@ describe("CharacterController", function () { it("inActive modification", function () { roleEntity.isActive = false; const controller = roleEntity.getComponent(CharacterController); - controller.contactOffset = 0.1; controller.stepOffset = 1; controller.slopeLimit = 1; controller.nonWalkableMode = ControllerNonWalkableMode.PreventClimbingAndForceSliding; diff --git a/tests/src/core/physics/ColliderShape.test.ts b/tests/src/core/physics/ColliderShape.test.ts index 6ea29d627d..ce2b15a43f 100644 --- a/tests/src/core/physics/ColliderShape.test.ts +++ b/tests/src/core/physics/ColliderShape.test.ts @@ -268,10 +268,10 @@ describe("ColliderShape PhysX", () => { // @ts-ignore engine.sceneManager.activeScene.physics._update(1 / 60); let distance = boxShape.getClosestPoint(point, closestPoint); - expect(formatValue(distance)).to.eq(10.492); - expect(formatValue(closestPoint.x)).to.eq(-16.0876); - expect(formatValue(closestPoint.y)).to.eq(10.7095); - expect(formatValue(closestPoint.z)).to.eq(12.7889); + expect(formatValue(distance)).to.eq(6.897); + expect(formatValue(closestPoint.x)).to.eq(-12.3658); + expect(formatValue(closestPoint.y)).to.eq(10.107); + expect(formatValue(closestPoint.z)).to.eq(11.1562); entity.transform.setScale(1, 1, 1); entity.transform.setRotation(0, 0, 0); @@ -497,10 +497,10 @@ describe("ColliderShape Lite", () => { // @ts-ignore engine.sceneManager.activeScene.physics._update(1 / 60); let distance = boxShape.getClosestPoint(point, closestPoint); - expect(formatValue(distance)).to.eq(10.492); - expect(formatValue(closestPoint.x)).to.eq(-16.0876); - expect(formatValue(closestPoint.y)).to.eq(10.7095); - expect(formatValue(closestPoint.z)).to.eq(12.7889); + expect(formatValue(distance)).to.eq(6.897); + expect(formatValue(closestPoint.x)).to.eq(-12.3658); + expect(formatValue(closestPoint.y)).to.eq(10.107); + expect(formatValue(closestPoint.z)).to.eq(11.1562); entity.transform.setScale(1, 1, 1); entity.transform.setRotation(0, 0, 0); diff --git a/tests/src/core/physics/DynamicCollider.test.ts b/tests/src/core/physics/DynamicCollider.test.ts index b1b2b23fa1..0a49903b8a 100644 --- a/tests/src/core/physics/DynamicCollider.test.ts +++ b/tests/src/core/physics/DynamicCollider.test.ts @@ -155,12 +155,12 @@ describe("DynamicCollider", function () { expect(formatValue(boxCollider.angularVelocity.y)).eq(0); expect(formatValue(box.transform.rotation.y)).eq(0); - boxCollider.angularVelocity = new Vector3(0, 1, 0); + boxCollider.angularVelocity = new Vector3(0, 45, 0); boxCollider.angularDamping = 0; // @ts-ignore engine.sceneManager.activeScene.physics._update(1); - expect(formatValue(boxCollider.angularVelocity.y)).eq(1); - expect(formatValue(box.transform.rotation.y)).eq(57.29577); + expect(formatValue(boxCollider.angularVelocity.y)).eq(45); + expect(formatValue(box.transform.rotation.y)).closeTo(45, 0.0001); }); it("mass", function () { @@ -241,7 +241,7 @@ describe("DynamicCollider", function () { // @ts-ignore engine.sceneManager.activeScene.physics._update(1); expect(formatValue(boxCollider.inertiaTensor.y)).eq(1); - expect(formatValue(boxCollider.angularVelocity.y)).eq(0.15853); + expect(formatValue(boxCollider.angularVelocity.y)).eq(9.08338); boxCollider.inertiaTensor = new Vector3(0, 2, 0); boxCollider.angularVelocity.y = 0; @@ -249,7 +249,7 @@ describe("DynamicCollider", function () { // @ts-ignore engine.sceneManager.activeScene.physics._update(1); expect(formatValue(boxCollider.inertiaTensor.y)).eq(2); - expect(formatValue(boxCollider.angularVelocity.y)).eq(0.07927); + expect(formatValue(boxCollider.angularVelocity.y)).eq(4.54169); }); it("automaticInertiaTensor", function () { @@ -364,6 +364,21 @@ describe("DynamicCollider", function () { expect(Math.abs(formatValue(boxCollider2.linearVelocity.x))).lessThan(4); }); + it("useGravity", function () { + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 10, 0)); + const boxCollider = box.getComponent(DynamicCollider); + boxCollider.useGravity = false; + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + expect(formatValue(box.transform.position.y)).eq(10); + + boxCollider.useGravity = true; + boxCollider.wakeUp(); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + expect(formatValue(box.transform.position.y)).lessThan(10); + }); + it("isKinematic", function () { const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 1, 0)); const boxCollider = box.getComponent(DynamicCollider); diff --git a/tests/src/core/physics/HingeJoint.test.ts b/tests/src/core/physics/HingeJoint.test.ts index dcc4d63ada..d6f1ad12d8 100644 --- a/tests/src/core/physics/HingeJoint.test.ts +++ b/tests/src/core/physics/HingeJoint.test.ts @@ -68,7 +68,7 @@ describe("HingeJoint", function () { // @ts-ignore engine.sceneManager.activeScene.physics._update(1 / 60); expect(formatValue(joint.velocity)).eq(6.89082); - expect(formatValue(joint.angle)).eq(0.11485); + expect(formatValue(joint.angle)).eq(6.58019); }); it("hardLimit", function () { @@ -94,12 +94,12 @@ describe("HingeJoint", function () { collider2.applyTorque(new Vector3(0, 1000, 0)); // @ts-ignore engine.sceneManager.activeScene.physics._update(1); - expect(formatValue(joint.angle)).eq(1.5708); + expect(formatValue(joint.angle)).eq(1.57019); collider2.applyTorque(new Vector3(0, -1000, 0)); // @ts-ignore engine.sceneManager.activeScene.physics._update(1); - expect(formatValue(joint.angle)).eq(-1.5708); + expect(formatValue(joint.angle)).eq(-1.57019); }); it("softLimit", function () { @@ -121,8 +121,8 @@ describe("HingeJoint", function () { joint.useLimits = true; joint.useSpring = true; const limits = new JointLimits(); - limits.min = -Math.PI / 2; - limits.max = Math.PI / 2; + limits.min = -90; + limits.max = 90; limits.stiffness = 1000; limits.damping = 30; joint.limits = limits; @@ -132,7 +132,7 @@ describe("HingeJoint", function () { collider2.applyTorque(new Vector3(0, 1000, 0)); // @ts-ignore engine.sceneManager.activeScene.physics._update(1); - expect(formatValue(joint.angle)).eq(0.10957); + expect(formatValue(joint.angle)).eq(6.27762); }); it("stiffness", function () { @@ -153,8 +153,8 @@ describe("HingeJoint", function () { joint.useLimits = true; joint.useSpring = true; const limits = new JointLimits(); - limits.min = -Math.PI / 2; - limits.max = Math.PI / 2; + limits.min = -90; + limits.max = 90; limits.stiffness = 2000; limits.damping = 30; joint.limits = limits; @@ -164,7 +164,7 @@ describe("HingeJoint", function () { collider2.applyTorque(new Vector3(0, 1000, 0)); // @ts-ignore engine.sceneManager.activeScene.physics._update(1); - expect(formatValue(joint.angle)).eq(-0.22578); + expect(formatValue(joint.angle)).eq(-12.93617); }); it("damping", function () { @@ -185,8 +185,8 @@ describe("HingeJoint", function () { joint.useLimits = true; joint.useSpring = true; const limits = new JointLimits(); - limits.min = -Math.PI / 2; - limits.max = Math.PI / 2; + limits.min = -90; + limits.max = 90; limits.stiffness = 1000; limits.damping = 100; joint.limits = limits; @@ -196,7 +196,7 @@ describe("HingeJoint", function () { collider2.applyTorque(new Vector3(0, 1000, 0)); // @ts-ignore engine.sceneManager.activeScene.physics._update(1); - expect(formatValue(joint.angle)).eq(0.87375); + expect(formatValue(joint.angle)).eq(50.06221); }); it("motor", function () { @@ -219,7 +219,7 @@ describe("HingeJoint", function () { // @ts-ignore engine.sceneManager.activeScene.physics._update(1); expect(formatValue(joint.velocity)).eq(30); - expect(formatValue(joint.angle)).eq(4.86726); + expect(formatValue(joint.angle)).eq(278.87335); }); it("forceLimit", function () { @@ -304,18 +304,18 @@ describe("HingeJoint", function () { collider2.applyTorque(new Vector3(0, 1000, 0)); // @ts-ignore engine.sceneManager.activeScene.physics._update(1); - expect(formatValue(collider2.angularVelocity.y)).eq(30); + expect(formatValue(collider2.angularVelocity.y)).eq(1718.87328); motor.targetVelocity = 30; motor.freeSpin = true; // @ts-ignore engine.sceneManager.activeScene.physics._update(1); - expect(formatValue(collider2.angularVelocity.y)).eq(30); + expect(formatValue(collider2.angularVelocity.y)).eq(1718.87328); collider2.applyTorque(new Vector3(0, 1000, 0)); // @ts-ignore engine.sceneManager.activeScene.physics._update(1); - expect(formatValue(collider2.angularVelocity.y)).eq(95.20031); + expect(formatValue(collider2.angularVelocity.y)).eq(5454.57596); }); it("clone", function () { @@ -355,7 +355,7 @@ describe("HingeJoint", function () { // @ts-ignore engine.sceneManager.activeScene.physics._update(1 / 60); expect(formatValue(newJoint.velocity)).eq(6.89082); - expect(formatValue(newJoint.angle)).eq(0.11485); + expect(formatValue(newJoint.angle)).eq(6.58019); }); it("inActive modification", function () { diff --git a/tests/src/core/physics/Joint.test.ts b/tests/src/core/physics/Joint.test.ts index 90d65174a1..0d5c59c4f5 100644 --- a/tests/src/core/physics/Joint.test.ts +++ b/tests/src/core/physics/Joint.test.ts @@ -198,7 +198,7 @@ describe("Joint", function () { box2Collider.applyTorque(new Vector3(0, 1000, 0)); // @ts-ignore engine.sceneManager.activeScene.physics._update(1 / 60); - expect(formatValue(box2Collider.angularVelocity.y)).eq(8.32635); + expect(formatValue(box2Collider.angularVelocity.y)).eq(477.06462); box2.transform.rotation = new Vector3(0, 0, 0); box1Collider.angularVelocity = new Vector3(0, 0, 0); @@ -211,7 +211,7 @@ describe("Joint", function () { box2Collider.applyTorque(new Vector3(0, 1000, 0)); // @ts-ignore engine.sceneManager.activeScene.physics._update(1 / 60); - expect(formatValue(box2Collider.angularVelocity.y)).closeTo(1.513, 0.001); + expect(formatValue(box2Collider.angularVelocity.y)).closeTo(86.74174, 0.01); box2.transform.rotation = new Vector3(0, 0, 0); box2Collider.inertiaTensor.y = 1; @@ -226,7 +226,7 @@ describe("Joint", function () { box2Collider.applyTorque(new Vector3(0, 1000, 0)); // @ts-ignore engine.sceneManager.activeScene.physics._update(1 / 60); - expect(formatValue(box2Collider.angularVelocity.y)).closeTo(1.513, 0.001); + expect(formatValue(box2Collider.angularVelocity.y)).closeTo(86.74174, 0.01); }); it("connectedInertiaScale", function () { @@ -245,7 +245,7 @@ describe("Joint", function () { box2Collider.applyTorque(new Vector3(0, 1000, 0)); // @ts-ignore engine.sceneManager.activeScene.physics._update(1 / 60); - expect(formatValue(box2Collider.angularVelocity.y)).eq(8.32635); + expect(formatValue(box2Collider.angularVelocity.y)).eq(477.06451); box2.transform.rotation = new Vector3(0, 0, 0); box1Collider.angularVelocity = new Vector3(0, 0, 0); @@ -258,7 +258,7 @@ describe("Joint", function () { box2Collider.applyTorque(new Vector3(0, 1000, 0)); // @ts-ignore engine.sceneManager.activeScene.physics._update(1 / 60); - expect(formatValue(box2Collider.angularVelocity.y)).closeTo(1.513, 0.001); + expect(formatValue(box2Collider.angularVelocity.y)).closeTo(86.74174, 0.01); box2.transform.rotation = new Vector3(0, 0, 0); box2Collider.inertiaTensor.y = 1; @@ -273,7 +273,7 @@ describe("Joint", function () { box2Collider.applyTorque(new Vector3(0, 1000, 0)); // @ts-ignore engine.sceneManager.activeScene.physics._update(1 / 60); - expect(formatValue(box2Collider.angularVelocity.y)).closeTo(1.513, 0.001); + expect(formatValue(box2Collider.angularVelocity.y)).closeTo(86.74174, 0.01); }); it("breakForce", function () {