A question about shaders.

Previous topic - Next topic

Hatonastick

As I wasn't sure where to put this exactly, I thought maybe off topic was best.

For a while now I've wanted to do an effect a bit like the retro one used in Darwinia for a simple 3D game I'm working on.   I know I've talked about the effect before and someone came up with a sort of (well, ok it was pretty good really just very CPU intensive) solution.  Anyway it occurred to me that it might (as I'm guessing the original is) be done via a shader.

Basically the way I see it is you split whatever graphic you want to have this effect up into 4x4 pixel blocks, then average the colors in those blocks and paint a 4x4 pixel block of a single color (which you got from averaging the original 4x4 colors).

eg. (I'm going to use a 2x2 block here to save time typing :))

Assuming for a moment we are only using 256 colors in RGB format (although I'd really want true color or what-have-you if it was possible) a sample 2x2 block could be:
1.1) 0:0:0               1.2) 255:179:010
2.1) 150:070:155   2.2) 255:255:134

R average = 0 + 255 + 150 + 255 / 4 = 165
G average = 0 + 179 + 70 + 255 / 4 = 126
B average = 0 + 10 + 155 + 134 / 4 = 74.75 (round to 75)

So paint a 2x2 block with color RGB 165:126:75 over the top.

Making sense?  If so any tips on where to start or whether this is even possible with Open GL shaders?  Shaders are a new fangled thing as far as I'm concerned. :)
Mat. 5: 14 - 16

Android: Toshiba Thrive Tablet (3.2), Samsung Galaxy Tab 2 (4.1.2).
Netbook: Samsung N150+ Netbook (Win 7 32-bit + Ubuntu 11.10).
Desktop: Intel i5 Desktop with NVIDIA GeForce GTX 460 (Win 8.1 64-bit).

Kitty Hello

Very funny, because I had the very same idea this week. A shader that would output one pixel as:


That's what you mean?

Hatonastick

#2
Hmm dont think so if Im understanding correctly.  More like attached picture even though I'm having doubts about it work.  Maybe my sample wasn't the best choice of colors as they are unlikely to be together.  Maybe.

[attachment deleted by admin]
Mat. 5: 14 - 16

Android: Toshiba Thrive Tablet (3.2), Samsung Galaxy Tab 2 (4.1.2).
Netbook: Samsung N150+ Netbook (Win 7 32-bit + Ubuntu 11.10).
Desktop: Intel i5 Desktop with NVIDIA GeForce GTX 460 (Win 8.1 64-bit).

Slydog

#3
From the screen shots of Darwinia, it looks like each triangle face has one colour, but it's neighbor may have a slightly different shade of that colour.  To tweak a polygon face colour, you would use a vertex shader, and shouldn't need a fragment one.
Unless you want a different effect.

But I'm not sure if you actually need a shader for this effect.
Where are you getting the 3D objects from?  A pre-made 3D model, or user generated in code?
If it's a existing model, just colour / uv paint the model to look like that effect. (You could use the texture described below too)
If it's in code, you can use a colour brightness algo to adjust each face and apply to each vertex corner of that face.

Create a texture of just a simple white triangle filled grey (either a right angle, or centered top point) and uv map each poly face corner to the corners of the texture triangle and apply the generated colour.  The white / grey colours will give you a bright border with a darker inside, but with the same colour type.  So if you apply a red colour, the borders will be bright red and the interior more dull.

Or are you picturing something different?

[Edit]
Something like my following screenshot (ha, yet another style I'm trying, got rid of those train track graphics)

[attachment deleted by admin]
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

Hatonastick

#4
Ah sorry no, not the retro colors effect you see with the polygons that make up the scenes (although I like the look of yours!).  I'm talking about the pixelization effect that you see with the trooper bots you control.  Hmm I might have to fire it up and have a look.  Been a while.

Edit: Yeah, it becomes especially obvious that its an effect placed over the top of the vector models (turning it on and off makes it easier to see).  It honestly looks just like I described.  Averaging of colors in a 4x4 pixel block painted all the averaged color.  Not sure it's a shader though.  What I suspect is a little along the lines of what someone has already done as an example (just was slower than it should be though): Draw the model in a backbuffer in same position, angle etc. it will be on screen.  Use the average algorithm to do each 4x4 block.  Blit the retro pixelized drawn bits over the top of the model on the display screen (you can sometimes see the polygons underneath the effect if you zoom in far enough and move them about).  Honestly though I'd have thought a shader would be quicker...

BTW I do want to know how the effect you are talking about is done too.  I'm going the retro 3D feel route for this game as well.  =D

Edit: I found this shader at http://www.geeks3d.com/20101029/shader-library-pixelation-post-processing-effect-glsl/:

Code (glbasic) Select
The Pixelation GLSL Shader

Language: OpenGL 2 – GLSL

Type: Post processing filter.

Inputs

sceneTex (sampler2D): the final scene image.
vx_offset (float): offset of the vertical red line
rt_w (float): render target width (GeeXLab built-in uniform)
rt_h (float): render target height (GeeXLab built-in uniform)
pixel_w (float): width of a low resolution pixel
pixel_h (float): height of a low resolution pixel
Ouputs: color buffer

Shader code:

[Vertex_Shader]
void main(void)
{
  gl_Position = ftransform();
  gl_TexCoord[0] = gl_MultiTexCoord0;
}
[Pixel_Shader]
uniform sampler2D sceneTex; // 0
uniform float vx_offset;
uniform float rt_w; // GeeXLab built-in
uniform float rt_h; // GeeXLab built-in
uniform float pixel_w; // 15.0
uniform float pixel_h; // 10.0
void main()
{
  vec2 uv = gl_TexCoord[0].xy;

  vec3 tc = vec3(1.0, 0.0, 0.0);
  if (uv.x < (vx_offset-0.005))
  {
    float dx = pixel_w*(1./rt_w);
    float dy = pixel_h*(1./rt_h);
    vec2 coord = vec2(dx*floor(uv.x/dx),
                      dy*floor(uv.y/dy));
    tc = texture2D(sceneTex, coord).rgb;
  }
  else if (uv.x>=(vx_offset+0.005))
  {
    tc = texture2D(sceneTex, uv).rgb;
  }
gl_FragColor = vec4(tc, 1.0);
}


Credits:

This pixelation shader is a modified version for GeeXLab of Agnius Vasiliauskas's original work (unsure who has done the modification though).


I can get it to 'compile' (well doesn't give an error) but not surprisingly nothing happens because it takes input and gives an output, neither of which I have any idea on how to handle -- or indeed whether or not this sort of shader can be used with GLB.  Heck I barely understand any of this so not much chance of sorting it out (not that it will stop me from trying of course!).  :blink:
Mat. 5: 14 - 16

Android: Toshiba Thrive Tablet (3.2), Samsung Galaxy Tab 2 (4.1.2).
Netbook: Samsung N150+ Netbook (Win 7 32-bit + Ubuntu 11.10).
Desktop: Intel i5 Desktop with NVIDIA GeForce GTX 460 (Win 8.1 64-bit).

Slydog

#5
So the pixelization on the model isn't all of the time, just under certain circumstances?
In that case, you could still have two more options without using shaders:

  • Create two separate texture files and just use the X_SETTEXTURE command just before drawing the model, specifying which texture you want to use. 
    One texture would be the full resolution, and the other would be a pixelated version when you need that effect.
    You could maybe apply the pixel effect to your original image using your paint program, if it has that plugin.
  • If you need both textures at the same time (you said you can see the other texture up close through the pixelized), OpenGL supports models with multiple overlaying textures.
    You would just disable / enable the pixel texture when needed.
    But I don't know if GLB allows this with its commands, plus mobile devices may not support it.

If you end up needing to use a shader, I would be interested in helping out, for my experience.  I want to learn as much as I can about them.
I've only recently created a basic shader to specify a colour overlay, so I can dynamically change my model's colour shade each frame, without updating all the vertice data each frame.

And yes, it's possible to manipulate vertices.  You can edit exiting models already in memory using commands such as X_GETFACE.
Or create the model with pure code using X_OBJADDVERTEX, as my game does.  I have no pre-made models (yet).

The effect I was talking about could be explained better by looking at my texture file below.  Nothing fancy.  Just instead of octagons and rectangles, use a triangle shape.


[attachment deleted by admin]
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

Hatonastick

I just found the screenshots I'd taken of the effect in question ages back (I think I'd asked about it on the Blitz forums).  Anyway this illustrates it far better than I could.  One is just polygons with the effect turned off, the other is the bots with the effect turned on.  The pixelization looks better when zoomed out btw.  Anyway if you look at the screenshot where the effect is on, you can just see on a couple of places that the polygon version is underneath the pixelized image.

[attachment deleted by admin]
Mat. 5: 14 - 16

Android: Toshiba Thrive Tablet (3.2), Samsung Galaxy Tab 2 (4.1.2).
Netbook: Samsung N150+ Netbook (Win 7 32-bit + Ubuntu 11.10).
Desktop: Intel i5 Desktop with NVIDIA GeForce GTX 460 (Win 8.1 64-bit).

Slydog

I took a quick look at that shader, and it appears to be a post processing shader.
In that it doesn't apply to a specific model, but to the entire scene as a whole.
This means you can't localize this shader to a model to pixelate it.
However it may be modified for this purpose, I'm not sure exactly.

It could be easily modified to NOT be a shader, but a regular function.
Just pass it ANY image, and desired pixel width / height, and it could return a pixelized sprite, or something.

Here's my understand of what / how this shader works:
Code (glbasic) Select

Inputs

// This is the source, final scene image, before pixlation
sceneTex (sampler2D): the final scene image.

vx_offset (float): offset of the vertical red line
// I *think* this is how wide and tall the scene image is
rt_w (float): render target width (GeeXLab built-in uniform)
rt_h (float): render target height (GeeXLab built-in uniform)
// How wide / tall the pixels will be
pixel_w (float): width of a low resolution pixel
pixel_h (float): height of a low resolution pixel
Ouputs: color buffer

Shader code:

// The Vertex shader only applys to the model vertices, so nothing here is modified, just passed through
[Vertex_Shader]
void main(void)
{
  gl_Position = ftransform();
  gl_TexCoord[0] = gl_MultiTexCoord0;
}

[Pixel_Shader]
uniform sampler2D sceneTex; // Reference to the scene image
uniform float vx_offset; // How far over to draw the red line, or where to split the image, from [0] to [1] range I think
uniform float rt_w; // GeeXLab built-in  // Scene width
uniform float rt_h; // GeeXLab built-in  // Scene height
uniform float pixel_w; // 15.0  // Width of Pixels
uniform float pixel_h; // 10.0  // Ht of Pixels

// This is called once for EVERY pixel in the scene
void main()
{
// Return the current X/Y of the source pixel in scene image we're processing
  vec2 uv = gl_TexCoord[0].xy;

// New colour: Red,  will be updated to the destination colour if before or after the red line
  vec3 tc = vec3(1.0, 0.0, 0.0);

// If we're before the red split line (ie: the pixelated portion)
  if (uv.x < (vx_offset-0.005))
  {
// How wide, in percentage (0 to 1) is the desired pixel width based on original scene width
    float dx = pixel_w*(1./rt_w);
// How tall, in percentage (0 to 1) is the desired pixel height based on original scene height
    float dy = pixel_h*(1./rt_h);
// Create an x/y variable, x=pixel percent * (current x/ pixel percent), etc
// This effect basically figures out what pixelated group the current pixel belongs to.
// The returned x/y will be the same colour for all pixels in same group.
// No averaging is being done, it just returns an original colour, but the same pixel shares the same colour
    vec2 coord = vec2(dx*floor(uv.x/dx),
                      dy*floor(uv.y/dy));
// Get the original pixel colour, but at the shared location.
    tc = texture2D(sceneTex, coord).rgb;
  }

// We're beyond the red line split, so just copy the original source pixel unmodified
  else if (uv.x>=(vx_offset+0.005))
  {
// Get the rgb value from the source image at the x/y location 'uv' (the current processed location)
    tc = texture2D(sceneTex, uv).rgb;
  }

// Finally, just set the destination pixel to the determined colour
gl_FragColor = vec4(tc, 1.0);
}


I don't know the limitations of GLBasic shaders, if you can pass it an image, this should work fine.
But as I said, this isn't what you want, but it could be helpful for creating your new shader.

But again, you could use this shader on just the models you want pixelated by drawing those models offscreen, apply this shader, then paste the sprite into your scene at the same location.  May be slow.  And again, you don't need a shader, just convert the above code to a function and pass it the scene image, and it returns a pixelated version.
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

Slydog

Or, could this be done by simply:

  • drawing your models that require pixelization to an offscreen buffer
  • take that offscreen buffer sprite and reduce it to a much smaller size (based on your desired pixel width) There are image reduction algos around, or use polyvector or STRETCHSPRITE to reduce it.
  • Enlarge the new sprite back to original size, image data / quality SHOULD be lost, ie: the pixel effect
  • Draw new sprite where you would have draw the original models

The shrink / enlarging may not look right and not so pixelly, but it may end up being smoothed out to reduce the effect.
Or a custom algo that just loops and skips to every 'x' pixels (based on pixel effect size) in both x and y directions.
It takes whatever pixel it finds, and pastes to new image, the end result being a much smaller image by eliminating pixels (kinda the same as above but custom algo)
It doesn't HAVE to average, just take whatever pixel colour it finds in that region and use that as the colour for all others that share that location.
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

erico


Hatonastick

#10
Thanks for taking a look at it.

What would be really handy is if GLB handled the passing of an image so a post processing shader could do its magic.  I honestly have more use for such shaders now and in the future than I do for shaders that affect models -- but I'm possibly the only one.  :whistle:  Hmm maybe it already can and I just don't know how.  Really that's one for Gernot to answer I guess...

As for alternatives, last time I looked into it (I admit it was with Blitz 3D though) the methods you've listed were tried and were pretty much too slow on any screen at a modern resolution.  Certainly nowhere near the speed you can get from a mid-range GPU with shaders at least.

Not sure if this hackish approach would work, but I guess there could be a fake model with a texture that is used to store the screen, rewrite the shader to act upon that texture, then use it to draw to the screen.  Would that work?  I have no idea.  I might give it a go when I'm somewhat less fatigued.

Anyway thanks again for spending your time looking into this!  To be honest, although I've been busy on other things and haven't had a chance to get back to it yet.
Mat. 5: 14 - 16

Android: Toshiba Thrive Tablet (3.2), Samsung Galaxy Tab 2 (4.1.2).
Netbook: Samsung N150+ Netbook (Win 7 32-bit + Ubuntu 11.10).
Desktop: Intel i5 Desktop with NVIDIA GeForce GTX 460 (Win 8.1 64-bit).

Kitty Hello

you can set a texture with X_SETTEXTURE. You have 2 units then.

Hatonastick

Mat. 5: 14 - 16

Android: Toshiba Thrive Tablet (3.2), Samsung Galaxy Tab 2 (4.1.2).
Netbook: Samsung N150+ Netbook (Win 7 32-bit + Ubuntu 11.10).
Desktop: Intel i5 Desktop with NVIDIA GeForce GTX 460 (Win 8.1 64-bit).

Kitty Hello

yes. For multitexturing, or texture blending.
Bump-mapping e.g. binds 2 textures and uses the 2nd texture as the "bump normals" RGB=XYZ.

So, there's really not really any limit in the use of shaders with GLBasic. See my "C64-izer" demo in the showroom.

Hatonastick

Ahhh of course!  I thought I'd seen a demo of similar shaders done in GLB.  Sorry, brain is on the blink mate...  I'll have to give it a go.
Mat. 5: 14 - 16

Android: Toshiba Thrive Tablet (3.2), Samsung Galaxy Tab 2 (4.1.2).
Netbook: Samsung N150+ Netbook (Win 7 32-bit + Ubuntu 11.10).
Desktop: Intel i5 Desktop with NVIDIA GeForce GTX 460 (Win 8.1 64-bit).