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
Глава 29. Chroma key – Игра в Написание Игры

Глава 29. Chroma key

Chroma keying, понятно, это способ считать какой-то конкретный цвет прозрачным. В нашем случае – зеленый:

Конечно, после загрузки изображения, перед тем как сделать из него текстуру, мы можем легко просканировать его на предмет зеленых пикселей, и установить их Альфа-каналы в ноль. Правда, когда GL rasterizer выбирает tUV координаты рядом с зеленой зоной, результирующий пиксель на экране будет частично “прозрачным” и частичнозеленым. Кстати, безотносительно прозрачности. В нашем примере это проявляется как зеленоватые тонкие линии по границам проекций:

Да, это важно, какие цвета окружают ваши изображения.

Решение: при сканировании на предмет зеленых пикселей, можно установить их Альфа-каналы в ноль, И также их RGB компоненты – в цвета ближайших непрозрачных пикселей. Чтобы результирующее изображение было:

  • Бывшие зеленые области теперь прозрачные, плюс их RGB компоненты больше не повлияют на соседние пиксели.

Реализация:

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


В классе Texture у нас новая функция, applyCkey(..), и новый параметр в loadTexture(..).

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, unsigned int ckey, int glRepeatH = GL_MIRRORED_REPEAT, int glRepeatV = GL_MIRRORED_REPEAT);
    static int applyCkey(unsigned char* imgData, int w, int h, unsigned int ckey);
    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"
#include "MyColor.h"

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

int Texture::loadTexture(std::string filePath, unsigned int ckey, 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());
    }
    if (ckey != 0)
        applyCkey(imgData, w, h, ckey);
    // 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;
}
int Texture::applyCkey(unsigned char* imgData, int w0, int h0, unsigned int ckey) {
    if (ckey == 0)
        return 0;
    MyColor transparentPixel;
    transparentPixel.setRGBA(127, 127, 127, 0);
    unsigned int transparentValue = transparentPixel.getUint32();
    unsigned int* pIntData = (unsigned int*)imgData;
    int dataIntsN = w0 * h0;
    int transparentPixelsN = 0;
    for (int i = 0; i < dataIntsN; i++) {
        if (pIntData[i] != ckey)
            continue;
        //here - have ckey pixel
        transparentPixelsN++;
        pIntData[i] = transparentValue;
    }
    if (transparentPixelsN == 0)
        return 0;
    //re-calculate transparent RGBs
    int dataCharsN = dataIntsN * 4;
    //duplicate image
    unsigned char* imgTemp = new unsigned char[dataCharsN];
    memcpy(imgTemp, imgData, dataCharsN);
    //scan
    int blurLevel = 1;
    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++) {
            if (imgData[(y0 * w0 + x0) * 4 + 3] != 0) //check current pixel's alpha component
                continue; //non-transparent pixel
            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[3] = { 0,0,0 };
            int opaquePixelsN = 0;
            for (int y = y1; y < y1 + h1; y++) {
                for (int x = x1; x < x1 + w1; x++) {
                    int idx = (y * w0 + x) * 4;
                    if (imgTemp[idx + 3] == 0) //check alpha channel
                        continue; //transparent pixel
                    opaquePixelsN++;
                    for (int ch = 0; ch < 3; ch++)
                        sum[ch] += imgTemp[idx + ch];
                }
            }
            if (opaquePixelsN == 0)
                continue;
            int idx = (y0 * w0 + x0) * 4;
            for (int ch = 0; ch < 3; ch++)
                imgData[idx + ch] = (unsigned char)(sum[ch] / opaquePixelsN);
        }
    }
    delete[] imgTemp;
    saveTGA("/dt/02.tga", imgData, w0, h0, 3);
    return 1;
}


В ModelLoader-е уже есть код, который читает и исполняет chroma key для текстуры. Надо просто добавить chroma key в model descriptor.

4. В Текстовом редакторе откроем

C:\CPP\a997modeler\dt\models\misc\marlboro01\root01.txt

и заменим код на:

<texture_as="tx0" src="marlboro03small.png" ckey="#00ff00"/>
<mt_type="phong" uTex0_use="tx0" />
<vs="box_tank" whl="53,83,21" ext=1 sectR=1 />
<a="front v,back v" xywh="2,1,323,495"/>
<a="right all" xywh="327,1,128,495"/>
<a="left all" xywh="457,1,128,495"/>
<a="top" xywh="588,1,323,133"/>
<a="bottom" xywh="587,136,324,134"/>

  • Заметим: добавлена новая переменная/property ckey=”#00ff00″.
  • Задана она в HTML HEX формате. Также ее можно задать как массив decimal integers: ckey=”0,255,0″.

5. Компиляция и запуск.

Теперь – без зеленых линий!


Проверено на Андроиде тоже – все норм.

Наша следующая цель – сделать изображение реалистичнее чем в реальности!


Leave a Reply

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