版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
本章內容編寫圖形程序學習OpenGL程序的基本組成開發(fā)繪制點、直線、折線和多邊形的基本圖形工具學會用鼠標和鍵盤控制程序1本章內容編寫圖形程序12.0vcopengl編程
指導例子熟悉代碼,多次練習見圖形學第二課opengl-console.doc,創(chuàng)建一個控制臺的應用程序,本書采用的編程方式見圖形學第二課opengl-nehe-win.doc,創(chuàng)建一個win32的應用程序,nehe教程(共48課)采用的編程方式見圖形學第三課opengl-nehe-mfc.doc,創(chuàng)建一個mfc的應用程序,本人推薦采用的編程方式,需要學習vc和mfc。22.0vcopengl編程
指導例子熟2.1生成圖像初步通過練習(編寫和測試各種不同圖形的程序)可以很快掌握計算機圖形學。先從簡單的任務開始,一旦掌握,就試著變變花樣,看看有哪些變化,試著進一步繪制更復雜的場景。最好的學習辦法是認真、仔細地學習每行代碼或等式。當你這么做時,就能理解每個新概念。每個圖形程序都以一些初始化工作為開始,由此建立程序所需要的顯示模型和坐標系。32.1生成圖像初步通過練習(編寫和測試各種不同圖形的程序2.1生成圖像初步初始化時,將顯示器設置為“圖形模式”,并且建立了坐標系,坐標x和y以像素為單位,x向右遞增,y向下遞增。42.1生成圖像初步初始化時,將顯示器設置為“圖形模式”,2.1.1設備無關的編程
和OpenGLOpenGL程序可以在不同的圖形環(huán)境里編譯和運行,即它是與設備無關的編程。OpenGL是一個開源圖形庫,可在下載。OpenGL常被稱為應用程序接口(API)。這個接口是程序員可以調用的例程,先從OpenGL繪制簡單的二維物體開始,然后學習繪制三維圖形。52.1.1設備無關的編程
和OpenGLO2.1.2窗口的編程事件驅動編程:事件包括單擊鼠標,按下鍵盤上的按鍵,或者重新調整窗口的大小。程序員將程序組織成回調函數的集合,這些回調函數一有事件發(fā)生就執(zhí)行。即什么也不做,等待事件發(fā)生,事件發(fā)生后再做指定的事。OpenGL有一個GLUT庫,它用來打開窗口,管理菜單和處理事件等。注冊回調函數:有一種方法將每種類型的事件與要求的回調函數關聯起來,這個方法稱之為注冊回調函數。程序中用到的每一個事件類型都必須用回調函數注冊,該回調函數的名字和定義由程序員選擇。62.1.2窗口的編程事件驅動編程:事件包括單擊鼠標,按下2.1.2窗口的編程下面是使用GLUT庫,名為myMouse的回調函數例子,它方便地注冊了與鼠標關聯的事件:glutMouseFunc(myMouse);glutMouseFunc是GLUT庫的固有函數,但是回調函數myMouse是程序員定義的,并由程序員編寫代碼,處理每個可能感興趣的鼠標動作。4種主要的OpenGL庫:1)基本GL庫,OpenGL庫的基礎。它提供OpenGL的基本函數。每個OpenGL函數都以字符GL開頭。72.1.2窗口的編程下面是使用GLUT庫,名為myMou2.1.2窗口的編程2)GLUT庫:GL實用工具包。它用來打開窗口,開發(fā)和管理菜單,以及管理事件等。3)GLU庫:GL實用庫,它提供高級例程,處理矩陣操作和繪制二次曲面如球和圓柱體;4)GLUI庫:用戶接口庫,提供控制工具和菜單。glutDisplayFunc(myDisplay):重繪窗口調用myDisplay回調函數;glutReshapeFun(myReshape):對屏幕窗口的形狀進行調整;82.1.2窗口的編程2)GLUT庫:GL實用工具包。它用voidmain(){glutDisplayFunc(myDisplay);//注冊重繪函數glutReshapeFunc(myReshape);//注冊改變窗口形狀函數glutMouseFunc(myMouse);//注冊鼠標動作函數glutMotionFunc(myMouse);//注冊鼠標移動函數glutKeyboardFunc(myKeyboard);//注冊鍵盤動作函數//可能初始化其他工作glutMainLoop();//進入主循環(huán)等待事件發(fā)生}9voidmain()92.1.3如何打開一個窗口畫圖glutInit:該函數初始化工具包glutDisplayMode(GLUT_SINGLE|GLUT_RGB):分配單個顯示緩存glutInitWindowSize(640,480):該函數指定屏幕的初始尺寸,寬640像素,高480像素。glutCreateWindow(“myfirstattempt”):該函數打開并顯示屏幕窗口嗎,并顯示標題。代碼如下:102.1.3如何打開一個窗口畫圖glutInit:該函數voidmain(intargc,char**argv){glutInit(&argc,argv);//初始化工具包glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB);//設置顯示模式glutInitWindowSize(640,480);//設置窗口大小glutInitWindowPosition(100,150);//設置窗口在屏幕上的位置glutCreateWindow("myfirstattempt");//打開屏幕窗口11voidmain(intargc,char**arg//注冊回調函數glutDisplayFunc(myDisplay);glutReshapeFunc(myReshape);glutMouseFunc(myMouse);glutKeyboardFunc(myKeyboard);myInit();//必要的其他初始化工作glutMainLoop();//進入循環(huán)}12//注冊回調函數122.2OpenGL的基本圖形元素屏幕坐標系:寬為640,高為640。x坐標從左邊界0增加到右邊界639,y坐標從0增加到上邊的479。132.2OpenGL的基本圖形元素屏幕坐標系:寬為640,高繪制三個點:glBegin(GL_POINTS);glVertex2i(100,50);glVertex2i(100,130);glVertex2i(150,130);glEnd();或者用浮點值代替整數值:glBegin(GL_POINTS);glVertex2d(100.0,50.0);glVertex2d(100.0,130.0);glVertex2d(150.0,130.0);glEnd();14繪制三個點:14OpenGL數據類型:命令后綴和參數數據類型OpenGL狀態(tài):OpenGL是由許多狀態(tài)變量組成的狀態(tài)機,這些狀態(tài)包括點的大小、繪圖的顏色額屏幕窗口大小等,點的大小可以用glPointSize()來設置,畫圖的顏色用這條語句設置:glColor3f(red,green,blue);坐標系的建立:1515voidmyInit(void){glMatrixMode(GL_PROJECTION);glLoadIdentity();gluOrtho2D(0,640.0,0,480.0);}myInit設置坐標系,設置透視投影變換。gluOrtho2D設置正交投影。組合在一起:建立起一個完整的OpenGL程序glFlush()函數保證所有的數據被完全處理并顯示。16voidmyInit(void)162.2.1幾個點叢繪制的例子點叢是由大量點組成的某種圖案。下面有幾個點叢例子。172.2.1幾個點叢繪制的例子點叢是由大量點組成的某種圖案例子2.2.1北斗星群
見計算機圖形學課件2016\計算機圖形學(Opengl版第三版)書源代碼word版\2.2.1-threeDots.doc例子2.2.2Sierpinski(塞平斯基)墊片見計算機圖形學課件2016\計算機圖形學(Opengl版第三版)書源代碼word版\2.2.1-threeDots.doc算法如下:第k個點表示為P(k)=(x(k),y(k)),每一個點都依賴于前一個點P(k-1),過程如下:(1)選擇三個固定的點T0,T1,T2,構成一個三角形,稱為每個Sierpinski墊片的父三角形;(2)隨機選擇父三角形頂點T0,T1,T2中的一點作為要繪制的初始點P0。迭代下面的(3)~(5)步,直到圖案填充完畢。(3)隨機選擇T0,T1,T2中的一點,稱為T。(4)構造下一個點P(k),作為T和前一個已建好的點P(k-1)之間的之間點,即P(k)=(P(k-1)+T)/2(5)用drawDot()繪制P(k)。18例子2.2.1北斗星群18例子2.2.2Sierpinski(塞平斯基)墊片//初始值P(0),在for循環(huán)外面GLintPointpoint=T[index];//初始值drawDot(point.x,point.y);//畫初始點for(inti=0;i<1000;i++)//畫1000點{index=rand()%3;//P(k)=(P(k-1)+T)/2point.x=(point.x+T[index].x)/2;point.y=(point.y+T[index].y)/2;drawDot(point.x,point.y);}19例子2.2.2Sierpinski(塞平斯基)墊片19例子2.2.3用點集繪制函數
見計算機圖形學課件2016\計算機圖形學(Opengl版第三版)書源代碼word版\2.2.1-threeDots.doc繪制某個數學函數f(x),當x在0~4變化時的曲線。當繪制曲線時,因為0~4之間的x值位于屏幕窗口左下角的前4個像素上,而且f(.)的負值位于窗口的下面,根本看不到。需要進行平移和縮放使曲線正好出現在合適的屏幕窗口。將世界坐標系轉換為窗口坐標系。x方向縮放:假定屏幕窗口為screenWidth,單位為像素,現在x的范圍為0~4,故sx=x*screenWidth/4.0;y方向縮放:假定屏幕窗口為screenHeight,單位為像素,f(x)的取值在–1.0和1.0之間,縮放screenHeight/2,平移為screenHeight/2,sy=(y+1.0)*screenHeight/2.0;sx,sy為屏幕坐標,x,y為窗口坐標;轉換方程為:sx=A*x+Bsy=C*y+D20例子2.2.3用點集繪制函數20constintscreenWidth=640;//widthofscreenwindowinpixelsconstintscreenHeight=480;//heightofscreenwindowinpixelsGLdoubleA,B,C,D;//valuesusedforscalingandshiftingvoidmyInit(void){glClearColor(1.0,1.0,1.0,0.0);//backgroundcoloriswhiteglColor3f(0.0f,0.0f,0.0f);//drawingcolorisblackglPointSize(2.0);//a'dot'is2by2pixelsglMatrixMode(GL_PROJECTION);//set"camerashape"glLoadIdentity();gluOrtho2D(0.0,(GLdouble)screenWidth,0.0,(GLdouble)screenHeight);A=screenWidth/4.0;//x方向的縮放量AB=0.0;//x方向的平移量BC=D=screenHeight/2.0;//y方向的縮放量C和平移量D}voidmyDisplay(void){glClear(GL_COLOR_BUFFER_BIT);//clearthescreenglBegin(GL_POINTS);for(GLdoublex=0;x<4.0;x+=0.005){Gldoublefunc=exp(-x)*cos(2*3.14159265*x);//sx=A*x+B,sy=C*y+DglVertex2d(A*x+B,C*func+D);}glEnd();glFlush();//sendalloutputtodisplay}21constintscreenWidth=640;/例子2.2.3用點集繪制函數
見計算機圖形學課件2016\計算機圖形學(Opengl版第三版)書源代碼word版\2.2.1-threeDots.docf(x)=sin(x),f(x)=sin(2x),x從-π到π,y從-1到1A=screenWidth/6.28;//x方向的縮放量A,屏幕寬度/x方向的變化范圍B=screenWidth/2.0;//x方向的平移量B,屏幕寬度的一半C=screenHeight/2.0;//y方向的縮放量C,屏幕高度/y方向的變化范圍
D=screenHeight/2.0;//y方向的平移量D,屏幕的高度的一半for(GLdoublex=-3.14;x<3.14;x+=0.005){Gldoublefunc=sin(x);Gldoublefunc1=sin(2x);//sx=A*x+B,sy=C*y+DglVertex2d(A*x+B,C*func+D);glVertex2d(A*x+B,C*func1+D);}
22例子2.2.3用點集繪制函數222.3OpenGL中的直線繪制繪制線段glBegin(GL_LINES);//useconstantGL_LINEShereglVertex2i(40,100);glVertex2i(202,96);glEnd();或者編寫一個函數voiddrawLineInt(GLintx1,GLinty1,GLintx2,GLinty2){glBegin(GL_LINES);glVertex2i(x1,y1);glVertex2i(x2,y2);glEnd();}232.3OpenGL中的直線繪制繪制線段232.3.1繪制折線和多邊形折線就是一系列頭尾相連的線段的集合,也稱為線帶。glBegin(GL_LINE_STRIP);//drawanopenpolylineglVertex2i(20,10);glVertex2i(50,10);glVertex2i(20,80);glVertex2i(50,80);glEnd();glFlush();繪制多邊形,只需要用GL_LINE_LOOP替換GL_LINE_STRIP即可,即是閉合的。f(x)=300-100cos(2px/100)+30cos(4px/100)+6cos(6px/100)<CalculateconstantsA,B,CandDforscalingandshifting>glBegin(GL_LINE_STRIP);for(x=0;x<=300;x+=3)glVertex2d(A*x+B,C*f(x)+D);glEnd();glFlush;242.3.1繪制折線和多邊形24例子2.3.2繪制存儲在文件中的折線
見計算機圖形學課件2016\計算機圖形學(Opengl版第三版)書源代碼word版\2.2.1-threeDots.doc,文件dino.dat:21//文件中折線的數量4//第一條折線的點數169118//第一條折線的第一個點174120//1791241781265//第二條折線的點數29886//第二條折線的第一個點30492310104314114314119293243510439...等等
25例子2.3.2繪制存儲在文件中的折線25例子2.3.3參數化圖形
voidhardwiredHouse(void){glBegin(GL_LINE_LOOP);//繪制房子外形glVertex2i(40,40);glVertex2i(40,90);glVertex2i(70,120);glVertex2i(100,90);glVertex2i(100,40);glEnd();glBegin(GL_LINE_STRIP);//繪制煙囪glVertex2i(50,100);glVertex2i(50,120);glVertex2i(60,120);glVertex2i(60,110);glEnd();...//繪制門...//繪制窗戶}voidparameterizedHouse(GLintPointpeak,GLintwidth,GLintheight)//房子的頂點由peak給定,房子的大小由width和height給定{glBegin(GL_LINE_LOOP);glVertex2i(peak.x,peak.y);//繪制房子外形glVertex2i(peak.x+width/2,peak.y-3*height/8);glVertex2i(peak.x+width/2peak.y-height);glVertex2i(peak.x-width/2,peak.y-height);glVertex2i(peak.x-width/2,peak.y-3*height/8);glEnd();drawchimneyinthesamefashiondrawthedoordrawthewindow}第一個方法將由幾條折線組成的簡易房子的每個端點位置被強制寫入代碼中,而第二種方法則是利用參數來繪制,用參數指定房屋頂點的位置和房子的寬度和高度,比較靈活。
26例子2.3.3參數化圖形26例子2.3.4構造一個折線繪圖器
一些應用程序需要用一個列表存儲折線的頂點。這個列表可能是數組或者鏈表,可以采用類或者結構體保存數組,對于大量數據,c++STL(標準模板庫)容器可能是一個安全的數據結構。structGLintPoint{GLintx,y;};classGLintPointArray{constintMAX_NUM=100;public:intnum;GLintPointpt[MAX_NUM];};voiddrawPolyLine(GlintPointArraypoly,intclosed){glBegin(closed?GL_LINE_LOOP:GL_LINE_STRIP);for(inti=0;i<poly.num;i++)glVertex2i(poly.pt[i].x,poly.pt[i].y);glEnd();glFlush();}
27例子2.3.4構造一個折線繪圖器272.3.2使用moveTo()和lineTo()繪制線段
OpenGL繪制線段很容易??梢杂靡唤M命令GL_LINES,...,GL_END,也可以用drawLine()函數。moveTo和lineTo函數可以管理一個虛擬畫筆,它的位置叫當前位置或CP(currentposition)。CP像一個OpenGL的狀態(tài)變量,如畫畫的顏色和背景一樣,它在程序的控制之下。moveTo(x,y)//設定CP到(x,y)lineTo(x,y)//在CP和(x,y)之間畫一條線,更新CP。使用下面的代碼,容易繪制基于點的列表(x0,y0),(x1,y1),...,(xn-1,yn-1)的折線。moveto(x[0],y[0]);for(inti=1;i<n;i++)lineto(x[i],y[i]);可以直接在OpenGL建立moveTo和lineTo這兩個函數,并保持CP
282.3.2使用moveTo()和lineTo()繪制線段moveTo和lineTo函數實現為:GLintPointCP;//全局位置變量//<<<<<<<<<<<<<moveto>>>>>>>>>>>>>>voidmoveto(GLintx,GLinty){CP.x=x;CP.y=y;//更新CP}//<<<<<<<<<<<<lineTo>>>>>>>>>>>>>>>>>voidlineto(GLintx,GLinty){glBegin(GL_LINES);//繪制曲線glVertex2i(CP.x,CP.y);glVertex2i(x,y);glEnd();glFlush();CP.x=x;CP.y=y;//更新CP}
29moveTo和lineTo函數實現為:292.3.3繪制邊校正的矩形多邊形的特例是邊校正的矩形,因為它的邊與坐標軸平行,OpenGL提供了一個現成的函數glRecti()。glRecti(GLintx1,GLinty1,GLintx2,GLinty2);//以對角點(x1,y1)and(x2,y2)繪制矩形該函數基于兩個給定點繪制邊校正矩形,并以當前顏色填充矩形。glClearColor(1.0,1.0,1.0,0.0);//白色背景glClear(GL_COLOR_BUFFER_BIT);//清除glColor3f(0.6,0.6,0.6);//淺灰glRecti(20,20,100,70);glColor3f(0.2,0.2,0.2);//深灰glRecti(70,50,150,130);glFlush();a)隨機生成的矩形雪花b)不同灰度等級的棋盤voiddrawFlurry(intnum,intnumColors,intWidth,intHeight)//drawnumrandomrectanglesinaWidthbyHeightrectangle{for(inti=0;i<num;i++){GLintx1=random(Width);//placecornerrandomlyGLinty1=random(Height);GLintx2=random(Width);//pickthesizesoitfitsGLinty2=random(Height);GLfloatlev=random(10)/10.0;//randomvalue,inrange0to1glColor3f(lev,lev,lev);//setthegraylevelglRecti(x1,y1,x2,y2);//drawtherectangle}glFlush();}302.3.3繪制邊校正的矩形30練習練習2.3.1畫棋盤寫出例程checkerboard(intsize),這64個正方形的邊長都設為size個像素。為這些正方形選擇兩種好看的顏色。解:第ij個正方形的左下角位于(i*size,j*size)處(i=0,..,7;j=0..7).使用下面的代碼,可以將顏色在(r1,g1,b1)和(r2,g2,b2)間變換。if((i+j)%2==0)//如果i+j是偶數glColor3f(r1,g1,b1);elseglColor3f(r2,g2,b2);練習練習2.3.1畫棋盤寫出例程checkerboard(intsize),這64個正方形的邊長都設為size個像素。為這些正方形選擇兩種好看的顏色。解:第ij個正方形的左下角位于(i*size,j*size)處(i=0,..,7;j=0..7).使用下面的代碼,可以將顏色在(r1,g1,b1)和(r2,g2,b2)間變換。if((i+j)%2==0)//如果i+j是偶數glColor3f(r1,g1,b1);elseglColor3f(r2,g2,b2);31練習練習312.3.5填充多邊形OpenGL支持用圖案或色彩填充一般的多邊形,那么這個多邊形是凸的。
凸多邊形:如果多邊形中任意兩點的連線完全位于多邊形內部,那么這個多邊形是凸的。要繪制凸多邊形,使用頂點列表,把頂點列表放在glBegin(GL_POLYGON)和glEnd()之間??梢杂命c畫線圖案填充多邊形,會把圖像貼在多邊形上,作為應用紋理的。C,E,F是凸的多邊形。322.3.5填充多邊形322.3.6OpenGL中的其他圖形元素除了點、線和多邊形,OpenGL還支持繪制五種其他元素332.3.6OpenGL中的其他圖形元素33//使用鼠標指定一個矩形{staticGLintPointcorner[2];staticintnumCorners=0;//initialvalueis0if(button==GLUT_LEFT_BUTTON&&state==GLUT_DOWN){corner[numCorners].x=x;corner[numCorners].y=screenHeight-y;//flipycoordinatenumCorners++;//haveanotherpointif(numCorners==2){glRecti(corner[0].x,corner[0].y,corner[1].x,corner[1].y);numCorners=0;//backto0corners}}elseif(button==GLUT_RIGHT_BUTTON&&state==GLUT_DOWN)glClear(GL_COLOR_BUFFER_BIT);//clearthewindowglFlush();}34//使用鼠標指定一個矩形342.4與鼠標和鍵盤的交互當用戶按下或釋放鼠標按鈕、移動鼠標或者松開或按下鍵盤時,就會產生一個相關事件。程序員可以用每類事件注冊一個回調函數
glutMouseFunc(myMouse):利用按下或者鼠標按鈕時發(fā)生事件來注冊myMouse()·glutMotionFunc(myMovedMouse)利用鼠標移動注冊函數myMovedMouse()
glutKeyboardFunc(myKeyboard)
利用按下或者松開鍵盤按鍵時注冊函數myKeyBoard()2.4.1用鼠標交互注冊一個函數到glutMouseFunc(myMouse),myMouse()的名字可以任意,有四個參數:voidmyMouse(intbutton,intstate,intx,inty);button的值如下:GLUT_LEFT_BUTTON,GLUT_MIDDLE_BUTTON,或GLUT_RIGHT_BUTTON;而state的值可能為:GLUT_UP或GLUT_DOWN。x和y為事件發(fā)生時鼠標的位置。352.4與鼠標和鍵盤的交互352.4.1用鼠標繪制點當用戶每次按下鼠標時,就會在屏幕窗口上鼠標所在的位置繪制出一個點。由于鼠標位置的y值是距離屏幕頂端的像素值,故不在(x,y),而是在(x,screenHeight–y)上繪制,其中screenHeight為屏幕的高度。voidmyMouse(intbutton,intstate,intx,inty){if(button==GLUT_LEFT_BUTTON&&state==GLUT_DOWN)drawDot(x,screenHeight-y);elseif(button==GLUT_RIGHT_BUTTON&&state==GLUT_DOWN)exit(-1);}2.4.2用鼠標指定一個矩形想讓用戶畫尺寸大小由鼠標輸入的長方形。用戶在兩個點上單擊鼠標,指定邊校正矩形的兩個角點,就可以畫出矩形。用戶右鍵點擊鼠標即可清屏。362.4.1用鼠標繪制點36//使用鼠標指定一個矩形{staticGLintPointcorner[2];staticintnumCorners=0;//initialvalueis0if(button==GLUT_LEFT_BUTTON&&state==GLUT_DOWN){corner[numCorners].x=x;corner[numCorners].y=screenHeight-y;//flipycoordinatenumCorners++;//haveanotherpointif(numCorners==2){glRecti(corner[0].x,corner[0].y,corner[1].x,corner[1].y);numCorners=0;//backto0corners}}elseif(button==GLUT_RIGHT_BUTTON&&state==GLUT_DOWN)glClear(GL_COLOR_BUFFER_BIT);//clearthewindowglFlush();}37//使用鼠標指定一個矩形372.4.3用鼠標控制Sierpinski墊片使用戶用鼠標指定初始三角形的三個頂點,并置于數組corners[]中,代碼為staticGLintPointcorners[3];staticintnumCorners=0;if(button==GLUT_LEFT_BUTTON&&state==GLUT_DOWN){corner[numCorners].x=x;corner[numCorners].y=screenHeight-y;//flipycoordinateif(++numCorners==3){Sierpinski(corners);//drawthegasketnumCorners=0;//backto0corners}}382.4.3用鼠標控制Sierpinski墊片38鼠標移動鼠標移動會產生一個鼠標事件:glutMotionFunc(myMovedMouse);注冊函數myMovedMouse(intx,inty);有兩個參數,即事件發(fā)生時鼠標所在的位置。glutPassiveMotionFunc(myPassiveMotion);這個函數是沒有按下鼠標按鈕的情況下,鼠標在窗口內移動時調用。該函數可以繪制并顯示橡皮矩形:隨著用戶移動鼠標,矩形將相應地變大或變小。下面的程序是用戶單擊鼠標建立矩形的一個角點,然后不按下鼠標時移動鼠標,調用myPassiveMotion(intx,inty),這決定了矩形的第二個角點。glutPostRedisplay()調用myDisplay()函數。代碼見見計算機圖形學課件2016\計算機圖形學(Opengl版第三版)書源代碼word版\2.2.1-threeDots.doc39鼠標移動392.4.2鍵盤交互按下鍵盤上的某個按鍵時就會產生一個鍵盤事件,并放入消息隊列?;卣{函數myKeyboard()通過glutKeyboardFunc(myKeyboard)來注冊這種事件,該函數的原型為:voidmyKeyboard(unsignedintkey,intx,inty);key值就是鍵的ASCII值,x和y為鼠標所在的位置。實例代碼為:voidmyKeyboard(unsignedchartheKey,intmouseX,intmouseY){GLintx=mouseX;GLinty=screenHeight-mouseY;//反轉y的值switch(theKey){case‘p’:drawDot(x,y);//drawadotatthemousepositionbreak;caseGLUT_KEY_LEFT:List[++last].x=x;//addapointList[last].y=y;break;case‘E’:exit(-1);//terminatetheprogramdefault:break;//donothing}}402.4.2鍵盤交互402.5程序中的菜單設計與使用如果一個圖形程序能夠提供菜單功能,對用戶來說,使用就比較方便了。建議使用vc。
設計代碼和使用GLUT菜單創(chuàng)建菜單使用voidglutCreateMenu(),菜單項使用一個帶參數的函數glutCreateMenu(ProcessMenuEvents)來定義,必須編寫ProcessMenuEvents函數。使用函數glutAddMenuEntry()可以在菜單中添加選項,一旦菜單設計好,可以用glutAttachMenu(GLUT_RIGHT_BUTTON)建立與鼠標按鈕的聯系。代碼見書P69.412.5程序中的菜單設計與使用如果一個圖形程序能夠提供菜單功案例分析
為了鞏固所學知識,最好的方式就是使用學過的知識,在最初的學習中更是如此,因為一開始學習計算機圖形學,不了解程序的編寫是必須克服的障礙。為了強調這一點,每章都有案例,其中介紹的編程項目不僅本身有趣,而且濃縮了該章中所提及的知識。難易程度:I:簡單的練習,課堂即可完成。.II:教難任務,可能需要幾天才能完成。III:復雜任務,可能需要兩周完成。案例2.1偽隨機點云(難度II)rand()函數不是隨機產生的,而是通過一個很有規(guī)律的機制產生的:每個數ni都是由它前一個數ni-1,通過一個等式確定:ni=(ni-1*A+B)modN
其中A,B,和N是適當選擇的常數。一組有效的值為:A=1103515245,B=12345,和N=32767.這個數限制在0到N-1.散亂點圖,調用rand(N),它會返回一個0,...,N-1之間的數。glBegin(GL_POINTS);for(inti=0;i<num;i++)//doitnumtimesglVertex2i(random(N),random(N));glEnd();選擇不同的A,B,N的值生成(x,y)對。42案例分析42案例分析
案例2.2迭代函數系統介紹(難度II)繪制Sierpinski墊片時的操作就是迭代函數(IFS)的一個例子。許多計算機生成的有趣圖形(如分形、Mandelbrot集合等)都是基于迭代函數變化生成的。如:?f(.)=2(.);倍增器,倍增它的參數;?f(.)=cos(.);?f(.)=4(.)(1-(.))?f(.)=(.)2+cIFS產生的數字序列:d0d1=f(d0)d2=f(f(d0))d3=f(f(f(d0)))dk=f[k](d0)dk=f(d(k-1))對于給定的d0,k=1,2,3,...,.項目1:預報冰雹系列寫一個程序,繪制上面公式對k所產生的數值序列。用戶給出1到4,000,000,000之間的初始值y0。每個yk值被畫成點圖(k,yk)。43案例分析43案例2.2迭代函數項目2:姜餅人這是混沌理論中的常見圖形。glLoadIdentity(); glTranslatef(-30,-30,-90); glScaled(0.15,0.15,0.15); floatx,y; staticfloatold_x=115,old_y=121; staticfloatR=40,S=3; glBegin(GL_POINTS); for(inti=0;i<1000;i++) { x=R*(1+2*S)-old_y+abs(int(old_x-S*R));//TheIFSforx y=old_x; old_x=x; old_y=y; glVertex2f(x,y);//Outputthatmagicvertex } glEnd(); glFlush();這里R=M=40,S=L=3,p_x=old_x,p_y=old_y,q_x=x,q_y=y;44案例2.2迭代函數44案例2.3黃金分割(難度I)部分代碼:1)phi0=1.0;for(i=0;i<5000;i++){phi=sqrt(1+sqrt(phi0));phi0=phi;}2)phi0=1.0;for(i=0;i<5000;i++){phi=1.0+1.0/(1.0+phi0));phi0=phi;}45案例2.3黃金分割45案例2.4如何創(chuàng)建和使用折線段文件(難度I)復雜圖形恐龍dino.dat由一個很大的折線集合構成。折線數據一般都是存在一個文件里。(1)編寫一個程序,可以從文件中讀取折線數據并依次繪制每條折線。編寫一個折線數據,將文件中的數據用你編寫的程序繪制出來。設計你的程序,確保畫折線時不需要每次都讀取折線數據文件,可以用數組或鏈表存儲折線數據;(2)擴展上面的程序,使其能夠接受其他文件格式。例如程序可以接受差分編碼的x和y坐標。(3)對前面的文件作修改,使文件中每條折線都和一個顏色值相關,使顏色值與折線包含點數的數值在一行上。(4)修改折線例程,使它在折線的點數為負數時繪制一個封閉多邊形。46案例2.4如何創(chuàng)建和使用折線段文件46案例2.5迷宮(難度III)繪制一個100行和150列的長方形迷宮,目標是從左邊界的開口穿過迷宮到達右邊界的開口。(1)生成并顯示一個具有R行和C列的矩形迷宮;(2)找到并顯示一條從迷宮起點通向終點的路徑。怎樣表示一個迷宮呢?一種方法是,對每個單元格都做出聲明,無論其北墻完整還是其東墻完整,建議使用下面的數據結構:charnorthWall[R][C],eastWall[R][C];見書P7947案例2.5迷宮474848本章內容編寫圖形程序學習OpenGL程序的基本組成開發(fā)繪制點、直線、折線和多邊形的基本圖形工具學會用鼠標和鍵盤控制程序49本章內容編寫圖形程序12.0vcopengl編程
指導例子熟悉代碼,多次練習見圖形學第二課opengl-console.doc,創(chuàng)建一個控制臺的應用程序,本書采用的編程方式見圖形學第二課opengl-nehe-win.doc,創(chuàng)建一個win32的應用程序,nehe教程(共48課)采用的編程方式見圖形學第三課opengl-nehe-mfc.doc,創(chuàng)建一個mfc的應用程序,本人推薦采用的編程方式,需要學習vc和mfc。502.0vcopengl編程
指導例子熟2.1生成圖像初步通過練習(編寫和測試各種不同圖形的程序)可以很快掌握計算機圖形學。先從簡單的任務開始,一旦掌握,就試著變變花樣,看看有哪些變化,試著進一步繪制更復雜的場景。最好的學習辦法是認真、仔細地學習每行代碼或等式。當你這么做時,就能理解每個新概念。每個圖形程序都以一些初始化工作為開始,由此建立程序所需要的顯示模型和坐標系。512.1生成圖像初步通過練習(編寫和測試各種不同圖形的程序2.1生成圖像初步初始化時,將顯示器設置為“圖形模式”,并且建立了坐標系,坐標x和y以像素為單位,x向右遞增,y向下遞增。522.1生成圖像初步初始化時,將顯示器設置為“圖形模式”,2.1.1設備無關的編程
和OpenGLOpenGL程序可以在不同的圖形環(huán)境里編譯和運行,即它是與設備無關的編程。OpenGL是一個開源圖形庫,可在下載。OpenGL常被稱為應用程序接口(API)。這個接口是程序員可以調用的例程,先從OpenGL繪制簡單的二維物體開始,然后學習繪制三維圖形。532.1.1設備無關的編程
和OpenGLO2.1.2窗口的編程事件驅動編程:事件包括單擊鼠標,按下鍵盤上的按鍵,或者重新調整窗口的大小。程序員將程序組織成回調函數的集合,這些回調函數一有事件發(fā)生就執(zhí)行。即什么也不做,等待事件發(fā)生,事件發(fā)生后再做指定的事。OpenGL有一個GLUT庫,它用來打開窗口,管理菜單和處理事件等。注冊回調函數:有一種方法將每種類型的事件與要求的回調函數關聯起來,這個方法稱之為注冊回調函數。程序中用到的每一個事件類型都必須用回調函數注冊,該回調函數的名字和定義由程序員選擇。542.1.2窗口的編程事件驅動編程:事件包括單擊鼠標,按下2.1.2窗口的編程下面是使用GLUT庫,名為myMouse的回調函數例子,它方便地注冊了與鼠標關聯的事件:glutMouseFunc(myMouse);glutMouseFunc是GLUT庫的固有函數,但是回調函數myMouse是程序員定義的,并由程序員編寫代碼,處理每個可能感興趣的鼠標動作。4種主要的OpenGL庫:1)基本GL庫,OpenGL庫的基礎。它提供OpenGL的基本函數。每個OpenGL函數都以字符GL開頭。552.1.2窗口的編程下面是使用GLUT庫,名為myMou2.1.2窗口的編程2)GLUT庫:GL實用工具包。它用來打開窗口,開發(fā)和管理菜單,以及管理事件等。3)GLU庫:GL實用庫,它提供高級例程,處理矩陣操作和繪制二次曲面如球和圓柱體;4)GLUI庫:用戶接口庫,提供控制工具和菜單。glutDisplayFunc(myDisplay):重繪窗口調用myDisplay回調函數;glutReshapeFun(myReshape):對屏幕窗口的形狀進行調整;562.1.2窗口的編程2)GLUT庫:GL實用工具包。它用voidmain(){glutDisplayFunc(myDisplay);//注冊重繪函數glutReshapeFunc(myReshape);//注冊改變窗口形狀函數glutMouseFunc(myMouse);//注冊鼠標動作函數glutMotionFunc(myMouse);//注冊鼠標移動函數glutKeyboardFunc(myKeyboard);//注冊鍵盤動作函數//可能初始化其他工作glutMainLoop();//進入主循環(huán)等待事件發(fā)生}57voidmain()92.1.3如何打開一個窗口畫圖glutInit:該函數初始化工具包glutDisplayMode(GLUT_SINGLE|GLUT_RGB):分配單個顯示緩存glutInitWindowSize(640,480):該函數指定屏幕的初始尺寸,寬640像素,高480像素。glutCreateWindow(“myfirstattempt”):該函數打開并顯示屏幕窗口嗎,并顯示標題。代碼如下:582.1.3如何打開一個窗口畫圖glutInit:該函數voidmain(intargc,char**argv){glutInit(&argc,argv);//初始化工具包glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB);//設置顯示模式glutInitWindowSize(640,480);//設置窗口大小glutInitWindowPosition(100,150);//設置窗口在屏幕上的位置glutCreateWindow("myfirstattempt");//打開屏幕窗口59voidmain(intargc,char**arg//注冊回調函數glutDisplayFunc(myDisplay);glutReshapeFunc(myReshape);glutMouseFunc(myMouse);glutKeyboardFunc(myKeyboard);myInit();//必要的其他初始化工作glutMainLoop();//進入循環(huán)}60//注冊回調函數122.2OpenGL的基本圖形元素屏幕坐標系:寬為640,高為640。x坐標從左邊界0增加到右邊界639,y坐標從0增加到上邊的479。612.2OpenGL的基本圖形元素屏幕坐標系:寬為640,高繪制三個點:glBegin(GL_POINTS);glVertex2i(100,50);glVertex2i(100,130);glVertex2i(150,130);glEnd();或者用浮點值代替整數值:glBegin(GL_POINTS);glVertex2d(100.0,50.0);glVertex2d(100.0,130.0);glVertex2d(150.0,130.0);glEnd();62繪制三個點:14OpenGL數據類型:命令后綴和參數數據類型OpenGL狀態(tài):OpenGL是由許多狀態(tài)變量組成的狀態(tài)機,這些狀態(tài)包括點的大小、繪圖的顏色額屏幕窗口大小等,點的大小可以用glPointSize()來設置,畫圖的顏色用這條語句設置:glColor3f(red,green,blue);坐標系的建立:6315voidmyInit(void){glMatrixMode(GL_PROJECTION);glLoadIdentity();gluOrtho2D(0,640.0,0,480.0);}myInit設置坐標系,設置透視投影變換。gluOrtho2D設置正交投影。組合在一起:建立起一個完整的OpenGL程序glFlush()函數保證所有的數據被完全處理并顯示。64voidmyInit(void)162.2.1幾個點叢繪制的例子點叢是由大量點組成的某種圖案。下面有幾個點叢例子。652.2.1幾個點叢繪制的例子點叢是由大量點組成的某種圖案例子2.2.1北斗星群
見計算機圖形學課件2016\計算機圖形學(Opengl版第三版)書源代碼word版\2.2.1-threeDots.doc例子2.2.2Sierpinski(塞平斯基)墊片見計算機圖形學課件2016\計算機圖形學(Opengl版第三版)書源代碼word版\2.2.1-threeDots.doc算法如下:第k個點表示為P(k)=(x(k),y(k)),每一個點都依賴于前一個點P(k-1),過程如下:(1)選擇三個固定的點T0,T1,T2,構成一個三角形,稱為每個Sierpinski墊片的父三角形;(2)隨機選擇父三角形頂點T0,T1,T2中的一點作為要繪制的初始點P0。迭代下面的(3)~(5)步,直到圖案填充完畢。(3)隨機選擇T0,T1,T2中的一點,稱為T。(4)構造下一個點P(k),作為T和前一個已建好的點P(k-1)之間的之間點,即P(k)=(P(k-1)+T)/2(5)用drawDot()繪制P(k)。66例子2.2.1北斗星群18例子2.2.2Sierpinski(塞平斯基)墊片//初始值P(0),在for循環(huán)外面GLintPointpoint=T[index];//初始值drawDot(point.x,point.y);//畫初始點for(inti=0;i<1000;i++)//畫1000點{index=rand()%3;//P(k)=(P(k-1)+T)/2point.x=(point.x+T[index].x)/2;point.y=(point.y+T[index].y)/2;drawDot(point.x,point.y);}67例子2.2.2Sierpinski(塞平斯基)墊片19例子2.2.3用點集繪制函數
見計算機圖形學課件2016\計算機圖形學(Opengl版第三版)書源代碼word版\2.2.1-threeDots.doc繪制某個數學函數f(x),當x在0~4變化時的曲線。當繪制曲線時,因為0~4之間的x值位于屏幕窗口左下角的前4個像素上,而且f(.)的負值位于窗口的下面,根本看不到。需要進行平移和縮放使曲線正好出現在合適的屏幕窗口。將世界坐標系轉換為窗口坐標系。x方向縮放:假定屏幕窗口為screenWidth,單位為像素,現在x的范圍為0~4,故sx=x*screenWidth/4.0;y方向縮放:假定屏幕窗口為screenHeight,單位為像素,f(x)的取值在–1.0和1.0之間,縮放screenHeight/2,平移為screenHeight/2,sy=(y+1.0)*screenHeight/2.0;sx,sy為屏幕坐標,x,y為窗口坐標;轉換方程為:sx=A*x+Bsy=C*y+D68例子2.2.3用點集繪制函數20constintscreenWidth=640;//widthofscreenwindowinpixelsconstintscreenHeight=480;//heightofscreenwindowinpixelsGLdoubleA,B,C,D;//valuesusedforscalingandshiftingvoidmyInit(void){glClearColor(1.0,1.0,1.0,0.0);//backgroundcoloriswhiteglColor3f(0.0f,0.0f,0.0f);//drawingcolorisblackglPointSize(2.0);//a'dot'is2by2pixelsglMatrixMode(GL_PROJECTION);//set"camerashape"glLoadIdentity();gluOrtho2D(0.0,(GLdouble)screenWidth,0.0,(GLdouble)screenHeight);A=screenWidth/4.0;//x方向的縮放量AB=0.0;//x方向的平移量BC=D=screenHeight/2.0;//y方向的縮放量C和平移量D}voidmyDisplay(void){glClear(GL_COLOR_BUFFER_BIT);//clearthescreenglBegin(GL_POINTS);for(GLdoublex=0;x<4.0;x+=0.005){Gldoublefunc=exp(-x)*cos(2*3.14159265*x);//sx=A*x+B,sy=C*y+DglVertex2d(A*x+B,C*func+D);}glEnd();glFlush();//sendalloutputtodisplay}69constintscreenWidth=640;/例子2.2.3用點集繪制函數
見計算機圖形學課件2016\計算機圖形學(Opengl版第三版)書源代碼word版\2.2.1-threeDots.docf(x)=sin(x),f(x)=sin(2x),x從-π到π,y從-1到1A=screenWidth/6.28;//x方向的縮放量A,屏幕寬度/x方向的變化范圍B=screenWidth/2.0;//x方向的平移量B,屏幕寬度的一半C=screenHeight/2.0;//y方向的縮放量C,屏幕高度/y方向的變化范圍
D=screenHeight/2.0;//y方向的平移量D,屏幕的高度的一半for(GLdoublex=-3.14;x<3.14;x+=0.005){Gldoublefunc=sin(x);Gldoublefunc1=sin(2x);//sx=A*x+B,sy=C*y+DglVertex2d(A*x+B,C*func+D);glVertex2d(A*x+B,C*func1+D);}
70例子2.2.3用點集繪制函數222.3OpenGL中的直線繪制繪制線段glBegin(GL_LINES);//useconstantGL_LINEShereglVertex2i(40,100);glVertex2i(202,96);glEnd();或者編寫一個函數voiddrawLineInt(GLintx1,GLinty1,GLintx2,GLinty2){glBegin(GL_LINES);glVertex2i(x1,y1);glVertex2i(x2,y2);glEnd();}712.3OpenGL中的直線繪制繪制線段232.3.1繪制折線和多邊形折線就是一系列頭尾相連的線段的集合,也稱為線帶。glBegin(GL_LINE_STRIP);//drawanopenpolylineglVertex2i(20,10);glVertex2i(50,10);glVertex2i(20,80);glVertex2i(50,80);glEnd();glFlush();繪制多邊形,只需要用GL_LINE_LOOP替換GL_LINE_STRIP即可,即是閉合的。f(x)=300-100cos(2px/100)+30cos(4px/100)+6cos(6px/100)<CalculateconstantsA,B,CandDforscalingandshifting>glBegin(GL_LINE_STRIP);for(x=0;x<=300;x+=3)glVertex2d(A*x+B,C*f(x)+D);glEnd();glFlush;722.3.1繪制折線和多邊形24例子2.3.2繪制存儲在文件中的折線
見計算機圖形學課件2016\計算機圖形學(Opengl版第三版)書源代碼word版\2.2.1-threeDots.doc,文件dino.dat:21//文件中折線的數量4//第一條折線的點數169118//第一條折線的第一個點174120//1791241781265//第二條折線的點數29886//第二條折線的第一個點30492310104314114314119293243510439...等等
73例子2.3.2繪制存儲在文件中的折線25例子2.3.3參數化圖形
voidhardwiredHouse(void){glBegin(GL_LINE_LOOP);//繪制房子外形glVertex2i(40,40);glVertex2i(40,90);glVertex2i(70,120);glVertex2i(100,90);glVertex2i(100,40);glEnd();glBegin(GL_LINE_STRIP);//繪制煙囪glVertex2i(50,100);glVertex2i(50,120);glVertex2i(60,120);glVertex2i(60,110);glEnd();...//繪制門...//繪制窗戶}voidparameterizedHouse(GLintPointpeak,GLintwidth,GLintheight)//房子的頂點由peak給定,房子的大小由width和height給定{glBegin(GL_LINE_LOOP);glVertex2i(peak.x,peak.y);//繪制房子外形glVertex2i(peak.x+width/2,peak.y-3*height/8);glVertex2i(peak.x+width/2peak.y-height);glVertex2i(peak.x-width/2,peak.y-height);glVertex2i(peak.x-width/2,peak.y-3*height/8);glEnd();drawchimneyinthesamefashiondrawthedoordrawthewindow}第一個方法將由幾條折線組成的簡易房子的每個端點位置被強制寫入代碼中,而第二種方法則是利用參數來繪制,用參數指定房屋頂點的位置和房子的寬度和高度,比較靈活。
74例子2.3.3參數化圖形26例子2.3.4構造一個折線繪圖器
一些應用程序需要用一個列表存儲折線的頂點。這個列表可能是數組或者鏈表,可以采用類或者結構體保存數組,對于大量數據,c++STL(標準模板庫)容器可能是一個安全的數據結構。structGLintPoint{GLintx,y;};classGLintPointArray{constintMAX_NUM=100;public:intnum;GLintPointpt[MAX_NUM];};voiddrawPolyLine(GlintPointArraypoly,intclosed){glBegin(closed?GL_LINE_LOOP:GL_LINE_STRIP);for(inti=0;i<poly.num;i++)glVertex2i(poly.pt[i].x,poly.pt[i].y);glEnd();glFlush();}
75例子2.3.4構造一個折線繪圖器272.3.2使用moveTo()和lineTo()繪制線段
OpenGL繪制線段很容易??梢杂靡唤M命令GL_LINES,...,GL_END,也可以用drawLine()函數。moveTo和lineTo函數可以管理一個虛擬畫筆,它的位置叫當前位置或CP(currentposition)。CP像一個OpenGL的狀態(tài)變量,如畫畫的顏色和背景一樣,它在程序的控制之下。moveTo(x,y)//設定CP到(x,y)lineTo(x,y)//在CP和(x,y)之間畫一條線,更新CP。使用下面的代碼,容易繪制基于點的列表(x0,y0),(x1,y1),...,(xn-1,yn-1)的折線。moveto(x[0],y[0]);for(inti=1;i<n;i++)lineto(x[i],y[i]);可以直接在OpenGL建立moveTo和lineTo這兩個函數,并保持CP
762.3.2使用moveTo()和lineTo()繪制線段moveTo和lineTo函數實現為:GLintPointCP;//全局位置變量//<<<<<<<<<<<<<moveto>>>>>>>>>>>>>>voidmoveto(GLintx,GLinty){CP.x=x;CP.y=y;//更新CP}//<<<<<<<<<<<<lineTo>>>>>>>>>>>>>>>>>voidlineto(GLintx,GLinty){glBegin(GL_LINES);//繪制曲線glVertex2i(CP.x,CP.y);glVertex2i(x,y);glEnd();glFlush();CP.x=x;CP.y=y;//更新CP}
77moveTo和lineTo函數實現為:292.3.3繪制邊校正的矩形多邊形的特例是邊校正的矩形,因為它的邊與坐標軸平行,OpenGL提
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- GB/T 46991.1-2025電動汽車車載動力電池耐久性要求及試驗方法第1部分:輕型汽車
- 湖南省衡陽市2025-2026學年八年級上學期1月期末考試英語試卷(含答案無聽力原文及音頻)
- 貴州省銅仁市松桃民族中學2025-2026學年高二上學期期末模擬測試化學試卷(含答案)
- 2026年上海市寶山區(qū)初三一模語文試卷(含答案)
- 2025-2026學年遼寧省丹東五中九年級(上)期末數學試卷(含答案)
- 五年級上冊語文期末考試卷及答案
- 衛(wèi)生事業(yè)單位面試真題及答案
- 裝飾工程、防水工程試題答案
- 部編版三年級語文(下冊)期末試卷及答案(今年)
- 雙十一光棍節(jié)酒店策劃
- 補戶口本代辦委托書
- GB/Z 17626.1-2024電磁兼容試驗和測量技術第1部分:抗擾度試驗總論
- T-CNCIA 01004-2017 水性石墨烯電磁屏蔽建筑涂料
- 50萬噸年脫硫石膏及20萬噸年廢硫磺綜合利用項目可行性研究報告寫作模板-申批備案
- 《床上擦浴技術》評分標準
- 設備安裝可行性方案
- 高中化學人教版(2019)選擇性必修二知識點總結
- 消化系統常見癥狀與體征課件整理-002
- 流程與TOC改善案例
- 【當代中國婚禮空間設計研究4200字(論文)】
- GB/T 20322-2023石油及天然氣工業(yè)往復壓縮機
評論
0/150
提交評論