Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the antispam-bee domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /home/ruwritingagame/public_html/wp-includes/functions.php on line 6121
Глава 26. Рендер в и чтение из GL текстуры – Игра в Написание Игры

Глава 26. Рендер в и чтение из GL текстуры

Здесь я хочу сгенерировать белый шум посложнее: с более крупными точками и с размывкой между ними. Задача разбивается на следующие шаги:

  • Создать простое черно-белое изображение (как в предыдущей главе)
  • Сгенерировать из него GL текстуру
  • Сделать ее renderable (связать ее с render buffer-ом)
  • Направить GL рендер в новый render buffer
  • Нарендрить туда случайных черно-белых больших точек
  • Прочитать текстуру обратно из GPU
  • Размыть полученное изображение
  • И сохранить в файл

Это потребует дополнительных функций и переменных в классеTexture.

1. Запускаем VS, открываем C:\CPP\a997modeler\p_windows\p_windows.sln.


2. Заменим Texture.h код на:

 #pragma once
#include <string>
#include <vector>
#include "platform.h"

class Texture
{
public:
    //texture's individual descriptor:
    unsigned int GLid = -1; // GL texture id
    int size[2] = { 0,0 };  // image size
    std::string source; //file name
    //if renderable ?
    unsigned int frameBufferId = 0;
    unsigned int depthBufferId = 0;
    //end of descriptor

    //static array (vector) of all loaded textures
    static std::vector<Texture*> textures;

public:
    static int loadTexture(std::string filePath, int glRepeatH = GL_MIRRORED_REPEAT, int glRepeatV = GL_MIRRORED_REPEAT);
    static int findTexture(std::string filePath);
    static int cleanUp();
    static unsigned int getGLid(int texN) { return textures.at(texN)->GLid; };

    static int saveBMP(std::string filePath, unsigned char* buff, int w, int h, int bytesPerPixel=4);
    static int saveTGA(std::string filePath, unsigned char* buff, int w, int h, int bytesPerPixel=4);

    static int generateTexture(std::string imgID, int w, int h, unsigned char* imgData, int glRepeatH = GL_MIRRORED_REPEAT, int glRepeatV = GL_MIRRORED_REPEAT);
    static int detachRenderBuffer(Texture* pTex);
    static int attachRenderBuffer(int texN, bool zBuffer = false) { return attachRenderBuffer(textures.at(texN), zBuffer); };
    static int attachRenderBuffer(Texture* pTex, bool zBuffer = false);
    static int setRenderToTexture(int texN) { return setRenderToTexture(textures.at(texN)); };
    static int setRenderToTexture(Texture* pTex);
    static int getImageFromTexture(int texN, unsigned char* imgData);
    static int blurRGBA(unsigned char* imgData, int w, int h, int blurLevel);
};


3. Заменим Texture.cpp код на:

#include "Texture.h"
#define STB_IMAGE_IMPLEMENTATION  //required by stb_image.h
#include "stb_image.h"
#include "platform.h"
#include "utils.h"

//static array (vector) of all loaded textures
std::vector<Texture*> Texture::textures;

int Texture::loadTexture(std::string filePath, int glRepeatH, int glRepeatV) {
    int texN = findTexture(filePath);
    if (texN >= 0)
        return texN;
    //if here - texture wasn't loaded
    // load an image
    int nrChannels, w, h;
    unsigned char* imgData = stbi_load(filePath.c_str(),
        &w, &h, &nrChannels, 4); //"4"-convert to 4 channels -RGBA
    if (imgData == NULL) {
        mylog("ERROR in Texture::loadTexture loading image %s\n", filePath.c_str());
    }
    // generate texture
    generateTexture(filePath, w, h, imgData, glRepeatH, glRepeatV);
    // release image data
    stbi_image_free(imgData);

    return (textures.size() - 1);
}
int Texture::findTexture(std::string filePath) {
    int texturesN = textures.size();
    if (texturesN < 1)
        return -1;
    for (int i = 0; i < texturesN; i++) {
        Texture* pTex = textures.at(i);
        if (pTex->source.compare(filePath) == 0)
            return i;
    }
    return -1;
}
int Texture::cleanUp() {
    int texturesN = textures.size();
    if (texturesN < 1)
        return -1;
    //detach all textures
    glActiveTexture(GL_TEXTURE0); // activate the texture unit first before binding texture
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE3);
    glBindTexture(GL_TEXTURE_2D, 0);
    //release all textures
    for (int i = 0; i < texturesN; i++) {
        Texture* pTex = textures.at(i);
        detachRenderBuffer(pTex);
        glDeleteTextures(1, (GLuint*)&pTex->GLid);
        pTex->GLid = 0;
        delete pTex;
    }
    textures.clear();
    return 1;
}
int Texture::saveBMP(std::string filePath, unsigned char* buff, int w, int h, int bytesPerPixel) {
    std::string fullPath = getFullPath(filePath);
    std::string inAppPath = getInAppPath(fullPath);
    makeDirs(inAppPath);
    FILE* outFile;
    myFopen_s(&outFile, fullPath.c_str(), "wb");
    if (outFile == NULL) {
        mylog("ERROR in Texture::saveBMP: Can't create file %s\n", filePath.c_str());
        return -1;
    }
    struct {
        char chars2skip[2]; //
            //BMP Header
        char bm[2] = { 0x42, 0x4D }; //	"BM"
        myUint32 fileSize = 0; // Size of the BMP file, little-endian
        myUint32 unused = 0;
        myUint32 dataOffset = 0; // Offset where the pixel array (bitmap data) can be found, little-endian
        //DIB Header
        myUint32 dibHeaderSize = 0; // Number of bytes in the DIB header, little-endian
        myUint32 imgW = 0; // Width of the bitmap in pixels, little-endian
        myUint32 imgH = 0; // Height of the bitmap in pixels, little-endian
        char colorPlainsN[2] = { 1,0 };
        char bitsPerPixel[2] = { 32,0 };
        myUint32 compression = 0; //0-BI_RGB
        myUint32 dataSize = 0; // Size of the raw bitmap data (including padding), little-endian
        myUint32 printResution[2] = { 2835 ,2835 }; // Print resolution of the image,
                //72 DPI × 39.3701 inches per metre yields 2834.6472, little-endian
        myUint32 paletteColors = 0; // Number of colors in the palette
        myUint32 importantColors = 0; //0 means all colors are important
    } bmpHeader;
    int rowSize = w * bytesPerPixel;
    int rowPadding = (4 - rowSize % 4) % 4;
    int rowSizeWithPadding = rowSize + rowPadding;
    int dataSize = rowSizeWithPadding * h;
    int headerSize = sizeof(bmpHeader) - 2; //-chars2skip
    bmpHeader.fileSize = dataSize + headerSize;
    bmpHeader.dataOffset = headerSize;
    bmpHeader.dibHeaderSize = headerSize - 14; //-BMP Header size
    bmpHeader.imgW = w;
    bmpHeader.imgH = h;
    if (bytesPerPixel != 4)
        bmpHeader.bitsPerPixel[0] = bytesPerPixel * 8;
    bmpHeader.dataSize = dataSize;
    fwrite(&bmpHeader.bm, 1, headerSize, outFile);
    //data, from bottom to top
    unsigned char zero[4] = { 0,0,0,0 };
    unsigned char bgra[4];
    for (int y = h - 1; y >= 0; y--) {
        for (int x = 0; x < w; x++) {
            int pixelOffset = y * rowSize + x * 4;
            bgra[0] = buff[pixelOffset + 2];
            bgra[1] = buff[pixelOffset + 1];
            bgra[2] = buff[pixelOffset + 0];
            bgra[3] = buff[pixelOffset + 3];
            fwrite(bgra, 1, bytesPerPixel, outFile);
        }
        if (rowPadding != 0)
            fwrite(zero, 1, rowPadding, outFile);
    }
    fflush(outFile);
    fclose(outFile);

    return 1;
}

int Texture::saveTGA(std::string filePath, unsigned char* buff, int w, int h, int bytesPerPixel) {
    std::string fullPath = getFullPath(filePath);
    std::string inAppPath = getInAppPath(fullPath);
    makeDirs(inAppPath);
    FILE* outFile;
    myFopen_s(&outFile, fullPath.c_str(), "wb");
    if (outFile == NULL) {
        mylog("ERROR in Texture::saveBMP: Can't create file %s\n", filePath.c_str());
        return -1;
    }
    unsigned char tgaHeader[18] = { 0,0,2,0,0,0,0,0,0,0,0,0, (unsigned char)(w % 256), (unsigned char)(w / 256),
        (unsigned char)(h % 256), (unsigned char)(h / 256), (unsigned char)(bytesPerPixel * 8), 0x20 };
    fwrite(tgaHeader, 1, 18, outFile);
    //data
    unsigned char bgra[4];
    for (int i = 0; i < w * h; i++) {
        int pixelOffset = i * 4;
        bgra[0] = buff[pixelOffset + 2];
        bgra[1] = buff[pixelOffset + 1];
        bgra[2] = buff[pixelOffset + 0];
        bgra[3] = buff[pixelOffset + 3];
        fwrite(bgra, 1, bytesPerPixel, outFile);
    }
    fflush(outFile);
    fclose(outFile);

    return 1;
}
int Texture::generateTexture(std::string imgID, int w, int h, unsigned char* imgData, int glRepeatH, int glRepeatV) {
    //glRepeat options: GL_REPEAT, GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT
    if (!imgID.empty()) {
        int texN = findTexture(imgID);
        if (texN >= 0)
            return texN;
    }
    //if here - texture wasn't generated
    //create Texture object
    Texture* pTex = new Texture();
    textures.push_back(pTex);
    pTex->size[0] = w;
    pTex->size[1] = h;
    pTex->source.assign(imgID);
    // generate texture
    glGenTextures(1, (GLuint*)&pTex->GLid);
    glBindTexture(GL_TEXTURE_2D, pTex->GLid);
    // set the texture wrapping/filtering options (on the currently bound texture object)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glRepeatH);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glRepeatV);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);// GL_LINEAR);  //
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // attach image data (if provided)
    if (imgData != NULL) {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pTex->size[0], pTex->size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, imgData);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    return (textures.size() - 1);
}
int Texture::detachRenderBuffer(Texture* pTex) {
    if (pTex->frameBufferId == 0)
        return 0;
    if (pTex->depthBufferId > 0) {
        glDeleteRenderbuffers(1, (GLuint*)&pTex->depthBufferId);
        pTex->depthBufferId = 0;
    }
    glDeleteFramebuffers(1, (GLuint*)&pTex->frameBufferId);
    pTex->frameBufferId = 0;
    return 1;
}

int Texture::attachRenderBuffer(Texture* pTex, bool zBuffer) {
    if (pTex->frameBufferId > 0)
        return 0; //attached already
    //generate frame buffer
    glGenFramebuffers(1, (GLuint*)&pTex->frameBufferId);
    if (zBuffer) {
        //generate depth buffer
        glGenRenderbuffers(1, (GLuint*)&pTex->depthBufferId);
        // create render buffer and bind 16-bit depth buffer
        glBindRenderbuffer(GL_RENDERBUFFER, pTex->depthBufferId);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, pTex->size[0], pTex->size[1]);
        glBindRenderbuffer(GL_RENDERBUFFER, 0); //release
    }
    return 1;
}
int Texture::setRenderToTexture(Texture* pTex) {
    if (pTex->frameBufferId == 0) {
        mylog("ERROR in Texture::setRenderToTexture: %s not renderable", pTex->source.c_str());
        return -1;
    }
    // Bind the framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, pTex->frameBufferId);
    // specify texture as color attachment
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pTex->GLid, 0);
    // attach render buffer as depth buffer
    if (pTex->depthBufferId > 0) {
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, pTex->depthBufferId);
        glClear(GL_DEPTH_BUFFER_BIT);
    }
    // check status
    int status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE) {
        std::string str;
        if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
            str.assign("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
        else if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
            str.assign("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
        else if (status == GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)
            str.assign("GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE");
        else if (status == GL_FRAMEBUFFER_UNSUPPORTED)
            str.assign("GL_FRAMEBUFFER_UNSUPPORTED");
        else
            str.assign("hz");
        mylog("Modeler.setRenderToTextureBind to texture %s failed: %s\n", pTex->source.c_str(), str.c_str());
        return -1;
    }
    glViewport(0, 0, pTex->size[0], pTex->size[1]);
    return 1;
}
int Texture::getImageFromTexture(int texN, unsigned char* imgData) {
    Texture* pTex = textures.at(texN);
    glBindTexture(GL_TEXTURE_2D, pTex->GLid);
    glBindFramebuffer(GL_FRAMEBUFFER, pTex->frameBufferId);

    glReadPixels(0, 0, pTex->size[0], pTex->size[1], GL_RGBA, GL_UNSIGNED_BYTE, imgData);
    return 1;
}
int Texture::blurRGBA(unsigned char* imgData, int w0, int h0, int blurLevel) {
    unsigned char* imgTemp = new unsigned char[w0 * h0 * 4];
    int w00 = blurLevel * 2 + 1;
    for (int y0 = 0; y0 < h0; y0++) {
        int y1 = y0 - blurLevel;
        int h1 = w00;
        if (y1 < 0) {
            int d = -y1;
            y1 += d;
            h1 -= d;
        }
        else if (y1 > h0 - w00) {
            int d = y1 - (h0 - w00);
            h1 -= d;
        }
        for (int x0 = 0; x0 < w0; x0++) {
            int x1 = x0 - blurLevel;
            int w1 = w00;
            if (x1 < 0) {
                int d = -x1;
                x1 += d;
                w1 -= d;
            }
            else if (x1 > w0 - w00) {
                int d = x1 - (w0 - w00);
                w1 -= d;
            }
            int sum[4] = { 0,0,0,0 };
            for (int y = y1; y < y1 + h1; y++) {
                for (int x = x1; x < x1 + w1; x++) {
                    int idx = (y * w0 + x) * 4;
                    for (int ch = 0; ch < 4; ch++)
                        sum[ch] += imgData[idx + ch];
                }
            }
            int n = w1 * h1;
            int idx = (y0 * w0 + x0) * 4;
            for (int ch = 0; ch < 4; ch++)
                imgTemp[idx + ch] = (unsigned char)(sum[ch] / n);
        }
    }
    memcpy(imgData, imgTemp, w0 * h0 * 4);
    delete[] imgTemp;
    return 1;
}

Надеюсь, код достаточно читаемый.


Следующий код рендрит 3 изображения с разной степенью размывки:

int TheGame::run() {
    /*
    getReady();
    while (!bExitGame) {
        drawFrame();
    }
    cleanUp();
    */
    Shader::loadShaders();

    int wh[2] = { 64,64 };
    int bytesPerPixel = 4;
    unsigned char* imgData = new unsigned char[wh[1] * wh[0] * 4];
    for (int y = 0; y < wh[1]; y++)
        for (int x = 0; x < wh[0]; x++) {
            int idx = (y * wh[1] + x) * bytesPerPixel;
            for (int i = 0; i < 4; i++)
                imgData[idx + i] = getRandom(0, 1) * 255;
        }
    //background generated, generate texture:
    int texN = Texture::generateTexture("", wh[0], wh[1], imgData);
    Texture::attachRenderBuffer(texN);
    Texture::setRenderToTexture(texN);

    mat4x4 mProjection, mMVP;
    mat4x4_ortho(mProjection, -wh[0]  / 2, wh[0] / 2, -wh[1] / 2, wh[1] / 2, 1.f, -1.f);
    unsigned char RGBA[4];

    //create a simple 1x1 ucolor square
    GameSubj* pSquare = new GameSubj();
    gameSubjs.push_back(pSquare);
    ModelBuilder* pMB = new ModelBuilder();
    pMB->useSubjN(gameSubjs.size() - 1);
    //define VirtualShape
    VirtualShape vs;
    vs.setShapeType("box");
    v3set(vs.whl, 1, 1, 0);
    Material mt;
    //define material - flat red
    mt.shaderN = Shader::spN_flat_ucolor;
    mt.primitiveType = GL_TRIANGLES;
    mt.uColor.setRGBA(255, 0, 0, 255); //red
    pMB->useMaterial(&mt);
    pMB->buildBoxFace(pMB, "front", &vs);
    pMB->buildDrawJobs(gameSubjs);
    delete pMB;
    //copy mt to pAltMaterial
    pSquare->pAltMaterial = new Material(mt);
    //--end of pSquare
    DrawJob* pDJ = DrawJob::drawJobs.back();

    //-------------blurLevel 3
    int blurLevel = 3;
    std::string fileName = "wn64_blur3";
    int dotSize = blurLevel * 2;
    v3set(pSquare->scale, dotSize, dotSize, 1);

    for (int i = 0; i < 500; i++) {
        pSquare->ownCoords.pos[0] = getRandom(-wh[0] / 2, wh[0] / 2);
        pSquare->ownCoords.pos[1] = getRandom(-wh[1] / 2, wh[1] / 2);
        pSquare->ownCoords.setDegrees(0,0,getRandom(0,90));
        for (int ch = 0; ch < 4; ch++)
            RGBA[ch] = (unsigned char)(getRandom(0, 1) * 255);
        pSquare->pAltMaterial->uColor.setRGBA(RGBA);

        //prepare subject for rendering
        pSquare->buildModelMatrix(pSquare);
        //build MVP matrix for given subject
        mat4x4_mul(mMVP, mProjection, pSquare->ownModelMatrix);
        //render subject
        pDJ->execute((float*)mMVP, NULL, NULL, NULL, pSquare->pAltMaterial);
    }
    Texture::getImageFromTexture(texN, imgData);
    Texture::blurRGBA(imgData, wh[0], wh[1], blurLevel);

    Texture::saveBMP("/dt/out/" + fileName + ".bmp", imgData, wh[0], wh[1]);
    //-------------blurLevel 2
    blurLevel = 2;
    fileName = "wn64_blur2";
    dotSize = blurLevel * 2;
    v3set(pSquare->scale, dotSize, dotSize, 1);

    for (int i = 0; i < 500; i++) {
        pSquare->ownCoords.pos[0] = getRandom(-wh[0] / 2, wh[0] / 2);
        pSquare->ownCoords.pos[1] = getRandom(-wh[1] / 2, wh[1] / 2);
        pSquare->ownCoords.setDegrees(0, 0, getRandom(0, 90));
        for (int ch = 0; ch < 4; ch++)
            RGBA[ch] = (unsigned char)(getRandom(0, 1) * 255);
        pSquare->pAltMaterial->uColor.setRGBA(RGBA);

        //prepare subject for rendering
        pSquare->buildModelMatrix(pSquare);
        //build MVP matrix for given subject
        mat4x4_mul(mMVP, mProjection, pSquare->ownModelMatrix);
        //render subject
        pDJ->execute((float*)mMVP, NULL, NULL, NULL, pSquare->pAltMaterial);
    }
    Texture::getImageFromTexture(texN, imgData);
    Texture::blurRGBA(imgData, wh[0], wh[1], blurLevel);

    Texture::saveBMP("/dt/out/" + fileName + ".bmp", imgData, wh[0], wh[1]);
    //-------------blurLevel 1
    blurLevel = 1;
    fileName = "wn64_blur1";
    dotSize = blurLevel * 2;
    v3set(pSquare->scale, dotSize, dotSize, 1);

    for (int i = 0; i < 500; i++) {
        pSquare->ownCoords.pos[0] = getRandom(-wh[0] / 2, wh[0] / 2);
        pSquare->ownCoords.pos[1] = getRandom(-wh[1] / 2, wh[1] / 2);
        pSquare->ownCoords.setDegrees(0, 0, getRandom(0, 90));
        for (int ch = 0; ch < 4; ch++)
            RGBA[ch] = (unsigned char)(getRandom(0, 1) * 255);
        pSquare->pAltMaterial->uColor.setRGBA(RGBA);

        //prepare subject for rendering
        pSquare->buildModelMatrix(pSquare);
        //build MVP matrix for given subject
        mat4x4_mul(mMVP, mProjection, pSquare->ownModelMatrix);
        //render subject
        pDJ->execute((float*)mMVP, NULL, NULL, NULL, pSquare->pAltMaterial);
    }
    Texture::getImageFromTexture(texN, imgData);
    Texture::blurRGBA(imgData, wh[0], wh[1], blurLevel);

    Texture::saveBMP("/dt/out/" + fileName + ".bmp", imgData, wh[0], wh[1]);

    delete[] imgData;
    mylog("Ready\n");

    return 1;
}

4. Откроем TheGame.cpp и заменим функцию TheGame::run() вышеприведенной.

Подробности:

a) Строка 13: создаем 64×64 RGBA buffer.

b) Строки 14-19: заполняем буфер простым черно-белым фоном (как в прошлой главе). Каждый из 4-х каналов будет выглядеть как

Совмещенное изображение:

c) Строки 21-23: генерируем текстуру, render buffer и направляем rendering в созданный render buffer/texture.

d) Строки 29-49: создаем flat 1×1 квадрат, который и будем рендрить в созданную текстуру.

e) Строка 55: Для первого изображения размер квадратика будет 6×6.

f) Строки 58-72: Рендрим квадратики случайных цветов в случайные координаты.

g) Строка 73: Читаем полученное изображение обратно из GPU:

где каждый канал выглядит как

h) Строка 74: Размывка (Blur):

где каждый канал выглядит как

i) Строка 78: Запись файла.

j) Строки с77 по 126: то же самое для степени размывки 2

и 1


5. Компиляция и запуск. Новые изображения сгенерированы и сохранены в

C:\CPP\a997modeler\p_windows\Debug\dt\out


6. В Windows File Explorer-е скопируем созданные файлы из

C:\CPP\a997modeler\p_windows\Debug\dt\out

в C:\CPP\engine\dt\common\img\whitenoise

Теперь у нас там 5 BMP файлов.


7. Каталог C:\CPP\a997modeler\p_windows\Debug\dt можно удалить.


Сами-по-себе эти картинки не особо интересны, а вот их черно-белые каналы – весьма. И скоро они нам понадобятся.


Leave a Reply

Your email address will not be published. Required fields are marked *