一、OpenGL 纹理基础
纹理基础。
cocos2dx中的Texture2D类是对GL 纹理的封装,以便对GL纹理进行管理。
二、纹理格式
Cocos 3.17支持的纹理像素格式:
class CC_DLL Texture2D : public Ref{enum class PixelFormat {AUTO, // 自动检测格式,即采用引擎内部的固定转化规则,比如解析png格式图片文件时,会根据不同// 情况,将PNG图片转化成不同的像素格式,下面会讲。//**************普通纹理格式// 事实上,最终的纹理格式就是RGB(A)格式,因为显示器就是采用的RGBA标准。BGRA8888, // 32位真彩色,4个通道:red、green、blue、alpha,RGBA8888, // 每个通道占用8bits。占用内存大,运算速度慢。RGB888, // 24位纹理,相比于上面,缺少alpha通道,所以不支持透明度。RGB565, // 16位纹理,红色通道5位,绿色通道6位,蓝色通道5位,没有透明通道。// 它能尽最大努力提供一个高品质的16位纹理,前提是不需要透明度的支持。// 因为人眼对绿色更敏感一些,所以G位多了1bit,// 适用于游戏背景。A8, // 用作蒙版(mask)的8位纹理,用来做遮罩图。I8, // 8位强度/灰度纹理,用来做灰度图使用。AI88, // 用作蒙版(mask)的16位纹理,存储透明和灰度双功能的值RGBA4444, // 包含RGBA通道的16位纹理,适用于需要有不同的透明度精灵RGB5A1, // 包含 RGB通道(各5bit)和alpha通道(1bit)的 6位纹理,适用于无需支持透明度的精灵//**************压缩纹理格式PVRTC4, // 4-bit PVRTC-compressed texturePVRTC4A, // 4-bit PVRTC-compressed texture (has alpha channel)PVRTC2, // 2-bit PVRTC-compressed texturePVRTC2A, // 2-bit PVRTC-compressed texture (has alpha channel)ETC, // ETC-compressed textureS3TC_DXT1, // S3TC-compressed textureS3TC_DXT3, // S3TC-compressed textureS3TC_DXT5, // S3TC-compressed textureATC_RGB, // ATITC-compressed textureATC_EXPLICIT_ALPHA, // ATITC-compressed textureATC_INTERPOLATED_ALPHA, // ATITC-compressed textureDEFAULT = AUTO, // 默认是自动检测格式。};......}
对于每种格式的纹理,在用glTexImage2D创建纹理的时候都有固定的参数,因此cocos用了一个map来存储这些映射数据。
class CC_DLL Texture2D : public Ref{// 保存一种纹理格式对应的glTexImage2D参数。struct PixelFormatInfo {PixelFormatInfo(GLenum anInternalFormat,GLenum aFormat,GLenum aType,int aBpp,bool aCompressed,bool anAlpha): internalFormat(anInternalFormat), format(aFormat), type(aType), bpp(aBpp), compressed(aCompressed), alpha(anAlpha){}GLenum internalFormat; // glTexImage2D参数interalformatGLenum format; // glTexImage2D参数formatGLenum type; // glTexImage2D参数typeint bpp; // bits per pixelbool compressed; // 是不是压缩纹理格式,不然就要用glCompressedTexImage2Dbool alpha; // 是否支持alpha};// 纹理格式 -> 纹理参数typedef std::map<Texture2D::PixelFormat, const PixelFormatInfo> PixelFormatInfoMap;}// ***********************************************************// ********** CCTexture2D.cpp// ***********************************************************namespace {// map的value_typetypedef Texture2D::PixelFormatInfoMap::value_type PixelFormatInfoMapValue;// 用一个数组来保存当前设备支持的纹理格式及其对应参数。// 通过预编译+数组来动态初始化map来减少内存占用。static const PixelFormatInfoMapValue TexturePixelFormatInfoTablesValue[] ={// 普通纹理格式PixelFormatInfoMapValue(Texture2D::PixelFormat::BGRA8888, Texture2D::PixelFormatInfo(GL_BGRA, GL_BGRA, GL_UNSIGNED_BYTE, 32, false, true)),PixelFormatInfoMapValue(Texture2D::PixelFormat::RGBA8888, Texture2D::PixelFormatInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, 32, false, true)),PixelFormatInfoMapValue(Texture2D::PixelFormat::RGBA4444, Texture2D::PixelFormatInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, 16, false, true)),PixelFormatInfoMapValue(Texture2D::PixelFormat::RGB5A1, Texture2D::PixelFormatInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, 16, false, true)),PixelFormatInfoMapValue(Texture2D::PixelFormat::RGB565, Texture2D::PixelFormatInfo(GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 16, false, false)),PixelFormatInfoMapValue(Texture2D::PixelFormat::RGB888, Texture2D::PixelFormatInfo(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, 24, false, false)),PixelFormatInfoMapValue(Texture2D::PixelFormat::A8, Texture2D::PixelFormatInfo(GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE, 8, false, false)),PixelFormatInfoMapValue(Texture2D::PixelFormat::I8, Texture2D::PixelFormatInfo(GL_LUMINANCE, GL_LUMINANCE, GL_UNSIGNED_BYTE, 8, false, false)),PixelFormatInfoMapValue(Texture2D::PixelFormat::AI88, Texture2D::PixelFormatInfo(GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, 16, false, true)),// PVRTC压缩纹理格式#ifdef GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMGPixelFormatInfoMapValue(Texture2D::PixelFormat::PVRTC2, Texture2D::PixelFormatInfo(GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG, 0xFFFFFFFF, 0xFFFFFFFF, 2, true, false)),PixelFormatInfoMapValue(Texture2D::PixelFormat::PVRTC2A, Texture2D::PixelFormatInfo(GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, 0xFFFFFFFF, 0xFFFFFFFF, 2, true, true)),PixelFormatInfoMapValue(Texture2D::PixelFormat::PVRTC4, Texture2D::PixelFormatInfo(GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, 0xFFFFFFFF, 0xFFFFFFFF, 4, true, false)),PixelFormatInfoMapValue(Texture2D::PixelFormat::PVRTC4A, Texture2D::PixelFormatInfo(GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, 0xFFFFFFFF, 0xFFFFFFFF, 4, true, true)),#endif#ifdef GL_ETC1_RGB8_OESPixelFormatInfoMapValue(Texture2D::PixelFormat::ETC, Texture2D::PixelFormatInfo(GL_ETC1_RGB8_OES, 0xFFFFFFFF, 0xFFFFFFFF, 4, true, false)),#endif#ifdef GL_COMPRESSED_RGBA_S3TC_DXT1_EXTPixelFormatInfoMapValue(Texture2D::PixelFormat::S3TC_DXT1, Texture2D::PixelFormatInfo(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, 0xFFFFFFFF, 0xFFFFFFFF, 4, true, false)),#endif#ifdef GL_COMPRESSED_RGBA_S3TC_DXT3_EXTPixelFormatInfoMapValue(Texture2D::PixelFormat::S3TC_DXT3, Texture2D::PixelFormatInfo(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 0xFFFFFFFF, 0xFFFFFFFF, 8, true, false)),#endif#ifdef GL_COMPRESSED_RGBA_S3TC_DXT5_EXTPixelFormatInfoMapValue(Texture2D::PixelFormat::S3TC_DXT5, Texture2D::PixelFormatInfo(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, 0xFFFFFFFF, 0xFFFFFFFF, 8, true, false)),#endif#ifdef GL_ATC_RGB_AMDPixelFormatInfoMapValue(Texture2D::PixelFormat::ATC_RGB, Texture2D::PixelFormatInfo(GL_ATC_RGB_AMD, 0xFFFFFFFF, 0xFFFFFFFF, 4, true, false)),#endif#ifdef GL_ATC_RGBA_EXPLICIT_ALPHA_AMDPixelFormatInfoMapValue(Texture2D::PixelFormat::ATC_EXPLICIT_ALPHA, Texture2D::PixelFormatInfo(GL_ATC_RGBA_EXPLICIT_ALPHA_AMD, 0xFFFFFFFF, 0xFFFFFFFF, 8, true, false)),#endif#ifdef GL_ATC_RGBA_INTERPOLATED_ALPHA_AMDPixelFormatInfoMapValue(Texture2D::PixelFormat::ATC_INTERPOLATED_ALPHA, Texture2D::PixelFormatInfo(GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD, 0xFFFFFFFF, 0xFFFFFFFF, 8, true, false)),#endif};}// std::map(start, end)初始化mapconst Texture2D::PixelFormatInfoMap Texture2D::_pixelFormatInfoTables(TexturePixelFormatInfoTablesValue,TexturePixelFormatInfoTablesValue + sizeof(TexturePixelFormatInfoTablesValue) / sizeof(TexturePixelFormatInfoTablesValue[0]));
cocos检测设备对压缩纹理格式的支持:
void Configuration::gatherGPUInfo() {......_supportsETC1 = checkForGLExtension("GL_OES_compressed_ETC1_RGB8_texture");_valueDict["gl.supports_ETC1"] = Value(_supportsETC1);_supportsS3TC = checkForGLExtension("GL_EXT_texture_compression_s3tc");_valueDict["gl.supports_S3TC"] = Value(_supportsS3TC);_supportsATITC = checkForGLExtension("GL_AMD_compressed_ATC_texture");_valueDict["gl.supports_ATITC"] = Value(_supportsATITC);_supportsPVRTC = checkForGLExtension("GL_IMG_texture_compression_pvrtc");_valueDict["gl.supports_PVRTC"] = Value(_supportsPVRTC);......}bool Configuration::supportsATITC() const {return _supportsATITC;}bool Configuration::supportsETC() const {//GL_ETC1_RGB8_OES is not defined in old opengl version#ifdef GL_ETC1_RGB8_OESreturn _supportsETC1;#elsereturn false;#endif}bool Configuration::supportsS3TC() const {#ifdef GL_EXT_texture_compression_s3tcreturn _supportsS3TC;#elsereturn false;#endif}bool Configuration::supportsPVRTC() const {return _supportsPVRTC;}
三、操作纹理
Texture2D* pTexture = new Texture2D();// 在显存中创建GL纹理。pTexture->initWithData(......);pTexture->initWidthImage(......); // 从image file初始化pTexture->initWithMipmaps(......); // 多级纹理pTexture->initWithString(......); // 字体纹理......; // 使用Texture2DCC_SAFE_RELEASE(pTexture); // 触发析构,在析构中,删除显存中的GL纹理。
1、纹理数据
Texture2D纹理中保存的数据:
class CC_DLL Texture2D : public Ref {......Texture2D::PixelFormat _pixelFormat; // 纹理格式int _pixelsWide; // 像素宽度int _pixelsHigh; // 像素高度GLuint _name; // texture name/id,GL纹理对象的句柄GLfloat _maxS; // 纹理坐标的s坐标的最大值(相当于X坐标)GLfloat _maxT; // 同上理(相当于Y坐标)Size _contentSize; // 像素宽高bool _hasPremultipliedAlpha; // 是否开启alpha预乘bool _hasMipmaps; // 是否是多级纹理GLProgram* _shaderProgram; // drawAtPoint and drawInRect时使用的shader program// 上面已讲static const PixelFormatInfoMap _pixelFormatInfoTables;bool _antialiasEnabled; // 是否开启抗锯齿,其实就是采用GL_LINEAR线性采样NinePatchInfo* _ninePatchInfo; // 待完成bool _valid; // 貌似没鸟用。std::string _filePath; // 如果是从image file初始化,则保存该image file的路径Texture2D* _alphaTexture; // 为了让ETC1纹理支持透明......}
2、初始化纹理
initWithMipmaps
// 多级纹理初始化bool Texture2D::initWithMipmaps(MipmapInfo* mipmaps, // MipmapInfo其实就是一个数组,保存纹理数据int mipmapsNum, // 纹理级数,=1表示普通纹理,只有一张纹理。PixelFormat pixelFormat, // 纹理像素格式int pixelsWide, // 纹理像素宽高int pixelsHigh){//the pixelFormat must be a certain valueCCASSERT(pixelFormat != PixelFormat::NONE && pixelFormat != PixelFormat::AUTO, "the \"pixelFormat\" param must be a certain value!");CCASSERT(pixelsWide>0 && pixelsHigh>0, "Invalid size");if (mipmapsNum <= 0){CCLOG("cocos2d: WARNING: mipmap number is less than 1");return false;}// 纹理格式pixelFormat对应的glTexImage2D函数参数auto formatItr = _pixelFormatInfoTables.find(pixelFormat);if(formatItr == _pixelFormatInfoTables.end()){CCLOG("cocos2d: WARNING: unsupported pixelformat: %lx", (unsigned long)pixelFormat );return false;}const PixelFormatInfo& info = formatItr->second;// 纹理为压缩纹理,且设备不支持cocos的支持的所有压缩纹理if (info.compressed && !Configuration::getInstance()->supportsPVRTC()&& !Configuration::getInstance()->supportsETC()&& !Configuration::getInstance()->supportsS3TC()&& !Configuration::getInstance()->supportsATITC()){CCLOG("cocos2d: WARNING: PVRTC/ETC images are not supported");return false;}if (mipmapsNum == 1 && !info.compressed) // 单张普通纹理的纹理内存对齐(在显存中内存对齐){unsigned int bytesPerRow = pixelsWide * info.bpp / 8;if(bytesPerRow % 8 == 0) {glPixelStorei(GL_UNPACK_ALIGNMENT, 8);}else if(bytesPerRow % 4 == 0) {glPixelStorei(GL_UNPACK_ALIGNMENT, 4);}else if(bytesPerRow % 2 == 0) {glPixelStorei(GL_UNPACK_ALIGNMENT, 2);}else {glPixelStorei(GL_UNPACK_ALIGNMENT, 1);}}else { // 其他情况,都不考虑内存对齐glPixelStorei(GL_UNPACK_ALIGNMENT, 1);}if(_name != 0) // 删除先前的GL纹理{GL::deleteTexture(_name);_name = 0;}glGenTextures(1, &_name);GL::bindTexture2D(_name);// 单张纹理的MIN_FILTER:GL_LINEAR、GL_NEAREST// 多级纹理的MIN_FILETER参数:GL_LINEAR_MIPMAP_NEAREST、GL_NEAREST_MIPMAP_NEARESTif (mipmapsNum == 1){glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _antialiasEnabled ? GL_LINEAR : GL_NEAREST);}else{glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _antialiasEnabled ? GL_LINEAR_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_NEAREST);}glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _antialiasEnabled ? GL_LINEAR : GL_NEAREST );glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );#if CC_ENABLE_CACHE_TEXTURE_DATAif (_antialiasEnabled){TexParams texParams = {(GLuint)(_hasMipmaps?GL_LINEAR_MIPMAP_NEAREST:GL_LINEAR),GL_LINEAR,GL_NONE,GL_NONE};VolatileTextureMgr::setTexParameters(this, texParams);}else{TexParams texParams = {(GLuint)(_hasMipmaps?GL_NEAREST_MIPMAP_NEAREST:GL_NEAREST),GL_NEAREST,GL_NONE,GL_NONE};VolatileTextureMgr::setTexParameters(this, texParams);}#endif// 检查前面GL调用的错误日志GLenum err = glGetError();if (err != GL_NO_ERROR){cocos2d::log("OpenGL error 0x%04X in %s %s %d\n", err, __FILE__, __FUNCTION__, __LINE__);}int width = pixelsWide;int height = pixelsHigh;// Specify OpenGL texture image// 如果是多级纹理,无非就是多调用几次glTexImage2D,mipmapsNum=1就是普通单张纹理,也即调用一次glTexImage2D// 压缩纹理调用glCompressedTexImage2D// 非压缩纹理调用glTexImage2Dfor (int i = 0; i < mipmapsNum; ++i){unsigned char *data = mipmaps[i].address;GLsizei datalen = mipmaps[i].len;if (info.compressed){glCompressedTexImage2D(GL_TEXTURE_2D, i, info.internalFormat, (GLsizei)width, (GLsizei)height, 0, datalen, data);}else{glTexImage2D(GL_TEXTURE_2D, i, info.internalFormat, (GLsizei)width, (GLsizei)height, 0, info.format, info.type, data);}// 多级纹理必须是正方的,且必须是POT(2次幂尺寸的)if (i > 0 && (width != height || ccNextPOT(width) != width )){CCLOG("cocos2d: Texture2D. WARNING. Mipmap level %u is not squared. Texture won't render correctly. width=%d != height=%d", i, width, height);}// 检查前面GL调用的错误日志err = glGetError();if (err != GL_NO_ERROR){CCLOG("cocos2d: Texture2D: Error uploading compressed texture level: %u . glError: 0x%04X", i, err);return false;}// 多级纹理,n+1级的尺寸=n级尺寸的1/2width = MAX(width >> 1, 1);height = MAX(height >> 1, 1);}_contentSize = Size((float)pixelsWide, (float)pixelsHigh);_pixelsWide = pixelsWide;_pixelsHigh = pixelsHigh;_pixelFormat = pixelFormat;_maxS = 1;_maxT = 1;_hasPremultipliedAlpha = false;_hasMipmaps = mipmapsNum > 1;// 指定使用的着色器,在调用draw***方法时。setGLProgram(GLProgramCache::getInstance()->getGLProgram(GLProgram::SHADER_NAME_POSITION_TEXTURE));return true;}
initWithData
// 通过void *data初始化纹理,其实本质就是mipmapsNum=1的mipmapbool Texture2D::initWithData(const void *data,ssize_t dataLen,Texture2D::PixelFormat pixelFormat,int pixelsWide,int pixelsHigh,const Size& /*contentSize*/) // 没鸟用{CCASSERT(dataLen>0 && pixelsWide>0 && pixelsHigh>0, "Invalid size");//if data has no mipmaps, we will consider it has only one mipmapMipmapInfo mipmap;mipmap.address = (unsigned char*)data;mipmap.len = static_cast<int>(dataLen);return initWithMipmaps(&mipmap, 1, pixelFormat, pixelsWide, pixelsHigh);}
initWithImage
// 从一个image file 初始化纹理。 并将image format 转换成指定的texture format,比如png -> RGBA4444bool Texture2D::initWithImage(Image *image, PixelFormat format){if (image == nullptr){CCLOG("cocos2d: Texture2D. Can't create Texture. UIImage is nil");return false;}int imageWidth = image->getWidth();int imageHeight = image->getHeight();this->_filePath = image->getFilePath(); // image file的full path// 单张纹理大小有尺寸限制,glGetIntegerv(GL_MAX_TEXTURE_SIZE)获得Configuration *conf = Configuration::getInstance();int maxTextureSize = conf->getMaxTextureSize();if (imageWidth > maxTextureSize || imageHeight > maxTextureSize){CCLOG("cocos2d: WARNING: Image (%u x %u) is bigger than the supported %u x %u", imageWidth, imageHeight, maxTextureSize, maxTextureSize);return false;}unsigned char* tempData = image->getData(); // image datasize_t tempDataLen = image->getDataLen();Size imageSize = Size((float)imageWidth, (float)imageHeight);// pixelFormat表示我们需要纹理是什么格式的PixelFormat pixelFormat = ((PixelFormat::NONE == format) || (PixelFormat::AUTO == format)) ? image->getRenderFormat() : format;// renderFormat表示加载image file引擎默认转换的纹理格式PixelFormat renderFormat = image->getRenderFormat();if (image->getNumberOfMipmaps() > 1) // 多级纹理{// 如果是多级纹理,且不会转换成指定格式的纹理,在加载image file时是什么格式就是什么格式。if (pixelFormat != image->getRenderFormat()){CCLOG("cocos2d: WARNING: This image has more than 1 mipmaps and we will not convert the data format");}initWithMipmaps(image->getMipmaps(), image->getNumberOfMipmaps(), image->getRenderFormat(), imageWidth, imageHeight);// set the premultiplied tag_hasPremultipliedAlpha = image->hasPremultipliedAlpha();return true;}else if (image->isCompressed()) //压缩纹理{// 暂不支持压缩纹理的格式转换if (pixelFormat != image->getRenderFormat()){CCLOG("cocos2d: WARNING: This image is compressed and we can't convert it for now");}initWithData(tempData, tempDataLen, image->getRenderFormat(), imageWidth, imageHeight, imageSize);// set the premultiplied tag_hasPremultipliedAlpha = image->hasPremultipliedAlpha();return true;}else // 单张普通纹理,只有这种情况才支持转换成指定纹理格式{unsigned char* outTempData = nullptr;ssize_t outTempDataLen = 0;// 首先要从renderFormat纹理格式转换成我们指定的pixelFormat纹理格式pixelFormat = convertDataToFormat(tempData, tempDataLen, renderFormat, pixelFormat, &outTempData, &outTempDataLen);// 执行初始化initWithData(outTempData, outTempDataLen, pixelFormat, imageWidth, imageHeight, imageSize);if (outTempData != nullptr && outTempData != tempData) {free(outTempData);}// set the premultiplied tag_hasPremultipliedAlpha = image->hasPremultipliedAlpha();return true;}}
initWithString
创建字体纹理。
bool Texture2D::initWithString(const char *text, // A null terminated string.const std::string &fontName, // The font name.float fontSize, // The font size.const Size& dimensions = Size(0, 0), // The font dimension.TextHAlignment hAlignment = TextHAlignment::CENTER, // The font horizontal text alignment type.TextVAlignment vAlignment = TextVAlignment::TOP, // The font vertical text alignment type.bool enableWrap = true, // Whether enable text wrap or not.int overflow = 0); // Whether shrink font size when content larger than the dimensions.{FontDefinition tempDef;tempDef._shadow._shadowEnabled = false;tempDef._stroke._strokeEnabled = false;tempDef._fontName = fontName;tempDef._fontSize = fontSize;tempDef._dimensions = dimensions;tempDef._alignment = hAlignment;tempDef._vertAlignment = vAlignment;tempDef._fontFillColor = Color3B::WHITE;tempDef._enableWrap = enableWrap;tempDef._overflow = overflow;return initWithString(text, tempDef);}bool Texture2D::initWithString(const char *text, const FontDefinition& textDefinition){if(!text || 0 == strlen(text)){return false;}#if CC_ENABLE_CACHE_TEXTURE_DATA// cache the texture dataVolatileTextureMgr::addStringTexture(this, text, textDefinition);#endifbool ret = false;Device::TextAlign align;if (TextVAlignment::TOP == textDefinition._vertAlignment){align = (TextHAlignment::CENTER == textDefinition._alignment) ? Device::TextAlign::TOP: (TextHAlignment::LEFT == textDefinition._alignment) ? Device::TextAlign::TOP_LEFT : Device::TextAlign::TOP_RIGHT;}else if (TextVAlignment::CENTER == textDefinition._vertAlignment){align = (TextHAlignment::CENTER == textDefinition._alignment) ? Device::TextAlign::CENTER: (TextHAlignment::LEFT == textDefinition._alignment) ? Device::TextAlign::LEFT : Device::TextAlign::RIGHT;}else if (TextVAlignment::BOTTOM == textDefinition._vertAlignment){align = (TextHAlignment::CENTER == textDefinition._alignment) ? Device::TextAlign::BOTTOM: (TextHAlignment::LEFT == textDefinition._alignment) ? Device::TextAlign::BOTTOM_LEFT : Device::TextAlign::BOTTOM_RIGHT;}else{CCASSERT(false, "Not supported alignment format!");return false;}#if (CC_TARGET_PLATFORM != CC_PLATFORM_ANDROID) && (CC_TARGET_PLATFORM != CC_PLATFORM_IOS)CCASSERT(textDefinition._stroke._strokeEnabled == false, "Currently stroke only supported on iOS and Android!");#endifPixelFormat pixelFormat = g_defaultAlphaPixelFormat; // 字体纹理采用默认纹理格式unsigned char* outTempData = nullptr;ssize_t outTempDataLen = 0;int imageWidth;int imageHeight;auto textDef = textDefinition;auto contentScaleFactor = CC_CONTENT_SCALE_FACTOR();textDef._fontSize *= contentScaleFactor;textDef._lineSpacing *= contentScaleFactor;textDef._dimensions.width *= contentScaleFactor;textDef._dimensions.height *= contentScaleFactor;textDef._stroke._strokeSize *= contentScaleFactor;textDef._shadow._shadowEnabled = false;bool hasPremultipliedAlpha;// 获取纹理数据,分平台实现Data outData = Device::getTextureDataForText(text, textDef, align, imageWidth, imageHeight, hasPremultipliedAlpha);if(outData.isNull()){return false;}Size imageSize = Size((float)imageWidth, (float)imageHeight);// 转换纹理格式,cocos 3.17版本pixelFormat=AUTO,表示无需转换格式,使用默认格式// 从这里我们也知道,字体纹理的格式默认看成是RGBA8888pixelFormat = convertDataToFormat(outData.getBytes(), imageWidth*imageHeight*4, PixelFormat::RGBA8888, pixelFormat, &outTempData, &outTempDataLen);ret = initWithData(outTempData, outTempDataLen, pixelFormat, imageWidth, imageHeight, imageSize);if (outTempData != nullptr && outTempData != outData.getBytes()){free(outTempData);}_hasPremultipliedAlpha = hasPremultipliedAlpha;return ret;}
3、更新纹理
// 用data的数据更新纹理的这块子区域:// x: [offsetX, offsetX + width - 1]// y: [offsetY, offsetY + height - 1]bool Texture2D::updateWithData(const void *data,int offsetX,int offsetY,int width,int height){if (_name){GL::bindTexture2D(_name);const PixelFormatInfo& info = _pixelFormatInfoTables.at(_pixelFormat);glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, width, height, info.format, info.type, data);return true;}return false;}
4、删除纹理
// 方法一Texture2D::~Texture2D() // 析构时,清除显存纹理{......if(_name){GL::deleteTexture(_name); // glDeleteTexture清除显存纹理。}}// 方法二void Texture2D::releaseGLTexture(){if(_name){GL::deleteTexture(_name);}_name = 0;}
四、抗锯齿
注意与多重采样扛锯齿区分,这里的抗锯齿是通过设置Filtering为GL_LINEAR来得到模糊抗锯齿效果。
1、setAntiAliasTexParameters
// 开启抗锯齿,其实就是;// - GL_TEXTURE_MIN_FILTER = GL_LINEAR// - GL_TEXTURE_MAG_FILTER = GL_LINEARvoid Texture2D::setAntiAliasTexParameters(){if ( _antialiasEnabled ){return;}_antialiasEnabled = true;if (_name == 0){return;}GL::bindTexture2D( _name );if( ! _hasMipmaps ){glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );}else{glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );}glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );#if CC_ENABLE_CACHE_TEXTURE_DATATexParams texParams = {(GLuint)(_hasMipmaps?GL_LINEAR_MIPMAP_NEAREST:GL_LINEAR),GL_LINEAR,GL_NONE,GL_NONE};VolatileTextureMgr::setTexParameters(this, texParams);#endif}
2、setAliasTexParameters
// 关闭扛锯齿,其实就是;// - GL_TEXTURE_MIN_FILTER = GL_NEAREST// - GL_TEXTURE_MAG_FILTER = GL_NEARESTvoid Texture2D::setAliasTexParameters(){if (! _antialiasEnabled){return;}_antialiasEnabled = false;if (_name == 0){return;}GL::bindTexture2D( _name );if( ! _hasMipmaps ){glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );}else{glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST );}glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );#if CC_ENABLE_CACHE_TEXTURE_DATATexParams texParams = {(GLuint)(_hasMipmaps?GL_NEAREST_MIPMAP_NEAREST:GL_NEAREST),GL_NEAREST,GL_NONE,GL_NONE};VolatileTextureMgr::setTexParameters(this, texParams);#endif}
默认是开启抗锯齿:
Texture2D::Texture2D()......, _antialiasEnabled(true)......{}
3、多重采样抗锯齿
OpenGL多重采样抗锯齿知识。
cocos中默认不开启多重采样。
cocos如何开启多重采样:
//*****************AppDelegate.h// if you want a different context, modify the value of glContextAttrs// it will affect all platformsvoid AppDelegate::initGLContextAttrs(){// set OpenGL context attributes: red,green,blue,alpha,depth,stencilGLContextAttrs glContextAttrs = {8, 8, 8, 8, 24, 8, 0};GLView::setGLContextAttrs(glContextAttrs);}//*****************CCGLView.hstruct GLContextAttrs{int redBits;int greenBits;int blueBits;int alphaBits;int depthBits;int stencilBits;int multisamplingCount; // 采样数量// 默认为0,即不开启多重采样// 大于0,表示开启多重采样};//*****************glview.h#define GL_MAX_SAMPLES 0x8D57 //最大采样数
五、动态多级纹理
通过前面initWithMipmaps源码的学习,我们知道,引擎默认是采用静态多级纹理模式,也即是在程序外部借助工具预生成多级纹理,这样可以提高性能。
另外我们也可以通过glGenerateMipmap在运行时动态生成多级纹理,这很方便但也损耗性能。
void Texture2D::generateMipmap(){// 只支持2次幂纹理CCASSERT(_pixelsWide == ccNextPOT(_pixelsWide) && _pixelsHigh == ccNextPOT(_pixelsHigh), "Mipmap texture only works in POT textures");GL::bindTexture2D( _name );glGenerateMipmap(GL_TEXTURE_2D);_hasMipmaps = true;#if CC_ENABLE_CACHE_TEXTURE_DATAVolatileTextureMgr::setHasMipmaps(this, _hasMipmaps);#endif}
六、设置纹理参数
// 从下面我们可以看到,支持设置以下纹理参数// 过滤:GL_TEXTURE_MIN_FILTER、GL_TEXTURE_MAG_FILTER// 封装:GL_TEXTURE_WRAP_S、GL_TEXTURE_WRAP_Ttypedef struct _TexParams {GLuint minFilter;GLuint magFilter;GLuint wrapS;GLuint wrapT;} TexParams;void Texture2D::setTexParameters(const TexParams &texParams){CCASSERT((_pixelsWide == ccNextPOT(_pixelsWide) || texParams.wrapS == GL_CLAMP_TO_EDGE) &&(_pixelsHigh == ccNextPOT(_pixelsHigh) || texParams.wrapT == GL_CLAMP_TO_EDGE),"GL_CLAMP_TO_EDGE should be used in NPOT dimensions");GL::bindTexture2D( _name );glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texParams.minFilter );glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texParams.magFilter );glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, texParams.wrapS );glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, texParams.wrapT );#if CC_ENABLE_CACHE_TEXTURE_DATAVolatileTextureMgr::setTexParameters(this, texParams);#endif}
七、手动OpenGL绘制
drawAtPoint
// 以point为左下角点,绘制一张矩形纹理,宽高为纹理的宽高。void Texture2D::drawAtPoint(const Vec2& point){GLfloat width = (GLfloat)_pixelsWide * _maxS,GLfloat height = (GLfloat)_pixelsHigh * _maxT;// 我们可以发现,是将纹理的Y方向倒置然后映射到顶点坐标构成的矩形上 。GLfloat coordinates[] = {0.0f, _maxT, // 纹理左上角 -> 顶点矩形区域左下角_maxS, _maxT, // 纹理右上角 -> 顶点矩形区域右下角0.0f, 0.0f, // 纹理左下角 -> 顶点矩形区域左上角_maxS, 0.0f // 纹理右下角 -> 顶点矩形区域右上角};GLfloat vertices[] = {point.x, point.y,width + point.x, point.y,point.x, height + point.y,width + point.x, height + point.y };// 启用用于绘制纹理的shader_shaderProgram->use();_shaderProgram->setUniformsForBuiltins();// 绑定纹理GL::bindTexture2D( _name );// 设置顶点属性GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POSITION | GL::VERTEX_ATTRIB_FLAG_TEX_COORD );glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices);glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, coordinates);// 绘制三角形带,顶点绘制顺序如下:// 2 ----- 3// | \ |// | \ |// 0 ----- 1// 构成两个三角形012和123。glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);}
drawInRect
// 将纹理绘制到rect指定的矩形区域// 和drawAtPoint的区别是,前者只指定了绘制位置,而drawInRect同时还指定了绘制的宽高void Texture2D::drawInRect(const Rect& rect){GLfloat coordinates[] = {0.0f, _maxT,_maxS, _maxT,0.0f, 0.0f,_maxS, 0.0f};// 和drawAtPoint的区别就在这里,矩形区域的宽高不同。GLfloat vertices[] = {rect.origin.x, rect.origin.y, /*0.0f,*/rect.origin.x + rect.size.width, rect.origin.y, /*0.0f,*/rect.origin.x, rect.origin.y + rect.size.height, /*0.0f,*/rect.origin.x + rect.size.width, rect.origin.y + rect.size.height, /*0.0f*/ };// 启用用于绘制纹理的shader_shaderProgram->use();_shaderProgram->setUniformsForBuiltins();// 绑定纹理GL::bindTexture2D( _name );// 设置顶点属性GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POSITION | GL::VERTEX_ATTRIB_FLAG_TEX_COORD );glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices);glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, coordinates);// 绘制同上glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);}
八、alpha预乘
场景:纹理支持alpha。
一般做法如下:
// ****************************************// ****** C++ 代 码// ****************************************......;glEnable(GL_BLEND);glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);createTexture(......); // glTexImage2D加载纹理到显存......; // 各种使用纹理// ****************************************// ****** 片段着色器代码// ****************************************uniform Sampler2D texture; // 纹理varying vec2 v_texCoord; // 纹理坐标void main() {vec3 texColor = texture2D(texture, v_texCoord);// 纹理的alpha值就在texColor.a中// 在后续的Blending阶段,将按如下公式计算片段的最终颜色值// (Rs, Gs, Bs) * As + (Rd, Gd, Bd) * (1 - As)// Rs: source片段的R值// As: source片段的alpha值// Rd: 目标片段的R值// 前后两部分都要进行计算。gl_FragColor = texColor;}
优化做法(alpha预乘):
// ****************************************// ****** C++ 代 码// ****************************************......;glEnable(GL_BLEND);glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // GL_ONE代替GL_SRC_ALPHAcreateTextureEx(......); // 在这一步中,纹理的RGB值已经乘以了alpha值。......; // 各种使用纹理// ****************************************// ****** 片段着色器代码// ****************************************uniform Sampler2D texture; // 纹理varying vec2 v_texCoord; // 纹理坐标void main() {vec3 texColor = texture2D(texture, v_texCoord);// 在后续的Blending阶段,将按如下公式计算片段的最终颜色值// (Rs, Gs, Bs) * 1 + (Rd, Gd, Bd) * (1 - As)// 省去了(Rs, Gs, Bs) * As的计算工作,提升了性能。gl_FragColor = texColor;}
相关源码:
Texture2D::Texture2D()......, _hasPremultipliedAlpha(false) // 纹理默认不开启alpha预乘......{}// 从下面几个函数的相关源码,我们可以看出,只有在加载字体纹理和通过Image初始化纹理时,才可能支持alpha预乘。bool Texture2D::initWithMipmaps(......){......_hasPremultipliedAlpha = false;......}bool Texture2D::initWithImage(Image *image, PixelFormat format){......if (image->getNumberOfMipmaps() > 1) {......initWithMipmaps(......);......_hasPremultipliedAlpha = image->hasPremultipliedAlpha();......}else if (image->isCompressed()) {......initWithData(......);......_hasPremultipliedAlpha = image->hasPremultipliedAlpha();......}else {......initWithData(outTempData, outTempDataLen, pixelFormat, imageWidth, imageHeight, imageSize);......_hasPremultipliedAlpha = image->hasPremultipliedAlpha();......}}bool Texture2D::initWithString(const char *text, const FontDefinition& textDefinition){......bool hasPremultipliedAlpha;Data outData = Device::getTextureDataForText(......, hasPremultipliedAlpha);ret = initWithData(......);_hasPremultipliedAlpha = hasPremultipliedAlpha;return ret;}/// halx99 spec, ANDROID ETC1 ALPHA supports.// 这个函数主要是用于增加ETC1压缩纹理的alpha支持,ETC1本身是不支持alpha的。// 实现方法就是额外添加一张同大小的alpha纹理,与原纹理混合,从而达到alpha效果。void Texture2D::setAlphaTexture(Texture2D* alphaTexture){if (alphaTexture != nullptr) { // 这张纹理其实就保存在了上面着色器代码中的texture_alphaalphaTexture->retain();CC_SAFE_RELEASE(_alphaTexture);_alphaTexture = alphaTexture;_hasPremultipliedAlpha = true; // 采用alpha预乘,提升性能}}
ETC1添加alpha支持
ETC1压缩纹理并不支持alpha,通过额外添加一张相同大小的alpha纹理,并与原纹理混合,从而达到支持alpha的效果,具体实现原理如下:
// ****************************************// ****** C++ 代 码// ****************************************......;glEnable(GL_BLEND);glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // 使用alpha预乘......;// ****************************************// ****** 片段着色器代码// ****************************************uniform Sampler2D texture; // 纹理uniform Sampler2D texture_alpha; // alpha纹理,GL_RED格式,即用R分量保存alpha值varying vec2 v_texCoord; // 纹理坐标void main() {float alpha = texture2D(texture_alpha, v_texCoord).r;vec3 texColor = vec4(texture2D(texture, v_texCoord).rgb, alpha);texColor.rgb *= alpha; // 在这里提前将源片段颜色值与alpha相乘,在后面的混合阶段,就无需在乘以alpha了。gl_FragColor = texColor;}void Texture2D::setAlphaTexture(Texture2D* alphaTexture){if (alphaTexture != nullptr) // 就对应上面的texture_alpha 采样器{alphaTexture->retain();CC_SAFE_RELEASE(_alphaTexture);_alphaTexture = alphaTexture;_hasPremultipliedAlpha = true; // PremultipliedAlpha should be true.}}
九、纹理内存计算
工具检测
XCode的OpenGL ES Analytics工具可以检测纹理占用大小。
程序计算
工具检测不够灵活、不够精细,无能实时检测程序各个时间点的内存占用情况,并关联到具体的纹理上。我们可以自己进行计算:
// textureBytes: 纹理内存占用大小(字节数)// size: 纹理像素宽高// bpp: 每像素的bit数(bits per pixel)textureBytes = size.width * size.height * bpp >> 3;
cocos中各纹理格式的BPP如下:
| 格式 | bpp | 是否支持alpha |
|---|---|---|
| BGRA8888 | 32 | 是 |
| RGBA8888 | 32 | 是 |
| RGB888 | 24 | 否 |
| RGB565 | 16 | 否 |
| A8 | 8 | 否 |
| I8 | 8 | 否 |
| AI88 | 16 | 否 |
| RGBA4444 | 16 | 是 |
| RGB5A1 | 16 | 1 bit alpha |
| PVRTC4 | 4 | 是 |
| PVRTC4A | 4 | 是 |
| PVRTC2 | 2 | 是 |
| PVRTC2A | 2 | 是 |
| ETC | 4 | 否 |
| S3TC_DXT1 | 4 | 1 bit alpha |
| S3TC_DXT3 | 8 | 是 |
| S3TC_DXT5 | 8 | 是 |
| ATC_RGB | 4 | 否 |
| ATC_EXPLICIT_ALPHA | 8 | 是 |
| ATC_INTERPOLATED_ALPHA | 8 | 是 |
注意多级纹理的计算,外部工具预生成的多级纹理,按上面的公式正常计算,OpenGL ES命令(glGenerateMipmap)生成的多级纹理,则还需要额外计算每级纹理的内存。
