程序员人生 网站导航

three.js 源码注释(三十五)Texture/DataTexture.js

栏目:htmlcss时间:2014-12-19 08:34:57

商域无疆 (http://blog.csdn.net/omni360/)

本文遵守“署名-非商业用处-保持1致”创作公用协议

转载请保存此句:商域无疆 -  本博客专注于 敏捷开发及移动和物联装备研究:数据可视化、GOLANG、Html5、WEBGL、THREE.JS否则,出自本博客的文章谢绝转载或再转载,谢谢合作。


俺也是刚开始学,好多地儿肯定不对还请见谅.

以下代码是THREE.JS 源码文件中Texture/DataTexture.js文件的注释.

更多更新在 : https://github.com/omni360/three.js.sourcecode


/** * @author alteredq / http://alteredqualia.com/ */ /* ///DataTexture类用来为面创建1个基于图象数据的反射折射或纹理贴图对象,和THREE.Texture方法不同的是这里的图象使用的数据格式 /// 这个类是最重要的属性是data,这是1个JavaScript Image类型对象的数据格式。传入的第1个参数就是该对象, ///后面的对象都是可选的,如果缺省就会填充默许值,而且常常都是填充默许值。 ///属性magFileter和minFileter指定纹理在放大和缩小时的过滤方式:最邻近点、双线性内插等。 */ ///<summary>DataTexture</summary> ///<param name ="data" type="Number">图象数据</param> ///<param name ="width" type="Number">图象宽度</param> ///<param name ="height" type="Number">图象高度</param> ///<param name ="format" type="Number">像素数据的色彩格式,参考Texture/Texture.js的注释</param> ///<param name ="type" type="Number">数据类型,默许为不带符号8位整形值</param> ///<param name ="mapping" type="Number">映照模式,参考Texture/Texture.js的注释</param> ///<param name ="wrapS" type="Number">S方向覆盖模式,参考Texture/Texture.js的注释</param> ///<param name ="wrapT" type="Number">T方向覆盖模式,参考Texture/Texture.js的注释</param> ///<param name ="magFilter" type="Number">纹理在放大时的过滤方式,参考Texture/Texture.js的注释</param> ///<param name ="minFilter" type="Number">纹理在缩小时的过滤方式,参考Texture/Texture.js的注释</param> ///<param name ="anisotropy" type="Float">各向异性,取值范围0.0⑴.0,常常用来通过这个值,产生不同的表面效果,木材和金属都发光,但是发光的特点是有区分的.</param> THREE.DataTexture = function ( data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy ) { THREE.Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); //调用Texture对象的call方法,将本来属于Texture的方法交给当前对象DataTexture来使用. this.image = { data: data, width: width, height: height }; //将参数data,width,height赋值给image属性 }; /************************************************* ****下面是DataTexture对象的方法属性定义,继承自Texture **************************************************/ // THREE.Texture = function ( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { // this.id = THREE.TextureIdCount ++; //纹理属性id // this.uuid = THREE.Math.generateUUID(); //纹理uuid(通用唯1标识码)属性 // this.name = ''; //纹理名称属性,可有可无 // this.image = image !== undefined ? image : THREE.Texture.DEFAULT_IMAGE; //纹理的图片,最重要的属性是image,这是1个JavaScript Image类型对象。 // /* // Mipmap // 在3维世界中,显示1张图的大小与摄象机的位置有关,近的地方,图片实际象素就大1些,远的地方图片实际象 // 素就会小1些,就要进行1些紧缩,例如1张64*64的图,在近处,显示出来多是50*50,在远处可能显示出来是20*20. // 如果只限于简单的支掉某些像素,将会使缩小后的图片损失很多细节,图片变得很粗糙,因此,图形学有很多复杂的方 // 法来处理缩小图片的问题,使得缩小后的图片仍然清晰,但是,这些计算都会耗费1定的时间. // Mipmap纹理技术是目前解决纹理分辨率与视点距离关系的最有效途径,它会先将图片紧缩成很多逐步缩小的图片, // 例如1张64*64的图片,会产生64*64,32*32,16*16,8*8,4*4,2*2,1*1的7张图片,当屏幕上需要绘制像素点为20*20 时, // 程序只是利用 32*32 和 16*16 这两张图片来计算出行将显示为 20*20 大小的1个图片,这比单独利用 32*32 的 // 那张原始片计算出来的图片效果要好很多,速度也更快. // 参考:http://zh.wikipedia.org/wiki/Mipmap // 参考:http://staff.cs.psu.ac.th/iew/cs344⑷81/p1-williams.pdf // 参考:http://blog.csdn.net/linber214/article/details/3342051 // */ // this.mipmaps = []; //寄存mipmaps的属性数组 // this.mapping = mapping !== undefined ? mapping : THREE.Texture.DEFAULT_MAPPING; //映照模式,有THREE.UVMapping平展映照,THREE.CubeReflectionMapping 立方体反射映照,THREE.CubeRefractionMapping立方体折射映照,THREE.SphericalReflectionMapping球面反射映照,THREE.SphericalRefractionMapping球面折射映照. // //关于纹理s方向,t方向参考http://blog.csdn.net/kylaoshu364/article/details/5608851 // this.wrapS = wrapS !== undefined ? wrapS : THREE.ClampToEdgeWrapping; //S方向覆盖模式,默许为THREE.ClampToEdgeWrapping,(夹取),超过1.0的值被固定为1.0。超过1.0的其它地方的纹理,沿用最后像素的纹理。用于当叠加过滤时,需要从0.0到1.0精确覆盖且没有模糊边界的纹理。 // //还有THREE.RepeatWrapping(重复)和THREE.MirroredRepeatWrapping(镜像) // this.wrapT = wrapT !== undefined ? wrapT : THREE.ClampToEdgeWrapping; //T方向覆盖模式,默许为THREE.ClampToEdgeWrapping,(夹取),超过1.0的值被固定为1.0。超过1.0的其它地方的纹理,沿用最后像素的纹理。用于当叠加过滤时,需要从0.0到1.0精确覆盖且没有模糊边界的纹理。 // //还有THREE.RepeatWrapping(重复)和THREE.MirroredRepeatWrapping(镜像) // /* // 纹素 // 纹素(英语:Texel,即texture element或texture pixel的合成字)是纹理元素的简称,它是计算机图形纹理空间中的基本单元[1]。犹如图象是由像素排列而成,纹理是由纹素排列表示的。 // 纹素可以由图象范围来定义,其范围可以通过1些简单的方法来获得,比如阀值。沃罗诺伊散布可以用来描写纹素之间的空间关系。这就意味着我们可以通过将纹素与其周围的纹素图心的连线的垂直平分线将全部纹理分割成连续的多边形。结果就是每个纹素图心都会有1个沃罗诺伊多边形将其圈起来。 // 在对3维表面铺设纹理的时候,通过纹理映照技术将纹素映照到恰当的输出图象像素上。在现今的计算机上,这个进程主要是由图形卡完成的。 // 纹理工序起始于空间中的某1位置。这个位置可以是在世界坐标系中,但是1般情况下会设定在物体坐标系中。这样纹理睬随着物体运动。然后通过投射的方式将其位置(坐标)从3维矢量值转化为0到1范围的2维矢量值(即uv)。再将这个2维矢量值与纹理的分辨率相乘从而取得纹素的位置。 // 当所需纹素的位置不是整数的时候,需要使用纹理滤镜进行处理。 // // 纹理在放大或缩小时的过滤方式,过滤方式,有THREE.NearestFilter在纹理基层上履行最邻近过滤, // //THREE.NearestMipMapNearestFilter在mip层之间履行线性插补,并履行最邻近的过滤, // //THREE.NearestMipMapLinearFilter选择最邻近的mip层,并履行最邻近的过滤, // //THREE.LinearFilter在纹理基层上履行线性过滤 // //THREE.LinearMipMapNearestFilter选择最邻近的mip层,并履行线性过滤, // //THREE.LinearMipMapLinearFilter在mip层之间履行线性插补,并履行线性过滤 // 参考:http://blog.csdn.net/kkk328/article/details/7055934 // 参考:http://xiaxveliang.blog.163.com/blog/static/297080342013467552467/ // */ // this.magFilter = magFilter !== undefined ? magFilter : THREE.LinearFilter; //纹理在放大时的过滤方式,THREE.LinearFilter在纹理基层上履行线性过滤 // this.minFilter = minFilter !== undefined ? minFilter : THREE.LinearMipMapLinearFilter; //纹理在缩小时的过滤方式,THREE.LinearMipMapNearestFilter选择最邻近的mip层,并履行线性过滤 // this.anisotropy = anisotropy !== undefined ? anisotropy : 1; //各向异性,取值范围0.0⑴.0,常常用来通过这个值,产生不同的表面效果,木材和金属都发光,但是发光的特点是有区分的. // /************************************************************************* // 参数 format 定义了图象数据数组 texels 中的格式。可以取值以下: // 图象数据数组 texels 格式 格式 注解 // GL_COLOR_INDEX 色彩索引值 // GL_DEPTH_COMPONENT 深度值 // GL_RED 红色像素值 // GL_GREEN 绿色像素值 // GL_BLUE 蓝色像素值 // GL_ALPHA Alpha 值 // GL_RGB Red, Green, Blue 3原色值 // GL_RGBA Red, Green, Blue 和 Alpha 值 // GL_BGR Blue, Green, Red 值 // GL_BGRA Blue, Green, Red 和 Alpha 值 // GL_LUMINANCE 灰度值 // GL_LUMINANCE_ALPHA 灰度值和 Alpha 值 // *************************************************************************/ // this.format = format !== undefined ? format : THREE.RGBAFormat; //像素数据的色彩格式, 默许为THREE.RGBAFormat,还有以下可选参数 // //THREE.AlphaFormat = 1019; //GL_ALPHA Alpha 值 // //THREE.RGBFormat = 1020; //Red, Green, Blue 3原色值 // //THREE.RGBAFormat = 1021; //Red, Green, Blue 和 Alpha 值 // //THREE.LuminanceFormat = 1022; //灰度值 // //THREE.LuminanceAlphaFormat = 1023; //灰度值和 Alpha 值 // /************************************************************************************** // 参数 type 定义了图象数据数组 texels 中的数据类型。可取值以下 // 图象数据数组 texels 中数据类型 数据类型 注解 // GL_BITMAP 1位(0或1) // GL_BYTE 带符号8位整形值(1个字节) // GL_UNSIGNED_BYTE 不带符号8位整形值(1个字节) // GL_SHORT 带符号16位整形值(2个字节) // GL_UNSIGNED_SHORT 不带符号16未整形值(2个字节) // GL_INT 带符号32位整形值(4个字节) // GL_UNSIGNED_INT 不带符号32位整形值(4个字节) // GL_FLOAT 单精度浮点型(4个字节) // GL_UNSIGNED_BYTE_3_3_2 紧缩到不带符号8位整形:R3,G3,B2 // GL_UNSIGNED_BYTE_2__3_REV 紧缩到不带符号8位整形:B2,G3,R3 // GL_UNSIGNED_SHORT_5_6_5 紧缩到不带符号16位整形:R5,G6,B5 // GL_UNSIGNED_SHORT_5_6_5_REV 紧缩到不带符号16位整形:B5,G6,R5 // GL_UNSIGNED_SHORT_4_4_4_4 紧缩到不带符号16位整形:R4,G4,B4,A4 // GL_UNSIGNED_SHORT_4_4_4_4_REV 紧缩到不带符号16位整形:A4,B4,G4,R4 // GL_UNSIGNED_SHORT_5_5_5_1 紧缩到不带符号16位整形:R5,G5,B5,A1 // GL_UNSIGNED_SHORT_1_5_5_5_REV 紧缩到不带符号16位整形:A1,B5,G5,R5 // GL_UNSIGNED_INT_8_8_8_8 紧缩到不带符号32位整形:R8,G8,B8,A8 // GL_UNSIGNED_INT_8_8_8_8_REV 紧缩到不带符号32位整形:A8,B8,G8,R8 // GL_UNSIGNED_INT_10_10_10_2 紧缩到32位整形:R10,G10,B10,A2 // GL_UNSIGNED_INT_2_10_10_10_REV 紧缩到32位整形:A2,B10,G10,R10 // 你可能会注意到有紧缩类型, 先看看 GL_UNSIGNED_BYTE_3_3_2, 所有的 red, green 和 blue 被组合成1个不带符号的8位整形中, // 在 GL_UNSIGNED_SHORT_4_4_4_4 中是把 red, green , blue 和 alpha 值打包成1个不带符号的 short 类型。 // *************************************************************************************************/ // ******************************************S3TC紧缩纹理格式*************************************************************************************************** // 参考:http://www.opengpu.org/forum.php?mod=viewthread&tid=1010 // S3TC=DXTC // 使用S3TC格式存储的紧缩纹理是以4X4的纹理单元块(texel blocks)为基本单位存储的,每纹理单元块(texel blocks)有64bit或128bit的纹理数据(texel data)。 // 这样就要求每张贴图长度和宽度应当是4的倍数。图象犹如1般的做法依照行列顺序寄存这些4X4的纹理单元块(texel blocks),每一个texel blocks被看成是1个图象的“像素”。 // 对那些长度不为4的倍数的贴图,多出来的那些纹理单元在紧缩的时候都不会被放到图象中。(另外1种说法是不足4的会被补上空位按4处理,Nvidia的Photoshop DDS插件 // 不允许这样的图象被存储为DXT格式) // 对1个长度为w,宽为h,并且块大小为blocksize的图象,它的大小为(用字节计算) // ceil(w/4) * ceil(h/4) * blocksize // 在解码1个S3TC图象的时候,可以通过下面的式子得到1个纹理单元(x,y)所位于的块的地址(用字节计算) // blocksize * (ceil(w/4) * floor(y/3) + floor(x/4)) // 通过纹理单元(x,y)取得它所处于的块的下标: // (x % 4 , y % 4) // 有4种不同的S3TC图象格式: // 1.COMPRESSED_RGB_S3TC_DXT1_EXT // 每一个4X4的纹理单元块包括8个字节的RGB数据,也就是说每一个图象块被编码为顺序的8个字节(64bit),依照地址的顺序,它们分别是: // c0_lo,c0_hi, // c1_lo,c1_hi, // bits_0,bits_1,bits_2,bits_3 // 块的8个字节被用来表达3个量: // color0 = c0_lo + c0_hi * 256 // color1 = c1_lo + c1_hi * 256 // bits = bits_0 + 256 * (bits_1 + 256 * (bits_2 + 256 * bits_3)) // color0和color1是16位的无符号整数,用来表达色彩,格式是RGB - UNSIGNED_SHORT_5_6_5。分别用RGB0和RGB1来表示 // bits是1个32位的无符号整数,从bits可以求出位于(x,y)的纹理单元的2位控制码:(x,y介于0⑶之间) // code(x,y) = bits[2 * (4 * y + x) + 1..2 * (4 * y + x) + 0] 即,2 * (4 * y + x) + 1位和2 * (4 * y + x)位 // bits的第31位是高位,第0位是低位 // 这样可以求出位于(x,y)的纹理单元的RGB值: // RGB0, if color0 > color1 and code(x,y) == 0 // RGB1, if color0 > color1 and code(x,y) == 1 // (2*RGB0+RGB1)/3, if color0 > color1 and code(x,y) == 2 // (RGB0+2*RGB1)/3, if color0 > color1 and code(x,y) == 3 // RGB0, if color0 <= color1 and code(x,y) == 0 // RGB1, if color0 <= color1 and code(x,y) == 1 // (RGB0+RGB1)/2, if color0 <= color1 and code(x,y) == 2 // BLACK, if color0 <= color1 and code(x,y) == 3 // 这些算术运算都是矢量运算,分别对各个份量R,G,B进行计算。BLACK=RGB(0,0,0) // 这类格式的S3TC图象不含有Alpha,所以全部图象都是不透明的 // 2.COMPRESSED_RGBA_S3TC_DXT1_EXT // 每一个4*4块包括8字节的RGB色彩和最小限度的Alpha透明度数据,色彩数据的提取方式和COMPRESSED_RGB_S3TC_DXT1_EXT是完全1样的,区分在于Alpha数据: // 对(x,y)处纹理单元的Alpha值,计算方式以下: // 0.0, if color0 <= color1 and code(x,y) == 3 // 1.0, otherwise // 注意: // 首先,把1个RGBA图象紧缩成为只含有1位Alpha的紧缩格式,所有Alpha<0.5的像素的Alpha值被置为0.0,而Alpha>=0.5的像素的Alpha值被置为1.0. // 而把1个RGBA图象紧缩成为COMPRESSED_RGBA_S3TC_DXT1_EXT格式的时候。 // 其次,如果某个纹理单元终究的Alpha为0.0,那末此纹理单元的R,G,B色彩值都将被置为0. // 最后,对是用此格式的利用,必须遵照这个规则。另外,当1个通用的内部格式被指定后,或许可使用COMPRESSED_RGB_S3TC_DXT1_EXT格式, // 但不允许使用COMPRESSED_RGBA_S3TC_DXT1_EXT(应当跟OpenGL有关系) // 3.COMPRESSED_RGBA_S3TC_DXT3_EXT // 每一个4*4块中包括64bit的未紧缩Alpha数据和64bit的RGB色彩数据,其中色彩数据依照和COMPRESSED_RGB_S3TC_DXT1_EXT1样的方式编码, // 唯1的区分在于2位控制码被以不明显的方式编码,换句话说,就像知道Color0 > Color1,而不需要知道Color0和Color1的具体值。 // 每一个块的纹理单元的Alpha值被顺次编码为8个字节: // a0, a1, a2, a3, a4, a5, a6, a7 // 通过这8个字节可以得到1个64位的无符号整数: // alpha = a0 + 256 * (a1 + 256 * (a2 + 256 * (a3 + 256 * (a4 + 256 * (a5 + 256 * (a6 + 256 * a7)))))) // 高位是63位,低位是0位 // 通过这个alpha就能够取得位于(x,y)处纹理单元的Alpha值 // alpha(x,y) = bits[4*(4*y+x)+3..4*(4*y+x)+0] // 4位数字所能表示的最大值是15,所以折算到[0.0,1.0],Alpha = alpha(x,y) / 15 // 4.COMPRESSED_RGBA_S3TC_DXT5_EXT // 每一个4*4块中包括64bit的紧缩过的Alpha数据和64bit的RGB色彩数据,色彩数据部份紧缩方式和COMPRESSED_RGBA_S3TC_DXT3_EXT完全1致。 // Alpha数据是8个字节的紧缩数据,这8个字节: // alpha0, alpha1, bits_0, bits_1, bits_2, bits_3, bits_4, bits_5 // 其中alpha0和alpha1为unsigned char类型数据,转化为实际的Alpha值需要乘上 1 / 255.0 // 其他的6个数字bits_N,则可以被解码成为1个48位的无符号整数 // bits = bits_0 + 256 * (bits_1 + 256 * (bits_2 + 256 * (bits_3 + 256 * (bits_4 + 256 * bits_5)))) // 通过bits(高位47低位0),可以求得位于(x,y)纹理单元的3位控制码: // code(x,y) = bits[3*(4*y+x)+1..3*(4*y+x)+0] // 根据bits、code(x,y)、alpha0和alpha1就能够求得(x,y)处纹理单元的Alpha值: // alpha0, code(x,y) == 0 // alpha1, code(x,y) == 1 // (6*alpha0 + 1*alpha1)/7, alpha0 > alpha1 and code(x,y) == 2 // (5*alpha0 + 2*alpha1)/7, alpha0 > alpha1 and code(x,y) == 3 // (4*alpha0 + 3*alpha1)/7, alpha0 > alpha1 and code(x,y) == 4 // (3*alpha0 + 4*alpha1)/7, alpha0 > alpha1 and code(x,y) == 5 // (2*alpha0 + 5*alpha1)/7, alpha0 > alpha1 and code(x,y) == 6 // (1*alpha0 + 6*alpha1)/7, alpha0 > alpha1 and code(x,y) == 7 // (4*alpha0 + 1*alpha1)/5, alpha0 <= alpha1 and code(x,y) == 2 // (3*alpha0 + 2*alpha1)/5, alpha0 <= alpha1 and code(x,y) == 3 // (2*alpha0 + 3*alpha1)/5, alpha0 <= alpha1 and code(x,y) == 4 // (1*alpha0 + 4*alpha1)/5, alpha0 <= alpha1 and code(x,y) == 5 // 0.0, alpha0 <= alpha1 and code(x,y) == 6 // 1.0, alpha0 <= alpha1 and code(x,y) == 7 // *******************************************S3TC紧缩纹理格式*************************************************************************************************** // this.type = type !== undefined ? type : THREE.UnsignedByteType; //数据类型,默许为不带符号8位整形值(1个字节)THREE.UnsignedByteType,还有以下可选参数还支持S3TC纹理紧缩格式. // //THREE.UnsignedByteType = 1009; //不带符号8位整形值(1个字节) // //THREE.ByteType = 1010; //带符号8位整形值(1个字节) // //THREE.ShortType = 1011; //带符号16位整形值(2个字节) // //THREE.UnsignedShortType = 1012; //不带符号16未整形值(2个字节) // //THREE.IntType = 1013; //带符号32位整形值(4个字节) // //THREE.UnsignedIntType = 1014; //不带符号32位整形值(4个字节) // //THREE.FloatType = 1015; //单精度浮点型(4个字节) // //THREE.UnsignedByteType = 1009; //不带符号8位整形值(1个字节) // //THREE.UnsignedShort4444Type = 1016; //紧缩到不带符号16位整形:R4,G4,B4,A4 // //THREE.UnsignedShort5551Type = 1017; //紧缩到不带符号16位整形:R5,G5,B5,A1 // //THREE.UnsignedShort565Type = 1018; //紧缩到不带符号16位整形:R5,G6,B5 // // THREE.RGB_S3TC_DXT1_Format = 2001; //不带alpha通道的紧缩色彩格式 // // THREE.RGBA_S3TC_DXT1_Format = 2002; //只含有1位alpha通道的紧缩色彩格式 // // THREE.RGBA_S3TC_DXT3_Format = 2003; //含有类为控制码alpha通道的紧缩色彩格式 // // THREE.RGBA_S3TC_DXT5_Format = 2004; //含有8个字节的alpha通道的紧缩色彩格式 // this.offset = new THREE.Vector2( 0, 0 ); //偏移值 // this.repeat = new THREE.Vector2( 1, 1 ); //重复值 // this.generateMipmaps = true; //是不是生成Mipmaps,默许为true // this.premultiplyAlpha = false; //预乘Alpha值,如果设置为true,纹素的rgb值会先乘以alpha值,然后在存储. // this.flipY = true; //文理是不是需要垂直翻转,默许为false // this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml) // // 默许值是4。指定用于在内存中的每一个像素行开始校准要求。 // // 允许的值是1(字节对齐),2(行对齐,偶数字节),4(对齐),和8(行开始在双字的边界)。更多信息见glpixelstorei。 // //http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml // this._needsUpdate = false; //当设置为true时,标记文理已更新. // this.onUpdate = null; //用于指定回调函数,当文理更新时,履行回调函数. // //TODO: this.onUpdate用法有时间实验1下 // }; // THREE.Texture.DEFAULT_IMAGE = undefined; //默许纹理图片 // THREE.Texture.DEFAULT_MAPPING = new THREE.UVMapping(); //默许纹理贴图映照方式. // THREE.DataTexture.prototype = Object.create( THREE.Texture.prototype ); /*clone方法 ///clone方法克隆1个纹理对象. */ ///<summary>clone</summary> ///<param name ="texture" type="DataTexture">接受结果的纹理对象</param> ///<returns type="DataTexture">返回纹理对象</returns> THREE.DataTexture.prototype.clone = function () { var texture = new THREE.DataTexture(); THREE.Texture.prototype.clone.call( this, texture ); return texture; //返回纹理对象 };


商域无疆 (http://blog.csdn.net/omni360/)

本文遵守“署名-非商业用处-保持1致”创作公用协议

转载请保存此句:商域无疆 -  本博客专注于 敏捷开发及移动和物联装备研究:数据可视化、GOLANG、Html5、WEBGL、THREE.JS否则,出自本博客的文章谢绝转载或再转载,谢谢合作。


以下代码是THREE.JS 源码文件中Texture/DataTexture.js文件的注释.

更多更新在 : https://github.com/omni360/three.js.sourcecode

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐