首页 [西门子plc编程入门]DIRECTX9编程入门

[西门子plc编程入门]DIRECTX9编程入门

举报
开通vip

[西门子plc编程入门]DIRECTX9编程入门[西门子plc编程入门]DIRECTX9编程入门 [西门子plc编程入门]DIRECTX9编程入门 篇一 : DIRECTX9编程入门 ?窗口类WNDCLASS struct WNDCLASS { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCSTR lpsz...

[西门子plc编程入门]DIRECTX9编程入门
[西门子plc编程入门]DIRECTX9编程入门 [西门子plc编程入门]DIRECTX9编程入门 篇一 : DIRECTX9编程入门 ?窗口类WNDCLASS struct WNDCLASS { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCSTR lpszMenuName; LPCSTR lpszClassName; }; style:用来定义窗口的行为。如果打算共同使用GDI和D3D的话,可以使用CS_OWNDC作为参数。 lpfnWndProc:一个函数指针,指向与这个窗口类绑定在一起的处理窗口消息的函数。 cbClsExtra和cbWndExtra:为窗口和为分配内存空间。很少使 用到这两个参数,一般设为0; hInstance:应用程序的实例句柄。你可以使用GetModuleHandle 来得到它,也可以从Win32程序的入口函数WinMain那里得到它。当然,你也可以把它设为NULL hIcon,hCursor,hbrBackground:设置默认的图标、鼠标、背景颜色。不过在这里设置这些其实并不怎么重要,因为我们可以在后面定制自己的渲染 方法 快递客服问题件处理详细方法山木方法pdf计算方法pdf华与华方法下载八字理论方法下载 。 lpszMenuName:用来创建菜单 lpszClassName:窗口类的名字。我们可以通过这个名字来创建以这个窗口类为模板的窗口。甚至可以通过这个名字来得到窗口的句柄。 设置好窗口类结构的内容后,使用RegisterClass函数来注册它。关闭窗口后可以用UnregisterClass来撤销注册。 ?创建窗口CreateWindow HWND CreateWindow; lpClassName:窗口类的名字。即窗口类结构体中的lpszClassName成员。 lpWindowName:如果你的应用程序有标题栏,这个就是你标题栏上显示的内容。 dwStyle:窗口的风格决定你的窗口是否有标题栏、最大最小化按钮、窗口边框等属性。在全屏的模式下,WS_POPUP|WS_VISIBLE 是常用的设置,因为它产生一个不带任何东西的全屏窗口。在窗口的模式下,你可以设置很多窗口的风格,具体可以查看相关资料,这里 不详细 说明 关于失联党员情况说明岗位说明总经理岗位说明书会计岗位说明书行政主管岗位说明书 ,不过WS_OVERLAPPED|WS_SYSMENU|WS_VISIBLE 是一组常用的风格。 x和y:窗口创建的位置。表示窗口的左上角位置。 nWidth和nHeight:用来设置窗口的宽度和高度,以像素为单位。如果你想创建一个全屏的窗口,使用GetSystemMetrics和GetSystemMetrics可以得到当前显示器屏幕的大小 hWndParent:指定这个新建窗口的父窗口。在D3D应用程序中很少用,一般设为NULL。 hMenu:菜单句柄。 hInstance:应用程序的实例句柄。你可以使用GetModuleHandle 来得到它,也可以从Win32程序的入口函数WinMain那里得到它。当然,你也可以把它设为NULL lpParam:一个很神秘的参数。除非你知道自己在做什么,否则还是把它设为NULL吧。 ?销毁窗口DestroyWindow 销毁窗口有两种方法,一种是隐式的,一种是显式的。我们都知道Windows操作系统是一个基于消息驱动的系统。流动于系统中的消息使我们的窗口跑起来。在很多软件开发特别是商业软件的开发过程中,窗口的产生和销毁都是交由系统去做的,因为这些不是这类开发的关注所在。但是游戏开发不一样,尽管你也可以只向系统发送一条WM_DESTROY消息来销毁窗口,我们还是希望窗口是销毁的明明白白的。由于窗口的注册、产生和使用都是由我们亲手来做的, 那么当然窗口的销毁也得由我们亲自来做。不过还是得说明一点,使用WM_DESTROY消息和DestroyWindow函数来销毁窗口在本质上并无太大差别,使用哪种方法可以说是根据个人的爱好吧。 销毁窗口后是不是就完事了呢,不,还没有,因为应用程序的消息队列里可能还有没处理完的消息,为了彻底的安全,我们还得把那些消息都处理完。所以结束应用程序的时候,可以使用以下方法: MSG msg; DestroyWindow; while) { TranslateMessage; DispatchMessage; } ?窗口消息处理过程 窗口消息的处理函数是一个回调函数,什么是回调函数,就是由操作系统负责调用的函数。CALLBACK这个宏其实就是__stdcall,这是一种函数调用的方式,在这里不多说这些了,有兴趣的可以参考一些Windows编程的 关于书的成语关于读书的排比句社区图书漂流公约怎么写关于读书的小报汉书pdf 籍,里面会有很详尽的说明。 Windows里面有很多消息,这些消息都跑去哪里了呢,其实它们都在自己的消息队列里等候。消息是怎么从队列里出去的呢,就是通过GetMessage和PeekMessage这两个函数。那么消息从队列里出去后又到哪里了呢,嗯,这时候消息就正式进入了我们的窗口消息处 理过程,也即是窗口类中lpfnWndProc所指定的函数。一个消息处理函数有四个参数,下面分别说说: 参数1:HWND p_hWnd 消息不都是传到以窗口类为模板产生的窗口吗,为什么还要使用窗口句柄来指明窗口呢,别忘了一个窗口类是可以产生多个窗口的呀,如果一个应用程序里面有多个窗口,并且它们之中的一些窗口是共用一个窗口类的,那么就得用一个窗口句柄来指明究竟这个消息是哪个窗口发过来的。 参数2:UINT p_msg 这是一个消息类型,就是WM_KEYDOWN , WM_CLOSE , WM_TIMER这些东东。 参数3:WPARAM p_wparam 这个参数内容就是消息的主要内容。如果是WM_KEYDOWN消息,那么p_wparam就是用来告诉你究竟是哪个键被按下。 参数4:LPARAM p_lparam 这个参数的内容一般是消息的一些附加内容。 最后说明一下DefWindowProc的作用。有时候我们把一个消息传到窗口消息处理函数里面,但是里面没有处理这个消息的内容。怎么办,很容易,交给DefWindowProc处理就对了。 ?创建IDirect3D接口 DirectX是一组COM组件,COM是一种二进制 标准 excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载 ,每一个COM里面提供了至少一个接口,而接口就是一组相关的函数,我们 使用DirectX,其实就是 使用那些函数。COM和C++中的类有点像,只不过COM使用自己的方法来创建实例。创建COM实例的一般方法是使用coCreateInstance函数。有关coCreateInstance的使用方法,可以参考有关COM方面的资料,这里暂时不详细说明了,因为DirectX提供了更简洁的方法来创建DirectX组件的实例。这一章我要讲的就是Direct3D组件的使用方法。 为了使用D3D中的函数,我们得先定义一个指向IDirect3D9这个接口的指针,顺便说明一下为什么要定义这个指针。首先,我们要知道接口的内容就是一些纯虚拟函数,所以接口是不能被实例化的,但是我们可以定义一个指向接口的指针。其次,我们要知道利用多态性我们可以使用一个基类指针来访问派生类中的方法。既然接口是不能被实例化的,那么我们肯定是使用从接口派生出来的类的方法。怎么获到这个派生类的指针呢,就是通过之前定义的接口指针来获得。所以我们所需做的就是把一个接口指针的地址传给某个函数,让这个函数来帮我们获到正确的派生类指针,这样我们就可以使用接口指针来做一些实际的东西了。实际上,我们只需要知道接口里面有什么方法以及它能完成什么工作就行了,至于这些方法是怎么实现的我们不必去关心。我们要做的就是定义一个接口指针,把它传给某个函数,函数使我们的接口指针有意义,接着我们使用接口,就这么简单。定义完这个接口指针后,例如IDirect3D9 *g_pD3D;现在我们使用Direct3DCreate9这个函数来创建一个D3D接口: g_pD3D = Direct3DCreate9; Direct3DCreate9这个函数只有一个参数,它表明要创建接口的版本。如果你想创建一个老的接口版本当然也可以,不过没有人会那样做吧。 创建接口后就可以创建D3D设备了,什么是D3D设备,你可以想象为你机上的那块显卡~什么,你有几块显卡~~没关系,那就创建多几个D3D设备接口吧。创建D3D设备需要的参数很多,如果把那些参数都挤在一个函数里面,那就太长了,所以就把一些参数放进结构体里面,只要先设定好这些结构体,再把这些结构体当作参数传给创建D3D设备的函数,那就清晰多了。首先要讲的就是D3DPRESENT_PARAMETERS这个结构。下面是它的定义: struct D3DPRESENT_PARAMETERS{ UINT BackBufferWidth; UINT BackBufferHeight; D3DFORMAT BackBufferFormat; UINT BackBufferCount; D3DMULTISAMPLE_TYPE MultiSampleType; DWORD MultiSampleQuality; D3DSWAPEFFECT SwapEffect; HWND hDeviceWindow; BOOL Windowed; BOOL EnableAutoDepthStencil; D3DFORMAT AutoDepthStencilFormat; DWORD Flags; UINT FullScreen_RefreshRateInHz; UINT PresentationInterval; }; BackBufferWidth和BackBufferHeight:后备缓冲的宽度和高度。在全屏模式下,这两者的值必需符合显卡所支持的分辨率。例如,。 BackBufferFormat:后备缓冲的格式。这个参数是一个D3DFORMAT枚举类型,它的值有很多种,例如D3DFMT_R5G6B5,这说明后备缓冲的格式是每个像素16位,其实红色占5位,绿色占6位,蓝色占5位,为什么绿色会多一位呢,据说是因为人的眼睛对绿色比较敏感。DX9只支持16位和32位的后备缓冲格式,24位并不支持。如果对这D3DFORMAT不熟悉的话,可以把它设为D3DFMT_UNKNOWN,这时候它将使用桌面的格式。 BackBufferCount:后备缓冲的数目,范围是从0到3,如果为0,那就当成1来处理。大多数情况我们只使用一个后备缓冲。使用多个后备缓冲可以使画面很流畅,但是却会造成输入设备响应过慢,还会消耗很多内存。 MultiSampleType和MultiSampleQuality:前者指的是全屏抗锯齿的类型,后者指的是全屏抗锯齿的质量等级。这两个参数可以使你的渲染场景变得更好看,但是却消耗你很多内存资源,而且,并不是所有的显卡都支持这两者的所设定的功能的。在这里我们分别把它们 设为D3DMULTISAMPLE_NONE和0。 SwapEffect:交换缓冲支持的效果类型,指定表面在交换链中是如何被交换的。它是D3DSWAPEFFECT枚举类型,可以设定为以下三者之一:D3DSWAPEFFECT_DISCARD,D3DSWAPEFFECT_FLIP,D3DSWAPEFFECT_COPY。如果设定为D3DSWAPEFFECT_DISCARD,则后备缓冲区的ufferWidth和BackBufferHeight的值就得符合显示模式中所设定的值。 EnableAutoDepthStencil:如果要使用Z缓冲或模板缓冲,则把它设为TRUE。 AutoDepthStencilFormat:如果不使用深度缓冲,那么这个参数将没有用。如果启动了深度缓冲,那么这个参数将为深度缓冲设定缓冲格式 Flags:可以设置为0或D3DPRESENTFLAG_LOCKABLE_BACKBUFFER。不太清楚是用来做什么的,看字面好像是一个能否锁定后备缓冲区的标记。 FullScreen_RefreshRateInHz:显示器的刷新率,单位是HZ,如果设定了一个显示器不支持的刷新率,将会不能创建设备或发出警告信息。为了方便,一般设为D3DPRESENT_RATE_DEFAULT就行了。 PresentationInterval:如果设置为 D3DPRENSENT_INTERVAL_DEFAULT,则说明在显示一个渲染画面的时候必要等候显示器刷新完一次屏幕。例如你的显示器刷新率设为80HZ的话,则一秒内你最多可以显示80个渲染画面。另外你也 可以设置在显示器刷新一次屏幕的时间内显示1到4个画面。如果设置为D3DPRENSENT_INTERVAL_IMMEDIATE,则表示可以以实时的方式来显示渲染画面,虽然这样可以提高帧速,但是却会产生图像撕裂的情况。 ?创建IDirect3DDevice界面 当你把D3DPRESENT_PARAMETERS的参数都设置好后,就可以创建一个D3D设备了,和创建D3D接口一样,先定义一个接口指针IDirect3DDevice9 * g_pD3DDevice;然后使用D3D接口里面的CreateDevice函数来创建设备。CreateDevice的声明为: HRESULT CreatDevice; Count:说明你要清空的矩形数目。如果要清空的是整个客户区窗口,则设为0; pRects:这是一个D3DRECT结构体的一个数组,如果count中设为5,则这个数组中就得有5个元素。它可以使我们只清除屏幕中的某一部分。 Flags:一些标记组合,它指定我们要清除的目标缓冲区。只有三种标记:D3DCLEAR_STENCIL , D3DCLEAR_TARGET , D3DCLEAR_ZBUFFER。 分别为清除模板缓冲区、清除目标缓冲区、清除深度缓冲区。 Color:清除目标区域所使用的颜色。 float:设置Z缓冲的Z初始值。小于或等于这个Z初始值的Z值才会被改写,但它的值只能取0到1之间。如果还不清楚什么是Z 缓冲的话,可以自己找相关数据看一下,这里不介绍了,呵呵。 Stencil:设置范本缓冲的初始值。它的取值范围是0到2的n次方减1。其中n是范本缓冲的深度。 清除后备缓冲区后,就可以对它进行渲染了。渲染完毕,使用Present函数来把后备缓冲区的内容显示到屏幕上。 HRESULT Present; pSourceRect:你想要显示的后备缓冲区的一个矩形区域。设为NULL则表示要把整个后备缓冲区的内容都显示。 pDestRect:表示一个显示区域。设为NULL表示整个客户显示区。 hDestWindowOverride:你可以通过它来把显示的内容显示到不同的窗口去。设为NULL则表示显示到主窗口。 pDirtyRegion:高级使用。一般设为NULL。 ?顶点属性与顶点格式 顶点可谓是3D世界中的基本元素。在计算机所能描绘的3D世界中,任何物体都是由多边形构成的,可以是三边形,也可以是四边形等。由于三边形,即三角形所具有的特殊性质决定其在3D世界中得到广泛的使用。构成三角形需要三个点,这些点的性质就是这章所要讲的内容。 也许你已经知道顶点的结构定义,你可能会奇怪为什么D3D会知道我们―随便‖定义的那些结构呢,其实那些顶点的定义可不是那么随便的哦。下面列举在Direct3D中,顶点所具有的所有属性。 位置:顶点的位置,可以分别指定x,y,x三个值,也可以使用 D3DXVECTOR3结构来定义。 RHW:齐次坐标W的倒数。如果顶点为变换顶点的话,就要有这个值。设置这个值意味着你所定义的顶点将不需要Direct3D的辅助,要求你自己对顶点数据进行处理。至于W是什么,W和XYZ一样,只是一个四元组的一部分。RHW的英文是Reciprocal of the Homogenous W,即1/W,它是为了处理矩阵的工作变得容易一些。一般设RHW的值为1.0。 混合加权:用于矩阵混合。高级应用,这里不讲了 顶点法线:学过高等数学就应该知道法线是什么吧,在这里是指经过顶点且和由顶点引出的边相垂直的线,即和三角形那个面垂直。用三个分量来描述它的方向,这个属性用于光照计算。 顶点大小:设定顶点的大小,这样顶点就可以不用只占一个像素了。 漫反射色:即光线照射到物体上产生反射的着色。理解这个比较麻烦,因为3D光照和真实光照没什么关系,不能像理解真实光照那样去理解3D光照。 镜面反射色:它可以让一个3D物体的表面看起来很光滑。 纹理坐标:如果想要在那些用多边形组成的物体上面贴上纹理,就要使用纹理坐标。由于纹理都是二维的,所以用两个值就可以表示纹理上面某一点的位置。在纹理坐标中,只能在0.0到1.0之间取值。例如表示纹理的左上角,表示纹理的右下角。 好了,请记住上面属性的顺序。我们定义一个顶点结构的时候, 不一定要包括全部的属性,但是一定要按照上面的顺序来定义。例如: struct MYVERTEX { D3DXVECTOR3 position; float rhw; D3DCOLOR color; } 上面定义了一个有漫反射色的变换顶点。 定义完了顶点的结构后,我们就要告诉D3D我们定义的是什么格式。为了方便,我们通常会用#define来定义一个叫做描述―灵活顶点格式‖的宏。例如:#define MYFVF D3DFVF_XYZ | D3DFVF_NORMAL。根据之前定义的顶点属性结构体,我们要定义相对应的宏。假如顶点结构中有位置属性,那么就要使用D3DFVF_XYZ;如果是变换顶点的话,就要使用D3DFVF_XYZRHW;如果使用了漫反射色属性的话,就要使用D3DFVF_DIFFUSE。这些值是可以组合使用的,像上面那样用―|‖符号作为连结符。定义完灵活顶点格式后,使用IDirect3DDevice9::SetFVF函数来告诉D3D我们所定义的顶点格式,例如:g_pD3DDevice->SetFVF; ?顶点缓冲 处理顶点信息的地方有两个,一个是在数组里,另一个是在D3D所定义的顶点缓冲里。换个说法的话就是一个在我们所能直接操作的内存里,另一个在D3D管理的内存里。对于我们这些对操作 系统底层了解不多的菜鸟来说,直接操作内存实在是太恐怖了,所以还是交给D3D帮我们处理吧,虽然不知道背后有些什么操作。要想把顶点信息交给D3D处理,我们就要先创建一个顶点缓冲区,可以使用IDirect3DDevice9->CreateVertexBuffer,它的原型是: HRESULT CreateVertexBuffer; Length:缓冲区的长度。通常是顶点数目乘以顶点大小,使用Sizeof就可以知道顶点的大小了。 Usage:高级应用。设为0就可以了。 FVF:就是我们之前定义的灵活顶点格式。 Pool:告诉D3D将顶点缓冲存储在内存中的哪个位置。高级应用,通常可取的三个值是:D3DPOOL_DEFAULT,D3DPOOL_MANAGED,D3DPOOL_SYSTEMMEM。多数情况下使用D3DPOOL_DEFAULT就可以了。 ppVertexBuffer:返回来的指向IDirect3DVertexBuffer9的指针。之后对顶点缓冲进行的操作就是通过这个指针啦。到这里还要再提醒一下,对于这些接口指针,在使用完毕后,一定要使用Release来释放它。 pSharedHandle:设为NULL就行了。 得到一个指向IDirect3DVertexBuffer9的指针后,顶点缓冲也就创建完毕了。现在要做的就是把之前保存在数组中的顶点信息放在顶点缓冲区里面。首先,使用IDirect3DVertexBuffer9::Lock来锁定顶点缓冲区: HRESULT Lock; OffsetToLock:指定要开始锁定的缓冲区的位置。通常在起始位置0开始锁定。 SizeToLock:指定在锁定的缓冲区的大小。设为0的话就是表示要锁定整个缓冲区。 ppbData:用来保存返回的指向顶点缓冲区的指针。通过这个指针来向顶点缓冲区填充数据。 Flags:高级应用。通常设为0。 填充为顶点缓冲区后,使用IDirect3DDevice9::Unlock来解锁。 最后在渲染的时候使用IDirect3DDevice9::SetStreamSource来告诉D3D要渲染哪个顶点缓冲区里面的顶点。 HRESULT SetStreamSource; StreamNumber:设置数据流的数量。顶点缓冲最多可以使用16个数据流。确定所支持的数据流的数量,可以检查D3DCAPS中的MaxStreams成员的值。通常设为0,表示使用单数据流。 pStreamData:要与数据流绑定的数据。在这里我们要把顶点缓冲区与数据流绑定。 OffsetInBytes:设置从哪个位置开始读数据。设为0表示从头读起。 Stride:数据流里面数据单元的大小。在这里是每个顶点的大小。 ?索引缓冲 很多时候,相邻的三角形会共用一些顶点,例如组成四方形的 两个三角形就共用了一条边,即共用了两个顶点信息。如果不使用索引,我们需要六个顶点的信息来绘制这个四方形,但实际上绘制一个四方形只要四个顶点信息就足够了。如果使用了索引就不一样了,在顶点缓冲区里我们可以只保存四个顶点的信息,然后通过索引来读取顶点信息。要使用索引得先创建一个索引缓冲。也许读到这里你会有个疑问,创建一个索引缓冲不就更浪费内存空间了吗,其实不然,索引缓冲区的元素保存的是数字,一个数字所占用的内存肯定要比一个顶点所占用的小得多啦。当你节省了几千个顶点,你就会发现浪费那么一点点索引缓冲区是很值得的。 创建索引缓冲的函数是:IDirect3DDevice9::CreateIndexBuffer HRESULT CreateIndexBuffer; Length:索引缓冲区的长度。通常使用索引数目乘以sizeof或sizeof来设置,因为索引号的数据类型是字节或双字节,嗯,一个WORD只有两个字节,DWORD也就只有四个字节,比顶点的大小小多了吧。 Usage:和CreateVertexBuffer中的Usage设置一样。一般设为0。 Format:设置索引格式。不是D3DFMT_INDEX16就是D3DFMT_INDEX32的啦。 Pool:又是和CreateVertexBuffer中的一样。一般设为D3DPOOL_DEFAULT。 ppIndexBuffer:指向IDirect3DIndexBuffer9的指针。操作索引缓冲区就靠它的啦。记得使用完后要Release啊。 和填充顶点缓冲区一样,要填充索引缓冲区,要先使用IDirect3DIndexBuffer9::Lock来锁定缓冲区。 HRESULT Lock; 是不是和IDirect3DVertexBuffer9::Lock一样呢,具体说明也可以参照上面的内容。填充完之后使用IDirect3DIndexBuffer9::UnLock 来解锁。 最后使用IDirect3DDevice9::SetIndices来告诉设备要使用哪个索引。 HRESULT Setindices; pIndexData:设置使用哪个索引缓冲。 BaseVertexIndex:设置以顶点缓冲区中的哪个顶点为索引0。 有关顶点的知识就说到这了。一下章说说点、线、三角形这种D3D所支持的图元。 ?D3D中的图元简介 在D3D中,一共有三种基本图元,分别是点、线和三角形。点是最简单的图元,由它可以构成一种叫点列的图元类型。线是由两个不重合的点构成的,一些不相连的线组成的集合就叫线列,而一些首尾相连但不形成环路的线的集合就叫线带。同理,单独的三角形集合就叫三角形列,类似于线带的三角形集合就叫三角形带,另外,如果多个三角形共用一个顶点作为它们的一个顶点的话,那么这个集合就叫三角形扇。还是画图比较容易理解吧: 这些图元有什么用呢,基本上我们可以使用这些图元来画我们想要的任何物体。例如画一个四方形可以使用三角形带来画,画一 个圆则使用三角形扇。 现在介绍一种不需要顶点缓冲来渲染的方法,就是使用IDirect3DDevice9::DrawPrimitiveUP函数。UP就是User Pointer的意思,也即是说要使用用户定义的内存空间。 HRESULT DrawPrimitiveUP; PrimitiveType:要绘画的图元的种类。就是上面介绍的那六种类型。 PrimitiveCount:要绘画的图元的数量。假设有n个顶点信息,绘画的图元类型是点列的话,那么图元的数量就是n;如果绘画的图元类型是线列的话,那么图元的数量就是n/2;如果是线带的话就是n-1;三角形列就是n/3;三角形带就是n-2;三角形扇出是n-2。 pVertexStreamZeroData:存储顶点信息的数组指针 VertexStreamZeroStride:顶点的大小 ?使用顶点缓冲来绘画图元 很多时候我们使用顶点来定义图形之后,就把这些顶点信息放进顶点缓冲里面,然后再进行渲染。使用点顶缓冲的好处以及如何创建顶点缓冲我已经在上一章已讲过了,现在讲讲怎么把顶点缓冲里面的图元给画出来。其实也很简单,和上面的 IDirect3DDevice9::DrawPrimitiveUP函数差不多,我们使用IDirect3DDevice9::DrawPrimitive函数。不过在使用这个函数之前,我们得告诉设备我们使用哪个数据源,使用 IDirect3DDevice9::SetStreamSource函数可以设定数据源。 HRESULT SetStreamSource; StreamNumber:设置和哪个数据流梆定。如果使用单数据流的话,这里设为0。最多支持16个数据流。 pStreamData:要绑定的数据。也就是我们创建的顶点缓冲区里面的数 据。 OffsetInBytes:设置从哪个字节开始读起。如果要读整个缓冲区里面的数据的话,这里设为0。 Stride:单个数据元素的大小。如果数据源是顶点缓冲的话,那么这里 就是每个顶点信息的大小。 设置好数据源后,就可以使用IDirect3DDevice9::DrawPrimitive 来绘画 了。 HRESULT DrawPrimitive; PrimitiveType:要绘画的图元的种类。 StarVertex: 设置从顶点缓冲区中的第几个顶点画起。没有特殊情况当然是想把全部的顶点画出来啦,所以一般这里设置从0开始。 PrimitiveCount:要绘画的图元的数量。 ?向量 向量就是包含大小和方向的一个量。向量有2维的,也有3维 甚至4维的。在DX的所有结构体中,有一个结构体是用来表示3维向量的,它就是D3DVECTOR,这个结构体很简单,只有三个成员:x、y、z。一般来说,如果不涉及到向量运算的话,用这个结构体来定义一个向量就可以了。我们可以它来表示方向以及顶点在3D世界中的位置等。如果你要对那些向量进行一些运算的话,使用D3DVECTOR就很不方便了,因为在D3DVECTOR这个结构体中没有重载任何的运算符,如果想要做一个加法运算,就得分别对结构体中的每一个成员进行运算了。嘿嘿,不用怕,在DX里面有个叫D3DX的东东,它里面定义了很多方便我们进行数学计算的函数和结构。其中就有D3DXVECTOR2,D3DXVECTOR3,D3DXVECTOR4这三个结构体。看它们的名字就应该知道它们的作用了吧。对于2维和4维的结构体这里就不讲了,其实它们也很简单,和D3DXVECTOR3差不多。不过要说明一点的是D3DXVECTOR3是从D3DVECTOR派生过来的,说明它和D3DVECTOR一样,有x、y、z这三个成员,除此之外,D3DXVECTOR3还重载了小部分算术运算符,这样我们就可以像对待整型那样对D3DXVECTOR3的对象进行加减乘除以及判断是否相等的运算了。同时,由于D3DXVECTOR3是从D3DVECTOR派生过来的,所以两者的对象可以互相赋值,在这两种类型中随便转换。 还是简单说一下向量的数学运算吧。矢量的加减法很简单,就是分别把两个向量的各个分量作加减运算。向量的乘除法也很简单,它只能对一个数值进行乘除法,运算的结果就是向量中的各个分量分 别对那个数值进行乘除法后得出的结果。向量的模就是向量的长度,就是各个分量的平方的和的开方。向量的标准化就是使得向量的模为1,这对在3D世界中实现光照是很有用的。对于向量的运算,还有两个―乘法‖,那就是点乘和叉乘了。点乘的结果就是两个向量的模相乘,然后再与这两个向量的夹角的余弦值相乘。或者说是两个向量的各个分量分别相乘的结果的和。很明显,点乘的结果就是一个数,这个数对我们分析这 两个向量的特点很有帮助。如果点乘的结果为0,那么这两个向量互相垂直;如果结果大于0,那么这两个向量的夹角小于90度;如果结果小于0,那么这两个向量的夹角大于90度。对于叉乘,它的运算公式令人头晕,我就不说了,大家看下面的公式自己领悟吧?? //v3 = v1 X v2 v3.x = v1.y*v2.z – v1.z*v2.y v3.y = v1.z*v2.x – v1.x*v2.z v3.z = v1.x*v2.y – v1.y*v2.x 是不是很难记啊,如果暂时记不了就算了。其实我们主要还是要知道叉乘的意义。和点乘的结果不一样,叉乘的结果是一个新的向量,这个新的向量与原来两个向量都垂直,至于它的方向嘛,不知大家是否还记得左手定则。来,伸出你的左手,按照第一个向量指向第二个向量弯曲你的手掌,这时你的拇指所指向的方向就是新向量的方向了。通过叉乘,我们很容易就得到某个平面的法线了。 终于写完了上面的文字,描述数学问题可真是费劲,自己又不 愿意画图,辛苦大家了。如果你觉得上面的文字很枯燥,那也没关系。因为上面的不是重点,下面介绍的函数才是希望大家要记住的。 D3DX中有很多很有用的函数,它们可以帮助我们实现上面所讲的所有运算。不过下面我只说和D3DXVECTOR3有关的函数: 计算点乘:FLOAT D3DXVec3Dot 计算叉乘:D3DXVECTOR3* D3DXVec3Cross 计算模:FLOAT D3DXVec3Length 标准化向量:D3DXVECTOR3* D3DXVec3Normalize 对于D3DXVECTOR3的加减乘除运算,上面已经讲了,用+ - * / 就行了。 ?矩阵与矩阵运算 什么是矩阵,这个概念还真不好解释,不过学过线性代数的人肯定都知道矩阵长什么样,那我在这里就不解释了。在D3D中,定义矩阵的结构体是D3DMATRIX: typedef struct _D3DMATRIX { union { struct { float _11, _12, _13, _14; float _21, _22, _23, _24; float _31, _32, _33, _34; float _41, _42, _43, _44; }; float m[4][4]; }; } D3DMATRIX; 看这个结构的样子,你就应该很清楚怎么使用它来定义一个矩阵了吧。在这里我顺便说一下C++中union的特性吧。像上面定义的结构体所示,在union里面有两个部分,一个是结构体,另一个是二维数组,它有16个元素。在union中,所有的成员都是共用一个内存块的,这是什么意思呢,继续看上面的代码,结构体中的成员_11和成员m数组的第一个元素是共用一个内存空间,即它们的值是一样的,你对_11赋值的同时也对m[0][0]进行了赋值,_11和m[0] [0]的值是一样的。这样有什么好处呢,比如你定义了一个矩阵变量D3DMATRIX mat;你想访问矩阵中第三行第四列的元素,可以这样做:mat._34;另外也可以这样:mat.m[2][3]。看起来使用后者比较麻烦,不过当你把中括号里面的数换成i和j,使用mat.m[i][j]来访问矩阵中的元素,你就应该知道它的好处了吧。 实际上直接使用D3DMATRIX的情况不多,因为在D3DX中有个更好的结构体,那就是D3DXMATRIX。和D3DXVECTOR3相似,D3DXMATRIX是从D3DMATRIX继承过来的,它重载了很多运算符,使得矩阵的运算很简单。矩阵的运算方法我不打算多说了,下面只介绍和矩阵性质有关的三个函数。 产生一个单位矩阵:D3DXMATRIX *D3DXMatrixIdentity;//返回结果 求转置矩阵:D3DXMATRIX *D3DXMatrixTranspose;//目标矩 阵 求逆矩阵:D3DXMATRIX *D3DXMatrixInverse;//目标矩阵 至于什么是单位矩阵,什么是转置矩阵,什么是逆矩阵我就不 说了,可以看一下线性代数的书,一看就明白了。简单的加减乘除法 可以使用D3DXMATRIX结构体里面重载的运算符。两个矩阵相乘也 可以用函数来实现,这将在接下来的矩阵变换中讲到。 ?矩阵变换 矩阵的基本变换有三种:平移,旋转和缩放。 平移: D3DXMATRIX *D3DXMatrixTranslation //Z轴上的平移量 ; 绕X轴旋转: D3DXMATRIX *D3DXMatrixRotationX; 绕Y轴旋转: D3DXMATRIX *D3DXMatrixRotationY; 绕Z轴旋转: D3DXMATRIX *D3DXMatrixRotationZ; 绕指定轴旋转: D3DXMATRIX *D3DXMatrixRotationAxis; 缩放: D3DXMATRIX *D3DXMatrixScaling; 好了,这章就写这么一些东西。如果你觉得好像没学到什么的话,可能是因为不知道上面的知识有什么用吧。下一章我将介绍世界空间、视图空间以及投影,这三者对应的是世界矩阵、视图矩阵和投影矩阵。搞清楚这三个空间的作用后,我们就可以利用这章的知识使我们的3D世界动起来了。 好了,这章比较简单。写到这章的时候我才发现这不是入门手册,有一些重要但是我觉得没必要讲的东西我都没有讲明。如果是新手看我写的这些东西,搞不好还会被我迷惑了,呵呵。所以还是建议大家看DXSDK里面的说明文档,虽然是英文的,但是很详细,我现在都还没有看完呢。 嗯,前面四章把最基本的东西讲完了,使用前面的知识我们可以画一些简单的静止图形。下一章就开始讲矩阵了,它可以使我们的图形动起来。 无论计算机图形技术如何发展,只要它以二维的屏幕作为显示介质,那么它显示的图像即使多么的有立体感,也还是二维的。有时我会想,有没有以某个空间作为显示介质的的可能呢,不过即使有,也只能是显示某个范围内的图像,不可能有无限大的空间作为显示介质,如果有,那就是现实世界了。 既然显示器的屏幕是二维的,那么我们就要对图像作些处理,让它可以欺骗我们的眼睛,产生一种立体的真实感。在D3D中,这种处理就是一系列的空间变换,从模型空间变到世界空间,再变到视图空间,最后投影到我们的显示器屏幕上。 ?世界空间与世界矩阵 什么是模型空间呢,每个模型都有它自己的空间,空间的中心就是模型的中心。在模型空间里,只有模型上的不同点有位置的相对关系。那什么是世界空间呢,世界就是物体所存在的地方。当我们把一个模型放进世界里面去,那么它就有了一个世界坐标,这个世界坐标是用来标记世界中不同的模型所处的位置的。在世界空间里,世界的中心就是原点,也就是你显示器屏幕中间的那一点。我们可以在世界空间里摆放很多个模型,并且设置它们在世界空间中的坐标,这样模型与模型之间就有了相对的位置。 世界矩阵有什么用呢,我们可以利用它来改变世界空间的坐标。这样,在世界空间里面的模型就可以移动、旋转和缩放了。 我们可以使用上一章末尾所讲的那几个函数来产生世界矩阵。例如产生一个绕X轴旋转的转阵:D3DXMatrixRotationX。利用matrix这个矩阵,就可以使世界空间中的物体绕X轴转动1弧度。 可以结合后面的例子来理解世界矩阵。 ?视图空间与视图矩阵 世界空间建立起来后,我们不一定能看到模型,因为我们还没有―眼睛‖啊。在视图空间里,我们可以建立我们在三维空间中的眼睛:摄像机。我们就是通过这个虚拟的摄像机来观察世界空间中的模型的。所以视图空间也叫摄像机空间。 要建立起这个虚拟的摄像机,我们需要一个视图矩阵,产生视图矩阵的 一个函数是: D3DXMATRIX *D3DXMatrixLookAtLH; pOut:返回的视图矩阵指针 pEye:设置摄像机的位置 pAt:设置摄像机的观察点 pUp:设置方向―上‖ 这个函数的后缀LH是表示左手系的意思,聪明的你一定能够猜出肯定有个叫D3DXMatrixLookAtRH的函数。至于左手系和右手系的区别,这里就不多说了,记住左手系中的Z正方向是指向显示器里面的就行了。只能弄懂了视图矩阵的含义,建立视图矩阵完成可以不依赖函数,自己手动完成。视图矩阵其实就是定义了摄像机在世界空间中的位置、观察点、方向―上‖这些信息。 可以结合后面的例子来理解视图矩阵。 ?投影与投影矩阵 定义投影矩阵很像是定义摄像机的镜头,下面看它的函数声明: D3DXMATRIX *D3DXMatrixPerspectiveFovLH; pOut:返回的投影矩阵指针 fovY:定义镜头垂直观察范围,以弧度为单位。对于这个参数,下面是我的理解:如果定义为D3DX_PI/4,那么就是表示以摄像机的观察方向为平分线,上方45度角和下方45度角就是摄像机所能看到的垂直范围了。嗯,可以想象一下自己的眼睛,如果可以把自己眼睛 的fovY值设为D3DX_PI/2,那么我们就可以不用抬头就看得见头顶的东西了。如果设为D3DX_PI的话。。。我先编译一下试试。哈哈,结果啥也看不见。很难想象如果自己能同时看到所有方向的物体,那么将是一个怎样的画面啊。 Aspect:设置纵横比。如果定义为1,那么所看到的物体大小不变。如果定义为其它值,你所看到的物体就会变形。不过一般情况下这个值设为显示器屏幕的长宽比。 zn:设置摄像机所能观察到的最远距离 zf:设置摄像机所能观察到的最近距离 ?一小段代码 请看以下代码片段: D3DXMATRIXA16 matWorld; D3DXMatrixIdentity; D3DXMatrixRotationX/1000.0f ); g_pd3dDevice->SetTransform; D3DXVECTOR3 vEyePt; D3DXVECTOR3 vLookatPt; D3DXVECTOR3 vUpVec; D3DXMATRIXA16 matView; D3DXMatrixLookAtLH; g_pd3dDevice->SetTransform; D3DXMATRIXA16 matProj; D3DXMatrixPerspectiveFovLH; g_pd3dDevice->SetTransform; 通过上面三个转换,就建立了一个我们可以通过显示器屏幕来观察的3D世界。上面三个转换分别是: 从模型空间到世界空间的世界转换:SetTransform。 从世界空间到视图空间的视图转换:SetTransform。 从视图空间到到屏幕的投影转换:SetTransform。 现在来观察matWorld,matView,matProj这三个矩阵的特点。我们使用D3DXMatrixRotationX函数来产生了一个绕X轴旋转的转换矩阵,通过设置世界转换,在世界空间里面的物体将绕X轴作旋转。然后我们定义了三个三维的向量,用来设置摄像机的位置,观察方向和定义方向―上‖。使用D3DXMatrixLookAtLH函数来把这三个向量放进视图矩阵里面去。然后通过设置视图转换,我们就建立了一个虚拟的摄像机。最后通过D3DXMatrixPerspectiveFovLH函数,我们得到一个投影矩阵,用来设置虚拟摄像机的镜头。 我还是解释一下上面说的那个方向―上‖是什么东西吧。这个―上‖其实指的就是摄像机在刚建立的时候是如何摆放的,是向左边侧着摆,还是向右边侧着摆,还是倒过来摆,都是通过这个方向―上‖来指定的。按照正常的理解,摄像机的―上‖方向就是Y轴的正方向,但是我们可以指定方向―上‖为Y轴的负方向,这样世界建立起来后就是颠倒的了。不过颠倒与否,也是相对来说的了,试问在没有引力的世界中,谁能说出哪是上哪是下呢,是不是看得一头雾水啊,只要自 己亲手改变一下这些参数,就可以体会到了。 设置上面三个转换的先后顺序并不一定得按照世界到视图到投影这个顺序,不过习惯上按照这种顺序来写,感觉会好一点。 ?使用矩阵相乘来创建世界矩阵 在世界空间中的物体运动往往是很复杂的,比如物体自身旋转的同时,还绕世界的原点旋转。怎么实现这种运动呢,通过矩阵相乘来把两个矩阵―混‖在一起。现在我们假设某一物体建立在世界的原点上,看以下代码: //定义三个矩阵 D3DXMATRIX matWorld, matWorldY,matMoveLeft; //一个矩阵把物体移到处,一个矩阵使物体绕原点旋转 D3DXMatrixTranslation; D3DXMatrixRotationY; //第一次矩阵相乘。先旋转,再平移 D3DXMatrixMultiply; //第二次矩阵相乘。在第一次矩阵相乘的结果上,再以Y轴旋转 D3DXMatrixMultiply; //设置世界矩阵 m_pD3DDevice->SetTransform; 矩阵相乘的时候,矩阵的先后顺序很重要,如果顺序弄错了,物体就不会按我们预料的那样运动。从最后一次矩阵相乘看起,最后 相乘的两个矩阵是matWorld和matWorldY,其中matWorld又是由matWorldY和matMoveRight相乘得来的,那么这三个矩阵相乘的顺序就是。这个顺序意味着什么呢,第一个matWorldY使物体绕Y轴旋转,这时候的物体还处于原点,所以它绕Y轴旋转也就是绕自身的旋转。它转呀转呀,这时候matMoveRight来了,它把物体从移到了,这时候物体就不再是绕Y轴旋转了,它是在这个位置继续绕自身旋转。然后matWorldY又来了,它使物体再次以Y轴旋转,不过此时物体不在原点了,所以物体就以原点为中心作画圆的运动,这个圆的半径是 30。如果换一个顺序,把matMoveRight放在第一的话,那么就是先移动再旋转再旋转,这时候物体就只是画圆运动而已,它自身没有旋转。如果把matMoveRight放在最后,那么就是先旋转再旋转再移动,这时候物体就没有作画圆运动了,它只是在这个位置上作自身旋转。好了,理解这个需要一点点想象力。你可以先写好几个矩阵相乘的顺序,自己想象一下相乘的结果会使物体作什么运动,然后再编译执行程序,看看物体的运动是不是和自己想像中的一样,这样可以锻炼自己的空间思维能力。 好了,又写完一章了。下一章可能要过一些日子才能写。因为自己还没找到工作,国文中笔者所提供的脚本都是使用bash。 如同其他语言一样,通过我们使用任意一种文字编辑器,比如nedit、kedit、emacs、vi等来编写我们的shell程序。程序必须以下面的行开始: #!/bin/sh 符号#!用来告诉系统它后面的参数是用来执行该文件的程序。在这个例子中我们使用/bin/sh来执行程序。当编辑好脚本时,如果要执行该脚本,还必须使其可执行。 要使脚本可执行: chmod +x filename 然后,您可以通过输入: ./filename 来执行您的脚本。 linux shell编程 2007年05月21日 星期一 21:00 注释 在进行shell编程时,以#开头的句子表示注释,直到这一行的结束。我们真诚地建议您在程序中使用注释。如果您使用了注释,那么即使相当长的时间内没有使用该脚本,您也能在很短的时间内明白该脚本的作用及工作原理。 变量 在其他编程语言中您必须使用变量。在shell编程中,所有的变量都由字符串组成,并且您不需要对变量进行声明。要赋值给一个变量,您可以这样写: 变量名=值 取出变量值可以加一个美元符号在变量前面: #!/bin/sh #对变量赋值: a=―hello world‖ # 现在打印变量a的内容: echo ―A is:‖ echo $a 在您的编辑器中输入以上内容,然后将其保存为一个文件first。之后执行chmod +x first。使其可执行,最后输入./first执行该脚本。 这个脚本将会输出: 有时候变量名很容易与其他文字混淆,比如: 这并不会打印出‖this is the 2nd‖,而仅仅打印‖this is the ―,因为shell会去搜索变量numnd的值,但是这个变量时没有值的。可以使用花括号来告诉shell我们要打印的是num变量: 这将打印: this is the 2nd 有许多变量是系统自动设定的,这将在后面使用这些变量时进行讨论。[) 如果您需要处理数学表达式,那么您需要使用诸如expr等程序。除了一般的仅在程序内有效的shell变量以外,还有环境变量。由export关键字处理过的变量叫做环境变量。我们不对环境变量进行讨论,因为通常情况下仅仅在登录脚本中使用环境变量。 Shell命令和流程控制 在shell脚本中可以使用三类命令: 虽然在shell脚本中可以使用任意的unix命令,但是还是由一些相对更常用的命令。这些命令通常是用来进行文件和文字操作的。 常用命令语法及功能: echo ―some text‖: 将文字内容打印在屏幕上。 ls: 文件列表。 wc –l file wc -w file wc -c file: 计算文件行数 计算文件中的单词数 计算文件中的字符数。 cp sourcefile destfile: 文件拷贝。 mv oldname newname : 重命名文件或移动文件。 rm file: 删除文件。 grep ?pattern‘ file: 在文件内搜索字符串比如:grep ?searchstring‘ file.txt cut -b colnum file: 指定欲显示的文件内容范围,并将它们输出到标准输出设备比如:输出每行第5个到第9个字符cut –b 5-9 file.txt 千万不要和cat命令混淆,这是两个完全不同的命令。 cat file.txt: 输出文件内容到标准输出设备上。 file somefile: 得到文件类型。 read var: 提示用户输入,并将输入赋值给变量。 sort file.txt: 对file.txt文件中的行进行排序。 uniq: 删除文本文件中出现的行列比如: sort file.txt | uniq。 expr: 进行数学运算Example: add 2 and 3 expr 2 ―+‖ 3。 find: 搜索文件比如:根据文件名搜索find . -name filename -print。 tee: 将数据输出到标准输出设备 和文件比如:somecommand | tee outfile。 basename file: 返回不包含路径的文件名比如: basename /bin/tux将返回 tux。 dirname file: 返回文件所在路径比如:dirname /bin/tux将返回 /bin。 head file: 打印文本文件开头几行。 tail file : 打印文本文件末尾几行。 sed: Sed是一个基本的查找替换程序。可以从标准输入读入文本,并将结果输出到标准输出。该命令采用正则表达式进行搜索。不要和shell中的通配符相混淆。比如:将linuxfocus 替换为 LinuxFocus :cat text.file | sed ?s/linuxfocus/LinuxFocus/‘ > newtext.file。 awk: awk 用来从文本文件中提取字段。缺省地,字段分割符是空格,可以使用-F指定其他分割符。cat file.txt 命令输出结果为: 2) 概念: 管道, 重定向和 backtick 这些不是系统命令,但是他们真的很重要。 在file.txt中搜索包含有‖hello‖的行并计算其行数。在这里grep命令的输出作为wc命令的输入。当然您可以使用多个命令。 重定向:将命令的结果输出到文件,而不是标准输出。 > 写入文件并覆盖旧文件。 >> 加到文件的尾部,保留旧文件内容。 反短斜线,使用反短斜线可以将一个命令的输出作为另外一个命令的一个命令行参数。 命令: 用来查找过去24小时内修改过的文件。如果您想将所有查找到的文件打一个包,则可以使用以下脚本: 3) 流程控制 ―if‖ 表达式 如果条件为真则执行then后面的部分: if ....; then .... elif ....; then .... else .... fi 大多数情况下,可以使用测试命令来对条件进行测试。比如可以比较字符串、判断文件是否存在及是否可读等等... 通常用‖ [ ] ―来表示条件测试。注意这里的空格很重要。要确保方括号的空格。 [ -f ―somefile‖ ] :判断是否是一个文件 [ -x ―/bin/ls‖ ] :判断/bin/ls是否存在并有可执行权限 [ -n ―$var‖ ] :判断$var变量是否有值 [ ―$a‖ = ―$b‖ ] :判断$a和$b是否相等 执行man test可以查看所有测试表达式可以比较和判断的类型。 直接执行以下脚本: #!/bin/sh if [ ―$SHELL‖ = ―/bin/bash‖ ]; then echo ―your login shell is the bash ― else echo ―your login shell is not bash but $SHELL‖ fi 变量$SHELL包含了登录shell的名称,我们和/bin/bash进行了比较。 快捷操作符 熟悉C语言的朋友可能会很喜欢下面的表达式: [ -f ―/etc/shadow‖ ] && echo ―This computer uses shadow passwors‖ 这里 && 就是一个快捷操作符,如果左边的表达式为真则执行右边的语句。替换成合适的文件名,它变量替换成变量值。为了防 止程序作这种替换,您可以使用引号:让我们来看一个例子,假设在当前目录下有一些文件,两个jpg文件, mail.jpg 和tux.jpg。 #!/bin/sh echo *.jpg 这将打印出‖mail.jpg tux.jpg‖的结果。 引号 将防止这种通配符扩展: #!/bin/sh echo ―*.jpg‖ echo ?*.jpg‘ 这将打印‖*.jpg‖ 两次。 单引号更严格一些。它可以防止任何变量扩展。双引号可以防止通配符扩展但允许变量扩展。 #!/bin/sh echo $SHELL echo ―$SHELL‖ echo ?$SHELL‘ 运行结果为: /bin/bash /bin/bash $SHELL 最后,还有一种防止这种扩展的方法,那就是使用转义字符——反斜杆: echo *.jpg echo $SHELL 这将输出: *.jpg $SHELL Here documents 当要将几行文字传递给一个命令时,here documents一种不错的方法。对每个脚本写一段帮助性的文字是很有用的,此时如果我们四有那个 here documents就不必用echo函数一行行输出。 一个 ―Here document‖ 以 #!/bin/sh # we have less than 3 arguments. Print the help text: if [ $# -lt 3 ] ; then cat ren -- renames a number of files using sed regular expressions USAGE: ren ?regexp‘ ?replacement‘ files... EXAMPLE: rename all *.HTM files in *.html: ren ?HTM$‘ ?html‘ *.HTM HELP exit 0 fi OLD=―$1‖ NEW=―$2‖ # The shift command removes one argument from the list of # command line arguments. shift shift # $* contains now all the files: for file in $*; do if [ -f ―$file‖ ] ; then newfile=`echo ―$file‖ | sed ―s/${OLD}/${NEW}/g‖` if [ -f ―$newfile‖ ]; then echo ―ERROR: $newfile exists already‖ else echo ―renaming $file to $newfile ...‖ mv ―$file‖ ―$newfile‖ fi fi done 这是一个复杂一些的例子。 。如果输入参数小于3个,则将帮助文字传递给cat命令,然后由cat命令将其打印在屏幕上。打印帮助文字后程序退出。 如果输入参数等于或大于3个,我们就将第一个参数赋值给变量OLD,第二个参数赋值给变量NEW。下一步, 我们使用shift命令将第一个和第二个参数从 参数列表中删除,这样原来的第三个参数就成为参数列表$*的第一个参数。然后我们开始循环,命令行参数列表被一个接一个地被赋值给变量$file。接着我 们判断该文件是否存在,如果存在则通过sed命令搜索和替换来产生新的文件名。然后将反短斜线内命令结果赋值给newfile。这样我们就达到了我们的目 的:得到了旧文件名和新文件名。然后使用mv命令进行重命名。 函数 如果您写了一些稍微复杂一些的程序,您就会发现在程序中可能在几个地方使用了相同的代码,并且您也会发现,如果我们使用了函数,会方便很多。一个函数是这个样子的: functionname { # inside the body $1 is the first argument given to the function # $2 the second ... body } 您需要在每个程序的开始对函数进行声明。 下面是一个叫做xtitlebar的脚本,使用这个脚本您可以改变终端窗口的名称。这里使用了一个叫做help 的函数。 { cat xtitlebar -- change the name of an xterm, gnome-terminal or kde konsole USAGE: xtitlebar [-h] ―string_for_titelbar‖ OPTIONS: -h help text EXAMPLE: xtitlebar ―cvs‖ HELP exit 0 } # in case of error or if -h is given we call the function help: [ -z ―$1‖ ] && help [ ―$1‖ = ―-h‖ ] && help # send the escape sequence to change the xterm titelbar: echo -e ―33]0;$107‖ # 在脚本中提供帮助是一种很好的编程习惯,这样方便其他用户使用和理解脚本。 命令行参数 我们已经见过$* 和 $1, $2 ... $9 等特殊变量,这些特殊变量包含了用户从命令行输入的参数。迄今为止,我们仅仅了解了一些简单的命令行语法。 但是在编写更复杂的程序时,您可能会发现您需要更多的自定义的选项。通常的惯例是在所有可选的参数之前加一个减号,后面再加上参数值 。 有好多方法可以实现对输入参数的分析,但是下面的使用case 表达式的例子无遗是一个不错的方法。 #!/bin/sh help { cat This is a generic command line parser demo. USAGE EXAMPLE: cmdparser -l hello -f -- -somefile1 somefile2 HELP exit 0 } while [ -n ―$1‖ ]; do case $1 in -h) help;shift 1;; # function help is called -f) opt_f=1;shift 1;; # variable opt_f is set -l) opt_l=$2;shift 2;; # -l takes an argument -> shift by 2 --) shift;break;; # end of options -*) echo ―error: no such option $1. -h for help‖;exit 1;; *) break;; esac done echo ―opt_f is $opt_f‖ echo ―opt_l is $opt_l‖ echo ―first arg is $1‖ echo ―2nd arg is $2‖ 您可以这样运行该脚本: cmdparser -l hello -f -- -somefile1 somefile2 返回的结果是: opt_f is 1 opt_l is hello first arg is -somefile1 2nd arg is somefile2 这个脚本是如何工作的呢,脚本首先在所有输入命令行参数中进行循环,将输入参数与case表达式进行比较,如果匹配则设置一个变量并且移除该参数。 转换为相应的十进制数。这也是一个用expr命令进行数学运算的例子: #!/bin/sh # vim: set sw=4 ts=4 et: help { cat b2h -- convert binary to decimal USAGE: b2h [-h] binarynum OPTIONS: -h help text EXAMPLE: b2h 111010 will return 58 HELP exit 0 } error { # print an error and exit echo ―$1‖ exit 1 } lastchar { # return the last character of a string in $rval if [ -z ―$1‖ ]; then # empty string rval=―― return fi # wc puts some space behind the output this is why we need sed: numofchar=`echo -n ―$1‖ | wc -c | sed ?s/ //g‘ ` # now cut out the last char rval=`echo -n ―$1‖ | cut -b $numofchar` } chop { # remove the last character in string and return it in $rval if [ -z ―$1‖ ]; then # empty string rval=―― return fi # wc puts some space behind the output this is why we need sed: numofchar=`echo -n ―$1‖ | wc -c | sed ?s/ //g‘ ` if [ ―$numofchar‖ = ―1‖ ]; then # only one char in string rval=―― return fi numofcharminus1=`expr $numofchar ―-‖ 1` # now cut all but the last char: rval=`echo -n ―$1‖ | cut -b 0-${numofcharminus1}` } while [ -n ―$1‖ ]; do case $1 in -h) help;shift 1;; # function help is called --) shift;break;; # end of options -*) error ―error: no such option $1. -h for help‖;; *) break;; esac done # The main program sum=0 weight=1 # one arg must be given: [ -z ―$1‖ ] && help binnum=―$1‖ binnumorig=―$1‖ while [ -n ―$binnum‖ ]; do lastchar ―$binnum‖ if [ ―$rval‖ = ―1‖ ]; then sum=`expr ―$weight‖ ―+‖ ―$sum‖` fi # remove the last position in $binnum chop ―$binnum‖ binnum=―$rval‖ weight=`expr ―$weight‖ ―*‖ 2` done echo ―binary $binnumorig is decimal $sum‖ # 该脚本使用的算法是利用十进制和二进制数权值 ,比如二进制‖10‖可以这样转换成十进制: 0 * 1 + 1 * 2 = 2 为了得到单个的二进制数我们是用了lastchar 函数。[)该函数使用wc -c计算字符个数,然后使用cut命令取出末尾一个字符。Chop函数的功能则是移除最后一个字符。 文件循环程序 或许您是想将所有发出的邮件保存到一个文件中的人们中的一员,但是在过了几个月以后,这个文件可能会变得很大以至于使对该文件的访问速度变慢。下面的 脚本rotatefile 可以解决这个问题。这个脚本可以重命名邮件保存文件为outmail.1,而对于outmail.1就变成了outmail.2 等等等等... #!/bin/sh # vim: set sw=4 ts=4 et: ver=―0.1‖ help { cat rotatefile -- rotate the file name USAGE: rotatefile [-h] filename OPTIONS: -h help text EXAMPLE: rotatefile out This will e.g rename out.2 to out.3, out.1 to out.2, out to out.1 and create an empty out-file The max number is 10 version $ver HELP exit 0 } error { echo ―$1‖ exit 1 } while [ -n ―$1‖ ]; do case $1 in -h) help;shift 1;; --) break;; -*) echo ―error: no such option $1. -h for help‖;exit 1;; *) break;; esac done # input check: if [ -z ―$1‖ ] ; then error ―ERROR: you must specify a file, use -h for help‖ fi filen=―$1‖ # rename any .1 , .2 etc file: for n in 9 8 7 6 5 4 3 2 1; do if [ -f ―$filen.$n‖ ]; then p=`expr $n + 1` echo ―mv $filen.$n $filen.$p‖ mv $filen.$n $filen.$p fi done # rename the original file: if [ -f ―$filen‖ ]; then echo ―mv $filen $filen.1‖ mv $filen $filen.1 fi echo touch $filen touch $filen 这个脚本是如何工作的呢,在检测用户提供了一个文件名以后,我们进行一个9到1的循环。如果在脚本‖strangescript‖ 中有错误,您可以这样来进行调试: sh -x strangescript 这将执行该脚本并显示所有变量的值。 shell还有一个不需要执行脚本只是检查语法的模式。可以这样使用: sh -n your_script 这将返回所有语法错误。 我们希望您现在可以开始写您自己的shell脚本,希望您玩得开心。 篇三 : Winsock编程入门 什么是Winsock Winsock是Windows下的网络编程接口,它是由Unix下的BSD Socket发展而来,是一个与网络 协议 离婚协议模板下载合伙人协议 下载渠道分销协议免费下载敬业协议下载授课协议下载 无关的编程接口。[) 构建编程环境 Winsock在常见的Windows平台上有两个主要的版本,即Winsock1和Winsock2。编写与Winsock1兼容的程序你需要引用头文件WINSOCK.H,如果编写使用Winsock2的程序,则需要引用WINSOCK2.H。此外还有一个MSWSOCK.H头文件,它是专门用来支持在Windows平台上高性能网络程序扩展功能的。使用WINSOCK.H头文件时,同时需要库文件WSOCK32.LIB,使用WINSOCK2.H时,则需要WS2_32.LIB,如果使用MSWSOCK.H中的扩展API,则需要MSWSOCK.LIB。正确引用了头文件,并链接了对应的库文件,你就构建起编写WINSOCK网络程序的环境了。 初始化Winsock 每个Winsock程序必须使用WSAStartup载入合适的Winsock动态链接库,如果载入失败,WSAStartup将返回SOCKET_ERROR, 这个错误就是WSANOTINITIALISED,WSAStartup的定义如下: int WSAStartup; wVersionRequested指定了你想载入的Winsock版本,其高字节指定了次版本号,而低字节指定了主版本号。你可以使用宏MAKEWORD来指定版本号,这里x代表主版本,而y代表次版本。lpWSAData是一个指向WSAData结构的指针,WSAStartup会向该结构中填充其载入的Winsock动态链 库的信息。 lpWSAData是一个指向WSAData结构的指针,WSAStartup会向该结构中填充其载入的Winsock动态链 库的信息。 lpWSAData是一个指向WSAData结构的指针,WSAStartup会向该结构中填充其载入的Winsock动态链 库的信息。 typedef struct WSAData { WORD wVersion; WORD wHighVersion; char szDescription[WSADESCRIPTION_LEN + 1]; char szSystemStatus[WSASYS_STATUS_LEN + 1]; unsigned short iMaxSockets; unsigned short iMaxUdpDg; char FAR * lpVendorInfo; } WSADATA, * LPWSADATA; wVersion为你将使用的Winsock版本号,wHighVersion为载入的Winsock动态库支持的最高版本,注意,它们的高字节代表次版本,低字节代表主版本。 szDescription与szSystemStatus由特定版本的Winsock设置,实际上没有太大用处。 iMaxSockets表示最大数量的并发Sockets,其值依赖于可使用的硬件资源。 iMaxUdpDg表示数据报的最大长度;然而,获取数据报的最大长度,你需要使用WSAEnumProtocols对协议进行查询。 最大数量的并发Sockets并不是什么神奇的数字,它是由可用的物理资源来决定的. lpVendorInfo是为Winsock实现而保留的制造商信息,这个在Windows平台上并没有什么用处. 自Windows 95以后的操作系统都支持Winsock 2.2的版本.即使是这样,你也不能认为这些Windows平台支持最新的Winsock版本,为了让你的程序能够运行于大多数平台,最好使用Winsock1.1规范. 当你使用完Winsock接口后,要调用下面的函数对其占用的资源进行释放: int WSACleanup; 如果调用该函数失败也没有什么问题,因为操作系统为自动将其释放,对应于每一个WSAStartup调用都应该有一个WSACleanup调用. 错误处理 Winsock函数调用失败大多会返回 SOCKET_ERROR,你可以调用WSAGetLastError得到错误的详细信息: int WSAGetLastError ; 对该函数的调用将返回一个错误码,其码值在WINSOCK.H或WINSOCK2.H中已经定义,这些预定义值都以WSAE开头.同时你还可以使用WSASetLastError来自定义错误码值. 以下是一个使用上述知识初始化Winsock的一个例子. #include void main { WSADATA wsaData; int Ret; // Initialize Winsock version 2.2 if , &wsaData)) != 0) { // NOTE: Since Winsock failed to load we cannot use // WSAGetLastError to determine the specific error for // why it failed. Instead we can rely on the return // status of WSAStartup. printf; return; } // Setup Winsock communication code here // When your application is finished call WSACleanup if == SOCKET_ERROR) { printf); 扩展:winsock网络编程经络 / winsock编程 / winsock网络编程 pdf } } 扩展:winsock网络编程经络 / winsock编程 / winsock网络编 程 pdf
本文档为【[西门子plc编程入门]DIRECTX9编程入门】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_614050
暂无简介~
格式:doc
大小:92KB
软件:Word
页数:47
分类:初中语文
上传时间:2017-09-28
浏览量:451