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

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. MVC — 第 6 天

    7 天玩转 ASP.NET MVC — 第 6 天   目录 第 1 天 第 2 天 第 3 天 第 4 天 第 5 天 第 6 天 第 7 天 0. 前言 欢迎来到第六天的 MVC 系列学习中.希望 ...

  2. 正则表达式 \b (转)

    引用网上一段话: \b 是正则表达式规定的一个特殊代码(好吧,某些人叫它元字符,metacharacter),代表着单词的开头或结尾,也就是单词的分界处.虽然通常英文的单词是由空格,标点符号或者换行来 ...

  3. TortoiseGit push失败原因小结(转)

    花了我一个晚上,终于弄明白为什么总是 push 失败的原因了!竟然是因为我用的是注册的用户名而不是邮箱名……囧死. 另外搞清楚了一个问题,就是 Git 和远程仓库交互有两种方式,即 https 方式和 ...

  4. strncpy和strcpy

    实体化代码运行图: 实现代码: #include <stdio.h> #include <string.h> #include <math.h> #include ...

  5. ASP.NET MVC+EF框架+EasyUI实现权限管理系列(9)-TT模板的学习

    原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(9)-TT模板的学习 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇)   (1):框架搭建    (2): ...

  6. javascript系列之变量对象

    原文:javascript系列之变量对象 引言 一般在编程的时候,我们会定义函数和变量来成功的构造我们的系统.但是解析器该如何找到这些数据(函数,变量)呢?当我们引用需要的对象时,又发生了什么了? 很 ...

  7. android 登录推断是否邮箱或手机号

    /** * 验证邮件是合法的输入 *  * @param strEmail * @return */ public static boolean isEmail(String strEmail) { ...

  8. javascript 中的location.pathname

    1 location.pathname; 这在之前我没怎么注意过,所以研究研究.location.pathname:返回URL的域名(域名IP)后的部分.例如 http://www.joymood.c ...

  9. uva 12003 Array Transformer (大规模阵列)

    白皮书393页面. 乱搞了原始数组中.其实用另一种阵列块记录. 你不能改变原始数组. 请注意,与原来的阵列和阵列块的良好关系,稍微细心处理边境.这是不难. #include <cstdio> ...

  10. Java_POI之MS-Excel2003(扩展名.xls)升级至MS-Excel2007及以上版本(扩展名.xlsx)技术过程概要

    Java_POI之MS-Excel2003(扩展名.xls)升级至MS-Excel2007及以上版本(扩展名.xlsx)技术过程概要 作者:Eric.Zhang(花名:穿越者7号) 日期:2015年1 ...