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_Texture0yCC_Texture1en 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.