记录:纹理映射(一)
1. 二维纹理映射:
① 整体流程:
(1)指定纹理坐标:为几何体的每个顶点绑定二维UV坐标,告诉GPU“纹理图像的哪个像素对应顶点颜色”;
1. 顶点与纹理的映射关系:纹理坐标是二维向量,范围通常为[0,1],对应纹理图像的左上角(0,0)到右下角(1,1),每个顶点对应一个纹理坐标,实现“顶点->纹理像素”的映射。
2. geom->setTexCoordArray(0, 纹理坐标数组):将纹理坐标数组关联到几何体的纹理单元0【OSG不支持"非多重纹理",必须指定纹理单元(单个纹理用0即可),这是和早期OpenGL的重要区别】。
(2)加载纹理数据:将图像文件加载到内存,关联到纹理属性对象,准备上传到GPU;
// 读取图像
osg::ref_ptr image = new osg::Image;
image->setFileName("tree.rgb");
// 将图像关联到Texture2D对象
osg::ref_ptr tex = new osg::Texture2D;
tex->setImage(image.get());
// 优化内存:创建GPU纹理后释放CPU端的Image
tex->setUnRefImageDataAfterApply(true);
1. setFileName,指定图像文件路径,OSG会通过数据库插件自动加载图像数据到内存;
2. osg::Image是OSG管理图像像素数据的核心类,支持加载RGB、PNG等多种格式的图像文件;
3. osg::Texture2D继承自osg::StateAttribute,是OSG管理OpenGL二维纹理的核心类,它会在第一次渲染时,将image的像素数据上传到GPU,创建OpenGL纹理对象;
4. tex->setImage(image.get()),将CPU端的Image像素数据关联到GPU端的Texture2D对象,建立“CPU图像->GPU纹理”的映射;
5. tex->setUnRefImageDataAfterApply(true);在OpenGL纹理创建完成后,释放Texture2D对Image的引用,减少CPU端内存占用(GPU已经保存了纹理数据,如果CPU端的Image不再需要使用该纹理就可以调用该函数。)
(3)将纹理属性关联到StateSet,开启纹理模式,让GPU正确应用纹理。
1. state->setTextureAttribute(0, tex.get());:将纹理属性tex关联到StateSet的纹理单元0;
2. OSG在渲染时:先把StateSet中纹理单元0的Texture2D上传到GPU的纹理单元0;再读取geom中纹理单元0的UV坐标,从GPU纹理单元0中采样颜色;
2. 多重纹理采样
① 特点:允许同时将多张纹理图像按顺序叠加到同一个多边形上,是“多通道”纹理映射,每个纹理对应一个“纹理单元”,前一个纹理的输出作为后一个纹理的输入,最终合成复杂效果;
② 关键原理:
GPU内部有多个独立的“纹理单元”(如0、1、2......),每个单元负责处理一张纹理的采样与计算,多重纹理的流程是:纹理单元0采样第一张纹理,输出颜色,纹理单元1以单元0的输出作为输入,采样第二张纹理并叠加,后续纹理单元依次处理,直到所有单元完成,最终输出叠加后的颜色,作为最终的像素颜色。
③ 注意事项:
(1)纹理单元与坐标必须一一对应:如果单元号不匹配,GPU无法正确采样纹理,导致纹理失效或覆盖;
(2)不同纹理单元不能复用纹理对象:每个纹理单元必须绑定独立的osg::Texture2D对象,否则后绑定的纹理会覆盖前一个,导致多重纹理叠加失效;
【技术上,同一个Texture2D对象绑定多个单元,意味着所有单元采样同一张图,失去了“多重”的意义,关键点应该是:每个纹理单元需要关联独立的纹理图像数据。】
(3)纹理模式需显式开启:每个纹理单元对应的GL_TEXTURE_2D模式需显示开启(通过setTextureAttributeAndModels的ON参数),否则纹理单元不会被激活;
(4)注意纹理叠加顺序:根据效果需求调整顺序。
3. MipMap纹理映射
① 背景:在动态场景(如飞行模拟器、游戏)中,当纹理快速远离相机时,会出现两个严重问题:
(1)纹理闪烁/抖动:高分辨率纹理被急剧缩小到单个像素时,相邻像素的颜色差异会导致采样时出现明显的颜色跳变,画面产生闪烁;
【颜色跳变本质:高分辨率纹理缩到屏幕单个像素时,GPU需要从多个纹理像素中选一个作为屏幕像素的颜色,由于相邻帧采样的纹理像素颜色差异大,屏幕像素的颜色就会快速跳变,视觉上就是闪烁。Mipmap的解决逻辑:预先生成小尺寸的模糊版纹理,当物体远处时,GPU直接采样这个纹理的单个像素,这个纹理是平滑模糊的,相邻帧采样的颜色差异极小,就不会跳变了。】
(2)渲染效率低下:远处的物体只需要低分辨率的纹理细节,但GPU仍在采样高分辨率纹理,浪费显存带宽和计算资源。
② 原理:预先生成一系列分辨率递减、尺寸为2的幂次的纹理图像集合(称为MipMap层级),从原始分辨率(层级0)到1*1(层级N),每个层级的尺寸是上一层的1/2(宽度和高度都减半,面积为上一层的1/4),当物体远离相机时,GPU会自动选择最匹配的MipMap层级。
③ Mipmap使用setGenerateMipmap告诉OSG/OpenGL自动生成全部的Mipmap层级,只需要提供原始图(第0层)。
④ 注意事项:
(1)纹理尺寸必须是2的幂次:因为每次层级减半后必须是整数,建议:提前将纹理编辑为2的幂次尺寸,避免OSG自动缩放导致的性能损失【自动缩放到2次幂的函数:tex->setResizeNonPowerOfTwoHint(true);】;
(2)层级数据的顺序必须正确:必须按“层级从大到小的顺序存放层级数据(层级0是最高分辨率)”,同时偏移地址的记录必须准确,否则GPU会读取错误的像素数据,导致纹理显示异常;
(3)OSG函数调用顺序不能颠倒:必须先调用setImage设置像素数据,再调用setMipmapLevels设置层级偏移,如果顺序颠倒,osg::Image会因为没有像素数据而无法关联层级偏移,导致Mipmap失效;
(4)内存与性能的权衡:Mipmap会增加纹理的内存占用(总内存是原始纹理的4/3,因为各层面积之和是W*H+W*H/4+W*H/16+...=W*H*(4/3)),但提升的视觉质量和渲染效率通常远大于内存增加的代价,尤其是在动态场景中。
(5)配合合适的过滤模式:Mipmap需要配合过滤模式才能实现平滑过渡,在OSG中通过osg::Texture2D::setFilter设置,其中MIN_FILTER是纹理缩小(远处)时的过滤模式,MAG_FILTER是纹理放大(近处)时的过滤模式。
4. TextureRectangle纹理映射
① TextureRectangle是二维纹理类型,本质是“无单位化坐标的矩形纹理”,它和Texture2D同属二维纹理,但设计目标完全不同:
(1)Texture2D是为了高效的纹理采样、重复平铺、多级渐远(Mipmap)而设计,适合游戏、3D建模等场景;
(2)TextureRectangle是为了像素级精确访问、任意分辨率输入而设计,适合视频处理、相机图像采集等场景。
② 两个二维纹理的核心区别:
(1)纹理坐标:单位化vs像素级
1. Texture2D:纹理坐标必须单位化(范围0~1),超出范围的坐标按照环绕方式来处理,例如纹理大小为1024*512时,坐标(0.5,0.5)对应纹理中心(512*256位置);
2. TextureRectangle:纹理坐标不需要单位化,直接使用像素尺寸作为坐标范围,例如纹理大小为513*1025时,坐标(256,512)直接对应纹理的第256列,第512行像素,TextureRectangle无需将坐标除以纹理宽高转为0~1,更直观易用;
(2)纹理大小:2的幂vs任意尺寸
1. Texture2D:传统要求是2的n次方,仅当显卡支持ARB_non_power_of_two或OpenGL2.0后,才支持非2的幂尺寸;
2. TextureRectangle:纹理大小完全任意,直接处理相机原始输入。
③ 使用限制:
(1)纹理环绕方式:仅支持CLAMP、CLAMP_TO_EDGE或CLAMP_TO_BORDER,不支持REPEAT,原因:TextureRectangle的坐标是像素级的,重复平铺没有实际意义;
【REPEAT模式:当纹理坐标超出0~1范围时,仅保留坐标的小数部分作为采样坐标,整数部分对应纹理重复的次数。】
(2)纹理滤波:仅支持NEAREST或LINEAR,不支持Mipmap,原因:Mipmap需要生成不同分辨率的纹理层级,而TextureRectangle面向“原始分辨率的像素级访问”,Mipmap的降采样会破坏像素精度;
(3)不支持纹理边框:无法设置纹理边框,原因:TextureRectangle聚焦于“精确的像素级访问”,边框功能不符合其设计目标。
④ 注:TextureRectangle是OpenGL早期为了解决NPOT(任意尺寸)纹理和像素精确访问而引入的临时扩展,自OpenGL2.0引入ARB_non_power_of_two对NPOT纹理的核心支持后,Texture2D已经胜任所有工作,且功能更全面(支持Mipmap等),因此,在新项目中,更多的是使用Texture2D和setTesizeNonPowerOfTwoHint(false)。
5. osg::TexGen自动生成纹理坐标
① 原因:手动为每个顶点指定纹理坐标是常见的方式,但有些场景下手动指定效率低下或无法实现:
(1)动态效果:比如物体移动时的轮廓线、光泽反射,纹理坐标需要随视角/物体位置动态变化;
(2)复杂映射:比如球面环境反射、立方图纹理,手动计算纹理坐标非常复杂;
(3)简化开发:对于简单的平面映射、轮廓线效果,自动生成代码可以减少代码量,避免重复劳动。
② 不同的生成纹理坐标的方法有不同的用途,几种自动生成纹理坐标的模式:
(1)OBJECT_LINEAR(物体空间线性映射)
1. 原理:基于【物体局部坐标系】生成纹理坐标,纹理坐标与物体的位置、旋转无关,始终与物体保持相对固定;
2. 适用场景:当纹理需要【贴在物体表面,随物体移动而移动】时使用;
3. 关键参数:需要指定【参考平面】(通过setPlane设置),纹理坐标是顶点在该平面的投影值。
(2)EYE_LINEAR(视觉空间线性映射)
1. 原理:基于相机(视角)坐标系生成纹理坐标,纹理坐标随相机视角变化而动态更新。
2. 适用场景:需要【随视角变化的动态效果】时使用。
(3)SPHERE_MAP(球面环境贴图)
1. 原理:自动生成球面映射的纹理坐标,模拟物体反射周围球面环境的效果(如金属球反射天空、周围物体);
2. 适用场景:需要【球面环境反射】的场景。
(4)NORMAL_MAP(立方图纹理映射)
1. 原理:基于顶点法线生成立方图纹理坐标,用于立方图环境映射(如天空盒、全景环境反射);
2. 适用场景:需要【全景环境反射】的场景,例如:室内场景的墙面反射窗外环境。
(5)REFLECTION_MAP(球面环境纹理映射)
1. 原理:与SPHERE_MAP类似,也是生成球面反射的纹理坐标,但精度更高,支持更复杂的环境反射计算;
2. 使用场景:需要【高精度球面反射】的场景,例如:珠宝、金属制品的逼真反光效果。
③ TexGen对每个顶点做的事:
(1)取顶点在物体局部坐标系中的(x,y,z,w)值;
(2)按平面公式计算S=z,T=y得到纹理坐标;
(3)把(S,T)传给GPU用于纹理采样。
texGen->setMode(osg::TexGen::OBJECT_LINEAR);
texGen->setPlane(osg::TexGen::S, osg::Plane(0.f, 0.f, 1.f, 0.f)); // S = 0*x + 0*y + 1*z + 0*w = z
texGen->setPlane(osg::TexGen::T, osg::Plane(0.f, 1.f, 0.f, 0.f)); // T = 0*x + 1*y + 0*z + 0*w = y
【注:持续优化中......】







