pixelWidth/pixelHeight: a miracle

For developers using the Construct 2 Javascript SDK

Post » Sat Mar 09, 2013 10:01 pm

hi there!
i'm pretty much into pixel shader lately, and it's going pretty well so far. but one thing that bothers me, is that i don't have any clue about antialiasing/smoothing hard edges, which occur, when i want to tile ranges between 0.0 and 1.0. a common practice when tiling values is using mod or fract.
currently, i try to develope a shader which can tile/offset/rotate a sprite's texture like on the following image. as you can see, fract introduces nasty jaggies, which i like to get rid off.

on my research, i found a pretty interesting tutorial that deals with a halftone shader, which can be found here and sheds light on antialiasing as well. in step "3. Anti-aliasing is required" it is mentioned that we normally use dFdx() and dFdy() or fwidth to determine automatic derivatives which can interpolate our rough fract values painlessly. but since this cool webgl extension is not enabled within C2, we have to find other options to smooth out jaggies by finding customized versions of our functions.

so my question is, perhaps @Ashley , how are the pre-defined uniforms pixelWidth/pixelHeight connected to those unavailable functions dFdx/dFdy/fWidth and how can i achieve a smoothing on hard edges between 0.0 and 1.0 maybe with the help of pixelWidth/Height? i really like to offer some generative shaders to the community with some sort of antialiasing... please help me out on this one.
B
59
S
7
G
2
Posts: 93
Reputation: 4,228

Post » Sat Mar 16, 2013 6:08 am

I can't help much, but since you don't seem to get an answer, I'll give that little bit of information I have.
I'm not working with C2, and my experiences are from HLSL and Ashley's implementation of pixelWidth/Height there, but since GLSL is very close to HLSL and there's probably no reason for Ashley to change the behavior of pixelWidth/Height (I'll omit 'pixelHeight' now), here's my two cents.

pixelWidth is nothing more than the width relative to the normalized display size. Or, in other words, pixelWidth = 1/display size and unnormalized display size = 1/pixelWidth.

You should be able to use smoothstep, since it is available from v1.3 up. The example, you linked to, contains an alternate method to aa when dFdx/dFdy are not available. It explains that you need to know the "window size, the view transformation, the gradient in texture space and the relation between texture coordinates and world coordinates".
Well, the texture coords are absolute values of the normalized world (display) coordinates, like 0.25, 0.25, 0.5, 0.5 (a texture half of the display size, centered on screen). You get the window (display) size from 1/pixelWidth, you should already know, how you transform the view (or expect it to be untransformed), but I don't exactly understand "gradient in texture space". Maybe that's the range of grey shades from the texture's colors? That's the last point you have to find out by yourself.

Maybe this doesn't help you, or you already knew. But better told twice than never ;)tulamide2013-03-16 06:08:24
Image
B
23
S
8
G
10
Posts: 1,820
Reputation: 8,242

Post » Sat Mar 16, 2013 12:44 pm

thanks for your effort in explaining the problem to me, tulamide!
well, i knew what pixelWidth meant in general. the thing i didn't get was the relation to dFdx/dFdy/fWidth.
back to problem itself. Stefan Gustavson is calculating black and white circles and therefore speaks of gradient which describes antialiasing the circles' egdges.

in my shader, i like to tile & rotate textures. so i need to interpolate the transition areas between one texture and the follow next. to illustrate my problem a bit more, i like show the code i use:
[CODE]
precision mediump float;
varying mediump vec2 vTex;
uniform lowp sampler2D samplerFront;
uniform float pixelWidth;
uniform float pixelHeight;

uniform float offx;
uniform float offy; //Offset the texture
uniform float tilex;
uniform float tiley; //Tiling parameter for X and Y
uniform float rot; //Rotation factor

void main(void)
{
     float ro = radians(rot); //degree to radians
     mat2 r = mat2(cos(ro), -sin(ro), sin(ro), cos(ro)); //simple rotation matrix
     vec2 tile = vec2(tilex,tiley); //tiling vector
     vec2 off = vTex*tile+vec2(offx,offy);//apply tiling & offset
     vec2 pos = fract(off*r); //new position to sample from

     vec4 front = texture2D(samplerFront, pos);
     gl_FragColor = front;
     //sampling & output
}[/CODE]
this code produces jaggies of course:


if we knew the exact location of the texture transition area, we could sample there and do the interpolation. currently we jump from the end of texture one (gradient = 1.0) to the beginning of texture two (gradient = 0.0). a smoothing factor provided as a variable could describe that interpolation area (threshold) on the basis of pixelWidth/pixelHeight.
my main problem is that i can't imagine how to implement this gap area with my current code. Gustavson suggest taking into account the variables uScale cos(uYrot), but i don't know exactly which part of my code corresponds to those.
B
59
S
7
G
2
Posts: 93
Reputation: 4,228

Post » Sun Mar 17, 2013 2:13 pm

[QUOTE=oppenheimer]the thing i didn't get was the relation to dFdx/dFdy/fWidth.[/QUOTE] There is no direct relation. dFdx/dFdy tell you the difference in x- or y-direction (based on a 2x2 or 3x3 block) and fWidth tells you the overall difference to the neighboring blocks. So, in this context they are variable while pixelWidth/pixelHeight are constant.

[QUOTE=oppenheimer]if we knew the exact location of the texture transition area, we could sample there and do the interpolation. currently we jump from the end of texture one (gradient = 1.0) to the beginning of texture two (gradient = 0.0). a smoothing factor provided as a variable could describe that interpolation area (threshold) on the basis of pixelWidth/pixelHeight.[/QUOTE] You shouldn't work pixel-based when you want to smooth edges. But if you want to give it a try: You get the adjacent pixel positions from

(floor(x/pixelWidth) [+, -] 1) * pixelWidth
(floor(y/pixelHeight) [+, -] 1) * pixelHeight

As an example, the next pixel on the right from the current position would be located at
vec2 right = vec2((floor(vTex.x/pixelWidth) + 1) * pixelWidth, floor(vTex.y/pixelHeight) * pixelHeight)

[QUOTE=oppenheimer]my main problem is that i can't imagine how to implement this gap area with my current code. Gustavson suggest taking into account the variables uScale cos(uYrot), but i don't know exactly which part of my code corresponds to those.[/QUOTE] From what I see you're not scaling, so uScale will be 1, while uYrot should be equivalent to 'ro'.


EDIT
@oppenheimer
I made a big mistake! The only excuse I have is that I was awake for almost 30 hours, when I wrote it. I'm very sorry. I corrected the error and add it here again:

[CODE]

(floor(x/pixelWidth) (+, -) 1) * pixelWidth
(floor(y/pixelHeight) (+, -) 1) * pixelHeight

vec2 right = vec2((floor(vTex.x/pixelWidth) + 1) * pixelWidth, floor(vTex.y/pixelHeight) * pixelHeight)

[/CODE]

1. Keep in mind that you might work with subpixels. For example, when a sprite's texture is 32x32 but its size stretched to 128x128, you'll have 4 subpixels per pixel, and therefore your shader will be called 4 times per absolute texture pixel. (You're working in screenspace)

2. The above formula is only true when a pixel's "hotspot" is at upper-left (which should be the default). If it is centered, try replacing floor with round.


tulamide2013-03-18 04:37:01
Image
B
23
S
8
G
10
Posts: 1,820
Reputation: 8,242


Return to Javascript SDK

Who is online

Users browsing this forum: No registered users and 0 guests