I don't understand height map code

Hello, I’m following this tutorial about height maps. My code works perfectly but I have a problem with understanding it’s main part. So
starting from the beginning:

  1. Where does this formula come from?
    unsigned char* texel = data + (j + width * i) * nChannels;
    Why is it like that?

  2. To get x and z vertices we divide proper negated value by 2 and add i or j. Why is this height or width of an image if the coordinates should be in range between 0-1? For instance let’s say that I have an 800x600 image so -800/2=-400, (i = 0) -400 + i = -400.

  3. Finally why y = texel[0]? Does it store the ‘grayness’ value?

for(unsigned int i = 0; i < height; i++)
{
    for(unsigned int j = 0; j < width; j++)
    {
        // retrieve texel for (i,j) tex coord
        unsigned char* texel = data + (j + width * i) * nChannels;
        // raw height at coordinate
        unsigned char y = texel[0];

        // vertex
        vertices.push_back( -height/2.0f + i );        // v.x
        vertices.push_back( (int)y * yScale - yShift); // v.y
        vertices.push_back( -width/2.0f + j/ );        // v.z
    }
}

this is used to retrieve the texel color data from the image previously loaded, in the previous code shown in the same tutorial, on which, j and i represents the x and y image coordinates.

You should use a square image for the purpose

texel[0] will give you only one of the channels of the loaded image, therefore will give out the R component of the image, so you could interpret as a single channel value wich can be read as a scale of red/grayscale.

this is used to retrieve the texel color data from the image previously loaded, in the previous code shown in the same tutorial, on which, j and i represents the x and y image coordinates.

Yea, I understand role of texel variable but why do we get singe texel doing this mathematical operation?

data + (j + width * i) * nChannels;

You should use a square image for the purpose

Can you be more specific?

Let’s assume that data is a linear one-dimensional array of bytes (each byte one color value) in the structure of Red, Green, Blue, Red, Green, Blue,… and each of the Red-Green-Blue groups represents one texel, beginning with the first texel (x = 0, y = 0), followed by the second texel (x = 1, y = 0), … until the last texel (x = width-1, y = height-1).
So, each row of texels would contain width * nChannels (in our example that would be the 3 channels Red, Green, and Blue) bytes, and the whole texture width * height * nChannels bytes.

Since the array is one-dimensional, we can’t easily use 3 coordinates (row, column, and channel) to address the values in it - we need to calculate the position in the array ourself.
The variables i and j denote the column and row respectively, in your code piece.
We know the start of each row is width * nChannels away from the row before and after it. So we can multiply i with width * channels to get to the beginning of the row. When we add j * nChannels to that location we get to beginning of the color triplet of the texel (j, i), and if we would add the channel, we want to get the value of, to that combined location, we have a location of that value in the data array (But let’s ignore the last part, as your code isn’t calculating channel values location, but just the texels start).

texelLocationInArray = j * nChannels + i * width * nChannels
or after factoring out nChannels
texelLocationInArray = (j + i * width) * nChannels

Since the variable texel in your code is not just the index into the data array but a pointer to the memory location, where the texel starts, the code uses pointer math and adds the index (our texelLocationInArray) to the location of the data array to get a new pointer.

1 Like

So i can be viewed as the y coordinate in the image grid, and j as the x. nChannels are the color channels tha image has as in RGB/RGBA/etc. Therefore, you are incrementing 1 pixel in the row each time and adding a whole row everytime you reach its width so you can progress the whole image.

Look at it this way, an image is a 2D grid, but this operation is converting a grid into a single 1D array of pixels in order to iterate through all of them, and then is multiplying by nChannel because you are using char so you need to perform a stride as the same size as the number of other channels to reach the value of the next pixel’s channel.

eg: you have an image of 3x3 RGB all RED, so:

     column1 | column2 | column3
row1 1,0,0   | 1,0,0   |1,0,0
row2 1,0,0   | 1,0,0   |1,0,0
row3 1,0,0   | 1,0,0   |1,0,0

no converting it to a 1D array of bytes, would be something like this:

100100100100100100100100100

now let us get only the R channel for each pixel:

so

|   |   |   |   |   |   |   |   |
v   v   v   v   v   v   v   v   v 
100|100|100|100|100|100|100|100|100
           ^           ^
           |end row1   |end row2

note that we move 2 bytes each pixel

1 Like

Image size =/= UV coordinates

You are mixing 2 concepts here:

Image/Map Sizes/Coordinates (which refers to a structure of pixels or a raster map)
and
UV Coordinates (which refers to a position in the Vertex to help the GPU interpolate the graphical output)

which means that, an image has a size and coordinate system based on their pixel count (x pixels wide, and Y pixels long)
A UV coordinate (which has use mostly on a vertex) will have a range from 0 (being the left corner of the texture in U axis), and 1 (being the right corner of texture in U axis) but this could also be outside this values, so basically 0UV could means pixel 0, and 1 UV could mean pixel 1023.

Thanks for excellent explanation of this concept!!! I understood everything. Can you also explain me this peace of code below?

        vertices.push_back( -height/2.0f + i );        // v.x
        vertices.push_back( (int)y * yScale - yShift); // v.y
        vertices.push_back( -width/2.0f + j/ );        // v.z

Why is it negated value of width/height divide by 2 plus i/j???

To obtain a grid where x=0, z=0 is in the centre rather than at one corner.

Not sure why x and z are swapped, though.

This topic was automatically closed 183 days after the last reply. New replies are no longer allowed.