From 5f77293d7e3b5980fac9efd2fb14e071e7da9529 Mon Sep 17 00:00:00 2001 From: ChenMo Date: Tue, 24 Feb 2026 15:53:58 +0800 Subject: [PATCH] Optimize TrailRenderer texture scale and remove widthMultiplier (#2889) * feat: update trail texture scaling to support separate X and Y scales --- e2e/case/trailRenderer-basic.ts | 6 +-- .../originImage/Trail_trailRenderer-basic.jpg | 4 +- .../core/src/shaderlib/extra/trail.vs.glsl | 14 +++-- packages/core/src/trail/TrailRenderer.ts | 51 ++++++++++--------- tests/src/core/Trail.test.ts | 34 +++++-------- 5 files changed, 49 insertions(+), 60 deletions(-) diff --git a/e2e/case/trailRenderer-basic.ts b/e2e/case/trailRenderer-basic.ts index d816d7cf3..6a54e6c15 100644 --- a/e2e/case/trailRenderer-basic.ts +++ b/e2e/case/trailRenderer-basic.ts @@ -211,12 +211,12 @@ WebGLEngine.create({ material.emissiveColor.copyFrom(config.emissive); trail.setMaterial(material); trail.time = config.time; - trail.width = config.width; trail.minVertexDistance = 0.15; trailMaterials.push(material); - // Tapered width curve - trail.widthCurve = new ParticleCurve(new CurveKey(0, 1), new CurveKey(0.8, 0.3), new CurveKey(1, 0)); + // Tapered width curve (width baked into curve values) + const w = config.width; + trail.widthCurve = new ParticleCurve(new CurveKey(0, w), new CurveKey(0.8, 0.3 * w), new CurveKey(1, 0)); // Color gradient const gradient = new ParticleGradient( diff --git a/e2e/fixtures/originImage/Trail_trailRenderer-basic.jpg b/e2e/fixtures/originImage/Trail_trailRenderer-basic.jpg index 94ba73900..daa93e289 100644 --- a/e2e/fixtures/originImage/Trail_trailRenderer-basic.jpg +++ b/e2e/fixtures/originImage/Trail_trailRenderer-basic.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b51b7d04cf5e0c04fe6f5bb333b96a1dc0b4fa903cb74730ccf10fe76e4c1804 -size 61042 +oid sha256:3e1e20c0df21d8aacf8b23d89f3fc61c2c6f13b025397f07ddfdfad6c3571e1c +size 61033 diff --git a/packages/core/src/shaderlib/extra/trail.vs.glsl b/packages/core/src/shaderlib/extra/trail.vs.glsl index cb5d0f461..960ce0978 100644 --- a/packages/core/src/shaderlib/extra/trail.vs.glsl +++ b/packages/core/src/shaderlib/extra/trail.vs.glsl @@ -2,7 +2,7 @@ attribute vec4 a_PositionBirthTime; // xyz: World position, w: Birth time (used attribute vec4 a_CornerTangent; // x: Corner (-1 or 1), yzw: Tangent direction attribute float a_Distance; // Absolute cumulative distance (written once per point) -uniform vec4 renderer_TrailParams; // x: Width, y: TextureMode (0: Stretch, 1: Tile), z: TextureScale +uniform vec4 renderer_TrailParams; // x: TextureMode (0: Stretch, 1: Tile), y: TextureScaleX, z: TextureScaleY uniform vec2 renderer_DistanceParams; // x: HeadDistance, y: TailDistance uniform vec3 camera_Position; uniform mat4 camera_ViewMat; @@ -41,17 +41,15 @@ void main() { } right = right * inversesqrt(rightLenSq); - float widthMultiplier = evaluateParticleCurve(renderer_WidthCurve, min(relativePos, renderer_CurveMaxTime.z)); - float width = renderer_TrailParams.x * widthMultiplier; + float width = evaluateParticleCurve(renderer_WidthCurve, min(relativePos, renderer_CurveMaxTime.z)); vec3 worldPosition = position + right * width * 0.5 * corner; gl_Position = camera_ProjMat * camera_ViewMat * vec4(worldPosition, 1.0); - // UV: u=corner side, v=position along trail - float u = corner * 0.5 + 0.5; - // Stretch: normalize to 0-1, Tile: use world distance directly - float v = renderer_TrailParams.y == 0.0 ? relativePos : distFromHead; - v_uv = vec2(u, v * renderer_TrailParams.z); + // u = position along trail (affected by textureMode), v = corner side. + float u = renderer_TrailParams.x == 0.0 ? relativePos : distFromHead; + float v = corner * 0.5 + 0.5; + v_uv = vec2(u * renderer_TrailParams.y, v * renderer_TrailParams.z); v_color = evaluateParticleGradient(renderer_ColorKeys, renderer_CurveMaxTime.x, renderer_AlphaKeys, renderer_CurveMaxTime.y, relativePos); } diff --git a/packages/core/src/trail/TrailRenderer.ts b/packages/core/src/trail/TrailRenderer.ts index 440ba9272..b8d97f436 100644 --- a/packages/core/src/trail/TrailRenderer.ts +++ b/packages/core/src/trail/TrailRenderer.ts @@ -60,7 +60,9 @@ export class TrailRenderer extends Renderer { // Shader parameters @deepClone - private _trailParams = new Vector4(1.0, TrailTextureMode.Stretch, 1.0, 0); // x: width, y: textureMode, z: textureScale + private _trailParams = new Vector4(TrailTextureMode.Stretch, 1.0, 1.0, 0); // x: textureMode, y: textureScaleX, z: textureScaleY + @deepClone + private _textureScale = new Vector2(1.0, 1.0); @ignoreClone private _distanceParams = new Vector2(); // x: headDistance, y: tailDistance @ignoreClone @@ -120,36 +122,26 @@ export class TrailRenderer extends Renderer { } /** - * The width of the trail. + * The texture mapping mode for the trail. */ - get width(): number { + get textureMode(): TrailTextureMode { return this._trailParams.x; } - set width(value: number) { + set textureMode(value: TrailTextureMode) { this._trailParams.x = value; } /** - * The texture mapping mode for the trail. + * Scale of the UV coordinates. + * x scales the coordinate along the trail, y scales the coordinate across the trail. */ - get textureMode(): TrailTextureMode { - return this._trailParams.y; + get textureScale(): Vector2 { + return this._textureScale; } - set textureMode(value: TrailTextureMode) { - this._trailParams.y = value; - } - - /** - * The texture scale when using Tile texture mode. - */ - get textureScale(): number { - return this._trailParams.z; - } - - set textureScale(value: number) { - this._trailParams.z = value; + set textureScale(value: Vector2) { + value !== this._textureScale && this._textureScale.copyFrom(value); } /** @@ -157,6 +149,9 @@ export class TrailRenderer extends Renderer { */ constructor(entity: Entity) { super(entity); + // @ts-ignore + this._textureScale._onValueChanged = this._onTextureScaleChanged.bind(this); + this._onTextureScaleChanged(); this._initGeometry(); } @@ -299,16 +294,16 @@ export class TrailRenderer extends Renderer { // Only expand by half width when there's actual/upcoming trail geometry if (hasTrailGeometry) { - // Find max width multiplier from widthCurve - let maxWidthMultiplier = 0; + // Find max width from widthCurve + let maxWidth = 0; const widthKeys = this.widthCurve.keys; for (let i = 0, n = widthKeys.length; i < n; i++) { const value = widthKeys[i].value; - if (value > maxWidthMultiplier) { - maxWidthMultiplier = value; + if (value > maxWidth) { + maxWidth = value; } } - const halfWidth = this.width * maxWidthMultiplier * 0.5; + const halfWidth = maxWidth * 0.5; min.set(min.x - halfWidth, min.y - halfWidth, min.z - halfWidth); max.set(max.x + halfWidth, max.y + halfWidth, max.z + halfWidth); } @@ -606,4 +601,10 @@ export class TrailRenderer extends Renderer { subRenderElement.set(this, material, this._primitive, subPrimitive); renderElement.addSubRenderElement(subRenderElement); } + + @ignoreClone + private _onTextureScaleChanged(): void { + this._trailParams.y = this._textureScale.x; + this._trailParams.z = this._textureScale.y; + } } diff --git a/tests/src/core/Trail.test.ts b/tests/src/core/Trail.test.ts index a246c1376..94505ca91 100644 --- a/tests/src/core/Trail.test.ts +++ b/tests/src/core/Trail.test.ts @@ -11,7 +11,7 @@ import { BlendMode, Camera } from "@galacean/engine-core"; -import { Color, Vector3 } from "@galacean/engine-math"; +import { Color, Vector2, Vector3 } from "@galacean/engine-math"; import { describe, it, expect, beforeEach } from "vitest"; describe("Trail", async () => { @@ -38,9 +38,9 @@ describe("Trail", async () => { expect(trailRenderer.emitting).to.eq(true); expect(trailRenderer.minVertexDistance).to.eq(0.1); expect(trailRenderer.time).to.eq(5.0); - expect(trailRenderer.width).to.eq(1.0); expect(trailRenderer.textureMode).to.eq(TrailTextureMode.Stretch); - expect(trailRenderer.textureScale).to.eq(1.0); + expect(trailRenderer.textureScale.x).to.eq(1.0); + expect(trailRenderer.textureScale.y).to.eq(1.0); }); it("set emitting", () => { @@ -79,18 +79,6 @@ describe("Trail", async () => { expect(trailRenderer.time).to.eq(10.0); }); - it("set width", () => { - const rootEntity = scene.getRootEntity(); - const trailEntity = rootEntity.createChild("trail"); - const trailRenderer = trailEntity.addComponent(TrailRenderer); - - trailRenderer.width = 0.5; - expect(trailRenderer.width).to.eq(0.5); - - trailRenderer.width = 2.0; - expect(trailRenderer.width).to.eq(2.0); - }); - it("set textureMode", () => { const rootEntity = scene.getRootEntity(); const trailEntity = rootEntity.createChild("trail"); @@ -108,11 +96,13 @@ describe("Trail", async () => { const trailEntity = rootEntity.createChild("trail"); const trailRenderer = trailEntity.addComponent(TrailRenderer); - trailRenderer.textureScale = 2.0; - expect(trailRenderer.textureScale).to.eq(2.0); + trailRenderer.textureScale = new Vector2(2.0, 0.5); + expect(trailRenderer.textureScale.x).to.eq(2.0); + expect(trailRenderer.textureScale.y).to.eq(0.5); - trailRenderer.textureScale = 0.5; - expect(trailRenderer.textureScale).to.eq(0.5); + trailRenderer.textureScale.set(0.5, 3.0); + expect(trailRenderer.textureScale.x).to.eq(0.5); + expect(trailRenderer.textureScale.y).to.eq(3.0); }); it("set widthCurve", () => { @@ -180,10 +170,10 @@ describe("Trail", async () => { const trailEntity = rootEntity.createChild("trail"); const trailRenderer = trailEntity.addComponent(TrailRenderer); trailRenderer.setMaterial(new TrailMaterial(engine)); - trailRenderer.width = 2.0; + trailRenderer.widthCurve = new ParticleCurve(new CurveKey(0, 2), new CurveKey(1, 2)); trailRenderer.minVertexDistance = 0.1; - const halfWidth = trailRenderer.width * 0.5; // 1.0 + const halfWidth = 2.0 * 0.5; // 1.0 // Initial bounds is (0,0,0) because dirty flag is not set initially expect(trailRenderer.bounds.min).to.deep.include({ x: 0, y: 0, z: 0 }); @@ -214,7 +204,7 @@ describe("Trail", async () => { expect(trailRenderer.bounds.max.z).to.closeTo(halfWidth, 0.01); // Test width change affects bounds - trailRenderer.width = 4.0; + trailRenderer.widthCurve = new ParticleCurve(new CurveKey(0, 4), new CurveKey(1, 4)); const newHalfWidth = 2.0; trailEntity.transform.position = new Vector3(5, 4, 0);