让我们通过以下简单步骤开始我们的配方:

1.通过读取外部的体数据文件,并通过该加载数据集数据转换成一个OpenGL纹理。也使硬件的mipmap生成。通常情况下,从使用一个横截面中获得的体积数据文件存储密度影像学检查方法,如CT或MRI扫描。每个CT/ MRI扫描是一个二维切片。我们在Z方向上积累简单的2D纹理的数组获得3D纹理。密度存储不同的材料的类型,例如值在0到20之间的是空气。当我们有一个8位无符号数据集,我们把数据集存储到GLubyte类型的本地数组。如果我们有一个16位无符号的数据集我们可以将其存储到GLushort类型的本地数组。3D纹理的情况下,除了S和T的参数,我们有一个额外的参数R控制我们在3D纹理下的切片。

std::ifstream infile(volume_file.c_str(), std::ios_base::binary);
if(infile.good()) {
GLubyte* pData = new GLubyte[XDIM*YDIM*ZDIM];
infile.read(reinterpret_cast<char*>(pData),
XDIM*YDIM*ZDIM*sizeof
(GLubyte));
infile.close();

glGenTextures(, &textureID);
glBindTexture(GL_TEXTURE_3D, textureID);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S,
GL_CLAMP);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T,
GL_CLAMP);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R,
GL_CLAMP);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER,
GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL,
);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, );
glTexImage3D(GL_TEXTURE_3D,,GL_RED,XDIM,YDIM,ZDIM,
,GL_RED,GL_
UNSIGNED_BYTE,pData);

glGenerateMipmap(GL_TEXTURE_3D);
return true;
} else {
return false;
}

3D纹理的过滤参数类似于我们之前看到的2D纹理参数。Mipmap是被用于细节功能的纹理的下采样版本的集合。如果观众距离被应用纹理物体非常远,它们帮助使用下采样纹理。这有助于改善应用程序的性能。我们必须指定最大数目的层级(GL_TEXTURE_MAX_LEVEL),即是给定纹理生成的最大mipmap数。另外,基本层(GL_TEXTURE_BASE_LEVEL)表示当对象最近时第一级所使用的mipmap。

glGenerateMipMap函数的通过在之前的层级过滤减少操作生成派生数组作用。所以让我们说我们有三个mipmap层级,我们的3D纹理在层级0有256*256*256的分辨率。在层级1mipmap,0级数据通过过滤减少到一半大小至128*128*128.对于第二层级mipmap,层级一被过滤并减少到64*64*64.最后,作为第三层级,层级2将被过滤并减少到32*32*32.

2.设置一个顶点数组对象和顶点缓冲区对象存储的几何代理切片。确保缓冲区对象使用被指定为GL_DYNAMIC_DRAW。初始glBufferData 调用分配GPU内存以获得最大切片数。vTextureSlices数组是全局定义,它存储由顶点纹理切片操作三角产生。glBufferData用0初始化,该数据将在运行时被动态的填充。

const int MAX_SLICES = ;
glm::vec3 vTextureSlices[MAX_SLICES*];
glGenVertexArrays(, &volumeVAO);
glGenBuffers(, &volumeVBO);
glBindVertexArray(volumeVAO);
glBindBuffer (GL_ARRAY_BUFFER, volumeVBO);
glBufferData (GL_ARRAY_BUFFER,
sizeof(vTextureSlices), , GL_
DYNAMIC_DRAW);
glEnableVertexAttribArray();
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE,,);
glBindVertexArray();

3.通过找到一个垂直于观察方向的具有代理切片的单位立方体交叉点实现体的切片。这是SliceVolume函数的功能。

因为我们的数据在所有三个轴具有相等大小即256*256*256.我们使用单位立方体。如果我们有不等尺寸的数据集我们可以适当的缩放单位立方体。

//determine max and min distances
glm::vec3 vecStart[];
glm::vec3 vecDir[];
float
float
float
float
float
lambda[];
lambda_inc[];
denom = ;
plane_dist = min_dist;
plane_dist_inc = (max_dist-min_dist)/float(num_slices);
//determine vecStart and vecDir values
glm::vec3 intersection[];
float dL[];
for(int i=num_slices-;i>=;i--) {
for(int e = ; e < ; e++)
{
dL[e] = lambda[e] + i*lambda_inc[e];
}
if
((dL[] >= 0.0) && (dL[] < 1.0))
intersection[] = vecStart[] +
dL[]*vecDir[];
{
}
//like wise for all intersection points
int indices[]={,,, ,,, ,,, ,,};
for(int i=;i<;i++)
vTextureSlices[count++]=intersection[indices[i]];
}
//update buffer object
glBindBuffer(GL_ARRAY_BUFFER, volumeVBO);
glBufferSubData(GL_ARRAY_BUFFER, ,
sizeof(vTextureSlices), &(vTextureSlices[].x));

4.在渲染函数,设置混合绑定体顶点数组对象,结合shader,然后调用glDrawArrays函数

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindVertexArray(volumeVAO);
shader.Use();
glUniformMatrix4fv(shader("MVP"), , GL_FALSE, glm::value_
ptr(MVP));
glDrawArrays(GL_TRIANGLES, , sizeof(vTextureSlices)/
sizeof(vTextureSlices[]));
shader.UnUse();
glDisable(GL_BLEND);

它是如何工作的:

使用3D纹理切片体绘制接近由alpha混合纹理切片整体化的体绘制。第一步是加载并通过体数据生成3D纹理。装载体数据集之后,体切片由代理切片带出。这些体切片被定向在垂直于观察方向的方向。此外,我们需要找到代理多边形和单位立方体边界的交叉点。这由SliceVolume函数实现。需要注意的是切片只当视图转动时实现。

我们首先获得视线方向矢量(viewDir),它是模型试图矩阵的第三列。模型视图矩阵的第一列存放右向量,第二列存放上向量。我们现在详细介绍SliceVolume函数内部如何工作。我们通过计算在观看方向上8个单位顶点的最小最大距离找到当前观看方向最小最大顶点。这些距离通过每个单位立方体顶点与视线方向向量点积得到。

float max_dist = glm::dot(viewDir, vertexList[]);
float min_dist = max_dist;
int max_index = ;
int count = ;
for(int i=;i<;i++) {
float dist = glm::dot(viewDir, vertexList[i]);
if(dist > max_dist) {
max_dist = dist;
max_index = i;
}
if(dist<min_dist)
min_dist = dist;
}
int max_dim = FindAbsMax(viewDir);
min_dist -= EPSILON;
max_dist += EPSILON;

从最近顶点走到最远顶点,从相机只有三个唯一路径。我们存储每个顶点的所有可能路径到一个边表,定义如下:

int edgeList[][]={{,,,, ,,,, ,,, }, //v0 is front
{,,,, ,,,,,,, }, //v1 is front
{,,,,,,,,,,,}, //v2 is front
{ ,,,, ,,,,,,, }, // v3 is front
{ ,,,,,,,, ,,, }, // v4 is front
{ ,,,, ,,,, ,,, }, // v5 is front
{ ,,,,,,,,,,,}, // v6 is front
{ ,,,, ,,,,,,, } // v7 is front

接下来,平面交叉口为单位立方体的12个边索引估计距离:

glm::vec3 vecStart[];
glm::vec3 vecDir[];
float lambda[];
float lambda_inc[];
float denom = ;
float plane_dist = min_dist;
float plane_dist_inc = (max_dist-min_dist)/float(num_slices);
for(int i=;i<;i++) {
vecStart[i]=vertexList[edges[edgeList[max_index][i]][]];
vecDir[i]=vertexList[edges[edgeList[max_index][i]][]]-
vecStart[i];
denom = glm::dot(vecDir[i], viewDir);
if (1.0 + denom != 1.0) {
lambda_inc[i] = plane_dist_inc/denom;
lambda[i]=(plane_dist-glm::dot(vecStart[i],viewDir))/denom;
} else {
lambda[i]
= -1.0;
lambda_inc[i] = 0.0;
}
}

最后,通过在视线方向从后端到前端移动带出内插交点和单位立方体边。代理切片生成后,顶点缓冲对象用新的数据更新。

for(int i=num_slices-;i>=;i--) {
for(int e = ; e < ; e++) {
dL[e] = lambda[e] + i*lambda_inc[e];
}
if ((dL[] >= 0.0) && (dL[] < 1.0)) {
intersection[] = vecStart[] + dL[]*vecDir[];
} else if ((dL[] >= 0.0) && (dL[] < 1.0)) {
intersection[] = vecStart[] + dL[]*vecDir[];
} else if ((dL[] >= 0.0) && (dL[] < 1.0)) {
intersection[] = vecStart[] + dL[]*vecDir[];
} else continue;
if ((dL[] >= 0.0) && (dL[] < 1.0)){
intersection[] = vecStart[] + dL[]*vecDir[];
} else if ((dL[] >= 0.0) && (dL[] < 1.0)){
intersection[] = vecStart[] + dL[]*vecDir[];
} else if ((dL[] >= 0.0) && (dL[] < 1.0)){
intersection[] = vecStart[] + dL[]*vecDir[];
} else {intersection[] = vecStart[] + dL[]*vecDir[];
}
//similarly for others edges unitl intersection[5]
int indices[]={,,, ,,, ,,, ,,};
for(int i=;i<;i++)
vTextureSlices[count++]=intersection[indices[i]];
}
glBindBuffer(GL_ARRAY_BUFFER, volumeVBO);
glBufferSubData(GL_ARRAY_BUFFER, , sizeof(vTextureSlices),
&(vTextureSlices[].x));

在渲染函数,合适的shader被绑定。顶点shader通过对象空间顶点位置(vPosition)乘以混合模型视图投影(MVP)矩阵,计算出夹子空间位置。它还计算用于3D纹理坐标的体数据。因为我们渲染一个单位立方体,最小的顶点位置将是(-0.5,-0.5,-0.5)而最大顶点位置将是(0.5,0.5,0.5)。由于我们3D纹理查询需要从(0,0,0)到(1,1,1)的坐标。我们添加(0.5,0.5,0.5)到对象空间顶点位置来获得正确的3D纹理坐标。

smooth out vec3 vUV;
void main() {
gl_Position = MVP*vec4(vVertex.xyz,);
vUV = vVertex + vec3(0.5);
}

然后片段shader使用3D纹理坐标进行体数据采样(其现在为3D纹理采用一个新的采样类型sampler3D)来显示密度。在创建3D纹理时,我们指定内部格式GL_RED(glTexImage3D函数的第三个参数)。因此,我们现在可以通过红色通道访问我们的密度。要获得灰色的shader,我们把绿色蓝色和alpha通道设为相同的值。

smooth in vec3 vUV;
uniform sampler3D volume;
void main(void) {
vFragColor = texture(volume, vUV).rrrr;
}

在以前的OpenGL版本,我们将体密度存储在一个特殊的内部格式GL_INTENSITY。这在OpenGL3.3核心特性已经过时了。所以现在我们必须使用GL_RED,GL_GREEN,GL_BLUE,或GL_ALPHA内部格式。

OpenGL Development Cookbook chapter7部分翻译的更多相关文章

  1. 【odoo14】odoo 14 Development Cookbook【目录篇】

    网上已经有大佬翻译过odoo12并且在翻译odoo14了.各位着急的可以自行搜索下... 这本书是为了让自己从odoo12转odoo14学习.也是为了锻炼下自己... odoo 14 Developm ...

  2. Setting up an OpenGL development environment in ubuntu

    1.opening terminal window and entering the apt-get command for the packages: sudo apt-get install me ...

  3. OpenGL 4.3配置教程

    OpenGL 4.3配置教程 下载开发包 需要下载的开发包主要包含如下几个组件:freeglut+glew+ OpenGL.Development.Cookbook+源码+GLM+SOIL. Open ...

  4. OpenGL book list

      From: https://www.codeproject.com/Articles/771225/Learning-Modern-OpenGL   A little guide about mo ...

  5. (转) [it-ebooks]电子书列表

    [it-ebooks]电子书列表   [2014]: Learning Objective-C by Developing iPhone Games || Leverage Xcode and Obj ...

  6. Visual C++ for Linux Development

    原文  https://blogs.msdn.microsoft.com/vcblog/2016/03/30/visual-c-for-linux-development/ Visual C++ fo ...

  7. Nhibernate cookbook 3.0-翻译

    /* Style Definitions */ table.MsoNormalTable {mso-style-name:普通表格; mso-tstyle-rowband-size:0; mso-ts ...

  8. OpenGL教程之新手上路

    Jeff Molofee(NeHe)的OpenGL教程- 新手上路 译者的话:NeHe的教程一共同拥有30多课,内容翔实,而且不断更新 .国内的站点实在应该向他们学习.令人吃惊的是,NeHe提供的例程 ...

  9. Linux(Ubuntu) OpenGL 开发环境

    Linux(Ubuntu) OpenGL 开发环境 在 PC 平台上开发 OpenGL 可以使用的辅助工具有很多选择,这里我主要参考了 learnopengl 的配置,使用 GLFW 和 GLAD. ...

随机推荐

  1. TinyMCE实现简单的本地上传

    TinyMCE这个东西很多地方再用,不过我以前一直没用过,最近才接触,因为有一套现成的metro风格的皮肤,仅此而已,不过最终如何调用还是我得来实现.其他的都好说,网上的资料一大把一大把的,唯独这个本 ...

  2. 易Android登录Demo

    上一页介绍Android项目简单的页面跳转实例,算是对开发环境的熟悉,这一篇将在此基础上增加一些简单的逻辑,实现登录的效果. 登录之前: 登录成功: watermark/2/text/aHR0cDov ...

  3. Android一些解决方案内存问题(一)

    通常我们遇到内存问题时,,解决方案一般有以下的例子: 1.做一些处理上的内存引用,经常使用软引用.加强引用.弱引用: 2.加载在内存中的照片时,它可以处理直接在内存,例如:压缩边界. 3.内存的动态恢 ...

  4. 苹果公司的新的编程语言 Swift 高级语言()两--基本数据类型

    一  .   常量和变量 Swift语言 对常量和变量的声明进行了明白的区分 Swift语言的常量类型比C 语言的constants类型更加强大,语义更加明白. 常量和变量的差别是常量在设置或初始化后 ...

  5. String.Split()功能

    我们在过去的教训 String.Join功能(http://blog.csdn.net/zhvsby/archive/2008/11/28/3404704.aspx).当中用到了String.SPli ...

  6. Set <STL>

    set是维护集合的容器 #include <cstdio> #include <set> using namespace std; int main() { //声明 set& ...

  7. Use PRODUCT_USER_PROFILE To Limit User

    The PRODUCT_USER_PROFILE (PUP) table provides product-level security that supplements the user-level ...

  8. Alice&#39;s Chance

    id=1698" style="background-color:rgb(51,255,51)">主题链接 意甲冠军: 爱丽丝要拍电影.有n部电影,规定爱丽丝第i部 ...

  9. uva10827-Maximum sum on a torus(矩阵最大和的变形)

    题目;uva10827-Maximum sum on a torus(矩阵最大和的变形) 题目大意:就是uva108的变形,矩阵能够连通,就是能够从后面连到前面.这里把矩阵复制三遍,然后又一次生成一个 ...

  10. linux通过key区别登陆的人

    key区分登录用户 脚本放 /etc/profile.d,会默认登录的时候执行, 类似于 #!/bin/bash # filename: /etc/profile.d/set_log_file.sh ...