【C++ OpenGL ES 2.0程式設計筆記】5: mipmap
作者是現在對相關知識理解還不是很深入,後續會不斷完善。因此文中內容僅供參考,具體的知識點請以OpenGL的官方文件為準
前言
本文介紹了OpenGL ES 2.0 中常用的多級紋理貼圖技術,mipmap, 給出了一個使用mipmap的3D場景示例。
在三維計算機圖形的貼圖渲染中有一個常用的技術被稱為Mipmapping。為了加快渲染速度和減少影象鋸齒,貼圖被處理成由一系列被預先計算和優化過的圖片組成的檔案,這樣的貼圖被稱為 MIP map 或者 mipmap。這個技術在三維遊戲中被非常廣泛的使用。“MIP”來自於拉丁語 multum in parvo 的首字母,意思是“放置很多東西的小空間”。Mipmap 需要佔用一定的記憶體空間,同時也遵循小波壓縮規則 (wavelet compression)
mipmap的常見使用場景是,在一個採用透視投影的三維場景中,我們看到的東西是近大遠小的,對於同一種東西,比如地板,近處使用畫素尺寸較大的紋理,遠處的使用畫素較小的紋理,這樣就節省了渲染的工作量。通過使用OpenGL的glTexImage2D函式可以實現多級紋理的載入,它的第二個引數就是紋理的級別。
效果圖
從近到遠,分別是不同級別圖片繪製的結果,越遠的位置圖片畫素越少。
實現
製作六張圖片,大小分別為32x32, 16x16, 8x8, 4x4, 2x2, 1x1,如圖,使用“繪圖”工具,依次建立6個圖片,畫一個矩形,中間用純色填充:
1x1畫素的圖:
32x32畫素的圖
製作好的6個級別的圖片依次如下所示,其中1x1的太小了,與16x16的都為紅色:
把圖片儲存為p1x1.bmp ~ p32x32.bmp。接下來要在OpenGL程式中把它們組合成一個多級紋理,我該怎麼做呢?
下面是程式碼示例,先來怎麼定義一個函式把多張圖片打包成一個mipmap.
// 組成多級紋理每一級的圖片名字, 這個例子一共分為6級,畫素尺寸從2的五次方到2的零次方。
std::vector<std::string> fileNames =
{
"images/p32x32.bmp",
"images/p16x16.bmp",
"images/p8x8.bmp" ,
"images/p4x4.bmp",
"images/p2x2.bmp",
"images/p1x1.bmp"
};
mipmapTextureId = loadMipMap(fileNames);
unsigned int MipMapTexture::loadMipMap(const std::vector<std::string> &fileNames)
{
unsigned int textureId = 0;
// 生成一個紋理
glGenTextures(1, &textureId);
// 繫結為2D紋理
glBindTexture(GL_TEXTURE_2D, textureId);
// 指定遠端過濾方式, 僅兩種模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// 指定近端過濾方式, 包含6種模式,GL_LINEAR_MIPMAP_LINEAR 在相鄰的紋理級別之間做線性插值計算.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
// 載入每個級別的紋理
size_t nums = fileNames.size();
for (size_t i=0; i<nums; ++i)
{
// 得到圖片的紋理資料, 格式為RGB 各一個位元組,共24位
FREE_IMAGE_FORMAT format = FreeImage_GetFileType(fileNames[i].c_str(), 0);
FIBITMAP *bitmap = FreeImage_Load(format, fileNames[i].c_str(), 0);
bitmap = FreeImage_ConvertTo24Bits(bitmap);
BYTE *pixels = FreeImage_GetBits(bitmap);
int width = FreeImage_GetWidth(bitmap);
int height = FreeImage_GetHeight(bitmap);
// bgr to rgb. (windows)
for (size_t j = 0; j < width*height * 3; j += 3)
{
BYTE temp = pixels[j];
pixels[j] = pixels[j + 2];
pixels[j + 2] = temp;
}
// 繫結第i級紋理.
glTexImage2D(GL_TEXTURE_2D, i, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
FreeImage_Unload(bitmap);
}
return textureId;
}
通過呼叫這個loadMipMap()
函式,我就可以創建出mipmap形式的多級紋理了,下面我把創建出的紋理貼在一個透視投影的三維場景裡,我先把render的程式碼貼在這吧:
// 建立mipmap多級紋理
bool MipMapTexture::init()
{
_valid = ShaderProgram::initWithFile(_vsFileName, _fsFileName);
if ( _valid )
{
_position = glGetAttribLocation(_programId, "_position");
_uv = glGetAttribLocation(_programId, "_uv");
_mipmapTexture = glGetUniformLocation(_programId, "_textureBg");
_mvp = glGetUniformLocation(_programId, "_mvp");
}
std::vector<std::string> fileNames =
{
"images/p32x32.bmp",
"images/p16x16.bmp",
"images/p8x8.bmp",
"images/p4x4.bmp",
"images/p2x2.bmp",
"images/p1x1.bmp"
};
_mipmapTextureId = loadMipMap(fileNames);
using CELL::float3;
_camera._eye = float3(1, 1, 1);
_camera._look = float3(0.5f, -0.4f, -5.5f);
_camera._up = float3(0.0f, 1.0f, 0.0f);
_camera._right = float3(1.0f, 0.0f, 0.0f);
return _valid;
}
// 繪製mipmap
void MipMapTexture::render()
{
using namespace CELL;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
auto director = Director::getInstance();
Size s = director->getFrameSize();
float width = s._width;
float height = s._height;
glViewport(0, 0, width, height);
_camera.updateCamera(0.016);
float groundSize = 100;
float groundPosition = -5;
float repeat = 100;
Vertex ground[] =
{
{ -groundSize, groundPosition, -groundSize, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f },
{ groundSize, groundPosition, -groundSize, repeat, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f },
{ groundSize, groundPosition, groundSize, repeat, repeat, 1.0f, 1.0f, 1.0f, 1.0f },
{ -groundSize, groundPosition, -groundSize, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f },
{ groundSize, groundPosition, groundSize, repeat, repeat, 1.0f, 1.0f, 1.0f, 1.0f },
{ -groundSize, groundPosition, groundSize, 0.0f, repeat, 1.0f, 1.0f, 1.0f, 1.0f },
};
begin();
matrix4 matWorld(1);
matrix4 matView = lookAt(_camera._eye, _camera._look, _camera._up);
matrix4 matProj = perspective(45.0f, width / height, 0.1f, 100.f);
matrix4 mvp = matProj * matView * matWorld;
// 給片段shader中的紋理資料傳mipmap資料
glUniform1i(_mipmapTexture, 0);
glBindTexture(GL_TEXTURE_2D, _mipmapTextureId);
glUniformMatrix4fv(_mvp, 1, false, mvp.data());
// 頂點位置
glVertexAttribPointer(_position, 3, GL_FLOAT, false, sizeof(Vertex), &ground[0].x);
// 紋理座標uv
glVertexAttribPointer(_uv, 2, GL_FLOAT, false, sizeof(Vertex), &ground[0].u);
// 繪製兩個三角形 = 一個矩形 地板場景
glDrawArrays(GL_TRIANGLES, 0, sizeof(ground) / sizeof (ground[0]));
end();
}
頂點shader
precision lowp float;
attribute vec3 _position;
uniform mat4 _mvp;
attribute vec2 _uv;
varying vec2 _outUv;
void main() {
vec4 pos = vec4(_position.x, _position.y, _position.z, 1);
gl_Position = _mvp * pos;
_outUv = _uv;
}
片段shader
precision lowp float;
varying vec2 _outUv;
uniform sampler2D _textureBg;
void main()
{
vec4 bgColor = texture2D(_textureBg, _outUv);
gl_FragColor = bgColor;
}
完整的原始碼
MipMapTexture.h
#pragma once
#include "gl_include.h"
#include "ELShaderProgram.h"
#include <string>
#include <vector>
NS_BEGIN(elloop);
NS_BEGIN(mip_map);
class ACamera
{
public:
ACamera()
: _moveSpeed(5)
, _eye(0, 10, 0)
, _look(0.5, -0.4, -0.5)
, _up(0, 1, 0)
, _right(1, 0, 0)
{
}
CELL::float3 _eye;
CELL::float3 _look;
CELL::float3 _up;
CELL::float3 _right;
float _moveSpeed;
void updateCamera(float dt)
{
using namespace CELL;
float3 tempLook = _look;
float3 direction = _look - _eye;
direction = normalize(direction);
unsigned char keys[300];
GetKeyboardState(keys);
if (keys[VK_UP] & 0x80)
{
_eye -= direction * (-_moveSpeed) * dt;
_look -= direction * (-_moveSpeed) * dt;
}
if (keys[VK_DOWN] & 0x80)
{
_eye += direction * (-_moveSpeed) * dt;
_look += direction * (-_moveSpeed) * dt;
}
if (keys[VK_LEFT] & 0x80)
{
_eye -= _right * _moveSpeed * dt;
_look -= _right * _moveSpeed * dt;
}
if (keys[VK_RIGHT] & 0x80)
{
_eye += _right * _moveSpeed * dt;
_look += _right * _moveSpeed * dt;
}
}
};
class MipMapTexture : public ShaderProgram
{
public:
static MipMapTexture* create();
void begin() override;
void end() override;
void render() override;
uniform _mvp;
uniform _mipmapTexture;
attribute _position;
attribute _uv;
unsigned int _mipmapTextureId;
ACamera _camera;
protected:
struct Vertex
{
float x, y, z;
float u, v;
float r, g, b, a;
};
bool init();
MipMapTexture()
: _mvp(-1)
, _mipmapTexture(-1)
, _mipmapTextureId(-1)
, _position(-1)
, _uv(-1)
{
_vsFileName = "shaders/3D_projection_vs.glsl";
_fsFileName = "shaders/3D_projection_fs.glsl";
}
~MipMapTexture()
{
glDeleteTextures(1, &_mipmapTextureId);
}
unsigned int loadTexture(const std::string &fileName);
unsigned int loadMipMap(const std::vector<std::string> &fileNames);
};
NS_END(mip_map);
NS_END(elloop);
MipMapTexture.cpp
#include "scenes/MipMapTexture.h"
#include "app_control/ELDirector.h"
#include "math/ELGeometry.h"
#include "include/freeImage/FreeImage.h"
NS_BEGIN(elloop);
NS_BEGIN(mip_map);
void MipMapTexture::begin()
{
glUseProgram(_programId);
glEnableVertexAttribArray(_position);
glEnableVertexAttribArray(_uv);
}
void MipMapTexture::end()
{
glDisableVertexAttribArray(_uv);
glDisableVertexAttribArray(_position);
glUseProgram(0);
}
bool MipMapTexture::init()
{
_valid = ShaderProgram::initWithFile(_vsFileName, _fsFileName);
if ( _valid )
{
_position = glGetAttribLocation(_programId, "_position");
_uv = glGetAttribLocation(_programId, "_uv");
_mipmapTexture = glGetUniformLocation(_programId, "_textureBg");
_mvp = glGetUniformLocation(_programId, "_mvp");
}
std::vector<std::string> fileNames =
{
"images/p32x32.bmp",
"images/p16x16.bmp",
"images/p8x8.bmp",
"images/p4x4.bmp",
"images/p2x2.bmp",
"images/p1x1.bmp"
};
_mipmapTextureId = loadMipMap(fileNames);
using CELL::float3;
_camera._eye = float3(1, 1, 1);
_camera._look = float3(0.5f, -0.4f, -5.5f);
_camera._up = float3(0.0f, 1.0f, 0.0f);
_camera._right = float3(1.0f, 0.0f, 0.0f);
return _valid;
}
MipMapTexture* MipMapTexture::create()
{
auto * self = new MipMapTexture();
if ( self && self->init() )
{
self->autorelease();
return self;
}
return nullptr;
}
unsigned int MipMapTexture::loadMipMap(const std::vector<std::string> &fileNames)
{
unsigned int textureId = 0;
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
size_t nums = fileNames.size();
for (size_t i=0; i<nums; ++i)
{
FREE_IMAGE_FORMAT format = FreeImage_GetFileType(fileNames[i].c_str(), 0);
FIBITMAP *bitmap = FreeImage_Load(format, fileNames[i].c_str(), 0);
bitmap = FreeImage_ConvertTo24Bits(bitmap);
BYTE *pixels = FreeImage_GetBits(bitmap);
int width = FreeImage_GetWidth(bitmap);
int height = FreeImage_GetHeight(bitmap);
for (size_t j = 0; j < width*height * 3; j += 3)
{
BYTE temp = pixels[j];
pixels[j] = pixels[j + 2];
pixels[j + 2] = temp;
}
glTexImage2D(GL_TEXTURE_2D, i, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
FreeImage_Unload(bitmap);
}
return textureId;
}
void MipMapTexture::render()
{
using namespace CELL;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
auto director = Director::getInstance();
Size s = director->getFrameSize();
float width = s._width;
float height = s._height;
glViewport(0, 0, width, height);
_camera.updateCamera(0.016);
float groundSize = 100;
float groundPosition = -5;
float repeat = 100;
Vertex ground[] =
{
{ -groundSize, groundPosition, -groundSize, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f },
{ groundSize, groundPosition, -groundSize, repeat, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f },
{ groundSize, groundPosition, groundSize, repeat, repeat, 1.0f, 1.0f, 1.0f, 1.0f },
{ -groundSize, groundPosition, -groundSize, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f },
{ groundSize, groundPosition, groundSize, repeat, repeat, 1.0f, 1.0f, 1.0f, 1.0f },
{ -groundSize, groundPosition, groundSize, 0.0f, repeat, 1.0f, 1.0f, 1.0f, 1.0f },
};
begin();
matrix4 matWorld(1);
matrix4 matView = lookAt(_camera._eye, _camera._look, _camera._up);
matrix4 matProj = perspective(45.0f, width / height, 0.1f, 100.f);
matrix4 mvp = matProj * matView * matWorld;
glUniform1i(_mipmapTexture, 0);
glBindTexture(GL_TEXTURE_2D, _mipmapTextureId);
glUniformMatrix4fv(_mvp, 1, false, mvp.data());
glVertexAttribPointer(_position, 3, GL_FLOAT, false, sizeof(Vertex), &ground[0].x);
glVertexAttribPointer(_uv, 2, GL_FLOAT, false, sizeof(Vertex), &ground[0].u);
glDrawArrays(GL_TRIANGLES, 0, sizeof(ground) / sizeof (ground[0]));
end();
}
NS_END(mip_map);
NS_END(elloop);
完整專案原始碼
如果原始碼對您有幫助,請幫忙在github上給我點個Star, 感謝 :)
作者水平有限,對相關知識的理解和總結難免有錯誤,還望給予指正,非常感謝!