通过 OpenGL 的三维纹理实现 3D 地震数据的 3 方向切片显示,系列代码已开源。
开源链接:opengl_practice (github.com)
2D 纹理实现回顾
回顾 2D 纹理的实现,是画了两个三角形,然后给两个三角形贴上对应位置的纹理,相关代码:
float vertices[] = {
// positions // texture coords
1.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top right
1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom right
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom left
-1.0f, 1.0f, 0.0f, 0.0f, 1.0f // top left
};
unsigned int indices[] = {
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
指定顶点数组 vertices 中前 3 个值为三角形顶点的空间坐标,后 2 个值为纹理坐标:
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// texture coord attribute
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
按照 indices 从vertices 中取顶点画三角形,如果将 indices 改为:
unsigned int indices[] = {
0, 1, 3, // first triangle
//1, 2, 3 // second triangle
};
此时运行结果就只有第一个三角形了:
3D 切片实现
3D 纹理不只有宽度和高度,还有深度,其最大宽度和高度与 2D 纹理,最大深度可通过查询 GL_MAX_3D_TEXTURE_SIZE 的值来获取,我这里的值是 2048。
3D 纹理的实现与 2D 纹理相似,此时顶点数组中纹理坐标需要有 3 个值,下面只展示 y-z 切片的顶点数组:
float vertices[] = {
// positions // texture coords
0.0f, 1.0f, 1.0f, 0.5f, 1.0f, 1.0f, // top right
0.0f, 1.0f, -1.0f, 0.5f, 1.0f, 0.0f, // bottom right
0.0f, -1.0f, 1.0f, 0.5f, 0.0f, 1.0f, // top left
0.0f, 1.0f, -1.0f, 0.5f, 1.0f, 0.0f, // bottom right
0.0f, -1.0f, -1.0f, 0.5f, 0.0f, 0.0f, // bottom left
0.0f, -1.0f, 1.0f, 0.5f, 0.0f, 1.0f, // top left
};
指定顶点数组 vertices 中前 3 个值为三角形顶点的空间坐标,后 3 个值为纹理坐标:
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// texture coord attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
和 2D 纹理不同,这里我用 6 组坐标画 2 个三角形,没有用 indices 来复用坐标,两种方法都可以。
注意,在顶点着色器和片段着色器中,也需要将纹理坐标改为 3 个。
最后效果:
让切片动起来
能否通过设置切片位置,让切片自动滑动呢?是可以实现的。
在 GLSL 中,uniform 变量可以将外部程序中的变量传递给着色器。可以发现,上一小节顶点数组中,纹理坐标与顶点的空间坐标存在线性关系:空间坐标范围是 [-1, 1],纹理坐标范围是 [0, 1],故 (空间坐标 + 1) / 2 = 纹理坐标。
实际上,纹理坐标的范围必须是0~1,而顶点空间坐标可以根据需要设置。
由于纹理坐标可由空间坐标得到,将切片位置设置为 uniform 变量,那么 y-z 切片的顶点数组可以写为:
float vertices[] = {
// index //positions
0, 1.0f, 1.0f,
0, 1.0f, -1.0f,
0, -1.0f, 1.0f,
0, 1.0f, -1.0f,
0, -1.0f, -1.0f,
0, -1.0f, 1.0f,
}
第一个值为与切片垂直的轴,即切片移动的轴,0 表示 y-z 切片,即 x 轴。
指定顶点数组 vertices 中前 1 个值为切片移动的轴,后 2 个值为三角形顶点空间坐标:
// axis
glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// position attribute
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(1 * sizeof(float)));
glEnableVertexAttribArray(1);
以 y-z 切片为例,顶点着色器部分代码:
uniform float id0;
void main()
{
vec2 aTexCoord = (aPos+1)/2;
vec4 Pos;
if (aAxis < 1){
TexCoord = vec3(id0, 1-aTexCoord.x, aTexCoord.y);
Pos = vec4(2*id0-1, aPos.x, aPos.y, 1.0f);
}
gl_Position = projection * view * model * Pos;
}
此处只展示 y-z 切片相关代码,完整代码需要将 3 方向切片都写入 if else 中。最开始我是用 switch 语句,但是那样只显示 aAxis=0 的切片。如有更好的方法,还望读者赐教。
最终效果(切片随时间移动,这里将 5 个时刻的图像拼接显示):