pixijs
pixijs copied to clipboard
Feat/transform feedback
I have enthusiasm to introduce GPUParticle to PixiJS world.
There're various ways to implement GPUParitlce and I choose to use TransformFeedback.
To do that, I've implemented TransformFeedbackSystem.
TransformFeedbackSystem manages stuffs related to TransformFeedback.
Then, we can make a GPUParticle with existing PIXI classes like Mesh
, Shader
.
class GPUParticle extends Container
{
// Meshes to update Particle data
particleUpdateMeshes: Mesh<Shader>[];
// TransformFeedbacks
transformFeedbacks: TransformFeedback[];
// Meshes to render Particle data
particleRenderMeshes: Mesh<Shader>[];
// Flag for double buffering
inputFlag = 0;
particleUpdateShader: Shader;
particleRenderShader: Shader;
constructor()
{
this.particleUpdateShader = new Shader(vert1, frag1, 'ParticleUpdate', {
transformFeedbackVaryings: {
names: ['out_Position', 'out_Age'],
bufferMode: 'separate'
}
});
this.particleRenderShader = new Shader(vert2, frag2, 'ParticleRender');
// Create two particleUpdateMeshes with particleUpdateShader
this.particleUpdateMeshes.push(this.createParticleUpdateMesh());
this.particleUpdateMeshes.push(this.createParticleUpdateMesh());
// Create two particleRenderMeshes with particleRenderShader
this.particleRenderMeshes.push(this.createParticleRenderMesh());
this.particleRenderMeshes.push(this.createParticleRenderMesh());
const transformFeedback1 = new TransformFeedback();
transformFeedback.bindBuffer(0, particleUpdateMeshes[1].geometry.getBuffer('in_Position'));
transformFeedback.bindBuffer(1, particleUpdateMeshes[1].geometry.getBuffer('in_Age'));
transformFeedbacks.push(transformFeedback1);
// Create transformFeedback2
// ....
protected _render(renderer: Renderer): void
{
const inputFlag = this.inputFlag;
const updateMesh = this.particleUpdateMeshes[inputFlag];
const renderMesh = this.particleRenderMeshes[inputFlag];
const transformFeedback.= this.transformFeedbacks[inputFlag];
renderer.gl.enable(renderer.gl.RASTERIZER_DISCARD);
// Update Particle
renderer.transformFeedback.bind(transformFeedback);
renderer.transformFeedback.beginTransformFeedback(updateMesh.shader, DRAW_MODES.POINTS);
updateMesh.render(renderer);
renderer.transformFeedback.endTransformFeedback();
renderer.transformFeedback.unbind();
renderer.gl.disable(renderer.gl.RASTERIZER_DISCARD);
// Render Particle
renderMesh.render(renderer);
this.inputFlag = inputFlag ? 0 : 1;
}
}
Description of change
Pre-Merge Checklist
- [ ] Tests and/or benchmarks are included
- [x] Documentation is changed or added
- [x] Lint process passed (
npm run lint
) - [x] Tests passed (
npm run test
)
can you add some example of usage of this new feature in documentation in this repo and/or in https://github.com/pixijs/examples ?
can you add some example of usage of this new feature in documentation in this repo and/or in https://github.com/pixijs/examples ?
Sure! I think it could be done in examples repo after this PR is merged.
@orange4glace would love to get this in v7. Would you might resolving these conflicts?
@orange4glace would love to get this in v7. Would you might resolving these conflicts?
Sure! just fixed. (new System implementations are also applied, like extensions.add(TransformFeedbackSystem);
)
I've checked it works well with following example code,
<script src="./pixi.js"></script>
<body></body>
<script>
const vert1 = `
#version 300 es
in vec4 aPos;
void main(void) {
gl_PointSize = 20.;
gl_Position = vec4(aPos.x + 0.005, aPos.yzw);
}
`
const frag1 = `
#version 300 es
precision mediump float;
out vec4 fragColor;
void main(void){
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`
class GPUParticle extends PIXI.Container
{
// Meshes to update Particle data
particleUpdateMeshes = [];
// TransformFeedbacks
transformFeedbacks = [];
// Meshes to render Particle data
particleRenderMeshes = [];
// Flag for double buffering
inputFlag = 0;
particleUpdateShader;
particleRenderShader;
constructor()
{
super();
this.particleUpdateShader = new PIXI.Program(vert1, frag1, 'ParticleUpdate', {
transformFeedbackVaryings: {
names: ['gl_Position'],
bufferMode: 'separate'
}
});
this.particleRenderShader = new PIXI.Program(vert1, frag1, 'ParticleRender');
// Create two particleUpdateMeshes with particleUpdateShader
this.particleUpdateMeshes.push(this.createParticleUpdateMesh());
this.particleUpdateMeshes.push(this.createParticleUpdateMesh());
// Create two particleRenderMeshes with particleRenderShader
this.particleRenderMeshes.push(this.createParticleRenderMesh(this.particleUpdateMeshes[0].geometry.getBuffer('aPos')));
this.particleRenderMeshes.push(this.createParticleRenderMesh(this.particleUpdateMeshes[1].geometry.getBuffer('aPos')));
const transformFeedback1 = new PIXI.TransformFeedback();
transformFeedback1.bindBuffer(0, this.particleUpdateMeshes[1].geometry.getBuffer('aPos'));
this.transformFeedbacks.push(transformFeedback1);
const transformFeedback2 = new PIXI.TransformFeedback();
transformFeedback2.bindBuffer(0, this.particleUpdateMeshes[0].geometry.getBuffer('aPos'));
this.transformFeedbacks.push(transformFeedback2);
}
createParticleUpdateMesh() {
const geometry = new PIXI.Geometry()
.addAttribute('aPos', [-1, 0, 0, 1]);
const mesh = new PIXI.Mesh(geometry, new PIXI.Shader(this.particleUpdateShader), undefined, PIXI.DRAW_MODES.POINTS);
return mesh;
}
createParticleRenderMesh(buffer) {
const geometry = new PIXI.Geometry()
.addAttribute('aPos', buffer);
const mesh = new PIXI.Mesh(geometry, new PIXI.Shader(this.particleRenderShader), undefined, PIXI.DRAW_MODES.POINTS);
return mesh;
}
_render(renderer)
{
const inputFlag = this.inputFlag;
const updateMesh = this.particleUpdateMeshes[inputFlag];
const renderMesh = this.particleRenderMeshes[inputFlag];
const transformFeedback = this.transformFeedbacks[inputFlag];
renderer.gl.enable(renderer.gl.RASTERIZER_DISCARD);
// Update Particle
renderer.transformFeedback.bind(transformFeedback);
renderer.transformFeedback.beginTransformFeedback(PIXI.DRAW_MODES.POINTS, updateMesh.shader);
updateMesh.render(renderer);
renderer.transformFeedback.endTransformFeedback();
renderer.transformFeedback.unbind();
renderer.gl.disable(renderer.gl.RASTERIZER_DISCARD);
// Render Particle
renderMesh.render(renderer);
this.inputFlag = inputFlag ? 0 : 1;
}
}
// Create the application helper and add its render target to the page
let app = new PIXI.Application({ width: 640, height: 360 });
document.body.appendChild(app.view);
const particle = new GPUParticle();
app.stage.addChild(particle)
// Add a ticker callback to move the sprite back and forth
let elapsed = 0.0;
app.ticker.add((delta) => {
elapsed += delta;
});
</script>