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 來產生原始的像素資料, 我們需要一個指向此圖片
的參照. 這個很容易, 我們可以使用 UIImage 的 CGImageRef 屬性.
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. 編譯並執行
立方體已貼上紋理了, 立方體的正面看起來還好, 側面卻被不正確的伸展.
沒有留言:
張貼留言
注意:只有此網誌的成員可以留言。