Sign in to follow this  
Iavra

Converting a filter to PIXI v4

Recommended Posts

In PIXI v2, i was using the following fragment filter to convert a texture from rectangular to polar coordinates, as part of a process to create a shadowmap:

PIXI.AbstractFilter.call(this, [
    '#define PI 3.14',
           
    'precision mediump float;',
    'varying vec2 vTextureCoord;',
    'uniform sampler2D uSampler;',
               
    'void main(void) {',
    '    vec2 norm = vTextureCoord * 2.0 - 1.0;',
    '    float theta = PI + norm.x * PI;',
    '    float r = (1.0 + norm.y) * 0.5;',
    '    vec2 coord = vec2(-r * sin(theta), -r * cos(theta)) / 2.0 + 0.5;',
    '    gl_FragColor = texture2D(uSampler, coord);',
    '}'
]);

The first 2 pictures in this thread show an example of how the filter works: http://www.gmlscripts.com/forums/viewtopic.php?id=1657

However, with PIXI v4 (and maybe v3, i skipped that one), the resulting image looks like this:

Unbenannt.png

Furthermore, the result changes, depending on the position of the sprite on the screen, which is something i don't think v2 did.

Has anyone tried to do something similar in v4 and can point me in the right direction?

Share this post


Link to post
Share on other sites

That can help with filter. The problem is it uses power-of-two temporary textures. May be its better to make it as a shader for Sprite? Or render it to some RenderTexture first, then make create a Sprite, specify its shader, and render it onto other RenderTexture? Then blend it both using new stage where one sprite is original and second is shadowmap. May be even use some awesome blending mode like https://github.com/pixijs/pixi-picture has :) OVERLAY/HARD_LIGHT or something

Share this post


Link to post
Share on other sites

Hmm, since my test image is 256x256, it should work fine with power-of-two, but i'll give it a try. Haven't worked with shaders in v4 so far, but i'll take a look at it, too.

I want to use a RenderTexture in the end, so i can use the wrapped-modified-unwrapped occlusion map as a mask for the light. It will probably contain artifacts, since i lose out on precision during the process, but if i draw soft shadows i should be able to smoothen it out.

Share this post


Link to post
Share on other sites

Using calculateNormalizedScreenSpaceMatrix doesn't seem to work, the filter still behaves different depending on the sprite's position. I tried using filters as shaders, but it doesn't seem to work?

Using the BlurFilter, i tried the following approaches:

sprite.filters = [new PIXI.filters.BlurFilter()];
sprite.shader = new PIXI.filters.BlurFilter();

The second one doesn't seem to do anything and i couldn't find an example on how to use shaders in v4.

Share this post


Link to post
Share on other sites

Second one wont work in pixi-v4 anymore. there are no examples for custom shaders, and its kinda difficult because we have multitexturing now and i didnt check out how affects it that shader. I can look it for you :)  pixi-picture for now is the only thing i can recomment to look at. Sprite calls custom renderer that can do many things

Share this post


Link to post
Share on other sites

I think it would be enough, if there is some way to port the filter listed in the OP over to v4, either as filter or shader (i think i actually used it as a shader back then).

vTextureCoord used to be in range 0.0-1.0 over the whole sprite area, independent from the sprite's position on the screen. As of now, vTextureCoord seems to change, depending on the sprite's position and even more so if part of the sprite is outside the rendered part of the scene.

Share this post


Link to post
Share on other sites

Hmm, since i need to access the texture for the wrapping, i guess i would need to appy the mappedMatrix, calculate polar coordinates and transform them back into non-mapped ones to grab the correct pixels on the texture. How would i do this?

Basically, i would:

  1. Apply the mappedMatrix to vTextureCoord to get coordinates in range 0.0-1.0.
  2. Calculate the polar coordinates.
  3. Divide out the matrix again to get the correct coordinates i need to access uSampler.

Does this sound correct?

Share this post


Link to post
Share on other sites

This is my filter so far:

var Filter = function() {
    PIXI.Filter.call(this, null, [
        '#define PI 3.14159265358979323846264',
        'varying vec2 vTextureCoord;',
        'uniform sampler2D uSampler;',
        'uniform mat3 mappedMatrix;',
        'uniform mat3 unmappedMatrix;',
        'void main(void) {',
        '    vec2 mappedCoord = (vec3(vTextureCoord, 1.0) * mappedMatrix).xy;',

        '    vec2 norm = mappedCoord * 2.0 - 1.0;',
        '    float theta = PI + norm.x * PI;',
        '    float r = (1.0 + norm.y) * 0.5;',
        '    vec2 coord = vec2(-r * sin(theta), -r * cos(theta)) / 2.0 + 0.5;',

        '    gl_FragColor = texture2D(uSampler, (vec3(coord, 1.0) * unmappedMatrix).xy);',
        '}'
    ].join('\n'));
    this.uniforms.mappedMatrix = new PIXI.Matrix();
    this.uniforms.unmappedMatrix = new PIXI.Matrix();
};

Filter.prototype = Object.create(PIXI.Filter.prototype);

Filter.prototype.apply = function(filterManager, input, output) {
    filterManager.calculateNormalizedScreenSpaceMatrix(this.uniforms.mappedMatrix);
    this.uniforms.unmappedMatrix = this.uniforms.mappedMatrix.clone().invert();
    filterManager.applyFilter(this, input, output);
};

It works fine, until the sprite gets moved near or outside the edge of the scene (like negative x/y coordinates), which is when this happens:

test.jpg0.png10.png-10.png

Theese are, from left to right:

  • Original
  • sprite.x = sprite.y = 0
  • sprite.x = sprite.y = 10
  • sprite.x = sprite.y = -10

The second transformed one looks somewhat correct, even though it's still different from the desired result and the dimensions seem to be messed up a bit (because of the mapped matrix?).

For comparison, how it should look like:

raster-transform1b.jpg

Share this post


Link to post
Share on other sites

I see, this works for sprite.x = sprite.y = 0, but not for negative coordinates. Shouldn't matter, if i render the sprite on a RenderTexture, but i guess i would need to either modify the filterArea or part of the projectionMatrix to apply the filter on the whole sprite, not only the visible part?

Share this post


Link to post
Share on other sites

I guess i have a small problem regarding precision when unwrapping. I'm rendering the occlusion map with these 2 filters, assuming the light is at the center of the texture:

#define PI 3.1415926535897932384626433832795
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
uniform mat3 mappedMatrix;
uniform mat3 unmappedMatrix;
const float h = 256.0; // height of the texture, currently hardcoded
void main(void) {
    vec3 mappedCoord = vec3(vTextureCoord, 1.0) * mappedMatrix;
    for(float y = 0.0; y < h; y += 1.0) {
        if(y / h > mappedCoord.y) { break; }
        vec2 norm = vec2(mappedCoord.x, y / h) * 2.0 - 1.0;
        float theta = PI + norm.x * PI;
        float r = (1.0 + norm.y) * 0.5;
        vec2 coord = vec2(-r * sin(theta), -r * cos(theta)) / 2.0 + 0.5;
        vec3 unmappedCoord = vec3(coord, 1.0) * unmappedMatrix;
        if(texture2D(uSampler, unmappedCoord.xy).a > 0.0) {
            gl_FragColor = vec4(1.0);
            return;
        }
    }
    gl_FragColor = vec4(0.0);
}
#define PI 3.1415926535897932384626433832795
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
uniform mat3 mappedMatrix;
uniform mat3 unmappedMatrix;
void main(void) {
    vec2 norm = (vec3(vTextureCoord, 1.0) * mappedMatrix).xy * 2.0 - 1.0;
    float theta = PI + atan(norm.x, norm.y);
    float r = length(norm);
    vec2 coord = vec2(theta / (2.0 * PI), r);
    gl_FragColor = texture2D(uSampler, (vec3(coord, 1.0) * unmappedMatrix).xy);
}

Following is a sample image before and after the filter:

test.pngUnbenannt.png

The transformation seems fine, but you can see the 1px wide gap at the top center. I don't remember if that was there in v2 as well and i currently don't know how to fix it.

Share this post


Link to post
Share on other sites

It kinda "works", if i change the last line of filter 2 like this:

gl_FragColor = texture2D(uSampler, (vec3(coord, 1.0) * unmappedMatrix).xy * 0.99);

Though that also means i would need to add a 3rd render pass reverting that, losing further precision. It also feels really really dirty.

/edit: I'm pondering, whether i should drop per-pixel shading and use raycasting, instead. Though that would either limit me to basic shapes or i'll need to cast a whole bunch of rays and still have jittery results.

Share this post


Link to post
Share on other sites

Indeed, if i take the unmapped coords in filter 2 and clamp them, it fixes the gap. So, i should be able to take the resulting texture, invert it (i can do that in pass 1, by drawing light instead of shadow) and use it to mask the light texture. I should probably blur the mask and use alpha masking, instead, to hide some of the lost precision.

 

Though i still don't get, why the filtered texture is 260x260, while the original is 256x256. Somewhere in the code, i'm scaling the coordinates slightly. Maybe i should add a clamp to pass 1, too.

Share this post


Link to post
Share on other sites

My only example image so far is this one:

test.png

This is (part of) the occlusion map, which contains all shadowcasting objects. The whole process should work like this:

  1. For each light, pick a part of the occlusion map, that contains the outer circle of the light (the filter only works on the inner circle of the shadow texture, so we need to make sure to grab a bigger part).
  2. Assuming, that the center of the light is the center of the occlusion map, render it using both filters (for now on a RenderTexture). The last 2-3 lines in pass 1 need to be reversed, so we are drawing light, instead of shadow.
  3. Use the RenderTexture as a mask for the light (which is a texture itself).
  4. Tint the whole screen in darkness and blend it with the masked lights (i'm not there, yet, so i don't know which blendmode i should use for this).

This is my whole code so far: http://pastebin.com/qrFV6ThW

/edit: Also, after setting padding = 0 on the first pass, i can even remove the clamp without encountering the filtering issue (the gap).

Share this post


Link to post
Share on other sites

4 can be done either with multiply either with something like overlay. The best way is to add all all lights as Sprites to special container that is above everything else, then assign "VoidFilter" to that container and add blendMode=multiply to that filter. I haven't implement Overlay yet, it works only on single sprites :(

Share this post


Link to post
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...
Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.