DDS简介:DDS文件格式要追述到S3(Silicon & Software Systems)公司提出的一种纹理压缩格式S3TC(S3 Texture Compression), 其目的是通过对纹理的压缩, 以达到节约系统带宽并提高效能的目的. S3TC就是通过压缩方式, 利用有限的纹理缓存空间来存储更多的纹理, 因为它支持6:1的压缩比例, 所以6M的纹理可以被压缩为1M存放在材质缓存中, 从而在节约了缓存的同时也提高了显示性能. 后来的DXTC和FXT1都是与S3TC类似的技术, 它们分别是微软和3dfx开发的纹理压缩标准, FXT1能提供比S3TC更高的压缩比, 达到8:1, 同时它也在3DFX新版本的Glide中得到支持. DXTC是1999年微软从S3公司取得S3TC的授权后更名而来的, 并在DirectX6中提供了支持, 即使用户的图形硬件不能支持S3TC, DirectX API会自动解码压缩后的纹理贴图. 压缩纹理贴图可以使用高品质的离线压缩器, 不会造成加载程序时有很多延时, 而DDS文件就可以使用DXTC方式压缩或是存储未压缩的像素格式.
简单的说 DDS纹理的特性是占用磁盘空间大,但其纹理压缩格式的特性,可以节约缓存使占用内存大大降低,一些3D建模游戏会运用DDS贴图,提高游戏技能,顺畅度也大幅提高。
我对于DDS纹理贴图的研究其实源于项目的需求。先来讲个故事:BOSS某天给我指派了一个任务,大概是你看别人家的游戏为什么如此顺畅,你来研究一下别人用了什么框架,什么技术然后需要用扒到的素材做出一款简化的demo。(求当是我心里的阴影面积)我当是还是蛮激动的,因为毕竟不用去做琐碎无聊的事,也可趁此机会也可以学习下其他的框架。
在chrome调试器中扒到得资源里的DDS格式文件,此类文件可以下载一个XnView软件工具直接打开查看,或者下载一个可以安装到Photoshop中的s3tc格式的插件。
cocos2dx如何加载DDS纹理:(这里我用的JS脚本)
1.Native加载
对于JSBinding来说,加载很方便。因为cocos底层有直接解压缩S3TC压缩格式的函数。可以直接采用用路径名建立精灵
如:var spr = new cc.sprite(“res/test.dds”);
与加载png的方式并无差异,将精灵加在显示的layer上,可以直接看到加载DDS纹理的图片。
2.H5加载
说来很奇怪,cocos html并没有提供可以直接加载DDS文件格式的函数。若是如Native那样加载,会出现“.dds文件类型不能识别的错误。”
网上可供参考的资料少之又少,通过对OpenGL渲染纹理的了解和对底层代码的分析,终于将DDS贴图渲染出来,显示在我的TestLayer上。
这里贴出代码:
_getSourceBuffer: function(){
var self = this;
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function (ev) {
console.log("xhr resp");
if (xhr.status == 200) {
self.parseDDS(xhr.response, self._uploadCallback, errorCallback, "");
} else {
errorCallback(xhr.statusText);
}
}, false);
xhr.open('GET', "res/test.dds", true);
xhr.responseType = 'arraybuffer';
xhr.send(null);
}
getSourceBuffer函数是采用XMLHttpRequest的方式读取出test.dds的ArrayBuffer,即二进制的原始缓存数据。
parseDDS: function(arrayBuffer, callback, errorCallback, desc) {
console.log("parseDDS");
if (!callback || !errorCallback) {
return
}
var header = new Int32Array(arrayBuffer, 0, DDS_HEADER_LENGTH);
if (header[DDS_HEADER_MAGIC] != DDS_MAGIC) {
errorCallback("Invalid magic number in DDS header");
return 0
}
if (!header[DDS_HEADER_PF_FLAGS] & DDPF_FOURCC) {
errorCallback("Unsupported format, must contain a FourCC code");
return 0
}
var fourCC = header[DDS_HEADER_PF_FOURCC];
var internalFormat;
switch (fourCC) {
case FOURCC_DXT1:
internalFormat = COMPRESSED_RGB_S3TC_DXT1_EXT;
break;
case FOURCC_DXT3:
internalFormat = COMPRESSED_RGBA_S3TC_DXT3_EXT;
break;
case FOURCC_DXT5:
internalFormat = COMPRESSED_RGBA_S3TC_DXT5_EXT;
break;
case FOURCC_ATC:
internalFormat = COMPRESSED_RGB_ATC_WEBGL;
break;
case FOURCC_ATCA:
internalFormat = COMPRESSED_RGBA_ATC_EXPLICIT_ALPHA_WEBGL;
break;
case FOURCC_ATCI:
internalFormat = COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL;
break;
default:
errorCallback("Unsupported FourCC code: " + int32ToFourCC(fourCC));
return
}
var levels = 1;
if (header[DDS_HEADER_FLAGS] & DDSD_MIPMAPCOUNT) {
levels = Math.max(1, header[DDS_HEADER_MIPMAPCOUNT])
}
var width = header[DDS_HEADER_WIDTH];
var height = header[DDS_HEADER_HEIGHT];
var dataOffset = header[DDS_HEADER_SIZE] + 4;
var dxtData = new Uint8Array(arrayBuffer, dataOffset);
var B0 = this.textureLevelSize(internalFormat, width, height)
var A0 = new Uint8Array(dxtData.buffer,dxtData.byteOffset,B0);
this.initWithData(A0,internalFormat,width,height,cc.size(width,height));
if(callback) callback();
},
parseDDS函数的功能是将上述得到的二进制缓存数组进行解析,获得人力的width,height,size和纹理数据块压缩格式等信息,再通过initWithData进行Texture2D的初始化:
initWithData: function (data, pixelFormat, pixelsWide, pixelsHigh, contentSize) {
var self = this, tex2d = cc.Texture2D;
var gl = cc._renderContext;
var format = gl.RGBA, type = gl.UNSIGNED_BYTE;
var bitsPerPixel = cc.Texture2D._B[pixelFormat];
var bytesPerRow = pixelsWide * bitsPerPixel / 8;
if (bytesPerRow % 8 === 0) {
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 8);
} else if (bytesPerRow % 4 === 0) {
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 4);
} else if (bytesPerRow % 2 === 0) {
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 2);
} else {
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
}
self._webTextureObj = gl.createTexture();
cc.glBindTexture2D(self);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
var ext = (
gl.getExtension("WEBGL_compressed_texture_s3tc") ||
gl.getExtension("MOZ_WEBGL_compressed_texture_s3tc") ||
gl.getExtension("WEBKIT_WEBGL_compressed_texture_s3tc")
);
// Specify OpenGL texture image
switch (pixelFormat) {
case tex2d.PIXEL_FORMAT_RGBA8888:
format = gl.RGBA;
break;
case tex2d.PIXEL_FORMAT_RGB888:
format = gl.RGB;
break;
case tex2d.PIXEL_FORMAT_RGBA4444:
type = gl.UNSIGNED_SHORT_4_4_4_4;
break;
case tex2d.PIXEL_FORMAT_RGB5A1:
type = gl.UNSIGNED_SHORT_5_5_5_1;
break;
case tex2d.PIXEL_FORMAT_RGB565:
type = gl.UNSIGNED_SHORT_5_6_5;
break;
case tex2d.PIXEL_FORMAT_AI88:
format = gl.LUMINANCE_ALPHA;
break;
case tex2d.PIXEL_FORMAT_A8:
format = gl.ALPHA;
break;
case tex2d.PIXEL_FORMAT_I8:
format = gl.LUMINANCE;
break;
case ext.COMPRESSED_RGBA_S3TC_DXT5_EXT:
format = ext.COMPRESSED_RGBA_S3TC_DXT5_EXT;
break;
default:
cc.assert(0, cc._LogInfos.Texture2D_initWithData);
}
if(format == ext.COMPRESSED_RGBA_S3TC_DXT5_EXT){
//gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, pixelsWide, pixelsHigh, 0, format, type, data);
gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, pixelsWide, pixelsHigh, 0, data);
}else{
gl.texImage2D(gl.TEXTURE_2D, 0, format, pixelsWide, pixelsHigh, 0, format, type, data);
}
self._contentSize.width = contentSize.width;
self._contentSize.height = contentSize.height;
self._pixelsWide = pixelsWide;
self._pixelsHigh = pixelsHigh;
self._pixelFormat = pixelFormat;
self.maxS = contentSize.width / pixelsWide;
self.maxT = contentSize.height / pixelsHigh;
self._hasPremultipliedAlpha = false;
self._hasMipmaps = false;
self.shaderProgram = cc.shaderCache.programForKey(cc.SHADER_POSITION_TEXTURE);
self._textureLoaded = true;
return true;
}
至此一个已将DDS格式纹理解析好的数据初始化了一个Texture2D的对象,初始化流程参考OPENGL基本渲染流程和cocos引擎底层对cc.Texture2D的渲染流程。
再用解析渲染后的Texture建立精灵,addChild在layer上即可显示与场景之中。
附:解析所需其他宏的定义
function fourCCToInt32(value) {
return value.charCodeAt(0) +
(value.charCodeAt(1) << 8) +
(value.charCodeAt(2) << 16) +
(value.charCodeAt(3) << 24);
}
var FOURCC_DXT1 = fourCCToInt32("DXT1");
var FOURCC_DXT3 = fourCCToInt32("DXT3");
var FOURCC_DXT5 = fourCCToInt32("DXT5");
var FOURCC_ATC = fourCCToInt32("ATC ");
var FOURCC_ATCA = fourCCToInt32("ATCA");
var FOURCC_ATCI = fourCCToInt32("ATCI");
// DXT formats, from:
// http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
var COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0;
var COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
var COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
var COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
// ATC formats, from:
// http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_atc/
var COMPRESSED_RGB_ATC_WEBGL = 0x8C92;
var COMPRESSED_RGBA_ATC_EXPLICIT_ALPHA_WEBGL = 0x8C93;
var COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL = 0x87EE;
var DDS_MAGIC = 0x20534444;
var DDSD_MIPMAPCOUNT = 0x20000;
var DDPF_FOURCC = 0x4;
var DDS_HEADER_LENGTH = 31; // The header length in 32 bit ints.
// Offsets into the header array.
var DDS_HEADER_MAGIC = 0;
var DDS_HEADER_SIZE = 1;
var DDS_HEADER_FLAGS = 2;
var DDS_HEADER_HEIGHT = 3;
var DDS_HEADER_WIDTH = 4;
var DDS_HEADER_MIPMAPCOUNT = 7;
var DDS_HEADER_PF_FLAGS = 20;
var DDS_HEADER_PF_FOURCC = 21;