Still More Textures




CS116b/CS216

Chris Pollett

Feb 10, 2014

Outline

Introduction

Two teapots demonstrating normal mapping

Adding shaders, changes in Glut-land

Changes to ShaderState Constructor

I set up a second and third texture handles in my ShaderState. Note only one texture is actually ever being used at one time, so I could have done this more efficiently in my shader code later

struct ShaderState {
    GlProgram program;

    // Handles to uniform variables
    GLint h_uLight;
    GLint h_uProjMatrix;
    GLint h_uModelViewMatrix;
    GLint h_uNormalMatrix;
    GLint h_uTexUnit0;
    GLint h_uTexUnit1;
    GLint h_uTexUnit2; //won't need coords for cube map

    // Handles to vertex attributes
    GLint h_aPosition;
    GLint h_aNormal;
    GLint h_aTexCoord0;
    GLint h_aTexCoord1;

    ShaderState(const char* vsfn, const char* fsfn) {
        readAndCompileShader(program, vsfn, fsfn);

        const GLuint h = program; // short hand

        // Retrieve handles to uniform variables
        h_uLight = safe_glGetUniformLocation(h, "uLight");
        h_uProjMatrix = safe_glGetUniformLocation(h, "uProjMatrix");
        h_uModelViewMatrix = safe_glGetUniformLocation(h, "uModelViewMatrix");
        h_uNormalMatrix = safe_glGetUniformLocation(h, "uNormalMatrix");
        h_uTexUnit0 = safe_glGetUniformLocation(h, "uTexUnit0");
        h_uTexUnit1 = safe_glGetUniformLocation(h, "uTexUnit1");
        h_uTexUnit2 = safe_glGetUniformLocation(h, "uTexUnit2");

        // Retrieve handles to vertex attributes
        h_aPosition = safe_glGetAttribLocation(h, "aPosition");
        h_aNormal = safe_glGetAttribLocation(h, "aNormal");
        h_aTexCoord0 = safe_glGetAttribLocation(h, "aTexCoord0");
        h_aTexCoord1 = safe_glGetAttribLocation(h, "aTexCoord1");

        if (!g_Gl2Compatible)
            glBindFragDataLocation(h, 0, "fragColor");
        checkGlErrors();
    }

};

New Geometry Draw Method

Here we basically, just add a couple of lines for the second texture (we won't need to set up a vbo for the cube map):

    void draw(const ShaderState& curSS)
    {
        // Enable the attributes used by our shader
        safe_glEnableVertexAttribArray(curSS.h_aPosition);
        safe_glEnableVertexAttribArray(curSS.h_aNormal);
        safe_glEnableVertexAttribArray(curSS.h_aTexCoord0);
        safe_glEnableVertexAttribArray(curSS.h_aTexCoord1);

        // bind vbo
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        safe_glVertexAttribPointer(curSS.h_aPosition, 3, GL_FLOAT, GL_FALSE,
            sizeof(GenericVertex), FIELD_OFFSET(GenericVertex, pos));
        safe_glVertexAttribPointer(curSS.h_aNormal, 3, GL_FLOAT, GL_FALSE,
            sizeof(GenericVertex), FIELD_OFFSET(GenericVertex, normal));
        glBindBuffer(GL_ARRAY_BUFFER, texVbo);
        safe_glVertexAttribPointer(curSS.h_aTexCoord0, 2, GL_FLOAT, GL_FALSE,
            sizeof(GenericVertex), FIELD_OFFSET(GenericVertex, tex));
        safe_glVertexAttribPointer(curSS.h_aTexCoord1, 2, GL_FLOAT, GL_FALSE,
            sizeof(GenericVertex), FIELD_OFFSET(GenericVertex, tex));
        // bind ibo
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

        // draw!
        glDrawElements(GL_TRIANGLES, iboLen, GL_UNSIGNED_SHORT, 0);

        // Disable the attributes used by our shader
        safe_glDisableVertexAttribArray(curSS.h_aPosition);
        safe_glDisableVertexAttribArray(curSS.h_aNormal);
        safe_glDisableVertexAttribArray(curSS.h_aTexCoord0);
        safe_glDisableVertexAttribArray(curSS.h_aTexCoord1);
    }

Initing the Normal Map

The interesting part of the new normal map code is how we actually set up the texture. Here we modify to intTextures to set up the second texture, but to get the texture data for this texture we call loadSphereNormalTexture which calculates the values. We will also have a function loadCubeTexture which we'll talk about when we get to cube maps.

Notice glTexParameter* takes the kind of texture, the parameter to set and its value. The magnifcation minification filter and nearest have to do with how we round texture pixel values when we are taking pixels from the texture

static void loadSphereNormalTexture(GLuint type, GLuint texHandle)
{
    int width = 512, height = 512;
    vector<PackedPixel> pixels;
    float x = 0;
    float y = 0;
    float z = 0;
    float invRootThree = 1/sqrt(3);

    pixels.resize(width * height);
    for (int row = height - 1; row >= 0; row--) {
        for (int l = 0; l < width; l++) {
            PackedPixel &p = pixels[row * width + l];
            x = invRootThree * ((float)(row - width/2)/(width/2));
            y = invRootThree * ((float)(l - height/2)/(height/2));
            z = sqrt(1 - x*x - y*y);
            p.r = (unsigned char)(255 * (x + 1)/2);
            p.g = (unsigned char)(255 * (y + 1)/2);
            p.b = (unsigned char)(255 * (z + 1)/2);
        }
    }

    glActiveTexture(type);
    glBindTexture(GL_TEXTURE_2D, texHandle);
    glTexImage2D(GL_TEXTURE_2D, 0, g_Gl2Compatible ? GL_RGB : GL_SRGB, width,
                 height, 0, GL_RGB, GL_UNSIGNED_BYTE, &pixels[0]);
    checkGlErrors();
}

static void initTextures() {
    g_tex0.reset(new GlTexture());
    
    loadTexture(GL_TEXTURE0, *g_tex0, "myphoto.ppm");
    
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, *g_tex0);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

    g_tex1.reset(new GlTexture());

    loadSphereNormalTexture(GL_TEXTURE1, *g_tex1);

    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, *g_tex1);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

    g_tex2.reset(new GlTexture());

    loadCubeTexture(GL_TEXTURE2, *g_tex2, "one.ppm", "two.ppm",
        "three.ppm", "four.ppm", "five.ppm", "six.ppm");
    glActiveTexture(GL_TEXTURE2);
    glEnable(GL_TEXTURE_CUBE_MAP);
    glBindTexture(GL_TEXTURE_CUBE_MAP, *g_tex2);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

}

Normal Fragment Shaders

Notice the fragment shader only uses the texture stuff related to the normal mapping not to the basic or cube map shaders.

We are being lazy here. The normals from the texture are constants for the front face in the original orientation. For other faces in the initial view, we should transform these by how we would need to rotate the initial face to get that face. Further, if we rotate the whole object we should really multiply these normals by the NMVM.

Old Style

uniform vec3 uLight;
uniform sampler2D uTexUnit1;

varying vec3 vPosition;
varying vec2 vTexCoord1;

void main() 
{
    vec3 tolight = normalize(uLight - vPosition);
    vec4 texNormal = texture2D(uTexUnit1, vTexCoord1);
    vec3 scaledTexNormal = normalize(2.0 * vec3(texNormal)  - vec3(1., 1., 1.));

    float diffuse = max(0.0, dot(vec3(scaledTexNormal), tolight));
    gl_FragColor = vec4(0.7, 0.7, 0.7, 1.0) * diffuse;
}

New Style

#version 130

uniform vec3 uLight;
uniform sampler2D uTexUnit1;

in vec3 vPosition;
in vec3 vTexCoord1;

out vec4 fragColor;

void main() 
{
    vec3 tolight = normalize(uLight - vPosition);
    vec4 texNormal = texture2D(uTexUnit1, vTexCoord1);
    vec3 scaledTexNormal = normalize(2.0 * vec3(texNormal)  - vec3(1., 1., 1.));

    float diffuse = max(0.0, dot(vec3(scaledTexNormal), tolight));
    fragColor = vec4(0.7, 0.7, 0.7, 1) * diffuse;
}

Quiz

Which of the following statements is true?

  1. glViewport is used to set the projection matrix to map on to normalized device coordinates
  2. In our material shader example last week, the specular component of the output intensity at a point depended on the color of the material at that point.
  3. In the fragment shader last week to do texturing, we make use of both a uniform variable for the image data to sample from as well as a varying variable to say where one should take the sample.

Cube Maps

Two images, a cube map texture and a scene rendered with it

Loading our Cube Map

Recall we called the following function in our initTextures. Here is where we do the work of setting up the faces of our cube map

static void loadCubeTexture(GLuint type, GLuint texHandle,
    const char *ppmFilename1, const char *ppmFilename2,
    const char *ppmFilename3, const char *ppmFilename4,
    const char *ppmFilename5, const char *ppmFilename6)
{
    int texWidth, texHeight;
    vector<PackedPixel> pixData1, pixData2, pixData3,
        pixData4, pixData5, pixData6;

    ppmRead(ppmFilename1, texWidth, texHeight, pixData1);
    ppmRead(ppmFilename2, texWidth, texHeight, pixData2);
    ppmRead(ppmFilename3, texWidth, texHeight, pixData3);
    ppmRead(ppmFilename4, texWidth, texHeight, pixData4);
    ppmRead(ppmFilename5, texWidth, texHeight, pixData5);
    ppmRead(ppmFilename6, texWidth, texHeight, pixData6);

    glActiveTexture(type);
    glBindTexture(GL_TEXTURE_CUBE_MAP, texHandle);

    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0,
        GL_RGB, texWidth, texHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, &pixData1[0]);
    glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0,
        GL_RGB, texWidth, texHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, &pixData2[0]);
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0,
        GL_RGB, texWidth, texHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, &pixData3[0]);
    glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0,
        GL_RGB, texWidth, texHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, &pixData4[0]);
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0,
        GL_RGB, texWidth, texHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, &pixData5[0]);
    glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0,
        GL_RGB, texWidth, texHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, &pixData6[0]);
    checkGlErrors();
}

Implementing a Cube Map Shader

Old Shader

uniform samplerCube uTexUnit2;

varying vec3 vNormal;
varying vec3 vPosition;

vec3 reflect(vec3 w, vec3 n)
{
    return -w + n *(dot(w,n) *2.0);
}

void main(void)
{
    vec3 normal = normalize(vNormal);
    vec3 reflected = reflect(normalize(-vPosition), normal);
    gl_FragColor = textureCube(uTexUnit2, reflected);
}

New Shader

#version 330

uniform samplerCube uTexUnit2;

in vec3 vNormal;
in vec3 vPosition;

out fragColor;

vec3 reflect(vec3 w, vec3 n)
{
    return -w + n *(dot(w,n) *2.0);
}

void main(void)
{
    vec3 normal = normalize(vNormal);
    vec3 reflected = reflect(normalize(vec3(-vPosition)), normal);
    vec4 texColor0 = textureCube(texUnit2, reflected);
    fragColor = texColor0;
}

CubeMap Remarks

Projector Texture Mapping

Photograph, model, and texture mapping

Projector Texture Mapping Calculation

Geometry of Projector Texture Mapping

Vertex Shader for Projection Mapping

#version 330
uniform mat4 uModelViewmatrix;
uniform mat4 uProjMatrix;

uniform mat4 uSProjMatrix;
uniform mat4 uSModelViewMatrix;

in vec4 aVertex;
out vec4 aTexCoord;

void main()
{
   vTexCoord = uSProjMatrix * uSModelViewMatrix * aVertex;
   gl_Position = uProjMatrix * uModelViewMatrix * aVertex;
}

Fragment Shader for Projection Mapping

#version 330

uniform sampler2D vTexUnit0;

in vec4 vTexCoord;
out fragColor;

void main()
{
    vec2 tex2;
    tex2.x = vTexCoord.x/vTexCoord.w;
    tex2.y = vTexCoord.y/vTexCoord.w;
    vec4 texColor0 = texture2D(vTexUnit0, tex2);
    fragColor = texColor0;
}

Multipass Textures

Dynamic Reflection mapping