diff --git a/docs/en/assets/load.mdx b/docs/en/assets/load.mdx index d4994aa2e8..7582ecd77b 100644 --- a/docs/en/assets/load.mdx +++ b/docs/en/assets/load.mdx @@ -77,7 +77,7 @@ this.engine.resourceManager.load("https://a.png"); // 加载 base64 this.engine.resourceManager.load({ url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==", - type: AssetType.Texture2D, + type: AssetType.Texture, }); // 加载编辑器虚拟路径下的资源 diff --git a/docs/en/core/color.mdx b/docs/en/core/color.mdx index f132fecb9e..ffe6047d1c 100644 --- a/docs/en/core/color.mdx +++ b/docs/en/core/color.mdx @@ -53,10 +53,10 @@ When uploading textures in the editor, the sRGB toggle is enabled by default. If [Loading textures](/en/docs/assets/load) uses the sRGB color space by default. If linear space is required, you can disable the sRGB setting via parameters: ```ts showLineNumbers {7} -// Load textures using Texture2DLoader +// Load textures using TextureLoader engine.resourceManager .load({ - type: AssetType.Texture2D, + type: AssetType.Texture, url: "https://***.png", params: { isSRGBColorSpace: false // Disable sRGB, use linear space, e.g., for normal maps diff --git a/docs/en/graphics/background/sky.mdx b/docs/en/graphics/background/sky.mdx index ecd41d38e7..27fe914182 100644 --- a/docs/en/graphics/background/sky.mdx +++ b/docs/en/graphics/background/sky.mdx @@ -34,31 +34,6 @@ Finally, follow the path **[Hierarchy Panel](/en/docs/interface/hierarchy)** -> -### Code to Set Skybox - -```typescript -// 创建天空盒纹理 -const textureCube = await engine.resourceManager.load({ - urls: [ - "px - right 图片 url", - "nx - left 图片 url", - "py - top 图片 url", - "ny - bottom 图片 url", - "pz - front 图片 url", - "nz - back 图片 url", - ], - type: AssetType.TextureCube, -}); -// 创建天空盒材质 -const skyMaterial = new SkyBoxMaterial(engine); -skyMaterial.texture = textureCube; -// 设置天空盒 -const background = scene.background; -background.mode = BackgroundMode.Sky; -background.sky.material = skyMaterial; -background.sky.mesh = PrimitiveMesh.createCuboid(engine, 2, 2, 2); -``` - ## Setting Procedural Sky Procedural sky is the default background in 3D projects in the editor. You can also follow the path **[Hierarchy Panel](/en/docs/interface/hierarchy)** -> **Select Scene** -> **[Inspector Panel](/en/docs/interface/inspector)** -> **Background section** -> **Set Mode to Sky** -> **Select the built-in SkyMat material** -> **Set Mesh to the built-in Sphere** diff --git a/docs/en/graphics/background/texture.mdx b/docs/en/graphics/background/texture.mdx index 590351bf21..b8fc931a13 100644 --- a/docs/en/graphics/background/texture.mdx +++ b/docs/en/graphics/background/texture.mdx @@ -24,7 +24,7 @@ background.mode = BackgroundMode.Texture; // 将一张 2D 纹理设置为背景纹理 background.texture = await engine.resourceManager.load({ url: "XXX.jpg", - type: AssetType.Texture2D, + type: AssetType.Texture, }); // 设置填充模式为铺满 background.textureFillMode = BackgroundTextureFillMode.Fill; diff --git a/docs/en/graphics/particle/renderer-main-module.mdx b/docs/en/graphics/particle/renderer-main-module.mdx index 4e3888feab..ea6d2c8e55 100644 --- a/docs/en/graphics/particle/renderer-main-module.mdx +++ b/docs/en/graphics/particle/renderer-main-module.mdx @@ -44,6 +44,6 @@ You can debug each property in the provided example to better understand and con The scalingMode has the following options: -- **Local**: Particles inherit the local transformation of the particle generator, meaning the particle transformation occurs in the generator's local coordinate system. -- **World**: Particles inherit the global transformation of the particle generator, meaning the particle transformation occurs in the world coordinate system. -- **Hierarchy**: Particles inherit transformations from the entire transformation hierarchy, meaning particles consider transformations of the generator's parent and higher-level transformations. \ No newline at end of file +- **World**: Scale particle emission position and size using the world scale, including all parent transforms. +- **Local**: Scale particle emission position and size using only its own transform scale, ignoring parent scale. +- **Shape**: Only scale the emission shape area, particles themselves are not affected. \ No newline at end of file diff --git a/docs/en/graphics/texture/2d.mdx b/docs/en/graphics/texture/2d.mdx index 121826560c..d551235716 100644 --- a/docs/en/graphics/texture/2d.mdx +++ b/docs/en/graphics/texture/2d.mdx @@ -21,7 +21,7 @@ In a script, you can load an image with [ResourceManager](/apis/core/#ResourceMa ```typescript engine.resourceManager .load({ - type: AssetType.Texture2D, + type: AssetType.Texture, url: `image url`, }) .then((texture) => { diff --git a/docs/en/graphics/texture/cube.mdx b/docs/en/graphics/texture/cube.mdx index ce19913801..0e91dbb1f7 100644 --- a/docs/en/graphics/texture/cube.mdx +++ b/docs/en/graphics/texture/cube.mdx @@ -31,28 +31,6 @@ After preparing the HDR image, follow the steps: -Of course, you can also create a cube texture in scripts by loading six textures in the corresponding order. - -```typescript -const cubeTextureResource = { - type: AssetType.TextureCube, - urls: [ - "px - right image url", - "nx - left image url", - "py - top image url", - "ny - bottom image url", - "pz - front image url", - "nz - back image url", - ], -}; - -engine.resourceManager.load(cubeTextureResource).then((resource) => { - // Cube texture supported by the engine - const cubeTexture = resource; - // The texture can now be applied to materials or used for other operations -}); -``` - ## Usage Cube textures are primarily used in skyboxes. For more details, refer to [Sky Background](/en/docs/graphics/background/sky/). \ No newline at end of file diff --git a/docs/zh/assets/load.mdx b/docs/zh/assets/load.mdx index 089121564b..f37a32b622 100644 --- a/docs/zh/assets/load.mdx +++ b/docs/zh/assets/load.mdx @@ -77,7 +77,7 @@ this.engine.resourceManager.load("https://a.png"); // 加载 base64 this.engine.resourceManager.load({ url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==", - type: AssetType.Texture2D, + type: AssetType.Texture, }); // 加载编辑器虚拟路径下的资源 diff --git a/docs/zh/core/color.mdx b/docs/zh/core/color.mdx index b91bfde7c5..785fbfe30e 100644 --- a/docs/zh/core/color.mdx +++ b/docs/zh/core/color.mdx @@ -54,10 +54,10 @@ sRGB 色彩空间的伽马校正正是基于这种感知特性设计的,并对 [加载纹理](/docs/assets/load) 默认使用 sRGB 色彩空间。如果需要使用线性空间,可以通过参数关闭 sRGB 设置: ```ts showLineNumbers {7} -// 使用 Texture2DLoader 加载纹理 +// 使用 TextureLoader 加载纹理 engine.resourceManager .load({ - type: AssetType.Texture2D, + type: AssetType.Texture, url: "https://***.png", params: { isSRGBColorSpace: false // 关闭 sRGB,使用线性空间,如法线贴图 diff --git a/docs/zh/graphics/background/sky.mdx b/docs/zh/graphics/background/sky.mdx index 7119309a63..e5fd67af3c 100644 --- a/docs/zh/graphics/background/sky.mdx +++ b/docs/zh/graphics/background/sky.mdx @@ -34,31 +34,6 @@ label: Graphics/Background -### 代码设置天空盒 - -```typescript -// 创建天空盒纹理 -const textureCube = await engine.resourceManager.load({ - urls: [ - "px - right 图片 url", - "nx - left 图片 url", - "py - top 图片 url", - "ny - bottom 图片 url", - "pz - front 图片 url", - "nz - back 图片 url", - ], - type: AssetType.TextureCube, -}); -// 创建天空盒材质 -const skyMaterial = new SkyBoxMaterial(engine); -skyMaterial.texture = textureCube; -// 设置天空盒 -const background = scene.background; -background.mode = BackgroundMode.Sky; -background.sky.material = skyMaterial; -background.sky.mesh = PrimitiveMesh.createCuboid(engine, 2, 2, 2); -``` - ## 设置程序化天空 程序化天空是编辑器在 3D 项目中的默认背景,您也可以依照路径 **[层级面板](/docs/interface/hierarchy)** -> **选中 Scene** -> **[检查器面板](/docs/interface/inspector)** -> **Background 栏** -> **Mode 设置 Sky** -> **Material 选择内置的 SkyMat 材质** -> **Mesh 设置内置的 Sphere** diff --git a/docs/zh/graphics/background/texture.mdx b/docs/zh/graphics/background/texture.mdx index 30b2d94a77..73243c6971 100644 --- a/docs/zh/graphics/background/texture.mdx +++ b/docs/zh/graphics/background/texture.mdx @@ -24,7 +24,7 @@ background.mode = BackgroundMode.Texture; // 将一张 2D 纹理设置为背景纹理 background.texture = await engine.resourceManager.load({ url: "XXX.jpg", - type: AssetType.Texture2D, + type: AssetType.Texture, }); // 设置填充模式为铺满 background.textureFillMode = BackgroundTextureFillMode.Fill; diff --git a/docs/zh/graphics/particle/renderer-main-module.mdx b/docs/zh/graphics/particle/renderer-main-module.mdx index b551210dc7..1faa4e8bee 100644 --- a/docs/zh/graphics/particle/renderer-main-module.mdx +++ b/docs/zh/graphics/particle/renderer-main-module.mdx @@ -44,6 +44,6 @@ label: Graphics/Particle scalingMode 有以下几种模式: -- **Local**:粒子会继承粒子生成器的局部变换,即粒子的变换是在生成器的本地坐标系中进行的 -- **World**:粒子会继承粒子生成器的全局变换,即粒子的变换是在世界坐标系中进行的 -- **Hierarchy**:粒子会继承整个变换层级中的变换,即粒子会考虑到生成器的父级及更上级的变换 +- **World**:使用世界缩放(包含所有父级变换)来缩放粒子的发射位置和大小 +- **Local**:仅使用自身 Transform 的缩放来缩放粒子的发射位置和大小,忽略父级缩放 +- **Shape**:仅缩放发射形状区域,粒子本身的大小不受影响 diff --git a/docs/zh/graphics/texture/2d.mdx b/docs/zh/graphics/texture/2d.mdx index 1ec53d6c51..5092146e32 100644 --- a/docs/zh/graphics/texture/2d.mdx +++ b/docs/zh/graphics/texture/2d.mdx @@ -21,7 +21,7 @@ label: Graphics/Texture ```typescript engine.resourceManager .load({ - type: AssetType.Texture2D, + type: AssetType.Texture, url: `图片url`, }) .then((texture) => { diff --git a/docs/zh/graphics/texture/cube.mdx b/docs/zh/graphics/texture/cube.mdx index 187bc523e4..b42844589e 100644 --- a/docs/zh/graphics/texture/cube.mdx +++ b/docs/zh/graphics/texture/cube.mdx @@ -31,28 +31,6 @@ label: Graphics/Texture -当然,你也可以在脚本中通过加载六张对应顺序的纹理得到相应的立方纹理。 - -```typescript -const cubeTextureResource = { - type: AssetType.TextureCube, - urls: [ - "px - right 图片 url", - "nx - left 图片 url", - "py - top 图片 url", - "ny - bottom 图片 url", - "pz - front 图片 url", - "nz - back 图片 url", - ], -}; - -engine.resourceManager.load(cubeTextureResource).then((resource) => { - // 引擎支持的立方纹理 - const cubeTexture = resource; - // 接下来可以将纹理应用到材质上或者进行其他操作 -}); -``` - ## 使用 立方纹理主要在天空盒中使用,详情可参考[天空背景](/docs/graphics/background/sky/) diff --git a/e2e/case/.initPostProcessEnv.ts b/e2e/case/.initPostProcessEnv.ts index 005552bb97..2c3381ad9e 100644 --- a/e2e/case/.initPostProcessEnv.ts +++ b/e2e/case/.initPostProcessEnv.ts @@ -38,7 +38,7 @@ export async function initPostProcessEnv( engine.resourceManager .load({ type: AssetType.AmbientLight, - url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*kY8wSIrDC2gAAAAAgBAAAAgAekp5AQ/ambient.ambLight" + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*eRJ8QKzf5zAAAAAAgBAAAAgAekp5AQ/ambient.ambLight" }) .then((ambientLight) => { scene.ambientLight = ambientLight; @@ -52,7 +52,7 @@ export async function initPostProcessEnv( return ambientLight; }), engine.resourceManager.load({ - type: AssetType.Texture2D, + type: AssetType.Texture, url: "https://mdn.alipayobjects.com/huamei_dmxymu/afts/img/A*tMeTQ4Mx60oAAAAAAAAAAAAADuuHAQ/original" }) ]).then((resArray) => { diff --git a/e2e/case/camera-ssao.ts b/e2e/case/camera-ssao.ts index cfa897f4c6..d3dd438f86 100644 --- a/e2e/case/camera-ssao.ts +++ b/e2e/case/camera-ssao.ts @@ -114,7 +114,7 @@ WebGLEngine.create({ engine.resourceManager .load({ type: AssetType.AmbientLight, - url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*kY8wSIrDC2gAAAAAgBAAAAgAekp5AQ/ambient.ambLight" + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*eRJ8QKzf5zAAAAAAgBAAAAgAekp5AQ/ambient.ambLight" }) .then((ambientLight) => { scene.ambientLight = ambientLight; diff --git a/e2e/case/canvas-transparency.ts b/e2e/case/canvas-transparency.ts index a1366e7f5a..424d9191bf 100644 --- a/e2e/case/canvas-transparency.ts +++ b/e2e/case/canvas-transparency.ts @@ -35,11 +35,11 @@ WebGLEngine.create({ canvas: "canvas", graphicDeviceOptions: { alpha: false } }) .load([ { url: "https://mdn.alipayobjects.com/huamei_kz4wfo/afts/img/A*3d2oSbam_wcAAAAAAAAAAAAAesp6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://mdn.alipayobjects.com/huamei_qbugvr/afts/img/A*j6MERb-exVgAAAAAAAAAAAAADtKFAQ/original", - type: AssetType.Texture2D + type: AssetType.Texture } ]) .then((resources) => { diff --git a/e2e/case/material-blinn-phong.ts b/e2e/case/material-blinn-phong.ts index ace494c3ab..15d93b04cf 100644 --- a/e2e/case/material-blinn-phong.ts +++ b/e2e/case/material-blinn-phong.ts @@ -39,11 +39,11 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { engine.resourceManager .load([ { - type: AssetType.Texture2D, + type: AssetType.Texture, url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*g_HIRqQdNUcAAAAAAAAAAAAAARQnAQ" }, { - type: AssetType.Texture2D, + type: AssetType.Texture, url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*H7nMRY2SuWcAAAAAAAAAAAAAARQnAQ", params: { isSRGBColorSpace: false diff --git a/e2e/case/material-pbr-clearcoat.ts b/e2e/case/material-pbr-clearcoat.ts index 51711499f2..d4b8eefac4 100644 --- a/e2e/case/material-pbr-clearcoat.ts +++ b/e2e/case/material-pbr-clearcoat.ts @@ -45,7 +45,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { engine.resourceManager .load({ type: AssetType.AmbientLight, - url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*kY8wSIrDC2gAAAAAgBAAAAgAekp5AQ/ambient.ambLight" + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*eRJ8QKzf5zAAAAAAgBAAAAgAekp5AQ/ambient.ambLight" }) .then((ambientLight) => { scene.ambientLight = ambientLight; diff --git a/e2e/case/material-pbr-specular.ts b/e2e/case/material-pbr-specular.ts index cbd6f0d75e..fe68e36d50 100644 --- a/e2e/case/material-pbr-specular.ts +++ b/e2e/case/material-pbr-specular.ts @@ -56,7 +56,7 @@ WebGLEngine.create({ canvas: "canvas", shaderLab: new ShaderLab() }).then((engin engine.resourceManager .load({ type: AssetType.AmbientLight, - url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*kY8wSIrDC2gAAAAAgBAAAAgAekp5AQ/ambient.ambLight" + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*eRJ8QKzf5zAAAAAAgBAAAAgAekp5AQ/ambient.ambLight" }) .then((ambientLight) => { scene.ambientLight = ambientLight; diff --git a/e2e/case/material-pbr.ts b/e2e/case/material-pbr.ts index 6f893ed559..243c09b10e 100644 --- a/e2e/case/material-pbr.ts +++ b/e2e/case/material-pbr.ts @@ -42,7 +42,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { engine.resourceManager .load({ type: AssetType.AmbientLight, - url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*kY8wSIrDC2gAAAAAgBAAAAgAekp5AQ/ambient.ambLight" + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*eRJ8QKzf5zAAAAAAgBAAAAgAekp5AQ/ambient.ambLight" }) .then((ambientLight) => { scene.ambientLight = ambientLight; diff --git a/e2e/case/material-shaderReplacement.ts b/e2e/case/material-shaderReplacement.ts index 2abfbc79f2..398064d346 100644 --- a/e2e/case/material-shaderReplacement.ts +++ b/e2e/case/material-shaderReplacement.ts @@ -49,7 +49,7 @@ async function main() { .load([ { type: AssetType.AmbientLight, - url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*kY8wSIrDC2gAAAAAgBAAAAgAekp5AQ/ambient.ambLight" + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*eRJ8QKzf5zAAAAAAgBAAAAgAekp5AQ/ambient.ambLight" }, { url: "https://gw.alipayobjects.com/os/bmw-prod/150e44f6-7810-4c45-8029-3575d36aff30.gltf", @@ -61,7 +61,7 @@ async function main() { }, { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*Q60vQ40ZERsAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture } ]) .then((resources) => { diff --git a/e2e/case/particleRenderer-burst-cycles.ts b/e2e/case/particleRenderer-burst-cycles.ts new file mode 100644 index 0000000000..597e38c7dd --- /dev/null +++ b/e2e/case/particleRenderer-burst-cycles.ts @@ -0,0 +1,110 @@ +/** + * @title Particle Burst Cycles + * @category Particle + */ +import { + AssetType, + BlendMode, + Burst, + Camera, + Color, + Engine, + Entity, + ParticleCompositeCurve, + ParticleCurveMode, + ParticleGradientMode, + ParticleMaterial, + ParticleRenderer, + ParticleSimulationSpace, + SphereShape, + Texture2D, + WebGLEngine +} from "@galacean/engine"; +import { initScreenshot, updateForE2E } from "./.mockForE2E"; + +WebGLEngine.create({ + canvas: "canvas" +}).then((engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity(); + scene.background.solidColor = new Color(0, 0, 0, 1); + + const cameraEntity = rootEntity.createChild("camera"); + cameraEntity.transform.setPosition(0, 2, 18); + const camera = cameraEntity.addComponent(Camera); + camera.fieldOfView = 60; + + engine.resourceManager + .load({ + url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*JPsCSK5LtYkAAAAAAAAAAAAADil6AQ/original", + type: AssetType.Texture + }) + .then((texture) => { + createBurstCyclesParticle(engine, rootEntity, texture); + updateForE2E(engine, 50); + initScreenshot(engine, camera); + }); +}); + +function createBurstCyclesParticle(engine: Engine, rootEntity: Entity, texture: Texture2D): void { + const particleEntity = new Entity(engine, "BurstCycles"); + particleEntity.transform.setPosition(0, 0, 0); + + const particleRenderer = particleEntity.addComponent(ParticleRenderer); + const generator = particleRenderer.generator; + generator.useAutoRandomSeed = false; + + const material = new ParticleMaterial(engine); + material.baseColor = new Color(0.4, 0.8, 1.0, 1.0); + material.blendMode = BlendMode.Additive; + material.baseTexture = texture; + particleRenderer.setMaterial(material); + + const { main, emission, colorOverLifetime, sizeOverLifetime } = generator; + + // Main + main.duration = 3; + main.isLoop = true; + main.startLifetime.constantMin = 1; + main.startLifetime.constantMax = 2; + main.startLifetime.mode = ParticleCurveMode.TwoConstants; + main.startSpeed.constantMin = 2; + main.startSpeed.constantMax = 5; + main.startSpeed.mode = ParticleCurveMode.TwoConstants; + main.startSize.constantMin = 0.1; + main.startSize.constantMax = 0.3; + main.startSize.mode = ParticleCurveMode.TwoConstants; + main.gravityModifier.constant = -0.5; + main.simulationSpace = ParticleSimulationSpace.World; + main.maxParticles = 500; + + // Emission with burst cycles + emission.rateOverTime.constant = 0; + + // Burst at t=0, 20 particles, repeats 4 times every 0.3s -> fires at 0, 0.3, 0.6, 0.9 + emission.addBurst(new Burst(0, new ParticleCompositeCurve(20), 4, 0.3)); + + const sphereShape = new SphereShape(); + sphereShape.radius = 0.5; + emission.shape = sphereShape; + + // Color over lifetime + colorOverLifetime.enabled = true; + colorOverLifetime.color.mode = ParticleGradientMode.Gradient; + const gradient = colorOverLifetime.color.gradient; + gradient.alphaKeys[0].alpha = 0; + gradient.alphaKeys[1].alpha = 0; + gradient.addAlphaKey(0.1, 1.0); + gradient.addAlphaKey(0.7, 1.0); + + // Size over lifetime + sizeOverLifetime.enabled = true; + sizeOverLifetime.size.mode = ParticleCurveMode.Curve; + const curve = sizeOverLifetime.size.curve; + curve.keys[0].value = 1; + curve.keys[1].value = 0; + + rootEntity.addChild(particleEntity); +} diff --git a/e2e/case/particleRenderer-dream.ts b/e2e/case/particleRenderer-dream.ts index 6eb51637ed..1d65203827 100644 --- a/e2e/case/particleRenderer-dream.ts +++ b/e2e/case/particleRenderer-dream.ts @@ -45,19 +45,19 @@ WebGLEngine.create({ .load([ { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*JPsCSK5LtYkAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*eWTFRZPqfDMAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*J8uhRoxJtYgAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*Ea3qRb1yCQMAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture } ]) .then((textures) => { diff --git a/e2e/case/particleRenderer-emissive.ts b/e2e/case/particleRenderer-emissive.ts index 90a22c9220..b1a20a1ad9 100644 --- a/e2e/case/particleRenderer-emissive.ts +++ b/e2e/case/particleRenderer-emissive.ts @@ -47,7 +47,7 @@ WebGLEngine.create({ .load([ { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*JPsCSK5LtYkAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://gw.alipayobjects.com/os/bmw-prod/5e3c1e4e-496e-45f8-8e05-f89f2bd5e4a4.glb", @@ -55,7 +55,7 @@ WebGLEngine.create({ }, { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*Q60vQ40ZERsAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture } ]) .then((resources) => { diff --git a/e2e/case/particleRenderer-emit-billboard-stretched.ts b/e2e/case/particleRenderer-emit-billboard-stretched.ts index 812073b8b5..c717bac56f 100644 --- a/e2e/case/particleRenderer-emit-billboard-stretched.ts +++ b/e2e/case/particleRenderer-emit-billboard-stretched.ts @@ -45,7 +45,7 @@ WebGLEngine.create({ .load([ { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*yu-DSb0surwAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://gw.alipayobjects.com/os/OasisHub/267000040/9994/%25E5%25BD%2592%25E6%25A1%25A3.gltf", diff --git a/e2e/case/particleRenderer-emit-mesh-cone-scale-3d-rotation-life-seperate.ts b/e2e/case/particleRenderer-emit-mesh-cone-scale-3d-rotation-life-seperate.ts index d6e14339d5..d8b3638f92 100644 --- a/e2e/case/particleRenderer-emit-mesh-cone-scale-3d-rotation-life-seperate.ts +++ b/e2e/case/particleRenderer-emit-mesh-cone-scale-3d-rotation-life-seperate.ts @@ -45,7 +45,7 @@ WebGLEngine.create({ .load([ { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*yu-DSb0surwAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://gw.alipayobjects.com/os/OasisHub/267000040/9994/%25E5%25BD%2592%25E6%25A1%25A3.gltf", diff --git a/e2e/case/particleRenderer-emit-mesh-cone-scale-3d-rotation.ts b/e2e/case/particleRenderer-emit-mesh-cone-scale-3d-rotation.ts index 7d24c58576..3446cdbf1a 100644 --- a/e2e/case/particleRenderer-emit-mesh-cone-scale-3d-rotation.ts +++ b/e2e/case/particleRenderer-emit-mesh-cone-scale-3d-rotation.ts @@ -45,7 +45,7 @@ WebGLEngine.create({ .load([ { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*yu-DSb0surwAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://gw.alipayobjects.com/os/OasisHub/267000040/9994/%25E5%25BD%2592%25E6%25A1%25A3.gltf", diff --git a/e2e/case/particleRenderer-emit-mesh-cone-scale-rotation-life-seperate.ts b/e2e/case/particleRenderer-emit-mesh-cone-scale-rotation-life-seperate.ts index c2543552f3..3e65929bb8 100644 --- a/e2e/case/particleRenderer-emit-mesh-cone-scale-rotation-life-seperate.ts +++ b/e2e/case/particleRenderer-emit-mesh-cone-scale-rotation-life-seperate.ts @@ -45,7 +45,7 @@ WebGLEngine.create({ .load([ { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*yu-DSb0surwAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://gw.alipayobjects.com/os/OasisHub/267000040/9994/%25E5%25BD%2592%25E6%25A1%25A3.gltf", diff --git a/e2e/case/particleRenderer-emit-mesh-cone-scale-rotation-life.ts b/e2e/case/particleRenderer-emit-mesh-cone-scale-rotation-life.ts index 5adbd3723f..8e7a3b355c 100644 --- a/e2e/case/particleRenderer-emit-mesh-cone-scale-rotation-life.ts +++ b/e2e/case/particleRenderer-emit-mesh-cone-scale-rotation-life.ts @@ -45,7 +45,7 @@ WebGLEngine.create({ .load([ { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*yu-DSb0surwAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://gw.alipayobjects.com/os/OasisHub/267000040/9994/%25E5%25BD%2592%25E6%25A1%25A3.gltf", diff --git a/e2e/case/particleRenderer-emit-mesh-cone-scale-rotation-world.ts b/e2e/case/particleRenderer-emit-mesh-cone-scale-rotation-world.ts index 195ad2be75..090b9179f1 100644 --- a/e2e/case/particleRenderer-emit-mesh-cone-scale-rotation-world.ts +++ b/e2e/case/particleRenderer-emit-mesh-cone-scale-rotation-world.ts @@ -45,7 +45,7 @@ WebGLEngine.create({ .load([ { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*yu-DSb0surwAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://gw.alipayobjects.com/os/OasisHub/267000040/9994/%25E5%25BD%2592%25E6%25A1%25A3.gltf", diff --git a/e2e/case/particleRenderer-emit-mesh-cone-scale-rotation.ts b/e2e/case/particleRenderer-emit-mesh-cone-scale-rotation.ts index fccce4056f..6656fd5bed 100644 --- a/e2e/case/particleRenderer-emit-mesh-cone-scale-rotation.ts +++ b/e2e/case/particleRenderer-emit-mesh-cone-scale-rotation.ts @@ -45,7 +45,7 @@ WebGLEngine.create({ .load([ { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*yu-DSb0surwAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://gw.alipayobjects.com/os/OasisHub/267000040/9994/%25E5%25BD%2592%25E6%25A1%25A3.gltf", diff --git a/e2e/case/particleRenderer-emit-mesh-cone.ts b/e2e/case/particleRenderer-emit-mesh-cone.ts index bd01097d00..6954fff204 100644 --- a/e2e/case/particleRenderer-emit-mesh-cone.ts +++ b/e2e/case/particleRenderer-emit-mesh-cone.ts @@ -57,7 +57,7 @@ WebGLEngine.create({ .load([ { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*yu-DSb0surwAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://gw.alipayobjects.com/os/OasisHub/267000040/9994/%25E5%25BD%2592%25E6%25A1%25A3.gltf", diff --git a/e2e/case/particleRenderer-emit-mesh-no-shape-world.ts b/e2e/case/particleRenderer-emit-mesh-no-shape-world.ts index a1abc275a1..28c8e2edd0 100644 --- a/e2e/case/particleRenderer-emit-mesh-no-shape-world.ts +++ b/e2e/case/particleRenderer-emit-mesh-no-shape-world.ts @@ -44,7 +44,7 @@ WebGLEngine.create({ .load([ { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*yu-DSb0surwAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://gw.alipayobjects.com/os/OasisHub/267000040/9994/%25E5%25BD%2592%25E6%25A1%25A3.gltf", diff --git a/e2e/case/particleRenderer-emit-mesh-no-shape.ts b/e2e/case/particleRenderer-emit-mesh-no-shape.ts index 276e8d0e7c..e80c228f94 100644 --- a/e2e/case/particleRenderer-emit-mesh-no-shape.ts +++ b/e2e/case/particleRenderer-emit-mesh-no-shape.ts @@ -44,7 +44,7 @@ WebGLEngine.create({ .load([ { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*yu-DSb0surwAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://gw.alipayobjects.com/os/OasisHub/267000040/9994/%25E5%25BD%2592%25E6%25A1%25A3.gltf", diff --git a/e2e/case/particleRenderer-fire.ts b/e2e/case/particleRenderer-fire.ts index 6344f7bba7..d1b0c56899 100644 --- a/e2e/case/particleRenderer-fire.ts +++ b/e2e/case/particleRenderer-fire.ts @@ -44,7 +44,7 @@ WebGLEngine.create({ // Create camera const cameraEntity = rootEntity.createChild("camera_entity"); - cameraEntity.transform.position = new Vector3(-10, 1, 3);// -10 can test bounds transform + cameraEntity.transform.position = new Vector3(-10, 1, 3); // -10 can test bounds transform const camera = cameraEntity.addComponent(Camera); camera.fieldOfView = 60; @@ -52,19 +52,19 @@ WebGLEngine.create({ .load([ { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*yu-DSb0surwAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: " https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*JlayRa2WltYAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*cFafRr6WaWUAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*TASTTpESkIIAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture } ]) .then((textures) => { @@ -199,7 +199,7 @@ function createFireGlowParticle(fireEntity: Entity, texture: Texture2D): void { main.simulationSpace = ParticleSimulationSpace.World; - main.scalingMode = ParticleScaleMode.Hierarchy; + main.scalingMode = ParticleScaleMode.World; // Emission module emission.rateOverTime.constant = 20; @@ -270,7 +270,7 @@ function createFireSmokeParticle(fireEntity: Entity, texture: Texture2D): void { main.simulationSpace = ParticleSimulationSpace.World; - main.scalingMode = ParticleScaleMode.Hierarchy; + main.scalingMode = ParticleScaleMode.World; // Emission module emission.rateOverTime.constant = 25; @@ -353,7 +353,7 @@ function createFireEmbersParticle(fireEntity: Entity, texture: Texture2D): void main.simulationSpace = ParticleSimulationSpace.World; - main.scalingMode = ParticleScaleMode.Hierarchy; + main.scalingMode = ParticleScaleMode.World; // Emission module emission.rateOverTime.constant = 65; diff --git a/e2e/case/particleRenderer-force.ts b/e2e/case/particleRenderer-force.ts index 3e243a8b20..553007a2ce 100644 --- a/e2e/case/particleRenderer-force.ts +++ b/e2e/case/particleRenderer-force.ts @@ -62,19 +62,19 @@ WebGLEngine.create({ .load([ { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*JPsCSK5LtYkAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*eWTFRZPqfDMAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*J8uhRoxJtYgAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*Ea3qRb1yCQMAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture } ]) .then((textures) => { diff --git a/e2e/case/particleRenderer-horizontal-billboard.ts b/e2e/case/particleRenderer-horizontal-billboard.ts index 3f373f6845..f05a3cffea 100644 --- a/e2e/case/particleRenderer-horizontal-billboard.ts +++ b/e2e/case/particleRenderer-horizontal-billboard.ts @@ -37,7 +37,7 @@ WebGLEngine.create({ .load([ { url: "https://mdn.alipayobjects.com/huamei_9ahbho/afts/img/A*QJvmQ6g4ujYAAAAAgCAAAAgAegDwAQ/original", - type: AssetType.Texture2D + type: AssetType.Texture } ]) .then((resources) => { diff --git a/e2e/case/particleRenderer-limitVelocity.ts b/e2e/case/particleRenderer-limitVelocity.ts index 86d02f1fed..87b5d53044 100644 --- a/e2e/case/particleRenderer-limitVelocity.ts +++ b/e2e/case/particleRenderer-limitVelocity.ts @@ -51,7 +51,7 @@ WebGLEngine.create({ engine.resourceManager .load({ url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*JPsCSK5LtYkAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }) .then((texture) => { createParticle(engine, rootEntity, texture); diff --git a/e2e/case/particleRenderer-noise.ts b/e2e/case/particleRenderer-noise.ts new file mode 100644 index 0000000000..2f4c9220c6 --- /dev/null +++ b/e2e/case/particleRenderer-noise.ts @@ -0,0 +1,111 @@ +/** + * @title Particle Noise + * @category Particle + */ +import { + AssetType, + BlendMode, + Camera, + Color, + Engine, + Entity, + ParticleCompositeCurve, + ParticleCurveMode, + ParticleGradientMode, + ParticleMaterial, + ParticleRenderer, + ParticleSimulationSpace, + ConeShape, + Texture2D, + WebGLEngine +} from "@galacean/engine"; +import { initScreenshot, updateForE2E } from "./.mockForE2E"; + +WebGLEngine.create({ + canvas: "canvas" +}).then((engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity(); + scene.background.solidColor = new Color(0, 0, 0, 1); + + const cameraEntity = rootEntity.createChild("camera"); + cameraEntity.transform.setPosition(0, 0, 0); + const camera = cameraEntity.addComponent(Camera); + camera.fieldOfView = 60; + + engine.resourceManager + .load({ + url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*JPsCSK5LtYkAAAAAAAAAAAAADil6AQ/original", + type: AssetType.Texture + }) + .then((texture) => { + createNoiseParticle(engine, rootEntity, texture); + + updateForE2E(engine, 200); + initScreenshot(engine, camera); + }); +}); + +function createNoiseParticle(engine: Engine, rootEntity: Entity, texture: Texture2D): void { + const particleEntity = new Entity(engine, "Noise"); + particleEntity.transform.setPosition(0, 0, -2); + + const particleRenderer = particleEntity.addComponent(ParticleRenderer); + const generator = particleRenderer.generator; + generator.useAutoRandomSeed = false; + + const material = new ParticleMaterial(engine); + material.baseColor = new Color(0.4, 0.8, 1.0, 1.0); + material.blendMode = BlendMode.Additive; + material.baseTexture = texture; + particleRenderer.setMaterial(material); + + const { main, emission, noise, colorOverLifetime } = generator; + + // Main + main.duration = 3; + main.isLoop = true; + main.startLifetime.constantMin = 0.3; + main.startLifetime.constantMax = 0.6; + main.startLifetime.mode = ParticleCurveMode.TwoConstants; + main.startSpeed.constantMin = 4; + main.startSpeed.constantMax = 4; + main.startSpeed.mode = ParticleCurveMode.TwoConstants; + main.startSize.constantMin = 0.05; + main.startSize.constantMax = 0.1; + main.startSize.mode = ParticleCurveMode.TwoConstants; + main.gravityModifier.constant = -0.5; + main.simulationSpace = ParticleSimulationSpace.Local; + main.maxParticles = 200; + + // Emission + emission.rateOverTime.constant = 40; + const coneShape = new ConeShape(); + coneShape.angle = 25; + coneShape.radius = 0.00001; + emission.shape = coneShape; + + // Color over lifetime + // colorOverLifetime.enabled = true; + // colorOverLifetime.color.mode = ParticleGradientMode.Gradient; + // const gradient = colorOverLifetime.color.gradient; + // gradient.alphaKeys[0].alpha = 0; + // gradient.alphaKeys[1].alpha = 0; + // gradient.addAlphaKey(0.1, 1.0); + // gradient.addAlphaKey(0.8, 1.0); + + // Noise + noise.enabled = true; + noise.strengthX = new ParticleCompositeCurve(1); + noise.strengthY = new ParticleCompositeCurve(1); + noise.strengthZ = new ParticleCompositeCurve(1); + noise.frequency = 1; + noise.scrollSpeed = 0; + noise.octaveCount = 1; + noise.octaveIntensityMultiplier = 0.5; + noise.octaveFrequencyMultiplier = 2.0; + + rootEntity.addChild(particleEntity); +} diff --git a/e2e/case/particleRenderer-shape-mesh.ts b/e2e/case/particleRenderer-shape-mesh.ts index d865496788..f44fe1deb8 100644 --- a/e2e/case/particleRenderer-shape-mesh.ts +++ b/e2e/case/particleRenderer-shape-mesh.ts @@ -47,7 +47,7 @@ WebGLEngine.create({ .load([ { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*JPsCSK5LtYkAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://gw.alipayobjects.com/os/bmw-prod/5e3c1e4e-496e-45f8-8e05-f89f2bd5e4a4.glb", diff --git a/e2e/case/particleRenderer-shape-transform.ts b/e2e/case/particleRenderer-shape-transform.ts new file mode 100644 index 0000000000..3fc0bf00da --- /dev/null +++ b/e2e/case/particleRenderer-shape-transform.ts @@ -0,0 +1,89 @@ +/** + * @title Particle Shape Transform + * @category Particle + */ +import { + Camera, + Color, + ConeShape, + BoxShape, + Engine, + Entity, + Logger, + ParticleMaterial, + ParticleRenderer, + ParticleSimulationSpace, + Vector3, + WebGLEngine +} from "@galacean/engine"; +import { initScreenshot, updateForE2E } from "./.mockForE2E"; + +WebGLEngine.create({ + canvas: "canvas" +}).then((engine) => { + Logger.enable(); + engine.canvas.resizeByClientSize(); + + const rootEntity = engine.sceneManager.activeScene.createRootEntity("Root"); + + const cameraEntity = rootEntity.createChild("Camera"); + cameraEntity.transform.position = new Vector3(0, 0, 30); + const camera = cameraEntity.addComponent(Camera); + camera.fieldOfView = 60; + camera.nearClipPlane = 0.3; + camera.farClipPlane = 1000; + + // Cone with shape position offset + createParticle(rootEntity, engine, -6, () => { + const shape = new ConeShape(); + shape.position.set(0, 3, 0); + return shape; + }); + + // Cone with shape rotation + createParticle(rootEntity, engine, -2, () => { + const shape = new ConeShape(); + shape.rotation.set(0, 0, 90); + return shape; + }); + + // Box with shape scale + createParticle(rootEntity, engine, 2, () => { + const shape = new BoxShape(); + shape.scale.set(3, 1, 1); + return shape; + }); + + // Cone with combined transform + createParticle(rootEntity, engine, 6, () => { + const shape = new ConeShape(); + shape.position.set(0, 2, 0); + shape.rotation.set(0, 0, 45); + shape.scale.set(2, 1, 1); + return shape; + }); + + updateForE2E(engine, 500); + initScreenshot(engine, camera); +}); + +function createParticle(rootEntity: Entity, engine: Engine, xPos: number, createShape: () => any): void { + const particleEntity = rootEntity.createChild("Particle"); + particleEntity.transform.position.set(xPos, 0, 0); + + const particleRenderer = particleEntity.addComponent(ParticleRenderer); + + const material = new ParticleMaterial(engine); + material.baseColor = new Color(1.0, 1.0, 1.0, 1.0); + particleRenderer.setMaterial(material); + + const generator = particleRenderer.generator; + generator.useAutoRandomSeed = false; + + const { main, emission } = generator; + main.startSpeed.constant = 3; + main.startSize.constant = 0.15; + main.simulationSpace = ParticleSimulationSpace.Local; + + emission.shape = createShape(); +} diff --git a/e2e/case/particleRenderer-textureSheetAnimation.ts b/e2e/case/particleRenderer-textureSheetAnimation.ts index c9662e3014..23504f6c53 100644 --- a/e2e/case/particleRenderer-textureSheetAnimation.ts +++ b/e2e/case/particleRenderer-textureSheetAnimation.ts @@ -45,7 +45,7 @@ WebGLEngine.create({ engine.resourceManager .load({ url: "https://mdn.alipayobjects.com/huamei_qbugvr/afts/img/A*5EyLSqmA7q0AAAAAAAAAAAAADtKFAQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }) .then((texture) => { const particleEntity = new Entity(engine); diff --git a/e2e/case/physx-collision.ts b/e2e/case/physx-collision.ts index 37faf02d3d..81f6ba5b74 100644 --- a/e2e/case/physx-collision.ts +++ b/e2e/case/physx-collision.ts @@ -146,7 +146,7 @@ WebGLEngine.create({ canvas: "canvas", physics: new PhysXPhysics() }).then((engi engine.resourceManager .load({ type: AssetType.AmbientLight, - url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*kY8wSIrDC2gAAAAAgBAAAAgAekp5AQ/ambient.ambLight" + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*eRJ8QKzf5zAAAAAAgBAAAAgAekp5AQ/ambient.ambLight" }) .then((ambientLight) => { scene.ambientLight = ambientLight; diff --git a/e2e/case/physx-customUrl.ts b/e2e/case/physx-customUrl.ts index b8b5f40403..61316228ca 100644 --- a/e2e/case/physx-customUrl.ts +++ b/e2e/case/physx-customUrl.ts @@ -151,7 +151,7 @@ WebGLEngine.create({ engine.resourceManager .load({ type: AssetType.AmbientLight, - url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*kY8wSIrDC2gAAAAAgBAAAAgAekp5AQ/ambient.ambLight" + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*eRJ8QKzf5zAAAAAAgBAAAAgAekp5AQ/ambient.ambLight" }) .then((ambientLight) => { scene.ambientLight = ambientLight; diff --git a/e2e/case/physx-mesh-collider-data.ts b/e2e/case/physx-mesh-collider-data.ts index dcfbbfcdbb..ef5cd99b58 100644 --- a/e2e/case/physx-mesh-collider-data.ts +++ b/e2e/case/physx-mesh-collider-data.ts @@ -266,7 +266,7 @@ WebGLEngine.create({ canvas: "canvas", physics: new PhysXPhysics() }).then((engi engine.resourceManager .load({ type: AssetType.AmbientLight, - url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*kY8wSIrDC2gAAAAAgBAAAAgAekp5AQ/ambient.ambLight" + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*eRJ8QKzf5zAAAAAAgBAAAAgAekp5AQ/ambient.ambLight" }) .then((ambientLight) => { scene.ambientLight = ambientLight; diff --git a/e2e/case/physx-mesh-collider.ts b/e2e/case/physx-mesh-collider.ts index 9d3fa54576..c5f8d6cd66 100644 --- a/e2e/case/physx-mesh-collider.ts +++ b/e2e/case/physx-mesh-collider.ts @@ -85,7 +85,7 @@ WebGLEngine.create({ canvas: "canvas", physics }).then((engine) => { }), engine.resourceManager.load({ type: AssetType.AmbientLight, - url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*kY8wSIrDC2gAAAAAgBAAAAgAekp5AQ/ambient.ambLight" + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*eRJ8QKzf5zAAAAAAgBAAAAgAekp5AQ/ambient.ambLight" }) ]).then(([gltf, ambientLight]) => { scene.ambientLight = ambientLight; diff --git a/e2e/case/primitive-capsule.ts b/e2e/case/primitive-capsule.ts index ec7fcdd07a..c192a3f0a8 100644 --- a/e2e/case/primitive-capsule.ts +++ b/e2e/case/primitive-capsule.ts @@ -44,7 +44,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { engine.resourceManager .load({ url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*ArCHTbfVPXUAAAAAAAAAAAAAARQnAQ", - type: AssetType.Texture2D + type: AssetType.Texture }) .then((texture: Texture2D) => { const material = new BlinnPhongMaterial(engine); diff --git a/e2e/case/primitive-cone.ts b/e2e/case/primitive-cone.ts index 810752660e..bb064766e0 100644 --- a/e2e/case/primitive-cone.ts +++ b/e2e/case/primitive-cone.ts @@ -44,7 +44,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { engine.resourceManager .load({ url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*ArCHTbfVPXUAAAAAAAAAAAAAARQnAQ", - type: AssetType.Texture2D + type: AssetType.Texture }) .then((texture: Texture2D) => { const material = new BlinnPhongMaterial(engine); diff --git a/e2e/case/primitive-cuboid.ts b/e2e/case/primitive-cuboid.ts index 690828a0fc..023b0b03de 100644 --- a/e2e/case/primitive-cuboid.ts +++ b/e2e/case/primitive-cuboid.ts @@ -44,7 +44,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { engine.resourceManager .load({ url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*ArCHTbfVPXUAAAAAAAAAAAAAARQnAQ", - type: AssetType.Texture2D + type: AssetType.Texture }) .then((texture: Texture2D) => { const material = new BlinnPhongMaterial(engine); diff --git a/e2e/case/primitive-cylinder.ts b/e2e/case/primitive-cylinder.ts index 3cfcf50311..0f573941e2 100644 --- a/e2e/case/primitive-cylinder.ts +++ b/e2e/case/primitive-cylinder.ts @@ -44,7 +44,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { engine.resourceManager .load({ url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*ArCHTbfVPXUAAAAAAAAAAAAAARQnAQ", - type: AssetType.Texture2D + type: AssetType.Texture }) .then((texture: Texture2D) => { const material = new BlinnPhongMaterial(engine); diff --git a/e2e/case/primitive-plane.ts b/e2e/case/primitive-plane.ts index e436c1bb36..c40f349716 100644 --- a/e2e/case/primitive-plane.ts +++ b/e2e/case/primitive-plane.ts @@ -44,7 +44,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { engine.resourceManager .load({ url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*ArCHTbfVPXUAAAAAAAAAAAAAARQnAQ", - type: AssetType.Texture2D + type: AssetType.Texture }) .then((texture: Texture2D) => { const material = new BlinnPhongMaterial(engine); diff --git a/e2e/case/primitive-sphere.ts b/e2e/case/primitive-sphere.ts index 4ca7716bec..5ef1ae3e94 100644 --- a/e2e/case/primitive-sphere.ts +++ b/e2e/case/primitive-sphere.ts @@ -44,7 +44,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { engine.resourceManager .load({ url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*ArCHTbfVPXUAAAAAAAAAAAAAARQnAQ", - type: AssetType.Texture2D + type: AssetType.Texture }) .then((texture: Texture2D) => { const material = new BlinnPhongMaterial(engine); diff --git a/e2e/case/primitive-torus.ts b/e2e/case/primitive-torus.ts index 8127843a36..b2423e356a 100644 --- a/e2e/case/primitive-torus.ts +++ b/e2e/case/primitive-torus.ts @@ -44,7 +44,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { engine.resourceManager .load({ url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*ArCHTbfVPXUAAAAAAAAAAAAAARQnAQ", - type: AssetType.Texture2D + type: AssetType.Texture }) .then((texture: Texture2D) => { const material = new BlinnPhongMaterial(engine); diff --git a/e2e/case/shaderlab-pbr.ts b/e2e/case/shaderlab-pbr.ts index 91c535211d..e81ffba62e 100644 --- a/e2e/case/shaderlab-pbr.ts +++ b/e2e/case/shaderlab-pbr.ts @@ -364,7 +364,7 @@ WebGLEngine.create({ canvas: "canvas", shaderLab }).then((engine) => { engine.resourceManager .load({ type: AssetType.AmbientLight, - url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*kY8wSIrDC2gAAAAAgBAAAAgAekp5AQ/ambient.ambLight" + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*eRJ8QKzf5zAAAAAAgBAAAAgAekp5AQ/ambient.ambLight" }) .then((ambientLight) => { scene.ambientLight = ambientLight; @@ -378,7 +378,7 @@ WebGLEngine.create({ canvas: "canvas", shaderLab }).then((engine) => { return ambientLight; }), engine.resourceManager.load({ - type: AssetType.Texture2D, + type: AssetType.Texture, url: "https://mdn.alipayobjects.com/huamei_dmxymu/afts/img/A*tMeTQ4Mx60oAAAAAAAAAAAAADuuHAQ/original" }) ]).then((resArray) => { diff --git a/e2e/case/spriteMask-customStencil.ts b/e2e/case/spriteMask-customStencil.ts index 5f880ef556..89a8db45b7 100644 --- a/e2e/case/spriteMask-customStencil.ts +++ b/e2e/case/spriteMask-customStencil.ts @@ -39,12 +39,12 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { { // Sprite texture url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*rgNGR4Vb7lQAAAAAAAAAAAAAARQnAQ", - type: AssetType.Texture2D + type: AssetType.Texture }, { // Mask texture url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*qyhFT5Un5AgAAAAAAAAAAAAAARQnAQ", - type: AssetType.Texture2D, + type: AssetType.Texture, params: { isSRGBColorSpace: false } diff --git a/e2e/case/texture-hdr.ts b/e2e/case/texture-hdr.ts index f305f46334..7d78e5b71d 100644 --- a/e2e/case/texture-hdr.ts +++ b/e2e/case/texture-hdr.ts @@ -28,8 +28,8 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { engine.resourceManager .load({ - type: AssetType.TextureCube, - url: "https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/1k/kloofendal_48d_partly_cloudy_puresky_1k.hdr" + type: AssetType.Texture, + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*1pMlS5cTtoMAAAAAgBAAAAgAekp5AQ/kloofendal_48d_partly_cloudy_puresky_1k.tex" }) .then((textureCube) => { const sky = scene.background.sky; diff --git a/e2e/case/trailRenderer-basic.ts b/e2e/case/trailRenderer-basic.ts index 6a54e6c158..01955223bf 100644 --- a/e2e/case/trailRenderer-basic.ts +++ b/e2e/case/trailRenderer-basic.ts @@ -238,7 +238,7 @@ WebGLEngine.create({ engine.resourceManager .load({ url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*-DEWQZ0ncrEAAAAASTAAAAgAeil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }) .then((texture) => { // Set texture on all trail materials diff --git a/e2e/config.ts b/e2e/config.ts index e5a9b621cf..2d8250d341 100644 --- a/e2e/config.ts +++ b/e2e/config.ts @@ -117,7 +117,7 @@ export const E2E_CONFIG = { category: "Material", caseFileName: "material-pbr-clearcoat", threshold: 0, - diffPercentage: 0.12 + diffPercentage: 0.1293 }, "white-furnace": { category: "Material", @@ -135,7 +135,7 @@ export const E2E_CONFIG = { category: "Material", caseFileName: "material-pbr", threshold: 0, - diffPercentage: 0.0044 + diffPercentage: 0.0080 }, shaderLab: { category: "Material", @@ -197,7 +197,7 @@ export const E2E_CONFIG = { category: "Texture", caseFileName: "texture-hdr", threshold: 0, - diffPercentage: 0.0512 + diffPercentage: 0.0523 } }, Shadow: { @@ -283,7 +283,7 @@ export const E2E_CONFIG = { category: "Physics", caseFileName: "physx-collision", threshold: 0, - diffPercentage: 0 + diffPercentage: 0.0011459 }, "LitePhysics Collision Group": { category: "Physics", @@ -301,13 +301,13 @@ export const E2E_CONFIG = { category: "Physics", caseFileName: "physx-customUrl", threshold: 0, - diffPercentage: 0 + diffPercentage: 0.001146 }, "PhysX Mesh Collider": { category: "Physics", caseFileName: "physx-mesh-collider", threshold: 0, - diffPercentage: 0.12094 + diffPercentage: 0.13803 }, "PhysX Mesh Collider Data": { category: "Physics", @@ -333,7 +333,7 @@ export const E2E_CONFIG = { category: "Particle", caseFileName: "particleRenderer-fire", threshold: 0, - diffPercentage: 0.0707 + diffPercentage: 0.15386 }, forceOverLifetime: { category: "Particle", @@ -430,6 +430,24 @@ export const E2E_CONFIG = { caseFileName: "particleRenderer-horizontal-billboard", threshold: 0, diffPercentage: 0.2162 + }, + noiseModule: { + category: "Particle", + caseFileName: "particleRenderer-noise", + threshold: 0, + diffPercentage: 0 + }, + shapeTransform: { + category: "Particle", + caseFileName: "particleRenderer-shape-transform", + threshold: 0, + diffPercentage: 0.334 + }, + burstCycles: { + category: "Particle", + caseFileName: "particleRenderer-burst-cycles", + threshold: 0, + diffPercentage: 0.2 } }, PostProcess: { @@ -449,7 +467,7 @@ export const E2E_CONFIG = { category: "PostProcess", caseFileName: "postProcess-LDR-bloom-neutral", threshold: 0, - diffPercentage: 0.097 + diffPercentage: 0.4829 }, customPass: { category: "PostProcess", diff --git a/e2e/fixtures/originImage/Camera_camera-fxaa.jpg b/e2e/fixtures/originImage/Camera_camera-fxaa.jpg index 9fce09309d..761f793115 100644 --- a/e2e/fixtures/originImage/Camera_camera-fxaa.jpg +++ b/e2e/fixtures/originImage/Camera_camera-fxaa.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:871dc9b61f57625430d1ac87e2da4df368271dac223c3f95626293cef974f7f2 -size 327887 +oid sha256:92cdc36e6ff21df3cdd82a293f654b8702a6be0eb32f68d561d3c7bb89ea1b0c +size 298050 diff --git a/e2e/fixtures/originImage/Camera_camera-ssao.jpg b/e2e/fixtures/originImage/Camera_camera-ssao.jpg index 7f14498267..3c0e8c4b15 100644 --- a/e2e/fixtures/originImage/Camera_camera-ssao.jpg +++ b/e2e/fixtures/originImage/Camera_camera-ssao.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f612e96332087c133e81f0bc9820f8f2ec910ebccb81949669d1f6cc75d87325 -size 296468 +oid sha256:4e450a237ce0da6608886ae4593bca7e40340af8d199a4b5ce4165277e454894 +size 251443 diff --git a/e2e/fixtures/originImage/Material_material-pbr-clearcoat.jpg b/e2e/fixtures/originImage/Material_material-pbr-clearcoat.jpg index ae7dab0725..5b17e654c6 100644 --- a/e2e/fixtures/originImage/Material_material-pbr-clearcoat.jpg +++ b/e2e/fixtures/originImage/Material_material-pbr-clearcoat.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc10422299e127448c3728b21b87d140d6741ae7864b8e6e4366c740f81ea2d8 -size 300194 +oid sha256:fda78382a4328e7eff7cd5696872b20ddb8785b3ff218c6d27225491377a7c58 +size 308542 diff --git a/e2e/fixtures/originImage/Material_material-pbr-specular.jpg b/e2e/fixtures/originImage/Material_material-pbr-specular.jpg index a48524351a..30eed2f7d2 100644 --- a/e2e/fixtures/originImage/Material_material-pbr-specular.jpg +++ b/e2e/fixtures/originImage/Material_material-pbr-specular.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c9616a83158f9982cdf618a9e7b27a5f6554af02fb7cbd37a175d8a97205342a -size 282987 +oid sha256:e45a32db260a0fea2b07201b2f72409fd6d264cb3fb1a62ba5d97319050c7f79 +size 304612 diff --git a/e2e/fixtures/originImage/Material_material-pbr.jpg b/e2e/fixtures/originImage/Material_material-pbr.jpg index 098fa2e5d7..8fb8ef36ab 100644 --- a/e2e/fixtures/originImage/Material_material-pbr.jpg +++ b/e2e/fixtures/originImage/Material_material-pbr.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:75c7e051247df42bcd701679279866413abe07129b263d05b600fe75e1257e9d -size 167127 +oid sha256:696b2cfccad082735fb48a9bc04532dfccc129e749cdde91447a649821fe8610 +size 165651 diff --git a/e2e/fixtures/originImage/Particle_particleRenderer-burst-cycles.jpg b/e2e/fixtures/originImage/Particle_particleRenderer-burst-cycles.jpg new file mode 100644 index 0000000000..4059882afb --- /dev/null +++ b/e2e/fixtures/originImage/Particle_particleRenderer-burst-cycles.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7fe0429fd83b750bdd4061ca8516c6767af36729c19dd3346b9eb8566b02950 +size 24627 diff --git a/e2e/fixtures/originImage/Particle_particleRenderer-dream.jpg b/e2e/fixtures/originImage/Particle_particleRenderer-dream.jpg index daa194622d..43d618d57c 100644 --- a/e2e/fixtures/originImage/Particle_particleRenderer-dream.jpg +++ b/e2e/fixtures/originImage/Particle_particleRenderer-dream.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b3cb3ff3020d884d3dfa4255bdb5f1bae7e776a4ef5c652db59dea0f4b32958 -size 406500 +oid sha256:ebc8ffa111a004ac5dd95c7dd123734dd535e06ac0133b5e6a84ff386d4d37f7 +size 405492 diff --git a/e2e/fixtures/originImage/Particle_particleRenderer-emissive.jpg b/e2e/fixtures/originImage/Particle_particleRenderer-emissive.jpg index 2903c7c3f3..1726df9dc3 100644 --- a/e2e/fixtures/originImage/Particle_particleRenderer-emissive.jpg +++ b/e2e/fixtures/originImage/Particle_particleRenderer-emissive.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a533c5c3990205d47f51244660e4f04b082b08058b2aa4a41f1a966661e9554 -size 145542 +oid sha256:6d552b754b7df62e5bf30980084912d3965028b6bdca1d715c32c8c97f3a6380 +size 145146 diff --git a/e2e/fixtures/originImage/Particle_particleRenderer-emit-mesh-cone-scale-3d-rotation-life-seperate.jpg b/e2e/fixtures/originImage/Particle_particleRenderer-emit-mesh-cone-scale-3d-rotation-life-seperate.jpg index 677e079b5a..4e41693021 100644 --- a/e2e/fixtures/originImage/Particle_particleRenderer-emit-mesh-cone-scale-3d-rotation-life-seperate.jpg +++ b/e2e/fixtures/originImage/Particle_particleRenderer-emit-mesh-cone-scale-3d-rotation-life-seperate.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36b948e847409ab00faa46c5e6e707701a67198c4cff82c56c7f406e79795d69 +oid sha256:05dde457049ed9276cc12d328d88a8d5a3178a0ef77cb80d84d8b54b88481817 size 168814 diff --git a/e2e/fixtures/originImage/Particle_particleRenderer-emit-mesh-cone-scale-rotation-life.jpg b/e2e/fixtures/originImage/Particle_particleRenderer-emit-mesh-cone-scale-rotation-life.jpg index e2df3c9853..67ffe795b0 100644 --- a/e2e/fixtures/originImage/Particle_particleRenderer-emit-mesh-cone-scale-rotation-life.jpg +++ b/e2e/fixtures/originImage/Particle_particleRenderer-emit-mesh-cone-scale-rotation-life.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5c23ecdc09372dcf7583df25f80193e8877bd921c67eee19b0eb87ba215b36ca -size 189197 +oid sha256:24e4b97384202cf7c0c05ca63064a38c40aeace3fdf494e9852ff8f06100f3b4 +size 189203 diff --git a/e2e/fixtures/originImage/Particle_particleRenderer-fire.jpg b/e2e/fixtures/originImage/Particle_particleRenderer-fire.jpg index c185dec9f8..54cf5f6751 100644 --- a/e2e/fixtures/originImage/Particle_particleRenderer-fire.jpg +++ b/e2e/fixtures/originImage/Particle_particleRenderer-fire.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:189c50cb8cfad8631d4f51c9913a1e366d63710f626afd4891446eba4d232ac3 -size 129908 +oid sha256:89c655799945badefb53bfdb2eb5c04d8dfb196a862b082ec0509df3b9125f8a +size 127626 diff --git a/e2e/fixtures/originImage/Particle_particleRenderer-force.jpg b/e2e/fixtures/originImage/Particle_particleRenderer-force.jpg index 7bc9108edd..b6bda1b9b6 100644 --- a/e2e/fixtures/originImage/Particle_particleRenderer-force.jpg +++ b/e2e/fixtures/originImage/Particle_particleRenderer-force.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae33bd68046a7fb89c485721302d4039f98678b618e580b34e665773b55bc763 -size 489996 +oid sha256:7795f07115ca315e84b63a8b03b935a69d7fb9ff67991f9ca50791448ac45f50 +size 489799 diff --git a/e2e/fixtures/originImage/Particle_particleRenderer-horizontal-billboard.jpg b/e2e/fixtures/originImage/Particle_particleRenderer-horizontal-billboard.jpg index 01a3398378..4df2edead1 100644 --- a/e2e/fixtures/originImage/Particle_particleRenderer-horizontal-billboard.jpg +++ b/e2e/fixtures/originImage/Particle_particleRenderer-horizontal-billboard.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e6d349ac4271c7127504e4b087c6ebbf08b53823c4e24c4253942d45e6512cbb -size 141096 +oid sha256:a1e6efff3dcda18ab95014385ef5c0226557b5a12b7a49eed4ea2bd898984566 +size 133980 diff --git a/e2e/fixtures/originImage/Particle_particleRenderer-noise.jpg b/e2e/fixtures/originImage/Particle_particleRenderer-noise.jpg new file mode 100644 index 0000000000..8cc6c183aa --- /dev/null +++ b/e2e/fixtures/originImage/Particle_particleRenderer-noise.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5402556ff82f7dfa50d7cd0a9afd4171e7374d10417ecfecb48d13dacee43f63 +size 27119 diff --git a/e2e/fixtures/originImage/Particle_particleRenderer-shape-mesh.jpg b/e2e/fixtures/originImage/Particle_particleRenderer-shape-mesh.jpg index 24783e78f4..3fafad2cc8 100644 --- a/e2e/fixtures/originImage/Particle_particleRenderer-shape-mesh.jpg +++ b/e2e/fixtures/originImage/Particle_particleRenderer-shape-mesh.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bbae61e649ab8ce96c2af9b5ddecbb12b26f775d1862503577a494f63bf436c4 -size 152260 +oid sha256:96eb0e294bbed43d5dd64549c8debe3682aafbc8dd73c49d9901a7945ff1847d +size 152835 diff --git a/e2e/fixtures/originImage/Particle_particleRenderer-shape-transform.jpg b/e2e/fixtures/originImage/Particle_particleRenderer-shape-transform.jpg new file mode 100644 index 0000000000..6fb96ec8f6 --- /dev/null +++ b/e2e/fixtures/originImage/Particle_particleRenderer-shape-transform.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa7a4bc8c57d966ad7ca49f6ea4f7177bb5d4e9c4c666f3c92ecc600c08b28f5 +size 23645 diff --git a/e2e/fixtures/originImage/Physics_physx-collision.jpg b/e2e/fixtures/originImage/Physics_physx-collision.jpg index b1e9becba9..eb5586184e 100644 --- a/e2e/fixtures/originImage/Physics_physx-collision.jpg +++ b/e2e/fixtures/originImage/Physics_physx-collision.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:55cd5d3035ef575743d7332317b571ea2403f9330c918a3c45e96dbbe853e164 -size 39366 +oid sha256:affc2095f2ab25f0f6e4f1726d9740348359a91de089655ef0fd314445690d45 +size 39157 diff --git a/e2e/fixtures/originImage/Physics_physx-customUrl.jpg b/e2e/fixtures/originImage/Physics_physx-customUrl.jpg index b1e9becba9..eb5586184e 100644 --- a/e2e/fixtures/originImage/Physics_physx-customUrl.jpg +++ b/e2e/fixtures/originImage/Physics_physx-customUrl.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:55cd5d3035ef575743d7332317b571ea2403f9330c918a3c45e96dbbe853e164 -size 39366 +oid sha256:affc2095f2ab25f0f6e4f1726d9740348359a91de089655ef0fd314445690d45 +size 39157 diff --git a/e2e/fixtures/originImage/Physics_physx-mesh-collider-data.jpg b/e2e/fixtures/originImage/Physics_physx-mesh-collider-data.jpg index 6bdbf7360d..b179343be2 100644 --- a/e2e/fixtures/originImage/Physics_physx-mesh-collider-data.jpg +++ b/e2e/fixtures/originImage/Physics_physx-mesh-collider-data.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e62ca32193b97c4a47ce59666a4f7754f0f20486e055ac3998de2e07aa4d1272 -size 128943 +oid sha256:a1e13bb7569d857bbf63e2f2fe9ab43d738d12c1fa5689b3d9659912930989df +size 143176 diff --git a/e2e/fixtures/originImage/Physics_physx-mesh-collider.jpg b/e2e/fixtures/originImage/Physics_physx-mesh-collider.jpg index d4f8cfcfab..73b706fafd 100644 --- a/e2e/fixtures/originImage/Physics_physx-mesh-collider.jpg +++ b/e2e/fixtures/originImage/Physics_physx-mesh-collider.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51034ffc3e36141a2ba60436cea65bc88d4f496e6727183bfb2088c50d8a5b5c -size 317407 +oid sha256:8b14f8617b1c7f262c4f25504334c2c22bf8257eb118b5588432020501dfd3f8 +size 325187 diff --git a/e2e/fixtures/originImage/PostProcess_postProcess-HDR-bloom-ACES.jpg b/e2e/fixtures/originImage/PostProcess_postProcess-HDR-bloom-ACES.jpg index cdadb736c5..0581a7bd04 100644 --- a/e2e/fixtures/originImage/PostProcess_postProcess-HDR-bloom-ACES.jpg +++ b/e2e/fixtures/originImage/PostProcess_postProcess-HDR-bloom-ACES.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4584782ac0bd2dad95cad5a479765ee5614fba1e9d4fda32f35a622c99edc0ec -size 347313 +oid sha256:27c95a9cfd8683a44aace9ef4b7741d9e3b97b12269a4913f2e862c550ed0b4c +size 343002 diff --git a/e2e/fixtures/originImage/PostProcess_postProcess-HDR-bloom-neutral.jpg b/e2e/fixtures/originImage/PostProcess_postProcess-HDR-bloom-neutral.jpg index e72e87ec3d..738e7c0437 100644 --- a/e2e/fixtures/originImage/PostProcess_postProcess-HDR-bloom-neutral.jpg +++ b/e2e/fixtures/originImage/PostProcess_postProcess-HDR-bloom-neutral.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40a3b2a3e56f99fd17ef6b52ccc4fc5c43326dc683daa62bfd6306f2bd7d06b8 -size 349197 +oid sha256:67e86f3281ea5afe23930a7ddd83491dbec40e955216ea17b467e56610e1da43 +size 358910 diff --git a/e2e/fixtures/originImage/PostProcess_postProcess-LDR-bloom-neutral.jpg b/e2e/fixtures/originImage/PostProcess_postProcess-LDR-bloom-neutral.jpg index 362caa1581..948c315ed7 100644 --- a/e2e/fixtures/originImage/PostProcess_postProcess-LDR-bloom-neutral.jpg +++ b/e2e/fixtures/originImage/PostProcess_postProcess-LDR-bloom-neutral.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c12c259962e13bab71bbb60834b20af2698d22c9047b9e9ffc17ca1fa222b79 -size 365831 +oid sha256:8253ece7e21253e3ab11e3a11f1ed7dd2fefc5865dfa2a6e94adf0935c98c57b +size 355494 diff --git a/e2e/fixtures/originImage/PostProcess_postProcess-customPass.jpg b/e2e/fixtures/originImage/PostProcess_postProcess-customPass.jpg index adb46a88a4..ca65c06cc0 100644 --- a/e2e/fixtures/originImage/PostProcess_postProcess-customPass.jpg +++ b/e2e/fixtures/originImage/PostProcess_postProcess-customPass.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:80e4e3ca30bb08c968d780e5359b4318d47fa88439055e81c643c4693ebff6d8 -size 148144 +oid sha256:f9651e3c572f25ad8ef58a1420eede1bc53202d5ee6d6a5d4d6817335e6272db +size 166863 diff --git a/e2e/fixtures/originImage/Texture_texture-hdr.jpg b/e2e/fixtures/originImage/Texture_texture-hdr.jpg index 3059526ff8..f3f7e08fc4 100644 --- a/e2e/fixtures/originImage/Texture_texture-hdr.jpg +++ b/e2e/fixtures/originImage/Texture_texture-hdr.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88f997da0044b46bee22c4bcd572bbcc65a33a79b49693db670f135ab6b7cc97 -size 300509 +oid sha256:d6d11bb5fd8c53298a7221e66ea997b61ebeb390a00a31b76f6b3da361f24cdf +size 293876 diff --git a/examples/src/paricle-emit-mesh.ts b/examples/src/paricle-emit-mesh.ts index 2d19604549..730e316257 100644 --- a/examples/src/paricle-emit-mesh.ts +++ b/examples/src/paricle-emit-mesh.ts @@ -122,7 +122,7 @@ async function main() { .load([ { url: "https://mdn.alipayobjects.com/huamei_b4l2if/afts/img/A*yu-DSb0surwAAAAAAAAAAAAADil6AQ/original", - type: AssetType.Texture2D + type: AssetType.Texture }, { url: "https://gw.alipayobjects.com/os/OasisHub/267000040/9994/%25E5%25BD%2592%25E6%25A1%25A3.gltf", diff --git a/examples/src/project-loader.ts b/examples/src/project-loader.ts index b283d2e857..e6b1b8000a 100644 --- a/examples/src/project-loader.ts +++ b/examples/src/project-loader.ts @@ -19,7 +19,7 @@ WebGLEngine.create({ engine.resourceManager .load({ - url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*4ZL9R5YX718AAAAAQMAAAAgAekp5AQ/project.json", + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*aE8_QotPNL4AAAAAQNAAAAgAekp5AQ/project.json", type: AssetType.Project }) .then(() => { diff --git a/packages/core/src/2d/atlas/FontAtlas.ts b/packages/core/src/2d/atlas/FontAtlas.ts index 36170f2f49..e84bfae4a9 100644 --- a/packages/core/src/2d/atlas/FontAtlas.ts +++ b/packages/core/src/2d/atlas/FontAtlas.ts @@ -21,7 +21,7 @@ export class FontAtlas extends ReferResource { super(engine); this.isGCIgnored = true; const format = engine._hardwareRenderer.isWebGL2 ? TextureFormat.R8 : TextureFormat.Alpha8; - const texture = new Texture2D(engine, 512, 512, format, false); + const texture = new Texture2D(engine, 512, 512, format, false, false); texture.filterMode = TextureFilterMode.Bilinear; texture.isGCIgnored = true; this.texture = texture; diff --git a/packages/core/src/asset/AssetType.ts b/packages/core/src/asset/AssetType.ts index 17ee100043..027f52aa7e 100644 --- a/packages/core/src/asset/AssetType.ts +++ b/packages/core/src/asset/AssetType.ts @@ -8,10 +8,8 @@ export enum AssetType { JSON = "JSON", /** ArrayBuffer. */ Buffer = "Buffer", - /** 2D Texture. */ - Texture2D = "Texture2D", - /** Cube Texture. */ - TextureCube = "TextureCube", + /** Texture. */ + Texture = "Texture", /** Material. */ Material = "Material", /** Shader. */ diff --git a/packages/core/src/audio/AudioManager.ts b/packages/core/src/audio/AudioManager.ts index ad93f5ed59..3aff4b19ee 100644 --- a/packages/core/src/audio/AudioManager.ts +++ b/packages/core/src/audio/AudioManager.ts @@ -15,7 +15,7 @@ export class AudioManager { * @returns A promise that resolves when the audio context is suspended */ static suspend(): Promise { - return AudioManager._context.suspend(); + return AudioManager.getContext().suspend(); } /** @@ -24,7 +24,7 @@ export class AudioManager { * @returns A promise that resolves when the audio context is resumed */ static resume(): Promise { - return (AudioManager._resumePromise ??= AudioManager._context + return (AudioManager._resumePromise ??= AudioManager.getContext() .resume() .then(() => { AudioManager._needsUserGestureResume = false; diff --git a/packages/core/src/particle/ParticleGenerator.ts b/packages/core/src/particle/ParticleGenerator.ts index 75c50b5cf6..343487db1e 100644 --- a/packages/core/src/particle/ParticleGenerator.ts +++ b/packages/core/src/particle/ParticleGenerator.ts @@ -33,6 +33,7 @@ import { ParticleCompositeCurve } from "./modules/ParticleCompositeCurve"; import { RotationOverLifetimeModule } from "./modules/RotationOverLifetimeModule"; import { SizeOverLifetimeModule } from "./modules/SizeOverLifetimeModule"; import { TextureSheetAnimationModule } from "./modules/TextureSheetAnimationModule"; +import { NoiseModule } from "./modules/NoiseModule"; import { VelocityOverLifetimeModule } from "./modules/VelocityOverLifetimeModule"; /** @@ -83,6 +84,9 @@ export class ParticleGenerator { /** Texture sheet animation module. */ @deepClone readonly textureSheetAnimation = new TextureSheetAnimationModule(this); + /** Noise module. */ + @deepClone + readonly noise: NoiseModule; /** @internal */ _currentParticleCount = 0; @@ -191,6 +195,7 @@ export class ParticleGenerator { this.forceOverLifetime = new ForceOverLifetimeModule(this); this.sizeOverLifetime = new SizeOverLifetimeModule(this); this.limitVelocityOverLifetime = new LimitVelocityOverLifetimeModule(this); + this.noise = new NoiseModule(this); this.emission.enabled = true; } @@ -614,6 +619,7 @@ export class ParticleGenerator { this.sizeOverLifetime._updateShaderData(shaderData); this.rotationOverLifetime._updateShaderData(shaderData); this.colorOverLifetime._updateShaderData(shaderData); + this.noise._updateShaderData(shaderData); } /** @@ -629,20 +635,23 @@ export class ParticleGenerator { this.limitVelocityOverLifetime._resetRandomSeed(seed); this.rotationOverLifetime._resetRandomSeed(seed); this.colorOverLifetime._resetRandomSeed(seed); + this.noise._resetRandomSeed(seed); } /** * @internal */ - _setTransformFeedback(enabled: boolean): void { - this._useTransformFeedback = enabled; + _setTransformFeedback(): void { + const needed = this.limitVelocityOverLifetime.enabled || this.noise.enabled; + if (needed === this._useTransformFeedback) return; + this._useTransformFeedback = needed; // Switching TF mode invalidates all active particle state: feedback buffers and instance // buffer layout are incompatible between the two paths. Clear rather than show a one-frame // jump; new particles will fill in naturally from the next emit cycle. this._clearActiveParticles(); - if (enabled) { + if (needed) { if (!this._feedbackSimulator) { this._feedbackSimulator = new ParticleTransformFeedbackSimulator(this._renderer.engine); } @@ -702,9 +711,7 @@ export class ParticleGenerator { * @internal */ _cloneTo(target: ParticleGenerator): void { - if (target.limitVelocityOverLifetime.enabled) { - target._setTransformFeedback(true); - } + target._setTransformFeedback(); } /** @@ -912,12 +919,8 @@ export class ParticleGenerator { // Start rotation const { _startRotationRand: startRotationRand, flipRotation } = main; - let isFlip = flipRotation > startRotationRand.random(); + const isFlip = flipRotation > startRotationRand.random(); - // @todo:None-Mesh mode should inverse the rotation, maybe should unify it - if (this._renderer.renderMode !== ParticleRenderMode.Mesh) { - isFlip = !isFlip; - } const rotationZ = main.startRotationZ.evaluate(undefined, startRotationRand.random()); if (main.startRotation3D) { const rotationX = main.startRotationX.evaluate(undefined, startRotationRand.random()); @@ -947,7 +950,9 @@ export class ParticleGenerator { instanceVertices[offset + 20] = colorOverLifetime._colorGradientRand.random(); } - // instanceVertices[offset + 21] = rand.random(); + if (this.noise.enabled) { + instanceVertices[offset + 21] = this.noise._noiseRand.random(); + } const rotationOverLifetime = this.rotationOverLifetime; if (rotationOverLifetime.enabled && rotationOverLifetime.rotationZ.mode === ParticleCurveMode.TwoConstants) { @@ -1224,7 +1229,7 @@ export class ParticleGenerator { // StartSpeed's impact const { shape } = this.emission; if (shape?.enabled) { - shape._getPositionRange(min, max); + shape._getPositionRange(bounds); shape._getDirectionRange(directionMin, directionMax); } else { min.set(0, 0, 0); @@ -1390,6 +1395,22 @@ export class ParticleGenerator { min.add(worldOffsetMin); max.add(worldOffsetMax); + // Noise module impact: noise output is normalized to [-1, 1], + // max displacement = |strength_max| + const { noise } = this; + if (noise.enabled) { + let noiseMaxX: number, noiseMaxY: number, noiseMaxZ: number; + if (noise.separateAxes) { + noiseMaxX = Math.abs(noise.strengthX._getMax()); + noiseMaxY = Math.abs(noise.strengthY._getMax()); + noiseMaxZ = Math.abs(noise.strengthZ._getMax()); + } else { + noiseMaxX = noiseMaxY = noiseMaxZ = Math.abs(noise.strengthX._getMax()); + } + min.set(min.x - noiseMaxX, min.y - noiseMaxY, min.z - noiseMaxZ); + max.set(max.x + noiseMaxX, max.y + noiseMaxY, max.z + noiseMaxZ); + } + min.add(worldPosition); max.add(worldPosition); } diff --git a/packages/core/src/particle/enums/ParticleRandomSubSeeds.ts b/packages/core/src/particle/enums/ParticleRandomSubSeeds.ts index 2e344e8115..c7b17a46bb 100644 --- a/packages/core/src/particle/enums/ParticleRandomSubSeeds.ts +++ b/packages/core/src/particle/enums/ParticleRandomSubSeeds.ts @@ -18,5 +18,6 @@ export enum ParticleRandomSubSeeds { Shape = 0xaf502044, GravityModifier = 0xa47b8c4d, ForceOverLifetime = 0xe6fb937c, - LimitVelocityOverLifetime = 0xb5a21f7e + LimitVelocityOverLifetime = 0xb5a21f7e, + Noise = 0xf4b2c8a1 } diff --git a/packages/core/src/particle/enums/ParticleScaleMode.ts b/packages/core/src/particle/enums/ParticleScaleMode.ts index 0f780ef943..b3ec3a3fe6 100644 --- a/packages/core/src/particle/enums/ParticleScaleMode.ts +++ b/packages/core/src/particle/enums/ParticleScaleMode.ts @@ -2,10 +2,10 @@ * Control how Particle Generator apply transform scale. */ export enum ParticleScaleMode { - /** Scale the Particle Generator using the entire transform hierarchy. */ - Hierarchy, - /** Scale the Particle Generator using only its own transform scale. (Ignores parent scale). */ + /** Scale the Particle Generator using the world scale, including all parent transforms. */ + World, + /** Scale the Particle Generator using only its own transform scale, ignoring parent scale. */ Local, - /** Only apply transform scale to the shape component, which controls where particles are spawned, but does not affect their size or movement. */ - World + /** Scale only the emitter shape positions; particle size and movement are unaffected. */ + Shape } diff --git a/packages/core/src/particle/index.ts b/packages/core/src/particle/index.ts index e43f1ae4ed..1cf0a48829 100644 --- a/packages/core/src/particle/index.ts +++ b/packages/core/src/particle/index.ts @@ -20,4 +20,5 @@ export { SizeOverLifetimeModule } from "./modules/SizeOverLifetimeModule"; export { TextureSheetAnimationModule } from "./modules/TextureSheetAnimationModule"; export { VelocityOverLifetimeModule } from "./modules/VelocityOverLifetimeModule"; export { LimitVelocityOverLifetimeModule } from "./modules/LimitVelocityOverLifetimeModule"; +export { NoiseModule } from "./modules/NoiseModule"; export * from "./modules/shape/index"; diff --git a/packages/core/src/particle/modules/Burst.ts b/packages/core/src/particle/modules/Burst.ts index be046cb583..a444381dc2 100644 --- a/packages/core/src/particle/modules/Burst.ts +++ b/packages/core/src/particle/modules/Burst.ts @@ -9,13 +9,49 @@ export class Burst { @deepClone public count: ParticleCompositeCurve; + private _cycles: number; + private _repeatInterval: number; + + /** + * Number of times to repeat the burst. + */ + get cycles(): number { + return this._cycles; + } + + set cycles(value: number) { + this._cycles = Math.max(value, 1); + } + + /** + * Time interval between each repeated burst. + */ + get repeatInterval(): number { + return this._repeatInterval; + } + + set repeatInterval(value: number) { + this._repeatInterval = Math.max(value, 0.01); + } + + /** + * Create a single-shot burst. + * @param time - Time to emit the burst + * @param count - Count of particles to emit + */ + constructor(time: number, count: ParticleCompositeCurve); /** - * Create burst object. + * Create a repeated burst. * @param time - Time to emit the burst * @param count - Count of particles to emit + * @param cycles - Number of times to repeat the burst + * @param repeatInterval - Time interval between each repeated burst */ - constructor(time: number, count: ParticleCompositeCurve) { + constructor(time: number, count: ParticleCompositeCurve, cycles: number, repeatInterval: number); + constructor(time: number, count: ParticleCompositeCurve, cycles?: number, repeatInterval?: number) { this.time = time; this.count = count; + this._cycles = Math.max(cycles ?? 1, 1); + this._repeatInterval = Math.max(repeatInterval ?? 0.01, 0.01); } } diff --git a/packages/core/src/particle/modules/EmissionModule.ts b/packages/core/src/particle/modules/EmissionModule.ts index 108832c38c..12227b6642 100644 --- a/packages/core/src/particle/modules/EmissionModule.ts +++ b/packages/core/src/particle/modules/EmissionModule.ts @@ -1,4 +1,4 @@ -import { Rand } from "@galacean/engine-math"; +import { MathUtil, Rand } from "@galacean/engine-math"; import { deepClone, ignoreClone } from "../../clone/CloneManager"; import { ShaderMacro } from "../../shader/ShaderMacro"; import { ParticleRandomSubSeeds } from "../enums/ParticleRandomSubSeeds"; @@ -32,7 +32,7 @@ export class EmissionModule extends ParticleGeneratorModule { @deepClone private _bursts: Burst[] = []; - private _currentBurstIndex: number = 0; + private _currentBurstIndex = 0; @ignoreClone private _burstRand: Rand = new Rand(0, ParticleRandomSubSeeds.Burst); @@ -191,34 +191,52 @@ export class EmissionModule extends ParticleGeneratorModule { this._emitBySubBurst(middleTime, playTime, duration); } else { - this._emitBySubBurst(lastPlayTime, playTime, duration); + if (lastPlayTime < duration) { + this._emitBySubBurst(lastPlayTime, Math.min(playTime, duration), duration); + } } } private _emitBySubBurst(lastPlayTime: number, playTime: number, duration: number): void { - const generator = this._generator; - const rand = this._burstRand; - const bursts = this.bursts; - - // Calculate the relative time of the burst + const { _generator: generator, _burstRand: rand, bursts } = this; const baseTime = Math.floor(lastPlayTime / duration) * duration; const startTime = lastPlayTime % duration; const endTime = startTime + (playTime - lastPlayTime); + let pendingIndex = -1; let index = this._currentBurstIndex; for (let n = bursts.length; index < n; index++) { const burst = bursts[index]; const burstTime = burst.time; + if (burstTime >= endTime) break; - if (burstTime > endTime) { - break; - } - - if (burstTime >= startTime) { - const count = burst.count.evaluate(undefined, rand.random()); - generator._emit(baseTime + burstTime, count); + const { cycles, repeatInterval } = burst; + if (cycles === 1) { + if (burstTime >= startTime) { + generator._emit(baseTime + burstTime, burst.count.evaluate(undefined, rand.random())); + } + } else { + const maxCycles = cycles === Infinity ? Math.ceil((duration - burstTime) / repeatInterval) : cycles; + + // Absorb float drift: (startTime - burstTime) / repeatInterval may land at cycle + 1e-15 + // when it should be exactly cycle, and ceil would then skip ahead to cycle + 1. + const tolerance = MathUtil.zeroTolerance; + const lastCycle = Math.ceil((endTime - burstTime) / repeatInterval - tolerance) - 1; + const first = Math.max(0, Math.ceil((startTime - burstTime) / repeatInterval - tolerance)); + const last = Math.min(maxCycles - 1, lastCycle); + for (let c = first; c <= last; c++) { + const effectiveTime = burstTime + c * repeatInterval; + if (effectiveTime >= duration) break; + generator._emit(baseTime + effectiveTime, burst.count.evaluate(undefined, rand.random())); + } + + // `_currentBurstIndex` caches next frame's scan start, so only the earliest unfinished + // burst can be the entry point — skipping past it would drop its remaining cycles + if (pendingIndex < 0 && lastCycle < maxCycles - 1) { + pendingIndex = index; + } } } - this._currentBurstIndex = index; + this._currentBurstIndex = pendingIndex >= 0 ? pendingIndex : index; } } diff --git a/packages/core/src/particle/modules/LimitVelocityOverLifetimeModule.ts b/packages/core/src/particle/modules/LimitVelocityOverLifetimeModule.ts index 650e75f1ba..9d6a91bc54 100644 --- a/packages/core/src/particle/modules/LimitVelocityOverLifetimeModule.ts +++ b/packages/core/src/particle/modules/LimitVelocityOverLifetimeModule.ts @@ -241,7 +241,7 @@ export class LimitVelocityOverLifetimeModule extends ParticleGeneratorModule { return; } this._enabled = value; - this._generator._setTransformFeedback(value); + this._generator._setTransformFeedback(); this._generator._renderer._onGeneratorParamsChanged(); } } diff --git a/packages/core/src/particle/modules/MainModule.ts b/packages/core/src/particle/modules/MainModule.ts index 9318cbdb4b..d17c156d9a 100644 --- a/packages/core/src/particle/modules/MainModule.ts +++ b/packages/core/src/particle/modules/MainModule.ts @@ -277,8 +277,8 @@ export class MainModule implements ICustomClone { _getPositionScale(): Vector3 { const transform = this._generator._renderer.entity.transform; switch (this.scalingMode) { - case ParticleScaleMode.Hierarchy: case ParticleScaleMode.World: + case ParticleScaleMode.Shape: return transform.lossyWorldScale; case ParticleScaleMode.Local: return transform.scale; @@ -306,7 +306,7 @@ export class MainModule implements ICustomClone { } switch (this.scalingMode) { - case ParticleScaleMode.Hierarchy: + case ParticleScaleMode.World: var scale = transform.lossyWorldScale; shaderData.setVector3(MainModule._positionScale, scale); shaderData.setVector3(MainModule._sizeScale, scale); @@ -316,7 +316,7 @@ export class MainModule implements ICustomClone { shaderData.setVector3(MainModule._positionScale, scale); shaderData.setVector3(MainModule._sizeScale, scale); break; - case ParticleScaleMode.World: + case ParticleScaleMode.Shape: shaderData.setVector3(MainModule._positionScale, transform.lossyWorldScale); shaderData.setVector3(MainModule._sizeScale, MainModule._vector3One); break; diff --git a/packages/core/src/particle/modules/NoiseModule.ts b/packages/core/src/particle/modules/NoiseModule.ts new file mode 100644 index 0000000000..153a40b5b9 --- /dev/null +++ b/packages/core/src/particle/modules/NoiseModule.ts @@ -0,0 +1,334 @@ +import { Rand, Vector3, Vector4 } from "@galacean/engine-math"; +import { deepClone, ignoreClone } from "../../clone/CloneManager"; +import { ShaderData, ShaderMacro, ShaderProperty } from "../../shader"; +import { ParticleGenerator } from "../ParticleGenerator"; +import { ParticleCurveMode } from "../enums/ParticleCurveMode"; +import { ParticleRandomSubSeeds } from "../enums/ParticleRandomSubSeeds"; +import { ParticleCompositeCurve } from "./ParticleCompositeCurve"; +import { ParticleGeneratorModule } from "./ParticleGeneratorModule"; + +/** + * Noise module for particle system. + * Adds simplex noise-based turbulence displacement to particles. + */ +export class NoiseModule extends ParticleGeneratorModule { + static readonly _enabledMacro = ShaderMacro.getByName("RENDERER_NOISE_MODULE_ENABLED"); + static readonly _strengthCurveMacro = ShaderMacro.getByName("RENDERER_NOISE_STRENGTH_CURVE"); + static readonly _strengthIsRandomTwoMacro = ShaderMacro.getByName("RENDERER_NOISE_STRENGTH_IS_RANDOM_TWO"); + static readonly _separateAxesMacro = ShaderMacro.getByName("RENDERER_NOISE_IS_SEPARATE"); + + static readonly _noiseProperty = ShaderProperty.getByName("renderer_NoiseParams"); + static readonly _noiseOctaveProperty = ShaderProperty.getByName("renderer_NoiseOctaveParams"); + static readonly _strengthMinConstProperty = ShaderProperty.getByName("renderer_NoiseStrengthMinConst"); + static readonly _strengthMaxCurveXProperty = ShaderProperty.getByName("renderer_NoiseStrengthMaxCurveX"); + static readonly _strengthMaxCurveYProperty = ShaderProperty.getByName("renderer_NoiseStrengthMaxCurveY"); + static readonly _strengthMaxCurveZProperty = ShaderProperty.getByName("renderer_NoiseStrengthMaxCurveZ"); + static readonly _strengthMinCurveXProperty = ShaderProperty.getByName("renderer_NoiseStrengthMinCurveX"); + static readonly _strengthMinCurveYProperty = ShaderProperty.getByName("renderer_NoiseStrengthMinCurveY"); + static readonly _strengthMinCurveZProperty = ShaderProperty.getByName("renderer_NoiseStrengthMinCurveZ"); + + @ignoreClone + private _enabledModuleMacro: ShaderMacro; + @ignoreClone + private _strengthCurveModeMacro: ShaderMacro; + @ignoreClone + private _strengthIsRandomTwoModeMacro: ShaderMacro; + @ignoreClone + private _separateAxesModeMacro: ShaderMacro; + + /** @internal */ + @ignoreClone + _noiseRand = new Rand(0, ParticleRandomSubSeeds.Noise); + + @ignoreClone + private _noiseParams = new Vector4(); + @ignoreClone + private _noiseOctaveParams = new Vector4(); + @ignoreClone + private _strengthMinConst = new Vector3(); + + @deepClone + private _strengthX: ParticleCompositeCurve; + @deepClone + private _strengthY: ParticleCompositeCurve; + @deepClone + private _strengthZ: ParticleCompositeCurve; + private _scrollSpeed = 0; + private _separateAxes = false; + private _frequency = 0.5; + private _octaveCount = 1; + private _octaveIntensityMultiplier = 0.5; + private _octaveFrequencyMultiplier = 2.0; + + /** + * Specifies whether the strength is separate on each axis, when disabled, only `strength` is used. + */ + get separateAxes(): boolean { + return this._separateAxes; + } + + set separateAxes(value: boolean) { + if (value !== this._separateAxes) { + this._separateAxes = value; + this._generator._renderer._onGeneratorParamsChanged(); + } + } + + /** + * Noise strength. When `separateAxes` is disabled, applies to all axes. + * When `separateAxes` is enabled, applies only to x axis. + */ + get strengthX(): ParticleCompositeCurve { + return this._strengthX; + } + + set strengthX(value: ParticleCompositeCurve) { + const lastValue = this._strengthX; + if (value !== lastValue) { + this._strengthX = value; + this._onCompositeCurveChange(lastValue, value); + } + } + + /** + * Noise strength for y axis, used when `separateAxes` is enabled. + */ + get strengthY(): ParticleCompositeCurve { + return this._strengthY; + } + + set strengthY(value: ParticleCompositeCurve) { + const lastValue = this._strengthY; + if (value !== lastValue) { + this._strengthY = value; + this._onCompositeCurveChange(lastValue, value); + } + } + + /** + * Noise strength for z axis, used when `separateAxes` is enabled. + */ + get strengthZ(): ParticleCompositeCurve { + return this._strengthZ; + } + + set strengthZ(value: ParticleCompositeCurve) { + const lastValue = this._strengthZ; + if (value !== lastValue) { + this._strengthZ = value; + this._onCompositeCurveChange(lastValue, value); + } + } + + /** + * Noise spatial frequency. + */ + get frequency(): number { + return this._frequency; + } + + set frequency(value: number) { + value = Math.max(1e-6, value); + if (value !== this._frequency) { + this._frequency = value; + this._generator._renderer._onGeneratorParamsChanged(); + } + } + + /** + * Noise field scroll speed over time. + */ + get scrollSpeed(): number { + return this._scrollSpeed; + } + + set scrollSpeed(value: number) { + if (value !== this._scrollSpeed) { + this._scrollSpeed = value; + this._generator._renderer._onGeneratorParamsChanged(); + } + } + + /** + * Number of noise octave layers (1-3). + */ + get octaveCount(): number { + return this._octaveCount; + } + + set octaveCount(value: number) { + value = Math.max(1, Math.min(3, Math.floor(value))); + if (value !== this._octaveCount) { + this._octaveCount = value; + this._generator._renderer._onGeneratorParamsChanged(); + } + } + + /** + * Intensity multiplier for each successive octave, only effective when `octaveCount` > 1. + * Each layer's contribution is scaled by this factor relative to the previous layer, range [0, 1]. + */ + get octaveIntensityMultiplier(): number { + return this._octaveIntensityMultiplier; + } + + set octaveIntensityMultiplier(value: number) { + value = Math.max(0, Math.min(1, value)); + if (value !== this._octaveIntensityMultiplier) { + this._octaveIntensityMultiplier = value; + this._generator._renderer._onGeneratorParamsChanged(); + } + } + + /** + * Frequency multiplier for each successive octave, only effective when `octaveCount` > 1. + * Each layer samples at this multiple of the previous layer's frequency, range [1, 4]. + */ + get octaveFrequencyMultiplier(): number { + return this._octaveFrequencyMultiplier; + } + + set octaveFrequencyMultiplier(value: number) { + value = Math.max(1, Math.min(4, value)); + if (value !== this._octaveFrequencyMultiplier) { + this._octaveFrequencyMultiplier = value; + this._generator._renderer._onGeneratorParamsChanged(); + } + } + + override get enabled(): boolean { + return this._enabled; + } + + override set enabled(value: boolean) { + if (value !== this._enabled) { + if (value && !this._generator._renderer.engine._hardwareRenderer.isWebGL2) { + return; + } + this._enabled = value; + this._generator._setTransformFeedback(); + this._generator._renderer._onGeneratorParamsChanged(); + } + } + + constructor(generator: ParticleGenerator) { + super(generator); + + this.strengthX = new ParticleCompositeCurve(1); + this.strengthY = new ParticleCompositeCurve(1); + this.strengthZ = new ParticleCompositeCurve(1); + } + + /** + * @internal + */ + _updateShaderData(shaderData: ShaderData): void { + let enabledMacro = null; + let strengthCurveMacro = null; + let strengthIsRandomTwoMacro = null; + let separateAxesMacro = null; + if (this.enabled) { + enabledMacro = NoiseModule._enabledMacro; + + const strengthX = this._strengthX; + const strengthY = this._strengthY; + const strengthZ = this._strengthZ; + const separateAxes = this._separateAxes; + + // Determine strength curve mode (following SOL pattern) + const isRandomCurveMode = separateAxes + ? strengthX.mode === ParticleCurveMode.TwoCurves && + strengthY.mode === ParticleCurveMode.TwoCurves && + strengthZ.mode === ParticleCurveMode.TwoCurves + : strengthX.mode === ParticleCurveMode.TwoCurves; + + const isCurveMode = + isRandomCurveMode || + (separateAxes + ? strengthX.mode === ParticleCurveMode.Curve && + strengthY.mode === ParticleCurveMode.Curve && + strengthZ.mode === ParticleCurveMode.Curve + : strengthX.mode === ParticleCurveMode.Curve); + + const isRandomConstMode = separateAxes + ? strengthX.mode === ParticleCurveMode.TwoConstants && + strengthY.mode === ParticleCurveMode.TwoConstants && + strengthZ.mode === ParticleCurveMode.TwoConstants + : strengthX.mode === ParticleCurveMode.TwoConstants; + + // noiseParams.w = frequency (always needed) + const noiseParams = this._noiseParams; + + if (isCurveMode) { + // Curve/TwoCurves: encode curve data as float arrays + shaderData.setFloatArray(NoiseModule._strengthMaxCurveXProperty, strengthX.curveMax._getTypeArray()); + if (separateAxes) { + shaderData.setFloatArray(NoiseModule._strengthMaxCurveYProperty, strengthY.curveMax._getTypeArray()); + shaderData.setFloatArray(NoiseModule._strengthMaxCurveZProperty, strengthZ.curveMax._getTypeArray()); + } + if (isRandomCurveMode) { + shaderData.setFloatArray(NoiseModule._strengthMinCurveXProperty, strengthX.curveMin._getTypeArray()); + if (separateAxes) { + shaderData.setFloatArray(NoiseModule._strengthMinCurveYProperty, strengthY.curveMin._getTypeArray()); + shaderData.setFloatArray(NoiseModule._strengthMinCurveZProperty, strengthZ.curveMin._getTypeArray()); + } + strengthIsRandomTwoMacro = NoiseModule._strengthIsRandomTwoMacro; + } + strengthCurveMacro = NoiseModule._strengthCurveMacro; + + // xyz unused in curve mode, just set frequency + noiseParams.set(0, 0, 0, this._frequency); + } else { + // Constant/TwoConstants: pack strength into noiseParams.xyz + if (separateAxes) { + noiseParams.set(strengthX.constantMax, strengthY.constantMax, strengthZ.constantMax, this._frequency); + } else { + const s = strengthX.constantMax; + noiseParams.set(s, s, s, this._frequency); + } + + if (isRandomConstMode) { + const minConst = this._strengthMinConst; + if (separateAxes) { + minConst.set(strengthX.constantMin, strengthY.constantMin, strengthZ.constantMin); + } else { + const sMin = strengthX.constantMin; + minConst.set(sMin, sMin, sMin); + } + shaderData.setVector3(NoiseModule._strengthMinConstProperty, minConst); + strengthIsRandomTwoMacro = NoiseModule._strengthIsRandomTwoMacro; + } + } + shaderData.setVector4(NoiseModule._noiseProperty, noiseParams); + + if (separateAxes) { + separateAxesMacro = NoiseModule._separateAxesMacro; + } + + const noiseOctaveParams = this._noiseOctaveParams; + noiseOctaveParams.set( + this._scrollSpeed, + this._octaveCount, + this._octaveIntensityMultiplier, + this._octaveFrequencyMultiplier + ); + shaderData.setVector4(NoiseModule._noiseOctaveProperty, noiseOctaveParams); + } + + this._enabledModuleMacro = this._enableMacro(shaderData, this._enabledModuleMacro, enabledMacro); + this._strengthCurveModeMacro = this._enableMacro(shaderData, this._strengthCurveModeMacro, strengthCurveMacro); + this._strengthIsRandomTwoModeMacro = this._enableMacro( + shaderData, + this._strengthIsRandomTwoModeMacro, + strengthIsRandomTwoMacro + ); + this._separateAxesModeMacro = this._enableMacro(shaderData, this._separateAxesModeMacro, separateAxesMacro); + } + + /** + * @internal + */ + _resetRandomSeed(seed: number): void { + this._noiseRand.reset(seed, ParticleRandomSubSeeds.Noise); + } +} diff --git a/packages/core/src/particle/modules/shape/BaseShape.ts b/packages/core/src/particle/modules/shape/BaseShape.ts index 87347b3e0d..9e00891b59 100644 --- a/packages/core/src/particle/modules/shape/BaseShape.ts +++ b/packages/core/src/particle/modules/shape/BaseShape.ts @@ -1,12 +1,21 @@ -import { Rand, Vector3 } from "@galacean/engine-math"; +import { BoundingBox, MathUtil, Matrix, Quaternion, Rand, Vector2, Vector3 } from "@galacean/engine-math"; import { ParticleShapeType } from "./enums/ParticleShapeType"; import { UpdateFlagManager } from "../../../UpdateFlagManager"; -import { ignoreClone } from "../../../clone/CloneManager"; +import { deepClone, ignoreClone } from "../../../clone/CloneManager"; /** * Base class for all particle shapes. */ export abstract class BaseShape { + /** @internal */ + static _tempVector20 = new Vector2(); + /** @internal */ + static _tempVector21 = new Vector2(); + /** @internal */ + static _tempVector30 = new Vector3(); + /** @internal */ + static _tempVector31 = new Vector3(); + private static _tempQuaternion = new Quaternion(); /** The type of shape to emit particles from. */ abstract readonly shapeType: ParticleShapeType; @@ -16,6 +25,19 @@ export abstract class BaseShape { private _enabled = true; private _randomDirectionAmount = 0; + @deepClone + private _position = new Vector3(0, 0, 0); + @deepClone + private _rotation = new Vector3(0, 0, 0); + @deepClone + private _scale = new Vector3(1, 1, 1); + @ignoreClone + private _matrix = new Matrix(); + @ignoreClone + private _transformDirty = false; + @ignoreClone + private _hasShapeTransform = false; + /** * Specifies whether the ShapeModule is enabled or disabled. */ @@ -44,6 +66,54 @@ export abstract class BaseShape { } } + /** + * Apply a local position offset to the shape. + */ + get position(): Vector3 { + return this._position; + } + + set position(value: Vector3) { + if (value !== this._position) { + this._position.copyFrom(value); + } + } + + /** + * Apply a local rotation to the shape, specified as euler angles in degrees. + */ + get rotation(): Vector3 { + return this._rotation; + } + + set rotation(value: Vector3) { + if (value !== this._rotation) { + this._rotation.copyFrom(value); + } + } + + /** + * Apply a local scale to the shape. + */ + get scale(): Vector3 { + return this._scale; + } + + set scale(value: Vector3) { + if (value !== this._scale) { + this._scale.copyFrom(value); + } + } + + constructor() { + // @ts-ignore + this._position._onValueChanged = this._onTransformChanged; + // @ts-ignore + this._rotation._onValueChanged = this._onTransformChanged; + // @ts-ignore + this._scale._onValueChanged = this._onTransformChanged; + } + /** * @internal */ @@ -61,15 +131,92 @@ export abstract class BaseShape { /** * @internal */ - abstract _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void; + _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { + this._generateLocalPositionAndDirection(rand, emitTime, position, direction); + if (this._hasShapeTransform) { + const matrix = this._getMatrix(); + Vector3.transformToVec3(position, matrix, position); + Vector3.transformNormal(direction, matrix, direction); + direction.normalize(); + } + } /** * @internal */ - abstract _getDirectionRange(outMin: Vector3, outMax: Vector3): void; + _getPositionRange(bounds: BoundingBox): void { + this._getLocalPositionRange(bounds.min, bounds.max); + if (this._hasShapeTransform) { + BoundingBox.transform(bounds, this._getMatrix(), bounds); + } + } /** * @internal */ - abstract _getPositionRange(outMin: Vector3, outMax: Vector3): void; + _getDirectionRange(outMin: Vector3, outMax: Vector3): void { + this._getLocalDirectionRange(outMin, outMax); + if (this._hasShapeTransform) { + this._transformDirectionRange(outMin, outMax); + } + } + + protected abstract _generateLocalPositionAndDirection( + rand: Rand, + emitTime: number, + position: Vector3, + direction: Vector3 + ): void; + + protected abstract _getLocalPositionRange(outMin: Vector3, outMax: Vector3): void; + + protected abstract _getLocalDirectionRange(outMin: Vector3, outMax: Vector3): void; + + @ignoreClone + protected _onTransformChanged = (): void => { + this._transformDirty = true; + const { _position: p, _rotation: r, _scale: s } = this; + this._hasShapeTransform = + p.x !== 0 || p.y !== 0 || p.z !== 0 || r.x !== 0 || r.y !== 0 || r.z !== 0 || s.x !== 1 || s.y !== 1 || s.z !== 1; + this._updateManager.dispatch(); + }; + + private _getMatrix(): Matrix { + if (this._transformDirty) { + const { _rotation: r } = this; + const q = BaseShape._tempQuaternion; + Quaternion.rotationEuler( + MathUtil.degreeToRadian(r.x), + MathUtil.degreeToRadian(r.y), + MathUtil.degreeToRadian(r.z), + q + ); + Matrix.affineTransformation(this._scale, q, this._position, this._matrix); + this._transformDirty = false; + } + return this._matrix; + } + + // Arvo min/max method without translation, only apply RS part of the matrix + private _transformDirectionRange(outMin: Vector3, outMax: Vector3): void { + const e = this._getMatrix().elements; + const { x: minX, y: minY, z: minZ } = outMin; + const { x: maxX, y: maxY, z: maxZ } = outMax; + // prettier-ignore + const e0 = e[0], e1 = e[1], e2 = e[2], + e4 = e[4], e5 = e[5], e6 = e[6], + e8 = e[8], e9 = e[9], e10 = e[10]; + + outMin.set( + (e0 > 0 ? e0 * minX : e0 * maxX) + (e4 > 0 ? e4 * minY : e4 * maxY) + (e8 > 0 ? e8 * minZ : e8 * maxZ), + (e1 > 0 ? e1 * minX : e1 * maxX) + (e5 > 0 ? e5 * minY : e5 * maxY) + (e9 > 0 ? e9 * minZ : e9 * maxZ), + (e2 > 0 ? e2 * minX : e2 * maxX) + (e6 > 0 ? e6 * minY : e6 * maxY) + (e10 > 0 ? e10 * minZ : e10 * maxZ) + ); + + outMax.set( + (e0 > 0 ? e0 * maxX : e0 * minX) + (e4 > 0 ? e4 * maxY : e4 * minY) + (e8 > 0 ? e8 * maxZ : e8 * minZ), + (e1 > 0 ? e1 * maxX : e1 * minX) + (e5 > 0 ? e5 * maxY : e5 * minY) + (e9 > 0 ? e9 * maxZ : e9 * minZ), + (e2 > 0 ? e2 * maxX : e2 * minX) + (e6 > 0 ? e6 * maxY : e6 * minY) + (e10 > 0 ? e10 * maxZ : e10 * minZ) + ); + } } diff --git a/packages/core/src/particle/modules/shape/BoxShape.ts b/packages/core/src/particle/modules/shape/BoxShape.ts index 5d23f54c81..7ad762e2e8 100644 --- a/packages/core/src/particle/modules/shape/BoxShape.ts +++ b/packages/core/src/particle/modules/shape/BoxShape.ts @@ -8,8 +8,6 @@ import { ParticleShapeType } from "./enums/ParticleShapeType"; * Particle shape that emits particles from a box. */ export class BoxShape extends BaseShape { - private static _tempVector30 = new Vector3(); - readonly shapeType = ParticleShapeType.Box; @deepClone @@ -37,11 +35,11 @@ export class BoxShape extends BaseShape { /** * @internal */ - _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { + _generateLocalPositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { ShapeUtils._randomPointInsideHalfUnitBox(position, rand); position.multiply(this.size); - const defaultDirection = BoxShape._tempVector30; + const defaultDirection = BaseShape._tempVector30; defaultDirection.set(0.0, 0.0, -1.0); ShapeUtils._randomPointUnitSphere(direction, rand); Vector3.lerp(defaultDirection, direction, this.randomDirectionAmount, direction); @@ -50,7 +48,7 @@ export class BoxShape extends BaseShape { /** * @internal */ - _getDirectionRange(outMin: Vector3, outMax: Vector3): void { + _getLocalDirectionRange(outMin: Vector3, outMax: Vector3): void { const radian = Math.PI * this.randomDirectionAmount; if (this.randomDirectionAmount < 0.5) { @@ -67,7 +65,7 @@ export class BoxShape extends BaseShape { /** * @internal */ - _getPositionRange(outMin: Vector3, outMax: Vector3): void { + _getLocalPositionRange(outMin: Vector3, outMax: Vector3): void { const { x, y, z } = this._size; outMin.set(-x * 0.5, -y * 0.5, -z * 0.5); outMax.set(x * 0.5, y * 0.5, z * 0.5); diff --git a/packages/core/src/particle/modules/shape/CircleShape.ts b/packages/core/src/particle/modules/shape/CircleShape.ts index 75e18e8508..cc9156f934 100644 --- a/packages/core/src/particle/modules/shape/CircleShape.ts +++ b/packages/core/src/particle/modules/shape/CircleShape.ts @@ -1,4 +1,4 @@ -import { MathUtil, Rand, Vector2, Vector3 } from "@galacean/engine-math"; +import { MathUtil, Rand, Vector3 } from "@galacean/engine-math"; import { BaseShape } from "./BaseShape"; import { ShapeUtils } from "./ShapeUtils"; import { ParticleShapeArcMode } from "./enums/ParticleShapeArcMode"; @@ -8,8 +8,6 @@ import { ParticleShapeType } from "./enums/ParticleShapeType"; * Particle shape that emits particles from a circle. */ export class CircleShape extends BaseShape { - private static _tempPositionPoint = new Vector2(); - readonly shapeType = ParticleShapeType.Circle; private _radius = 1.0; @@ -76,8 +74,8 @@ export class CircleShape extends BaseShape { /** * @internal */ - _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { - const positionPoint = CircleShape._tempPositionPoint; + _generateLocalPositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { + const positionPoint = BaseShape._tempVector20; switch (this.arcMode) { case ParticleShapeArcMode.Loop: @@ -101,7 +99,7 @@ export class CircleShape extends BaseShape { /** * @internal */ - _getDirectionRange(outMin: Vector3, outMax: Vector3): void { + _getLocalDirectionRange(outMin: Vector3, outMax: Vector3): void { const randomDirZ = this.randomDirectionAmount > 0.5 ? 1 : Math.sin(this.randomDirectionAmount * Math.PI); const randomDegreeOnXY = 0.5 * (360 - this._arc) * this.randomDirectionAmount; const randomDirY = randomDegreeOnXY > 90 ? -1 : -Math.sin(randomDegreeOnXY); @@ -111,7 +109,7 @@ export class CircleShape extends BaseShape { /** * @internal */ - _getPositionRange(outMin: Vector3, outMax: Vector3): void { + _getLocalPositionRange(outMin: Vector3, outMax: Vector3): void { this._getUnitArcRange(this._arc, outMin, outMax, 0, 0); outMin.scale(this._radius); outMax.scale(this._radius); diff --git a/packages/core/src/particle/modules/shape/ConeShape.ts b/packages/core/src/particle/modules/shape/ConeShape.ts index 4fc2b41449..fec2ee7afa 100644 --- a/packages/core/src/particle/modules/shape/ConeShape.ts +++ b/packages/core/src/particle/modules/shape/ConeShape.ts @@ -7,11 +7,6 @@ import { ParticleShapeType } from "./enums/ParticleShapeType"; * Cone shape. */ export class ConeShape extends BaseShape { - private static _tempVector20 = new Vector2(); - private static _tempVector21 = new Vector2(); - private static _tempVector30 = new Vector3(); - private static _tempVector31 = new Vector3(); - readonly shapeType = ParticleShapeType.Cone; private _angle = 25.0; @@ -78,8 +73,8 @@ export class ConeShape extends BaseShape { /** * @internal */ - _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { - const unitPosition = ConeShape._tempVector20; + _generateLocalPositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { + const unitPosition = BaseShape._tempVector20; const radian = MathUtil.degreeToRadian(this.angle); const dirSinA = Math.sin(radian); const dirCosA = Math.cos(radian); @@ -89,7 +84,7 @@ export class ConeShape extends BaseShape { ShapeUtils.randomPointInsideUnitCircle(unitPosition, rand); position.set(unitPosition.x * this.radius, unitPosition.y * this.radius, 0); - const unitDirection = ConeShape._tempVector21; + const unitDirection = BaseShape._tempVector21; ShapeUtils.randomPointInsideUnitCircle(unitDirection, rand); Vector2.lerp(unitPosition, unitDirection, this.randomDirectionAmount, unitDirection); direction.set(unitDirection.x * dirSinA, unitDirection.y * dirSinA, -dirCosA); @@ -101,11 +96,11 @@ export class ConeShape extends BaseShape { direction.set(unitPosition.x * dirSinA, unitPosition.y * dirSinA, -dirCosA); direction.normalize(); - const distance = ConeShape._tempVector30; + const distance = BaseShape._tempVector30; Vector3.scale(direction, this.length * rand.random(), distance); position.add(distance); - const randomDirection = ConeShape._tempVector31; + const randomDirection = BaseShape._tempVector31; ShapeUtils._randomPointUnitSphere(randomDirection, rand); Vector3.lerp(direction, randomDirection, this.randomDirectionAmount, direction); break; @@ -115,7 +110,7 @@ export class ConeShape extends BaseShape { /** * @internal */ - _getDirectionRange(outMin: Vector3, outMax: Vector3): void { + _getLocalDirectionRange(outMin: Vector3, outMax: Vector3): void { let radian = 0; switch (this.emitType) { case ConeEmitType.Base: @@ -135,7 +130,7 @@ export class ConeShape extends BaseShape { /** * @internal */ - _getPositionRange(outMin: Vector3, outMax: Vector3): void { + _getLocalPositionRange(outMin: Vector3, outMax: Vector3): void { const { radius } = this; switch (this.emitType) { diff --git a/packages/core/src/particle/modules/shape/HemisphereShape.ts b/packages/core/src/particle/modules/shape/HemisphereShape.ts index 6001460b5e..62ad271be6 100644 --- a/packages/core/src/particle/modules/shape/HemisphereShape.ts +++ b/packages/core/src/particle/modules/shape/HemisphereShape.ts @@ -28,7 +28,7 @@ export class HemisphereShape extends BaseShape { /** * @internal */ - _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { + _generateLocalPositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { ShapeUtils._randomPointInsideUnitSphere(position, rand); position.scale(this.radius); @@ -42,7 +42,7 @@ export class HemisphereShape extends BaseShape { /** * @internal */ - _getDirectionRange(outMin: Vector3, outMax: Vector3): void { + _getLocalDirectionRange(outMin: Vector3, outMax: Vector3): void { const randomDir = Math.sin(0.5 * this.randomDirectionAmount * Math.PI); outMin.set(-1, -1, -1); outMax.set(1, 1, randomDir); @@ -51,7 +51,7 @@ export class HemisphereShape extends BaseShape { /** * @internal */ - _getPositionRange(outMin: Vector3, outMax: Vector3): void { + _getLocalPositionRange(outMin: Vector3, outMax: Vector3): void { const radius = this._radius; outMin.set(-radius, -radius, -radius); outMax.set(radius, radius, 0); diff --git a/packages/core/src/particle/modules/shape/MeshShape.ts b/packages/core/src/particle/modules/shape/MeshShape.ts index cbdd66acfc..e425003704 100644 --- a/packages/core/src/particle/modules/shape/MeshShape.ts +++ b/packages/core/src/particle/modules/shape/MeshShape.ts @@ -54,7 +54,7 @@ export class MeshShape extends BaseShape { /** * @internal */ - _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { + _generateLocalPositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { const { _positionBuffer: positions, _positionElementInfo: positionInfo, @@ -78,7 +78,7 @@ export class MeshShape extends BaseShape { /** * @internal */ - _getPositionRange(outMin: Vector3, outMax: Vector3): void { + _getLocalPositionRange(outMin: Vector3, outMax: Vector3): void { const { bounds } = this._mesh; bounds.min.copyTo(outMin); bounds.max.copyTo(outMax); @@ -87,7 +87,7 @@ export class MeshShape extends BaseShape { /** * @internal */ - _getDirectionRange(outMin: Vector3, outMax: Vector3): void { + _getLocalDirectionRange(outMin: Vector3, outMax: Vector3): void { // @todo: Should use min and max of normal, use bounds is worst, but we can't get the min and max of normal by fast way. const { bounds } = this._mesh; bounds.min.copyTo(outMin); diff --git a/packages/core/src/particle/modules/shape/SphereShape.ts b/packages/core/src/particle/modules/shape/SphereShape.ts index 269ff20d4c..6bfa0caff0 100644 --- a/packages/core/src/particle/modules/shape/SphereShape.ts +++ b/packages/core/src/particle/modules/shape/SphereShape.ts @@ -28,7 +28,7 @@ export class SphereShape extends BaseShape { /** * @internal */ - _generatePositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { + _generateLocalPositionAndDirection(rand: Rand, emitTime: number, position: Vector3, direction: Vector3): void { ShapeUtils._randomPointInsideUnitSphere(position, rand); position.scale(this.radius); @@ -39,7 +39,7 @@ export class SphereShape extends BaseShape { /** * @internal */ - _getDirectionRange(outMin: Vector3, outMax: Vector3): void { + _getLocalDirectionRange(outMin: Vector3, outMax: Vector3): void { outMin.set(-1, -1, -1); outMax.set(1, 1, 1); } @@ -47,7 +47,7 @@ export class SphereShape extends BaseShape { /** * @internal */ - _getPositionRange(outMin: Vector3, outMax: Vector3): void { + _getLocalPositionRange(outMin: Vector3, outMax: Vector3): void { const radius = this._radius; outMin.set(-radius, -radius, -radius); outMax.set(radius, radius, radius); diff --git a/packages/core/src/shaderlib/ShaderLib.ts b/packages/core/src/shaderlib/ShaderLib.ts index 7fc81c3696..b7c09eddbe 100644 --- a/packages/core/src/shaderlib/ShaderLib.ts +++ b/packages/core/src/shaderlib/ShaderLib.ts @@ -34,22 +34,8 @@ import begin_viewdir_frag from "./begin_viewdir_frag.glsl"; import mobile_blinnphong_frag from "./mobile_blinnphong_frag.glsl"; -import noise_cellular from "./noise_cellular.glsl"; -import noise_cellular_2D from "./noise_cellular_2D.glsl"; -import noise_cellular_2x2 from "./noise_cellular_2x2.glsl"; -import noise_cellular_2x2x2 from "./noise_cellular_2x2x2.glsl"; -import noise_cellular_3D from "./noise_cellular_3D.glsl"; import noise_common from "./noise_common.glsl"; -import noise_perlin from "./noise_perlin.glsl"; -import noise_perlin_2D from "./noise_perlin_2D.glsl"; -import noise_perlin_3D from "./noise_perlin_3D.glsl"; -import noise_perlin_4D from "./noise_perlin_4D.glsl"; -import noise_psrd_2D from "./noise_psrd_2D.glsl"; -import noise_simplex from "./noise_simplex.glsl"; -import noise_simplex_2D from "./noise_simplex_2D.glsl"; -import noise_simplex_3D from "./noise_simplex_3D.glsl"; import noise_simplex_3D_grad from "./noise_simplex_3D_grad.glsl"; -import noise_simplex_4D from "./noise_simplex_4D.glsl"; import PBRShaderLib from "./pbr"; import ShadowLib from "./shadow"; @@ -95,21 +81,7 @@ export const ShaderLib = { mobile_blinnphong_frag, noise_common, - noise_cellular_2D, - noise_cellular_2x2, - noise_cellular_2x2x2, - noise_cellular_3D, - noise_cellular, - noise_perlin_2D, - noise_perlin_3D, - noise_perlin_4D, - noise_perlin, - noise_psrd_2D, - noise_simplex_2D, noise_simplex_3D_grad, - noise_simplex_3D, - noise_simplex_4D, - noise_simplex, ...ShadowLib, ...PBRShaderLib, diff --git a/packages/core/src/shaderlib/extra/particle.vs.glsl b/packages/core/src/shaderlib/extra/particle.vs.glsl index df5af96d8c..b92561d22c 100644 --- a/packages/core/src/shaderlib/extra/particle.vs.glsl +++ b/packages/core/src/shaderlib/extra/particle.vs.glsl @@ -74,6 +74,7 @@ uniform int renderer_SimulationSpace; #include #include #include +#include vec3 computeParticlePosition(in vec3 startVelocity, in float age, in float normalizedAge, vec3 gravityVelocity, vec4 worldRotation, inout vec3 localVelocity, inout vec3 worldVelocity) { vec3 startPosition = startVelocity * age; diff --git a/packages/core/src/shaderlib/extra/skybox.vs.glsl b/packages/core/src/shaderlib/extra/skybox.vs.glsl index 54c7185dd8..16eeb428e0 100644 --- a/packages/core/src/shaderlib/extra/skybox.vs.glsl +++ b/packages/core/src/shaderlib/extra/skybox.vs.glsl @@ -15,6 +15,6 @@ vec4 rotateY(vec4 v, float angle) { } void main() { - v_cubeUV = vec3( -POSITION.x, POSITION.yz ); // TextureCube is left-hand,so x need inverse + v_cubeUV = POSITION; gl_Position = camera_VPMat * rotateY(vec4(POSITION, 1.0), material_Rotation); } diff --git a/packages/core/src/shaderlib/noise_cellular.glsl b/packages/core/src/shaderlib/noise_cellular.glsl deleted file mode 100644 index 68c14f877d..0000000000 --- a/packages/core/src/shaderlib/noise_cellular.glsl +++ /dev/null @@ -1,4 +0,0 @@ -#include -#include -#include -#include diff --git a/packages/core/src/shaderlib/noise_cellular_2D.glsl b/packages/core/src/shaderlib/noise_cellular_2D.glsl deleted file mode 100644 index 5374bb7983..0000000000 --- a/packages/core/src/shaderlib/noise_cellular_2D.glsl +++ /dev/null @@ -1,48 +0,0 @@ - -// Cellular noise ("Worley noise") in 2D in GLSL. -// Copyright (c) Stefan Gustavson 2011-04-19. All rights reserved. -// This code is released under the conditions of the MIT license. -// See LICENSE file for details. -// https://github.com/stegu/webgl-noise - -// Cellular noise, returning F1 and F2 in a vec2. -// Standard 3x3 search window for good F1 and F2 values -vec2 cellular( vec2 P ) { - - vec2 Pi = mod289( floor( P ) ); - vec2 Pf = fract( P ); - vec3 oi = vec3( -1.0, 0.0, 1.0); - vec3 of = vec3( -0.5, 0.5, 1.5); - vec3 px = permute( Pi.x + oi ); - vec3 p = permute( px.x + Pi.y + oi ); // p11, p12, p13 - vec3 ox = fract( p * K ) - Ko; - vec3 oy = mod7( floor( p * K ) ) * K - Ko; - vec3 dx = Pf.x + 0.5 + jitter * ox; - vec3 dy = Pf.y - of + jitter * oy; - vec3 d1 = dx * dx + dy * dy; // d11, d12 and d13, squared - p = permute( px.y + Pi.y + oi ); // p21, p22, p23 - ox = fract( p * K ) - Ko; - oy = mod7( floor( p * K ) ) * K - Ko; - dx = Pf.x - 0.5 + jitter * ox; - dy = Pf.y - of + jitter * oy; - vec3 d2 = dx * dx + dy * dy; // d21, d22 and d23, squared - p = permute( px.z + Pi.y + oi ); // p31, p32, p33 - ox = fract( p * K ) - Ko; - oy = mod7( floor( p * K ) ) * K - Ko; - dx = Pf.x - 1.5 + jitter * ox; - dy = Pf.y - of + jitter * oy; - vec3 d3 = dx * dx + dy * dy; // d31, d32 and d33, squared - // Sort out the two smallest distances (F1, F2) - vec3 d1a = min( d1, d2 ); - d2 = max( d1, d2 ); // Swap to keep candidates for F2 - d2 = min( d2, d3 ); // neither F1 nor F2 are now in d3 - d1 = min( d1a, d2 ); // F1 is now in d1 - d2 = max( d1a, d2 ); // Swap to keep candidates for F2 - d1.xy = ( d1.x < d1.y ) ? d1.xy : d1.yx; // Swap if smaller - d1.xz = ( d1.x < d1.z ) ? d1.xz : d1.zx; // F1 is in d1.x - d1.yz = min( d1.yz, d2.yz ); // F2 is now not in d2.yz - d1.y = min( d1.y, d1.z ); // nor in d1.z - d1.y = min( d1.y, d2.x ); // F2 is in d1.y, we're done. - return sqrt( d1.xy ); - -} diff --git a/packages/core/src/shaderlib/noise_cellular_2x2.glsl b/packages/core/src/shaderlib/noise_cellular_2x2.glsl deleted file mode 100644 index c6608178d7..0000000000 --- a/packages/core/src/shaderlib/noise_cellular_2x2.glsl +++ /dev/null @@ -1,36 +0,0 @@ - -// Cellular noise ("Worley noise") in 2D in GLSL. -// Copyright (c) Stefan Gustavson 2011-04-19. All rights reserved. -// This code is released under the conditions of the MIT license. -// See LICENSE file for details. -// https://github.com/stegu/webgl-noise - -// Cellular noise, returning F1 and F2 in a vec2. -// Speeded up by using 2x2 search window instead of 3x3, -// at the expense of some strong pattern artifacts. -// F2 is often wrong and has sharp discontinuities. -// If you need a smooth F2, use the slower 3x3 version. -// F1 is sometimes wrong, too, but OK for most purposes. -vec2 cellular2x2( vec2 P ) { - - vec2 Pi = mod289( floor( P ) ); - vec2 Pf = fract( P ); - vec4 Pfx = Pf.x + vec4( -0.5, -1.5, -0.5, -1.5 ); - vec4 Pfy = Pf.y + vec4( -0.5, -0.5, -1.5, -1.5 ); - vec4 p = permute( Pi.x + vec4( 0.0, 1.0, 0.0, 1.0 ) ); - p = permute( p + Pi.y + vec4( 0.0, 0.0, 1.0, 1.0 ) ); - vec4 ox = mod7( p ) * K + Kd2; - vec4 oy = mod7( floor( p * K ) ) * K + Kd2; - vec4 dx = Pfx + jitter1 * ox; - vec4 dy = Pfy + jitter1 * oy; - vec4 d = dx * dx + dy * dy; // d11, d12, d21 and d22, squared - - // Do it right and find both F1 and F2 - d.xy = ( d.x < d.y ) ? d.xy : d.yx; // Swap if smaller - d.xz = ( d.x < d.z ) ? d.xz : d.zx; - d.xw = ( d.x < d.w ) ? d.xw : d.wx; - d.y = min( d.y, d.z ); - d.y = min( d.y, d.w ); - return sqrt( d.xy ); - -} diff --git a/packages/core/src/shaderlib/noise_cellular_2x2x2.glsl b/packages/core/src/shaderlib/noise_cellular_2x2x2.glsl deleted file mode 100644 index d5840c9b84..0000000000 --- a/packages/core/src/shaderlib/noise_cellular_2x2x2.glsl +++ /dev/null @@ -1,50 +0,0 @@ - -// Cellular noise ("Worley noise") in 3D in GLSL. -// Copyright (c) Stefan Gustavson 2011-04-19. All rights reserved. -// This code is released under the conditions of the MIT license. -// See LICENSE file for details. -// https://github.com/stegu/webgl-noise - -// Cellular noise, returning F1 and F2 in a vec2. -// Speeded up by using 2x2x2 search window instead of 3x3x3, -// at the expense of some pattern artifacts. -// F2 is often wrong and has sharp discontinuities. -// If you need a good F2, use the slower 3x3x3 version. -vec2 cellular2x2x2(vec3 P) { - - vec3 Pi = mod289( floor( P ) ); - vec3 Pf = fract( P ); - vec4 Pfx = Pf.x + vec4( 0.0, -1.0, 0.0, -1.0 ); - vec4 Pfy = Pf.y + vec4( 0.0, 0.0, -1.0, -1.0 ); - vec4 p = permute( Pi.x + vec4( 0.0, 1.0, 0.0, 1.0 ) ); - p = permute( p + Pi.y + vec4( 0.0, 0.0, 1.0, 1.0 ) ); - vec4 p1 = permute( p + Pi.z ); // z+0 - vec4 p2 = permute( p + Pi.z + vec4( 1.0 ) ); // z+1 - vec4 ox1 = fract( p1 * K ) - Ko; - vec4 oy1 = mod7( floor( p1 * K ) ) * K - Ko; - vec4 oz1 = floor( p1 * K2 ) * Kz - Kzo; // p1 < 289 guaranteed - vec4 ox2 = fract( p2 * K ) - Ko; - vec4 oy2 = mod7( floor( p2 * K ) ) * K - Ko; - vec4 oz2 = floor( p2 * K2 ) * Kz - Kzo; - vec4 dx1 = Pfx + jitter1 * ox1; - vec4 dy1 = Pfy + jitter1 * oy1; - vec4 dz1 = Pf.z + jitter1 * oz1; - vec4 dx2 = Pfx + jitter1 * ox2; - vec4 dy2 = Pfy + jitter1 * oy2; - vec4 dz2 = Pf.z - 1.0 + jitter1 * oz2; - vec4 d1 = dx1 * dx1 + dy1 * dy1 + dz1 * dz1; // z+0 - vec4 d2 = dx2 * dx2 + dy2 * dy2 + dz2 * dz2; // z+1 - - // Do it right and sort out both F1 and F2 - vec4 d = min( d1, d2 ); // F1 is now in d - d2 = max( d1, d2 ); // Make sure we keep all candidates for F2 - d.xy = ( d.x < d.y ) ? d.xy : d.yx; // Swap smallest to d.x - d.xz = ( d.x < d.z ) ? d.xz : d.zx; - d.xw = ( d.x < d.w ) ? d.xw : d.wx; // F1 is now in d.x - d.yzw = min( d.yzw, d2.yzw ); // F2 now not in d2.yzw - d.y = min( d.y, d.z ); // nor in d.z - d.y = min( d.y, d.w ); // nor in d.w - d.y = min( d.y, d2.x ); // F2 is now in d.y - return sqrt( d.xy ); // F1 and F2 - -} diff --git a/packages/core/src/shaderlib/noise_cellular_3D.glsl b/packages/core/src/shaderlib/noise_cellular_3D.glsl deleted file mode 100644 index 473f679ce2..0000000000 --- a/packages/core/src/shaderlib/noise_cellular_3D.glsl +++ /dev/null @@ -1,155 +0,0 @@ - -// Cellular noise ("Worley noise") in 3D in GLSL. -// Copyright (c) Stefan Gustavson 2011-04-19. All rights reserved. -// This code is released under the conditions of the MIT license. -// See LICENSE file for details. -// https://github.com/stegu/webgl-noise - -// Cellular noise, returning F1 and F2 in a vec2. -// 3x3x3 search region for good F2 everywhere, but a lot -// slower than the 2x2x2 version. -// The code below is a bit scary even to its author, -// but it has at least half decent performance on a -// modern GPU. In any case, it beats any software -// implementation of Worley noise hands down. - -vec2 cellular( vec3 P ) { - - vec3 Pi = mod289( floor( P ) ); - vec3 Pf = fract( P ) - 0.5; - - vec3 Pfx = Pf.x + vec3( 1.0, 0.0, -1.0 ); - vec3 Pfy = Pf.y + vec3( 1.0, 0.0, -1.0 ); - vec3 Pfz = Pf.z + vec3( 1.0, 0.0, -1.0 ); - - vec3 p = permute( Pi.x + vec3( -1.0, 0.0, 1.0 ) ); - vec3 p1 = permute( p + Pi.y - 1.0 ); - vec3 p2 = permute( p + Pi.y ); - vec3 p3 = permute( p + Pi.y + 1.0 ); - - vec3 p11 = permute( p1 + Pi.z - 1.0 ); - vec3 p12 = permute( p1 + Pi.z ); - vec3 p13 = permute( p1 + Pi.z + 1.0 ); - - vec3 p21 = permute( p2 + Pi.z - 1.0 ); - vec3 p22 = permute( p2 + Pi.z ); - vec3 p23 = permute( p2 + Pi.z + 1.0 ); - - vec3 p31 = permute( p3 + Pi.z - 1.0 ); - vec3 p32 = permute( p3 + Pi.z ); - vec3 p33 = permute( p3 + Pi.z + 1.0 ); - - vec3 ox11 = fract( p11 * K ) - Ko; - vec3 oy11 = mod7( floor( p11 * K ) ) * K - Ko; - vec3 oz11 = floor( p11 * K2 ) * Kz - Kzo; // p11 < 289 guaranteed - - vec3 ox12 = fract( p12 * K ) - Ko; - vec3 oy12 = mod7( floor( p12 * K ) ) * K - Ko; - vec3 oz12 = floor( p12 * K2 ) * Kz - Kzo; - - vec3 ox13 = fract( p13 * K ) - Ko; - vec3 oy13 = mod7( floor( p13 * K ) ) * K - Ko; - vec3 oz13 = floor( p13 * K2 ) * Kz - Kzo; - - vec3 ox21 = fract( p21 * K ) - Ko; - vec3 oy21 = mod7( floor( p21 * K ) ) * K - Ko; - vec3 oz21 = floor( p21 * K2 ) * Kz - Kzo; - - vec3 ox22 = fract( p22 * K ) - Ko; - vec3 oy22 = mod7( floor( p22 * K ) ) * K - Ko; - vec3 oz22 = floor( p22 * K2 ) * Kz - Kzo; - - vec3 ox23 = fract( p23 * K ) - Ko; - vec3 oy23 = mod7( floor( p23 * K ) ) * K - Ko; - vec3 oz23 = floor( p23 * K2 ) * Kz - Kzo; - - vec3 ox31 = fract( p31 * K ) - Ko; - vec3 oy31 = mod7( floor( p31 * K ) ) * K - Ko; - vec3 oz31 = floor( p31 * K2 ) * Kz - Kzo; - - vec3 ox32 = fract( p32 * K ) - Ko; - vec3 oy32 = mod7( floor( p32 * K ) ) * K - Ko; - vec3 oz32 = floor( p32 * K2 ) * Kz - Kzo; - - vec3 ox33 = fract( p33 * K ) - Ko; - vec3 oy33 = mod7( floor( p33 * K ) ) * K - Ko; - vec3 oz33 = floor( p33 * K2 ) * Kz - Kzo; - - vec3 dx11 = Pfx + jitter * ox11; - vec3 dy11 = Pfy.x + jitter * oy11; - vec3 dz11 = Pfz.x + jitter * oz11; - - vec3 dx12 = Pfx + jitter * ox12; - vec3 dy12 = Pfy.x + jitter * oy12; - vec3 dz12 = Pfz.y + jitter * oz12; - - vec3 dx13 = Pfx + jitter * ox13; - vec3 dy13 = Pfy.x + jitter * oy13; - vec3 dz13 = Pfz.z + jitter * oz13; - - vec3 dx21 = Pfx + jitter * ox21; - vec3 dy21 = Pfy.y + jitter * oy21; - vec3 dz21 = Pfz.x + jitter * oz21; - - vec3 dx22 = Pfx + jitter * ox22; - vec3 dy22 = Pfy.y + jitter * oy22; - vec3 dz22 = Pfz.y + jitter * oz22; - - vec3 dx23 = Pfx + jitter * ox23; - vec3 dy23 = Pfy.y + jitter * oy23; - vec3 dz23 = Pfz.z + jitter * oz23; - - vec3 dx31 = Pfx + jitter * ox31; - vec3 dy31 = Pfy.z + jitter * oy31; - vec3 dz31 = Pfz.x + jitter * oz31; - - vec3 dx32 = Pfx + jitter * ox32; - vec3 dy32 = Pfy.z + jitter * oy32; - vec3 dz32 = Pfz.y + jitter * oz32; - - vec3 dx33 = Pfx + jitter * ox33; - vec3 dy33 = Pfy.z + jitter * oy33; - vec3 dz33 = Pfz.z + jitter * oz33; - - vec3 d11 = dx11 * dx11 + dy11 * dy11 + dz11 * dz11; - vec3 d12 = dx12 * dx12 + dy12 * dy12 + dz12 * dz12; - vec3 d13 = dx13 * dx13 + dy13 * dy13 + dz13 * dz13; - vec3 d21 = dx21 * dx21 + dy21 * dy21 + dz21 * dz21; - vec3 d22 = dx22 * dx22 + dy22 * dy22 + dz22 * dz22; - vec3 d23 = dx23 * dx23 + dy23 * dy23 + dz23 * dz23; - vec3 d31 = dx31 * dx31 + dy31 * dy31 + dz31 * dz31; - vec3 d32 = dx32 * dx32 + dy32 * dy32 + dz32 * dz32; - vec3 d33 = dx33 * dx33 + dy33 * dy33 + dz33 * dz33; - - // Do it right and sort out both F1 and F2 - vec3 d1a = min( d11, d12 ); - d12 = max( d11, d12 ); - d11 = min( d1a, d13 ); // Smallest now not in d12 or d13 - d13 = max( d1a, d13 ); - d12 = min( d12, d13 ); // 2nd smallest now not in d13 - vec3 d2a = min( d21, d22 ); - d22 = max( d21, d22 ); - d21 = min( d2a, d23 ); // Smallest now not in d22 or d23 - d23 = max( d2a, d23 ); - d22 = min( d22, d23 ); // 2nd smallest now not in d23 - vec3 d3a = min( d31, d32 ); - d32 = max( d31, d32 ); - d31 = min( d3a, d33 ); // Smallest now not in d32 or d33 - d33 = max( d3a, d33 ); - d32 = min( d32, d33 ); // 2nd smallest now not in d33 - vec3 da = min( d11, d21 ); - d21 = max( d11, d21 ); - d11 = min( da, d31 ); // Smallest now in d11 - d31 = max( da, d31 ); // 2nd smallest now not in d31 - d11.xy = ( d11.x < d11.y ) ? d11.xy : d11.yx; - d11.xz = ( d11.x < d11.z ) ? d11.xz : d11.zx; // d11.x now smallest - d12 = min( d12, d21 ); // 2nd smallest now not in d21 - d12 = min( d12, d22 ); // nor in d22 - d12 = min( d12, d31 ); // nor in d31 - d12 = min( d12, d32 ); // nor in d32 - d11.yz = min( d11.yz, d12.xy ); // nor in d12.yz - d11.y = min( d11.y, d12.z ); // Only two more to go - d11.y = min( d11.y, d11.z ); // Done! (Phew! ) - return sqrt( d11.xy ); // F1, F2 - -} diff --git a/packages/core/src/shaderlib/noise_common.glsl b/packages/core/src/shaderlib/noise_common.glsl index 826079969e..cf689cb94e 100644 --- a/packages/core/src/shaderlib/noise_common.glsl +++ b/packages/core/src/shaderlib/noise_common.glsl @@ -1,3 +1,7 @@ +// Common helper functions for simplex noise. +// Algorithm: Ken Perlin, "Noise hardware" (2001) — simplex lattice improvement over classic Perlin noise (1985). +// GLSL implementation: Ian McEwan, Ashima Arts (MIT License) — https://github.com/ashima/webgl-noise + // Modulo 289 without a division (only multiplications) vec4 mod289( vec4 x ) { diff --git a/packages/core/src/shaderlib/noise_perlin.glsl b/packages/core/src/shaderlib/noise_perlin.glsl deleted file mode 100644 index 09522bf20a..0000000000 --- a/packages/core/src/shaderlib/noise_perlin.glsl +++ /dev/null @@ -1,3 +0,0 @@ -#include -#include -#include diff --git a/packages/core/src/shaderlib/noise_perlin_2D.glsl b/packages/core/src/shaderlib/noise_perlin_2D.glsl deleted file mode 100644 index 9a953fd734..0000000000 --- a/packages/core/src/shaderlib/noise_perlin_2D.glsl +++ /dev/null @@ -1,96 +0,0 @@ -// -// GLSL textureless classic 2D noise "cnoise", -// with an RSL-style periodic variant "pnoise". -// Author: Stefan Gustavson (stefan.gustavson@liu.se) -// Version: 2011-08-22 -// -// Many thanks to Ian McEwan of Ashima Arts for the -// ideas for permutation and gradient selection. -// -// Copyright (c) 2011 Stefan Gustavson. All rights reserved. -// Distributed under the MIT license. See LICENSE file. -// https://github.com/stegu/webgl-noise -// - -// Classic Perlin noise -float perlin( vec2 P ) { - - vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0); - vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0); - Pi = mod289(Pi); // To avoid truncation effects in permutation - vec4 ix = Pi.xzxz; - vec4 iy = Pi.yyww; - vec4 fx = Pf.xzxz; - vec4 fy = Pf.yyww; - - vec4 i = permute(permute(ix) + iy); - - vec4 gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ; - vec4 gy = abs(gx) - 0.5 ; - vec4 tx = floor(gx + 0.5); - gx = gx - tx; - - vec2 g00 = vec2(gx.x,gy.x); - vec2 g10 = vec2(gx.y,gy.y); - vec2 g01 = vec2(gx.z,gy.z); - vec2 g11 = vec2(gx.w,gy.w); - - vec4 norm = taylorInvSqrt(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11))); - g00 *= norm.x; - g01 *= norm.y; - g10 *= norm.z; - g11 *= norm.w; - - float n00 = dot(g00, vec2(fx.x, fy.x)); - float n10 = dot(g10, vec2(fx.y, fy.y)); - float n01 = dot(g01, vec2(fx.z, fy.z)); - float n11 = dot(g11, vec2(fx.w, fy.w)); - - vec2 fade_xy = fade(Pf.xy); - vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x); - float n_xy = mix(n_x.x, n_x.y, fade_xy.y); - return 2.3 * n_xy; - -} - -// Classic Perlin noise, periodic variant -float perlin( vec2 P, vec2 rep ) { - - vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0); - vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0); - Pi = mod(Pi, rep.xyxy); // To create noise with explicit period - Pi = mod289(Pi); // To avoid truncation effects in permutation - vec4 ix = Pi.xzxz; - vec4 iy = Pi.yyww; - vec4 fx = Pf.xzxz; - vec4 fy = Pf.yyww; - - vec4 i = permute(permute(ix) + iy); - - vec4 gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ; - vec4 gy = abs(gx) - 0.5 ; - vec4 tx = floor(gx + 0.5); - gx = gx - tx; - - vec2 g00 = vec2(gx.x,gy.x); - vec2 g10 = vec2(gx.y,gy.y); - vec2 g01 = vec2(gx.z,gy.z); - vec2 g11 = vec2(gx.w,gy.w); - - vec4 norm = taylorInvSqrt(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11))); - g00 *= norm.x; - g01 *= norm.y; - g10 *= norm.z; - g11 *= norm.w; - - float n00 = dot(g00, vec2(fx.x, fy.x)); - float n10 = dot(g10, vec2(fx.y, fy.y)); - float n01 = dot(g01, vec2(fx.z, fy.z)); - float n11 = dot(g11, vec2(fx.w, fy.w)); - - vec2 fade_xy = fade(Pf.xy); - vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x); - float n_xy = mix(n_x.x, n_x.y, fade_xy.y); - return 2.3 * n_xy; - -} diff --git a/packages/core/src/shaderlib/noise_perlin_3D.glsl b/packages/core/src/shaderlib/noise_perlin_3D.glsl deleted file mode 100644 index f48c4e11bc..0000000000 --- a/packages/core/src/shaderlib/noise_perlin_3D.glsl +++ /dev/null @@ -1,155 +0,0 @@ -// -// GLSL textureless classic 3D noise "cnoise", -// with an RSL-style periodic variant "pnoise". -// Author: Stefan Gustavson (stefan.gustavson@liu.se) -// Version: 2011-10-11 -// -// Many thanks to Ian McEwan of Ashima Arts for the -// ideas for permutation and gradient selection. -// -// Copyright (c) 2011 Stefan Gustavson. All rights reserved. -// Distributed under the MIT license. See LICENSE file. -// https://github.com/stegu/webgl-noise -// - -// Classic Perlin noise -float perlin( vec3 P ) { - - vec3 Pi0 = floor(P); // Integer part for indexing - vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 - Pi0 = mod289(Pi0); - Pi1 = mod289(Pi1); - vec3 Pf0 = fract(P); // Fractional part for interpolation - vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 - vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); - vec4 iy = vec4(Pi0.yy, Pi1.yy); - vec4 iz0 = Pi0.zzzz; - vec4 iz1 = Pi1.zzzz; - - vec4 ixy = permute(permute(ix) + iy); - vec4 ixy0 = permute(ixy + iz0); - vec4 ixy1 = permute(ixy + iz1); - - vec4 gx0 = ixy0 * (1.0 / 7.0); - vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; - gx0 = fract(gx0); - vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); - vec4 sz0 = step(gz0, vec4(0.0)); - gx0 -= sz0 * (step(0.0, gx0) - 0.5); - gy0 -= sz0 * (step(0.0, gy0) - 0.5); - - vec4 gx1 = ixy1 * (1.0 / 7.0); - vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; - gx1 = fract(gx1); - vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); - vec4 sz1 = step(gz1, vec4(0.0)); - gx1 -= sz1 * (step(0.0, gx1) - 0.5); - gy1 -= sz1 * (step(0.0, gy1) - 0.5); - - vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); - vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); - vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); - vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); - vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); - vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); - vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); - vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); - - vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); - g000 *= norm0.x; - g010 *= norm0.y; - g100 *= norm0.z; - g110 *= norm0.w; - vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); - g001 *= norm1.x; - g011 *= norm1.y; - g101 *= norm1.z; - g111 *= norm1.w; - - float n000 = dot(g000, Pf0); - float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); - float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); - float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); - float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); - float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); - float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); - float n111 = dot(g111, Pf1); - - vec3 fade_xyz = fade(Pf0); - vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); - vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); - float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); - return 2.2 * n_xyz; - -} - -// Classic Perlin noise, periodic variant -float perlin( vec3 P, vec3 rep ) { - - vec3 Pi0 = mod(floor(P), rep); // Integer part, modulo period - vec3 Pi1 = mod(Pi0 + vec3(1.0), rep); // Integer part + 1, mod period - Pi0 = mod289(Pi0); - Pi1 = mod289(Pi1); - vec3 Pf0 = fract(P); // Fractional part for interpolation - vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 - vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); - vec4 iy = vec4(Pi0.yy, Pi1.yy); - vec4 iz0 = Pi0.zzzz; - vec4 iz1 = Pi1.zzzz; - - vec4 ixy = permute(permute(ix) + iy); - vec4 ixy0 = permute(ixy + iz0); - vec4 ixy1 = permute(ixy + iz1); - - vec4 gx0 = ixy0 * (1.0 / 7.0); - vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; - gx0 = fract(gx0); - vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); - vec4 sz0 = step(gz0, vec4(0.0)); - gx0 -= sz0 * (step(0.0, gx0) - 0.5); - gy0 -= sz0 * (step(0.0, gy0) - 0.5); - - vec4 gx1 = ixy1 * (1.0 / 7.0); - vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; - gx1 = fract(gx1); - vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); - vec4 sz1 = step(gz1, vec4(0.0)); - gx1 -= sz1 * (step(0.0, gx1) - 0.5); - gy1 -= sz1 * (step(0.0, gy1) - 0.5); - - vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); - vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); - vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); - vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); - vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); - vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); - vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); - vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); - - vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); - g000 *= norm0.x; - g010 *= norm0.y; - g100 *= norm0.z; - g110 *= norm0.w; - vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); - g001 *= norm1.x; - g011 *= norm1.y; - g101 *= norm1.z; - g111 *= norm1.w; - - float n000 = dot(g000, Pf0); - float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); - float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); - float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); - float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); - float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); - float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); - float n111 = dot(g111, Pf1); - - vec3 fade_xyz = fade(Pf0); - vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); - vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); - float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); - return 2.2 * n_xyz; - -} diff --git a/packages/core/src/shaderlib/noise_perlin_4D.glsl b/packages/core/src/shaderlib/noise_perlin_4D.glsl deleted file mode 100644 index dee47bb902..0000000000 --- a/packages/core/src/shaderlib/noise_perlin_4D.glsl +++ /dev/null @@ -1,285 +0,0 @@ -// -// GLSL textureless classic 4D noise "cnoise", -// with an RSL-style periodic variant "pnoise". -// Author: Stefan Gustavson (stefan.gustavson@liu.se) -// Version: 2011-08-22 -// -// Many thanks to Ian McEwan of Ashima Arts for the -// ideas for permutation and gradient selection. -// -// Copyright (c) 2011 Stefan Gustavson. All rights reserved. -// Distributed under the MIT license. See LICENSE file. -// https://github.com/stegu/webgl-noise -// - -// Classic Perlin noise -float perlin( vec4 P ) { - - vec4 Pi0 = floor(P); // Integer part for indexing - vec4 Pi1 = Pi0 + 1.0; // Integer part + 1 - Pi0 = mod289(Pi0); - Pi1 = mod289(Pi1); - vec4 Pf0 = fract(P); // Fractional part for interpolation - vec4 Pf1 = Pf0 - 1.0; // Fractional part - 1.0 - vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); - vec4 iy = vec4(Pi0.yy, Pi1.yy); - vec4 iz0 = vec4(Pi0.zzzz); - vec4 iz1 = vec4(Pi1.zzzz); - vec4 iw0 = vec4(Pi0.wwww); - vec4 iw1 = vec4(Pi1.wwww); - - vec4 ixy = permute(permute(ix) + iy); - vec4 ixy0 = permute(ixy + iz0); - vec4 ixy1 = permute(ixy + iz1); - vec4 ixy00 = permute(ixy0 + iw0); - vec4 ixy01 = permute(ixy0 + iw1); - vec4 ixy10 = permute(ixy1 + iw0); - vec4 ixy11 = permute(ixy1 + iw1); - - vec4 gx00 = ixy00 * (1.0 / 7.0); - vec4 gy00 = floor(gx00) * (1.0 / 7.0); - vec4 gz00 = floor(gy00) * (1.0 / 6.0); - gx00 = fract(gx00) - 0.5; - gy00 = fract(gy00) - 0.5; - gz00 = fract(gz00) - 0.5; - vec4 gw00 = vec4(0.75) - abs(gx00) - abs(gy00) - abs(gz00); - vec4 sw00 = step(gw00, vec4(0.0)); - gx00 -= sw00 * (step(0.0, gx00) - 0.5); - gy00 -= sw00 * (step(0.0, gy00) - 0.5); - - vec4 gx01 = ixy01 * (1.0 / 7.0); - vec4 gy01 = floor(gx01) * (1.0 / 7.0); - vec4 gz01 = floor(gy01) * (1.0 / 6.0); - gx01 = fract(gx01) - 0.5; - gy01 = fract(gy01) - 0.5; - gz01 = fract(gz01) - 0.5; - vec4 gw01 = vec4(0.75) - abs(gx01) - abs(gy01) - abs(gz01); - vec4 sw01 = step(gw01, vec4(0.0)); - gx01 -= sw01 * (step(0.0, gx01) - 0.5); - gy01 -= sw01 * (step(0.0, gy01) - 0.5); - - vec4 gx10 = ixy10 * (1.0 / 7.0); - vec4 gy10 = floor(gx10) * (1.0 / 7.0); - vec4 gz10 = floor(gy10) * (1.0 / 6.0); - gx10 = fract(gx10) - 0.5; - gy10 = fract(gy10) - 0.5; - gz10 = fract(gz10) - 0.5; - vec4 gw10 = vec4(0.75) - abs(gx10) - abs(gy10) - abs(gz10); - vec4 sw10 = step(gw10, vec4(0.0)); - gx10 -= sw10 * (step(0.0, gx10) - 0.5); - gy10 -= sw10 * (step(0.0, gy10) - 0.5); - - vec4 gx11 = ixy11 * (1.0 / 7.0); - vec4 gy11 = floor(gx11) * (1.0 / 7.0); - vec4 gz11 = floor(gy11) * (1.0 / 6.0); - gx11 = fract(gx11) - 0.5; - gy11 = fract(gy11) - 0.5; - gz11 = fract(gz11) - 0.5; - vec4 gw11 = vec4(0.75) - abs(gx11) - abs(gy11) - abs(gz11); - vec4 sw11 = step(gw11, vec4(0.0)); - gx11 -= sw11 * (step(0.0, gx11) - 0.5); - gy11 -= sw11 * (step(0.0, gy11) - 0.5); - - vec4 g0000 = vec4(gx00.x,gy00.x,gz00.x,gw00.x); - vec4 g1000 = vec4(gx00.y,gy00.y,gz00.y,gw00.y); - vec4 g0100 = vec4(gx00.z,gy00.z,gz00.z,gw00.z); - vec4 g1100 = vec4(gx00.w,gy00.w,gz00.w,gw00.w); - vec4 g0010 = vec4(gx10.x,gy10.x,gz10.x,gw10.x); - vec4 g1010 = vec4(gx10.y,gy10.y,gz10.y,gw10.y); - vec4 g0110 = vec4(gx10.z,gy10.z,gz10.z,gw10.z); - vec4 g1110 = vec4(gx10.w,gy10.w,gz10.w,gw10.w); - vec4 g0001 = vec4(gx01.x,gy01.x,gz01.x,gw01.x); - vec4 g1001 = vec4(gx01.y,gy01.y,gz01.y,gw01.y); - vec4 g0101 = vec4(gx01.z,gy01.z,gz01.z,gw01.z); - vec4 g1101 = vec4(gx01.w,gy01.w,gz01.w,gw01.w); - vec4 g0011 = vec4(gx11.x,gy11.x,gz11.x,gw11.x); - vec4 g1011 = vec4(gx11.y,gy11.y,gz11.y,gw11.y); - vec4 g0111 = vec4(gx11.z,gy11.z,gz11.z,gw11.z); - vec4 g1111 = vec4(gx11.w,gy11.w,gz11.w,gw11.w); - - vec4 norm00 = taylorInvSqrt(vec4(dot(g0000, g0000), dot(g0100, g0100), dot(g1000, g1000), dot(g1100, g1100))); - g0000 *= norm00.x; - g0100 *= norm00.y; - g1000 *= norm00.z; - g1100 *= norm00.w; - - vec4 norm01 = taylorInvSqrt(vec4(dot(g0001, g0001), dot(g0101, g0101), dot(g1001, g1001), dot(g1101, g1101))); - g0001 *= norm01.x; - g0101 *= norm01.y; - g1001 *= norm01.z; - g1101 *= norm01.w; - - vec4 norm10 = taylorInvSqrt(vec4(dot(g0010, g0010), dot(g0110, g0110), dot(g1010, g1010), dot(g1110, g1110))); - g0010 *= norm10.x; - g0110 *= norm10.y; - g1010 *= norm10.z; - g1110 *= norm10.w; - - vec4 norm11 = taylorInvSqrt(vec4(dot(g0011, g0011), dot(g0111, g0111), dot(g1011, g1011), dot(g1111, g1111))); - g0011 *= norm11.x; - g0111 *= norm11.y; - g1011 *= norm11.z; - g1111 *= norm11.w; - - float n0000 = dot(g0000, Pf0); - float n1000 = dot(g1000, vec4(Pf1.x, Pf0.yzw)); - float n0100 = dot(g0100, vec4(Pf0.x, Pf1.y, Pf0.zw)); - float n1100 = dot(g1100, vec4(Pf1.xy, Pf0.zw)); - float n0010 = dot(g0010, vec4(Pf0.xy, Pf1.z, Pf0.w)); - float n1010 = dot(g1010, vec4(Pf1.x, Pf0.y, Pf1.z, Pf0.w)); - float n0110 = dot(g0110, vec4(Pf0.x, Pf1.yz, Pf0.w)); - float n1110 = dot(g1110, vec4(Pf1.xyz, Pf0.w)); - float n0001 = dot(g0001, vec4(Pf0.xyz, Pf1.w)); - float n1001 = dot(g1001, vec4(Pf1.x, Pf0.yz, Pf1.w)); - float n0101 = dot(g0101, vec4(Pf0.x, Pf1.y, Pf0.z, Pf1.w)); - float n1101 = dot(g1101, vec4(Pf1.xy, Pf0.z, Pf1.w)); - float n0011 = dot(g0011, vec4(Pf0.xy, Pf1.zw)); - float n1011 = dot(g1011, vec4(Pf1.x, Pf0.y, Pf1.zw)); - float n0111 = dot(g0111, vec4(Pf0.x, Pf1.yzw)); - float n1111 = dot(g1111, Pf1); - - vec4 fade_xyzw = fade(Pf0); - vec4 n_0w = mix(vec4(n0000, n1000, n0100, n1100), vec4(n0001, n1001, n0101, n1101), fade_xyzw.w); - vec4 n_1w = mix(vec4(n0010, n1010, n0110, n1110), vec4(n0011, n1011, n0111, n1111), fade_xyzw.w); - vec4 n_zw = mix(n_0w, n_1w, fade_xyzw.z); - vec2 n_yzw = mix(n_zw.xy, n_zw.zw, fade_xyzw.y); - float n_xyzw = mix(n_yzw.x, n_yzw.y, fade_xyzw.x); - return 2.2 * n_xyzw; - -} - -// Classic Perlin noise, periodic version -float perlin( vec4 P, vec4 rep ) { - - vec4 Pi0 = mod(floor(P), rep); // Integer part modulo rep - vec4 Pi1 = mod(Pi0 + 1.0, rep); // Integer part + 1 mod rep - Pi0 = mod289(Pi0); - Pi1 = mod289(Pi1); - vec4 Pf0 = fract(P); // Fractional part for interpolation - vec4 Pf1 = Pf0 - 1.0; // Fractional part - 1.0 - vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); - vec4 iy = vec4(Pi0.yy, Pi1.yy); - vec4 iz0 = vec4(Pi0.zzzz); - vec4 iz1 = vec4(Pi1.zzzz); - vec4 iw0 = vec4(Pi0.wwww); - vec4 iw1 = vec4(Pi1.wwww); - - vec4 ixy = permute(permute(ix) + iy); - vec4 ixy0 = permute(ixy + iz0); - vec4 ixy1 = permute(ixy + iz1); - vec4 ixy00 = permute(ixy0 + iw0); - vec4 ixy01 = permute(ixy0 + iw1); - vec4 ixy10 = permute(ixy1 + iw0); - vec4 ixy11 = permute(ixy1 + iw1); - - vec4 gx00 = ixy00 * (1.0 / 7.0); - vec4 gy00 = floor(gx00) * (1.0 / 7.0); - vec4 gz00 = floor(gy00) * (1.0 / 6.0); - gx00 = fract(gx00) - 0.5; - gy00 = fract(gy00) - 0.5; - gz00 = fract(gz00) - 0.5; - vec4 gw00 = vec4(0.75) - abs(gx00) - abs(gy00) - abs(gz00); - vec4 sw00 = step(gw00, vec4(0.0)); - gx00 -= sw00 * (step(0.0, gx00) - 0.5); - gy00 -= sw00 * (step(0.0, gy00) - 0.5); - - vec4 gx01 = ixy01 * (1.0 / 7.0); - vec4 gy01 = floor(gx01) * (1.0 / 7.0); - vec4 gz01 = floor(gy01) * (1.0 / 6.0); - gx01 = fract(gx01) - 0.5; - gy01 = fract(gy01) - 0.5; - gz01 = fract(gz01) - 0.5; - vec4 gw01 = vec4(0.75) - abs(gx01) - abs(gy01) - abs(gz01); - vec4 sw01 = step(gw01, vec4(0.0)); - gx01 -= sw01 * (step(0.0, gx01) - 0.5); - gy01 -= sw01 * (step(0.0, gy01) - 0.5); - - vec4 gx10 = ixy10 * (1.0 / 7.0); - vec4 gy10 = floor(gx10) * (1.0 / 7.0); - vec4 gz10 = floor(gy10) * (1.0 / 6.0); - gx10 = fract(gx10) - 0.5; - gy10 = fract(gy10) - 0.5; - gz10 = fract(gz10) - 0.5; - vec4 gw10 = vec4(0.75) - abs(gx10) - abs(gy10) - abs(gz10); - vec4 sw10 = step(gw10, vec4(0.0)); - gx10 -= sw10 * (step(0.0, gx10) - 0.5); - gy10 -= sw10 * (step(0.0, gy10) - 0.5); - - vec4 gx11 = ixy11 * (1.0 / 7.0); - vec4 gy11 = floor(gx11) * (1.0 / 7.0); - vec4 gz11 = floor(gy11) * (1.0 / 6.0); - gx11 = fract(gx11) - 0.5; - gy11 = fract(gy11) - 0.5; - gz11 = fract(gz11) - 0.5; - vec4 gw11 = vec4(0.75) - abs(gx11) - abs(gy11) - abs(gz11); - vec4 sw11 = step(gw11, vec4(0.0)); - gx11 -= sw11 * (step(0.0, gx11) - 0.5); - gy11 -= sw11 * (step(0.0, gy11) - 0.5); - - vec4 g0000 = vec4(gx00.x,gy00.x,gz00.x,gw00.x); - vec4 g1000 = vec4(gx00.y,gy00.y,gz00.y,gw00.y); - vec4 g0100 = vec4(gx00.z,gy00.z,gz00.z,gw00.z); - vec4 g1100 = vec4(gx00.w,gy00.w,gz00.w,gw00.w); - vec4 g0010 = vec4(gx10.x,gy10.x,gz10.x,gw10.x); - vec4 g1010 = vec4(gx10.y,gy10.y,gz10.y,gw10.y); - vec4 g0110 = vec4(gx10.z,gy10.z,gz10.z,gw10.z); - vec4 g1110 = vec4(gx10.w,gy10.w,gz10.w,gw10.w); - vec4 g0001 = vec4(gx01.x,gy01.x,gz01.x,gw01.x); - vec4 g1001 = vec4(gx01.y,gy01.y,gz01.y,gw01.y); - vec4 g0101 = vec4(gx01.z,gy01.z,gz01.z,gw01.z); - vec4 g1101 = vec4(gx01.w,gy01.w,gz01.w,gw01.w); - vec4 g0011 = vec4(gx11.x,gy11.x,gz11.x,gw11.x); - vec4 g1011 = vec4(gx11.y,gy11.y,gz11.y,gw11.y); - vec4 g0111 = vec4(gx11.z,gy11.z,gz11.z,gw11.z); - vec4 g1111 = vec4(gx11.w,gy11.w,gz11.w,gw11.w); - - vec4 norm00 = taylorInvSqrt(vec4(dot(g0000, g0000), dot(g0100, g0100), dot(g1000, g1000), dot(g1100, g1100))); - g0000 *= norm00.x; - g0100 *= norm00.y; - g1000 *= norm00.z; - g1100 *= norm00.w; - - vec4 norm01 = taylorInvSqrt(vec4(dot(g0001, g0001), dot(g0101, g0101), dot(g1001, g1001), dot(g1101, g1101))); - g0001 *= norm01.x; - g0101 *= norm01.y; - g1001 *= norm01.z; - g1101 *= norm01.w; - - vec4 norm10 = taylorInvSqrt(vec4(dot(g0010, g0010), dot(g0110, g0110), dot(g1010, g1010), dot(g1110, g1110))); - g0010 *= norm10.x; - g0110 *= norm10.y; - g1010 *= norm10.z; - g1110 *= norm10.w; - - vec4 norm11 = taylorInvSqrt(vec4(dot(g0011, g0011), dot(g0111, g0111), dot(g1011, g1011), dot(g1111, g1111))); - g0011 *= norm11.x; - g0111 *= norm11.y; - g1011 *= norm11.z; - g1111 *= norm11.w; - - float n0000 = dot(g0000, Pf0); - float n1000 = dot(g1000, vec4(Pf1.x, Pf0.yzw)); - float n0100 = dot(g0100, vec4(Pf0.x, Pf1.y, Pf0.zw)); - float n1100 = dot(g1100, vec4(Pf1.xy, Pf0.zw)); - float n0010 = dot(g0010, vec4(Pf0.xy, Pf1.z, Pf0.w)); - float n1010 = dot(g1010, vec4(Pf1.x, Pf0.y, Pf1.z, Pf0.w)); - float n0110 = dot(g0110, vec4(Pf0.x, Pf1.yz, Pf0.w)); - float n1110 = dot(g1110, vec4(Pf1.xyz, Pf0.w)); - float n0001 = dot(g0001, vec4(Pf0.xyz, Pf1.w)); - float n1001 = dot(g1001, vec4(Pf1.x, Pf0.yz, Pf1.w)); - float n0101 = dot(g0101, vec4(Pf0.x, Pf1.y, Pf0.z, Pf1.w)); - float n1101 = dot(g1101, vec4(Pf1.xy, Pf0.z, Pf1.w)); - float n0011 = dot(g0011, vec4(Pf0.xy, Pf1.zw)); - float n1011 = dot(g1011, vec4(Pf1.x, Pf0.y, Pf1.zw)); - float n0111 = dot(g0111, vec4(Pf0.x, Pf1.yzw)); - float n1111 = dot(g1111, Pf1); - - vec4 fade_xyzw = fade(Pf0); - vec4 n_0w = mix(vec4(n0000, n1000, n0100, n1100), vec4(n0001, n1001, n0101, n1101), fade_xyzw.w); - vec4 n_1w = mix(vec4(n0010, n1010, n0110, n1110), vec4(n0011, n1011, n0111, n1111), fade_xyzw.w); - vec4 n_zw = mix(n_0w, n_1w, fade_xyzw.z); - vec2 n_yzw = mix(n_zw.xy, n_zw.zw, fade_xyzw.y); - float n_xyzw = mix(n_yzw.x, n_yzw.y, fade_xyzw.x); - return 2.2 * n_xyzw; - -} diff --git a/packages/core/src/shaderlib/noise_psrd_2D.glsl b/packages/core/src/shaderlib/noise_psrd_2D.glsl deleted file mode 100644 index 4df67ce42f..0000000000 --- a/packages/core/src/shaderlib/noise_psrd_2D.glsl +++ /dev/null @@ -1,425 +0,0 @@ -// Periodic (tiling) 2-D simplex noise (hexagonal lattice gradient noise) -// with rotating gradients and analytic derivatives. -// Variants also without the derivative (no "d" in the name), without -// the tiling property (no "p" in the name) and without the rotating -// gradients (no "r" in the name). -// -// This is (yet) another variation on simplex noise. It's similar to the -// version presented by Ken Perlin, but the grid is axis-aligned and -// slightly stretched in the y direction to permit rectangular tiling. -// -// The noise can be made to tile seamlessly to any integer period in x and -// any even integer period in y. Odd periods may be specified for y, but -// then the actual tiling period will be twice that number. -// -// The rotating gradients give the appearance of a swirling motion, and can -// serve a similar purpose for animation as motion along z in 3-D noise. -// The rotating gradients in conjunction with the analytic derivatives -// can make "flow noise" effects as presented by Perlin and Neyret. -// -// vec3 {p}s{r}dnoise(vec2 pos {, vec2 per} {, float rot}) -// "pos" is the input (x,y) coordinate -// "per" is the x and y period, where per.x is a positive integer -// and per.y is a positive even integer -// "rot" is the angle to rotate the gradients (any float value, -// where 0.0 is no rotation and 1.0 is one full turn) -// The first component of the 3-element return vector is the noise value. -// The second and third components are the x and y partial derivatives. -// -// float {p}s{r}noise(vec2 pos {, vec2 per} {, float rot}) -// "pos" is the input (x,y) coordinate -// "per" is the x and y period, where per.x is a positive integer -// and per.y is a positive even integer -// "rot" is the angle to rotate the gradients (any float value, -// where 0.0 is no rotation and 1.0 is one full turn) -// The return value is the noise value. -// Partial derivatives are not computed, making these functions faster. -// -// Author: Stefan Gustavson (stefan.gustavson@gmail.com) -// Version 2016-05-10. -// -// Many thanks to Ian McEwan of Ashima Arts for the -// idea of using a permutation polynomial. -// -// Copyright (c) 2016 Stefan Gustavson. All rights reserved. -// Distributed under the MIT license. See LICENSE file. -// https://github.com/stegu/webgl-noise -// - -// Hashed 2-D gradients with an extra rotation. -// (The constant 0.0243902439 is 1/41) -vec2 rgrad2( vec2 p, float rot ) { - - // For more isotropic gradients, sin/cos can be used instead. - float u = permute( permute( p.x ) + p.y ) * 0.0243902439 + rot; // Rotate by shift - u = fract( u ) * 6.28318530718; // 2*pi - return vec2( cos( u ), sin( u )); - -} - -// -// 2-D tiling simplex noise with rotating gradients and analytical derivative. -// The first component of the 3-element return vector is the noise value, -// and the second and third components are the x and y partial derivatives. -// -vec3 psrdnoise(vec2 pos, vec2 per, float rot) { - // Hack: offset y slightly to hide some rare artifacts - pos.y += 0.01; - // Skew to hexagonal grid - vec2 uv = vec2(pos.x + pos.y*0.5, pos.y); - - vec2 i0 = floor(uv); - vec2 f0 = fract(uv); - // Traversal order - vec2 i1 = (f0.x > f0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); - - // Unskewed grid points in (x,y) space - vec2 p0 = vec2(i0.x - i0.y * 0.5, i0.y); - vec2 p1 = vec2(p0.x + i1.x - i1.y * 0.5, p0.y + i1.y); - vec2 p2 = vec2(p0.x + 0.5, p0.y + 1.0); - - // Integer grid point indices in (u,v) space - i1 = i0 + i1; - vec2 i2 = i0 + vec2(1.0, 1.0); - - // Vectors in unskewed (x,y) coordinates from - // each of the simplex corners to the evaluation point - vec2 d0 = pos - p0; - vec2 d1 = pos - p1; - vec2 d2 = pos - p2; - - // Wrap i0, i1 and i2 to the desired period before gradient hashing: - // wrap points in (x,y), map to (u,v) - vec3 xw = mod(vec3(p0.x, p1.x, p2.x), per.x); - vec3 yw = mod(vec3(p0.y, p1.y, p2.y), per.y); - vec3 iuw = xw + 0.5 * yw; - vec3 ivw = yw; - - // Create gradients from indices - vec2 g0 = rgrad2(vec2(iuw.x, ivw.x), rot); - vec2 g1 = rgrad2(vec2(iuw.y, ivw.y), rot); - vec2 g2 = rgrad2(vec2(iuw.z, ivw.z), rot); - - // Gradients dot vectors to corresponding corners - // (The derivatives of this are simply the gradients) - vec3 w = vec3(dot(g0, d0), dot(g1, d1), dot(g2, d2)); - - // Radial weights from corners - // 0.8 is the square of 2/sqrt(5), the distance from - // a grid point to the nearest simplex boundary - vec3 t = 0.8 - vec3(dot(d0, d0), dot(d1, d1), dot(d2, d2)); - - // Partial derivatives for analytical gradient computation - vec3 dtdx = -2.0 * vec3(d0.x, d1.x, d2.x); - vec3 dtdy = -2.0 * vec3(d0.y, d1.y, d2.y); - - // Set influence of each surflet to zero outside radius sqrt(0.8) - if (t.x < 0.0) { - dtdx.x = 0.0; - dtdy.x = 0.0; - t.x = 0.0; - } - if (t.y < 0.0) { - dtdx.y = 0.0; - dtdy.y = 0.0; - t.y = 0.0; - } - if (t.z < 0.0) { - dtdx.z = 0.0; - dtdy.z = 0.0; - t.z = 0.0; - } - - // Fourth power of t (and third power for derivative) - vec3 t2 = t * t; - vec3 t4 = t2 * t2; - vec3 t3 = t2 * t; - - // Final noise value is: - // sum of ((radial weights) times (gradient dot vector from corner)) - float n = dot(t4, w); - - // Final analytical derivative (gradient of a sum of scalar products) - vec2 dt0 = vec2(dtdx.x, dtdy.x) * 4.0 * t3.x; - vec2 dn0 = t4.x * g0 + dt0 * w.x; - vec2 dt1 = vec2(dtdx.y, dtdy.y) * 4.0 * t3.y; - vec2 dn1 = t4.y * g1 + dt1 * w.y; - vec2 dt2 = vec2(dtdx.z, dtdy.z) * 4.0 * t3.z; - vec2 dn2 = t4.z * g2 + dt2 * w.z; - - return 11.0*vec3(n, dn0 + dn1 + dn2); -} - -// -// 2-D tiling simplex noise with fixed gradients -// and analytical derivative. -// This function is implemented as a wrapper to "psrdnoise", -// at the minimal cost of three extra additions. -// -vec3 psdnoise(vec2 pos, vec2 per) { - return psrdnoise(pos, per, 0.0); -} - -// -// 2-D tiling simplex noise with rotating gradients, -// but without the analytical derivative. -// -float psrnoise(vec2 pos, vec2 per, float rot) { - // Offset y slightly to hide some rare artifacts - pos.y += 0.001; - // Skew to hexagonal grid - vec2 uv = vec2(pos.x + pos.y*0.5, pos.y); - - vec2 i0 = floor(uv); - vec2 f0 = fract(uv); - // Traversal order - vec2 i1 = (f0.x > f0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); - - // Unskewed grid points in (x,y) space - vec2 p0 = vec2(i0.x - i0.y * 0.5, i0.y); - vec2 p1 = vec2(p0.x + i1.x - i1.y * 0.5, p0.y + i1.y); - vec2 p2 = vec2(p0.x + 0.5, p0.y + 1.0); - - // Integer grid point indices in (u,v) space - i1 = i0 + i1; - vec2 i2 = i0 + vec2(1.0, 1.0); - - // Vectors in unskewed (x,y) coordinates from - // each of the simplex corners to the evaluation point - vec2 d0 = pos - p0; - vec2 d1 = pos - p1; - vec2 d2 = pos - p2; - - // Wrap i0, i1 and i2 to the desired period before gradient hashing: - // wrap points in (x,y), map to (u,v) - vec3 xw = mod(vec3(p0.x, p1.x, p2.x), per.x); - vec3 yw = mod(vec3(p0.y, p1.y, p2.y), per.y); - vec3 iuw = xw + 0.5 * yw; - vec3 ivw = yw; - - // Create gradients from indices - vec2 g0 = rgrad2(vec2(iuw.x, ivw.x), rot); - vec2 g1 = rgrad2(vec2(iuw.y, ivw.y), rot); - vec2 g2 = rgrad2(vec2(iuw.z, ivw.z), rot); - - // Gradients dot vectors to corresponding corners - // (The derivatives of this are simply the gradients) - vec3 w = vec3(dot(g0, d0), dot(g1, d1), dot(g2, d2)); - - // Radial weights from corners - // 0.8 is the square of 2/sqrt(5), the distance from - // a grid point to the nearest simplex boundary - vec3 t = 0.8 - vec3(dot(d0, d0), dot(d1, d1), dot(d2, d2)); - - // Set influence of each surflet to zero outside radius sqrt(0.8) - t = max(t, 0.0); - - // Fourth power of t - vec3 t2 = t * t; - vec3 t4 = t2 * t2; - - // Final noise value is: - // sum of ((radial weights) times (gradient dot vector from corner)) - float n = dot(t4, w); - - // Rescale to cover the range [-1,1] reasonably well - return 11.0*n; -} - -// -// 2-D tiling simplex noise with fixed gradients, -// without the analytical derivative. -// This function is implemented as a wrapper to "psrnoise", -// at the minimal cost of three extra additions. -// -float psnoise(vec2 pos, vec2 per) { - return psrnoise(pos, per, 0.0); -} - -// -// 2-D non-tiling simplex noise with rotating gradients and analytical derivative. -// The first component of the 3-element return vector is the noise value, -// and the second and third components are the x and y partial derivatives. -// -vec3 srdnoise(vec2 pos, float rot) { - // Offset y slightly to hide some rare artifacts - pos.y += 0.001; - // Skew to hexagonal grid - vec2 uv = vec2(pos.x + pos.y*0.5, pos.y); - - vec2 i0 = floor(uv); - vec2 f0 = fract(uv); - // Traversal order - vec2 i1 = (f0.x > f0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); - - // Unskewed grid points in (x,y) space - vec2 p0 = vec2(i0.x - i0.y * 0.5, i0.y); - vec2 p1 = vec2(p0.x + i1.x - i1.y * 0.5, p0.y + i1.y); - vec2 p2 = vec2(p0.x + 0.5, p0.y + 1.0); - - // Integer grid point indices in (u,v) space - i1 = i0 + i1; - vec2 i2 = i0 + vec2(1.0, 1.0); - - // Vectors in unskewed (x,y) coordinates from - // each of the simplex corners to the evaluation point - vec2 d0 = pos - p0; - vec2 d1 = pos - p1; - vec2 d2 = pos - p2; - - vec3 x = vec3(p0.x, p1.x, p2.x); - vec3 y = vec3(p0.y, p1.y, p2.y); - vec3 iuw = x + 0.5 * y; - vec3 ivw = y; - - // Avoid precision issues in permutation - iuw = mod289(iuw); - ivw = mod289(ivw); - - // Create gradients from indices - vec2 g0 = rgrad2(vec2(iuw.x, ivw.x), rot); - vec2 g1 = rgrad2(vec2(iuw.y, ivw.y), rot); - vec2 g2 = rgrad2(vec2(iuw.z, ivw.z), rot); - - // Gradients dot vectors to corresponding corners - // (The derivatives of this are simply the gradients) - vec3 w = vec3(dot(g0, d0), dot(g1, d1), dot(g2, d2)); - - // Radial weights from corners - // 0.8 is the square of 2/sqrt(5), the distance from - // a grid point to the nearest simplex boundary - vec3 t = 0.8 - vec3(dot(d0, d0), dot(d1, d1), dot(d2, d2)); - - // Partial derivatives for analytical gradient computation - vec3 dtdx = -2.0 * vec3(d0.x, d1.x, d2.x); - vec3 dtdy = -2.0 * vec3(d0.y, d1.y, d2.y); - - // Set influence of each surflet to zero outside radius sqrt(0.8) - if (t.x < 0.0) { - dtdx.x = 0.0; - dtdy.x = 0.0; - t.x = 0.0; - } - if (t.y < 0.0) { - dtdx.y = 0.0; - dtdy.y = 0.0; - t.y = 0.0; - } - if (t.z < 0.0) { - dtdx.z = 0.0; - dtdy.z = 0.0; - t.z = 0.0; - } - - // Fourth power of t (and third power for derivative) - vec3 t2 = t * t; - vec3 t4 = t2 * t2; - vec3 t3 = t2 * t; - - // Final noise value is: - // sum of ((radial weights) times (gradient dot vector from corner)) - float n = dot(t4, w); - - // Final analytical derivative (gradient of a sum of scalar products) - vec2 dt0 = vec2(dtdx.x, dtdy.x) * 4.0 * t3.x; - vec2 dn0 = t4.x * g0 + dt0 * w.x; - vec2 dt1 = vec2(dtdx.y, dtdy.y) * 4.0 * t3.y; - vec2 dn1 = t4.y * g1 + dt1 * w.y; - vec2 dt2 = vec2(dtdx.z, dtdy.z) * 4.0 * t3.z; - vec2 dn2 = t4.z * g2 + dt2 * w.z; - - return 11.0*vec3(n, dn0 + dn1 + dn2); -} - -// -// 2-D non-tiling simplex noise with fixed gradients and analytical derivative. -// This function is implemented as a wrapper to "srdnoise", -// at the minimal cost of three extra additions. -// -vec3 sdnoise(vec2 pos) { - return srdnoise(pos, 0.0); -} - -// -// 2-D non-tiling simplex noise with rotating gradients, -// without the analytical derivative. -// -float srnoise(vec2 pos, float rot) { - // Offset y slightly to hide some rare artifacts - pos.y += 0.001; - // Skew to hexagonal grid - vec2 uv = vec2(pos.x + pos.y*0.5, pos.y); - - vec2 i0 = floor(uv); - vec2 f0 = fract(uv); - // Traversal order - vec2 i1 = (f0.x > f0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); - - // Unskewed grid points in (x,y) space - vec2 p0 = vec2(i0.x - i0.y * 0.5, i0.y); - vec2 p1 = vec2(p0.x + i1.x - i1.y * 0.5, p0.y + i1.y); - vec2 p2 = vec2(p0.x + 0.5, p0.y + 1.0); - - // Integer grid point indices in (u,v) space - i1 = i0 + i1; - vec2 i2 = i0 + vec2(1.0, 1.0); - - // Vectors in unskewed (x,y) coordinates from - // each of the simplex corners to the evaluation point - vec2 d0 = pos - p0; - vec2 d1 = pos - p1; - vec2 d2 = pos - p2; - - // Wrap i0, i1 and i2 to the desired period before gradient hashing: - // wrap points in (x,y), map to (u,v) - vec3 x = vec3(p0.x, p1.x, p2.x); - vec3 y = vec3(p0.y, p1.y, p2.y); - vec3 iuw = x + 0.5 * y; - vec3 ivw = y; - - // Avoid precision issues in permutation - iuw = mod289(iuw); - ivw = mod289(ivw); - - // Create gradients from indices - vec2 g0 = rgrad2(vec2(iuw.x, ivw.x), rot); - vec2 g1 = rgrad2(vec2(iuw.y, ivw.y), rot); - vec2 g2 = rgrad2(vec2(iuw.z, ivw.z), rot); - - // Gradients dot vectors to corresponding corners - // (The derivatives of this are simply the gradients) - vec3 w = vec3(dot(g0, d0), dot(g1, d1), dot(g2, d2)); - - // Radial weights from corners - // 0.8 is the square of 2/sqrt(5), the distance from - // a grid point to the nearest simplex boundary - vec3 t = 0.8 - vec3(dot(d0, d0), dot(d1, d1), dot(d2, d2)); - - // Set influence of each surflet to zero outside radius sqrt(0.8) - t = max(t, 0.0); - - // Fourth power of t - vec3 t2 = t * t; - vec3 t4 = t2 * t2; - - // Final noise value is: - // sum of ((radial weights) times (gradient dot vector from corner)) - float n = dot(t4, w); - - // Rescale to cover the range [-1,1] reasonably well - return 11.0*n; -} - -// -// 2-D non-tiling simplex noise with fixed gradients, -// without the analytical derivative. -// This function is implemented as a wrapper to "srnoise", -// at the minimal cost of three extra additions. -// Note: if this kind of noise is all you want, there are faster -// GLSL implementations of non-tiling simplex noise out there. -// This one is included mainly for completeness and compatibility -// with the other functions in the file. -// -float snoise(vec2 pos) { - return srnoise(pos, 0.0); -} diff --git a/packages/core/src/shaderlib/noise_simplex.glsl b/packages/core/src/shaderlib/noise_simplex.glsl deleted file mode 100644 index 0273d3c274..0000000000 --- a/packages/core/src/shaderlib/noise_simplex.glsl +++ /dev/null @@ -1,4 +0,0 @@ -#include -#include -#include -#include diff --git a/packages/core/src/shaderlib/noise_simplex_2D.glsl b/packages/core/src/shaderlib/noise_simplex_2D.glsl deleted file mode 100644 index 811d1859dd..0000000000 --- a/packages/core/src/shaderlib/noise_simplex_2D.glsl +++ /dev/null @@ -1,60 +0,0 @@ -// -// Description : Array and textureless GLSL 2D simplex noise function. -// Author : Ian McEwan, Ashima Arts. -// Maintainer : stegu -// Lastmod : 20110822 (ijm) -// License : Copyright (C) 2011 Ashima Arts. All rights reserved. -// Distributed under the MIT License. See LICENSE file. -// https://github.com/ashima/webgl-noise -// https://github.com/stegu/webgl-noise -// - -float simplex( vec2 v ) { - - const vec4 C = vec4( 0.211324865405187, // (3.0-sqrt(3.0))/6.0 - 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) - -0.577350269189626, // -1.0 + 2.0 * C.x - 0.024390243902439 ); // 1.0 / 41.0 - // First corner - vec2 i = floor( v + dot( v, C.yy ) ); - vec2 x0 = v - i + dot( i, C.xx ); - - // Other corners - vec2 i1; - //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0 - //i1.y = 1.0 - i1.x; - i1 = ( x0.x > x0.y ) ? vec2( 1.0, 0.0 ) : vec2( 0.0, 1.0 ); - // x0 = x0 - 0.0 + 0.0 * C.xx ; - // x1 = x0 - i1 + 1.0 * C.xx ; - // x2 = x0 - 1.0 + 2.0 * C.xx ; - vec4 x12 = x0.xyxy + C.xxzz; - x12.xy -= i1; - - // Permutations - i = mod289( i ); // Avoid truncation effects in permutation - vec3 p = permute( permute( i.y + vec3( 0.0, i1.y, 1.0 ) ) - + i.x + vec3( 0.0, i1.x, 1.0 ) ); - - vec3 m = max( 0.5 - vec3( dot( x0, x0 ), dot( x12.xy, x12.xy ), dot( x12.zw, x12.zw ) ), 0.0 ); - m = m*m ; - m = m*m ; - - // Gradients: 41 points uniformly over a line, mapped onto a diamond. - // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) - - vec3 x = 2.0 * fract( p * C.www ) - 1.0; - vec3 h = abs( x ) - 0.5; - vec3 ox = floor( x + 0.5 ); - vec3 a0 = x - ox; - - // Normalise gradients implicitly by scaling m - // Approximation of: m *= inversesqrt( a0*a0 + h*h ); - m *= 1.79284291400159 - 0.85373472095314 * ( a0 * a0 + h * h ); - - // Compute final noise value at P - vec3 g; - g.x = a0.x * x0.x + h.x * x0.y; - g.yz = a0.yz * x12.xz + h.yz * x12.yw; - return 130.0 * dot( m, g ); - -} diff --git a/packages/core/src/shaderlib/noise_simplex_3D.glsl b/packages/core/src/shaderlib/noise_simplex_3D.glsl deleted file mode 100644 index c9ec149613..0000000000 --- a/packages/core/src/shaderlib/noise_simplex_3D.glsl +++ /dev/null @@ -1,81 +0,0 @@ -// -// Description : Array and textureless GLSL 2D/3D/4D simplex -// noise functions. -// Author : Ian McEwan, Ashima Arts. -// Maintainer : stegu -// Lastmod : 20110822 (ijm) -// License : Copyright (C) 2011 Ashima Arts. All rights reserved. -// Distributed under the MIT License. See LICENSE file. -// https://github.com/ashima/webgl-noise -// https://github.com/stegu/webgl-noise -// - -float simplex( vec3 v ) { - - const vec2 C = vec2( 1.0 / 6.0, 1.0 / 3.0 ); - const vec4 D = vec4( 0.0, 0.5, 1.0, 2.0 ); - - // First corner - vec3 i = floor( v + dot( v, C.yyy ) ); - vec3 x0 = v - i + dot( i, C.xxx ); - - // Other corners - vec3 g = step( x0.yzx, x0.xyz ); - vec3 l = 1.0 - g; - vec3 i1 = min( g.xyz, l.zxy ); - vec3 i2 = max( g.xyz, l.zxy ); - - vec3 x1 = x0 - i1 + C.xxx; - vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y - vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y - - // Permutations - i = mod289( i ); - vec4 p = permute( permute( permute( - i.z + vec4(0.0, i1.z, i2.z, 1.0 )) - + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) - + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); - - // Gradients: 7x7 points over a square, mapped onto an octahedron. - // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) - float n_ = 0.142857142857; // 1.0/7.0 - vec3 ns = n_ * D.wyz - D.xzx; - - vec4 j = p - 49.0 * floor( p * ns.z * ns.z ); // mod(p,7*7) - - vec4 x_ = floor(j * ns.z); - vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) - - vec4 x = x_ *ns.x + ns.yyyy; - vec4 y = y_ *ns.x + ns.yyyy; - vec4 h = 1.0 - abs( x ) - abs( y ); - - vec4 b0 = vec4( x.xy, y.xy ); - vec4 b1 = vec4( x.zw, y.zw ); - - vec4 s0 = floor( b0 ) * 2.0 + 1.0; - vec4 s1 = floor( b1 ) * 2.0 + 1.0; - vec4 sh = - step( h, vec4( 0.0 ) ); - - vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy ; - vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww ; - - vec3 p0 = vec3( a0.xy, h.x ); - vec3 p1 = vec3( a0.zw, h.y ); - vec3 p2 = vec3( a1.xy, h.z ); - vec3 p3 = vec3( a1.zw, h.w ); - - //Normalise gradients - vec4 norm = taylorInvSqrt( vec4( dot( p0, p0 ), dot( p1, p1 ), dot( p2, p2 ), dot( p3, p3 ) ) ); - p0 *= norm.x; - p1 *= norm.y; - p2 *= norm.z; - p3 *= norm.w; - - // Mix final noise value - vec4 m = max( 0.6 - vec4( dot( x0, x0 ), dot( x1, x1 ), dot( x2, x2 ), dot( x3, x3 ) ), 0.0 ); - m = m * m; - return 42.0 * dot( m*m, vec4( dot( p0, x0 ), dot( p1, x1 ), - dot( p2, x2 ), dot( p3, x3 ) ) ); - -} diff --git a/packages/core/src/shaderlib/noise_simplex_3D_grad.glsl b/packages/core/src/shaderlib/noise_simplex_3D_grad.glsl index 8b106bc0fa..afbf52de62 100644 --- a/packages/core/src/shaderlib/noise_simplex_3D_grad.glsl +++ b/packages/core/src/shaderlib/noise_simplex_3D_grad.glsl @@ -1,16 +1,9 @@ -// -// Description : Array and textureless GLSL 2D/3D/4D simplex -// noise functions. -// Author : Ian McEwan, Ashima Arts. -// Maintainer : stegu -// Lastmod : 20150104 (JcBernack) -// License : Copyright (C) 2011 Ashima Arts. All rights reserved. -// Distributed under the MIT License. See LICENSE file. -// https://github.com/ashima/webgl-noise -// https://github.com/stegu/webgl-noise -// - -float simplex( vec3 v, out vec3 gradient ) { +// 3D simplex noise analytical gradient. +// Algorithm: Ken Perlin, "Noise hardware" (2001) — simplex lattice improvement over classic Perlin noise (1985). +// Curl noise: Robert Bridson et al., "Curl-noise for procedural fluid flow" (2007). +// GLSL implementation: Ian McEwan, Ashima Arts (MIT License) — https://github.com/ashima/webgl-noise + +vec3 simplexGrad( vec3 v ) { const vec2 C = vec2( 1.0 / 6.0, 1.0 / 3.0 ); const vec4 D = vec4( 0.0, 0.5, 1.0, 2.0 ); @@ -78,12 +71,10 @@ float simplex( vec3 v, out vec3 gradient ) { vec4 m4 = m2 * m2; vec4 pdotx = vec4( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 ), dot( p3, x3 ) ); - // Determine noise gradient + // Compute and return noise gradient vec4 temp = m2 * m * pdotx; - gradient = - 8.0 * ( temp.x * x0 + temp.y * x1 + temp.z * x2 + temp.w * x3 ); - gradient += m4.x * p0 + m4.y * p1 + m4.z * p2 + m4.w * p3; - gradient *= 42.0; - - return 42.0 * dot( m4, pdotx ); + vec3 grad = - 8.0 * ( temp.x * x0 + temp.y * x1 + temp.z * x2 + temp.w * x3 ); + grad += m4.x * p0 + m4.y * p1 + m4.z * p2 + m4.w * p3; + return grad * 42.0; } diff --git a/packages/core/src/shaderlib/noise_simplex_4D.glsl b/packages/core/src/shaderlib/noise_simplex_4D.glsl deleted file mode 100644 index eec6c09d5d..0000000000 --- a/packages/core/src/shaderlib/noise_simplex_4D.glsl +++ /dev/null @@ -1,98 +0,0 @@ -// -// Description : Array and textureless GLSL 2D/3D/4D simplex -// noise functions. -// Author : Ian McEwan, Ashima Arts. -// Maintainer : stegu -// Lastmod : 20110822 (ijm) -// License : Copyright (C) 2011 Ashima Arts. All rights reserved. -// Distributed under the MIT License. See LICENSE file. -// https://github.com/ashima/webgl-noise -// https://github.com/stegu/webgl-noise -// - -vec4 grad4( float j, vec4 ip ) { - - const vec4 ones = vec4( 1.0, 1.0, 1.0, -1.0 ); - vec4 p, s; - - p.xyz = floor( fract( vec3( j ) * ip.xyz ) * 7.0 ) * ip.z - 1.0; - p.w = 1.5 - dot( abs( p.xyz ), ones.xyz ); - s = vec4( lessThan( p, vec4( 0.0 ) ) ); - p.xyz = p.xyz + ( s.xyz * 2.0 - 1.0 ) * s.www; - - return p; - -} - -// (sqrt(5) - 1)/4 = F4, used once below -#define F4 0.309016994374947451 - -float simplex(vec4 v) { - - const vec4 C = vec4( 0.138196601125011, // (5 - sqrt(5))/20 G4 - 0.276393202250021, // 2 * G4 - 0.414589803375032, // 3 * G4 - -0.447213595499958); // -1 + 4 * G4 - - // First corner - vec4 i = floor( v + dot( v, vec4( F4 ) ) ); - vec4 x0 = v - i + dot( i, C.xxxx ); - - // Other corners - - // Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI) - vec4 i0; - vec3 isX = step( x0.yzw, x0.xxx ); - vec3 isYZ = step( x0.zww, x0.yyz ); - i0.x = isX.x + isX.y + isX.z; - i0.yzw = 1.0 - isX; - i0.y += isYZ.x + isYZ.y; - i0.zw += 1.0 - isYZ.xy; - i0.z += isYZ.z; - i0.w += 1.0 - isYZ.z; - - vec4 i3 = clamp( i0, 0.0, 1.0 ); - vec4 i2 = clamp( i0 - 1.0, 0.0, 1.0 ); - vec4 i1 = clamp( i0 - 2.0, 0.0, 1.0 ); - - vec4 x1 = x0 - i1 + C.xxxx; - vec4 x2 = x0 - i2 + C.yyyy; - vec4 x3 = x0 - i3 + C.zzzz; - vec4 x4 = x0 + C.wwww; - - // Permutations - i = mod289( i ); - float j0 = permute( permute( permute( permute( i.w ) + i.z ) + i.y ) + i.x ); - vec4 j1 = permute( permute( permute( permute ( - i.w + vec4(i1.w, i2.w, i3.w, 1.0 )) - + i.z + vec4(i1.z, i2.z, i3.z, 1.0 )) - + i.y + vec4(i1.y, i2.y, i3.y, 1.0 )) - + i.x + vec4(i1.x, i2.x, i3.x, 1.0 )); - - // Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope - // 7*7*6 = 294, which is close to the ring size 17*17 = 289. - vec4 ip = vec4( 1.0 / 294.0, 1.0 / 49.0, 1.0 / 7.0, 0.0 ); - - vec4 p0 = grad4(j0, ip); - vec4 p1 = grad4(j1.x, ip); - vec4 p2 = grad4(j1.y, ip); - vec4 p3 = grad4(j1.z, ip); - vec4 p4 = grad4(j1.w, ip); - - // Normalise gradients - vec4 norm = taylorInvSqrt( vec4( dot( p0, p0 ), dot( p1, p1 ), dot( p2, p2 ), dot( p3, p3 ) ) ); - p0 *= norm.x; - p1 *= norm.y; - p2 *= norm.z; - p3 *= norm.w; - p4 *= taylorInvSqrt( dot( p4, p4 ) ); - - // Mix contributions from the five corners - vec3 m0 = max( 0.6 - vec3( dot( x0, x0 ), dot( x1, x1 ), dot( x2, x2 ) ), 0.0 ); - vec2 m1 = max( 0.6 - vec2( dot( x3, x3 ), dot( x4, x4 ) ), 0.0 ); - m0 = m0 * m0; - m1 = m1 * m1; - return 49.0 * ( dot(m0*m0, vec3( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 ))) - + dot(m1*m1, vec2( dot( p3, x3 ), dot( p4, x4 ) ) ) ) ; - -} diff --git a/packages/core/src/shaderlib/particle/index.ts b/packages/core/src/shaderlib/particle/index.ts index 83893232ef..dd67f726f2 100644 --- a/packages/core/src/shaderlib/particle/index.ts +++ b/packages/core/src/shaderlib/particle/index.ts @@ -6,6 +6,7 @@ import color_over_lifetime_module from "./color_over_lifetime_module.glsl"; import texture_sheet_animation_module from "./texture_sheet_animation_module.glsl"; import force_over_lifetime_module from "./force_over_lifetime_module.glsl"; import limit_velocity_over_lifetime_module from "./limit_velocity_over_lifetime_module.glsl"; +import noise_module from "./noise_module.glsl"; import particle_feedback_simulation from "./particle_feedback_simulation.glsl"; import sphere_billboard from "./sphere_billboard.glsl"; @@ -23,6 +24,7 @@ export default { texture_sheet_animation_module, force_over_lifetime_module, limit_velocity_over_lifetime_module, + noise_module, particle_feedback_simulation, sphere_billboard, diff --git a/packages/core/src/shaderlib/particle/noise_module.glsl b/packages/core/src/shaderlib/particle/noise_module.glsl new file mode 100644 index 0000000000..df14f8e471 --- /dev/null +++ b/packages/core/src/shaderlib/particle/noise_module.glsl @@ -0,0 +1,92 @@ +#ifdef RENDERER_NOISE_MODULE_ENABLED + +#include +#include + +uniform vec4 renderer_NoiseParams; // xyz = strength (constant mode only), w = frequency +uniform vec4 renderer_NoiseOctaveParams; // x = scrollSpeed, y = octaveCount, z = octaveIntensityMultiplier, w = octaveFrequencyMultiplier + +#ifdef RENDERER_NOISE_STRENGTH_CURVE + uniform vec2 renderer_NoiseStrengthMaxCurveX[4]; + #ifdef RENDERER_NOISE_IS_SEPARATE + uniform vec2 renderer_NoiseStrengthMaxCurveY[4]; + uniform vec2 renderer_NoiseStrengthMaxCurveZ[4]; + #endif + #ifdef RENDERER_NOISE_STRENGTH_IS_RANDOM_TWO + uniform vec2 renderer_NoiseStrengthMinCurveX[4]; + #ifdef RENDERER_NOISE_IS_SEPARATE + uniform vec2 renderer_NoiseStrengthMinCurveY[4]; + uniform vec2 renderer_NoiseStrengthMinCurveZ[4]; + #endif + #endif +#else + #ifdef RENDERER_NOISE_STRENGTH_IS_RANDOM_TWO + uniform vec3 renderer_NoiseStrengthMinConst; + #endif +#endif + +vec3 sampleCurlNoise3D(vec3 coord) { + float axisOffset = 100.0; + vec3 gradX = simplexGrad(vec3(coord.z, coord.y, coord.x)); + vec3 gradY = simplexGrad(vec3(coord.x + axisOffset, coord.z, coord.y)); + vec3 gradZ = simplexGrad(vec3(coord.y, coord.x + axisOffset, coord.z)); + return vec3( + gradZ.x - gradY.y, + gradX.x - gradZ.y, + gradY.x - gradX.y + ); +} + +vec3 computeNoiseVelocity(vec3 currentPosition, float normalizedAge) { + vec3 coord = currentPosition * renderer_NoiseParams.w + + vec3(renderer_CurrentTime * renderer_NoiseOctaveParams.x); + + int octaveCount = int(renderer_NoiseOctaveParams.y); + float octaveIntensityMultiplier = renderer_NoiseOctaveParams.z; + float octaveFrequencyMultiplier = renderer_NoiseOctaveParams.w; + + vec3 noiseValue = sampleCurlNoise3D(coord); + float totalAmplitude = 1.0; + + // Unrolled octave loop (GLSL ES 1.0 requires constant loop bounds) + if (octaveCount >= 2) { + float amplitude = octaveIntensityMultiplier; + totalAmplitude += amplitude; + noiseValue += amplitude * sampleCurlNoise3D(coord * octaveFrequencyMultiplier); + + if (octaveCount >= 3) { + amplitude *= octaveIntensityMultiplier; + totalAmplitude += amplitude; + noiseValue += amplitude * sampleCurlNoise3D(coord * octaveFrequencyMultiplier * octaveFrequencyMultiplier); + } + } + + // Evaluate strength (supports Constant, TwoConstants, Curve, TwoCurves). + vec3 strength; + #ifdef RENDERER_NOISE_STRENGTH_CURVE + float sx = evaluateParticleCurve(renderer_NoiseStrengthMaxCurveX, normalizedAge); + #ifdef RENDERER_NOISE_STRENGTH_IS_RANDOM_TWO + sx = mix(evaluateParticleCurve(renderer_NoiseStrengthMinCurveX, normalizedAge), sx, a_Random0.z); + #endif + #ifdef RENDERER_NOISE_IS_SEPARATE + float sy = evaluateParticleCurve(renderer_NoiseStrengthMaxCurveY, normalizedAge); + float sz = evaluateParticleCurve(renderer_NoiseStrengthMaxCurveZ, normalizedAge); + #ifdef RENDERER_NOISE_STRENGTH_IS_RANDOM_TWO + sy = mix(evaluateParticleCurve(renderer_NoiseStrengthMinCurveY, normalizedAge), sy, a_Random0.z); + sz = mix(evaluateParticleCurve(renderer_NoiseStrengthMinCurveZ, normalizedAge), sz, a_Random0.z); + #endif + strength = vec3(sx, sy, sz); + #else + strength = vec3(sx); + #endif + #else + strength = renderer_NoiseParams.xyz; + #ifdef RENDERER_NOISE_STRENGTH_IS_RANDOM_TWO + strength = mix(renderer_NoiseStrengthMinConst, strength, a_Random0.z); + #endif + #endif + + return (noiseValue / totalAmplitude) * strength; +} + +#endif diff --git a/packages/core/src/shaderlib/particle/particle_feedback_simulation.glsl b/packages/core/src/shaderlib/particle/particle_feedback_simulation.glsl index b4efd52087..cdb4bfd745 100644 --- a/packages/core/src/shaderlib/particle/particle_feedback_simulation.glsl +++ b/packages/core/src/shaderlib/particle/particle_feedback_simulation.glsl @@ -34,6 +34,7 @@ varying vec3 v_FeedbackVelocity; #include #include #include +#include // Get VOL instantaneous velocity at normalizedAge vec3 getVOLVelocity(float normalizedAge) { @@ -227,16 +228,27 @@ void main() { // World mode: position in world space, velocity rotated to world // ===================================================== // FOL is now fully in localVelocity (both local and world-space FOL). - // Only VOL overlay needs to be added here. + // VOL and Noise overlays are added here (not persisted). + vec3 totalVelocity; if (renderer_SimulationSpace == 0) { - // Local: integrate in local space - totalVelocity = localVelocity + volLocal - + rotationByQuaternions(volWorld, invWorldRotation); + totalVelocity = localVelocity + volLocal + rotationByQuaternions(volWorld, invWorldRotation); } else { - // World: integrate in world space totalVelocity = rotationByQuaternions(localVelocity + volLocal, worldRotation) + volWorld; } + #ifdef RENDERER_NOISE_MODULE_ENABLED + // Use analytical base position (birth + initial velocity * age) instead of + // a_FeedbackPosition to avoid feedback loop: position → noise → velocity → position + vec3 noiseBasePos; + if (renderer_SimulationSpace == 0) { + noiseBasePos = a_ShapePositionStartLifeTime.xyz + a_DirectionTime.xyz * a_StartSpeed * age; + } else { + noiseBasePos = rotationByQuaternions( + a_ShapePositionStartLifeTime.xyz + a_DirectionTime.xyz * a_StartSpeed * age, + worldRotation) + a_SimulationWorldPosition; + } + totalVelocity += computeNoiseVelocity(noiseBasePos, normalizedAge); + #endif vec3 position = a_FeedbackPosition + totalVelocity * dt; v_FeedbackPosition = position; diff --git a/packages/core/src/shaderlib/pbr/ibl_frag_define.glsl b/packages/core/src/shaderlib/pbr/ibl_frag_define.glsl index 6e311c6a9e..9149f30dff 100644 --- a/packages/core/src/shaderlib/pbr/ibl_frag_define.glsl +++ b/packages/core/src/shaderlib/pbr/ibl_frag_define.glsl @@ -2,7 +2,6 @@ // 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) + @@ -57,8 +56,7 @@ vec3 getLightProbeRadiance(Geometry geometry, vec3 normal, float roughness, int return vec3(0); #else vec3 reflectVec = getReflectedVector(geometry, normal); - reflectVec.x = -reflectVec.x; // TextureCube is left-hand,so x need inverse - + float specularMIPLevel = getSpecularMIPLevel(roughness, maxMIPLevel ); #ifdef HAS_TEX_LOD diff --git a/packages/core/src/texture/TextureUtils.ts b/packages/core/src/texture/TextureUtils.ts index 3a3fa94470..d217f5c4cd 100644 --- a/packages/core/src/texture/TextureUtils.ts +++ b/packages/core/src/texture/TextureUtils.ts @@ -43,6 +43,12 @@ export class TextureUtils { case TextureFormat.ETC2_RGB: case TextureFormat.ETC2_RGBA8: case TextureFormat.ASTC_4x4: + case TextureFormat.ASTC_5x5: + case TextureFormat.ASTC_6x6: + case TextureFormat.ASTC_8x8: + case TextureFormat.ASTC_10x10: + case TextureFormat.ASTC_12x12: + case TextureFormat.ETC2_RGBA5: return true; default: return false; diff --git a/packages/loader/src/HDRDecoder.ts b/packages/loader/src/HDRDecoder.ts index 3dd7419192..d0692926f0 100644 --- a/packages/loader/src/HDRDecoder.ts +++ b/packages/loader/src/HDRDecoder.ts @@ -1,23 +1,18 @@ /** - * HDR panorama to cubemap decoder. + * HDR (Radiance RGBE) image decoder. + * + * Decodes .hdr files into pixel data. Supports parsing the header + * and decoding RLE-compressed RGBE scanlines into R16G16B16A16 half-float pixels. */ export class HDRDecoder { - // Float32 to Float16 lookup tables (http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf) private static _float2HalfTables = HDRDecoder._generateFloat2HalfTables(); private static _floatView = new Float32Array(1); private static _uint32View = new Uint32Array(HDRDecoder._floatView.buffer); - private static _one = 0x3c00; // Half float for 1.0 - - // prettier-ignore - private static _faces = [ // Cubemap face corners [bottomLeft, bottomRight, topLeft, topRight] as flat xyz - /* +X */ [ 1,-1,-1, 1,-1, 1, 1, 1,-1, 1, 1, 1], - /* -X */ [-1,-1, 1, -1,-1,-1, -1, 1, 1, -1, 1,-1], - /* +Y */ [-1,-1, 1, 1,-1, 1, -1,-1,-1, 1,-1,-1], - /* -Y */ [-1, 1,-1, 1, 1,-1, -1, 1, 1, 1, 1, 1], - /* +Z */ [-1,-1,-1, 1,-1,-1, -1, 1,-1, 1, 1,-1], - /* -Z */ [ 1,-1, 1, -1,-1, 1, 1, 1, 1, -1, 1, 1] - ]; + /** + * Parse the header of an HDR file. + * @returns Header info including width, height, and data start position. + */ static parseHeader(uint8array: Uint8Array): IHDRHeader { let line = this._readStringLine(uint8array, 0); if (line[0] !== "#" || line[1] !== "?") { @@ -56,140 +51,50 @@ export class HDRDecoder { return { height, width, dataPosition: lineIndex + line.length + 1 }; } - static decodeFaces( - bufferArray: Uint8Array, - header: IHDRHeader, - onFace: (faceIndex: number, data: Uint16Array) => void - ): void { + /** + * Decode an HDR file buffer into R16G16B16A16 half-float pixel data. + * @param buffer - The full HDR file as Uint8Array. + * @returns Object with width, height, and half-float pixel data. + */ + static decode(buffer: Uint8Array): { width: number; height: number; pixels: Uint16Array } { + const header = this.parseHeader(buffer); const { width, height, dataPosition } = header; - const cubeSize = height >> 1; - const pixels = HDRDecoder._readPixels(bufferArray.subarray(dataPosition), width, height); - - const faces = HDRDecoder._faces; - const faceBuffer = new Uint16Array(cubeSize * cubeSize * 4); - for (let faceIndex = 0; faceIndex < 6; faceIndex++) { - HDRDecoder._createCubemapData(cubeSize, faces[faceIndex], pixels, width, height, faceBuffer); - onFace(faceIndex, faceBuffer); - } - } - - private static _generateFloat2HalfTables(): { baseTable: Uint32Array; shiftTable: Uint32Array } { - const baseTable = new Uint32Array(512); - const shiftTable = new Uint32Array(512); - for (let i = 0; i < 256; ++i) { - const e = i - 127; - if (e < -27) { - baseTable[i] = 0x0000; - baseTable[i | 0x100] = 0x8000; - shiftTable[i] = 24; - shiftTable[i | 0x100] = 24; - } else if (e < -14) { - baseTable[i] = 0x0400 >> (-e - 14); - baseTable[i | 0x100] = (0x0400 >> (-e - 14)) | 0x8000; - shiftTable[i] = -e - 1; - shiftTable[i | 0x100] = -e - 1; - } else if (e <= 15) { - baseTable[i] = (e + 15) << 10; - baseTable[i | 0x100] = ((e + 15) << 10) | 0x8000; - shiftTable[i] = 13; - shiftTable[i | 0x100] = 13; - } else if (e < 128) { - baseTable[i] = 0x7c00; - baseTable[i | 0x100] = 0xfc00; - shiftTable[i] = 24; - shiftTable[i | 0x100] = 24; - } else { - baseTable[i] = 0x7c00; - baseTable[i | 0x100] = 0xfc00; - shiftTable[i] = 13; - shiftTable[i | 0x100] = 13; - } - } - return { baseTable, shiftTable }; + const rgbe = this._readPixels(buffer.subarray(dataPosition), width, height); + const pixels = this._rgbeToHalfFloat(rgbe, width, height); + return { width, height, pixels }; } - private static _createCubemapData( - texSize: number, - face: number[], - pixels: Uint8Array, - inputWidth: number, - inputHeight: number, - facePixels: Uint16Array - ): void { - const invSize = 1 / texSize; - const rotDX1X = (face[3] - face[0]) * invSize; - const rotDX1Y = (face[4] - face[1]) * invSize; - const rotDX1Z = (face[5] - face[2]) * invSize; - const rotDX2X = (face[9] - face[6]) * invSize; - const rotDX2Y = (face[10] - face[7]) * invSize; - const rotDX2Z = (face[11] - face[8]) * invSize; - - const floatView = HDRDecoder._floatView; - const uint32View = HDRDecoder._uint32View; - const { baseTable, shiftTable } = HDRDecoder._float2HalfTables; - const one = HDRDecoder._one; - - let fy = 0; - for (let y = 0; y < texSize; y++) { - let xv1X = face[0], - xv1Y = face[1], - xv1Z = face[2]; - let xv2X = face[6], - xv2Y = face[7], - xv2Z = face[8]; - - for (let x = 0; x < texSize; x++) { - let dirX = xv1X + (xv2X - xv1X) * fy; - let dirY = xv1Y + (xv2Y - xv1Y) * fy; - let dirZ = xv1Z + (xv2Z - xv1Z) * fy; - const invLen = 1 / Math.sqrt(dirX * dirX + dirY * dirY + dirZ * dirZ); - dirX *= invLen; - dirY *= invLen; - dirZ *= invLen; - - let px = Math.round(((Math.atan2(dirZ, dirX) / Math.PI) * 0.5 + 0.5) * inputWidth); - if (px < 0) px = 0; - else if (px >= inputWidth) px = inputWidth - 1; - - let py = Math.round((Math.acos(dirY) / Math.PI) * inputHeight); - if (py < 0) py = 0; - else if (py >= inputHeight) py = inputHeight - 1; - - const srcIndex = (inputHeight - py - 1) * inputWidth * 4 + px * 4; - const scaleFactor = Math.pow(2, pixels[srcIndex + 3] - 128) / 255; - const dstIndex = y * texSize * 4 + x * 4; - - for (let c = 0; c < 3; c++) { - // Clamp to half-float max (65504) to prevent Infinity in R16G16B16A16 - floatView[0] = Math.min(pixels[srcIndex + c] * scaleFactor, 65504); - const f = uint32View[0]; - const e = (f >> 23) & 0x1ff; - facePixels[dstIndex + c] = baseTable[e] + ((f & 0x007fffff) >> shiftTable[e]); - } - facePixels[dstIndex + 3] = one; - - xv1X += rotDX1X; - xv1Y += rotDX1Y; - xv1Z += rotDX1Z; - xv2X += rotDX2X; - xv2Y += rotDX2Y; - xv2Z += rotDX2Z; + /** + * Convert RGBE pixel data to R16G16B16A16 half-float. + */ + static _rgbeToHalfFloat(rgbe: Uint8Array, width: number, height: number): Uint16Array { + const floatView = this._floatView; + const uint32View = this._uint32View; + const { baseTable, shiftTable } = this._float2HalfTables; + const one = 0x3c00; // Half float 1.0 + const pixelCount = width * height; + const result = new Uint16Array(pixelCount * 4); + + for (let i = 0; i < pixelCount; i++) { + const srcIdx = i * 4; + const dstIdx = i * 4; + const scaleFactor = Math.pow(2, rgbe[srcIdx + 3] - 128 - 8); + + for (let c = 0; c < 3; c++) { + floatView[0] = Math.min(rgbe[srcIdx + c] * scaleFactor, 65504); + const f = uint32View[0]; + const e = (f >> 23) & 0x1ff; + result[dstIdx + c] = baseTable[e] + ((f & 0x007fffff) >> shiftTable[e]); } - fy += invSize; - } - } - - private static _readStringLine(uint8array: Uint8Array, startIndex: number): string { - let line = ""; - for (let i = startIndex, n = uint8array.length; i < n; i++) { - const character = String.fromCharCode(uint8array[i]); - if (character === "\n") break; - line += character; + result[dstIdx + 3] = one; } - return line; + return result; } - private static _readPixels(buffer: Uint8Array, width: number, height: number): Uint8Array { + /** + * Decode RLE-compressed RGBE scanlines into raw RGBE pixel data. + */ + static _readPixels(buffer: Uint8Array, width: number, height: number): Uint8Array { const byteLength = buffer.byteLength; const dataRGBA = new Uint8Array(4 * width * height); let offset = 0; @@ -236,9 +141,54 @@ export class HDRDecoder { } return dataRGBA; } + + private static _generateFloat2HalfTables(): { baseTable: Uint32Array; shiftTable: Uint32Array } { + const baseTable = new Uint32Array(512); + const shiftTable = new Uint32Array(512); + for (let i = 0; i < 256; ++i) { + const e = i - 127; + if (e < -27) { + baseTable[i] = 0x0000; + baseTable[i | 0x100] = 0x8000; + shiftTable[i] = 24; + shiftTable[i | 0x100] = 24; + } else if (e < -14) { + baseTable[i] = 0x0400 >> (-e - 14); + baseTable[i | 0x100] = (0x0400 >> (-e - 14)) | 0x8000; + shiftTable[i] = -e - 1; + shiftTable[i | 0x100] = -e - 1; + } else if (e <= 15) { + baseTable[i] = (e + 15) << 10; + baseTable[i | 0x100] = ((e + 15) << 10) | 0x8000; + shiftTable[i] = 13; + shiftTable[i | 0x100] = 13; + } else if (e < 128) { + baseTable[i] = 0x7c00; + baseTable[i | 0x100] = 0xfc00; + shiftTable[i] = 24; + shiftTable[i | 0x100] = 24; + } else { + baseTable[i] = 0x7c00; + baseTable[i | 0x100] = 0xfc00; + shiftTable[i] = 13; + shiftTable[i | 0x100] = 13; + } + } + return { baseTable, shiftTable }; + } + + private static _readStringLine(uint8array: Uint8Array, startIndex: number): string { + let line = ""; + for (let i = startIndex, n = uint8array.length; i < n; i++) { + const character = String.fromCharCode(uint8array[i]); + if (character === "\n") break; + line += character; + } + return line; + } } -interface IHDRHeader { +export interface IHDRHeader { width: number; height: number; dataPosition: number; diff --git a/packages/loader/src/SpriteAtlasLoader.ts b/packages/loader/src/SpriteAtlasLoader.ts index 0bada4b334..0d0fe7550f 100644 --- a/packages/loader/src/SpriteAtlasLoader.ts +++ b/packages/loader/src/SpriteAtlasLoader.ts @@ -51,7 +51,7 @@ class SpriteAtlasLoader extends Loader { resourceManager .load({ url: Utils.resolveAbsoluteUrl(item.url, atlasItem.img), - type: atlasItem.type ?? AssetType.Texture2D, + type: atlasItem.type ?? AssetType.Texture, params: { format, mipmap } }) .then((texture: Texture2D) => { diff --git a/packages/loader/src/Texture2DLoader.ts b/packages/loader/src/Texture2DLoader.ts deleted file mode 100644 index 127b37eb96..0000000000 --- a/packages/loader/src/Texture2DLoader.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { - AssetPromise, - AssetType, - ContentRestorer, - LoadItem, - Loader, - RequestConfig, - ResourceManager, - Texture2D, - TextureFilterMode, - TextureFormat, - TextureUtils, - TextureWrapMode, - resourceLoader -} from "@galacean/engine-core"; -import { decode } from "./resource-deserialize"; -import { FileHeader } from "./resource-deserialize/utils/FileHeader"; - -function loadImageFromBuffer(buffer: ArrayBuffer): AssetPromise { - return new AssetPromise((resolve, reject) => { - const blob = new Blob([buffer]); - const img = new Image(); - img.onload = () => { - URL.revokeObjectURL(img.src); - resolve(img); - }; - img.onerror = reject; - img.src = URL.createObjectURL(blob); - }); -} - -@resourceLoader(AssetType.Texture2D, ["png", "jpg", "webp", "jpeg", "tex"]) -class Texture2DLoader extends Loader { - override load(item: LoadItem, resourceManager: ResourceManager): AssetPromise { - const url = item.url; - const requestConfig = { ...item, type: "arraybuffer" }; - return new AssetPromise((resolve, reject, setTaskCompleteProgress, setTaskDetailProgress) => { - resourceManager - // @ts-ignore - ._request(url, requestConfig) - .onProgress(setTaskCompleteProgress, setTaskDetailProgress) - .then((buffer) => { - if (FileHeader.checkMagic(buffer)) { - decode(buffer, resourceManager.engine).then((texture) => { - resourceManager.addContentRestorer(new Texture2DContentRestorer(texture, url, requestConfig)); - resolve(texture); - }, reject); - } else { - loadImageFromBuffer(buffer).then((img) => { - const texture = this._createTexture(img, item, resourceManager); - resourceManager.addContentRestorer(new Texture2DContentRestorer(texture, url, requestConfig)); - resolve(texture); - }, reject); - } - }) - .catch(reject); - }); - } - - private _createTexture(img: HTMLImageElement, item: LoadItem, resourceManager: ResourceManager): Texture2D { - const { - format = TextureFormat.R8G8B8A8, - anisoLevel, - wrapModeU, - wrapModeV, - filterMode, - isSRGBColorSpace = true, - mipmap = true - } = (item.params as Partial) ?? {}; - const { width, height } = img; - const engine = resourceManager.engine; - - const generateMipmap = TextureUtils.supportGenerateMipmapsWithCorrection( - engine, - width, - height, - format, - mipmap, - isSRGBColorSpace - ); - - const texture = new Texture2D(engine, width, height, format, generateMipmap, isSRGBColorSpace); - texture.anisoLevel = anisoLevel ?? texture.anisoLevel; - texture.filterMode = filterMode ?? texture.filterMode; - texture.wrapModeU = wrapModeU ?? texture.wrapModeU; - texture.wrapModeV = wrapModeV ?? texture.wrapModeV; - texture.setImageSource(img); - generateMipmap && texture.generateMipmaps(); - - const url = item.url; - if (url.indexOf("data:") !== 0) { - texture.name = url.substring(url.lastIndexOf("/") + 1); - } - - return texture; - } -} - -class Texture2DContentRestorer extends ContentRestorer { - constructor( - resource: Texture2D, - public url: string, - public requestConfig: RequestConfig - ) { - super(resource); - } - - override restoreContent(): AssetPromise { - const texture = this.resource; - const engine = texture.engine; - return ( - engine.resourceManager - // @ts-ignore - ._request(this.url, this.requestConfig) - .then((buffer) => { - if (FileHeader.checkMagic(buffer)) { - return decode(buffer, engine, texture); - } else { - return loadImageFromBuffer(buffer).then((img) => { - texture.setImageSource(img); - texture.generateMipmaps(); - return texture; - }); - } - }) - ); - } -} - -/** - * Texture2D loader params interface. - */ -export interface Texture2DParams { - /** Texture format. default `TextureFormat.R8G8B8A8` */ - format: TextureFormat; - /** Whether to use multi-level texture, default is true. */ - mipmap: boolean; - /** Wrapping mode for texture coordinate S. */ - wrapModeU: TextureWrapMode; - /** Wrapping mode for texture coordinate T. */ - wrapModeV: TextureWrapMode; - /** Filter mode for texture. */ - filterMode: TextureFilterMode; - /** Anisotropic level for texture. */ - anisoLevel: number; - /** Whether the texture data is in sRGB color space, otherwise is linear color space. @defaultValue `true` */ - isSRGBColorSpace: boolean; -} diff --git a/packages/loader/src/TextureCubeLoader.ts b/packages/loader/src/TextureCubeLoader.ts deleted file mode 100644 index aee01285f1..0000000000 --- a/packages/loader/src/TextureCubeLoader.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { - AssetPromise, - AssetType, - ContentRestorer, - LoadItem, - Loader, - RequestConfig, - ResourceManager, - SystemInfo, - TextureCube, - TextureCubeFace, - TextureFormat, - resourceLoader -} from "@galacean/engine-core"; -import { HDRDecoder } from "./HDRDecoder"; - -@resourceLoader(AssetType.TextureCube, ["texCube", "hdr"]) -class TextureCubeLoader extends Loader { - override load(item: LoadItem, resourceManager: ResourceManager): AssetPromise { - return new AssetPromise((resolve, reject) => { - const engine = resourceManager.engine; - const url = item.url; - const requestConfig = { ...item, type: "arraybuffer" } as RequestConfig; - resourceManager - // @ts-ignore - ._request(url, requestConfig) - .then((buffer) => { - if (!SystemInfo.supportsTextureFormat(engine, TextureFormat.R16G16B16A16)) { - reject(new Error("TextureCubeLoader: HDR texture requires half float support.")); - return; - } - const { mipmap = true, anisoLevel, wrapModeU, wrapModeV, filterMode } = item.params ?? {}; - const bufferArray = new Uint8Array(buffer); - const header = HDRDecoder.parseHeader(bufferArray); - const texture = new TextureCube(engine, header.height >> 1, TextureFormat.R16G16B16A16, mipmap, false); - HDRDecoder.decodeFaces(bufferArray, header, (faceIndex, data) => { - texture.setPixelBuffer(TextureCubeFace.PositiveX + faceIndex, data, 0); - }); - texture.generateMipmaps(); - texture.anisoLevel = anisoLevel ?? texture.anisoLevel; - texture.filterMode = filterMode ?? texture.filterMode; - texture.wrapModeU = wrapModeU ?? texture.wrapModeU; - texture.wrapModeV = wrapModeV ?? texture.wrapModeV; - resourceManager.addContentRestorer(new HDRContentRestorer(texture, url, requestConfig)); - resolve(texture); - }) - .catch(reject); - }); - } -} - -class HDRContentRestorer extends ContentRestorer { - constructor( - resource: TextureCube, - public url: string, - public requestConfig: RequestConfig - ) { - super(resource); - } - - override restoreContent(): AssetPromise { - return new AssetPromise((resolve, reject) => { - const resource = this.resource; - resource.engine.resourceManager - // @ts-ignore - ._request(this.url, this.requestConfig) - .then((buffer) => { - const bufferArray = new Uint8Array(buffer); - HDRDecoder.decodeFaces(bufferArray, HDRDecoder.parseHeader(bufferArray), (faceIndex, data) => { - resource.setPixelBuffer(TextureCubeFace.PositiveX + faceIndex, data, 0); - }); - resource.generateMipmaps(); - resolve(resource); - }) - .catch(reject); - }); - } -} diff --git a/packages/loader/src/TextureLoader.ts b/packages/loader/src/TextureLoader.ts new file mode 100644 index 0000000000..ff669d6c59 --- /dev/null +++ b/packages/loader/src/TextureLoader.ts @@ -0,0 +1,192 @@ +import { + AssetPromise, + AssetType, + ContentRestorer, + LoadItem, + Loader, + RequestConfig, + ResourceManager, + SystemInfo, + Texture, + Texture2D, + TextureFilterMode, + TextureFormat, + TextureUtils, + TextureWrapMode, + resourceLoader +} from "@galacean/engine-core"; +import { decode } from "./resource-deserialize"; +import { FileHeader } from "./resource-deserialize/utils/FileHeader"; +import { HDRDecoder } from "./HDRDecoder"; + +@resourceLoader(AssetType.Texture, ["tex", "png", "jpg", "webp", "jpeg", "hdr"]) +class TextureLoader extends Loader { + override load(item: LoadItem, resourceManager: ResourceManager): AssetPromise { + const url = item.url; + const requestConfig = { ...item, type: "arraybuffer" }; + return new AssetPromise((resolve, reject, setTaskCompleteProgress, setTaskDetailProgress) => { + resourceManager + // @ts-ignore + ._request(url, requestConfig) + .onProgress(setTaskCompleteProgress, setTaskDetailProgress) + .then((buffer) => { + this._decode(buffer, item, resourceManager).then((texture) => { + resourceManager.addContentRestorer(new TextureContentRestorer(texture, url, requestConfig)); + resolve(texture); + }, reject); + }) + .catch(reject); + }); + } + + private _decode(buffer: ArrayBuffer, item: LoadItem, resourceManager: ResourceManager): AssetPromise { + if (FileHeader.checkMagic(buffer)) { + return decode(buffer, resourceManager.engine); + } + + const bufferView = new Uint8Array(buffer); + const isHDR = bufferView[0] === 0x23 && bufferView[1] === 0x3f; + + if (isHDR) { + return this._decodeHDR(bufferView, item, resourceManager); + } + return this._decodeImage(buffer, item, resourceManager); + } + + private _decodeHDR(buffer: Uint8Array, item: LoadItem, resourceManager: ResourceManager): AssetPromise { + return new AssetPromise((resolve, reject) => { + const engine = resourceManager.engine; + if (!SystemInfo.supportsTextureFormat(engine, TextureFormat.R16G16B16A16)) { + reject(new Error("TextureLoader: HDR texture requires half float support.")); + return; + } + const { width, height, pixels } = HDRDecoder.decode(buffer); + const { mipmap = true } = (item.params as Partial) ?? {}; + + const texture = new Texture2D(engine, width, height, TextureFormat.R16G16B16A16, mipmap, false); + texture.setPixelBuffer(pixels); + mipmap && texture.generateMipmaps(); + this._applyParams(texture, item); + resolve(texture); + }); + } + + private _decodeImage(buffer: ArrayBuffer, item: LoadItem, resourceManager: ResourceManager): AssetPromise { + return new AssetPromise((resolve, reject) => { + const blob = new Blob([buffer]); + const img = new Image(); + img.onload = () => { + URL.revokeObjectURL(img.src); + const { + format = TextureFormat.R8G8B8A8, + isSRGBColorSpace = true, + mipmap = true + } = (item.params as Partial) ?? {}; + + const engine = resourceManager.engine; + const { width, height } = img; + const generateMipmap = TextureUtils.supportGenerateMipmapsWithCorrection( + engine, + width, + height, + format, + mipmap, + isSRGBColorSpace + ); + + const texture = new Texture2D(engine, width, height, format, generateMipmap, isSRGBColorSpace); + texture.setImageSource(img); + generateMipmap && texture.generateMipmaps(); + this._applyParams(texture, item); + resolve(texture); + }; + img.onerror = (e) => { + URL.revokeObjectURL(img.src); + reject(e); + }; + img.src = URL.createObjectURL(blob); + }); + } + + private _applyParams(texture: Texture2D, item: LoadItem): void { + const { anisoLevel, wrapModeU, wrapModeV, filterMode } = (item.params as Partial) ?? {}; + texture.anisoLevel = anisoLevel ?? texture.anisoLevel; + texture.filterMode = filterMode ?? texture.filterMode; + texture.wrapModeU = wrapModeU ?? texture.wrapModeU; + texture.wrapModeV = wrapModeV ?? texture.wrapModeV; + + const url = item.url; + if (url.indexOf("data:") !== 0) { + texture.name = url.substring(url.lastIndexOf("/") + 1); + } + } +} + +class TextureContentRestorer extends ContentRestorer { + constructor( + resource: Texture, + public url: string, + public requestConfig: RequestConfig + ) { + super(resource); + } + + override restoreContent(): AssetPromise { + return ( + this.resource.engine.resourceManager + // @ts-ignore + ._request(this.url, this.requestConfig) + .then((buffer) => { + if (FileHeader.checkMagic(buffer)) { + return decode(buffer, this.resource.engine, this.resource); + } + + const bufferView = new Uint8Array(buffer); + const texture = this.resource as Texture2D; + + if (bufferView[0] === 0x23 && bufferView[1] === 0x3f) { + const { pixels } = HDRDecoder.decode(bufferView); + texture.setPixelBuffer(pixels); + texture.mipmapCount > 1 && texture.generateMipmaps(); + return texture; + } + + return new AssetPromise((resolve, reject) => { + const blob = new Blob([buffer]); + const img = new Image(); + img.onload = () => { + URL.revokeObjectURL(img.src); + texture.setImageSource(img); + texture.mipmapCount > 1 && texture.generateMipmaps(); + resolve(texture); + }; + img.onerror = (e) => { + URL.revokeObjectURL(img.src); + reject(e); + }; + img.src = URL.createObjectURL(blob); + }); + }) + ); + } +} + +/** + * Texture loader params interface. + */ +export interface TextureParams { + /** Texture format. default `TextureFormat.R8G8B8A8` */ + format: TextureFormat; + /** Whether to use multi-level texture, default is true. */ + mipmap: boolean; + /** Wrapping mode for texture coordinate S. */ + wrapModeU: TextureWrapMode; + /** Wrapping mode for texture coordinate T. */ + wrapModeV: TextureWrapMode; + /** Filter mode for texture. */ + filterMode: TextureFilterMode; + /** Anisotropic level for texture. */ + anisoLevel: number; + /** Whether the texture data is in sRGB color space. @defaultValue `true` */ + isSRGBColorSpace: boolean; +} diff --git a/packages/loader/src/gltf/GLTFUtils.ts b/packages/loader/src/gltf/GLTFUtils.ts index 35f6488010..a8164a290b 100644 --- a/packages/loader/src/gltf/GLTFUtils.ts +++ b/packages/loader/src/gltf/GLTFUtils.ts @@ -364,11 +364,13 @@ export class GLTFUtils { const blob = new window.Blob([imageBuffer], { type }); const img = new Image(); img.onerror = function () { + URL.revokeObjectURL(img.src); reject(new Error("Failed to load image buffer")); }; img.onload = function () { // Call requestAnimationFrame to avoid iOS's bug. requestAnimationFrame(() => { + URL.revokeObjectURL(img.src); resolve(img); img.onload = null; img.onerror = null; diff --git a/packages/loader/src/gltf/parser/GLTFTextureParser.ts b/packages/loader/src/gltf/parser/GLTFTextureParser.ts index 4da7f1cc5b..fd0524ab2b 100644 --- a/packages/loader/src/gltf/parser/GLTFTextureParser.ts +++ b/packages/loader/src/gltf/parser/GLTFTextureParser.ts @@ -44,7 +44,7 @@ export class GLTFTextureParser extends GLTFParser { if (uri) { const extIndex = uri.lastIndexOf("."); const ext = uri.substring(extIndex + 1); - const type = ext.startsWith("ktx") ? AssetType.KTX : AssetType.Texture2D; + const type = ext.startsWith("ktx") ? AssetType.KTX : AssetType.Texture; texture = engine.resourceManager .load({ url: Utils.resolveAbsoluteUrl(url, uri), diff --git a/packages/loader/src/index.ts b/packages/loader/src/index.ts index 31417dee5c..22ec3c1d2c 100644 --- a/packages/loader/src/index.ts +++ b/packages/loader/src/index.ts @@ -16,8 +16,7 @@ import "./SourceFontLoader"; import "./SpriteAtlasLoader"; import "./SpriteLoader"; import "./TextLoader"; -import "./Texture2DLoader"; -import "./TextureCubeLoader"; +import "./TextureLoader"; import "./AudioLoader"; import "./ktx2/KTX2Loader"; import "./ShaderLoader"; @@ -28,7 +27,7 @@ import "./RenderTargetLoader"; export { GLTFLoader } from "./GLTFLoader"; export type { GLTFParams } from "./GLTFLoader"; export * from "./SceneLoader"; -export type { Texture2DParams } from "./Texture2DLoader"; +export type { TextureParams } from "./TextureLoader"; export { parseSingleKTX } from "./compressed-texture"; export * from "./gltf"; export { KTX2Loader } from "./ktx2/KTX2Loader"; diff --git a/packages/loader/src/resource-deserialize/index.ts b/packages/loader/src/resource-deserialize/index.ts index b50923d1cb..dd3c5ad4af 100644 --- a/packages/loader/src/resource-deserialize/index.ts +++ b/packages/loader/src/resource-deserialize/index.ts @@ -7,7 +7,8 @@ export * from "./resources/animationClip/AnimationClipDecoder"; export type { IModelMesh } from "./resources/mesh/IModelMesh"; export { MeshDecoder } from "./resources/mesh/MeshDecoder"; export { ReflectionParser } from "./resources/parser/ReflectionParser"; -export { Texture2DDecoder } from "./resources/texture2D/TextureDecoder"; +import "./resources/texture2D/Texture2DDecoder"; +import "./resources/textureCube/TextureCubeDecoder"; /** * Decode engine binary resource. diff --git a/packages/loader/src/resource-deserialize/resources/texture2D/Texture2DDecoder.ts b/packages/loader/src/resource-deserialize/resources/texture2D/Texture2DDecoder.ts new file mode 100644 index 0000000000..c0061e89cd --- /dev/null +++ b/packages/loader/src/resource-deserialize/resources/texture2D/Texture2DDecoder.ts @@ -0,0 +1,59 @@ +import { AssetPromise, Engine, Texture2D, TextureFormat } from "@galacean/engine-core"; +import { BufferReader } from "../../utils/BufferReader"; +import { decoder } from "../../utils/Decorator"; +import { HDRDecoder } from "../../../HDRDecoder"; + +/** + * Data format: [url] [mipmap(1B)] [filterMode(1B)] [anisoLevel(1B)] [wrapModeU(1B)] [wrapModeV(1B)] + * [format(1B)] [width(2B)] [height(2B)] [isSRGBColorSpace(1B)] [Uint32(imageSize) + imageBytes] + */ +@decoder("Texture2D") +class Texture2DDecoder { + static decode(engine: Engine, bufferReader: BufferReader, restoredTexture?: Texture2D): AssetPromise { + return new AssetPromise((resolve, reject) => { + const url = bufferReader.nextStr(); + const mipmap = !!bufferReader.nextUint8(); + const filterMode = bufferReader.nextUint8(); + const anisoLevel = bufferReader.nextUint8(); + const wrapModeU = bufferReader.nextUint8(); + const wrapModeV = bufferReader.nextUint8(); + const format = bufferReader.nextUint8(); + const width = bufferReader.nextUint16(); + const height = bufferReader.nextUint16(); + const isSRGBColorSpace = !!bufferReader.nextUint8(); + + const imageData = bufferReader.nextImagesData(1)[0]; + + const isHDR = imageData[0] === 0x23 && imageData[1] === 0x3f; + const textureFormat = isHDR ? TextureFormat.R16G16B16A16 : format; + const texture = + restoredTexture || + new Texture2D(engine, width, height, textureFormat, mipmap, isHDR ? false : isSRGBColorSpace); + texture.filterMode = filterMode; + texture.anisoLevel = anisoLevel; + texture.wrapModeU = wrapModeU; + texture.wrapModeV = wrapModeV; + + if (isHDR) { + const { pixels } = HDRDecoder.decode(imageData); + texture.setPixelBuffer(pixels); + mipmap && texture.generateMipmaps(); + resolve(texture); + } else { + const blob = new Blob([imageData]); + const img = new Image(); + img.onload = () => { + URL.revokeObjectURL(img.src); + texture.setImageSource(img); + mipmap && texture.generateMipmaps(); + resolve(texture); + }; + img.onerror = (e) => { + URL.revokeObjectURL(img.src); + reject(e); + }; + img.src = URL.createObjectURL(blob); + } + }); + } +} diff --git a/packages/loader/src/resource-deserialize/resources/texture2D/TextureDecoder.ts b/packages/loader/src/resource-deserialize/resources/texture2D/TextureDecoder.ts deleted file mode 100644 index 05de1b8be2..0000000000 --- a/packages/loader/src/resource-deserialize/resources/texture2D/TextureDecoder.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { AssetPromise, Engine, Texture2D } from "@galacean/engine-core"; -import { BufferReader } from "../../utils/BufferReader"; -import { decoder } from "../../utils/Decorator"; - -/** - * Data format: [url] [mipmap(1B)] [filterMode(1B)] [anisoLevel(1B)] [wrapModeU(1B)] [wrapModeV(1B)] - * [format(1B)] [width(2B)] [height(2B)] [isPixelBuffer(1B)] [isSRGBColorSpace(1B)] [mipCount(1B)] [imageData...] - */ -@decoder("Texture2D") -export class Texture2DDecoder { - static decode(engine: Engine, bufferReader: BufferReader, restoredTexture?: Texture2D): AssetPromise { - return new AssetPromise((resolve, reject) => { - const url = bufferReader.nextStr(); - const mipmap = !!bufferReader.nextUint8(); - const filterMode = bufferReader.nextUint8(); - const anisoLevel = bufferReader.nextUint8(); - const wrapModeU = bufferReader.nextUint8(); - const wrapModeV = bufferReader.nextUint8(); - const format = bufferReader.nextUint8(); - const width = bufferReader.nextUint16(); - const height = bufferReader.nextUint16(); - const isPixelBuffer = bufferReader.nextUint8(); - const isSRGBColorSpace = !!bufferReader.nextUint8(); - - const mipCount = bufferReader.nextUint8(); - const imagesData = bufferReader.nextImagesData(mipCount); - - const texture2D = restoredTexture || new Texture2D(engine, width, height, format, mipmap, isSRGBColorSpace); - texture2D.filterMode = filterMode; - texture2D.anisoLevel = anisoLevel; - texture2D.wrapModeU = wrapModeU; - texture2D.wrapModeV = wrapModeV; - - if (isPixelBuffer) { - const pixelBuffer = imagesData[0]; - texture2D.setPixelBuffer(pixelBuffer); - if (mipmap) { - texture2D.generateMipmaps(); - for (let i = 1; i < mipCount; i++) { - const pixelBuffer = imagesData[i]; - texture2D.setPixelBuffer(pixelBuffer, i); - } - } - // @ts-ignore - engine.resourceManager._objectPool[url] = texture2D; - resolve(texture2D); - } else { - const blob = new window.Blob([imagesData[0]]); - const img = new Image(); - img.onload = () => { - texture2D.setImageSource(img); - let completedCount = 0; - const onComplete = () => { - completedCount++; - if (completedCount >= mipCount) { - resolve(texture2D); - } - }; - onComplete(); - if (mipmap) { - texture2D.generateMipmaps(); - for (let i = 1; i < mipCount; i++) { - const blob = new window.Blob([imagesData[i]]); - const img = new Image(); - img.onload = () => { - texture2D.setImageSource(img, i); - onComplete(); - }; - img.src = URL.createObjectURL(blob); - } - } - }; - img.src = URL.createObjectURL(blob); - } - }); - } -} diff --git a/packages/loader/src/resource-deserialize/resources/textureCube/TextureCubeDecoder.ts b/packages/loader/src/resource-deserialize/resources/textureCube/TextureCubeDecoder.ts new file mode 100644 index 0000000000..9b8aad970d --- /dev/null +++ b/packages/loader/src/resource-deserialize/resources/textureCube/TextureCubeDecoder.ts @@ -0,0 +1,64 @@ +import { AssetPromise, Engine, TextureCube, TextureCubeFace, TextureFormat } from "@galacean/engine-core"; +import { BufferReader } from "../../utils/BufferReader"; +import { decoder } from "../../utils/Decorator"; +import { HDRDecoder } from "../../../HDRDecoder"; + +/** + * Data format: [url] [mipmap(1B)] [filterMode(1B)] [anisoLevel(1B)] [wrapModeU(1B)] [wrapModeV(1B)] + * [format(1B)] [faceSize(2B)] [isSRGBColorSpace(1B)] [Uint32(size) + faceBytes] × 6 + */ +@decoder("TextureCube") +class TextureCubeDecoder { + static decode(engine: Engine, bufferReader: BufferReader, restoredTexture?: TextureCube): AssetPromise { + return new AssetPromise((resolve, reject) => { + const url = bufferReader.nextStr(); + const mipmap = !!bufferReader.nextUint8(); + const filterMode = bufferReader.nextUint8(); + const anisoLevel = bufferReader.nextUint8(); + const wrapModeU = bufferReader.nextUint8(); + const wrapModeV = bufferReader.nextUint8(); + const format = bufferReader.nextUint8(); + const faceSize = bufferReader.nextUint16(); + const isSRGBColorSpace = !!bufferReader.nextUint8(); + + const facesData = bufferReader.nextImagesData(6); + + // Detect format by first face's magic bytes + const isHDR = facesData[0][0] === 0x23 && facesData[0][1] === 0x3f; + const textureFormat = isHDR ? TextureFormat.R16G16B16A16 : format; + const texture = restoredTexture || new TextureCube(engine, faceSize, textureFormat, mipmap, isSRGBColorSpace); + texture.filterMode = filterMode; + texture.anisoLevel = anisoLevel; + texture.wrapModeU = wrapModeU; + texture.wrapModeV = wrapModeV; + + if (isHDR) { + for (let i = 0; i < 6; i++) { + const { pixels } = HDRDecoder.decode(facesData[i]); + texture.setPixelBuffer(TextureCubeFace.PositiveX + i, pixels, 0); + } + mipmap && texture.generateMipmaps(); + resolve(texture); + } else { + let loadedCount = 0; + for (let i = 0; i < 6; i++) { + const blob = new Blob([facesData[i]]); + const img = new Image(); + img.onload = () => { + URL.revokeObjectURL(img.src); + texture.setImageSource(TextureCubeFace.PositiveX + i, img); + if (++loadedCount === 6) { + mipmap && texture.generateMipmaps(); + resolve(texture); + } + }; + img.onerror = (e) => { + URL.revokeObjectURL(img.src); + reject(e); + }; + img.src = URL.createObjectURL(blob); + } + } + }); + } +} diff --git a/packages/math/src/BoundingBox.ts b/packages/math/src/BoundingBox.ts index 9db87c8513..da387edaca 100644 --- a/packages/math/src/BoundingBox.ts +++ b/packages/math/src/BoundingBox.ts @@ -8,9 +8,6 @@ import { Vector3, Vector3Like } from "./Vector3"; * Axis Aligned Bound Box (AABB). */ export class BoundingBox implements IClone, ICopy { - private static _tempVec30: Vector3 = new Vector3(); - private static _tempVec31: Vector3 = new Vector3(); - /** * Calculate a bounding box from the center point and the extent of the bounding box. * @param center - The center point @@ -67,26 +64,46 @@ export class BoundingBox implements IClone, ICopy 0 ? e0 * minX : e0 < 0 ? e0 * maxX : 0) + + (e4 > 0 ? e4 * minY : e4 < 0 ? e4 * maxY : 0) + + (e8 > 0 ? e8 * minZ : e8 < 0 ? e8 * maxZ : 0) + + e[12], + (e1 > 0 ? e1 * minX : e1 < 0 ? e1 * maxX : 0) + + (e5 > 0 ? e5 * minY : e5 < 0 ? e5 * maxY : 0) + + (e9 > 0 ? e9 * minZ : e9 < 0 ? e9 * maxZ : 0) + + e[13], + (e2 > 0 ? e2 * minX : e2 < 0 ? e2 * maxX : 0) + + (e6 > 0 ? e6 * minY : e6 < 0 ? e6 * maxY : 0) + + (e10 > 0 ? e10 * minZ : e10 < 0 ? e10 * maxZ : 0) + + e[14] + ); + + out.max.set( + (e0 > 0 ? e0 * maxX : e0 < 0 ? e0 * minX : 0) + + (e4 > 0 ? e4 * maxY : e4 < 0 ? e4 * minY : 0) + + (e8 > 0 ? e8 * maxZ : e8 < 0 ? e8 * minZ : 0) + + e[12], + (e1 > 0 ? e1 * maxX : e1 < 0 ? e1 * minX : 0) + + (e5 > 0 ? e5 * maxY : e5 < 0 ? e5 * minY : 0) + + (e9 > 0 ? e9 * maxZ : e9 < 0 ? e9 * minZ : 0) + + e[13], + (e2 > 0 ? e2 * maxX : e2 < 0 ? e2 * minX : 0) + + (e6 > 0 ? e6 * maxY : e6 < 0 ? e6 * minY : 0) + + (e10 > 0 ? e10 * maxZ : e10 < 0 ? e10 * minZ : 0) + + e[14] ); - // set min、max - Vector3.subtract(center, extent, out.min); - Vector3.add(center, extent, out.max); } /** diff --git a/packages/rhi-webgl/src/GLTexture.ts b/packages/rhi-webgl/src/GLTexture.ts index bac9949baa..3570296e47 100644 --- a/packages/rhi-webgl/src/GLTexture.ts +++ b/packages/rhi-webgl/src/GLTexture.ts @@ -181,7 +181,9 @@ export class GLTexture implements IPlatformTexture { }; case TextureFormat.ETC2_RGBA5: return { - internalFormat: GLCompressedTextureInternalFormat.RGB8_PUNCHTHROUGH_ALPHA1_ETC2, + internalFormat: isSRGBColorSpace + ? GLCompressedTextureInternalFormat.SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 + : GLCompressedTextureInternalFormat.RGB8_PUNCHTHROUGH_ALPHA1_ETC2, isCompressed: true }; case TextureFormat.ETC2_RGBA8: @@ -220,27 +222,37 @@ export class GLTexture implements IPlatformTexture { }; case TextureFormat.ASTC_5x5: return { - internalFormat: GLCompressedTextureInternalFormat.RGBA_ASTC_5X5_KHR, + internalFormat: isSRGBColorSpace + ? GLCompressedTextureInternalFormat.SRGB8_ALPHA8_ASTC_5X5_KHR + : GLCompressedTextureInternalFormat.RGBA_ASTC_5X5_KHR, isCompressed: true }; case TextureFormat.ASTC_6x6: return { - internalFormat: GLCompressedTextureInternalFormat.RGBA_ASTC_6X6_KHR, + internalFormat: isSRGBColorSpace + ? GLCompressedTextureInternalFormat.SRGB8_ALPHA8_ASTC_6X6_KHR + : GLCompressedTextureInternalFormat.RGBA_ASTC_6X6_KHR, isCompressed: true }; case TextureFormat.ASTC_8x8: return { - internalFormat: GLCompressedTextureInternalFormat.RGBA_ASTC_8X8_KHR, + internalFormat: isSRGBColorSpace + ? GLCompressedTextureInternalFormat.SRGB8_ALPHA8_ASTC_8X8_KHR + : GLCompressedTextureInternalFormat.RGBA_ASTC_8X8_KHR, isCompressed: true }; case TextureFormat.ASTC_10x10: return { - internalFormat: GLCompressedTextureInternalFormat.RGBA_ASTC_10X10_KHR, + internalFormat: isSRGBColorSpace + ? GLCompressedTextureInternalFormat.SRGB8_ALPHA8_ASTC_10X10_KHR + : GLCompressedTextureInternalFormat.RGBA_ASTC_10X10_KHR, isCompressed: true }; case TextureFormat.ASTC_12x12: return { - internalFormat: GLCompressedTextureInternalFormat.RGBA_ASTC_12X12_KHR, + internalFormat: isSRGBColorSpace + ? GLCompressedTextureInternalFormat.SRGB8_ALPHA8_ASTC_12X12_KHR + : GLCompressedTextureInternalFormat.RGBA_ASTC_12X12_KHR, isCompressed: true }; diff --git a/packages/shader/src/shaders/shadingPBR/LightIndirectFunctions.glsl b/packages/shader/src/shaders/shadingPBR/LightIndirectFunctions.glsl index a714db7764..ad019e1348 100644 --- a/packages/shader/src/shaders/shadingPBR/LightIndirectFunctions.glsl +++ b/packages/shader/src/shaders/shadingPBR/LightIndirectFunctions.glsl @@ -22,8 +22,7 @@ vec3 getLightProbeRadiance(SurfaceData surfaceData, vec3 normal, float roughness 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 diff --git a/packages/shader/src/shaders/shadingPBR/LightIndirectPBR.glsl b/packages/shader/src/shaders/shadingPBR/LightIndirectPBR.glsl index 24dc25d223..2e47a3fa08 100644 --- a/packages/shader/src/shaders/shadingPBR/LightIndirectPBR.glsl +++ b/packages/shader/src/shaders/shadingPBR/LightIndirectPBR.glsl @@ -21,7 +21,6 @@ // 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) + diff --git a/packages/ui/README.md b/packages/ui/README.md index 06d695907f..ac591b3617 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -51,7 +51,7 @@ const image = imageEntity.addComponent(Image); engine.resourceManager .load({ url: "https://xxx.png", - type: AssetType.Texture2D + type: AssetType.Texture }) .then((texture) => { image.sprite = new Sprite(engine, texture); diff --git a/tests/src/core/DeviceLost.test.ts b/tests/src/core/DeviceLost.test.ts index bd0af34adb..d016bc2701 100644 --- a/tests/src/core/DeviceLost.test.ts +++ b/tests/src/core/DeviceLost.test.ts @@ -53,16 +53,16 @@ describe("Device lost test", function () { type: AssetType.AmbientLight }); const textureCube = await engine.resourceManager.load({ - url: "https://gw.alipayobjects.com/os/bmw-prod/10c5d68d-8580-4bd9-8795-6f1035782b94.bin", // sunset_1K - type: AssetType.TextureCube + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*dKYuQKLX9M4AAAAAgBAAAAgAekp5AQ/kloofendal_48d_partly_cloudy_puresky_1k.tex", + type: AssetType.Texture }); const ktx2Texture = await resourceManager.load({ url: "https://mdn.alipayobjects.com/oasis_be/afts/img/A*iaD4QaUJRKoAAAAAAAAAAAAADkp5AQ/original/DefaultTexture.ktx2", type: AssetType.KTX2 }); const editorTexture = await resourceManager.load({ - url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*oFd_T4ffoUMAAAAAQ8AAAAgAekp5AQ/Internal/Material/Internal/Material/DefaultTexture.tex", - type: AssetType.Texture2D + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*mePuTKQ_vzoAAAAAQKAAAAgAekp5AQ/20260305153504.tex", + type: AssetType.Texture }); await new Promise((resolve) => { diff --git a/tests/src/core/particle/Burst.test.ts b/tests/src/core/particle/Burst.test.ts new file mode 100644 index 0000000000..6823b328b1 --- /dev/null +++ b/tests/src/core/particle/Burst.test.ts @@ -0,0 +1,187 @@ +import { + Burst, + Camera, + Engine, + ParticleCompositeCurve, + ParticleMaterial, + ParticleRenderer, + ParticleStopMode +} from "@galacean/engine-core"; +import { Color } from "@galacean/engine-math"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { beforeAll, describe, expect, it } from "vitest"; + +function updateEngine(engine: Engine, frames: number, deltaTime = 100) { + //@ts-ignore + engine._vSyncCount = Infinity; + //@ts-ignore + engine._time._lastSystemTime = 0; + let times = 0; + performance.now = function () { + times++; + return times * deltaTime; + }; + for (let i = 0; i < frames; i++) { + engine.update(); + } +} + +describe("Burst", () => { + let engine: Engine; + + beforeAll(async function () { + engine = await WebGLEngine.create({ + canvas: document.createElement("canvas") + }); + + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity("root"); + const cameraEntity = rootEntity.createChild("Camera"); + cameraEntity.addComponent(Camera); + cameraEntity.transform.setPosition(0, 0, 10); + + engine.run(); + }); + + it("Default constructor", () => { + const burst = new Burst(0.5, new ParticleCompositeCurve(10), 1, 0.01); + expect(burst.time).to.equal(0.5); + expect(burst.count.evaluate(undefined, undefined)).to.equal(10); + expect(burst.cycles).to.equal(1); + expect(burst.repeatInterval).to.equal(0.01); + }); + + it("Constructor with cycles and repeatInterval", () => { + const burst = new Burst(0.5, new ParticleCompositeCurve(10), 3, 0.2); + expect(burst.time).to.equal(0.5); + expect(burst.count.evaluate(undefined, undefined)).to.equal(10); + expect(burst.cycles).to.equal(3); + expect(burst.repeatInterval).to.equal(0.2); + }); + + it("Modify cycles and repeatInterval", () => { + const burst = new Burst(0, new ParticleCompositeCurve(5), 1, 0.01); + burst.cycles = 5; + burst.repeatInterval = 0.1; + expect(burst.cycles).to.equal(5); + expect(burst.repeatInterval).to.equal(0.1); + }); + + it("Single cycle backward compatible", () => { + const scene = engine.sceneManager.activeScene; + const entity = scene.createRootEntity("SingleCycle"); + const renderer = entity.addComponent(ParticleRenderer); + const material = new ParticleMaterial(engine); + material.baseColor = new Color(1, 1, 1, 1); + renderer.setMaterial(material); + + const generator = renderer.generator; + generator.useAutoRandomSeed = false; + generator.main.duration = 5; + generator.main.isLoop = false; + generator.main.maxParticles = 1000; + generator.main.startLifetime.constant = 10; + generator.emission.rateOverTime.constant = 0; + + // Default cycles=1 should behave like original + generator.emission.addBurst(new Burst(0, new ParticleCompositeCurve(10), 1, 0.01)); + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + + // 20 frames at 100ms = 2s, burst at t=0 fires once + updateEngine(engine, 20); + expect(generator._getAliveParticleCount()).to.equal(10); + + entity.destroy(); + }); + + it("Burst cycles emit multiple times", () => { + const scene = engine.sceneManager.activeScene; + const entity = scene.createRootEntity("BurstCycles"); + const renderer = entity.addComponent(ParticleRenderer); + const material = new ParticleMaterial(engine); + material.baseColor = new Color(1, 1, 1, 1); + renderer.setMaterial(material); + + const generator = renderer.generator; + generator.useAutoRandomSeed = false; + generator.main.duration = 5; + generator.main.isLoop = false; + generator.main.maxParticles = 1000; + generator.main.startLifetime.constant = 10; + generator.emission.rateOverTime.constant = 0; + + // Burst at t=0, count=10, cycles=3, interval=0.5 -> fires at 0s, 0.5s, 1.0s + generator.emission.addBurst(new Burst(0, new ParticleCompositeCurve(10), 3, 0.5)); + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + + // 15 frames at 100ms = 1.5s, all 3 cycles should have fired + updateEngine(engine, 15); + expect(generator._getAliveParticleCount()).to.equal(30); + + entity.destroy(); + }); + + it("Burst cycles survive float drift (no skipped cycle)", () => { + const scene = engine.sceneManager.activeScene; + const entity = scene.createRootEntity("FloatDrift"); + const renderer = entity.addComponent(ParticleRenderer); + const material = new ParticleMaterial(engine); + material.baseColor = new Color(1, 1, 1, 1); + renderer.setMaterial(material); + + const generator = renderer.generator; + generator.useAutoRandomSeed = false; + generator.main.duration = 5; + generator.main.isLoop = false; + generator.main.maxParticles = 1000; + generator.main.startLifetime.constant = 10; + generator.emission.rateOverTime.constant = 0; + + // interval=0.1 triggers IEEE 754 drift (e.g. 0.1+0.2 != 0.3). Without tolerance in the + // cycle-index math, `ceil((startTime - burstTime) / 0.1)` can land at cycle + 1 and + // permanently skip a cycle at accumulated integer-multiple times. + generator.emission.addBurst(new Burst(0, new ParticleCompositeCurve(1), 10, 0.1)); + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + + // 15 frames at 100ms = 1.5s, all 10 cycles (0, 0.1, 0.2, ..., 0.9) must have fired + updateEngine(engine, 15); + expect(generator._getAliveParticleCount()).to.equal(10); + + entity.destroy(); + }); + + it("Burst cycles=Infinity emits infinitely within duration", () => { + const scene = engine.sceneManager.activeScene; + const entity = scene.createRootEntity("InfiniteCycles"); + const renderer = entity.addComponent(ParticleRenderer); + const material = new ParticleMaterial(engine); + material.baseColor = new Color(1, 1, 1, 1); + renderer.setMaterial(material); + + const generator = renderer.generator; + generator.useAutoRandomSeed = false; + generator.main.duration = 2; + generator.main.isLoop = false; + generator.main.maxParticles = 1000; + generator.main.startLifetime.constant = 10; + generator.emission.rateOverTime.constant = 0; + + // cycles=Infinity means unlimited, interval=0.5 -> fires at 0, 0.5, 1.0, 1.5 within duration=2 + generator.emission.addBurst(new Burst(0, new ParticleCompositeCurve(5), Infinity, 0.5)); + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + + // 25 frames at 100ms = 2.5s, should fire 4 times (at 0, 0.5, 1.0, 1.5) = 20 particles + updateEngine(engine, 25); + expect(generator._getAliveParticleCount()).to.equal(20); + + entity.destroy(); + }); +}); diff --git a/tests/src/core/particle/ParticleBoundingBox.test.ts b/tests/src/core/particle/ParticleBoundingBox.test.ts index 8b4e78b8f5..24f38f6e94 100644 --- a/tests/src/core/particle/ParticleBoundingBox.test.ts +++ b/tests/src/core/particle/ParticleBoundingBox.test.ts @@ -404,6 +404,73 @@ describe("ParticleBoundingBox", function () { ); }); + it("ShapeTransform-Position", function () { + const shape = new BoxShape(); + shape.position.set(5, 0, 0); + particleRenderer.generator.emission.shape = shape; + + // Same as default BoxShape bounds shifted by (5,0,0) + testParticleRendererBounds( + engine, + particleRenderer, + { x: 3.086, y: -1.914, z: -26.914 }, + { x: 6.914, y: 1.914, z: 1.914 }, + delta + ); + }); + + it("ShapeTransform-Rotation", function () { + const shape = new BoxShape(); + shape.size.set(1, 2, 1); + shape.rotation.set(0, 0, 90); + particleRenderer.generator.emission.shape = shape; + + // size(1,2,1): local pos range (-0.5,-1,-0.5)~(0.5,1,0.5) + // rotated 90 Z: x<->y swapped -> (-1,-0.5,-0.5)~(1,0.5,0.5) + testParticleRendererBounds( + engine, + particleRenderer, + { x: -2.414, y: -1.914, z: -26.914 }, + { x: 2.414, y: 1.914, z: 1.914 }, + delta + ); + }); + + it("ShapeTransform-Scale", function () { + const shape = new BoxShape(); + shape.scale.set(3, 1, 1); + particleRenderer.generator.emission.shape = shape; + + // Default box pos range (-0.5,-0.5,-0.5)~(0.5,0.5,0.5), X scaled 3x -> (-1.5,...)~(1.5,...) + testParticleRendererBounds( + engine, + particleRenderer, + { x: -2.914, y: -1.914, z: -26.914 }, + { x: 2.914, y: 1.914, z: 1.914 }, + delta + ); + }); + + it("ShapeTransform-Combined", function () { + const shape = new BoxShape(); + shape.position.set(0, 0, 5); + shape.rotation.set(0, 0, 90); + shape.scale.set(2, 1, 1); + particleRenderer.generator.emission.shape = shape; + + // Default size(1,1,1): local (-0.5,-0.5,-0.5)~(0.5,0.5,0.5) + // scale(2,1,1) -> (-1,-0.5,-0.5)~(1,0.5,0.5) + // rotate 90 Z: x<->y -> (-0.5,-1,-0.5)~(0.5,1,0.5) + // + position(0,0,5) -> (-0.5,-1,4.5)~(0.5,1,5.5) + testParticleRendererBounds( + engine, + particleRenderer, + { x: -1.914, y: -2.414, z: -21.914 }, + { x: 1.914, y: 2.414, z: 6.914 }, + delta + ); + }); + it("Transform", function () { entity.transform.position.set(1, 2, 3); testParticleRendererBounds( diff --git a/tests/src/core/particle/ParticleShapeTransform.test.ts b/tests/src/core/particle/ParticleShapeTransform.test.ts new file mode 100644 index 0000000000..d3ac6d2ac6 --- /dev/null +++ b/tests/src/core/particle/ParticleShapeTransform.test.ts @@ -0,0 +1,270 @@ +import { BoxShape, SphereShape, ConeShape } from "@galacean/engine-core"; +import { BoundingBox, Rand, Vector3 } from "@galacean/engine-math"; +import { describe, beforeEach, expect, it } from "vitest"; + +describe("ParticleShapeTransform", function () { + const position = new Vector3(); + const direction = new Vector3(); + const rand = new Rand(0, 1234); + const epsilon = 1e-5; + + describe("Position offset", function () { + it("should offset generated position by shape position", function () { + const shape = new BoxShape(); + shape.size.set(0, 0, 0); + shape.position.set(3, 5, 7); + + shape._generatePositionAndDirection(rand, 0, position, direction); + + expect(position.x).to.be.closeTo(3, epsilon); + expect(position.y).to.be.closeTo(5, epsilon); + expect(position.z).to.be.closeTo(7, epsilon); + }); + + it("should offset position range by shape position", function () { + const shape = new BoxShape(); + shape.size.set(2, 2, 2); + shape.position.set(10, 0, 0); + + const bounds = new BoundingBox(); + shape._getPositionRange(bounds); + + expect(bounds.min.x).to.be.closeTo(9, epsilon); + expect(bounds.max.x).to.be.closeTo(11, epsilon); + expect(bounds.min.y).to.be.closeTo(-1, epsilon); + expect(bounds.max.y).to.be.closeTo(1, epsilon); + }); + }); + + describe("Rotation", function () { + it("should rotate position range by shape rotation", function () { + const shape = new BoxShape(); + shape.size.set(2, 0, 0); + shape.rotation.set(0, 0, 90); + + const bounds = new BoundingBox(); + shape._getPositionRange(bounds); + + // Local range: (-1,0,0) to (1,0,0), rotated 90 around Z -> (0,-1,0) to (0,1,0) + expect(bounds.min.x).to.be.closeTo(0, epsilon); + expect(bounds.max.x).to.be.closeTo(0, epsilon); + expect(bounds.min.y).to.be.closeTo(-1, epsilon); + expect(bounds.max.y).to.be.closeTo(1, epsilon); + }); + + it("should rotate box position range", function () { + const shape = new BoxShape(); + shape.size.set(2, 4, 2); + shape.rotation.set(0, 0, 90); + + const bounds = new BoundingBox(); + shape._getPositionRange(bounds); + + // Original range: (-1,-2,-1) to (1,2,1) + // After 90 Z rotation: x<->y swapped + expect(bounds.min.x).to.be.closeTo(-2, epsilon); + expect(bounds.max.x).to.be.closeTo(2, epsilon); + expect(bounds.min.y).to.be.closeTo(-1, epsilon); + expect(bounds.max.y).to.be.closeTo(1, epsilon); + }); + + it("sphere bounds should be conservative after rotation", function () { + const shape = new SphereShape(); + shape.radius = 2; + + const boundsBefore = new BoundingBox(); + shape._getPositionRange(boundsBefore); + const minBefore = new Vector3(); + const maxBefore = new Vector3(); + minBefore.copyFrom(boundsBefore.min); + maxBefore.copyFrom(boundsBefore.max); + + shape.rotation.set(45, 30, 60); + const boundsAfter = new BoundingBox(); + shape._getPositionRange(boundsAfter); + + // Arvo rotates the AABB (cube), which expands it. Bounds should be >= original. + expect(boundsAfter.min.x).to.be.lessThanOrEqual(minBefore.x + epsilon); + expect(boundsAfter.min.y).to.be.lessThanOrEqual(minBefore.y + epsilon); + expect(boundsAfter.min.z).to.be.lessThanOrEqual(minBefore.z + epsilon); + expect(boundsAfter.max.x).to.be.greaterThanOrEqual(maxBefore.x - epsilon); + expect(boundsAfter.max.y).to.be.greaterThanOrEqual(maxBefore.y - epsilon); + expect(boundsAfter.max.z).to.be.greaterThanOrEqual(maxBefore.z - epsilon); + }); + }); + + describe("Scale", function () { + it("should scale generated position", function () { + const shape = new BoxShape(); + shape.size.set(0, 0, 0); + shape.position.set(1, 0, 0); + shape.scale.set(3, 1, 1); + + shape._generatePositionAndDirection(rand, 0, position, direction); + + // position (0,0,0) scaled then rotated then + position offset (1,0,0) + expect(position.x).to.be.closeTo(1, epsilon); + expect(position.y).to.be.closeTo(0, epsilon); + }); + + it("should scale position range", function () { + const shape = new BoxShape(); + shape.size.set(2, 2, 2); + shape.scale.set(3, 1, 1); + + const bounds = new BoundingBox(); + shape._getPositionRange(bounds); + + // Original: (-1,-1,-1) to (1,1,1), scaled X by 3 + expect(bounds.min.x).to.be.closeTo(-3, epsilon); + expect(bounds.max.x).to.be.closeTo(3, epsilon); + expect(bounds.min.y).to.be.closeTo(-1, epsilon); + expect(bounds.max.y).to.be.closeTo(1, epsilon); + }); + + it("negative scale should flip and reorder min/max", function () { + const shape = new BoxShape(); + shape.size.set(2, 2, 2); + shape.scale.set(-1, 1, 1); + + const bounds = new BoundingBox(); + shape._getPositionRange(bounds); + + // After negative X scale, reorder ensures min < max + expect(bounds.min.x).to.be.closeTo(-1, epsilon); + expect(bounds.max.x).to.be.closeTo(1, epsilon); + }); + }); + + describe("Combined transform", function () { + it("should apply scale then rotation then position", function () { + const shape = new BoxShape(); + shape.size.set(0, 0, 0); + shape.position.set(0, 0, 5); + shape.rotation.set(0, 90, 0); + shape.scale.set(2, 1, 1); + + // Generate from zero-size box at origin + shape._generatePositionAndDirection(rand, 0, position, direction); + + // Local pos (0,0,0) -> scale -> (0,0,0) -> rotate -> (0,0,0) -> + offset (0,0,5) + expect(position.x).to.be.closeTo(0, epsilon); + expect(position.y).to.be.closeTo(0, epsilon); + expect(position.z).to.be.closeTo(5, epsilon); + }); + }); + + describe("Default transform (no-op fast path)", function () { + it("should produce identical results when no transform set", function () { + const shape1 = new BoxShape(); + const shape2 = new BoxShape(); + shape1.size.set(2, 3, 4); + shape2.size.set(2, 3, 4); + + const bounds1 = new BoundingBox(); + const bounds2 = new BoundingBox(); + + shape1._getPositionRange(bounds1); + shape2._getPositionRange(bounds2); + + expect(bounds1.min.x).to.be.closeTo(bounds2.min.x, epsilon); + expect(bounds1.min.y).to.be.closeTo(bounds2.min.y, epsilon); + expect(bounds1.min.z).to.be.closeTo(bounds2.min.z, epsilon); + expect(bounds1.max.x).to.be.closeTo(bounds2.max.x, epsilon); + expect(bounds1.max.y).to.be.closeTo(bounds2.max.y, epsilon); + expect(bounds1.max.z).to.be.closeTo(bounds2.max.z, epsilon); + }); + }); + + describe("Direction range", function () { + it("should rotate direction range by shape rotation", function () { + const shape = new BoxShape(); + // Default direction range: min=(0,0,-1), max=(0,0,0) + shape.rotation.set(90, 0, 0); + + const min = new Vector3(); + const max = new Vector3(); + shape._getDirectionRange(min, max); + + // (0,0,-1) rotated 90 around X -> (0,1,0) + // Rotated AABB: min=(0,0,0), max=(0,1,0) + expect(min.x).to.be.closeTo(0, epsilon); + expect(min.y).to.be.closeTo(0, epsilon); + expect(min.z).to.be.closeTo(0, epsilon); + expect(max.y).to.be.closeTo(1, epsilon); + }); + }); + + describe("Clone", function () { + // Simulate CloneManager: deepClone calls copyFrom, then _cloneTo + function simulateClone(source: BoxShape): BoxShape { + const clone = new BoxShape(); + // @deepClone step: copyFrom on existing Vector3 (preserves constructor-bound callbacks) + clone.position.copyFrom(source.position); + clone.rotation.copyFrom(source.rotation); + clone.scale.copyFrom(source.scale); + return clone; + } + + it("cloned shape should have correct transform values", function () { + const shape = new BoxShape(); + shape.position.set(1, 2, 3); + shape.rotation.set(45, 0, 0); + shape.scale.set(2, 2, 2); + + const clone = simulateClone(shape); + + expect(clone.position.x).to.be.closeTo(1, epsilon); + expect(clone.position.y).to.be.closeTo(2, epsilon); + expect(clone.position.z).to.be.closeTo(3, epsilon); + expect(clone.rotation.x).to.be.closeTo(45, epsilon); + expect(clone.scale.x).to.be.closeTo(2, epsilon); + }); + + it("cloned shape should rebuild matrix correctly", function () { + const shape = new BoxShape(); + shape.size.set(2, 0, 0); + shape.rotation.set(0, 0, 90); + + const clone = simulateClone(shape); + clone.size.set(2, 0, 0); + + const boundsOrig = new BoundingBox(); + shape._getPositionRange(boundsOrig); + + const boundsClone = new BoundingBox(); + clone._getPositionRange(boundsClone); + + // Both should have: local (-1,0,0)~(1,0,0) rotated 90Z -> (0,-1,0)~(0,1,0) + expect(boundsClone.min.x).to.be.closeTo(boundsOrig.min.x, epsilon); + expect(boundsClone.min.y).to.be.closeTo(boundsOrig.min.y, epsilon); + expect(boundsClone.max.x).to.be.closeTo(boundsOrig.max.x, epsilon); + expect(boundsClone.max.y).to.be.closeTo(boundsOrig.max.y, epsilon); + }); + + it("modifying clone should not affect original", function () { + const shape = new BoxShape(); + shape.position.set(1, 2, 3); + + const clone = simulateClone(shape); + clone.position.set(10, 20, 30); + + expect(shape.position.x).to.be.closeTo(1, epsilon); + expect(shape.position.y).to.be.closeTo(2, epsilon); + expect(shape.position.z).to.be.closeTo(3, epsilon); + }); + + it("clone callback should trigger on cloned shape", function () { + const shape = new BoxShape(); + const clone = simulateClone(shape); + + let notified = false; + clone._registerOnValueChanged(() => { + notified = true; + }); + + clone.position.x = 10; + expect(notified).to.be.true; + }); + }); +}); diff --git a/tests/src/core/physics/PhysicsScene.test.ts b/tests/src/core/physics/PhysicsScene.test.ts index fb28579400..09ac931682 100644 --- a/tests/src/core/physics/PhysicsScene.test.ts +++ b/tests/src/core/physics/PhysicsScene.test.ts @@ -504,7 +504,6 @@ describe("Physics Test", () => { expect(physicsScene.raycast(ray, Number.MAX_VALUE, Layer.Everything, outHitResult)).to.eq(false); const rootEntityCharacter = root.createChild("root_character"); - rootEntityCharacter.layer = Layer.Layer3; rootEntityCharacter.transform.position = new Vector3(0, 0, 0); const characterController = rootEntityCharacter.addComponent(CharacterController); diff --git a/tests/src/math/BoundingBox.test.ts b/tests/src/math/BoundingBox.test.ts index 71e4a8245a..b097f2891b 100644 --- a/tests/src/math/BoundingBox.test.ts +++ b/tests/src/math/BoundingBox.test.ts @@ -63,8 +63,9 @@ describe("BoundingBox test", () => { new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE) ); BoundingBox.transform(maxValueBox, matrixWithoutScale, newBox); - expect(newBox.min).to.deep.eq(compare.set(-Infinity, -Infinity, -Infinity)); - expect(newBox.max).to.deep.eq(compare.set(Infinity, Infinity, Infinity)); + // Identity rotation * MAX_VALUE = MAX_VALUE, adding small translation doesn't overflow + expect(Math.abs(newBox.min.x)).eq(Number.MAX_VALUE); + expect(Math.abs(newBox.max.x)).eq(Number.MAX_VALUE); BoundingBox.transform(maxValueBox, matrixWithScale, newBox); expect(newBox.min).to.deep.eq(compare.set(-Infinity, -Infinity, -Infinity)); expect(newBox.max).to.deep.eq(compare.set(Infinity, Infinity, Infinity));