1. 程式人生 > >【C++ OpenGL ES 2.0程式設計筆記】5: mipmap

【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函式可以實現多級紋理的載入,它的第二個引數就是紋理的級別。

效果圖

mipmap_effect.gif

從近到遠,分別是不同級別圖片繪製的結果,越遠的位置圖片畫素越少。

實現

製作六張圖片,大小分別為32x32, 16x16, 8x8, 4x4, 2x2, 1x1,如圖,使用“繪圖”工具,依次建立6個圖片,畫一個矩形,中間用純色填充:

1x1畫素的圖

create_mipmap.jpg

32x32畫素的圖

create_mipmap32.jpg

製作好的6個級別的圖片依次如下所示,其中1x1的太小了,與16x16的都為紅色:

mipmap6.jpg

把圖片儲存為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, 感謝 :)

作者水平有限,對相關知識的理解和總結難免有錯誤,還望給予指正,非常感謝!