2012年6月19日 星期二

OpenGL ES 2.0 Tutorial Part 2 - 1

since: 2012/06/18
update: 2012/06/19


reference:
1. OpenGL ES 2.0 for iPhone Tutorial Part 2: Textures | Ray Wenderlich
2. I touchs: OpenGL ES 2.0 Tutorial Part 1 - 7

紋理貼圖之一

A. 下載 "紋理" 檔案.
      1. 下載由原作者的 lovely wife 所製作的 紋理 檔案.

      2. 解壓縮後, 並將二個檔案(地磚)加到專案裡, 記得勾選:
           Copy items into destination group's folder(if needed)



-----------------------------------------------------------------------------------------

B. 讀取像素資料

      1. 說明:
           a. 首先, 要將圖片資料提供給 OpenGL 使用. 但是, OpenGL 無法直接使用
                PNG 的圖片資料. 取而代之, OpenGL 需要你傳入像素資料buffer,
                並且需要指定正確的格式.

           b. 幸運地, 使用一些內建的 Quartz2D 函式, 可以輕鬆的取得像素資料buffer.
               備註: 可以參考 Core Graphics 101 的教學系列文章.

      2. 有四個主要的步驟來完成此事:
           a. 取得 Core Graphics 圖片的參照
               因為要使用 Core Graphics 來產生原始的像素資料, 我們需要一個指向此圖片
               的參照. 這個很容易, 我們可以使用 UIImageCGImageRef 屬性.

           b. 建立 Core Graphics 的 bitmap(點陣圖) context
               下一步是建立 Core Graphics 的 bitmap context, 它是屬於記憶體中的緩衝區,
               用來儲存原始的像素資料.

           c. 將圖片繪製到 context
               可以呼叫 Core Graphics 的簡單函式來達成, 之後緩衝區裡就會包含原始的
               像素資料.

           d. 將像素資料傳送給 OpenGL
               為此, 我們需要建立一個 OpenGL texture(紋理) 物件, 並且取得它的唯一
               ID (稱為: name), 接著就可以呼叫函式將像素資料傳送給 OpenGL. 

      3. 開啟 OpenGLView.h 檔案, 修改如下:
....
//@add for texture
- (GLuint)setupTexture:(NSString *)fileName;
....

      4. 開啟 OpenGLView.m 檔案, 修改如下:
....
//@add for texture
- (GLuint)setupTexture:(NSString *)fileName
{   
    // step 1: Get Core Graphics image reference
    CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
    if (!spriteImage) {
        NSLog(@"Failed to load image %@", fileName);
        exit(1);
    }
   
   
    // step 2: Create Core Graphics bitmap context
    //
    // 2-1: get the width and height of the image
    size_t width = CGImageGetWidth(spriteImage);
    size_t height = CGImageGetHeight(spriteImage);
   
    // 2-2: allocate width * height * 4(red, green, blue, and alpha) bytes
    GLubyte * spriteData = (GLubyte *) calloc(width*height*4, sizeof(GLubyte));
   
    // The fourth parameter to CGBitmapContextCreate is the bits per component,
    // and we set this to 8 bits (1 byte).

    CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast); 
   
   
    // step 3: Draw the image into the context
    CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);

    // Since we're done with the context at this point, we can release it.
    CGContextRelease(spriteContext);


    // step 4: Send the pixel data to OpenGL
    GLuint texName;
   
    // 4-1: create a texture object(and get its unique ID: texName)
    glGenTextures(1, &texName);
   
    // 4-2.: load our new texture name into the current texture unit.
    glBindTexture(GL_TEXTURE_2D, texName);
   
    // 4-3: set a texture parameter for our texture
    //
    // 在此將 GL_TEXTURE_MIN_FILTER 設為 GL_NEAREST
    // 說明: 即使沒有使用到 mipmaps, 仍然需要設定 GL_TEXTURE_MIN_FILTER
    //            (mipmaps: 細節層次. 主要是針對貼圖做漸層的處理, 依據解析度來決定
    //             貼圖大小)

    //
    // GL_TEXTURE_MIN_FILTER: 繪製遠方的物體時, 將紋理縮小
    // GL_NEAREST: 使用最鄰近所對應到的紋理像素來繪製頂點
    //                             (而 GL_LINEAR 為 "平滑" 的方式)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   
    // 4-4: send the pixel data buffer to OpenGL
    //
    // 在此指定傳入的像素資料格式為: GL_RGBA 與 GL_UNSIGNED_BYTE,
    // 代表: 格式為 紅, 綠, 藍與 alpha, 並各自佔有 1 byte 大小.
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
   
   
    // step 5: deallocate the pixel buffer
    // (we've sent the image data to OpenGL, OpenGL is storing the texture in the GPU)
    free(spriteData);       
   
   
    // step 6: returning the texture name
    // (we'll need to refer to it later when drawing)
    return texName;
}
....

-----------------------------------------------------------------------------------------

C. 使用紋理資料
     1. 說明:
         到目前為止, 我們已經有一個輔助的方法(setupTexture:), 用來載入圖片,
         傳送給 OpenGL 並回傳一個紋理名稱. 接下來, 就可以使用它來幫立方體
         貼上一層外皮了.

************************************************************

     2. 開啟 SimpleVertex.glsl 檔案, 修改如下:
attribute vec4 Position;

attribute vec4 SourceColor;

varying vec4 DestinationColor;

//@add for Projection
//
// uniform: This means that we just pass in a constant value for all vertices,
//          rather than a per-vertex value.
//
// mat4: a 4×4 matrix(used to scale, rotate, or translate vertices)
//
// We'll pass in a matrix that moves our vertices around according to the Projection.
uniform mat4 Projection;
//@add
uniform mat4 Modelview;

//@add for texture
//
// for each vertex, we'll specify the coordinate on the texture that it should map to.
attribute vec2 TexCoordIn;
varying vec2 TexCoordOut;

void main(void) {

    DestinationColor = SourceColor;
   
    //gl_Position = Position;
    //@update for Projection
    //
    // Set the final position of the vertex to be the Projection multiplied by the Position.
    //gl_Position = Projection * Position;
    //@update: add applying the modelview matrix to the position.
    gl_Position = Projection * Modelview * Position;
   
    //@add for texture
    TexCoordOut = TexCoordIn;
}

************************************************************

     3. 開啟 SimpleFragment.glsl 檔案, 修改如下:
varying lowp vec4 DestinationColor;
//@add for texture
varying lowp vec2 TexCoordOut;
uniform sampler2D Texture;

void main(void) {

    //gl_FragColor = DestinationColor;
    //@update for texture
    //
    // we multiply the color by whatever is in the texture at the specified coordinate.
    // texture2D is a built-in GLSL function that samples a texture for us.
    gl_FragColor = DestinationColor * texture2D(Texture, TexCoordOut);
}

************************************************************

     4. 開啟 OpenGLView.h 檔案, 修改如下:
....
@interface OpenGLView : UIView
{
    //@add
    CAEAGLLayer *_eaglLayer;
    EAGLContext *_context;
    GLuint _colorRenderBuffer;
   
    //@add
    GLuint _positionSlot;
    GLuint _colorSlot;
   
    //@add for uniform
    GLuint _projectionUniform;
    GLuint _modelViewUniform;
   
    //@add
    float _currentRotation;
    GLuint _depthRenderBuffer;
   
    //@add for texture
    //
    // keep track of the texture names for our two textures
    GLuint _floorTexture;
    GLuint _fishTexture;
    // input attribute slot
    GLuint _texCoordSlot;
    // texture uniform slot
    GLuint _textureUniform;
}
....

************************************************************

     5. 開啟 OpenGLView.m 檔案, 修改如下:
....
// step01
typedef struct {
    float Position[3];
    float Color[4];
    //@add for texture
    float TexCoord[2]; // texture coordinates
} Vertex;

....
// step02
/*
const Vertex Vertices[] = {
    {{1, -1, 0}, {1, 0, 0, 1}},
    {{1, 1, 0}, {1, 0, 0, 1}},
    {{-1, 1, 0}, {0, 1, 0, 1}},
    {{-1, -1, 0}, {0, 1, 0, 1}},
    {{1, -1, -1}, {1, 0, 0, 1}},
    {{1, 1, -1}, {1, 0, 0, 1}},
    {{-1, 1, -1}, {0, 1, 0, 1}},
    {{-1, -1, -1}, {0, 1, 0, 1}}
};
*/
//@update for texture
// Add texture coordinates to Vertices
const Vertex Vertices[] = {
    {{1, -1, 0}, {1, 0, 0, 1}, {1, 0}},
    {{1, 1, 0}, {1, 0, 0, 1}, {1, 1}},
    {{-1, 1, 0}, {0, 1, 0, 1}, {0, 1}},
    {{-1, -1, 0}, {0, 1, 0, 1}, {0, 0}},
    {{1, -1, -1}, {1, 0, 0, 1}, {1, 0}},
    {{1, 1, -1}, {1, 0, 0, 1}, {1, 1}},
    {{-1, 1, -1}, {0, 1, 0, 1}, {0, 1}},
    {{-1, -1, -1}, {0, 1, 0, 1}, {0, 0}}
};

....
// step03
- (void)compileShaders
{
....
    //@add for texture
    _texCoordSlot = glGetAttribLocation(programHandle, "TexCoordIn");
    glEnableVertexAttribArray(_texCoordSlot);
    _textureUniform = glGetUniformLocation(programHandle, "Texture");
}

....
// step04
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
....    
        //@add for texture
        _floorTexture = [self setupTexture:@"tile_floor.png"];
        _fishTexture = [self setupTexture:@"item_powerup_fish.png"];
    }
   
    return self;
}

....
// step05
- (void)render:(CADisplayLink *)displayLink
{
....
    //@add for texture(before glDrawElements)
    glVertexAttribPointer(_texCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) (sizeof(float) * 7)); 
   
    // activate the texture unit we want to load our texture into.
    // default: assuming GL_TEXTURE0 is already the active texture unit
    glActiveTexture(GL_TEXTURE0);
   
    // bind the texture into the current texture unit
    glBindTexture(GL_TEXTURE_2D, _floorTexture);
   
    // set the texture uniform to the index of the texture unit it's in (0).
    // defaults to 0
    glUniform1i(_textureUniform, 0);


    glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]),
                   GL_UNSIGNED_BYTE, 0);
   
    // Present the render/color buffer to the UIView's layer!
    [_context presentRenderbuffer:GL_RENDERBUFFER];
}
....

************************************************************

     6. 編譯並執行
         立方體已貼上紋理了, 
立方體的正面看起來還好, 側面卻不正確的伸展.

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。