OSG编程指南<十四>:OSG纹理渲染之普通纹理、多重纹理、Mipmap多级渐远纹理及TextureRectangle矩阵纹理

1、纹理映射介绍

  物体的外观不仅包括形状,不同物体表面有着不同的颜色和图案。一个简单而有效地实现这种特性的方法就是使用纹理映射。在三维图形中,纹理映射(Texture Mapping)的方法运用广泛,使用该技术可以大大提高物体的真实感。

  OSG 是对底层 OpenGL API 的封装,OpenGL 本身有非常标准而高效的纹理机制。OSG 全面支持OpenGL 的纹理映射机制,因此,在 OSG 中使用纹理映射机制非常简单。纹理映射主要包括一维纹理、二维纹理、三维纹理、凸凹纹理、多重纹理、Mipmap 纹理、压缩纹理和立方纹理等。本文主要针对一维纹理、二维纹理、三维纹理、多重纹理、Mipmap 纹理和立方纹理等经常使用的几种纹理加以解释说明。

下面讲解一些纹理的基础知识,这些对于熟悉 OpenGL 的朋友来说,应该都是再基础不过了。

(1)纹理坐标:

enum WrapParameter
{
WRAP_S, //x 轴
WRAP_T, //y 轴
WRAP_R //z 轴
};

(2)纹理的包装模式:

enum WrapMode
{
CLAMP = GL_CLAMP, //截取
CLAMP_TO_EDGE = GL_CLAMP_TO_EDGE,//边框始终被忽略
CLAMP_TO_BORDER = GL_CLAMP_TO_BORDER_ARB, //它使用的纹理取自图像的边框,没有边框就使用
常量边框颜色
REPEAT = GL_REPEAT, //纹理的重复映射
MIRROR = GL_MIRRORED_REPEAT_IBM //纹理镜像的重复映射
};

(3)纹理过滤方法:

enum FilterParameter
{
MIN_FILTER, //用于缩小
MAG_FILTER //用于放大
};

(4)纹理的过滤处理:

enum FilterMode
{
LINEAR= GL_LINEAR, //以周围 4 个像素的平均值作为纹理
LINEAR_MIPMAP_LINEAR= GL_LINEAR_MIPMAP_LINEAR,//使用线性均和计算两个纹理的值
LINEAR_MIPMAP_NEAREST= GL_LINEAR_MIPMAP_NEAREST,//线性地改写临近的纹理单元值
NEAREST= GL_NEAREST, //取比较接近的像素作为纹理
NEAREST_MIPMAP_LINEAR= GL_NEAREST_MIPMAP_LINEAR,//在两个纹理中选择最临近的纹理,并取它
们之间的线性均和值
NEAREST_MIPMAP_NEAREST= GL_NEAREST_MIPMAP_NEAREST //选择最临近的纹理单元值
};

(5)纹理映射模式(处理纹理图像数据与物体本身的融合):

enum Mode
{
DECAL= GL_DECAL, //贴花
MODULATE= GL_MODULATE, //调整
BLEND= GL_BLEND, //混合
REPLACE= GL_REPLACE, //替换,覆盖
ADD= GL_ADD //添加
};

(6)纹理坐标的自动生成模式:

enum Mode
{
OBJECT_LINEAR= GL_OBJECT_LINEAR,//物体线性,纹理贴图与移动物体保持固定
EYE_LINEAR= GL_EYE_LINEAR,//产生移动物体的动态轮廓线
SPHERE_MAP= GL_SPHERE_MAP,//球体贴图
NORMAL_MAP= GL_NORMAL_MAP_ARB, //法线贴图,用于立方图纹理
REFLECTION_MAP = GL_REFLECTION_MAP_ARB//反射贴图
};

(7)贴图坐标:

enum Coord
{
S, //x
T, //y
R, //z
Q //w
};

(8)纹理的内部格式:

enum InternalFormatMode
{
USE_IMAGE_DATA_FORMAT, //使用贴图本身的格式
USE_USER_DEFINED_FORMAT,//使用用户自定义的格式,如 GL_R3G3B3 等格式
USE_ARB_COMPRESSION, //使用 ARB 协会出的贴图压缩格式
USE_S3TC_DXT1_COMPRESSION,//使用 S3TC_DXT1 压缩格式
USE_S3TC_DXT3_COMPRESSION,//使用 S3TC_DXT3 压缩格式
USE_S3TC_DXT5_COMPRESSION //使用 S3TC_DXT5 压缩格式
};

2、二维纹理

2.1 纹理坐标和纹理数据

  在所有的纹理映射中,二维纹理映射的过程最简单,也非常容易理解。在用户应用程序中创建二维纹理的步骤如下:

(1)指定用户几何体的纹理坐标。
(2)创建纹理属性对象并保存纹理图形数据。
(3)为 StateSet 设置合适的纹理属性和模式。

1.纹理坐标
  在前面几何体的绘制时已经提到用一个二维的向量数据来保存纹理坐标。设置纹理坐标比较简单,纹理坐标是与顶点一一对应的,很像数学中的映射。
  下面的代码段创建了一个 osg::Vec2Array 数组,用于保存纹理坐标,同时将其关联到 Geometry 实例的纹理单元 0。如果要对单一的 Geometry 设置多个纹理,只需要将多个纹理坐标数组关联到Geometry,并针对不同的数组指定不同的纹理单元即可。

//创建一个 Geometry 几何体对象
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
//创建一个 Vec2Array 对象以保存纹理单元 0 的纹理坐标,并将其关联到 geom
osg::ref_ptr<osg::Vec2Array> tc = new osg::Vec2Array;
geom->setTexCoordArray( 0, tc.get());
tc->push_back(osg::Vec2( 0.f, 0.f));
tc->push_back(osg::Vec2( 1.f, 0.f));
tc->push_back(osg::Vec2( 1.f, 1.f));
tc->push_back(osg::Vec2( 0.f, 1.f));

  osg::Geometry::setTexCoordArray()的第一个参数是纹理单元号,第二个参数是纹理坐标数组。用户不需要使用类同 osg::Geometry::setTexCoordBinding()的函数输入点来绑定纹理数据。纹理坐标总是绑定到每个顶点的。在这里有一点要注意,OpenGL 的早期版本并不支持多重纹理,而加入多重纹理的特性之后,OpenGL 仍然支持非多重纹理的函数接口,以实现向下兼容。从本质上说,此时 OpenGL 将非多重纹理接口解释为使用纹理单元 0 对应所有纹理数据。与 OpenGL 不同,OSG 并不支持非多重纹理接口。因此,用户程序必须指定一个纹理单元,以对应纹理坐标数据和纹理状态。如果要使用单一纹理,只需要指定到纹理单元 0 即可。

2.纹理数据
  在大多数应用程序中,纹理数据都是从外部导入的图像文件。当没有必要导入图像时,可以生成一幅纹理数据贴图。这里使用从外部导入的纹理数据图形的方法。读取图像需要使用一个新类——osg::Image。osg::Image 继承自 osg::Object 类。面的代码将实现如何读取图像:

osg::ref_ptr<osg::Image> image = new osg::Image;
image->setFileName( "tree.rgb" );

  在读取一个图像后,需要创建一个纹理对象来关联图像。osg::Texture2D 属于 osg::StateAttribute 的派生类,用于管理 OpenGL 纹理对象,而 Image 用于管理图像像素数据。如果要使用 2D 图像文件作为纹理映射的图形,只要将文件名赋给 Image 对象并将 Image 关联到 Texture2D 即可。osg::Texture2D 继承自 osg::Texture。下面的代码将实现将图像关联到 2D 纹理对象上:

//将图像关联到 Texture2D 对象
osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
tex->setImage( image.get() );

  在关联图像以后,可以直接关联到渲染状态。值得注意的是:大量使用纹理贴图的程序往往需要实现更紧凑的内存管理。Image 类继承自 Referenced 类,而 Texture2D 内部保存了一个指向 Image 的ref_ptr<>指针。在第一次渲染时,OSG 创建了用于保存图像数据的 OpenGL 纹理对象,其结果是产生了两个纹理图像的副本,一个是 Image 对象,另一个由 OpenGL 拥有。简单的单环境(single-context)场景渲染中,读者可以通过设置 Texture2D 解除对 Image 的引用来降低内存损耗。如果当前引用 Image对象的只有 Texture2D 对象,那么 OSG 将释放 Image 及其内存空间。下面的代码演示了设置 Texture2D解除对 Image 引用的方法:

//创建 OpenGL 纹理对象后,释放内部的 ref_ptr<Image>,删除 Image 图像
tex->setUnRefImageDataAfterApply( true );

  默认情况下,Texture2D 不会自动释放对 Image 的引用。在多环境(multi-context)场景渲染中,这是一种期望行为,前提是纹理对象并没有在各环境中共享。

3.纹理状态
  用户程序可以使用纹理状态函数接口为每个纹理单元指定渲染状态。纹理状态函数接口与非纹理状态的接口类似。用户可以使用 osg::StateSet::setTextureAttribute()将一个纹理属性关联到 StateSet 对象。setTextureAttribute()的第一个参数是纹理单元,第二个参数是继承自 StateAttribute 类的一种纹理属性。

  合法的纹理属性类共有 6 种,其中包括 5 种纹理类型(osg::Texture1D、osg::Texture2D、osg::Texture3D、
osg::TextureCubeMap 和 osg::TextureRectangle)和一个用于纹理坐标的生成的类(osg::TexGen)。

  下面的代码将根据给定的 Texture2D 属性对象 tex 和渲染状态 StateSet 将 tex 关联到渲染状态,并设置使用纹理单元 0。

//创建一个 Texture2D 属性
Osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
//关联材质属性到材质单元 0
state->setTextureAttribute( 0, tex.get() );

  与上面的程序类似,用户可以调用 osg::StateSet::setTextureMode()方法来设置材质渲染模式,这个方法与 setMode()方法类似。用户可以使用 setTextureMode()来设置 GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3DGL_TEXTURE_CUBE_MAP、GL_TEXTURE_RECTANGLE、GL_TEXTURE_GEN_Q、GL_TEXTURE_GEN_R、GL_TEXTURE_GEN_S 以及 GL_TEXTURE_GEN_T 模式。

  与 setTextureAttribute()相似,setTextureMode()的第一个参数表示纹理单元。下面的代码段将禁止纹理单元 1 的 2D 纹理映射:

state->setTextureMode( 1, GL_TEXTURE_2D, osg::StateAttribute::OFF );

  当然,用户也可以使用 osg::StateSet::setTextureAttributesAndModes()来关联纹理渲染属性到StateSet,同时允许相应的纹理模式。如果属性是一个 TexGen 对象,那么 setTextureAttributesAndModes()将设置相应的坐标生成模式 GL_TEXTURE_GEN_Q、GL_TEXTURE_GEN_R、GL_TEXTURE_GEN_S和 GL_TEXTURE_GEN_T。对于其他纹理属性来说,这一模式是隐含的。例如,下面的代码中,由于第二个参数传入了一个 Texture2D 对象作为纹理属性,setTextureAttributesAndModes()将允许GL_TEXTURE_2D 模式:

//创建一个 Texture2D 属性对象
osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
//在纹理单元 0 上,关联 2D 纹理属性并许可 GL_TEXTURE_2D 模式
state->setTextureAttributeAndModes( 0, tex );

  setTextureAttributeAndModes() 的 第 三 个 参 数 的 默 认 值 为 ON , 即 允 许 纹 理 渲 染 模 式 。 与
setAttributeAndModes()类似,读者可以对这个参数使用位或操作包括 OVERRIDE、PROTECTED 和INHERIT,以修改纹理属性的继承特性。读者还可以通过修改 setTextureMode()和 setTextureAttribute()的第三个参数来指定这个继承标志。

2.2 示例效果

2.2.1 普通示例

  备注:osg默认会被图片大小非2的n次方的图片进行缩放处理。后续可与矩阵纹理进行对比

在这里插入图片描述

// TestOSGProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。#include <windows.h>
#include <osgViewer/Viewer>#include <osg/Node>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Geometry>
#include <osg/Image>
#include <osg/TexGen>
#include <osg/Texture1D>
#include <osg/TexEnv>
#include <osg/StateSet>
#include <osg/PrimitiveSet>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgViewer/ViewerEventHandlers> //事件监听
#include <osgGA/StateSetManipulator> //事件响应类,对渲染状态进行控制
#include <osgUtil/Simplifier> //简化几何体#include <osgUtil/Optimizer>#pragma comment(lib, "OpenThreadsd.lib")
#pragma comment(lib, "osgd.lib")
#pragma comment(lib, "osgDBd.lib")
#pragma comment(lib, "osgUtild.lib")
#pragma comment(lib, "osgGAd.lib")
#pragma comment(lib, "osgViewerd.lib")
#pragma comment(lib, "osgTextd.lib")//创建一个四边形节点
osg::ref_ptr<osg::Node> createNode()
{osg::ref_ptr<osg::Geode> geode = new osg::Geode();osg::ref_ptr<osg::Geometry> geom = new osg::Geometry();//设置顶点osg::ref_ptr<osg::Vec3Array> vc = new osg::Vec3Array();vc->push_back(osg::Vec3(0.0f, 0.0f, 0.0f));vc->push_back(osg::Vec3(1.0f, 0.0f, 0.0f));vc->push_back(osg::Vec3(1.0f, 0.0f, 1.0f));vc->push_back(osg::Vec3(0.0f, 0.0f, 1.0f));geom->setVertexArray(vc.get());//设置纹理坐标osg::ref_ptr<osg::Vec2Array> vt = new osg::Vec2Array();vt->push_back(osg::Vec2(0.0f, 0.0f));vt->push_back(osg::Vec2(1.0f, 0.0f));vt->push_back(osg::Vec2(1.0f, 1.0f));vt->push_back(osg::Vec2(0.0f, 1.0f));geom->setTexCoordArray(0, vt.get());//设置法线osg::ref_ptr<osg::Vec3Array> nc = new osg::Vec3Array();nc->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));geom->setNormalArray(nc.get());geom->setNormalBinding(osg::Geometry::BIND_OVERALL);//添加图元geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));//绘制geode->addDrawable(geom.get());return geode.get();
}//创建二维纹理状态对象
osg::ref_ptr<osg::StateSet> createTexture2DState(osg::ref_ptr<osg::Image> image)
{//创建状态集对象osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();//创建二维纹理对象osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D();texture->setDataVariance(osg::Object::DYNAMIC);//设置贴图texture->setImage(image.get());stateset->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON);return stateset.get();
}int main()
{osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();osg::ref_ptr<osg::Group> root = new osg::Group();//读取贴图文件osg::ref_ptr<osg::Image> image = osgDB::readImageFile("Images/primitives.gif");osg::ref_ptr<osg::Node> node = createNode();//创建状态集对象osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();stateset = createTexture2DState(image.get());//使用二维纹理node->setStateSet(stateset.get());root->addChild(node.get());//优化场景数据osgUtil::Optimizer optimizer;optimizer.optimize(root.get());//方便查看在多边形之间切换,以查看三角网viewer->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));viewer->addEventHandler(new osgViewer::StatsHandler());viewer->addEventHandler(new osgViewer::WindowSizeHandler());viewer->setSceneData(root.get());viewer->setUpViewInWindow(600, 600, 1000, 800);return viewer->run();
}
2.2.2 光照和混合模式示例

在这里插入图片描述

在这里插入图片描述

//创建二维纹理状态对象
osg::ref_ptr<osg::StateSet> createTexture2DState(osg::ref_ptr<osg::Image> image)
{//创建状态集对象osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();//创建二维纹理对象osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D();texture->setDataVariance(osg::Object::DYNAMIC);//设置贴图texture->setImage(image.get());//关联Texture2D纹理对象,第三个参数默认为ONstateset->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON);//启用混合stateset->setMode(GL_BLEND, osg::StateAttribute::ON);//关闭光照stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON);return stateset.get();
}
2.2.3 光照关闭示例

在这里插入图片描述

//创建二维纹理状态对象
osg::ref_ptr<osg::StateSet> createTexture2DState(osg::ref_ptr<osg::Image> image)
{//创建状态集对象osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();//创建二维纹理对象osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D();texture->setDataVariance(osg::Object::DYNAMIC);//设置贴图texture->setImage(image.get());//关联Texture2D纹理对象,第三个参数默认为ONstateset->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON);//启用混合stateset->setMode(GL_BLEND, osg::StateAttribute::ON);//关闭光照stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF);return stateset.get();
}

3、多重纹理

  在进行标准的二维纹理映射处理时,一次把一幅纹理图像应用到一个多边形上。多重纹理允许应用几个纹理,在纹理操作管线中把它们逐个应用到同一个多边形上。多重纹理存在一系列的纹理单元,每个纹理单元执行单独的纹理操作,并把它的结果传递给下一个纹理单元,直到所有纹理单元的操作完成为止,最终显示处理后的效果。

  多重纹理映射非常广泛,它能实现一些高级的渲染技巧,如光照、贴花、合成和细节纹理等。在OSG 中实现三维纹理主要有以下几个步骤:
(1)指定用户几何体的纹理坐标。
(2)创建多个纹理属性对象并保存纹理多个图形数据。
(3)为 StateSet 设置合适的纹理属性和模式。

  看起来和二维纹理映射的区别不大,简单地说就是多个二维纹理映射的叠加。但这里需要注意的是,对于不同的纹理属性对象需要指定不同的纹理单元及纹理坐标,否则就不会启用该纹理单元,或者该纹理单元会被覆盖。

2.2 示例效果

  原书上给的示例感觉不太合适不具备说明性,因此自己参照learnopengl实现了多重纹理混合示例。

在这里插入图片描述

2.2 源码

// TestOSGProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。#include <windows.h>
#include <osgViewer/Viewer>#include <osg/Node>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Geometry>
#include <osg/Image>
#include <osg/TexGen>
#include <osg/Texture1D>
#include <osg/TexEnv>
#include <osg/StateSet>
#include <osg/PrimitiveSet>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgViewer/ViewerEventHandlers> //事件监听
#include <osgGA/StateSetManipulator> //事件响应类,对渲染状态进行控制
#include <osgUtil/Simplifier> //简化几何体#include <osgUtil/Optimizer>#pragma comment(lib, "OpenThreadsd.lib")
#pragma comment(lib, "osgd.lib")
#pragma comment(lib, "osgDBd.lib")
#pragma comment(lib, "osgUtild.lib")
#pragma comment(lib, "osgGAd.lib")
#pragma comment(lib, "osgViewerd.lib")
#pragma comment(lib, "osgTextd.lib")//创建一个四边形节点
osg::ref_ptr<osg::Node> createNode()
{osg::ref_ptr<osg::Geode> geode = new osg::Geode();osg::ref_ptr<osg::Geometry> geom = new osg::Geometry();//设置顶点osg::ref_ptr<osg::Vec3Array> vc = new osg::Vec3Array();vc->push_back(osg::Vec3(0.0f, 0.0f, 0.0f));vc->push_back(osg::Vec3(1.0f, 0.0f, 0.0f));vc->push_back(osg::Vec3(1.0f, 0.0f, 1.0f));vc->push_back(osg::Vec3(0.0f, 0.0f, 1.0f));geom->setVertexArray(vc.get());//设置纹理坐标osg::ref_ptr<osg::Vec2Array> vt = new osg::Vec2Array();vt->push_back(osg::Vec2(0.0f, 0.0f));vt->push_back(osg::Vec2(1.0f, 0.0f));vt->push_back(osg::Vec2(1.0f, 1.0f));vt->push_back(osg::Vec2(0.0f, 1.0f));geom->setTexCoordArray(0, vt.get());geom->setTexCoordArray(1, vt.get());//设置法线osg::ref_ptr<osg::Vec3Array> nc = new osg::Vec3Array();nc->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));geom->setNormalArray(nc.get());geom->setNormalBinding(osg::Geometry::BIND_OVERALL);//添加图元geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));//绘制geode->addDrawable(geom.get());return geode.get();
}//创建二维纹理状态对象
osg::ref_ptr<osg::StateSet> createTexture2DState()
{//读取贴图文件osg::ref_ptr<osg::Image> image2 = osgDB::readImageFile("awesomeface.png");osg::ref_ptr<osg::Image> image1 = osgDB::readImageFile("container.jpg");//创建状态集对象osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();//创建二维纹理对象osg::ref_ptr<osg::Texture2D> texture1 = new osg::Texture2D();texture1->setDataVariance(osg::Object::DYNAMIC);//设置贴图texture1->setImage(image1.get());//关联Texture2D纹理对象,第三个参数默认为ONstateset->setTextureAttributeAndModes(0, texture1.get(), osg::StateAttribute::ON);//创建二维纹理对象osg::ref_ptr<osg::Texture2D> texture2 = new osg::Texture2D();texture2->setDataVariance(osg::Object::DYNAMIC);//设置贴图texture2->setImage(image2.get());//关联Texture2D纹理对象,第三个参数默认为ONstateset->setTextureAttributeAndModes(1, texture2.get(), osg::StateAttribute::ON);//启用混合stateset->setMode(GL_BLEND, osg::StateAttribute::ON);//关闭光照stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON);return stateset.get();
}int main()
{osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();osg::ref_ptr<osg::Group> root = new osg::Group();osg::ref_ptr<osg::Node> node = createNode();//创建状态集对象osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();stateset = createTexture2DState();//使用二维纹理node->setStateSet(stateset.get());root->addChild(node.get());//优化场景数据osgUtil::Optimizer optimizer;optimizer.optimize(root.get());//方便查看在多边形之间切换,以查看三角网viewer->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));viewer->addEventHandler(new osgViewer::StatsHandler());viewer->addEventHandler(new osgViewer::WindowSizeHandler());viewer->setSceneData(root.get());viewer->setUpViewInWindow(600, 600, 1000, 800);return viewer->run();
}

4、Mipmap多级渐远纹理

  在一个动态的场景中,当一个纹理对象迅速远离视点时,纹理图像必须随着被投影的图像一起缩小。为了实现这种效果,可以通过对纹理图像进行过滤,适当对它进行缩小,以使它映射到物体的表面时不会产生抖动或者闪烁的效果。但这时还存在一个问题,就是当视点距离速度变大时,单个纹理缩小为单个像素之前,在经过一些过渡点时,经过过滤的纹理图像会变化非常明显。同时,也没有必要使用一张那么大的纹理数据了;当使用一个很大的、包含很多贴图的场景时,对渲染效率的影响是相当大的。

  为了避免这种突然变化的现象及不必要的渲染负担,可以预先指定一系列分辨率递减的纹理图像。使用 Mipmap 纹理映射必须指定全系列大小为 2 的整数次方的纹理图像,其范围为从最大值到 1×1 的纹理单元。例如,如果最高的纹理分辨率为 32×32,那么必须指定的纹理图像为 32×32、16×16、8×8、4×4、2×2、1×1。通常来说,较小的纹理图像是上一级分辨率的纹理图像的 4 个纹理单元的平均值。当然,这里也没有确定的计算方法,一般是这样计算的。

在 OSG 中使用 Mipmap 纹理映射主要包括下面几个步骤:

(1)将各层的纹理图像数据按照从大到小的顺序(且尺寸必须为 2 的幂次)依次存放到 unsigned char*数组中,将这个数组使用 setImage 送入 Image 对象。
(2)将各层纹理数据在数组中的偏移地址记录到一个 osg::Image::MipmapDataType 列表中,用于选择正确的层次细节纹理图像。
(3)使用 setMipmapLevels()将 MipmapDataType 送入 Image 对象。注意,这一步的次序和 setImage不能颠倒,否则可能无法正确显示各级别的纹理图像。

  在示例程序中,可以看到清晰的纹理图像的各个层次级别细节的明显过渡,这里用颜色代替了纹理。

备注:OpenGL描述如下:当调用glTexImage2D时,当前绑定的纹理对象就会被附加上纹理图像。然而,目前只有基本级别(Base-level)的纹理图像被加载了,如果要使用多级渐远纹理,我们必须手动设置所有不同的图像(不断递增第二个参数)。或者,直接在生成纹理之后调用glGenerateMipmap。这会为当前绑定的纹理自动生成所有需要的多级渐远纹理。生成了纹理和相应的多级渐远纹理后,释放图像的内存并解绑纹理对象是一个很好的习惯。

4.1 示例效果

在这里插入图片描述

4.2 源码

// TestOSGProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。#include <windows.h>
#include <osgViewer/Viewer>#include <osg/Node>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Geometry>
#include <osg/Image>
#include <osg/TexGen>
#include <osg/Texture1D>
#include <osg/TexEnv>
#include <osg/StateSet>
#include <osg/PrimitiveSet>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgViewer/ViewerEventHandlers> //事件监听
#include <osgGA/StateSetManipulator> //事件响应类,对渲染状态进行控制
#include <osgUtil/Simplifier> //简化几何体#include <osgUtil/Optimizer>#pragma comment(lib, "OpenThreadsd.lib")
#pragma comment(lib, "osgd.lib")
#pragma comment(lib, "osgDBd.lib")
#pragma comment(lib, "osgUtild.lib")
#pragma comment(lib, "osgGAd.lib")
#pragma comment(lib, "osgViewerd.lib")
#pragma comment(lib, "osgTextd.lib")//创建一个四边形
osg::ref_ptr<osg::Geode> createQuad()
{osg::ref_ptr<osg::Geode> geode = new osg::Geode();osg::ref_ptr<osg::Geometry> geom = new osg::Geometry();geode->addDrawable(geom.get());//设置顶点osg::ref_ptr<osg::Vec3Array> vec = new osg::Vec3Array;vec->push_back(osg::Vec3(-10.0f, 0.0f, -10.0f));vec->push_back(osg::Vec3(-10.0f, 0.0f, 10.0f));vec->push_back(osg::Vec3(10.0f, 0.0f, 10.0f));vec->push_back(osg::Vec3(10.0f, 0.0f, -10.0f));geom->setVertexArray(vec.get());//设置法线osg::ref_ptr<osg::Vec3Array> nor = new osg::Vec3Array;nor->push_back(osg::Vec3f(0.0f, -1.0f, 0.0f));geom->setNormalArray(nor.get());geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET);//设置纹理坐标osg::ref_ptr<osg::Vec2Array> tex = new osg::Vec2Array;tex->push_back(osg::Vec2f(0.0f, 0.0f));tex->push_back(osg::Vec2f(0.0f, 1.0f));tex->push_back(osg::Vec2f(1.0f, 1.0f));tex->push_back(osg::Vec2f(1.0f, 0.0f));geom->setTexCoordArray(0, tex.get());geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));return geode.get();
}static void fillImage(unsigned char* ptr, unsigned int size)
{//黑色if (size == 1){float r = 0.5f;osg::Vec4 color(0.0f, 0.0f, 0.0f, 1.0f);*ptr++ = (unsigned char)((color[0]) * 255.0f);*ptr++ = (unsigned char)((color[1]) * 255.0f);*ptr++ = (unsigned char)((color[2]) * 255.0f);*ptr++ = (unsigned char)((color[3]) * 255.0f);}//白色if (size == 2){osg::Vec4 color(1.0f, 1.0f, 1.0f, 1.0f);for (unsigned int r = 0; r < size; ++r){for (unsigned int c = 0; c < size; ++c){*ptr++ = (unsigned char)((color[0]) * 255.0f);*ptr++ = (unsigned char)((color[1]) * 255.0f);*ptr++ = (unsigned char)((color[2]) * 255.0f);*ptr++ = (unsigned char)((color[3]) * 255.0f);}}}//黄色if (size == 4){osg::Vec4 color(0.0f, 1.0f, 0.0f, 1.0f);for (unsigned int r = 0; r < size; ++r){for (unsigned int c = 0; c < size; ++c){*ptr++ = (unsigned char)((color[0]) * 255.0f);*ptr++ = (unsigned char)((color[1]) * 255.0f);*ptr++ = (unsigned char)((color[2]) * 255.0f);*ptr++ = (unsigned char)((color[3]) * 255.0f);}}}//红色if (size == 8){osg::Vec4 color(1.0f, 0.0f, 0.0f, 1.0f);for (unsigned int r = 0; r < size; ++r){for (unsigned int c = 0; c < size; ++c){*ptr++ = (unsigned char)((color[0]) * 255.0f);*ptr++ = (unsigned char)((color[1]) * 255.0f);*ptr++ = (unsigned char)((color[2]) * 255.0f);*ptr++ = (unsigned char)((color[3]) * 255.0f);}}}//粉红色if (size == 16){osg::Vec4 color(1.0f, 0.0f, 1.0f, 1.0f);for (unsigned int r = 0; r < size; ++r){for (unsigned int c = 0; c < size; ++c){*ptr++ = (unsigned char)((color[0]) * 255.0f);*ptr++ = (unsigned char)((color[1]) * 255.0f);*ptr++ = (unsigned char)((color[2]) * 255.0f);*ptr++ = (unsigned char)((color[3]) * 255.0f);}}}//黄色if (size == 32){osg::Vec4 color(1.0f, 1.0f, 0.0f, 1.0f);for (unsigned int r = 0; r < size; ++r){for (unsigned int c = 0; c < size; ++c){*ptr++ = (unsigned char)((color[0]) * 255.0f);*ptr++ = (unsigned char)((color[1]) * 255.0f);*ptr++ = (unsigned char)((color[2]) * 255.0f);*ptr++ = (unsigned char)((color[3]) * 255.0f);}}}//蓝绿色if (size == 64){osg::Vec4 color(0.0f, 1.0f, 1.0f, 1.0f);for (unsigned int r = 0; r < size; ++r){for (unsigned int c = 0; c < size; ++c){*ptr++ = (unsigned char)((color[0]) * 255.0f);*ptr++ = (unsigned char)((color[1]) * 255.0f);*ptr++ = (unsigned char)((color[2]) * 255.0f);*ptr++ = (unsigned char)((color[3]) * 255.0f);}}}//灰白色if (size == 128){osg::Vec4 color(0.5f, 0.5f, 0.5f, 1.0f);for (unsigned int r = 0; r < size; ++r){for (unsigned int c = 0; c < size; ++c){*ptr++ = (unsigned char)((color[0]) * 255.0f);*ptr++ = (unsigned char)((color[1]) * 255.0f);*ptr++ = (unsigned char)((color[2]) * 255.0f);*ptr++ = (unsigned char)((color[3]) * 255.0f);}}}//蓝色if (size == 256){osg::Vec4 color(0.0f, 0.0f, 1.0f, 1.0f);for (unsigned int r = 0; r < size; ++r){for (unsigned int c = 0; c < size; ++c){*ptr++ = (unsigned char)((color[0]) * 255.0f);*ptr++ = (unsigned char)((color[1]) * 255.0f);*ptr++ = (unsigned char)((color[2]) * 255.0f);*ptr++ = (unsigned char)((color[3]) * 255.0f);}}}
}int main()
{osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();osg::ref_ptr<osg::Group> root = new osg::Group();//创建一个平面osg::ref_ptr<osg::Geode> geode = createQuad();osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();osg::ref_ptr<osg::Image> image = new osg::Image();//创建一个MipmapDataType列表,用来各层图片数据的偏移地址osg::Image::MipmapDataType mipmapData;//纹理的尺寸的最大值,必须为2的幂次unsigned int s = 256;//计算所需分配的数组的大小unsigned int totalSize = 0;for (unsigned int i = 0; s > 0; s >>= 1, ++i){if (i > 0){mipmapData.push_back(totalSize);}totalSize += s * s * 4;}//申请一个数据unsigned char* ptr = new unsigned char[totalSize];//设置image的尺寸大小,数据及数据格式image->setImage(256, 256, 256, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, ptr, osg::Image::USE_NEW_DELETE, 1);//将偏移地址传入imge对象image->setMipmapLevels(mipmapData);//向image中填充各层数据s = 256;for (unsigned int i = 0; s > 0; s >>= 1, ++i){fillImage(ptr, s);ptr += s * s * 4;}//创建一个二维纹理对象osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;//设置贴图texture->setImage(0, image.get());//设置边界处理为REPEATEtexture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);//设置滤波texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST_MIPMAP_NEAREST);texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);//启用二维纹理对象stateset->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON);geode->setStateSet(stateset.get());root->addChild(geode.get());//优化场景数据osgUtil::Optimizer optimizer;optimizer.optimize(root.get());//方便查看在多边形之间切换,以查看三角网viewer->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));viewer->addEventHandler(new osgViewer::StatsHandler());viewer->addEventHandler(new osgViewer::WindowSizeHandler());viewer->setSceneData(root.get());viewer->setUpViewInWindow(600, 600, 1000, 800);return viewer->run();
}

5、TextureRectangle矩阵纹理

  TextureRectangle 纹理映射是在 OpenGL 后来版本中的一个扩展——ARB_texture_rectangle,它也是一种二维纹理映射,但它与前面介绍的二维映射有很大的区别。
在这里插入图片描述

  使用 TextureRectangle 纹理映射时有以下几点需要注意:

纹理环绕模式。它并不支持所有的纹理包装模式,只能使用 CLAMP、CLAMP_TO_EDGE 或CLAMP_TO_BORDER,并不支持 REPEAT。
纹理滤波。它只支持NEAREST或者LINEAR,不支持Mipmap滤波,使用它时不能实现Mipmap纹理。
不支持纹理边框。

  对于没有图形学基础的开发人员来说,TextureRectangle 比较容易理解,也非常容易上手,可以简单理解为一个矩形纹理。

5.1 示例效果

在这里插入图片描述

5.2 源码

// TestOSGProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。#include <windows.h>
#include <osgViewer/Viewer>#include <osg/Node>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Geometry>
#include <osg/Image>
#include <osg/TexGen>
#include <osg/Texture1D>
#include <osg/TexEnv>
#include <osg/TextureRectangle>
#include <osg/TexMat>
#include <osg/StateSet>
#include <osg/PrimitiveSet>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgViewer/ViewerEventHandlers> //事件监听
#include <osgGA/StateSetManipulator> //事件响应类,对渲染状态进行控制
#include <osgUtil/Simplifier> //简化几何体#include <osgUtil/Optimizer>#pragma comment(lib, "OpenThreadsd.lib")
#pragma comment(lib, "osgd.lib")
#pragma comment(lib, "osgDBd.lib")
#pragma comment(lib, "osgUtild.lib")
#pragma comment(lib, "osgGAd.lib")
#pragma comment(lib, "osgViewerd.lib")
#pragma comment(lib, "osgTextd.lib")//创建一个四边形节点
osg::ref_ptr<osg::Node> createNode()
{osg::ref_ptr<osg::Geode> geode = new osg::Geode();osg::ref_ptr<osg::Geometry> geom = new osg::Geometry();//设置顶点osg::ref_ptr<osg::Vec3Array> vc = new osg::Vec3Array();vc->push_back(osg::Vec3(0.0f, 0.0f, 0.0f));vc->push_back(osg::Vec3(1.0f, 0.0f, 0.0f));vc->push_back(osg::Vec3(1.0f, 0.0f, 1.0f));vc->push_back(osg::Vec3(0.0f, 0.0f, 1.0f));geom->setVertexArray(vc.get());//设置纹理坐标osg::ref_ptr<osg::Vec2Array> vt = new osg::Vec2Array();vt->push_back(osg::Vec2(0.0f, 0.0f));vt->push_back(osg::Vec2(1.0f, 0.0f));vt->push_back(osg::Vec2(1.0f, 1.0f));vt->push_back(osg::Vec2(0.0f, 1.0f));geom->setTexCoordArray(0, vt.get());//设置法线osg::ref_ptr<osg::Vec3Array> nc = new osg::Vec3Array();nc->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));geom->setNormalArray(nc.get());geom->setNormalBinding(osg::Geometry::BIND_OVERALL);//添加图元geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));//绘制geode->addDrawable(geom.get());return geode.get();
}//创建二维纹理状态对象
osg::ref_ptr<osg::StateSet> createTexture2DState(osg::ref_ptr<osg::Image> image)
{//创建状态集对象osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();//创建二维纹理对象osg::ref_ptr<osg::TextureRectangle> texture = new osg::TextureRectangle();texture->setDataVariance(osg::Object::DYNAMIC);//设置贴图texture->setImage(image.get());//设置纹理矩阵,并设置为根据矩阵纹理(TextureRectangle)的大小自动缩放//从而允许应用一个矩形纹理到一个纹理坐标不在0-1上osg::ref_ptr<osg::TexMat> texmat = new osg::TexMat;texmat->setScaleByTextureRectangleSize(true);//启用纹理及纹理矩阵stateset->setTextureAttributeAndModes(0, texmat.get(), osg::StateAttribute::ON);stateset->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON);//关闭光照stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF);return stateset.get();
}int main()
{osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();osg::ref_ptr<osg::Group> root = new osg::Group();//读取贴图文件osg::ref_ptr<osg::Image> image = osgDB::readImageFile("Images/primitives.gif");osg::ref_ptr<osg::Node> node = createNode();//创建状态集对象osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();stateset = createTexture2DState(image.get());//使用二维纹理node->setStateSet(stateset.get());root->addChild(node.get());//优化场景数据osgUtil::Optimizer optimizer;optimizer.optimize(root.get());//方便查看在多边形之间切换,以查看三角网viewer->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));viewer->addEventHandler(new osgViewer::StatsHandler());viewer->addEventHandler(new osgViewer::WindowSizeHandler());viewer->setSceneData(root.get());viewer->setUpViewInWindow(600, 600, 1000, 800);return viewer->run();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/208595.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VUE语法--img图片不显示/img的src动态赋值图片显示

1、问题概述 常见情景1&#xff1a;在VUE中使用img显示图片的时候&#xff0c;通过传参的方式传入图片的路径和名称&#xff0c;VUE不加载本地资源而是通过http://localhost:8080/...的地址去加载网络资源&#xff0c;从而出现了图片无法显示的情况。 常见情景2&#xff1a;针…

W2311294-万宾科技可燃气体监测仪怎么进行数据监测

万宾科技可燃气体监测仪怎么进行数据监测 燃气是现代城市之中重要的能源&#xff0c;它已经渗透到城市生活的方方面面&#xff0c;对燃气管网的管理也在考验着政府人员的工作能力。燃气管网的安全运行和城市的安全和人民的生活直接挂钩。为了及时掌握燃气管网的运行状态&#x…

Nvidia VPI 双目相机生成深度图

nVidia VPI&#xff08;Vision Programming Interface&#xff09;提供了多种后端&#xff0c;用于执行图像处理和计算机视觉操作。不同的后端针对不同的硬件和用例进行了优化。这些后端包括&#xff1a; 1. CPU: 这是最通用的后端&#xff0c;它运行在标准的中央处理器&#…

字符函数 和 字符串函数

今天我打算介绍一些字符函数和字符串函数&#xff0c;有一些字符串函数我实现了模拟&#xff0c;但文章中没有放出来&#xff0c;如果需要的欢迎来到我的gitee里面拿取&#xff08;在test.c11-23里面&#xff09; 这是我的gitee:小汐 (lhysxx) - Gitee.com 字符函数 1. islow…

Vivado版本控制

Vivado版本控制 如果您有幸进入FPGA领域&#xff0c;那么会遇到版本控制问题&#xff0c;本文讲解的是如何用git进行Vivado进行版本控制。 搭建Git环境 一 首先需要一个git环境&#xff0c;并选择一个托管平台&#xff08;github,gitlab,gitee&#xff09; Git下载地址&…

Kubernetes学习笔记-Part.09 K8s集群构建

目录 Part.01 Kubernets与docker Part.02 Docker版本 Part.03 Kubernetes原理 Part.04 资源规划 Part.05 基础环境准备 Part.06 Docker安装 Part.07 Harbor搭建 Part.08 K8s环境安装 Part.09 K8s集群构建 Part.10 容器回退 第九章 K8s集群构建 9.1.集群初始化 集群初始化是首…

java+springboot校园一卡通学生卡管理系统+jsp

利用校园卡实现了学生在学校的身份认证&#xff0c;对学生在学校的各种消费提供了方便的途径。对于学校图书馆&#xff0c;将自动存储学生图书借阅情况&#xff0c;记录处罚情况.对于任课教师可以及时、准确、方便的了解学生出勤、作业等情况。是凭借发达的网络技术&#xff0c…

羽隔已就之图像处理之BP神经网络入门

小y最近非常忙&#xff0c;这一年来&#xff0c;活很多&#xff0c;一直在加班、出差&#xff0c;也没好好休息过。最近在武汉出差一个多月了&#xff0c;项目逐渐完结&#xff0c;有点闲时间了&#xff0c;回首望&#xff0c;这一年设定的很多目标都没完成。 还记得&#xff0…

JAVAEE---多线程

wait和notify--等待通知机制 当一个线程条件不满足&#xff0c;进入wait等待。其他线程这个时候获取到锁进行一系列操作后用notify唤醒线程&#xff0c;线程重新参与竞争。wait和join一样&#xff0c;也有两个版本&#xff0c;死等和按时间等待。 wait和sleep的区别 两者都可…

【KPDK】概述

DPDK的主要目标是为数据平面应用程序中的快速数据包处理提供一个简单、完整的框架。用户可以使用代码来理解所采用的一些技术&#xff0c;构建原型或添加自己的协议栈。可提供使用DPDK的替代生态系统选项。 DPDK框架通过创建环境抽象层&#xff08;EAL&#xff09;为特定环境创…

如何在Rocky Linux中安装nmon

一、环境基础 [rootlocalhost nmon16d]# cat /etc/redhat-release Rocky Linux release 9.2 (Blue Onyx) [rootlocalhost nmon16d]# uname -r 5.14.0-284.11.1.el9_2.x86_64 [rootlocalhost nmon16d]# 二、安装步骤 在Rocky Linux和AlmaLinux等基于RHEL 的发行版上&#xff…

剪切空间与归一化设备坐标【NDC】

有了投影变换的知识&#xff0c;我们现在可以讨论剪切空间&#xff08;Clip Space&#xff09;和 归一化设备坐标&#xff08;NDC&#xff1a;Normalized Device Coordinates&#xff09;。 为了理解这些主题&#xff0c;我们还需要深入了解齐次坐标的有趣世界。 NSDT工具推荐&…

【PTA-C语言】实验三-循环结构I

如果代码存在问题&#xff0c;麻烦大家指正 ~ ~有帮助麻烦点个赞 ~ ~ 实验三-循环结构I 7-1 求交错序列前N项和 &#xff08;分数 15&#xff09;7-2 寻找250&#xff08;分数 15&#xff09;7-3 最大公约数和最小公倍数&#xff08;分数 15&#xff09;7-4 统计字符&#xff0…

二十五、DSL查询文档(全文检索查询、精确查询、地理查询、复合查询)

目录 一、全文检索查询 1、match查询 语法: 2、multi_match查询 语法: 3、match和mult_match的区别 二、精确查询 1、term查询&#xff1a; 语法&#xff1a; 2、range查询&#xff1a;&#xff08;范围查询&#xff09; 语法&#xff1a; 三、地理查询 1、geo_bou…

Springboot如何快速生成分页展示以及统计条数

这是表结构&#xff1a; 前置知识&#xff1a; 分页查询公式&#xff08;&#xff09;&#xff1a; -- 推导一个公式 -- select * from emp -- order by empno -- limit 每页显示记录数 * (第几页-1)&#xff0c;每页显示记录数 统计条数公式&#xff1a; select count…

【动态规划】LeetCode-931.下降路径最小和

&#x1f388;算法那些事专栏说明&#xff1a;这是一个记录刷题日常的专栏&#xff0c;每个文章标题前都会写明这道题使用的算法。专栏每日计划至少更新1道题目&#xff0c;在这立下Flag&#x1f6a9; &#x1f3e0;个人主页&#xff1a;Jammingpro &#x1f4d5;专栏链接&…

Wordpress自动定时发布怎么开通-Wordpress怎么自动发布原创文章

在当今数字化时代&#xff0c;博客已经成为许多人分享观点、经验和知识的重要平台。然而&#xff0c;对于博主们来说&#xff0c;每天按时发布一篇又一篇的文章可能是一项具有挑战性的任务。为了解决这个问题&#xff0c;一些创新的工具应运而生&#xff0c;其中包括WordPress的…

Collection的其他相关知识

前置知识&#xff1a;可变参数 就是一种特殊参数&#xff0c;定义在方法 构造器的形参列表里&#xff0c;格式是&#xff1a;数据类型...参数名称&#xff1b; 可变参数的特点和好处 特点&#xff1a;可以不传数据给它&#xff1b;可以传一个或者同时传多个数据给它&#xff…

爬虫学习(三)用beautiful 解析html

安装库 import requests from bs4 import BeautifulSoup headers {"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0"} for start_num in range(0,250…

【工作生活】汽车ECU开发内容简介

目录 1. 目标 2. 要分享什么 3.1 行业知识 3.1.1车载行业知识&#xff1a; 3.1.2项目&#xff1a; 3.1.3开发测试工具&#xff1a; 3.2 硬件平台 3.3 基础知识 3.4 工作生活 3. 我们是谁 1. 目标 随着新能源汽车的快速崛起&#xff0c;汽车电子行业开始快速发展&…