More Textures




CS116b/CS216

Chris Pollett

Feb 5, 2014

Outline

Starting a Basic Texturing Project

My face on the faces of a cube

Getting a Face and Basic Project

  • PPM stands for portable pixmap.
  • The easiest and free-est way I could find to make new images in the format was to take a picture on a camera etc as a jpg. Then open this image in OpenOffice and save it (via right click) as a ppm image.
  • I then went to the book's website.
  • I set up my machine as per the instructions, and downloaded Hello World 3D and test built this project.
  • Since I am using a Mac, and Mavericks is less compatible with GLUT and Glew than before. I had to modify the Mac specific stuff in the included Makefile:
    ifeq ($(OS), Darwin) # Assume OS X
      CPPFLAGS += -D__MAC__ -I/usr/local/Cellar/glew/1.10.0/include/ --stdlib=libstdc++ -Wno-deprecated
      LDFLAGS += -framework GLUT -framework OpenGL -L/usr/local/Cellar/glew/1.10.0/lib/
    endif
    
    Notice I used brew to install the glew library and used the exact path to it above.
  • To use XCode I created an external build project, and added the files that came with the download. Under Product -> Edit Scheme -> Run, I set up the executable that should be run on launch.
  • For Windows users, the project comes with a Visual Studio solution.
  • The Shaders

    Vertex Old

    uniform mat4 uProjMatrix;
    uniform mat4 uModelViewMatrix;
    uniform mat4 uNormalMatrix;
    
    attribute vec3 aPosition;
    attribute vec3 aNormal;
    attribute vec2 aTexCoord0;
    
    varying vec3 vNormal;
    varying vec3 vPosition;
    varying vec2 vTexCoord0;
    
    void main() {
      vNormal = vec3(uNormalMatrix * vec4(aNormal, 0.0));
    
      // send position (eye coordinates) to fragment shader
      vec4 tPosition = uModelViewMatrix * vec4(aPosition, 1.0);
      vPosition = vec3(tPosition);
      vTexCoord0 = aTexCoord0;
      gl_Position = uProjMatrix * tPosition;
    }
    

    Vertex New

    #version 130
    
    uniform mat4 uProjMatrix;
    uniform mat4 uModelViewMatrix;
    uniform mat4 uNormalMatrix;
    
    in vec3 aPosition;
    in vec3 aNormal;
    in vec2 aTexCoord0;
    
    out vec3 vNormal;
    out vec3 vPosition;
    out vec2 vTexCoord0;
    
    void main() {
      vNormal = vec3(uNormalMatrix * vec4(aNormal, 0.0));
    
      // send position (eye coordinates) to fragment shader
      vec4 tPosition = uModelViewMatrix * vec4(aPosition, 1.0);
      vTexCoord0 = aTexCoord0;
      vPosition = vec3(tPosition);
      gl_Position = uProjMatrix * tPosition;
    }
    

    Fragment Old

    uniform vec3 uLight;
    uniform sampler2D uTexUnit0;
    
    varying vec3 vNormal;
    varying vec3 vPosition;
    varying vec2 vTexCoord0;
    
    void main() {
      vec3 tolight = normalize(uLight - vPosition);
      vec3 normal = normalize(vNormal);
      vec4 texColor0 = texture2D(uTexUnit0, vTexCoord0);
    
      float diffuse = max(0.0, dot(normal, tolight));
      gl_FragColor = texColor0 * diffuse;
    }
    

    Fragment New

    #version 130
    
    uniform vec3 uLight;
    uniform sampler2D uTexUnit0;
    
    in vec3 vNormal;
    in vec3 vPosition;
    in vec2 vTexCoord0;
    
    out vec4 fragColor;
    
    void main() {
      vec3 tolight = normalize(uLight - vPosition);
      vec3 normal = normalize(vNormal);
      vec4 texColor0 = texture2D(uTexUnit0, vTexCoord0);
    
      float diffuse = max(0.0, dot(normal, tolight));
      fragColor = texColor0 * diffuse;
    }
    

    Modifying main()

    main() is the first thing that is executed by our program, in the original Hello World program it sets things up, and then call glutMainLoop(). This creates the window, does the initial drawing, and waits for keyboard and mouse events. I added the line initTextures() below.

    int main(int argc, char * argv[]) {
      try {
        initGlutState(argc,argv);
    
        glewInit(); // load the OpenGL extensions
    
        cout << (g_Gl2Compatible ? 
           "Will use OpenGL 2.x / GLSL 1.0" : 
           "Will use OpenGL 3.x / GLSL 1.3") << endl;
        if ((!g_Gl2Compatible) && !GLEW_VERSION_3_0)
          throw runtime_error(
          "Error: card/driver does not support OpenGL Shading Language v1.3");
        else if (g_Gl2Compatible && !GLEW_VERSION_2_0)
          throw runtime_error(
          "Error: card/driver does not support OpenGL Shading Language v1.0");
    
        initGLState();
        initShaders();
        initGeometry();
        initTextures(); // added to Hello World 3D
    
        glutMainLoop();
        return 0;
      }
      catch (const runtime_error& e) {
        cout << "Exception caught: " << e.what() << endl;
        return -1;
      }
    }
    

    Changes to init Functions

    Below is the code for initTextures which loads and initializes the textures. I also modified the original initGeometry so that the scene now only has a cube and no ground (this allowed me to simplify my shaders earlier/not have to have another pair of shaders for the ground).

    
    static void initGeometry() {
      initCubes();
    }
    
    static void loadTexture(GLuint texHandle, const char *ppmFilename) {
        int texWidth, texHeight;
        vector<PackedPixel> pixData;
        
        ppmRead(ppmFilename, texWidth, texHeight, pixData);
        
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texHandle);
        glTexImage2D(GL_TEXTURE_2D, 0, g_Gl2Compatible ? 
           GL_RGB : GL_SRGB, texWidth, texHeight, 0, GL_RGB, 
                    GL_UNSIGNED_BYTE, &pixData[0]);
        checkGlErrors();
    }
    
    static void initTextures() {
        g_tex0.reset(new GlTexture());
        
        loadTexture(*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);
        
    }
    

    Setting up uniform and varying variables.

    This is done in initShader and makes use of a ShaderState struct. I modified this as below to now have textures.

    struct ShaderState {
      GlProgram program;
    
      // Handles to uniform variables
      GLint h_uLight;
      GLint h_uProjMatrix;
      GLint h_uModelViewMatrix;
      GLint h_uNormalMatrix;
      GLint h_uTexUnit0;
    
      // Handles to vertex attributes
      GLint h_aPosition;
      GLint h_aNormal;
      GLint h_aTexCoord0;
    
      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");
    
        // Retrieve handles to vertex attributes
        h_aPosition = safe_glGetAttribLocation(h, "aPosition");
        h_aNormal = safe_glGetAttribLocation(h, "aNormal");
        h_aTexCoord0 = safe_glGetAttribLocation(h, "aTexCoord0");
    
        if (!g_Gl2Compatible)
          glBindFragDataLocation(h, 0, "fragColor");
        checkGlErrors();
      }
    
    };
    

    Globals and Geometry Struct

    static const int g_numShaders = 1;
    static const char * const g_shaderFiles[g_numShaders][2] = {
      {"./shaders/basic-gl3.vshader", "./shaders/texture-gl3.fshader"}
    };
    static const char * const g_shaderFilesGl2[g_numShaders][2] = {
      {"./shaders/basic-gl2.vshader", "./shaders/texture-gl2.fshader"}
    };
    static vector<shared_ptr<ShaderState> > g_shaderStates; // our global shader states
    
    static shared_ptr<GlTexture> g_tex0;
    // --------- Geometry
    
    // Macro used to obtain relative offset of a field within a struct
    #define FIELD_OFFSET(StructType, field) &(((StructType *)0)->field)
    
    
    struct Geometry {
      GlBufferObject vbo, texVbo, ibo;
      int vboLen, iboLen;
        
      Geometry(GenericVertex *vtx, unsigned short *idx, int vboLen, int iboLen) {
        this->vboLen = vboLen;
        this->iboLen = iboLen;
    
        // Now create the VBO and IBO
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(GenericVertex) * vboLen, vtx, GL_STATIC_DRAW);
          
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short) * iboLen, idx, GL_STATIC_DRAW);
          
        glBindBuffer(GL_ARRAY_BUFFER, texVbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(GenericVertex) * vboLen, vtx,
                       GL_STATIC_DRAW);
      }
    
      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);
    
        // 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));
    
        // 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);
      }
    };
    
    
    // Vertex buffer and index buffer associated with the ground and cube geometry
    static shared_ptr<Geometry> g_cube;
    
    // --------- Scene
    
    static const Cvec3 g_light1(2.0, 3.0, 14.0);  // define light positions in world space
    

    GenericVertex

    The original GenericVertex did not have a copy constructor and assignment operator wasn't defined. So it could not be used in a vector. I added these. This was the last change I made.

    struct GenericVertex {
      Cvec3f pos;
      Cvec3f normal;
      Cvec2f tex;
      Cvec3f tangent, binormal;
    
      GenericVertex() {}
      GenericVertex(
        float x, float y, float z,
        float nx, float ny, float nz,
        float tu, float tv,
        float tx, float ty, float tz,
        float bx, float by, float bz)
        : pos(x,y,z), normal(nx,ny,nz), tex(tu, tv), 
          tangent(tx, ty, tz), binormal(bx, by, bz) {}
        GenericVertex(const GenericVertex& v) {
            *this = v;
        }
    
        GenericVertex& operator = (const GenericVertex& v) {
            pos = v.pos;
            normal = v.normal;
            tex = v.tex;
            tangent = v.tangent;
            binormal = v.binormal;
            return *this;
        }
    };
    

    Normal Mapping

    Two teapots demonstrating normal mapping