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

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. 【ThinkingInC++】2、输入和输出流

    /** *特征:输入和输出流 *时间:2014年8月8日07:37:35 *作者:cutter_point */ #include<iostream> using namespace st ...

  2. Session or Cookie?是否有必要使用Tomcat等一下Web集装箱Session

    Cookie是HTTP协议标准下的存储用户信息的工具.浏览器把用户信息存放到本地的文本文件里. Session是基于Cookie实现的. 2011年4月,武汉群硕面试的时候(实习生).面试官也问过这个 ...

  3. Qt Quick 组件和动态创建的对象具体的解释

    在<Qt Quick 事件处理之信号与槽>一文中介绍自己定义信号时,举了一个简单的样例.定义了一个颜色选择组件,当用户在组建内点击鼠标时,该组件会发出一个携带颜色值的信号,当时我使用 Co ...

  4. (hdu step 6.3.2)Girls and Boys(比赛离开后几个人求不匹配,与邻接矩阵)

    称号: Girls and Boys Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others ...

  5. JS常用方法总结,及jquery异步调用后台方法实例

    //前台接收get参数值 function getQueryString(name) {            var queryStrings = window.location.search.sp ...

  6. [Android] Upload package to device fails #2720

    错误描述: 解决办法:   来源:https://facebook.github.io/react-native/docs/android-setup.html

  7. linux_UBUNTU 12.04 上使用 SQUID 架设HTTP正向代理服务器

    配置普通HTTP正向代理 安装   1 sudo apt-get install squid squid-common 配置 squid3   1 sudo vim /etc/squid3/squid ...

  8. tomcat的webapps文件夹下放更新后的项目就訪问不了

    昨天给同事更新完程序,同事说更新后的程序訪问不了.它曾经的程序叫tj52,更新后的程序叫webapp.也就是tomcat的文件夹有两个文件架,一个叫webapp,一个叫tj52.最后另外一同事给了解决 ...

  9. 移动客户端与服务端Session那点秘密

    众所周知,做过Web开发的小伙伴可能知道,在浏览器向服务器发一个请求,服务器端会为当前的访问者创建一个session会话,随着浏览器的关闭而会话结束.但是移动客户端咋整呢(IOS/Android啥的) ...

  10. JQ优化性能

    一.注意定义jQuery变量的时候添加var关键字这个不仅仅是jQuery,所有javascript开发过程中,都需要注意,请一定不要定义成如下:$loading = $('#loading'); / ...