使用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的扩展,这大大提高了应用的FPS。ARB_Vertex_Buffer的扩展工作的和顶点数组很像,除了一点,那就是ARB_Vertx_Buffer把数据加载到显卡的高性能的显存里。大大降低了渲染时间。当然,这个扩展是依赖较新的硬件的,不是所有的图形卡都支持的,所以我们必须使用一些技术来进行平衡。
在这个教程里,我们将要:
1:从高度图里加载数据。
2: 使用顶点数组更加有效的把网格数据提交给OpenGL。
3: 通过VBO(Vertex_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; // FPS和FPS 计数器
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_ARRAY和GL_TEXTURE_COORD_ARRAY两个状态,然后我将要来设置我们的数据指针,我想,除非你有多个Mesh对象,否则你没有必要在渲染每个帧的时候都做这一步。但是这关系并不大,所以我不认为它是个大问题。
为一个特定的数据类型来设置一个指针,你需要使用对应的函数--glVertexPointer和glTexCoordPointer,在我们的例子里非常简单,把顶点需要变量的总数(一个顶点有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没有在glBegin和glEnd之间。因为没有必要。
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 英文教程知识点总结 #### 一、设置OpenGL在MacOS中的环境 在本章节中,作者详细介绍了如何在MacOS系统中设置OpenGL编程环境。 **所需工具:** - **编译器**:最常用且推荐的是Metrowerks Code...
亲测可用
资源内项目源码是来自个人的毕业设计,代码都测试ok,包含源码、数据集、可视化页面和部署说明,可产生核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,毕设答辩评审绝对信服的保底85分以上,放心下载使用,拿来就能用。包含源码、数据集、可视化页面和部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.txt文件,仅供学习参考, 切勿用于商业用途。
【项目资源】: 物联网项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
【项目资源】: 单片机项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
【项目资源】: 单片机项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
【项目资源】: 物联网项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
IDE护眼主题套件
内容概要:文章详细介绍了基于Matlab/Simulink构建的增程式电动车仿真模型。该模型由电池、电机、发动机、整车动力学、控制策略和驾驶员模块六大组件构成,重点在于各模块间的能量流动逻辑。文中特别强调了功率跟随控制策略,通过PID闭环控制使发动机功率与电池需求动态匹配,优化了燃油经济性和SOC控制精度。此外,模型采用开放式架构,所有参数通过m脚本集中管理,便于修改和扩展。文章展示了模型在典型工况下的性能表现,并突出了其在科研和工程应用中的灵活性和实用性。; 适合人群:对新能源汽车技术感兴趣的工程师、研究人员以及高校相关专业师生。; 使用场景及目标:①用于研究增程式电动车的能量管理策略;②作为教学案例帮助学生理解复杂系统的建模方法;③为实际工程项目提供可复用的仿真平台。; 阅读建议:读者应重点关注模型的架构设计和关键控制算法实现,同时结合提供的代码片段进行实践操作,以便更好地掌握增程式电动车的工作原理及其优化方法。
51a30-main.zip
内容概要:本文详细介绍了多种类型的数据库索引及其应用场景,包括普通索引、唯一性索引、单个索引、复合索引、聚簇索引、非聚簇索引、主索引、外键索引、全文索引和空间索引。每种索引都有其独特的定义、要点和适用场景,并附有具体的SQL代码示例。此外,文章还对比了InnoDB和MyISAM两种存储引擎的特点,解释了脏读、不可重复读、可重复读和幻读的概念,并讨论了SQL优化的方法以及数据库事务的ACID特性。 适合人群:具备一定数据库基础知识的开发者、数据库管理员以及参与数据库设计和优化的技术人员。 使用场景及目标:①帮助开发者选择合适的索引类型以提高查询效率;②理解不同存储引擎的特点,选择最适合应用场景的存储引擎;③掌握事务隔离级别的概念,避免数据不一致问题;④学习SQL优化技巧,提升数据库性能;⑤理解ACID特性,确保数据库操作的一致性和可靠性。 阅读建议:本文内容较为全面且深入,建议读者结合实际项目需求,重点理解不同类型索引的应用场景,掌握SQL优化的基本原则,并熟悉事务处理的最佳实践。
内容概要:本文详细介绍了MATLAB中优化算法的实现方法,涵盖确定性算法(如梯度下降法)和随机性算法(如遗传算法、粒子群优化)。文章首先讲解了梯度下降法和MATLAB优化工具箱的应用,展示了如何使用fmincon解决约束优化问题。接着,文章深入探讨了线性规划、非线性规划和多目标优化的理论和实践,提供了具体的MATLAB代码示例。此外,文中还介绍了遗传算法、粒子群优化和模拟退火算法的原理及应用,并通过实例展示了这些算法在实际问题中的使用。最后,文章讨论了优化算法在工程、金融和机器学习领域的高级应用,以及调试和优化的常见策略。 适合人群:具备一定编程基础,对优化算法感兴趣的工程师、研究人员和学生。 使用场景及目标:①理解优化算法的基础理论和实现方法;②掌握MATLAB优化工具箱的使用,解决线性、非线性、多目标优化问题;③学习遗传算法、粒子群优化和模拟退火算法的具体应用;④提高优化算法的性能和可靠性,解决实际工程、金融和机器学习问题。 阅读建议:本文内容丰富,涉及多种优化算法及其MATLAB实现,建议读者先掌握基本的优化理论和MATLAB编程基础,再逐步深入学习各类算法的具体应用。在学习过程中,结合提供的代码示例进行实践,并尝试调整参数以优化算法性能。
this is for myself learn coding, change a pc debug.
项目资源包含:可运行源码+sql文件 适用人群:学习不同技术领域的小白或进阶学习者;可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 项目具有较高的学习借鉴价值,也可拿来修改、二次开发。 有任何使用上的问题,欢迎随时与博主沟通,博主看到后会第一时间及时解答。 开发语言:Python 框架:django Python版本:python3.8 数据库:mysql 5.7 数据库工具:Navicat 开发软件:PyCharm 浏览器:谷歌浏览器
【项目资源】: 单片机项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。
内容概要:本文深入探讨了MMC型STATCOM/SVG的核心技术和调试技巧,重点讲解了载波移相调制(CPS-PWM)和电压均衡控制两大关键技术。载波移相调制通过为每个子模块设置不同的载波相位差,有效降低谐波含量并优化开关频率。电压均衡则分为桥臂内、桥臂间和相间三个层次,分别采用动态排序、比例控制和零序电压注入等方法,确保系统稳定运行。文章还分享了多个实战经验,如低压调试、红外热像仪检测以及避免参数设置不当引发的问题。; 适合人群:从事电力电子领域,特别是参与STATCOM/SVG项目的设计、开发和调试的技术人员。; 使用场景及目标:①理解MMC型STATCOM/SVG的工作原理和技术细节;②掌握载波移相调制的具体实现方法;③学习电压均衡控制的各种策略及其应用场景;④获取实际调试过程中常见问题的解决方案。; 阅读建议:本文涉及大量技术细节和实战经验,建议读者结合实际项目进行阅读,重点关注载波移相调制和电压均衡控制的具体实现,并参考提供的代码片段进行实践。
liangmmm_finalll.scdoc
内容概要:本文详细介绍了Solidity语言的核心概念和语法特性,涵盖结构体、函数修改器、事件、类型系统、数组、映射、操作符、合约可见性、构造函数、抽象合约、接口、继承、控制结构、异常处理和keccak256哈希函数等内容。通过这些知识点的讲解,帮助开发者理解如何构建高效、安全的智能合约。; 适合人群:对区块链开发感兴趣,尤其是希望深入了解以太坊智能合约开发的初学者及有一定编程基础的研发人员。; 使用场景及目标:①掌握Solidity语言的基本语法和高级特性,如结构体、函数修改器、事件等;②理解合约的可见性、继承、接口等面向对象编程特性;③学会使用keccak256等安全机制保障智能合约的安全性;④能够运用控制结构和异常处理编写健壮的合约逻辑。; 阅读建议:建议读者从基础语法开始逐步深入,结合实际案例进行练习。尤其要注意合约的安全性和性能优化,避免常见的漏洞和错误。在学习过程中,应多参考官方文档和其他优质资料,不断巩固和拓展知识体系。
原型模式课上代码.zip