`
javahigh1
  • 浏览: 1298120 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

使用OpenGL VBO扩展 --Nehe教程

 
阅读更多

使用VBO扩展 <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

原文出处 http://Nehe.gamedev.net lesson 45

翻译: xheartblue 潘李亮 Stanly Lee 2004-4-18

Homepage: http://gamehunter.3322.net/xpertsoft/

MSN/Email : xheartblue@etangc.om

任何一个3D应用程序的最大的目标之一就是速度,你需要自始至终的限制实际渲染的三角形数目,不管你是采用排序,剔除(Culling),还是层次细节(LOD)。如果其它的方法都无效了,你想简单提高多边形的提交速度的话,通常可以利用OpenGL提供的优化方法,顶点数组是一个比较好的方法。加上最近一个名称为Vertex Buffer Object的扩展,这大大提高了应用的FPSARB_Vertex_Buffer的扩展工作的和顶点数组很像,除了一点,那就是ARB_Vertx_Buffer把数据加载到显卡的高性能的显存里。大大降低了渲染时间。当然,这个扩展是依赖较新的硬件的,不是所有的图形卡都支持的,所以我们必须使用一些技术来进行平衡。

在这个教程里,我们将要:

1:从高度图里加载数据。

2: 使用顶点数组更加有效的把网格数据提交给OpenGL

3: 通过VBOVertex_Buffer_Object)把数据加载到高性能的显存里。

现在我们来定义几个应用程序的参数。

#define MESH_RESOLUTION 4.0f // 每个顶点对应几个像素

#define MESH_HEIGHTSCALE 1.0f // Mesh Height Scale

//#define NO_VBOS // 如果定义了,强制的不使用VBO

开头这两个参数是为高度图定义的。第一个定义了高度图里每一个像素的分辨率。后面一个设定了加载了高度图数据后在垂直方向需要进行的缩放比例。第三个常量,如果你定义了的话,将强行不使用VBO

下一步,我们将定义VBO 扩展的常量,数据类型,和函数指针。

//VBO扩展的定义,从 glext.h摘抄

#define GL_ARRAY_BUFFER_ARB 0x8892

#define GL_STATIC_DRAW_ARB 0x88E4

typedef void (APIENTRY * PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer);

typedef void (APIENTRY * PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers);

typedef void (APIENTRY * PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers);

typedef void (APIENTRY * PFNGLBUFFERDATAARBPROC) (GLenum target, int size, const GLvoid *data, GLenum usage);

// VBO 扩展的函数指针

PFNGLGENBUFFERSARBPROC glGenBuffersARB = NULL; // VBO 名字生成函数

PFNGLBINDBUFFERARBPROC glBindBufferARB = NULL; // VBO 绑定函数

PFNGLBUFFERDATAARBPROC glBufferDataARB = NULL; // VBO 数据加载函数

PFNGLDELETEBUFFERSARBPROC glDeleteBuffersARB = NULL; // VBO 删除函数

我只是包含了这个演示程序所需要的东西次。如果你需要其它更多的扩展函数,我建议你到http://www.opengl.org里去下载最新的glext.h文件并且使用它里面的定义(这样会在一定程度上让你的程序更加美观)。我们将深入那几个我们将会使用到的函数。

现在我们来定义基本数学对象,加上我们自己的网格类,所有这些都是这个演示程序的一个非常简单的设计,我建议大家开发一个自己的数学库。

class CVert // 顶点类

{

public:

float x; // X Component

float y; // Y Component

float z; // Z Component

};

typedef CVert CVec; // 定义CVec CVert是一样的。

class CTexCoord // 纹理坐标类

{

public:

float u; // U Component

float v; // V Component

};

class CMesh

{

public:

// 网格数据

int m_nVertexCount; // 顶点数目

CVert* m_pVertices; // 顶点数据

CTexCoord* m_pTexCoords; // 纹理坐标

unsigned int m_nTextureId; // 纹理标ID

// VBO 的名字

unsigned int m_nVBOVertices; // 顶点 VBO 的名字

unsigned int m_nVBOTexCoords; // 纹理坐标 VBO 的名字

// 临时数据

AUX_RGBImageRec* m_pTextureImage; //高度图的数据

public:

CMesh(); // Mesh Constructor

~CMesh(); // Mesh Deconstructor

//高度图加载函数

bool LoadHeightmap( char* szPath, float flHeightScale, float flResolution );

// 得到一个点的高度

float PtHeight( int nX, int nY );

// 绑定VBO对象。

void BuildVBOs();

};

大部分的代码是自注释的。请注意,我把顶点和纹理给分离开存放了。这不是必需的,后面我会给出为什么这么做的解释。

现在我们来定义全局变量。首先是VBO是不是被支持的标志变量,它将在初始化代码里被设置。接下来是我们的网格对象。以及绕Y轴旋转的角度。用来计算FPS的变量。我决定写一个基于FPS的东西来显示这个代码被优化的程度。

bool g_fVBOSupported = false; // ARB_vertex_buffer_object是否被支持?

CMesh* g_pMesh = NULL; //网格数据

float g_flYRot = 0.0f; //旋转

int g_nFPS = 0, g_nFrames = 0; // FPSFPS 计数器

DWORD g_dwLastFPS = 0; //最后依次计算FPS的时间

让我们直接CMesh函数的定义,从LoadHeightMap开始。对那些没有接触过Heightmap的人来,可以这样理解Heightmap,一个Heightmap就是一个二维的数据组,通常是一个图象,它指定了地形网格在垂直上的高度。有许多方法可以实现一个高度图的,但是几乎没有一个完美的。我的实现方法是从一个3个通道(24bit)的位图里读入数据,利用发光度算法来决定数据所定义的高度,这样无论你使用的彩色的图象还是灰度图,结果都是一样的。所以你可以使用彩色图象来定义高度图。个人建议使用四个通道的图象。如TGA等。我们可以使用它的Alpha通道来表示高度。但是,只为了这个教程,我想一个简单的Bitmap还是最合适的。

首先。我们确定一个高度图是不是存在。如果存在。我们使用GLaux的图象加载例程,这也许对写自己的图象加载例程比较有用,但是这已经超出了本教程的范围了。

bool CMesh :: LoadHeightmap( char* szPath, float flHeightScale, float flResolution )

{

// Error-Checking

FILE* fTest = fopen( szPath, "r" ); // Open The Image

if( !fTest ) // Make Sure It Was Found

return false; // If Not, The File Is Missing

fclose( fTest ); // Done With The Handle

// Load Texture Data

m_pTextureImage = auxDIBImageLoad( szPath ); // Utilize GLaux's Bitmap Load Routine

现在,事情变的有趣起来了。首先,我要指出我的高度图将为每一个三角形产生三个顶点。顶点是不和别的三角形共享的,我将在后面解释我为什么这么做,但是你在代码之前需要知道这点。

我开始计算网格里顶点的总数。算法是这样的: ( ( Terrain Width / Resolution ) * ( Terrain Length / Resolution ) * 3 Vertices in a Triangle * 2 Triangles in a Square )。接着分配数据,然后填充数据。

// Generate Vertex Field

m_nVertexCount = (int) ( m_pTextureImage->sizeX * m_pTextureImage->sizeY * 6 / ( flResolution * flResolution ) );

m_pVertices = new CVec[m_nVertexCount]; // Allocate Vertex Data

m_pTexCoords = new CTexCoord[m_nVertexCount]; // Allocate Tex Coord Data

int nX, nZ, nTri, nIndex=0; // Create Variables

float flX, flZ;

for( nZ = 0; nZ < m_pTextureImage->sizeY; nZ += (int) flResolution )

{

for( nX = 0; nX < m_pTextureImage->sizeX; nX += (int) flResolution )

{

for( nTri = 0; nTri < 6; nTri++ )

{

// Using This Quick Hack, Figure The X,Z Position Of The Point

flX = (float) nX + ( ( nTri == 1 || nTri == 2 || nTri == 5 ) ? flResolution : 0.0f );

flZ = (float) nZ + ( ( nTri == 2 || nTri == 4 || nTri == 5 ) ? flResolution : 0.0f );

// Set The Data, Using PtHeight To Obtain The Y Value

m_pVertices[nIndex].x = flX - ( m_pTextureImage->sizeX / 2 );

m_pVertices[nIndex].y = PtHeight( (int) flX, (int) flZ ) * flHeightScale;

m_pVertices[nIndex].z = flZ - ( m_pTextureImage->sizeY / 2 );

// Stretch The Texture Across The Entire Mesh

m_pTexCoords[nIndex].u = flX / m_pTextureImage->sizeX;

m_pTexCoords[nIndex].v = flZ / m_pTextureImage->sizeY;

// Increment Our Index

nIndex++;

}

}

}

函数的最后将要加载高度图的纹理到OpenGL里来。然后释放我们纹理数据的备份。这和前面的教程很类似的。

// Load The Texture Into OpenGL

glGenTextures( 1, &m_nTextureId ); // Get An Open ID

glBindTexture( GL_TEXTURE_2D, m_nTextureId ); // Bind The Texture

glTexImage2D( GL_TEXTURE_2D, 0, 3,

m_pTextureImage->sizeX, m_pTextureImage->sizeY, 0, GL_RGB,

GL_UNSIGNED_BYTE, m_pTextureImage->data );

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

// Free The Texture Data

if( m_pTextureImage )

{

if( m_pTextureImage->data )

free( m_pTextureImage->data );

free( m_pTextureImage );

}

return true;

}

PtHeight这个函数就比较简单了。它计算了需要查询的索引位置的数据的高度,并进行检测,然后计算高度,亮度值的计算也非常简单。你可以看的到,所以我就不在多讲了。

float CMesh :: PtHeight( int nX, int nY )

{

// Calculate The Position In The Texture, Careful Not To Overflow

int nPos = ( ( nX % m_pTextureImage->sizeX ) + ( ( nY % m_pTextureImage->sizeY ) * m_pTextureImage->sizeX ) ) * 3;

float flR = (float) m_pTextureImage->data[ nPos ]; // Get The Red Component

float flG = (float) m_pTextureImage->data[ nPos + 1 ]; // Get The Green Component

float flB = (float) m_pTextureImage->data[ nPos + 2 ]; // Get The Blue Component

return ( 0.299f * flR + 0.587f * flG + 0.114f * flB ); // Calculate The Height Using The Luminance Algorithm

}

万岁,现在是讲述顶点数组和VBOs的时候了。什么是顶点数组(Vertex Arrays),它是这样一个系统,你可以告诉OpenGL,你的顶点数据在哪里,然后分成子序列来渲染,只需要很少的函数调用就可以了。这样做的结果就是减少了函数的调用(glVertex,等),增加了程序的速度。什么是VBOs呢?Vertex Buffer Object使用高速的显卡内存,而不是普通的系统RAM内存。它不仅仅降低了每帧的内存操作,而且减少了数据在显卡和CPU之间的传输,在我的实验里,VBO大大提高了帧率,而不是提高了一点点。

现在我们来建立一个Vertex Buffer Objects。实际上,有两种方法可以做,其中一种是被称为"Mapping"的方法。我想最简单的方法也是最好的方法。过程如下:首先用glGenBuffersARB来产生一个可用的VBO名字,实质上,一个名字是一个ID数字,OpenGL用这个ID来关联你的数据。因为一个数字并不一定代表一个有效的VBO名字。然后,我们通过glBindBufferARB函数把VBO对象绑定,让其被激活。最后,我们把数据加载到图形卡里,这可以通过glBufferDataARB来实现,把数据的指针和大小传递进去,glBufferDataARB将把你的数据拷贝到显卡内存中,这意味着我们没有必要再维护这些数据,所以我们可以把它删除(内存中的数据)

void CMesh :: BuildVBOs()

{

// Generate And Bind The Vertex Buffer

glGenBuffersARB( 1, &m_nVBOVertices ); // Get A Valid Name

glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nVBOVertices ); // Bind The Buffer

// Load The Data

glBufferDataARB( GL_ARRAY_BUFFER_ARB,

m_nVertexCount*3*sizeof(float), m_pVertices,

GL_STATIC_DRAW_ARB );

// Generate And Bind The Texture Coordinate Buffer

glGenBuffersARB( 1, &m_nVBOTexCoords ); // Get A Valid Name

glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nVBOTexCoords ); // Bind The Buffer

// Load The Data

glBufferDataARB( GL_ARRAY_BUFFER_ARB,

m_nVertexCount*2*sizeof(float),

m_pTexCoords, GL_STATIC_DRAW_ARB );

// Our Copy Of The Data Is No Longer Necessary, It Is Safe In The Graphics Card

delete [] m_pVertices; m_pVertices = NULL;

delete [] m_pTexCoords; m_pTexCoords = NULL;

}

好了。该是初始化的时候了。我们给Mesh分配内存并加载数据,然后我们测试GL_ARB_vertex_buffer_object是不是支持,如果支持,我们就通过wglGetProcAddress函数得到所有VBO扩展函数的指针,然后建立我们的VBO对象。注意。如果不支持VBO,我们将像通常一样保留数据,同时也请注意前面提到的强制关闭VBOs.

// Load The Mesh Data

g_pMesh = new CMesh(); // Instantiate Our Mesh

if( !g_pMesh->LoadHeightmap( "terrain.bmp", // Load Our Heightmap

MESH_HEIGHTSCALE, MESH_RESOLUTION ) )

{

MessageBox( NULL, "Error Loading Heightmap", "Error", MB_OK );

return false;

}

// Check For VBOs Supported

#ifndef NO_VBOS

g_fVBOSupported = IsExtensionSupported( "GL_ARB_vertex_buffer_object" );

if( g_fVBOSupported )

{

// Get Pointers To The GL Functions

glGenBuffersARB = (PFNGLGENBUFFERSARBPROC) wglGetProcAddress("glGenBuffersARB");

glBindBufferARB = (PFNGLBINDBUFFERARBPROC) wglGetProcAddress("glBindBufferARB");

glBufferDataARB = (PFNGLBUFFERDATAARBPROC) wglGetProcAddress("glBufferDataARB");

glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC) wglGetProcAddress("glDeleteBuffersARB");

// Load Vertex Data Into The Graphics Card Memory

g_pMesh->BuildVBOs(); // Build The VBOs

}

#else /* NO_VBOS */

g_fVBOSupported = false;

#endif

IsExtensionSupported是一个可以从OpenGL.org上得到的函数,我函数的变化是:用我粗俗的观点来看是更清晰了。

bool IsExtensionSupported( char* szTargetExtension )

{

const unsigned char *pszExtensions = NULL;

const unsigned char *pszStart;

unsigned char *pszWhere, *pszTerminator;

// Extension names should not have spaces

pszWhere = (unsigned char *) strchr( szTargetExtension, ' ' );

if( pszWhere || *szTargetExtension == '/0' )

return false;

// Get Extensions String

pszExtensions = glGetString( GL_EXTENSIONS );

// Search The Extensions String For An Exact Copy

pszStart = pszExtensions;

for(;;)

{

pszWhere = (unsigned char *) strstr( (const char *) pszStart, szTargetExtension );

if( !pszWhere )

break;

pszTerminator = pszWhere + strlen( szTargetExtension );

if( pszWhere == pszStart || *( pszWhere - 1 ) == ' ' )

if( *pszTerminator == ' ' || *pszTerminator == '/0' )

return true;

pszStart = pszTerminator;

}

return false;

}

这个函数相对来说简单一些了。有些人简单的使用strstr来搜索子字符串,但是显然OpenGL.org并不信任这个函数,我不同意他们的观点。

基本上所有的都做好了。我们剩下来要做的事情就是把数据渲染出来。

void Draw (void)

{

glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer

glLoadIdentity (); // Reset The Modelview Matrix

// Get FPS

if( GetTickCount() - g_dwLastFPS >= 1000 ) // When A Second Has Passed...

{

g_dwLastFPS = GetTickCount(); // Update Our Time Variable

g_nFPS = g_nFrames; // Save The FPS

g_nFrames = 0; // Reset The FPS Counter

char szTitle[256]={0}; // Build The Title String

sprintf( szTitle, "Lesson 45: NeHe & Paul Frazee's VBO Tut - %d Triangles, %d FPS",

g_pMesh->m_nVertexCount / 3, g_nFPS );

if( g_fVBOSupported ) // Include A Notice About VBOs

strcat( szTitle, ", Using VBOs" );

else

strcat( szTitle, ", Not Using VBOs" );

SetWindowText( g_window->hWnd, szTitle ); // Set The Title

}

g_nFrames++; // Increment Our FPS Counter

// Move The Camera

glTranslatef( 0.0f, -220.0f, 0.0f ); // Move Above The Terrain

glRotatef( 10.0f, 1.0f, 0.0f, 0.0f ); // Look Down Slightly

glRotatef( g_flYRot, 0.0f, 1.0f, 0.0f ); // Rotate The Camera

相当的简单,每一秒过去的时候,我们都把帧计数器的值当作FPS的值保存起来。然后把帧计数器清零。接着,我们在Terrain上移动Camera(如果你改变了高度图,也许你需要调整他),和做一些旋转,g_flYRot在每个Update调用是被递增。

要使用顶点数组(和VBOs),你需要告诉OpenGL你想给它提供什么样的数据,所以第一步是打开客户端的GL_VERTEX_ARRAYGL_TEXTURE_COORD_ARRAY两个状态,然后我将要来设置我们的数据指针,我想,除非你有多个Mesh对象,否则你没有必要在渲染每个帧的时候都做这一步。但是这关系并不大,所以我不认为它是个大问题。

为一个特定的数据类型来设置一个指针,你需要使用对应的函数--glVertexPointerglTexCoordPointer,在我们的例子里非常简单,把顶点需要变量的总数(一个顶点有3个,一个纹理坐标有2个),数据类型(float),每项数据之间期望间隔(Stride)(如果顶点不是存放在连续的数据结构里的时候会有用),以及数据的指针,你也可以使用glInterleavedArrays,并把所有数据保存在一个大的缓冲区里,但是我选择了把数据分开,这样能更好的演示如何使用多个VBOs

说到VBOs。实现起来也没有太大不同,唯一个区别就是提供的数据指针,我们绑定了一个VBO后,只要把数据指针设置成NULL就可以了。请看下面:

// Set Pointers To Our Data

if( g_fVBOSupported )

{

glBindBufferARB( GL_ARRAY_BUFFER_ARB, g_pMesh->m_nVBOVertices );

glVertexPointer( 3, GL_FLOAT, 0, (char *) NULL ); // Set The Vertex Pointer To The Vertex Buffer

glBindBufferARB( GL_ARRAY_BUFFER_ARB, g_pMesh->m_nVBOTexCoords );

glTexCoordPointer( 2, GL_FLOAT, 0, (char *) NULL ); // Set The TexCoord Pointer To The TexCoord Buffer

} else

{

glVertexPointer( 3, GL_FLOAT, 0, g_pMesh->m_pVertices ); // Set The Vertex Pointer To Our Vertex Data

glTexCoordPointer( 2, GL_FLOAT, 0, g_pMesh->m_pTexCoords ); // Set The Vertex Pointer To Our TexCoord Data

}

渲染是非常简单容易的。

// Render

glDrawArrays( GL_TRIANGLES, 0, g_pMesh->m_nVertexCount ); // Draw All Of The Triangles At Once

这里我们使用glDrawArrays来把数据送给OpenGL.glDrawArrays检测哪个客户端状态被激活,然后使用它的指针来渲染。我们告诉OpenGL几何图元的类型,从那个索引开始,以及有多少顶点要渲染。有很多其他的方法可以把数据提交来渲染。如glArrayElement,但是这是最快的方法。你会注意到glDrawArrays没有在glBeginglEnd之间。因为没有必要。

glDrawArrays是为什么我选择不在三角形间共享顶点的原因。因为共享是不可能的,我所知道的优化内存使用的最佳方法是使用Triangle Strips,但是它已经不是本教程的范围了。也许你还意识到了要为每一个顶点指定一个向量,这意味你要使用向量,每个顶点都要有一个伴随向量,如果你为每个顶点计算出向量能大大提高渲染结果的视觉真实性。

现在我们要做的就是关闭顶点数组,到此,我们就结束了。

// Disable Pointers

glDisableClientState( GL_VERTEX_ARRAY ); // Disable Vertex Arrays

glDisableClientState( GL_TEXTURE_COORD_ARRAY ); // Disable Texture Coord Arrays

}

If you want more information on Vertex Buffer Objects, I recommend reading the documentation in SGI's extension registry - http://oss.sgi.com/projects/ogl-sample/registry. It is a little more tedious to read through than a tutorial, but it will give you much more detailed information. Well that does it for the tutorial. If you find any mistakes or misinformation, or simply have questions, you can contact me at paulfrazee@cox.net. * DOWNLOAD Visual C++ Code For This Lesson. * DOWNLOAD Borland C++ Builder 6 Code For This Lesson. ( Conversion by Le Thanh Cong ) * DOWNLOAD Code Warrior 5.3 Code For This Lesson. ( Conversion by Scott Lupton ) * DOWNLOAD Dev C++ Code For This Lesson. ( Conversion by Gerald Buchgraber ) * DOWNLOAD Linux/SDL Code For This Lesson. ( Conversion by Ilias Maratos ) * DOWNLOAD Visual Studio .NET Code For This Lesson. ( Conversion by Joachim Rohde )


分享到:
评论

相关推荐

    NeHe OPenGL 英文教程

    ### NeHe OpenGL 英文教程知识点总结 #### 一、设置OpenGL在MacOS中的环境 在本章节中,作者详细介绍了如何在MacOS系统中设置OpenGL编程环境。 **所需工具:** - **编译器**:最常用且推荐的是Metrowerks Code...

    基于Simulink的风火水储联合调频系统中储能SOC对ACE影响的技术分析

    内容概要:本文详细探讨了在Simulink环境中构建的风火水储联合调频系统中,储能系统的荷电状态(SOC)对区域控制偏差(ACE)的影响。文中通过具体案例和MATLAB代码展示了储能系统在不同SOC水平下的表现及其对系统稳定性的作用。同时,文章比较了储能单独调频与风火水储联合调频的效果,强调了储能系统在应对风电波动性和提高系统响应速度方面的重要作用。此外,作者提出了针对SOC变化率的参数整定方法以及多电源协同工作的优化策略,旨在减少ACE波动并确保系统稳定运行。 适合人群:从事电力系统调频研究的专业人士,尤其是熟悉Simulink仿真工具的研究人员和技术人员。 使用场景及目标:适用于希望深入了解储能系统在电力系统调频中作用的研究者和技术人员,目标是通过合理的SOC管理和多电源协同工作,优化调频效果,提高系统稳定性。 其他说明:文章提供了详细的MATLAB代码片段,帮助读者更好地理解和应用所讨论的概念。同时,文中提到的实际案例和仿真结果为理论分析提供了有力支持。

    欧姆龙PLC NJ中大型程序案例:结构化与面向对象编程的深度融合及应用

    内容概要:本文深入探讨了欧姆龙PLC NJ系列中大型程序中结构化编程与面向对象编程的结合及其应用。首先介绍了结构化编程作为程序框架的基础,通过功能块(FB)实现清晰的程序结构和流程控制。接着阐述了面向对象编程的理念,将现实世界的对象映射到程序中,利用类的概念实现模块化和可扩展性。两者结合提高了程序的容错率,增强了程序的稳定性和可维护性。文中通过多个实际案例展示了如何在工业自动化领域中应用这两种编程方法,如电机控制、设备类的创建、异常处理机制、接口实现多态性、配方管理和报警处理等。 适合人群:从事工业自动化领域的工程师和技术人员,尤其是那些希望提升PLC编程技能的人群。 使用场景及目标:适用于需要优化PLC程序结构、提高程序可靠性和可维护性的场合。目标是帮助工程师掌握结构化编程和面向对象编程的技巧,从而写出更加高效、稳定的PLC程序。 其他说明:文章强调了在实际项目中灵活运用两种编程方法的重要性,并提醒读者注意实时性要求高的动作控制应采用结构化编程,而工艺逻辑和HMI交互则更适合面向对象编程。

    matlab与聚类分析

    matlab与聚类分析。根据我国历年职工人数(单位:万人),使用有序样品的fisher法聚类。

    卡尔曼滤波生成航迹测量程序

    卡尔曼滤波生成航迹测量程序

    基于格子玻尔兹曼方法(LBM)的多孔电极浸润特性研究及其Python实现

    内容概要:本文详细介绍了利用格子玻尔兹曼方法(LBM)对多孔电极浸润特性的模拟研究。首先阐述了LBM的基本原理,包括碰撞和迁移两个关键步骤,并提供了相应的Python伪代码。接着讨论了如何处理多孔介质中的固体边界,特别是通过随机算法生成孔隙结构以及结合CT扫描数据进行三维重构的方法。文中还探讨了表面张力、接触角等因素对浸润过程的影响,并给出了具体的数学表达式。此外,文章提到了并行计算的应用,如使用CUDA加速大规模网格计算,以提高模拟效率。最后,作者分享了一些实用技巧,如通过调整松弛时间和润湿性参数来优化模拟效果,并强调了LBM在处理复杂几何结构方面的优势。 适合人群:从事电池研发、材料科学领域的研究人员和技术人员,尤其是关注多孔电极浸润性和电解液扩散机制的人群。 使用场景及目标:适用于希望深入了解多孔电极内部流体动力学行为的研究者,旨在帮助他们更好地理解和预测电极材料的浸润特性,从而改进电池设计和性能。 其他说明:尽管LBM在处理多孔介质方面表现出色,但在某些极端条件下仍需引入额外的修正项。同时,参数的选择和边界条件的设定对最终结果有着重要影响,因此需要谨慎对待。

    基于FPGA和W5500的TCP网络通信:Zynq扩展口开发测试平台(使用Vivado 2019.2纯Verilog实现)

    内容概要:本文详细介绍了在Zynq扩展口上使用FPGA和W5500实现TCP网络通信的过程。作者通过一系列实验和技术手段,解决了多个实际问题,最终实现了稳定的数据传输。主要内容包括:硬件搭建(SPI接口配置)、数据回环处理、压力测试及优化、多路复用扩展以及上位机测试脚本的编写。文中提供了大量Verilog代码片段,展示了如何通过状态机控制SPI通信、优化数据缓存管理、处理中断等问题。 适合人群:对FPGA开发和网络通信感兴趣的工程师,尤其是有一定Verilog编程基础的研发人员。 使用场景及目标:适用于需要在嵌入式系统中实现高效、稳定的TCP通信的应用场景。目标是帮助读者掌握FPGA与W5500结合进行网络通信的具体实现方法和技术细节。 其他说明:文章不仅提供了详细的代码实现,还分享了许多实践经验,如硬件连接注意事项、信号完整性问题的解决方案等。此外,作者还提到了未来的工作方向,如UDP组播和QoS优先级控制的实现。

    python3.10以上 可安装pyside6(类似pyqt),具体安装操作步骤

    python3.10以上 可安装pyside6(类似pyqt),具体安装操作步骤

    基于FDTD仿真的可调谐石墨烯超材料吸收体设计与实现

    内容概要:本文详细介绍了利用有限差分时域法(FDTD)进行可调谐石墨烯超材料吸收体的设计与仿真。文中解释了石墨烯超材料的基本结构(三层“三明治”结构)、关键参数(如化学势、周期、厚度等)及其对吸收性能的影响。同时展示了如何通过调整石墨烯的化学势来实现吸收峰的位置和强度的变化,以及如何优化结构参数以获得最佳的吸收效果。此外,还提供了具体的代码示例,帮助读者理解和重现相关实验结果。 适合人群:从事纳米光子学、超材料研究的专业人士,尤其是对石墨烯基超材料感兴趣的科研工作者和技术开发者。 使用场景及目标:适用于希望深入了解石墨烯超材料的工作原理及其潜在应用场景的研究人员;旨在探索新型可调谐光学器件的设计思路和发展方向。 其他说明:文中不仅分享了理论知识,还包括了许多实践经验,如避免常见错误、提高仿真相关效率的小技巧等。对于想要将研究成果应用于实际产品的团队来说,这些细节非常有价值。

    随机生成2字到10字的中文词组

    随机生成2字,3字,4字,5字,6字,7字,8字,9字,10字的中文词组20个

    【汽车电子电气架构】智能座舱域控平台设计:基于双片龍鷹一号SoC芯片的高性能硬件架构与多模态交互系统构建

    内容概要:本文详细探讨了智能座舱域控设计的发展历程和技术趋势。首先介绍了智能座舱从被动式交互到主动式交互的技术演变,包括硬件和交互方式的进步。随后,文章重点讨论了智能座舱功能发展趋势,涵盖车载显示技术的多屏化、大屏化和高端化,以及SoC芯片的多核异构架构和算力融合,强调了其在智能座舱中的核心作用。此外,还阐述了电子电气架构从分布式向集成化的转型,分析了其面临的挑战和未来趋势。最后,基于当前智能座舱的发展需求,提出了一种基于双片龍鷹一号芯片的新域控平台设计方案,详细描述了其硬件设计实现方案,旨在提供高性能、高可靠性的智能座舱解决方案。 适合人群:汽车电子工程师、智能座舱研发人员及相关领域的技术人员。 使用场景及目标:①帮助读者理解智能座舱的技术发展历程及其未来发展方向;②为智能座舱域控平台的设计和开发提供参考和技术支持;③探讨电子电气架构的转型对汽车行业的影响及应对策略。 其他说明:文章结合实际案例和技术数据,深入浅出地解释了智能座舱的各项技术细节,不仅提供了理论指导,还具有较强的实践意义。通过对智能座舱域控平台的全面剖析,有助于推动智能座舱技术的创新发展,提升用户体验。

    多智能体协同编队控制:无人机编队背后的Python实现与关键技术解析

    内容概要:本文详细介绍了多智能体协同编队控制的技术原理及其应用实例。首先通过生动形象的例子解释了编队控制的核心概念,如一致性算法、虚拟结构法和Leader-Follower模式。接着深入探讨了如何用Python实现基础的一致性控制,以及如何通过调整参数(如Kp、Ka)来优化编队效果。文中还讨论了实际工程中常见的问题,如通信延迟、避障策略和动态拓扑变化,并给出了相应的解决方案。最后,强调了参数调试的重要性,并分享了一些实用技巧,如预测补偿、力场融合算法和分布式控制策略。 适合人群:对多智能体系统、无人机编队控制感兴趣的科研人员、工程师和技术爱好者。 使用场景及目标:适用于希望深入了解多智能体协同编队控制理论并能够将其应用于实际项目的研究人员和开发者。目标是帮助读者掌握编队控制的关键技术和实现方法,提高系统的稳定性和可靠性。 其他说明:文章不仅提供了详细的理论讲解,还附有具体的代码示例,便于读者理解和实践。同时,作者结合自身经验分享了许多宝贵的调试技巧和注意事项,有助于读者在实际应用中少走弯路。

    评估管线钢环焊缝质量及其对氢脆的敏感性.pptx

    评估管线钢环焊缝质量及其对氢脆的敏感性.pptx

    C盘清理bat脚本自动清理C盘垃圾文件

    C盘清理bat脚本自动清理C盘垃圾文件

    GBT21266-2007 辣椒及辣椒制品中辣椒素类物质测定及辣度表示方法

    GBT21266-2007 辣椒及辣椒制品中辣椒素类物质测定及辣度表示方法

    弹跳球 XNA 游戏项目 演示如何使用 C# 在 Visual Studio XNA 中构建类似 arkanoiddx-ball 的游戏

    弹跳球 XNA 游戏项目。演示如何使用 C# 在 Visual Studio XNA 中构建类似 arkanoiddx-ball 的游戏。

    【人形机器人领域】宇树科技人形机器人:技术实力、市场炒作与应用前景分析

    内容概要:文章全面解析了宇树科技人形机器人的发展现状、技术实力、市场炒作现象及其应用前景和面临的挑战。宇树科技成立于2016年,凭借春晚舞台的惊艳亮相和社交媒体的热议迅速走红,其人形机器人具备先进的运动控制算法、传感器技术和仿生结构设计。然而,市场炒作现象如高价租赁、二手市场炒作和虚假宣传等影响了市场秩序。尽管存在炒作,人形机器人在工业、服务和家庭领域仍具广阔前景,但也面临技术升级、成本控制、安全性和政策监管等挑战。 适合人群:对机器人技术、人工智能以及科技发展趋势感兴趣的读者,包括科技爱好者、投资者和相关行业的从业者。 使用场景及目标:①帮助读者了解宇树科技人形机器人的技术特点和发展历程;②揭示市场炒作现象及其影响;③探讨人形机器人的应用前景和面临的挑战。 其他说明:文章强调了宇树科技人形机器人在技术上的突破和市场上的表现,同时也提醒读者关注市场炒作现象带来的风险,呼吁各方共同努力推动人形机器人产业健康发展。

    msvcp140.dll

    msvcp140.dll丢失怎样修复

    光学技术超透镜解决方案全球市场分析:前5强生产商排名及市场份额预测

    超透镜是一种将具有特殊电磁特性的纳米结构、按照一定方式进行排列的二维平面透镜,可实现对入射光振幅、相位、偏振等参量的灵活调控,在镜头模组、全息光学、AR/VR等方面具有重要应用,具有颠覆传统光学行业的潜力。 目前,超透镜解决方案的市场处于起步阶段,企业根据客户的具体需求和应用场景为其定制专用超透镜或超透镜产品。 根据QYResearch最新调研报告显示,预计2031年全球超透镜解决方案市场规模将达到29.26亿美元,未来几年年复合增长率CAGR为79.55%。 全球范围内,超透镜解决方案主要生产商包括Metalenz, Inc., Radiant Opto-Electronics (NIL Technology),迈塔兰斯、纳境科技、山河元景等,其中前五大厂商占有大约77.84%的市场份额。 目前,全球核心厂商主要分布在欧美和亚太地区。 就产品类型而言,目前红外超透镜解决方案是最主要的细分产品,占据大约96.76%的份额。 就产品类型而言,目前消费电子是最主要的需求来源,占据大约36.27%的份额。 主要驱动因素: 独特性能优势:超透镜解决方案具有更轻薄、成本更低、成像更好、更易集成、更高效及更易自由设计等优势。能以微米级厚度实现传统厘米级透镜功能,还可集多个光学元件功能于一身,大幅减小成像系统体积、重量,简化结构并优化性能。 技术创新推动:超透镜解决方案技术不断取得进步,设计技术和工艺水平持续提升,其性能和稳定性得以不断提高。制造工艺方面,电子束光刻等多种技术应用到超透镜解决方案生产中,推动超透镜解决方案向更高分辨率、更高产量、更大面积、更高性能的方向发展。 市场需求增长:消费电子、汽车电子、医疗、工业等众多领域快速发展,对高精度、高性能光学器件需求不断增加。如在手机摄像头中可缩小模组体积、提升成像分辨率和降低成本;在汽车电子领域能提高车载摄像头、激光雷达等传感器性能。

Global site tag (gtag.js) - Google Analytics