2
votes

I asked this question before about how to pass a data array to a fragment shader for coloring a terrain, and it was suggested I could use a texture's RGBA values.

I'm now stuck trying to work out how I would also use the yzw values. This is my fragment shader code:

vec4 data = texture2D(texture, vec2(verpos.x / 32.0, verpos.z / 32.0));
float blockID = data.x;
vec4 color;

if (blockID == 1.0) {
    color = vec4(0.28, 0.52, 0.05, 1.0);
}
else if (blockID == 2.0) {
    color = vec4(0.25, 0.46, 0.05, 1.0);
}
else if (blockID == 3.0) {
    color = vec4(0.27, 0.49, 0.05, 1.0);
}

gl_FragColor = color;

This works fine, however as you can see it's only using the float from the x-coordinate. If it was also using the yzw coordinates the texture size could be reduced to 16x16 instead of 32x32 (four times smaller).

The aim of this is to create a voxel-type terrain, where each 'block' is 1x1 in space coordinates and is colored based on the blockID. Looks like this: enter image description here

Outside of GLSL this would be simple, however with no ability to store which blocks have been computed I'm finding this difficult. No doubt, I'm over thinking things and it can be done with some simple math.


EDIT:
Code based on Wagner Patriota's answer:

vec2 pixel_of_target = vec2( verpos.xz * 32.0 - 0.5 ); // Assuming verpos.xz == uv_of_target ?

// For some reason mod() doesn't support integers so I have to convert it using int()
int X = int(mod(pixel_of_target.y, 2.0) * 2.0 + mod(pixel_of_target.x, 2.0));

// Gives the error "Index expression must be constant"
float blockID = data[ X ];

About the error, I asked a question about that before which actually led to me asking this one. :P
Any ideas? Thanks! :)

1

1 Answers

2
votes

The idea is to replace:

float blockID = data.x;

By

float blockID = data[ X ];

Where X is a integer that allows you to pick the R, G, B or A from your 16x16 data image.

The thing is how to calculate X in function of your UV?

Ok, you have a target image (32x32) and the data image (16x16). So let's do:

ivec pixel_of_target = ivec( uv_of_target * 32.0 - vec2( 0.5 ) ); // a trick!

Multiplying your UV with the texture dimesions (32 in this case) you find the exact pixel. The -0.5 is necessary because you are trying "to find a pixel from a texture". And of course the texture has interpolated values between the "center of the pixels". You need the exact center of the pixel...

Your pixel_of_target is an ivec (integers) and you can identify exactly where you are drawing! So the thing now is to identify (based on the pixel you are drawing) which channel you should get from the 16x16 texture.

int X = ( pixel_of_target.y % 2 ) * 2 + pixel_of_target.x % 2;
float blockID = data[ X ]; // party on!

This expression above allows you to pick up the correct index X based on the target pixel! On your "data texture" 16x16 map your (R,G,B,A) to (top-left, top-right, bottom-left, bottom-right) of every group of 4 pixels on your target (or maybe upside-down if you prefer... you can figure it out)

UPDATE:

Because you are using WebGL, some details should be changed. I did this and it worked.

vec2 pixel_of_target = vTextureCoord * 32.0 + vec2( 0.5 ); // the signal changed!
int _x = int( pixel_of_target.x );
int _y = int( pixel_of_target.y );
int X = mod( _y, 2 ) * 2 + mod( _x, 2 );

I used this for my test:

if ( X == 0 )
    gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );
else if ( X == 1 )
    gl_FragColor = vec4( 0.0, 1.0, 0.0, 1.0 );
else if ( X == 2 )
    gl_FragColor = vec4( 0.0, 0.0, 1.0, 1.0 );
else if ( X == 3 )
    gl_FragColor = vec4( 1.0, 0.0, 1.0, 1.0 );

My image worked perfectly fine:

Zoom

Here i zommed with Photoshop to see the deatails of the pixels.

PS1: Because I am not familiar with WebGL, I could not run WebGL in Chrome, I tried with Firefox, and I didn't find the mod() function either... So I did:

int mod( int a, int b )
{
    return a - int( floor( float( a ) / float( b ) ) * float( b ) );
}

PS2: I don't know why I had to sum vec2( 0.5 ) instead of subtract. WebGL is a little bit different. It probably has this shift. I don't know... It just works.