io7m
com.io7m.jcanephora 0.60.0
3.17
JCGLTexturesType

- 3.17.1. Description
- 3.17.2. Usage examples
- 3.17.3. Cube map coordinate systems

3.17.1
Description
1
The JCGLTexturesType interface provides functions for allocating, updating, and using textures.
3.17.2
Usage examples
1
The JCGLTexturesType interfaces expose functions that will allocate textures of formats supported by the current implementation. The interfaces only expose textures that are required to be supported by OpenGL 3.3 core.
2
In order to use a texture in OpenGL, the texture must be bound to a texture unit. There are a limited number of texture units available [6], and so the API requires the user to obtain access to a list of the current units:
3.17.2.1. Texture units

    JCGLTexturesType g;
    List<JCGLTextureUnitType> u = g.textureGetUnits();

3
A texture unit may have at most one texture bound to it at any one time. This is in slight contrast to the way that the OpenGL API normally works: The OpenGL API specifies that a given texture unit may have multiple textures bound to a given texture unit as long as all of the textures are of a different type (two-dimensional, cube map, etc). However, it then states that it is not legal to pass that texture unit to multiple different uniform parameters in a shading program. There is, therefore, very little practical utility to binding multiple textures to a single texture unit, and exposing such behaviour in the API complicates tracking of state and makes programs slightly more difficult to reason about. The jcanephora API therefore imposes a simple restriction: One texture per texture unit.
4
To allocate a 256 * 256 RGBA texture, 8 bits per channel (four bytes per pixel), with nearest-neighbour filtering and repeat wrapping around both axes:
3.17.2.2. Texture allocation

    JCGLTexturesType g;
    JCGLTextureUnitType u;

    JCGLTexture2DType t =
      g.texture2DAllocate(
        u,
        256,
        256,
        JCGLTextureFormat.TEXTURE_FORMAT_RGBA_8_4BPP,
        JCGLTextureWrapS.TEXTURE_WRAP_REPEAT,
        JCGLTextureWrapT.TEXTURE_WRAP_REPEAT,
        JCGLTextureFilterMinification.TEXTURE_FILTER_NEAREST,
        JCGLTextureFilterMagnification.TEXTURE_FILTER_NEAREST);

5
Once allocated, the texture will remain bound to the texture unit u until explicitly unbound.
6
The jcanephora exposes both two-dimensional and cube map textures. See the relevant superinterfaces.
3.17.3
Cube map coordinate systems
1
Note that in the OpenGL specification, cube textures arbitrarily use a left-handed coordinate system as opposed to OpenGL's right-handed system. This is a frequent source of errors in programs. See the following fragment shader:
3.17.3.1. Cube map shader

    in vec3 f_normal;   // Eye-space surface normal
    in vec3 f_position; // Eye-space surface position

    uniform mat4        m_view_inverse; // Inverse view matrix, eye-space -> world-space.
    uniform samplerCube t_reflection;

    out vec4 frag_color;

    void
    main (void)
    {
      vec3 n = normalize (f_normal);
      vec3 v = normalize (f_position);
      vec3 r = reflect (v, n);
      vec3 u = m_view_inverse * r;

      frag_color = texture (t_reflection, u);
    }

2
The program calculates a reflection vector r from the eye-space incident vector f_position and eye-space surface normal f_normal. It transforms r back into world-space, giving u, and then uses u to index into the cube texture. This would be textbook-correct, if OpenGL didn't arbitrarily use a left-handed coordinate system for cube textures! The coordinates u are obviously specified in a right-handed coordinate system. The practical results of this are that all of the cube texture faces will be displayed upside down, and the familiar concept of the negative Z axis meaning "forwards" will be reversed.
3
The jcanephora package contains support for using right-handed cube maps in OpenGL, consistent with the right-handed coordinate system used in most OpenGL programs. In order to use right-handed cube maps, the following steps are required:
3.17.3.2. Cube map addressing

1. Cube map textures are supplied to the OpenGL implementation using the "RH" variants of the texture loading and update functions. Faces are specified in a manner consistent with OpenGL's coordinate system. Informally, the "forward" face of the cube map is the negative Z face, the "right" face of the cube map is the positive X face, and so on.
2. In shaders, the coordinates passed to the texture function (or textureCube function, on old OpenGL implementations) should be multiplied with a matrix that effectively negates the Y and Z components.

4
The earlier example shader becomes:
3.17.3.3. Cube map shader

    in vec3 f_normal;   // Eye-space surface normal
    in vec3 f_position; // Eye-space surface position

    uniform mat4        m_view_inverse; // Inverse view matrix, eye-space -> world-space.
    uniform samplerCube t_reflection;

    out vec4 frag_color;

    void
    main (void)
    {
      const mat3 m_t = mat3 (
        1.0,  0.0,  0.0,
        0.0, -1.0,  0.0,
        0.0,  0.0, -1.0
      );

      vec3 n = normalize (f_normal);
      vec3 v = normalize (f_position);
      vec3 r = reflect (v, n);
      vec3 u = m_view_inverse * r;
      vec3 w = m_t * u;

      frag_color = texture (t_reflection, w);
    }

5
This approach means that both the loading and generation of cube textures is consistent with that of 2D textures and the rest of OpenGL, and the complexity of addressing the textures is limited to one function call in shaders. Renderers that produce cube maps do not need to use unusual projection matrices to "flip" the coordinate system when rendering, and everything works according to the principles of least astonishment!
6
The correct solution to the problem would have been for the cube map coordinate system to have been in a consistent right-handed coordinate space to begin with.

[6]
OpenGL 3.3 guarantees that at least 16 will be available in any implementation.