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

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. 从头开始学JavaScript (十)——垃圾收集

    原文:从头开始学JavaScript (十)--垃圾收集 一.垃圾收集 1.1javascript垃圾收集机制: 自动垃圾收集,执行环境会负责管理代码执行过程中的使用的内存.而在C和C++之类的语言中 ...

  2. java json字符串转List、Map等对象

    List<Map<String, Object>> map = g.fromJson(jsonStr, new TypeToken<List<Map<Stri ...

  3. 有趣iOS开展 - 网络请求

    网络请求 $(function () { $('pre.prettyprint code').each(function () { var lines = $(this).text().split(' ...

  4. elasticsearch的rest搜索--- 查询

    目录: 一.针对这次装B 的解释 二.下载,安装插件elasticsearch-1.7.0   三.索引的mapping 四. 查询 五.对于相关度的大牛的文档 四. 查询 1. 查询的官网的文档   ...

  5. 【百度地图API】让用户选择起点和终点的驾车导航

    原文:[百度地图API]让用户选择起点和终点的驾车导航 摘要: 如果用户搜索“从机场到火车站”,使用驾车导航DrivingRoute会默认显示一条结果.但同一个城市可能有多个机场和火车站,那么,如何用 ...

  6. 高效率的Shell

    1. 批量将Excel转为CSV文件 XLSX2CSV: https://github.com/dilshod/xlsx2csv sudo easy_install xlsx2csv #安装Xlsx2 ...

  7. MVC创建XML,并实现增删改

    原文:MVC创建XML,并实现增删改 如果创建如下的XML: <?xml version="1.0" encoding="utf-8" standalon ...

  8. css3中display和box小结

    display:table用处: 1.创建登高列 2.实现大小不确定元素的垂直居中 3.容器内子项目数目未知,子项目平均分配容器的水平空间 float必须指定其宽度才行,不确定的话就用display: ...

  9. C语言得到当前系统时间

    void getTime(){ //获取当前系统时间 time_t tTime;//距离1900年1月1日的秒数 char str[80]; struct tm* stTim;//时间结构 time( ...

  10. 第4章3节《MonkeyRunner源码剖析》ADB协议及服务: ADB协议概览SYNC.TXT翻译参考(原创)

    天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文“寻求合作伙伴编写<深入理解 MonkeyRunner>书籍“.但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在 ...