Jump to content

Simple Quad with custom shaders


Sebi
 Share

Recommended Posts

Hey guys,

due to the lack of several features in pixi v2, I didn't work much with pixi in the past year but instead ran my own rendering engine.

Now with pixi v3, I assume that I can continue using pixi. :)

I'm still going through the source code of v3 and I'm trying to understand all the changes since v2.

So far so good. It's really well structured.

 

What I currently need help with are custom shaders.

 

I have a simple full canvas QUAD -1, -1 to 1, 1, a vertex shader and a fragment shader.

I also have 3 textures that I pass to my shader.

 

it's trivial in webgl, but how would I go about this in pixi?

Can I use a PIXI.Sprite with width and height set to the renderer's width and height and apply a custom AbstractFilter to it,

or would I need to write my own RenderObject?

 

I really don't need any of the fancy stuff. No positions, no scaling, no rotation,

just a QUAD with a shader applied to it.

This is an isometric map renderer that I wrote for my game:

precision mediump float;

attribute vec2 position;
attribute vec2 texture;
            
varying vec2 pixelCoord;

uniform vec2 viewOffset;
uniform vec2 viewportSize;

void main(void) {
    pixelCoord = (texture * viewportSize) + viewOffset;
    gl_Position = vec4(position, 0.0, 1.0);
 }
precision mediump float;

varying vec2 pixelCoord;

uniform sampler2D tiles;
uniform sampler2D sprites;
uniform sampler2D hitTest;

uniform vec2 inverseTileTextureSize;
uniform vec2 inverseSpriteTextureSize;
uniform vec2 tileSize;
uniform vec2 animationStep;
uniform vec2 halfTileSize;
uniform vec2 inverseTileSize;


void main(void) {
   vec2 spriteCoord = mod(pixelCoord, tileSize);
   vec2 texCoord = vec2(0.0, 0.0);
   if (texture2D(hitTest, spriteCoord / tileSize).x > 0.5) {
      texCoord.x = floor(pixelCoord.x / tileSize.x);
      texCoord.y = 2.0 * floor(pixelCoord.y / tileSize.y);
   } else {
      texCoord.x = floor((pixelCoord.x + tileSize.y) / tileSize.x) - 1.0;
      texCoord.y = 2.0 * floor((pixelCoord.y + halfTileSize.y) / tileSize.y) - 1.0;
      spriteCoord = mod(spriteCoord + halfTileSize, tileSize);
   }
        
   vec4 tile = texture2D(tiles, texCoord * inverseTileTextureSize);
   if(tile.r == 1.0 && tile.g == 1.0) { discard; }
   vec2 spriteOffset = floor((tile.rg + tile.rb * animationStep) * 256.0) * tileSize;

   gl_FragColor = texture2D(sprites, (spriteOffset + spriteCoord) * inverseSpriteTextureSize);
}

As you can see, it's pretty simple, I just need to be able to pass 3 textures to my shader.

 

I also need to access the stencil buffer for a fog of war shader.

I guess I would have to study the webgl GraphicsRenderer and roll my own as well.

 

Anything I absolutely need to keep in mind to avoid pixi going crazy?

 

I would really appreciate it if anyone could tell me how I can create a custom sprite without a texture.

I can figure out the rest myself, if I get something as simple as this running, I'd be more than happy.

QUAD:

[    x   y  u  v
    -1, -1, 0, 1,
     1, -1, 1, 1,
     1,  1, 1, 0,

     -1, -1, 0, 1,
      1,  1, 1, 0,
     -1,  1, 0, 0
];

VERTEX SHADER

attribute vec2 aVertexPosition;
void main(void) {
    gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}

FRAGMENT SHADER

precision mediump float;

void main(void) {
    gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
}

That would be my quad drawn via gl.drawArrays(gl.TRIANGLES, 0, 6);

Cheers :)

Link to comment
Share on other sites

Yes, you need your own ObjectRenderer. Its difficult for the first time. After you do it, you'll understand how pixi works internally, and the process will become easy.

https://github.com/pixijs/pixi-tilemap/blob/master/src/pixi-tilemap.js - that's good example of how to create your own renderer class with custom shader that is not using traditional PIXI uniforms. Its also working with tiles. That library also have rpgmaker example and simple one as well.

0. Look at https://github.com/pixijs/pixi.js/blob/master/src/core/renderers/webgl/shaders/TextureShader.js , make your own shader class.

1. Look at https://github.com/pixijs/pixi.js/blob/master/src/core/sprites/webgl/SpriteRenderer.js . Clone it, register in WebGLRenderer with another name, remove all shit about batches. Create your shader (0) somewhere inside. You can use 

2. Look at https://github.com/pixijs/pixi.js/blob/master/src/core/sprites/Sprite.js , make a sprite (you know how to extend class, right?) with overriden renderWebGL method, that calls your renderer instead of Sprite one.

You also can use https://github.com/pixijs/pixi.js/blob/master/src/core/renderers/webgl/utils/Quad.js for help, just create it inside your ObjectRenderer.

UPD. Also, that way you'll be able to make it as library for other PIXI users. The more shaders and renderers we have - the better.

Link to comment
Share on other sites

Thanks, Ivan!

Sounds geat. I will take a look into that, Happy to hear that custom renderObjects are possible. :)

When I checked the v3 source and at renderer.plugins.sprite.render(this); I got lost and didn't know where to continue reading.

I will see if I can create a dead simple shader with your code. :D

 

 

Link to comment
Share on other sites

48 minutes ago, SebastianNette said:

I will see if I can create a dead simple shader with your code. :D

My code has two shaders: for both gl.TRIANGLES and gl.POINTS.

I dont think the way you made tiles is good for performance, I tried that approach too and it failed. But may be you'll make it faster :)

Link to comment
Share on other sites

You don't need to write your own object renderer, sprites are rendered as Quads. You can use the .shader property to override the behavior of how that sprite is drawn.

The only catch is that the quad of the sprite is determined by the .texture property. If that makes sense for you, that is the quad will be based on a texture then you're good to go. The only reason you would need a custom ObjectRenderer is if you need to do something the current renderer doesn't support.

In fact, if what you want is just to specify geometery and apply a custom shader, use the Mesh class (it can be any geom you want including just a quad) and set the .shader property. Its really rare that you have to implement a custom Object Renderer.

 

You also don't have to make raw Shader classes either, Filters work just fine. Create something extending AbstractFilter like you normally would create a filter, and just assign it to the .shader property. I would be suprised if this was harder than just use sprite/mesh and assign your filter to the .shader property.

Link to comment
Share on other sites

13 hours ago, ivan.popelyshev said:

My code has two shaders: for both gl.TRIANGLES and gl.POINTS.

I dont think the way you made tiles is good for performance, I tried that approach too and it failed. But may be you'll make it faster :)

The performance is actually pretty good, all the texture lookups should be cached by the gpu and the rest is just some simple math.

Another approach would be to pre-render 9 chunks at half the viewport width and height and move / redraw them as needed. Not sure yet which would be faster.

But the cost of drawing several quads should be greater than drawing the map in one go.

 

I'm still experimenting on this. I want a really fast background shader that ignores all depth. As few quads as possible.

My isometric renderer is inspired by Toji's 2d renderer for LTTP. 

http://media.tojicode.com/zelda/lttp.html

 

Just that I go 2.5d instead of 2d. I think no one can complain about the rendering speed :D

 

Thanks xerver,

that makes sense. I need 3 textures (the map image, the hit test tile and the spritesheet). 

Is there an example somewhere that shows how I would send more than 1 texture to my shader?

 

For my fog of war shader,

I basically just disable the colorMask and enable the stencil buffer,

then I draw a batch of white circle textures to all onscreen units, enable the color mask again and draw a fullscreen half transparent black quad and disable the stencil buffer again.

gl.enable(gl.STENCIL_TEST);
gl.stencilFunc(gl.ALWAYS, 0x1, 0xffffffff);
gl.stencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE);
gl.colorMask(false, false, false, false);

/* draw units /

gl.colorMask(true, true, true, true);
gl.depthMask(true);
gl.stencilFunc(gl.NOTEQUAL, 1, 1); 
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);

/* draw quad (0.0, 0.0, 0.0, 0.5) */

gl.disable(gl.STENCIL_TEST);

The shader for the fog would then just discard all pixel in the buffer drawn by the circle batch.

How would I go about this?

 

I don't want to use a PIXI.Graphics object, especially because I don't want it to calculate the circles as a polygon each frame but instead just batching those circle textures, like a ParticleContainer would do.

 

Is there a way for PIXI, to mask a Sprite by a ParticleContainer?

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...