閱讀文章前需要了解的知識,紋理:https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/
過程簡述:利用 FreeImage 庫加載圖像數據,再創建 OpenGL 紋理,通過 Canvas2D 畫布繪制,最后又 Renderer 渲染器渲染
本來想用 soil 庫加載圖像數據的,雖然方便,但是加載有些格式的圖像文件時會出現一些問題。最后,改用 FreeImage 庫來加載圖像了。
添加 FreeImage 庫到工程
解壓 FreeImage.rar 文件后得到三個文件
將其分別拷貝到 debug文件夾、External 文件夾、Lib 文件夾中,再鏈接上 lib 庫。
繪制圖片
創建一個紋理結構,儲存紋理索引、大小以及紋理坐標
struct DLL_export Texture { Rect size; Vec2 texcoords[4]; GLuint texture; };
創建一個紋理管理器類 TextureManager,用於創建和管理紋理。
利用 FreeImage 庫加載紋理
Texture* TexrureManager::createTexture(const char* filename) { GLuint texture = -1; std::string fullName = PathHelper::fullPath(filename); unsigned char* image_data = nullptr; FIBITMAP* bmp = nullptr; /* 初始化 FreeImage */ FreeImage_Initialise(TRUE); /* 獲取圖像文件類型 */ FREE_IMAGE_FORMAT fif = FIF_UNKNOWN; fif = FreeImage_GetFileType(fullName.c_str()); if ( fif == FIF_UNKNOWN ) { fif = FreeImage_GetFIFFromFilename(fullName.c_str()); } /* 加載所支持圖像類型的圖像 */ if ( (fif != FIF_UNKNOWN) && FreeImage_FIFSupportsReading(fif) ) { bmp = FreeImage_Load(fif, fullName.c_str(), JPEG_DEFAULT); } if ( !bmp ) return nullptr; int w = FreeImage_GetWidth(bmp); int h = FreeImage_GetHeight(bmp); int pixel_count = w * h; int byte_per_pixel = FreeImage_GetLine(bmp) / w; image_data = ( unsigned char* ) malloc(sizeof( unsigned char ) * pixel_count * 4); unsigned char* bits = FreeImage_GetBits(bmp); int current_pixel = 0; if ( byte_per_pixel == 4 ) { for ( int i = 0; i < pixel_count; i++ ) { image_data[i * 4 + 2] = bits[current_pixel++]; image_data[i * 4 + 1] = bits[current_pixel++]; image_data[i * 4 + 0] = bits[current_pixel++]; image_data[i * 4 + 3] = bits[current_pixel++]; } } else { for ( int i = 0; i < pixel_count; i++ ) { image_data[i * 4 + 2] = bits[current_pixel++]; image_data[i * 4 + 1] = bits[current_pixel++]; image_data[i * 4 + 0] = bits[current_pixel++]; image_data[i * 4 + 3] = 255; } } if ( bmp ) FreeImage_Unload(bmp); FreeImage_DeInitialise(); glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); /* 設置紋理選項 */ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data); glBindTexture(GL_TEXTURE_2D, 0); free(image_data); Texture* tex = new Texture(); tex->texture = texture; tex->size.set(0, 0, w, h); tex->texcoords[0].set(0, 0); tex->texcoords[1].set(0, 1); tex->texcoords[2].set(1, 1); tex->texcoords[3].set(1, 0); return tex; }
在函數中,使用 FreeImage 庫加載紋理數據,然后創建 OpenGL 2D紋理,將創建的紋理保存到 Texture 結構中,並設置了紋理坐標。
在 Canvas2D 中繪制紋理
void Canvas2D::drawTexture(int x, int y, Texture* texture, Color& color) { int w = texture->size.w; int h = texture->size.h; this->resizeVector(4, 6); vPositions[0].set(x + 0, y + 0, 0); vPositions[1].set(x + 0, y + h, 0); vPositions[2].set(x + w, y + h, 0); vPositions[3].set(x + w, y + 0, 0); vIndices[0] = 0; vIndices[1] = 2; vIndices[2] = 1; vIndices[3] = 0; vIndices[4] = 3; vIndices[5] = 2; static RenderUnit unit; unit.pPositions = &vPositions[0]; unit.nPositionCount = 4; unit.pTexcoords = texture->texcoords; unit.pIndices = &vIndices[0]; unit.nIndexCount = 6; unit.color = color; unit.texture = texture; unit.renderType = RENDER_TYPE_TEXTURE; pRenderer->pushRenderUnit(unit); }
函數很簡單,設置了頂點數據並填充了 RenderUnit,再傳到 渲染器中渲染。與繪制幾何圖形相比,多了紋理坐標,並把渲染類型設置為 渲染紋理。
渲染器 Renderer 渲染紋理
添加成員
std::map<Texture*, VertexData*> textureDatas;
每張紋理都有其相應的頂點數據,這樣可以把多張相同紋理的頂點數據放到一個緩沖區中渲染,保證了渲染多張相同紋理時只使用使用一個 DrawCall(調用 函數 glDrawElements 進行繪制的次數),提高渲染效率。
在 pushRenderUnit 函數中
else if ( unit.renderType == RENDER_TYPE_TEXTURE ) { auto it = textureDatas.find(unit.texture); if ( it == textureDatas.end() ) { vertexData = new VertexData(); vertexData->bHasTexcoord = true; vertexData->renderType = RENDER_TYPE_TEXTURE; textureDatas.insert(std::make_pair(unit.texture, vertexData)); } else { vertexData = it->second; } }
索引出紋理對應的 VertexData,然后填充數據。最后的渲染函數中添加填充紋理坐標代碼
/* 設置紋理 */ if ( vertexData->bHasTexcoord ) { glBindBuffer(GL_ARRAY_BUFFER, texcoordBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof( Vec2 ) * vertexData->nPositionCount, &vertexData->texcoords[0], GL_DYNAMIC_DRAW); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texrure); glUniform1i(glGetUniformLocation(shaderProgram, "defaulteTexture"), 0); }
為了能夠渲染紋理需要更改着色程序
頂點着色器
#version 330 core layout(location = 0) in vec3 Position; layout(location = 1) in vec2 Texcoord; layout(location = 2) in vec4 Color; out vec2 texcoord; out vec4 color; uniform int bRenderTexture; void main() { gl_Position = vec4(Position, 1.0f); color = Color; if( bRenderTexture != 0 ){ texcoord = Texcoord; } }
片段着色器
#version 330 core out vec4 Color; in vec2 texcoord; in vec4 color; uniform sampler2D defaultTexture; uniform int bRenderTexture; void main() { if( bRenderTexture != 0 ){ Color = texture(defaultTexture, texcoord) * color * color.w; } else{ Color = color; } }
為了開啟 Alpha 效果,設置OpenGL 的混合狀態
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
在主函數中添加繪制圖像的代碼
Texture* texture = TexrureManager::instance()->getTexture("image.png"); Texture* texture1 = TexrureManager::instance()->getTexture("image.jpg");
canvas.drawTexture(180, 0, texture1, Color(1, 1, 1, 1)); canvas.drawTexture(120, 100, texture, Color(1, 1, 1, 0.8));
程序的運行結果
這里繪制了 png 和 jpg 格式的圖像