Textures

Learn OpenGL - Textures

Loading and Creating Textures

The first thing we need to do is load up a texture. For that we'll use a new library.

JuicyPixels

JuicyPixels is the basic picture loading library in Haskell. It supports a lot of formats, a lot of pixel types, all the stuff you'd want to do. There's also hip, which is like JuicyPixels with more features, but we'll stick with the basics for now.

To start, we load up a sample file, and if we can't load the file we'll just supply a generated image instead.

-- Imports Section:
-- JuicyPixels
import Codec.Picture (readImage, generateImage, convertRGB8, DynamicImage(..), Image(..), PixelRGB8(..))

-- Before The Main Loop:
-- ready our texture
eErrDI <- readImage "texture-demo.jpg"
dyImage <- case eErrDI of
    Left e -> do
        putStrLn e
        return $ ImageRGB8 $ generateImage (\x y ->
            let x' = fromIntegral x in PixelRGB8 x' x' x') 800 600
    Right di -> return di
let ipixelrgb8 = convertRGB8 dyImage
    iWidth = fromIntegral $ imageWidth ipixelrgb8
    iHeight = fromIntegral $ imageHeight ipixelrgb8
    iData = imageData ipixelrgb8

Generating a Texture

Making the texture object is nearly the exact same as with the other objects, this time with glGenTextures instead. We also bind it with glBindTexture.

textureP <- malloc
glGenTextures 1 textureP
texture <- peek textureP
glBindTexture GL_TEXTURE_2D texture

And here comes the tricky part: iData is a Data.Vector.Storable Vector (PixelBaseComponent a), but what we need is a Ptr to the internals, so we use unsafeWith to access that for just a moment.

-- Import:
-- vector
import qualified Data.Vector.Storable as VS

-- Texture Loading:
VS.unsafeWith iData $ \dataP ->
    glTexImage2D GL_TEXTURE_2D 0 GL_RGB iWidth iHeight 0 GL_RGB GL_UNSIGNED_BYTE (castPtr dataP)

Finally, we have to be sure to make the Mipmap, and then set the Texture aside for now.

glGenerateMipmap GL_TEXTURE_2D
glBindTexture GL_TEXTURE_2D 0

Applying Textures

To use our texture, we just adjust our vertex data and update the shader code. Then in the main loop we draw like this:

-- draw our triangle
glUseProgram shaderProgram
glBindTexture GL_TEXTURE_2D texture
glBindVertexArray vao
glDrawElements GL_TRIANGLES 6 GL_UNSIGNED_INT nullPtr
glBindVertexArray 0
glBindTexture GL_TEXTURE_2D 0

And we get... an upside down picture! As explained a little later in the tutorial, OGL expects 0,0 to be the bottom left, but JuicyPixels has it as the top left, so we need to adjust our Vertex Shader to use the suggested fix:

TexCoord = vec2(texCoord.x, 1.0f - texCoord.y);

And if we want the rainbow overlay effect, we can also adjust the fragment shader.

color = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0f);

Code So Far

Texture Units

Now we're going to blend in a second picture. The process for loading and getting things arranged is much the same as before. We update our fragment shader so that it will use both textures. We update our Haskell source so that it loads both textures:

-- ready our texture0
texture0P <- malloc
glGenTextures 1 texture0P
texture0 <- peek texture0P
glBindTexture GL_TEXTURE_2D texture0
-- wrapping and filtering params would go here.
eErrDI0 <- readImage "texture-demo.jpg"
dyImage0 <- case eErrDI0 of
    Left e -> do
        putStrLn e
        return $ ImageRGB8 $ generateImage (\x y ->
            let x' = fromIntegral x in PixelRGB8 x' x' x') 800 600
    Right di -> return di
let ipixelrgb80 = convertRGB8 dyImage0
    iWidth0 = fromIntegral $ imageWidth ipixelrgb80
    iHeight0 = fromIntegral $ imageHeight ipixelrgb80
    iData0 = imageData ipixelrgb80
VS.unsafeWith iData0 $ \dataP ->
    glTexImage2D GL_TEXTURE_2D 0 GL_RGB iWidth0 iHeight0 0 GL_RGB GL_UNSIGNED_BYTE (castPtr dataP)
glGenerateMipmap GL_TEXTURE_2D
glBindTexture GL_TEXTURE_2D 0

-- ready our texture1
texture1P <- malloc
glGenTextures 1 texture1P
texture1 <- peek texture1P
glBindTexture GL_TEXTURE_2D texture1
-- wrapping and filtering params would go here.
eErrDI1 <- readImage "texture-demo2.jpg"
dyImage1 <- case eErrDI1 of
    Left e -> do
        putStrLn e
        return $ ImageRGB8 $ generateImage (\x y ->
            let x' = fromIntegral x in PixelRGB8 x' x' x') 800 600
    Right di -> return di
let ipixelrgb81 = convertRGB8 dyImage1
    iWidth1 = fromIntegral $ imageWidth ipixelrgb81
    iHeight1 = fromIntegral $ imageHeight ipixelrgb81
    iData1 = imageData ipixelrgb81
VS.unsafeWith iData1 $ \dataP ->
    glTexImage2D GL_TEXTURE_2D 0 GL_RGB iWidth0 iHeight0 0 GL_RGB GL_UNSIGNED_BYTE (castPtr dataP)
glGenerateMipmap GL_TEXTURE_2D
glBindTexture GL_TEXTURE_2D 0

Wow that's ugly and pure duplication between the first and second use (other than the names). We'll definitely have to take a moment to clean that up once we know a little more about how we'll be using textures on a regular basis (our shader loader code might give you an idea of how we might want to do it instead). We'd also possibly even make our fancy loader flip the rows around so that they're in the order that OGL expects.

For the moment we'll just move forward and adjust how we do our drawing so that we can see some results.

-- before Main Loop
-- the name of our uniforms
ourColor <- newCString "ourColor"
ourTexture0 <- newCString "ourTexture0"
ourTexture1 <- newCString "ourTexture1"

-- during Main Loop
-- bind textures using texture units
glActiveTexture GL_TEXTURE0
glBindTexture GL_TEXTURE_2D texture0
our0Loc <- glGetUniformLocation shaderProgram ourTexture0
glUniform1i our0Loc 0
glActiveTexture GL_TEXTURE1
glBindTexture GL_TEXTURE_2D texture1
our1Loc <- glGetUniformLocation shaderProgram ourTexture1
glUniform1i our1Loc 1
-- draw our rectangle with the textures on it
glBindVertexArray vao
glDrawElements GL_TRIANGLES 6 GL_UNSIGNED_INT nullPtr
glBindVertexArray 0

And now you should see both pictures blended on top of each other!

Full Code

results matching ""

    No results matching ""