好的,各位观众老爷们,今天咱们来聊聊Vispy——一个让你在科学可视化领域飞起来的OpenGL小火箭。别怕OpenGL听起来高大上,有了Vispy,咱们也能轻松驾驭。
开场白:为什么选择Vispy?
想象一下,你辛辛苦苦跑了一堆数据,结果用matplotlib画出来的图慢得像蜗牛,转个角度卡成PPT。是不是想摔键盘?这时候,Vispy就是你的救星!
简单来说,Vispy的优势在于:
- 高性能: 基于OpenGL,GPU加速,处理大数据不在话下。
- 灵活性: 可以定制各种shader,实现各种炫酷的可视化效果。
- 易用性: 提供Python接口,方便上手。
- 跨平台: Windows、macOS、Linux通吃。
第一部分:Vispy基础入门
首先,安装Vispy。打开你的终端,输入:
pip install vispy
安装完成之后,咱们来创建一个简单的窗口。
import vispy
from vispy import app
class Canvas(app.Canvas):
def __init__(self):
app.Canvas.__init__(self, keys='interactive', size=(800,600))
self.show()
def on_draw(self, event):
vispy.gloo.clear('white') # 设置背景颜色为白色
if __name__ == '__main__':
canvas = Canvas()
app.run()
这段代码创建了一个800×600的白色窗口。解释一下:
vispy.app.Canvas
:Vispy的画布类,所有可视化内容都绘制在这个上面。keys='interactive'
:允许使用键盘交互。on_draw
:当窗口需要重绘时调用的函数。vispy.gloo.clear('white')
:使用白色清除画布。app.run()
:启动Vispy的事件循环。
运行这段代码,你应该能看到一个白色的窗口。是不是很简单?
第二部分:绘制简单的图形
接下来,咱们来画一个简单的三角形。
import vispy
from vispy import app
from vispy import gloo
import numpy as np
VERT_SHADER = """
attribute vec4 a_position;
void main() {
gl_Position = a_position;
}
"""
FRAG_SHADER = """
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 红色
}
"""
class Canvas(app.Canvas):
def __init__(self):
app.Canvas.__init__(self, keys='interactive', size=(800,600))
# 定义三角形的顶点坐标
vertices = np.array([[-0.5, -0.5], [0.5, -0.5], [0.0, 0.5]], dtype=np.float32)
# 创建vertex buffer
self.vertex_buffer = gloo.VertexBuffer(vertices)
# 创建program
self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
self.program['a_position'] = self.vertex_buffer
self.show()
def on_draw(self, event):
vispy.gloo.clear('white')
self.program.draw('triangles')
if __name__ == '__main__':
canvas = Canvas()
app.run()
这段代码绘制了一个红色的三角形。解释一下:
VERT_SHADER
:顶点着色器,定义了顶点的位置。FRAG_SHADER
:片段着色器,定义了像素的颜色。vertices
:三角形的顶点坐标,是一个NumPy数组。gloo.VertexBuffer
:顶点缓冲区,用于存储顶点数据。gloo.Program
:OpenGL程序,将顶点着色器和片段着色器组合在一起。self.program['a_position'] = self.vertex_buffer
:将顶点缓冲区绑定到顶点着色器的a_position
属性。self.program.draw('triangles')
:绘制三角形。
重点来了:Shader!
Shader是OpenGL的核心。它们是用GLSL(OpenGL Shading Language)编写的小程序,运行在GPU上,负责处理顶点和像素。
- 顶点着色器(Vertex Shader): 处理顶点数据,例如位置、颜色、法线等。它的输入是顶点属性,输出是顶点的最终位置。
- 片段着色器(Fragment Shader): 处理像素数据,例如颜色、纹理等。它的输入是插值后的顶点属性,输出是像素的最终颜色。
上面的例子中,顶点着色器只是简单地将顶点位置传递给OpenGL,片段着色器将所有像素设置为红色。
第三部分:Vispy进阶技巧
-
使用Transformations
Vispy提供了各种transformation,例如平移、旋转、缩放等,方便我们操作图形。
from vispy import scene from vispy.scene import visuals canvas = scene.SceneCanvas(keys='interactive', size=(800, 600), show=True) view = canvas.central_widget.add_view() # 创建一个立方体 cube = visuals.Cube(size=1) view.add(cube) # 使用MatrixTransform进行旋转 from vispy.util import transforms matrix = transforms.MatrixTransform() matrix.rotate(30, (1, 1, 0)) # 绕(1, 1, 0)轴旋转30度 cube.transform = matrix # 设置相机 view.camera = scene.cameras.TurntableCamera(fov=60.0, distance=3) if __name__ == '__main__': scene.run()
这段代码创建了一个旋转的立方体。
MatrixTransform
可以方便地进行各种矩阵变换。 -
使用Mesh绘制复杂图形
Mesh是Vispy中最常用的图形类型之一,可以用来绘制各种复杂的3D模型。
import vispy from vispy import app from vispy import gloo import numpy as np VERT_SHADER = """ attribute vec4 a_position; attribute vec4 a_color; varying vec4 v_color; uniform mat4 u_model; uniform mat4 u_view; uniform mat4 u_projection; void main() { v_color = a_color; gl_Position = u_projection * u_view * u_model * a_position; } """ FRAG_SHADER = """ varying vec4 v_color; void main() { gl_FragColor = v_color; } """ class Canvas(app.Canvas): def __init__(self): app.Canvas.__init__(self, keys='interactive', size=(800,600)) # 创建一个立方体的顶点和颜色 vertices, faces, normals, values = vispy.geometry.create_cube() colors = np.random.rand(vertices.shape[0], 4).astype(np.float32) # 创建vertex buffer self.vertex_buffer = gloo.VertexBuffer(vertices) self.color_buffer = gloo.VertexBuffer(colors) self.index_buffer = gloo.IndexBuffer(faces) # 创建program self.program = gloo.Program(VERT_SHADER, FRAG_SHADER) self.program['a_position'] = self.vertex_buffer self.program['a_color'] = self.color_buffer # 设置model, view, projection矩阵 self.model = np.eye(4, dtype=np.float32) self.view = np.eye(4, dtype=np.float32) self.projection = np.eye(4, dtype=np.float32) self.program['u_model'] = self.model self.program['u_view'] = self.view self.program['u_projection'] = self.projection gloo.set_state(depth_test=True) self.theta = 0 self.phi = 0 self.show() self._timer = app.Timer('auto', connect=self.on_timer, start=True) def on_draw(self, event): vispy.gloo.clear('black', depth=True) self.program.draw('triangles', self.index_buffer) def on_timer(self, event): self.theta += .5 self.phi += .5 self.model = np.eye(4, dtype=np.float32) self.model = vispy.util.transforms.rotate(self.theta, (0, 0, 1)) self.model = np.dot(self.model, vispy.util.transforms.rotate(self.phi, (0, 1, 0))) self.program['u_model'] = self.model self.update() if __name__ == '__main__': canvas = Canvas() app.run()
这段代码创建了一个旋转的彩色立方体。注意,我们需要手动设置model, view, projection矩阵,才能正确地显示3D模型。
-
使用Visuals简化代码
Vispy提供了许多现成的Visuals,例如
Line
,Scatter
,Image
等,方便我们绘制常见的图形。from vispy import scene from vispy.scene import visuals import numpy as np canvas = scene.SceneCanvas(keys='interactive', size=(800, 600), show=True) view = canvas.central_widget.add_view() # 创建一些随机点 pos = np.random.normal(size=(100, 3)) colors = np.random.normal(size=(100, 4)) # 使用Scatter绘制散点图 scatter = visuals.Scatter(pos, color=colors, size=10) view.add(scatter) # 设置相机 view.camera = scene.cameras.TurntableCamera(fov=60.0, distance=3) if __name__ == '__main__': scene.run()
这段代码创建了一个随机的散点图。使用
visuals.Scatter
可以方便地绘制散点图。
第四部分:Vispy实战案例
-
绘制大规模点云
import vispy from vispy import app from vispy import gloo import numpy as np VERT_SHADER = """ attribute vec4 a_position; attribute vec4 a_color; varying vec4 v_color; uniform mat4 u_model; uniform mat4 u_view; uniform mat4 u_projection; void main() { v_color = a_color; gl_Position = u_projection * u_view * u_model * a_position; gl_PointSize = 2.0; // 设置点的大小 } """ FRAG_SHADER = """ varying vec4 v_color; void main() { gl_FragColor = v_color; } """ class Canvas(app.Canvas): def __init__(self): app.Canvas.__init__(self, keys='interactive', size=(800,600)) # 创建大规模点云 (100万个点) n_points = 1000000 vertices = np.random.normal(size=(n_points, 3), scale=0.5).astype(np.float32) colors = np.random.rand(n_points, 4).astype(np.float32) # 创建vertex buffer self.vertex_buffer = gloo.VertexBuffer(vertices) self.color_buffer = gloo.VertexBuffer(colors) # 创建program self.program = gloo.Program(VERT_SHADER, FRAG_SHADER) self.program['a_position'] = self.vertex_buffer self.program['a_color'] = self.color_buffer # 设置model, view, projection矩阵 self.model = np.eye(4, dtype=np.float32) self.view = np.eye(4, dtype=np.float32) self.projection = np.eye(4, dtype=np.float32) self.program['u_model'] = self.model self.program['u_view'] = self.view self.program['u_projection'] = self.projection gloo.set_state(depth_test=True) self.theta = 0 self.phi = 0 self.show() self._timer = app.Timer('auto', connect=self.on_timer, start=True) def on_draw(self, event): vispy.gloo.clear('black', depth=True) self.program.draw('points') # 绘制点云 def on_timer(self, event): self.theta += .5 self.phi += .5 self.model = np.eye(4, dtype=np.float32) self.model = vispy.util.transforms.rotate(self.theta, (0, 0, 1)) self.model = np.dot(self.model, vispy.util.transforms.rotate(self.phi, (0, 1, 0))) self.program['u_model'] = self.model self.update() if __name__ == '__main__': canvas = Canvas() app.run()
这段代码绘制了一个包含100万个点的旋转点云。注意,使用
gl_PointSize
可以设置点的大小。 -
绘制体数据(Volume Rendering)
import vispy from vispy import app from vispy import gloo import numpy as np VERT_SHADER = """ void main() { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); } """ FRAG_SHADER = """ uniform sampler3D u_volume; uniform vec3 u_data_shape; uniform mat4 u_model; uniform mat4 u_view; uniform mat4 u_projection; uniform float u_alpha; const int n_samples = 200; //采样点数 void main() { // 计算光线方向 vec3 ray_origin = (inverse(u_view) * vec4(0.0, 0.0, 0.0, 1.0)).xyz; vec3 ray_direction = normalize((inverse(u_model) * inverse(u_projection) * vec4(gl_FragCoord.xy / vec2(800.0, 600.0) * 2.0 - 1.0, 0.0, 1.0)).xyz - ray_origin); // 计算步长 float step_size = 1.0 / float(n_samples); // 从前向后采样 float alpha = 0.0; vec4 color = vec4(0.0); for (int i = 0; i < n_samples; ++i) { // 计算采样点位置 vec3 sample_position = ray_origin + ray_direction * step_size * float(i); // 将采样点位置归一化到[0, 1] vec3 normalized_position = (sample_position + u_data_shape / 2.0) / u_data_shape; // 采样体数据 float value = texture(u_volume, normalized_position).r; // 使用传递函数 (transfer function) float alpha_value = value * u_alpha; vec4 sample_color = vec4(value, value, value, alpha_value); // 前向合成 color = color + sample_color * (1.0 - color.a); alpha = color.a; // 如果alpha接近1,停止采样 if (alpha >= 0.95) { break; } } gl_FragColor = color; } """ class Canvas(app.Canvas): def __init__(self): app.Canvas.__init__(self, keys='interactive', size=(800,600)) # 创建体数据 (例如,一个3D高斯分布) data_shape = (64, 64, 64) x, y, z = np.mgrid[-1:1:data_shape[0]*1j, -1:1:data_shape[1]*1j, -1:1:data_shape[2]*1j] volume_data = np.exp(-5 * (x**2 + y**2 + z**2)).astype(np.float32) # 创建texture self.volume_texture = gloo.Texture3D(volume_data, internalformat='r32f') # 创建program self.program = gloo.Program(VERT_SHADER, FRAG_SHADER) self.program['u_volume'] = self.volume_texture self.program['u_data_shape'] = data_shape self.program['u_alpha'] = 0.1 # 控制透明度 # 设置model, view, projection矩阵 self.model = np.eye(4, dtype=np.float32) self.view = np.eye(4, dtype=np.float32) self.projection = np.eye(4, dtype=np.float32) # 设置透视投影 self.projection = vispy.util.transforms.perspective(60.0, 800.0/600.0, 0.1, 100.0) # 设置相机位置 self.view = vispy.util.transforms.translate((0, 0, -3)) self.program['u_model'] = self.model self.program['u_view'] = self.view self.program['u_projection'] = self.projection gloo.set_state(depth_test=False) self.theta = 0 self.phi = 0 self.show() self._timer = app.Timer('auto', connect=self.on_timer, start=True) def on_draw(self, event): vispy.gloo.clear('black') #绘制一个覆盖整个窗口的四边形 gloo.gl.glDrawArrays(gloo.gl.GL_TRIANGLE_STRIP, 0, 4) def on_timer(self, event): self.theta += .5 self.phi += .5 self.model = np.eye(4, dtype=np.float32) self.model = vispy.util.transforms.rotate(self.theta, (0, 0, 1)) self.model = np.dot(self.model, vispy.util.transforms.rotate(self.phi, (0, 1, 0))) self.program['u_model'] = self.model self.update() if __name__ == '__main__': canvas = Canvas() app.run()
这段代码使用光线投射(Ray Casting)技术渲染了一个3D高斯分布。注意,我们需要使用
sampler3D
类型的uniform变量来访问体数据。
总结:Vispy的优势与局限
特性 | 优势 | 局限 |
---|---|---|
性能 | 基于OpenGL,GPU加速,处理大数据效率高 | 需要一定的OpenGL基础才能发挥最大性能 |
灵活性 | 可以定制各种shader,实现各种炫酷的可视化效果 | Shader编写需要一定的GLSL知识 |
易用性 | 提供Python接口,方便上手,提供许多现成的Visuals | 相比matplotlib,学习曲线稍陡峭 |
跨平台 | Windows、macOS、Linux通吃 | |
社区支持 | 社区活跃,文档完善 |
最后的温馨提示:
- Vispy虽然强大,但也不是万能的。对于简单的绘图任务,matplotlib可能更方便。
- 学习Vispy需要一定的OpenGL基础,建议先了解一些OpenGL的基本概念。
- 多看Vispy的官方文档和示例代码,可以帮助你更快地掌握Vispy。
- 遇到问题不要慌,Google一下,或者到Vispy的社区提问。
好了,今天的Vispy讲座就到这里。希望大家能够掌握Vispy的基本用法,并在科学可视化领域取得更大的成就! 祝各位 coding 愉快!