生活随笔
收集整理的這篇文章主要介紹了
用OpenGL导演一场烟花盛会,迎接即将到来的新年
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
忙碌了一年,今天終于放假了。原本打算好好休息一下,沒成想只過了半天就覺得有點無聊。看家人和朋友們都在忙年,那我就用OpenGL導演一場煙花盛會,獻給即將到來的新年吧。
一說到OpenGL,很多人都會覺得復雜,其實不然。只要掌握了幾個基本的概念,借助于工具軟件,任何人都可以很輕松地上手。在制作煙花之前,我先介紹一下WxGL這個三維數據快速可視化工具。
1. 安裝WxGL
WxGL是一個基于PyOpenGL的三維數據可視化庫,以wx為顯示后端,提供Matplotlib風格的交互式應用方式。WxGL也可以和wxPython無縫結合,在wx的窗體上繪制三維模型。使用pip命令即可快速安裝WxGL及其所依賴的其他模塊。
pip install wxgl
2. 快速體驗
- 下面這幾行代碼,繪制了一個中心在坐標原點半徑為1的純色圓球。忽略模塊名的話,這些代碼和Matplotlib的風格是完全一致的。
>>> import wxgl
.wxplot
as plt
>>> plt
.uvsphere
((0,0,0), 1, color
='cyan')
>>> plt
.title
('快速體驗:$x^2+y^2=1$')
>>> plt
.show
()
生成一個地球模型是如此簡單。
>>> plt
.uvsphere
((0,0,0), 1, texture
='res/earth.jpg', xflip
=True, yflip
=False)
>>> plt
.show
()
讓地球自轉,更是易如反掌。
>>> plt
.uvsphere
((0,0,0), 1, texture
='res/earth.jpg', xflip
=True, yflip
=False,transform
= lambda tn
,gms
,tms
: ((0, 1, 0, (0.01*tms
)%360),)
)
>>> plt
.show
()
勾選“屏幕錄制”,點擊“播放”按鈕,即可保存為gif文件或mp4/avi格式的視頻文件。
這是代碼中用的的地球紋理圖片,可以直接下載使用。
3. 編寫自己的著色器
WxGL不僅提供了線段、散點、曲面、三維等值面等一系列繪圖函數,還支持用戶定制著色器程序,以實現更復雜的功能。下面這個例子,用粒子技術模擬了煙花升空的過程。
import numpy
as np
import wxgl
import wxgl
.wxplot
as plt
def rise(n
, pos
, h
, v
, a
, cycle
):"""煙花升空模型n - 粒子數量pos - 初始位置h - 上升行程v - 初始速度a - 上升加速度cycle - 循環周期"""vshader_src
= """#version 330 corein vec4 a_Position;in vec4 a_Color;in float a_Delay; // 粒子發射延遲時間(s)uniform float u_Ts; // 持續時間(s)uniform float u_V; // 初始速度uniform float u_A; // 上升加速度uniform mat4 u_MVPMatrix;out vec4 v_Color;out float v_Ts;void main() { float t = u_Ts - a_Delay;if (t < 0) t = 0;float s = u_V * t + 0.5 * u_A * t * t;gl_Position = u_MVPMatrix * vec4(a_Position.x, a_Position.y+s, a_Position.z, a_Position.w); gl_PointSize = 1;v_Color = a_Color;v_Ts = u_Ts;}"""fshader_src
= """#version 330 corein vec4 v_Color;uniform float u_Tmax;in float v_Ts;void main() { if(v_Ts > u_Tmax) discard;vec2 temp = gl_PointCoord - vec2(0.5);float f = dot(temp, temp);if(f > 0.25) discard;gl_FragColor = vec4(v_Color.rgb, 1); } """vs
= np
.array
(pos
) + (np
.random
.random
((n
,3)) - 0.5) * h
/100color
= np
.tile
(np
.array
((1.0,1.0,0.8)), (n
,1))delay
= np
.float32
(np
.absolute
(np
.random
.randn
(n
))) / 10tmax
= (pow(v
*v
+2*a
*h
, 0.5)-v
)/a
+ delay
.max()m
= wxgl
.Model
(wxgl
.POINTS
, vshader_src
, fshader_src
, sprite
=True)m
.set_vertex
('a_Position', vs
)m
.set_color
('a_Color', color
)m
.set_argument
('a_Delay', delay
)m
.set_argument
('u_Ts', lambda tn
,gms
,tms
:(tms
/1000)%cycle
)m
.set_argument
('u_V', v
)m
.set_argument
('u_A', a
)m
.set_argument
('u_Tmax', tmax
)m
.set_mvp_matrix
('u_MVPMatrix') return mvs
= np
.array
([[-1.5,2,1], [-1.5,0,1], [1.5,2,1], [1.5,0,1], [-1.5,2,-1], [-1.5,0,-1], [1.5,2,-1], [1.5,0,-1]])
vs
= vs
[[0,1,2,3,0,2,1,3,4,5,6,7,4,6,5,7,0,4,1,5,2,6,3,7]]
m
= rise
(n
=500, pos
=(0,0,0), h
=1.5, v
=2, a
=-1.2, cycle
=5)plt
.figure
(zoom
=0.7, elev
=10)
plt
.line
(vs
, color
=(0,1,1), method
='isolate')
plt
.model
(m
)
plt
.show
()
4. 綻放的煙花
只要理解了煙花升空的代碼,很容易寫出煙花在空中爆炸的著色器程序。下面的代碼除了煙花升空的著色器,還提供了兩種煙花爆炸的著色器,其中用到了一個紋理圖片,可直接下載下面這張圖使用。
如果將上面的紋理圖片替換成文字,就可以在煙花爆炸的瞬間顯示出文字了。WxGL提供了一個文本轉PIL圖形對象的函數,可以直接作為紋理使用。
import numpy
as np
import wxgl
import wxgl
.wxplot
as plt
def rise(n
, pos
, h
, v
, a
, cycle
):"""煙花升空模型n - 粒子數量pos - 初始位置h - 上升行程v - 初始速度a - 上升加速度cycle - 循環周期"""vshader_src
= """#version 330 corein vec4 a_Position;in vec4 a_Color;in float a_Delay; // 粒子發射延遲時間(s)uniform float u_Ts; // 持續時間(s)uniform float u_V; // 初始速度uniform float u_A; // 上升加速度uniform mat4 u_MVPMatrix;out vec4 v_Color;out float v_Ts;void main() { float t = u_Ts - a_Delay;if (t < 0) t = 0;float s = u_V * t + 0.5 * u_A * t * t;gl_Position = u_MVPMatrix * vec4(a_Position.x, a_Position.y+s, a_Position.z, a_Position.w); gl_PointSize = 1;v_Color = a_Color;v_Ts = u_Ts;}"""fshader_src
= """#version 330 corein vec4 v_Color;uniform float u_Tmax;in float v_Ts;void main() { if(v_Ts > u_Tmax) discard;vec2 temp = gl_PointCoord - vec2(0.5);float f = dot(temp, temp);if(f > 0.25) discard;gl_FragColor = vec4(v_Color.rgb, 1); } """vs
= np
.array
(pos
) + (np
.random
.random
((n
,3)) - 0.5) * h
/100color
= np
.tile
(np
.array
((1.0,1.0,0.8)), (n
,1))delay
= np
.float32
(np
.absolute
(np
.random
.randn
(n
))) / 10tmax
= (pow(v
*v
+2*a
*h
, 0.5)-v
)/a
+ delay
.max()m
= wxgl
.Model
(wxgl
.POINTS
, vshader_src
, fshader_src
, sprite
=True)m
.set_vertex
('a_Position', vs
)m
.set_color
('a_Color', color
)m
.set_argument
('a_Delay', delay
)m
.set_argument
('u_Ts', lambda tn
,gms
,tms
:(tms
/1000)%cycle
)m
.set_argument
('u_V', v
)m
.set_argument
('u_A', a
)m
.set_argument
('u_Tmax', tmax
)m
.set_mvp_matrix
('u_MVPMatrix') return m
, tmax
def bomb_1(n
, pos
, start
, a
, cycle
):"""煙花爆炸模型n - 粒子數量pos - 位置start - 時間a - 下降加速度cycle - 循環周期"""vshader_src
= """#version 330 corein vec4 a_Position;in vec3 a_Data;uniform float u_Ts;uniform float u_Start;uniform float u_A;uniform mat4 u_MVPMatrix;out vec4 v_Color;out float v_Ts;void main() { float t = u_Ts - u_Start;if (t < 0) t = 0;float lat = radians((a_Data.x - 0.5) * 90);float lon = radians(a_Data.y * 360);float r = (a_Data.z * 0.3 + 0.7) * 0.3 * t * (1 + 0.3 * a_Position.z);float y = r * sin(lat) + a_Position.y - 0.5*u_A*t*t;float xz = r * cos(lat);float x = xz * cos(lon) + a_Position.x;float z = xz * sin(lon) + a_Position.z;gl_Position = u_MVPMatrix * vec4(x,y,z,a_Position.w); gl_PointSize = 3 * t;v_Ts = t;int i = gl_VertexID % 6;if (i == 0) v_Color = vec4(1,0,0,1);else if (i == 1) v_Color = vec4(0,1,0,1);else if (i == 2) v_Color = vec4(0,0,1,1);else if (i == 3) v_Color = vec4(1,1,0,1);else if (i == 4) v_Color = vec4(0,1,1,1);else v_Color = vec4(1,0,1,1);}"""fshader_src
= """#version 330 corein vec4 v_Color;in float v_Ts;void main() { if(v_Ts <= 0 || v_Ts > 2) discard;vec2 temp = gl_PointCoord - vec2(0.5);float f = dot(temp, temp);if(f > 0.25) discard;//float alpha = v_Color.a * exp(1-30*f) * (4-v_Ts*v_Ts)/2;float alpha = v_Color.a * (1-4*f) * (4-v_Ts*v_Ts)/2;gl_FragColor = vec4(v_Color.rgb, alpha); } """vs
= np
.tile
(np
.array
(pos
), (n
,1))data
= np
.float32
(np
.random
.random
((n
,3)))m
= wxgl
.Model
(wxgl
.POINTS
, vshader_src
, fshader_src
, sprite
=True, opacity
=False)m
.set_vertex
('a_Position', vs
)m
.set_argument
('a_Data', data
)m
.set_argument
('u_Start', start
)m
.set_argument
('u_A', a
)m
.set_argument
('u_Ts', lambda tn
,gms
,tms
:(tms
/1000)%cycle
)m
.set_mvp_matrix
('u_MVPMatrix') return m
def bomb_2(pos
, start
, texture
, a
, size
, cycle
):"""煙花爆炸模型pos - 位置start - 時間texture - 紋理a - 下降加速度cycle - 循環周期"""vshader_src
= """#version 330 corein vec4 a_Position;uniform float u_Ts;uniform float u_Start;uniform float u_A;uniform float u_Size;uniform mat4 u_MVPMatrix;out float v_Ts;void main() { float t = u_Ts - u_Start;if (t < 0) t = 0;if (t < 2) gl_PointSize = t * u_Size/2 * (1 + 0.3 * a_Position.z);else gl_PointSize = u_Size * (1 + 0.3 * a_Position.z);gl_Position = u_MVPMatrix * vec4(a_Position.x, a_Position.y-0.5*u_A*t*t, a_Position.z, a_Position.w); v_Ts = t;}"""fshader_src
= """#version 330 coreuniform sampler2D u_Fireworks;in float v_Ts;void main() { if(v_Ts <= 0 || v_Ts > 2) discard;vec4 color = texture2D(u_Fireworks, gl_PointCoord);gl_FragColor = vec4(color.rgb, color.a*(4-v_Ts*v_Ts)/2);} """vs
= np
.array
(pos
).reshape
(-1,3)m
= wxgl
.Model
(wxgl
.POINTS
, vshader_src
, fshader_src
, sprite
=True)m
.set_vertex
('a_Position', vs
)m
.set_argument
('u_A', a
)m
.set_argument
('u_Size',size
)m
.set_argument
('u_Start', start
)m
.set_argument
('u_Ts', lambda tn
,gms
,tms
:(tms
/1000)%cycle
)m
.add_texture
('u_Fireworks', texture
, wxgl
.TEXTURE_2D
, yflip
=False)m
.set_mvp_matrix
('u_MVPMatrix') return m
if __name__
== '__main__':vs
= np
.array
([[-1.5,2,1], [-1.5,0,1], [1.5,2,1], [1.5,0,1], [-1.5,2,-1], [-1.5,0,-1], [1.5,2,-1], [1.5,0,-1]])vs
= vs
[[0,1,2,3,0,2,1,3,4,5,6,7,4,6,5,7,0,4,1,5,2,6,3,7]]plt
.figure
(zoom
=0.5, elev
=10)plt
.line
(vs
, color
=(0,1,1,0), method
='isolate') h
, v
, a
, cycle
= 1.7, 2.2, -1.2, 4for i
, ch
in enumerate('新春快樂'):x
= -1.5 + im1
, start
= rise
(n
=300, pos
=(x
,0,1), h
=h
, v
=v
, a
=a
, cycle
=cycle
)m2
= bomb_1
(200, (x
,h
,1), start
, a
=0.1, cycle
=cycle
)m3
= bomb_2
((x
,h
,1), start
, wxgl
.text2image
(ch
, 96, (1,0,0)), a
=0.1, size
=100, cycle
=cycle
)plt
.model
(m1
)plt
.model
(m2
)plt
.model
(m3
)for i
in range(20):x
, z
= (np
.random
.random
()-0.5)*4, (np
.random
.random
()-0.5)*2h
, v
, a
= 1.5+(np
.random
.random
()-0.5)*0.4, 2.2, -1.2cycle
= np
.random
.randint
(4, 7)m1
, start
= rise
(n
=300, pos
=(x
,0,z
), h
=h
, v
=v
, a
=a
, cycle
=cycle
)m2
= bomb_1
(200, (x
,h
,z
), start
, a
=0.1, cycle
=cycle
)plt
.model
(m1
)plt
.model
(m2
)for i
in range(20):x
, z
= (np
.random
.random
()-0.5)*4, (np
.random
.random
()-0.5)*2h
, v
, a
= 1.5+(np
.random
.random
()-0.5)*0.4, 2.3, -1.2cycle
= np
.random
.randint
(4, 7)m1
, start
= rise
(n
=300, pos
=(x
,0,z
), h
=h
, v
=v
, a
=a
, cycle
=cycle
)m2
= bomb_2
((x
,h
,z
), start
, 'res/fw.png', a
=0.1, size
=300, cycle
=cycle
)plt
.model
(m1
)plt
.model
(m2
)plt
.show
()
最終的效果如下面的gif所示。
總結
以上是生活随笔為你收集整理的用OpenGL导演一场烟花盛会,迎接即将到来的新年的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。