Jump to content

OpenGL GlReadPixels


Recommended Posts

I have been working with OpenGL recently seeing if I could rewrite the GDI graphics end of the game engine I have been working on. '?do=embed' frameborder='0' data-embedContent>>

It has been going well and I have been making a lot of progress so far. Most recently I changed the perspective to isometric view (see the top left image). I then decided as a test to make the entire view rotateable. (top right and bottom left images) This all worked but as you can imagine, tile picking doesn't work anymore.

2gv639u.png

I have been looking into the best way to do the tile picking and came to a conclusion that there are 3 methods. Ray casting, using the GL_SELECT rendering mode, or using color picking on an underlying and never visible layer when necessary. I decided on color picking by assigning each tile a color (bottom right image) and using the GlReadPixel function at the mouse coordinates. I would only need to check the colors on mouse clicks and drawing the color tiles in a display list made for little to no speed loss.

Unfortunately I can't seem to get the GlReadPixels function to work properly. I found two variations of the function in searches.

Func _glReadPixels($x, $y, $width, $height, $format, $type, ByRef $pixels)
    DllCall( 'opengl32.dll', 'none', 'glReadPixels', _
    'int', $x, _
    'int', $y, _
    'int', $width, _
    'int', $height, _
    'uint', $format, _
    'uint', $type, _
    'ptr', DllStructGetPtr( $pixels))
EndFunc




Func glReadpixels($x, $y, $width, $height, $format, $type, $pixels)
    DllCall("opengl32.dll", "none", "glReadpixels", "int", $x, "int", $y, "int", $width, "int", $height, "uint", $format, "uint", $type, "dword", $pixels)
EndFunc

I have been trying all sort of guess and test calls to get the correct return values which should be floats of the red, green, and blue values or even integers between 0 and 255 representing the values but can't seem to get the function to return anything but 0. I'm not very knowlegeable about pointers and dllstructs and was wondering if anyone could help please.

Some things I have tried:

Opt("MouseCoordMode", 2) ;to ensure correct window based coordinates

$Mouse = MouseGetPos()

$tColors = DllStructCreate("BYTE[10]") ;also tested as a float
Global $pColors = DllStructGetPtr($tColors)

_glReadpixels($Mouse[0], $Mouse[1], 1, 1, $GL_RGBA, $GL_UNSIGNED_BYTE, $pColors)

MsgBox(0, "", DllStructGetData($tColors, 1))

Msdn states that the correct function syntax would be as follows:

void WINAPI glReadPixels(
  GLint x,
  GLint y,
  GLsizei width,
  GLsizei height,
  GLenum format,
  GLenum type,
  GLvoid *pixels
);

 

I would appreciate any help with this. Thanks.

Edited by Tomb
Link to comment
Share on other sites

Hi Tomb,

I've just seen your message (I'm not logged on every day) and this problem. I think your code looks fine, but I'm pretty sure that "glReadpixels" in the DllCall is with a capital p: "glReadPixels". And I do think that matters. (I see now that you have a capital p in the first function.)

This function works for me:

Func glReadPixels($x, $y, $width, $height, $format, $type, $pixels)
    DllCall("opengl32.dll", "none", "glReadPixels", "int", $x, "int", $y, "int", $width, "int", $height, "uint", $format, "uint", $type, "ptr", $pixels)
    If @error Then MsgBox( 0, "glReadPixels", "Error" )
EndFunc

Regards Lars.

Edited by LarsJ
Link to comment
Share on other sites

I use this code for a screen dump of an OpenGL window:

Local Static $iDump = 0
If $iDump = 0 Then
    Local $tBuffer = DllStructCreate( "byte[" & $iWidth * $iHeight * 3 & "]" )
    glReadPixels( 0, 0, $iWidth, $iHeight, $GL_RGB, $GL_UNSIGNED_BYTE, DllStructGetPtr( $tBuffer ) )
    Local $hFile = FileOpen( "image.data", 18 )
    FileWrite( $hFile, DllStructGetData( $tBuffer, 1 ) )
    FileClose( $hFile )
    $iDump = 1
EndIf
I just add the code to the display function.

The raw RGB/RGBA image file "image.data" can be opened with GIMP (the extension must be "data").

Edited by LarsJ
Link to comment
Share on other sites

Thank you for your help LarsJ. With your example and function I managed to get the right return values. Now I can determine where the player clicks regardless of world rotation :)

Link to comment
Share on other sites

Good to see that it works.

The code can be slightly optimized. You don't need DllStructGetPtr():

Func glReadPixels( $x, $y, $width, $height, $format, $type, $pixels )
    DllCall( "opengl32.dll", "none", "glReadPixels", "int", $x, "int", $y, "int", $width, "int", $height, "uint", $format, "uint", $type, "struct*", $pixels )
EndFunc

Local Static $iDump = 0
If $iDump = 0 Then
    Local $tBuffer = DllStructCreate( "byte[" & $iWidth * $iHeight * 3 & "]" )
    glReadPixels( 0, 0, $iWidth, $iHeight, $GL_RGB, $GL_UNSIGNED_BYTE, $tBuffer )
    Local $hFile = FileOpen( "image.data", 18 )
    FileWrite( $hFile, DllStructGetData( $tBuffer, 1 ) )
    FileClose( $hFile )
    $iDump = 1
EndIf

If you have a small example that shows how you do that tile picking, I would like to see it.

Regards Lars.

Link to comment
Share on other sites

  • 2 weeks later...

Hey LarsJ. Thanks for posting this example its a neat optimization to get rid of the unnecessary DllStructGetPtr call.

I was trying to put together a small stripped down working example but decided I should just post the code snippets for now where the tile picking happens.

First the map is loaded into an array and I create two display lists of the data so that I can quickly redraw the map every loop.

$MapList = glGenLists(1)
$ColorMapList = glGenLists(1)

GLNewList($MapList, $GL_COMPILE)

    For $X = 1 to 12
        For $Y = 1 to 12
            DrawSprite2($X * 50 - 50, $Y * 50 - 50, 50, 50, 0, $Map[$X][$Y][0] - 1, 0)
        Next
    Next

GLEndList()



GLNewList($ColorMapList, $GL_COMPILE)
    glDisable($GL_TEXTURE_2D)

    For $X = 1 to 12
        For $Y = 1 to 12

            _glPushMatrix()

            _glTranslatef($X * 50 - 50, $Y * 50 - 50, 0)


            glBegin($GL_QUADS)
                glColor3f($X / 15, $Y / 15, 1.0)

                _glVertex3i(0, 0, 0)
                _glVertex3i(50, 0, 0); 
                _glVertex3i(50, 50, 0); 
                _glVertex3i(0, 50, 0);
            glEnd()

                glColor3f(1.0, 1.0, 1.0)

            _glPopMatrix()
        Next
    Next

    glEnable($GL_TEXTURE_2D)
GLEndList()

The first display list, $MapList is a collection of the map tiles that will be drawn where the other display list ColorMapList just contains colors which are calculated based on the coordinates.

DrawSprite2 is just a reusable function I made for placing the textured quads.

Func DrawSprite2($X, $Y, $W, $H, $R, $CurrentFrame = 0, $D = 0)

    Local $SpriteWidth = $W
    Local $SpriteHeight = $H
    Local $CalcW = $ImageHeight / $SpriteHeight


    _glPushMatrix()

    _glTranslatef($X, $Y, $D)


    $x_cell = $CurrentFrame * $cell_division
    $x_cell2 = ($CurrentFrame * $cell_division) + $cell_division


    If $R <> 0 Then
        _glTranslatef($W / 2, $H / 2, 0.0)
        _glRotatef($R, 0.0, 0.0, 1.0)
        _glTranslatef(-$W / 2, -$H / 2, 0.0)
    EndIf


    glBegin($GL_QUADS)
        _glTexCoord2d($x_cell, 0.0)
        _glVertex3i(0, 0, 0)

        _glTexCoord2d($x_cell2, 0.0)
        _glVertex3i($SpriteWidth, 0, 0); 

        _glTexCoord2d( $x_cell2, 1.0 /$CalcW)
        _glVertex3i( $SpriteWidth, $SpriteHeight, 0); 

        _glTexCoord2d( $x_cell, 1.0 /$CalcW)
        _glVertex3i(0, $SpriteHeight, 0);
    glEnd()

    _glPopMatrix()

EndFunc

For this current test I am only using tile picking when the left mouse is down. If the mouse is down, I draw the color map and check the pixel color, calculate which tile is under the mouse, clear the buffers, and draw the normal map.

If BitAND($aKeyBoardState[01], 0xF0) > 0 Then

    GLDisable($GL_LIGHTING)

    glCallList($ColorMapList)

    Local $iWidth = 1
    Local $iHeight = 1


    Local $tBufferR = DllStructCreate( "float[" & $iWidth * $iHeight * 1 & "]" )
    glReadPixels( $MouseX, $CurrentHeight - $MouseY, $iWidth, $iHeight, $GL_RED, $GL_FLOAT, $tBufferR)

    Local $tBufferG = DllStructCreate( "float[" & $iWidth * $iHeight * 1 & "]" )
    glReadPixels( $MouseX, $CurrentHeight - $MouseY, $iWidth, $iHeight, $GL_GREEN, $GL_FLOAT, $tBufferG)

    ;Local $tBufferB = DllStructCreate( "float[" & $iWidth * $iHeight * 1 & "]" )
    ;glReadPixels( $Mouse[0], 600 - $Mouse[1], $iWidth, $iHeight, $GL_BLUE, $GL_FLOAT, DllStructGetPtr( $tBufferB ) )


    $col = Round(DllStructGetData($tBufferR, 1) * 15 - 1)
    $row = Round(DllStructGetData($tBufferG, 1) * 15 - 1)


    _glClear($GL_COLOR_BUFFER_BIT + $GL_DEPTH_BUFFER_BIT)

    GLEnable($GL_LIGHTING)
EndIf

glCallList($MapList)

I separated the red and green values into their own glReadPixels calls for now to keep things simple. Unfortunately drawing the color map can be a little slow so for now I just check position on mouse clicks which works good but doesn't allow hover tile picking for tooltips or other sort of things. Although it does work great for determining which tile was clicked.

 

Hopefully the code doesn't make this post too long but I wanted to ask you one more question. I saw your examples using glCallLists to draw text and was trying to use glCallLists for non-text calls to merge the calls into a single call. For example using _glTranslatef once to set the draw position and draw a bunch of lists at the translated position using glCallLists instead of calling each list separately. Display lists at different locations would need to be translated and drawn separately which is fine. I was just thinking it might be a possible optimization.

I tried something like this:

$ListArray = glGenLists(10)

For $i = 0 to 10

glDisable($GL_TEXTURE_2D)

GLNewList($ListArray + $i, $GL_COMPILE)

        _glPushMatrix()

        _glTranslatef($i * 50 - 50, 0, 1)

        glBegin($GL_QUADS)
            glColor3f(Random(0, 1, 1), Random(0, 1, 1), 1.0)

            _glVertex3i(0, 0, 0)
            _glVertex3i(50, 0, 0);
            _glVertex3i(50, 50, 0);
            _glVertex3i(0, 50, 0);
        glEnd()

            glColor3f(1.0, 1.0, 1.0)

        _glPopMatrix()
GLEndList()

glEnable($GL_TEXTURE_2D)

Next

------------------------------------------
_glPushMatrix()
_glTranslatef(0, 0, 100.0)

Global $Arrays[10]
$Arrays[0] = 0
$Arrays[1] = 1
$Arrays[2] = 2
$Arrays[3] = 3
$Arrays[4] = 4
$Arrays[5] = 5
$Arrays[6] = 6
$Arrays[7] = 7
$Arrays[8] = 8
$Arrays[9] = 9
glListBase($ListArray)
glCallLists(10, $GL_UNSIGNED_BYTE, $Arrays)

_glPopMatrix()

But unfortunately none of the Display Lists appeared. Although a simple glCallList($ListArray + 1) worked.

Sorry for the delayed response and massive post, and thank you for your help! :)

Edited by Tomb
Link to comment
Share on other sites

What a lot of writing. And code too. Thank you. I have not had time to look at it yet. But I will look into it.

glCallLists. There is nothing wrong with your code. But there was a bug in my code. You have my apologies. You can find a flawless function here:

 

Func glCallLists( $n, $type, $lists )
    If IsString( $lists ) Then
        If $type <> $GL_UNSIGNED_BYTE Then Return SetError( 1, 0, 0 )
        DllCall( $dllOpenGL32, "none", "glCallLists", "int", $n, "uint", $type, "str", $lists )
        If @error Then Return SetError( 3, 0, 0 )
    ElseIf IsArray( $lists ) And UBound( $lists, 0 ) = 1 Then
        Local $sType, $l = UBound( $lists ), $struct
        Switch $type
            Case $GL_BYTE, $GL_UNSIGNED_BYTE
                $sType = "byte"
            Case $GL_SHORT
                $sType = "short"
            Case $GL_UNSIGNED_SHORT
                $sType = "ushort"
            Case $GL_INT
                $sType = "int"
            Case $GL_UNSIGNED_INT
                $sType = "uint"
            Case $GL_FLOAT
                $sType = "float"
            Case Else
                Return SetError( 1, 0, 0 )
        EndSwitch
        $struct = DllStructCreate( $sType & "[" & $l & "]" )
        For $i = 0 To $l - 1
            DllStructSetData( $struct, 1, $lists[$i], $i + 1 )
        Next
        DllCall( $dllOpenGL32, "none", "glCallLists", "int", $n, "uint", $type, "struct*", $struct )
        If @error Then Return SetError( 3, 0, 0 )
    Else
        Return SetError( 1, 0, 0 )
    EndIf
EndFunc

Regards Lars.

Link to comment
Share on other sites

Thank you very much for the new function LarsJ. It works perfectly and can be a great optimization to draw lots of things with fewer calls! :) I appreciate all of your help with getting the OpenGL functions working correctly.

Link to comment
Share on other sites

Thank you for pointing out the bug. I had only tested the first part of the function where $lists is a string. I had not seen this error.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...