CSharpGL(8)使用3D纹理渲染体数据 (Volume Rendering) 初探

2016-08-13

由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了。CSharpGL源码中包含10多个独立的Demo,更适合入门参考。

为了尽可能提升渲染效率,CSharpGL是面向Shader的,因此稍有难度。

+BIT祝威+悄悄在此留下版了个权的信息说:

一图抵千言

您可以在(http://files.cnblogs.com/files/bitzhuwei/VolumeRendering01.rar)下载此demo,或者到(https://github.com/bitzhuwei/CSharpGL)下载完整源码。

此demo来源于

+BIT祝威+悄悄在此留下版了个权的信息说:

3D纹理

比较常见的可能是2D纹理。用GL.TexImage2D(GL.GL_TEXTURE_2D,…);来设定2D纹理的数据。

             // generate texture.
{
// Lock the image bits (so that we can pass them to OGL).
BitmapData bitmapData = targetImage.LockBits(new Rectangle(, , targetImage.Width, targetImage.Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
//GL.ActiveTexture(GL.GL_TEXTURE0);
GL.GenTextures(, texture);
GL.BindTexture(GL.GL_TEXTURE_2D, texture[]);
GL.TexImage2D(GL.GL_TEXTURE_2D, , (int)GL.GL_RGBA,
targetImage.Width, targetImage.Height, , GL.GL_BGRA, GL.GL_UNSIGNED_BYTE,
bitmapData.Scan0);
// Unlock the image.
targetImage.UnlockBits(bitmapData);
/* We require 1 byte alignment when uploading texture data */
//GL.PixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
/* Clamping to edges is important to prevent artifacts when scaling */
GL.TexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, (int)GL.GL_CLAMP_TO_EDGE);
GL.TexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, (int)GL.GL_CLAMP_TO_EDGE);
/* Linear filtering usually looks best for text */
GL.TexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, (int)GL.GL_LINEAR);
GL.TexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, (int)GL.GL_LINEAR);
}
 

类似地可以用GL.TexImage3D(GL.GL_TEXTURE_3D来设置一个3D纹理。

             GL.GenTextures(, m_nTexId);

             GL.BindTexture(GL.GL_TEXTURE_3D, m_nTexId[]);
GL.TexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, (int)GL.GL_REPLACE);
GL.TexParameteri(GL.GL_TEXTURE_3D, GL.GL_TEXTURE_WRAP_S, (int)GL.GL_CLAMP_TO_BORDER);
GL.TexParameteri(GL.GL_TEXTURE_3D, GL.GL_TEXTURE_WRAP_T, (int)GL.GL_CLAMP_TO_BORDER);
GL.TexParameteri(GL.GL_TEXTURE_3D, GL.GL_TEXTURE_WRAP_R, (int)GL.GL_CLAMP_TO_BORDER);
GL.TexParameteri(GL.GL_TEXTURE_3D, GL.GL_TEXTURE_MAG_FILTER, (int)GL.GL_LINEAR);
GL.TexParameteri(GL.GL_TEXTURE_3D, GL.GL_TEXTURE_MIN_FILTER, (int)GL.GL_LINEAR); //uint target, int level, int internalformat, int width, int height, int depth, int border, uint format, uint type, IntPtr pixels) GL.TexImage3D(GL.GL_TEXTURE_3D, , (int)GL.GL_RGBA, m_uImageWidth, m_uImageHeight, m_uImageCount, ,
GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pRGBABuffer.Header);
GL.BindTexture(GL.GL_TEXTURE_3D, );
 

1D纹理是若干个点排成一排的一个线段。2D纹理是若干个1D纹理那样的线段排成的一个矩形。3D纹理是若干个2D纹理排成的一个长方体。如果理解了2D纹理,就可以推论到3D纹理上了。

Legacy OpenGL如何调用3D纹理渲染体数据?

OpenGL是不管什么体数据、volume rendering之类的,它只知道你设定了一个3D纹理,然后使用了这个纹理。

 
             GL.Clear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

             GL.Enable(GL.GL_ALPHA_TEST);
GL.AlphaFunc(GL.GL_GREATER, alphaThreshold); GL.Enable(GL.GL_BLEND);
GL.BlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); GL.MatrixMode(GL.GL_TEXTURE);
GL.LoadIdentity(); GL.Enable(GL.GL_TEXTURE_3D);
GL.BindTexture(GL.GL_TEXTURE_3D, m_pRawDataProc.GetTexture3D());
for (float fIndx = -; fIndx <= ; fIndx += 0.01f)
{
GL.Begin(GL.GL_QUADS); GL.TexCoord3f(0.0f, 0.0f, ((float)fIndx + 1.0f) / 2.0f);
GL.Vertex3f(-dOrthoSize, -dOrthoSize, fIndx); GL.TexCoord3f(1.0f, 0.0f, ((float)fIndx + 1.0f) / 2.0f);
GL.Vertex3f(dOrthoSize, -dOrthoSize, fIndx); GL.TexCoord3f(1.0f, 1.0f, ((float)fIndx + 1.0f) / 2.0f);
GL.Vertex3f(dOrthoSize, dOrthoSize, fIndx); GL.TexCoord3f(0.0f, 1.0f, ((float)fIndx + 1.0f) / 2.0f);
GL.Vertex3f(-dOrthoSize, dOrthoSize, fIndx); GL.End();
}
GL.BindTexture(GL.GL_TEXTURE_3D, );

Modern OpenGL如何调用3D纹理渲染体数据?

Modern OpenGL渲染一个最简单的三角形都是很繁琐的(好处是执行效率高)。这里正好整理一下这个过程,以后我打算做个GUI的向导,让计算机自动生成那些模式化的代码,既避免低级错误,又加快开发效率,还利于新手学习。

首先写出shader

为什么要先写shader?

因为shader虽小,五脏俱全,渲染一个模型所需的各路英雄都在里面露脸了。敲定了shader,之后就可以据此来逐步完成其他零散的部分。

最基本的2个shader

下面是用3D纹理渲染的vertex shader:

 #version  core

 in vec3 in_Position;
in vec3 in_uv;
out vec3 pass_uv; uniform mat4 MVP; void main(void)
{
gl_Position = MVP * vec4(in_Position, 1.0); pass_uv = in_uv;
}
 

下面是用3D纹理渲染的fragment shader:

 
 #version  core

 out vec4 out_Color;
in vec3 pass_uv; uniform sampler3D tex; void main(void)
{
vec4 color = texture(tex, pass_uv);
out_Color = color;
}

分析shader

shader敲定后,我们要从这里找到这样一些信息:

顶点属性

顶点属性都在vertex shader里。

这个例子中,有in_Position和in_uv两个属性。所以后面会有2个VBO。

其他

这个例子里还有一个' uniform sampler3D tex',所以后面会有1个3D纹理。

总的来说,shader说的是如何渲染数据,它包含了数据和处理过程(即算法),所以在逻辑上是完整的。我们先写出shader,就可以以此为指导方针,创建VBO、纹理了。

然后初始化shader

这是比较固定的一个过程。在初始化过程中这个要靠前,因为其他部分是依赖它的。

 
         ShaderProgram InitializeShader()
{
var vertexShaderSource = ManifestResourceLoader.LoadTextFile(@"VolumeRendering.DemoVolumeRendering01.vert");
var fragmentShaderSource = ManifestResourceLoader.LoadTextFile(@"VolumeRendering.DemoVolumeRendering01.frag"); var shaderProgram = new ShaderProgram();
shaderProgram.Create(vertexShaderSource, fragmentShaderSource); shaderProgram.AssertValid(); return shaderProgram;
}

然后初始化各个VBO

我们基于下面这几条规律,设计初始化VBO的过程。

VBO所需数据在CPU内存中指定,在初始化VBO时上传到GPU内存,此后CPU内存中的数据不再需要。

OpenGL提供的设置VBO的指令glBufferData(GLenum target,GLsizeiptr size,const GLvoid * data,GLenum usage);和void glVertexAttribPointer( GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,const GLvoid * pointer);没有任何业务逻辑上的含义,很容易出错,且难以调试。

我习惯的使用VBO的方式是这样的:

一个VBO只存放模型的一个顶点属性(例如只存放位置或只存放颜色)。这样能尽可能缩短一个VBO的长度,利于处理大量数据。

一个VBO里只有一种基本的图形对象(例如只有三角形或只有六面体)。这个图形对象用一个struct描述。在CPU内存中设置模型数据时,不用void*而是用具体的struct*类型来赋值。例如:

                 //创建位置VBO,并绑定到shader里的in_Position
VR01PositionBuffer positionBuffer = new VR01PositionBuffer(strin_Position);
//在CPU内存中申请VBO需要的内存空间(非托管数组)
positionBuffer.Alloc(zFrameCount);
//获取非托管数组的首地址,并转换为struct QuadPosition*类型
QuadPosition* array = (QuadPosition*)positionBuffer.FirstElement();
//设定VBO里的数值
for (int i = ; i < zFrameCount; i++)
{
array[i] = new QuadPosition(
new vec3(-xLength, -yLength, (float)i / (float)zFrameCount - 0.5f),
new vec3(xLength, -yLength, (float)i / (float)zFrameCount - 0.5f),
new vec3(xLength, yLength, (float)i / (float)zFrameCount - 0.5f),
new vec3(-xLength, yLength, (float)i / (float)zFrameCount - 0.5f)
);
}
//上传VBO数据到GPU内存,并获取renderer(用于渲染)。此时VR01PositionBuffer positionBuffer已经不再需要。
this.positionBufferRenderer = positionBuffer.GetRenderer();
//释放CPU内存(刚刚申请的非托管数组)
positionBuffer.Dispose();
+BIT祝威+悄悄在此留下版了个权的信息说:

初始化VAO

初始化VAO实际上就是把渲染过程执行一遍。

 
         public void Create(RenderEventArgs e, Shaders.ShaderProgram shaderProgram)
{
uint[] buffers = new uint[];
GL.GenVertexArrays(, buffers); this.ID = buffers[]; this.Bind();
foreach (var item in this.bufferRenderers)
{
item.Render(e, shaderProgram);
}
this.Unbind();
}
+BIT祝威+悄悄在此留下版了个权的信息说:

遇到的问题

在legacy OpenGL里完全没有问题的渲染方式,换成modern OpenGL就出现问题了。

Volume rendering是需要开启blend的,这样才能画出半透明的效果。但是在modern OpenGL下,开启blend时,各个顶点的渲染顺序不同就会改变渲染出的结果。(legacy OpenGL则没有出现这个问题)

所以下一步需要对VBO里的顶点进行排序,使远离camera的顶点先被渲染。

CSharpGL(8)使用3D纹理渲染体数据 (Volume Rendering) 初探的更多相关文章

  1. 利用VTK和PyQt5对医学体数据进行渲染并展示

    简介 在一些医学相关的简单的项目(也许是学生的作业?毕业设计?)中,有时候可能需要集成一些可视化的功能,本文简单介绍一下,如何利用PyQt5和VTK来渲染体数据(三维数据),并集成进PyQt的UI框架 ...

  2. CSharpGL(25)一个用raycast实现体渲染VolumeRender的例子

    CSharpGL(25)一个用raycast实现体渲染VolumeRender的例子 本文涉及的VolumeRendering相关的C#代码是从(https://github.com/toolchai ...

  3. DXVA2解码数据用texture纹理渲染

    FFmpeg DXVA2解码得到的数据使用surface来承载的,surface限制很多,如果能用纹理来渲染的话,那我们就可以充分开发D3D,比如可以用坐标变换来实现电子放大的功能,还可以用坐标变换来 ...

  4. 什么是体数据可视化(Volume data visualization)?及体绘制的各种算法和技术的特点?

    该文对体数据进行综述,并介绍了体数据的各种算法和技术的特点. 前言 由于3D数据采集领域的高速发展,以及在具有交互式帧率的现代化工作站上执行高级可视化的可能性,体数据的重要性将继续迅速增长. 数据集可 ...

  5. 新书《Cocos2dx 3.x 3D图形学渲染技术讲解》问世

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解 ...

  6. CSharpGL(14)用geometry shader渲染模型的法线(normal)

    +BIT祝威+悄悄在此留下版了个权的信息说: CSharpGL(14)用geometry shader渲染模型的法线(normal) +BIT祝威+悄悄在此留下版了个权的信息说: 2016-08-13 ...

  7. CSharpGL(10)两个纹理叠加

    CSharpGL(10)两个纹理叠加 本文很简单,只说明如何用shader实现叠加两个纹理的效果. 另外,最近CSharpGL对渲染框架做了修改,清理一些别扭的内容(DoRender()前后的事件都去 ...

  8. SDL 开发实战(五): SDL 纹理渲染

    本文我们讲一下如何使用SDL_Texture将视频纹理渲染出来. 1. SDL 视频渲染相关对象 SDL 视频渲染主要涉及到四个对象:SDL_Window.SDL_Render.SDL_Texture ...

  9. CSharpGL(19)用glReadPixels把渲染的内容保存为PNG图片(C#)

    CSharpGL(19)用glReadPixels把渲染的内容保存为PNG图片(C#) 效果图 本文解决了将OpenGL渲染出来的内容保存到PNG图片的方法. 下载 CSharpGL已在GitHub开 ...

随机推荐

  1. 前端框架 EasyUI (1)熟悉一下EasyUI

    jQuery EasyUI 官方网站 http://www.jeasyui.com/ .去年新开了个中文网 http://www.jeasyui.net/,不知道是不是官方的,不过看着挺像样.但是,广 ...

  2. Swift与C#的基础语法比较

    背景: 这两天不小心看了一下Swift的基础语法,感觉既然看了,还是写一下笔记,留个痕迹~ 总体而言,感觉Swift是一种前后端多种语言混合的产物~~~ 做为一名.NET阵营人士,少少多多总喜欢通过对 ...

  3. Appium移动自动化框架

    引言:Appium 是一个移动端自动化测试开源工具,可以针对不同的平台用一套API来编写测试用例.本文对Appium自动化测试框架的功能进行了概括. 本文选自<软件自动化测试开发>. Ap ...

  4. 01.LoT.UI 前后台通用框架分解系列之——小图片背景全屏显示(可自动切换背景)

    LOT.UI分解系列汇总:http://www.cnblogs.com/dunitian/p/4822808.html#lotui LoT.UI开源地址如下:https://github.com/du ...

  5. Redis数据库

    Redis是k-v型数据库的典范,设计思想及数据结构实现都值得学习. 1.数据类型 value支持五种数据类型:1.字符串(strings)2.字符串列表(lists)3.字符串集合(sets)4.有 ...

  6. C++随笔:.NET CoreCLR之corleCLR核心探索之coreconsole(1)

    一看这个标题,是不去取名有点绕呢?或者是,还有些问题?报告LZ...你的标题取得有问题,是个病句!↖(^ω^)↗!!!先不要急,其实我今天带给大家的就是CoreCLR中的coreclr.其中它是在名字 ...

  7. C# 用SoapUI调试WCF服务接口(WCF中包含用户名密码的验证)

    问题描述: 一般调试wcf程序可以直接建一个单元测试,直接调接口. 但是,这次,我还要测试在接口内的代码中看接收到的用户名密码是否正确,所以,单一的直接调用接口方法行不通, 然后就想办法通过soapU ...

  8. 3种web会话管理的方式

    http是无状态的,一次请求结束,连接断开,下次服务器再收到请求,它就不知道这个请求是哪个用户发过来的.当然它知道是哪个客户端地址发过来的,但是对于我们的应用来说,我们是靠用户来管理,而不是靠客户端. ...

  9. 屌丝giser成长记-大学篇

    作为一名屌丝giser的我,刚接触gis专业是2007年的大一,好悲催,当时gis这个专业是被调剂的,我压根都不知道gis为何物,那时候gis冷门的一逼,报名这个专业的寥寥无几.记得那时候得知被调剂到 ...

  10. 电信计费业务:预后融合OCS到底应该实扣还是虚扣?

    引入OCS的初衷之一是为了让计费系统能够参与到用户的通讯控制中来,也就是所谓的实时信控.用户在没有余额时,通讯就会被停止,不会造成"天价欠费 ",一方面保障用户的利益,一方面也保障 ...