Aprendiendo OpenGL ES 2.0 con Cocos2d-x: Texturas Múltiples

Este tutorial aborda la implementación de múltiples texturas en una superficie utiliznado Cocos2d-x y OpenGL ES 2.0. Se presentan dos enfoques fundamentales para lograr este efecto, con ejemplos de código modificados.

Enfoque 1: Renderizado por separado con una sola unidad de textura

En este método, se definen conjuntos separados de vértices e índices para cada superficie. Se utiliza una única unidad de textura (GL_TEXTURE0) y un único sampler en el shader. La secuencia implica configurar los buffers para el primer polígono, enlazar la primera textura, dibujar, y luego repetir el proceso para el segundo polígono con la segunda textura.

Shader de vértices (vert.glsl):

attribute vec4 a_pos;
attribute vec4 a_col;
attribute vec2 a_uv;

varying vec4 v_color;
varying vec2 v_texCoord;

void main() {
    v_color = a_col;
    v_texCoord = a_uv;
    gl_Position = CC_MVPMatrix * a_pos;
}

Shader de fragmentos (frag.glsl):

varying vec4 v_color;
varying vec2 v_texCoord;

void main() {
    gl_FragColor = v_color * texture2D(CC_Texture0, v_texCoord);
}

Implementación en C++ (Clase TexturaCubo):

#ifndef TEXTURA_CUBO_H
#define TEXTURA_CUBO_H

#include "cocos2d.h"

class TexturaCubo : public cocos2d::Layer {
public:
    static cocos2d::Scene* crearEscena();
    virtual bool init();
    void dibujar(cocos2d::Renderer *renderer, const cocos2d::Mat4 &transform, uint32_t flags) override;
    void onDibujar();
    CREATE_FUNC(TexturaCubo);

private:
    cocos2d::CustomCommand _cmdCustom;
    cocos2d::GLProgram* _programaShader;
    GLint _locPos, _locCol, _locUV;
    GLuint _uniTextura;
    GLuint _idText1, _idText2;
    GLuint _vboCubo, _iboCubo;
    GLuint _vboSuperficie, _iboSuperficie;
};

#endif
#include "TexturaCubo.h"
using namespace cocos2d;

Scene* TexturaCubo::crearEscena() {
    auto escena = Scene::create();
    auto capa = TexturaCubo::create();
    escena->addChild(capa);
    return escena;
}

bool TexturaCubo::init() {
    if (!Layer::init()) return false;

    _programaShader = new GLProgram();
    _programaShader->initWithFilenames("vert.glsl", "frag.glsl");
    _programaShader->link();
    _programaShader->updateUniforms();

    _idText1 = Director::getInstance()->getTextureCache()->addImage("fondo.png")->getName();
    _idText2 = Director::getInstance()->getTextureCache()->addImage("icono.png")->getName();

    glGenBuffers(1, &_vboCubo);
    glGenBuffers(1, &_iboCubo);
    glGenBuffers(1, &_vboSuperficie);
    glGenBuffers(1, &_iboSuperficie);

    return true;
}

void TexturaCubo::dibujar(Renderer *renderer, const Mat4 &transform, uint32_t flags) {
    Layer::dibujar(renderer, transform, flags);
    _cmdCustom.init(_globalZOrder);
    _cmdCustom.func = CC_CALLBACK_0(TexturaCubo::onDibujar, this);
    renderer->addCommand(&_cmdCustom);
}

void TexturaCubo::onDibujar() {
    // Configuración de matrices de vista
    Director::getInstance()->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    Director::getInstance()->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    Director::getInstance()->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    Director::getInstance()->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);

    Mat4 vista, proyeccion;
    Mat4::createLookAt(Vec3(0, 0, 6), Vec3(0, 0, 0), Vec3(0, 1, 0), &vista);
    vista.translate(0, 0, -5);
    vista.rotate(Vec3(1, 0, 0), 0.5f);
    Mat4::createPerspective(60, 1.5f, 1.0, 100, &proyeccion);

    Director::getInstance()->multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, proyeccion);
    Director::getInstance()->multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, vista);

    // Estructura de vértices y datos del cubo
    struct Vertice {
        float posicion[3];
        float color[4];
        float uv[2];
    };

    Vertice datosCubo[] = {
        // Frente
        {{-1,-1, 1}, {1,0,0,1}, {0,0}}, {{ 1,-1, 1}, {0,1,0,1}, {1,0}}, {{ 1, 1, 1}, {0,0,1,1}, {1,1}}, {{-1, 1, 1}, {1,1,1,1}, {0,1}},
        // Más caras...
    };

    GLubyte indicesCubo[] = {0,1,2, 2,3,0, /* más índices */};

    // Datos para la superposición de textura
    Vertice datosSuperpos[] = {
        {{-0.4, 0.4, 1.01}, {1,1,1,1}, {0,0}},
        {{ 0.4, 0.4, 1.01}, {1,1,1,1}, {1,0}},
        {{ 0.4,-0.4, 1.01}, {1,1,1,1}, {1,1}},
        {{-0.4,-0.4, 1.01}, {1,1,1,1}, {0,1}}
    };
    GLubyte indicesSuperpos[] = {0,1,2, 2,3,0};

    // Dibujar cubo con textura base
    glBindBuffer(GL_ARRAY_BUFFER, _vboCubo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(datosCubo), datosCubo, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _iboCubo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indicesCubo), indicesCubo, GL_STATIC_DRAW);

    _programaShader->use();
    _locPos = glGetAttribLocation(_programaShader->getProgram(), "a_pos");
    _locCol = glGetAttribLocation(_programaShader->getProgram(), "a_col");
    _locUV = glGetAttribLocation(_programaShader->getProgram(), "a_uv");
    _uniTextura = glGetUniformLocation(_programaShader->getProgram(), "CC_Texture0");

    glEnableVertexAttribArray(_locPos);
    glEnableVertexAttribArray(_locCol);
    glEnableVertexAttribArray(_locUV);

    glVertexAttribPointer(_locPos, 3, GL_FLOAT, GL_FALSE, sizeof(Vertice), (void*)offsetof(Vertice, posicion));
    glVertexAttribPointer(_locCol, 4, GL_FLOAT, GL_FALSE, sizeof(Vertice), (void*)offsetof(Vertice, color));
    glVertexAttribPointer(_locUV, 2, GL_FLOAT, GL_FALSE, sizeof(Vertice), (void*)offsetof(Vertice, uv));

    GL::bindTexture2DN(0, _idText1);
    glUniform1i(_uniTextura, 0);

    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, 0);

    // Dibujar superficie con segunda textura
    glBindBuffer(GL_ARRAY_BUFFER, _vboSuperficie);
    glBufferData(GL_ARRAY_BUFFER, sizeof(datosSuperpos), datosSuperpos, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _iboSuperficie);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indicesSuperpos), indicesSuperpos, GL_STATIC_DRAW);

    GL::bindTexture2DN(0, _idText2);
    glUniform1i(_uniTextura, 0);

    glVertexAttribPointer(_locPos, 3, GL_FLOAT, GL_FALSE, sizeof(Vertice), 0);
    glVertexAttribPointer(_locCol, 4, GL_FLOAT, GL_FALSE, sizeof(Vertice), (void*)(sizeof(float)*3));
    glVertexAttribPointer(_locUV, 2, GL_FLOAT, GL_FALSE, sizeof(Vertice), (void*)(sizeof(float)*7));

    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, 0);

    // Restaurar estado
    Director::getInstance()->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    Director::getInstance()->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
}

Enfoque 2: Texturizado múltiple en un solo draw call

Este método utiliza múltiples unidades de textura y samplers en el shader. Se preparan los buffers una sola vez, y durante el renderizado se enlazan ambas texturas a diferentes unidades antes de dibujar la geometría. El shader combina las texturas.

Shader de vértices (multi.vert):

attribute vec4 a_position;
attribute vec2 a_texcoord;

varying vec2 v_texcoord;

void main() {
    gl_Position = CC_MVPMatrix * a_position;
    v_texcoord = a_texcoord;
}

Shader de fragmentos (multi.frag):

precision mediump float;
varying vec2 v_texcoord;

void main() {
    vec4 colorBase = texture2D(CC_Texture0, v_texcoord);
    vec4 colorLuz = texture2D(CC_Texture1, v_texcoord);
    gl_FragColor = colorBase * (colorLuz + vec4(0.2));
}

Implementación en C++ (Clase TexturaMultiple):

#ifndef TEXTURA_MULTIPLE_H
#define TEXTURA_MULTIPLE_H

#include "cocos2d.h"

class TexturaMultiple : public cocos2d::Layer {
public:
    static cocos2d::Scene* crearEscena();
    virtual bool init();
    void dibujar(cocos2d::Renderer *renderer, const cocos2d::Mat4 &transform, uint32_t flags) override;
    void onDibujar();
    CREATE_FUNC(TexturaMultiple);

private:
    cocos2d::CustomCommand _cmdCustom;
    cocos2d::GLProgram* _programaShader;
    GLint _locPos, _locUV;
    GLuint _uniTextBase, _uniTextLuz;
    GLuint _idTextBase, _idTextLuz;
    GLuint _vao, _vbo, _ibo;
};

#endif
#include "TexturaMultiple.h"
using namespace cocos2d;

Scene* TexturaMultiple::crearEscena() {
    auto escena = Scene::create();
    auto capa = TexturaMultiple::create();
    escena->addChild(capa);
    return escena;
}

bool TexturaMultiple::init() {
    if (!Layer::init()) return false;

    _programaShader = new GLProgram();
    _programaShader->initWithFilenames("multi.vert", "multi.frag");
    _programaShader->link();
    _programaShader->updateUniforms();

    _idTextBase = Director::getInstance()->getTextureCache()->addImage("patron.png")->getName();
    _idTextLuz = Director::getInstance()->getTextureCache()->addImage("iluminacion.jpg")->getName();

    glGenVertexArrays(1, &_vao);
    glBindVertexArray(_vao);
    glGenBuffers(1, &_vbo);
    glGenBuffers(1, &_ibo);

    return true;
}

void TexturaMultiple::dibujar(Renderer *renderer, const Mat4 &transform, uint32_t flags) {
    Layer::dibujar(renderer, transform, flags);
    _cmdCustom.init(_globalZOrder);
    _cmdCustom.func = CC_CALLBACK_0(TexturaMultiple::onDibujar, this);
    renderer->addCommand(&_cmdCustom);
}

void TexturaMultiple::onDibujar() {
    Director::getInstance()->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    Director::getInstance()->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    Director::getInstance()->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    Director::getInstance()->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);

    // Definición de geometría
    struct Vertice {
        float pos[3];
        float uv[2];
    };

    Vertice vertices[] = {
        {{-1,-1,0}, {0,0}},
        {{ 1,-1,0}, {1,0}},
        {{ 1, 1,0}, {1,1}},
        {{-1, 1,0}, {0,1}}
    };
    GLubyte indices[] = {0,1,2, 2,3,0};

    // Configuración de buffers y atributos
    glBindBuffer(GL_ARRAY_BUFFER, _vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    _programaShader->use();
    _locPos = glGetAttribLocation(_programaShader->getProgram(), "a_position");
    _locUV = glGetAttribLocation(_programaShader->getProgram(), "a_texcoord");
    _uniTextBase = glGetUniformLocation(_programaShader->getProgram(), "CC_Texture0");
    _uniTextLuz = glGetUniformLocation(_programaShader->getProgram(), "CC_Texture1");

    glEnableVertexAttribArray(_locPos);
    glEnableVertexAttribArray(_locUV);

    glVertexAttribPointer(_locPos, 3, GL_FLOAT, GL_FALSE, sizeof(Vertice), (void*)offsetof(Vertice, pos));
    glVertexAttribPointer(_locUV, 2, GL_FLOAT, GL_FALSE, sizeof(Vertice), (void*)offsetof(Vertice, uv));

    // Enlazar texturas a diferentes unidades
    GL::bindTexture2DN(0, _idTextLuz);
    glUniform1i(_uniTextLuz, 0);
    GL::bindTexture2DN(1, _idTextBase);
    glUniform1i(_uniTextBase, 1);

    // Dibujar
    glBindVertexArray(_vao);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, 0);
    glBindVertexArray(0);

    // Restaurar matrices
    Director::getInstance()->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    Director::getInstance()->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
}

Consideraciones importantes:

  • Cocos2d-x define uniformes incorporados como CC_Texture0 y CC_Texture1 en los shaders. Declarar variables con estos mismos nombres puede causar conflictos.
  • El framework mantiene un caché de estado de OpenGL. Para cambiar unidades de textura activas, es preferible usar funciones envloventes como GL::bindTexture2DN() en lugar de las funciones nativas de OpenGL, para mantener la consistencia del caché.
  • La combinación de texturas en el shader permite efectos más complejos y se realiza en una sola operación de dibujo, lo cual puede ser más eficiente.

Etiquetas: Cocos2d-x OpenGL ES 2.0 shader texturas múltiples VBO

Publicado el 5-30 09:54