初試PyOpenGL二 (Python+OpenGL)基本地形生成與高度檢測


  在上文中,講述了PyOpenGL的基本配置,以及網格,球形的生成,以及基本的漫游。現在利用上一篇的內容,來利用高程圖實現一個基本的地形,並且,利用上文中的第三人稱漫游,以小球為視角,來在地形上前后左右漫游,能實時檢測高度。下面先看下效果圖:

  二張圖,球分別在不同的地方,不同的顯示模型,一個是全填充的,一個是線連,可以從中看到一些基本的思路。大致過程分別如下,首先拿到一張高度圖,檢索其中的高度對應的通道的值,然后用來改變網格的高度。這個過程只需要在初始化時生成就行了,所以我們可以簡單的用CPU來完成這個。然后是球體的漫游,這部分在上文中已經講了第一與第三人稱漫游,用的就是其中的第三人稱作漫游,在這里,我們主要是要檢索球下面的地形的高度,因為需要實時計算,這部分用GPU來完成。

  我們先看下,根據高度圖來改變網格高度的相關代碼(請對照前文中的網格類Plane來看,下面的setHeight為其中的一個方法):

 1     def setHeight(this,image):
 2         ix = image.size[0] 
 3         iy = image.size[1] 
 4         this.heightImage = image
 5         print ix,iy
 6         #print "xr,yr",this.xr,this.yr
 7         lerp = lambda a,b,d:a * d + b * (1.0 - d)  
 8         fade = lambda t : t*t*(3.0-2.0*t)  #t*t*t*(t*(t*6.0-15.0)+10.0)               
 9         for y in range(this.yr):
10             for x in range(this.xr): 
11                 index = 5 * (this.xr * y + x) + 3
12                 #print index
13                 fx = float(x) / float(this.xr - 1) * float(ix - 1)
14                 fy = float(y) / float(this.yr - 1) * float(iy - 1)
15                 #print float(x) / float(this.xr - 1),fx,float(y) / float(this.yr - 1),fy
16                 xl,xr,yu,yd = int(math.floor(fx)),int(math.ceil(fx)),int(math.floor(fy)),int(math.ceil(fy))
17                 dx,dy = fade(fx - xl),fade(fy - yu)  
18                 #print "loc:",xl,xr,yu,yd,dx,dy
19                 #left up,right up,left down,right down
20                 lu,ru,ld,rd = image.im[ix * yu + xl],image.im[ix * yu + xr],image.im[ix * yd + xl],image.im[ix * yd + xr] 
21                 #print ix * yu + xl,lu,ru,ld,rd  
22                 hight = lerp(lerp(lu,ru,dx),lerp(ld,rd,dx),dy)
23                 this.data[index] = hight / 255.0
24                 #print "setHeight:",hight / 255.0
檢索高度圖

  當初完成這段代碼后,我有時后悔在前面學習noise時,沒有自己先完成一個根據高度圖生成地形,不然理解柏林噪聲函數會是一件非常簡單的事,這段代碼很簡單,得到image的信息,然后把原來的地形網格他們二個做一個映射關系,就好像二個大小不同的矩陣,根據其中一個在本矩陣里的位置,找出在另一個矩陣中對應的位置。首先index得到的是當初位置網絡數據里的高度索引,第前面文章中,這個值是0,然后根據線性關系,就如上面后說,找到當前位置對應在image的位置,fx,fy.為什么我說很后悔先看了noise,大家可以看下,這里的代碼的邏輯和noise里的就是一樣,但是這里我可以自己推出來,而看noise里的過程花費太多不必要的時間。在這里,我們可以想象的到,fx,fy是整數的機會不大,那么無論取floor(fx)或ceil(fx),都有較大的偏差,正確的方法應該是根據fx的小數位來做floor(fx)或ceil(fx)權重進行計算。簡單來說,就是根據fx,fy來取在它周圍的四個像素點,然后根據他們的小數位來對四個小數點進行混合計算。其中小數部分需要的fade可以用lerp,也可以用二階平滑來至三階平滑的映射關系,而像素值根據簡單線性關系求就可以了。理解這里的以后,再去看柏林噪聲實踐(一) 海波等就容易理解多了。

  這個是初始化地形的高度值,下面就是重點,如果根據球所在的位置,來得到當前位置里的高度,然后用來設定球的高度(簡化問題,只求球心下的高度,二片卡住球的問題就沒考慮)。有了前面的基礎,在CPU中進行得到高度值也很容易,但是現在是漫游過程中,當前位置每時都在計算,CPU應該用來進行更復雜的邏輯運算,這部分交給GPU了,先給出相關着色器代碼:

 1 update_v = """        
 2         //#version 330 compatibility
 3         #version 130
 4         uniform sampler2D tex0;
 5         uniform float xw;
 6         uniform float yw;
 7         uniform float height;
 8         //the location of center of the sphere
 9         uniform vec2 xz;          
10         uniform float sphereRadius; 
11         uniform mat4 mMatrix;
12         uniform mat4 vMatrix;
13         uniform mat4 pMatrix;
14         out vec4 o_color;          
15         void main() {      
16             vec4 pos = vec4(gl_Vertex);              
17             vec2 uv = vec2(xz/vec2(xw,yw) + vec2(0.5,0.5));
18             uv.y = 1.0 - uv.y;
19             vec3 rgb =  texture2D(tex0, uv).rgb;
20             pos.y = pos.y + sphereRadius + rgb.r;//height;//
21             o_color = vec4(uv.x, uv.y, rgb.r, 1);
22             gl_Position = pMatrix * vMatrix * mMatrix * pos;
23 
24             //vec4 v = vec4(gl_Vertex);
25             //vec2 uv = vec2(xz/vec2(xw,yw) + vec2(0.5,0.5)).xy;
26             //uv.y = 1.0 - uv.y;    
27             //v.x = v.x + xz.x;
28             //v.z = v.z + xz.y;    
29             //v.y = v.y + texture2D(tex0, uv).r+ sphereRadius;
30             //o_color = vec4(uv.x,uv.y, 0, 1 );
31             //gl_Position = gl_ModelViewProjectionMatrix * v;
32         }"""
33 
34 update_f = """
35         //#version 330 compatibility
36         #version 130
37         in vec4 o_color;  
38         void main() {            
39             //vec4 color = texture2D(tex1, gl_TexCoord[0].st);
40             gl_FragColor = o_color;// vec4( 0, 1, 0, 1 );
41         }"""
着色器檢索高度

  這段代碼大致思想和前面一樣,不過紋理坐標需要映射到0-1之前,不過,也少了混合周圍定點的計算,因為我們在設定紋理時(glTexParameterf),已經告訴着色器,自動線性混合了。在這里,需要說明的是,因為130后,已經廢棄了固定管線的相關功能與API,雖然還能用,但是畢竟要向前看,所以主體的部分有二部分,一部分是用的是着色器版本120固定管線提供的gl_ModelViewProjectionMatrix,以及120后的,自己提供MVP,也順便練習下如何在PyOpenGL里進行矩陣的基本操作。其中height是CPU計算的高度,這個就放附件里,不拿出來說了,至於為什么還拿出一個CPU版,前面不是說了不交給CPU嗎,主要是我發現,有些舊的顯卡對於這段邏輯處理還是有些問題(有一問題浪費大量時間才發現是舊顯卡的問題),代碼就不放了,大家有興趣可以去附件里看。

  嗯,還有必要說下繪制部分,地形網絡用到一個顏色紋理,而球需要用到高度紋理,在glsl中,多紋理,多着色器如何設定以及完成。

 1 class allshader:
 2     def __init__(this):
 3         this.planeProgram = shaders.compileProgram(shaders.compileShader(plane_v, GL_VERTEX_SHADER),
 4             shaders.compileShader(plane_f, GL_FRAGMENT_SHADER)) 
 5         #the parameter tex0 must be use in shaders,otherwise the
 6         #glGetUniformLocation get -1
 7         this.planeProgram.tex0 = glGetUniformLocation(this.planeProgram,"tex0")
 8         this.planeProgram.tex1 = glGetUniformLocation(this.planeProgram,"tex1")        
 9         print this.planeProgram.tex0,this.planeProgram.tex1
10 
11         this.updateProgram = shaders.compileProgram(shaders.compileShader(update_v, GL_VERTEX_SHADER),
12             shaders.compileShader(update_f, GL_FRAGMENT_SHADER)) 
13         this.updateProgram.xl = glGetUniformLocation(this.updateProgram,"xw")
14         this.updateProgram.yl = glGetUniformLocation(this.updateProgram,"yw")  
15         this.updateProgram.height = glGetUniformLocation(this.updateProgram,"height")
16         this.updateProgram.sphereRadius = glGetUniformLocation(this.updateProgram,"sphereRadius")   
17         this.updateProgram.tex0 = glGetUniformLocation(this.updateProgram,"tex0")
18         this.updateProgram.xz = glGetUniformLocation(this.updateProgram,"xz")
19         this.updateProgram.hight = glGetUniformLocation(this.updateProgram,"hight")
20         this.updateProgram.mMatrix = glGetUniformLocation(this.updateProgram,"mMatrix")
21         this.updateProgram.vMatrix = glGetUniformLocation(this.updateProgram,"vMatrix")
22         this.updateProgram.pMatrix = glGetUniformLocation(this.updateProgram,"pMatrix")
23 
24 
25 def DrawGLScene():    
26     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)   
27     glMatrixMode(GL_MODELVIEW)     
28     camera.setLookat()
29     #texture set    
30     glActiveTexture(GL_TEXTURE0)
31     glBindTexture(GL_TEXTURE_2D, colorMap) 
32     glActiveTexture(GL_TEXTURE1)
33     glBindTexture(GL_TEXTURE_2D, hightMap)     
34     #plane
35     glUseProgram(shaderall.planeProgram)
36     glUniform1i(shaderall.planeProgram.tex0, 0) 
37     plane.draw() 
38     glUseProgram(0)
39     #sphare 
40     eyeLoc = camera.origin
41     uv = eyeLoc[0] / plane.xl + 0.5,eyeLoc[2] / plane.yl + 0.5
42     glUseProgram(shaderall.updateProgram)    
43     glUniform1f(shaderall.updateProgram.xl, plane.xl)  
44     glUniform1f(shaderall.updateProgram.yl, plane.yl) 
45     #CPU compute height
46     #glUniform1f(shaderall.updateProgram.height, plane.getHeight(eyeLoc[0],eyeLoc[2])) 
47     glUniform1f(shaderall.updateProgram.sphereRadius, sph.radius)
48     glUniform1i(shaderall.updateProgram.tex0, 1) 
49     #print uv
50     glUniform2f(shaderall.updateProgram.xz,eyeLoc[0],eyeLoc[2]) 
51     #print "eye:",eyeLoc,eyeLoc[0],eyeLoc[2]
52     getMVP(eyeLoc)
53     sph.draw()   
54     glUseProgram(0)  
55     glActiveTexture(GL_TEXTURE0)
56     glBindTexture(GL_TEXTURE_2D, 0)
57     glDisable(GL_TEXTURE_2D)
58     glActiveTexture(GL_TEXTURE1)
59     glBindTexture(GL_TEXTURE_2D, 0)
60     glDisable(GL_TEXTURE_2D)
61 
62     glBegin(GL_LINES)
63     glColor(1.0,0.0,0.0)
64     glVertex3f(-plane.xl / 2.0, 1.0, -plane.yl / 2.0)
65     glVertex3f(100.0, 1.0, -plane.yl / 2.0)
66     glColor(0.0,1.0,0.0)
67     glVertex3f(-plane.xl / 2.0, 1.0, -plane.yl / 2.0)
68     glVertex3f(-plane.xl / 2.0, 1.0, 100.0)
69 
70     glColor(1.0,0.0,0.0)
71     glVertex3f(0.0, 0.0,0.0)
72     glVertex3f(100.0, 0.0, 0.0)
73     glColor(0.0,1.0,0.0)
74     glVertex3f(0.0, 0.0, 0.0)
75     glVertex3f(0.0, 1.0, 100.0)
76     glEnd()
77                       
78     glutSwapBuffers()
glsl多紋理,多着色器

  嗯,python好像對中文注解支持不友好,故采用我的鬼哭神嚎的英語,大家就不要笑了。說一下,后面給出二個坐標系,用來確定當家位置的,一個是左下角,一個是中心,分別向X,Z正軸發射出去。

  下面這段是設定球MVP的代碼:

1 def getMVP(eye):
2     v = ny.array(glGetFloatv(GL_MODELVIEW_MATRIX), ny.float32)
3     p = ny.array(glGetFloatv(GL_PROJECTION_MATRIX), ny.float32)
4     m = ny.array([[1, 0, 0, 0],[0, 1, 0, 0], [0, 0, 1, 0],[eye[0],0,eye[2],1]],ny.float32)
5     #print m
6     glUniformMatrix4fv(shaderall.updateProgram.pMatrix,1,GL_FALSE,p)
7     glUniformMatrix4fv(shaderall.updateProgram.vMatrix,1,GL_FALSE,v)
8     glUniformMatrix4fv(shaderall.updateProgram.mMatrix,1,GL_FALSE,m) 
9     #glgeffloat
球MVP

  附件:Python地形.zip 和上方的漫游模式一樣,其中EDSF前后左右移動,WR分別向上與向下,鼠標右鍵加移動鼠標控制方向,V切換第一人稱與第三人稱。UP與DOWN切換前面操作的移動幅度。在第三人稱下,因為球中着色器限定了Y軸,故那時模式看起來如2.5D的那種游戲視角,能左右轉動視角,不能看到天,不知2.5D游戲里的那種是不是也是這樣被限制住了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM