mirror of
https://github.com/galacean/engine.git
synced 2026-06-08 16:52:58 +08:00
279 lines
8.6 KiB
TypeScript
279 lines
8.6 KiB
TypeScript
/**
|
|
* @title Buffer Mesh Particle Shader Effect
|
|
* @category Mesh
|
|
* @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*9bIIRZ7rnNgAAAAAAAAAAAAADiR2AQ/original
|
|
*/
|
|
import {
|
|
Buffer,
|
|
BufferBindFlag,
|
|
BufferMesh,
|
|
Camera,
|
|
MeshRenderer,
|
|
RenderFace,
|
|
Texture2D,
|
|
VertexBufferBinding,
|
|
VertexElement,
|
|
VertexElementFormat,
|
|
Engine,
|
|
Shader,
|
|
BaseMaterial,
|
|
Script,
|
|
WebGLEngine
|
|
} from "@galacean/engine";
|
|
import { OrbitControl } from "@galacean/engine-toolkit-controls";
|
|
import { ShaderLab } from "@galacean/engine-shaderlab";
|
|
|
|
const shaderLab = new ShaderLab();
|
|
|
|
const shaderSource = `Shader "ParticleEffect" {
|
|
SubShader "Default" {
|
|
Pass "0" {
|
|
VertexShader = vert;
|
|
FragmentShader = frag;
|
|
|
|
#define PI 3.14159265359
|
|
mat4 renderer_MVPMat;
|
|
|
|
float progress;
|
|
sampler2D texture1;
|
|
sampler2D texture2;
|
|
|
|
struct a2v {
|
|
vec3 POSITION;
|
|
vec3 INDEX;
|
|
vec2 UV;
|
|
};
|
|
|
|
struct v2f {
|
|
vec2 v_uv;
|
|
};
|
|
|
|
v2f vert(a2v v) {
|
|
v2f o;
|
|
|
|
o.v_uv = v.UV;
|
|
vec4 position = vec4(v.POSITION , 1.0);
|
|
float distance = length(v.INDEX.xy);
|
|
float maxDistance = 40.0 * 1.414;
|
|
float wait = distance / maxDistance * 0.5;
|
|
|
|
float p = clamp(progress-wait, 0.0, 2.0);
|
|
position.z += sin(p * PI * 6.0) * 3.0 * (maxDistance - distance * 0.5) / maxDistance * (2.0 - progress) * 0.5;
|
|
|
|
gl_Position = renderer_MVPMat * position;
|
|
return o;
|
|
}
|
|
|
|
// This function could be changed. Some great effects could be referred to https://gl-transitions.com/gallery
|
|
vec4 transition(vec2 p, float progress) {
|
|
vec2 dir = p - vec2(0.5);
|
|
float dist = length(dir);
|
|
|
|
if (dist > progress) {
|
|
return mix(texture2D(texture1, v_uv), texture2D(texture2, v_uv), progress);
|
|
} else {
|
|
vec2 offset = dir * sin(dist * 30.0 - progress * 30.0);
|
|
return mix(texture2D(texture1, v_uv + offset), texture2D(texture2, v_uv), progress);
|
|
}
|
|
}
|
|
|
|
void frag(v2f i) {
|
|
gl_FragColor = transition(i.v_uv, clamp(progress, 0.0, 1.0));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
|
|
class ParticleMeshMaterial extends BaseMaterial {
|
|
constructor(engine: Engine, shader: Shader) {
|
|
super(engine, shader);
|
|
}
|
|
|
|
clone(): ParticleMeshMaterial {
|
|
const dest = new ParticleMeshMaterial(this._engine, this.shader);
|
|
this.cloneTo(dest);
|
|
return dest;
|
|
}
|
|
|
|
get texture1(): Texture2D {
|
|
return <Texture2D>this.shaderData.getTexture("texture1");
|
|
}
|
|
|
|
set texture1(value: Texture2D) {
|
|
this.shaderData.setTexture("texture1", value);
|
|
}
|
|
|
|
get texture2(): Texture2D {
|
|
return <Texture2D>this.shaderData.getTexture("texture2");
|
|
}
|
|
|
|
set texture2(value: Texture2D) {
|
|
this.shaderData.setTexture("texture2", value);
|
|
}
|
|
|
|
get progress(): number {
|
|
return <number>this.shaderData.getFloat("progress");
|
|
}
|
|
|
|
set progress(value: number) {
|
|
this.shaderData.setFloat("progress", value);
|
|
}
|
|
}
|
|
|
|
class AnimationComponent extends Script {
|
|
time = 0;
|
|
mtl: ParticleMeshMaterial | undefined;
|
|
onAwake() {
|
|
this.mtl = this.entity.getComponent(MeshRenderer)!.getMaterial() as ParticleMeshMaterial;
|
|
}
|
|
onUpdate(time: number) {
|
|
this.time += time;
|
|
if (this.mtl) {
|
|
this.mtl.progress = (this.time / 5) % 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
// segmentX and segmentY handle how many particles we create
|
|
function createPlaneParticleMesh(
|
|
engine: WebGLEngine,
|
|
width: number,
|
|
height: number,
|
|
segmentX: number,
|
|
segmentY: number,
|
|
isIn: boolean
|
|
) {
|
|
const triangleCount = segmentX * segmentY * 2; // we create segmentX * segmentY rectangles, each rectangle has 2 triangles
|
|
const vertexCount = triangleCount * 3;
|
|
|
|
const halfWidth = width * 0.5;
|
|
const halfHeight = height * 0.5;
|
|
const segmentWidth = width / segmentX;
|
|
const segmentHeight = height / segmentY;
|
|
|
|
let positionBuffer = new Float32Array(vertexCount * 3);
|
|
let uvBuffer = new Float32Array(vertexCount * 2);
|
|
let indexBuffer = new Float32Array(vertexCount * 3);
|
|
|
|
let i = 0;
|
|
for (let y = 0; y < segmentY; y++) {
|
|
for (let x = 0; x < segmentX; x++) {
|
|
// create vertex attribute buffer according to each square seperated by segemntX and segmentY
|
|
let index = i * 3 * 3;
|
|
positionBuffer[index] = -halfWidth + x * segmentWidth;
|
|
positionBuffer[index + 1] = -halfHeight + y * segmentHeight;
|
|
positionBuffer[index + 2] = 0;
|
|
positionBuffer[index + 3] = -halfWidth + (x + 1) * segmentWidth;
|
|
positionBuffer[index + 4] = -halfHeight + y * segmentHeight;
|
|
positionBuffer[index + 5] = 0;
|
|
positionBuffer[index + 6] = -halfWidth + x * segmentWidth;
|
|
positionBuffer[index + 7] = -halfHeight + (y + 1) * segmentHeight;
|
|
positionBuffer[index + 8] = 0;
|
|
positionBuffer[index + 9] = -halfWidth + (x + 1) * segmentWidth;
|
|
positionBuffer[index + 10] = -halfHeight + y * segmentHeight;
|
|
positionBuffer[index + 11] = 0;
|
|
positionBuffer[index + 12] = -halfWidth + (x + 1) * segmentWidth;
|
|
positionBuffer[index + 13] = -halfHeight + (y + 1) * segmentHeight;
|
|
positionBuffer[index + 14] = 0;
|
|
positionBuffer[index + 15] = -halfWidth + x * segmentWidth;
|
|
positionBuffer[index + 16] = -halfHeight + (y + 1) * segmentHeight;
|
|
positionBuffer[index + 17] = 0;
|
|
|
|
indexBuffer[index] = x * 2 - segmentX;
|
|
indexBuffer[index + 1] = y * 2 - segmentY;
|
|
indexBuffer[index + 2] = i;
|
|
indexBuffer[index + 3] = x * 2 - segmentX;
|
|
indexBuffer[index + 4] = y * 2 - segmentY;
|
|
indexBuffer[index + 5] = i;
|
|
indexBuffer[index + 6] = x * 2 - segmentX;
|
|
indexBuffer[index + 7] = y * 2 - segmentY;
|
|
indexBuffer[index + 8] = i;
|
|
indexBuffer[index + 9] = x * 2 + 1 - segmentX;
|
|
indexBuffer[index + 10] = y * 2 + 1 - segmentY;
|
|
indexBuffer[index + 11] = i + 1;
|
|
indexBuffer[index + 12] = x * 2 + 1 - segmentX;
|
|
indexBuffer[index + 13] = y * 2 + 1 - segmentY;
|
|
indexBuffer[index + 14] = i + 1;
|
|
indexBuffer[index + 15] = x * 2 + 1 - segmentX;
|
|
indexBuffer[index + 16] = y * 2 + 1 - segmentY;
|
|
indexBuffer[index + 17] = i + 1;
|
|
|
|
index = i * 2 * 3;
|
|
uvBuffer[index] = x / segmentX;
|
|
uvBuffer[index + 1] = 1 - y / segmentY;
|
|
uvBuffer[index + 2] = (x + 1) / segmentX;
|
|
uvBuffer[index + 3] = 1 - y / segmentY;
|
|
uvBuffer[index + 4] = x / segmentX;
|
|
uvBuffer[index + 5] = 1 - (y + 1) / segmentY;
|
|
uvBuffer[index + 6] = (x + 1) / segmentX;
|
|
uvBuffer[index + 7] = 1 - y / segmentY;
|
|
uvBuffer[index + 8] = (x + 1) / segmentX;
|
|
uvBuffer[index + 9] = 1 - (y + 1) / segmentY;
|
|
uvBuffer[index + 10] = x / segmentX;
|
|
uvBuffer[index + 11] = 1 - (y + 1) / segmentY;
|
|
|
|
i += 2;
|
|
}
|
|
}
|
|
|
|
const mesh = new BufferMesh(engine);
|
|
mesh.setVertexBufferBindings([
|
|
new VertexBufferBinding(
|
|
new Buffer(engine, BufferBindFlag.VertexBuffer, positionBuffer),
|
|
3 * Float32Array.BYTES_PER_ELEMENT
|
|
),
|
|
new VertexBufferBinding(
|
|
new Buffer(engine, BufferBindFlag.VertexBuffer, uvBuffer),
|
|
2 * Float32Array.BYTES_PER_ELEMENT
|
|
),
|
|
new VertexBufferBinding(
|
|
new Buffer(engine, BufferBindFlag.VertexBuffer, indexBuffer),
|
|
3 * Float32Array.BYTES_PER_ELEMENT
|
|
)
|
|
]);
|
|
|
|
mesh.setVertexElements([
|
|
new VertexElement("POSITION", 0, VertexElementFormat.Vector3, 0),
|
|
new VertexElement("UV", 0, VertexElementFormat.Vector2, 1),
|
|
new VertexElement("INDEX", 0, VertexElementFormat.Vector3, 2)
|
|
]);
|
|
|
|
mesh.addSubMesh(0, vertexCount);
|
|
return mesh;
|
|
}
|
|
|
|
WebGLEngine.create({ canvas: "canvas", shaderLab }).then((engine) => {
|
|
engine.canvas.resizeByClientSize();
|
|
const scene = engine.sceneManager.activeScene;
|
|
const rootEntity = scene.createRootEntity();
|
|
|
|
const particleMeshShader = Shader.create(shaderSource);
|
|
|
|
const cameraEntity = rootEntity.createChild("camera");
|
|
cameraEntity.addComponent(Camera);
|
|
cameraEntity.transform.position.set(0, 0, 50);
|
|
cameraEntity.addComponent(OrbitControl);
|
|
|
|
engine.resourceManager
|
|
.load([
|
|
"https://gw.alipayobjects.com/zos/OasisHub/440001901/3736/spring.jpeg",
|
|
"https://gw.alipayobjects.com/zos/OasisHub/440001901/9546/winter.jpeg"
|
|
])
|
|
.then((assets) => {
|
|
const entity = rootEntity.createChild("plane");
|
|
const renderer = entity.addComponent(MeshRenderer);
|
|
const mesh = createPlaneParticleMesh(engine, 20, 20, 80, 80, true);
|
|
const mtl = new ParticleMeshMaterial(engine, particleMeshShader);
|
|
renderer.setMaterial(mtl);
|
|
renderer.mesh = mesh;
|
|
mtl.texture1 = assets[0] as Texture2D;
|
|
mtl.texture2 = assets[1] as Texture2D;
|
|
mtl.renderFace = RenderFace.Double;
|
|
|
|
entity.addComponent(AnimationComponent);
|
|
});
|
|
|
|
engine.run();
|
|
});
|