文章出處

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祝威+悄悄在此留下版了個權的信息說:
http://blog.csdn.net/comedate/article/details/50602257

3D紋理

比較常見的可能是2D紋理。用GL.TexImage2D(GL.GL_TEXTURE_2D,…);來設定2D紋理的數據。

 1             // generate texture.
 2             {
 3                 //  Lock the image bits (so that we can pass them to OGL).
 4                 BitmapData bitmapData = targetImage.LockBits(new Rectangle(0, 0, targetImage.Width, targetImage.Height),
 5                     ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
 6                 //GL.ActiveTexture(GL.GL_TEXTURE0);
 7                 GL.GenTextures(1, texture);
 8                 GL.BindTexture(GL.GL_TEXTURE_2D, texture[0]);
 9                 GL.TexImage2D(GL.GL_TEXTURE_2D, 0, (int)GL.GL_RGBA,
10                     targetImage.Width, targetImage.Height, 0, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE,
11                     bitmapData.Scan0);
12                 //  Unlock the image.
13                 targetImage.UnlockBits(bitmapData);
14                 /* We require 1 byte alignment when uploading texture data */
15                 //GL.PixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
16                 /* Clamping to edges is important to prevent artifacts when scaling */
17                 GL.TexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, (int)GL.GL_CLAMP_TO_EDGE);
18                 GL.TexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, (int)GL.GL_CLAMP_TO_EDGE);
19                 /* Linear filtering usually looks best for text */
20                 GL.TexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, (int)GL.GL_LINEAR);
21                 GL.TexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, (int)GL.GL_LINEAR);
22             }

 

 

類似地可以用GL.TexImage3D(GL.GL_TEXTURE_3D來設置一個3D紋理。

 1             GL.GenTextures(1, m_nTexId);
 2 
 3             GL.BindTexture(GL.GL_TEXTURE_3D, m_nTexId[0]);
 4             GL.TexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, (int)GL.GL_REPLACE);
 5             GL.TexParameteri(GL.GL_TEXTURE_3D, GL.GL_TEXTURE_WRAP_S, (int)GL.GL_CLAMP_TO_BORDER);
 6             GL.TexParameteri(GL.GL_TEXTURE_3D, GL.GL_TEXTURE_WRAP_T, (int)GL.GL_CLAMP_TO_BORDER);
 7             GL.TexParameteri(GL.GL_TEXTURE_3D, GL.GL_TEXTURE_WRAP_R, (int)GL.GL_CLAMP_TO_BORDER);
 8             GL.TexParameteri(GL.GL_TEXTURE_3D, GL.GL_TEXTURE_MAG_FILTER, (int)GL.GL_LINEAR);
 9             GL.TexParameteri(GL.GL_TEXTURE_3D, GL.GL_TEXTURE_MIN_FILTER, (int)GL.GL_LINEAR);
10 
11             //uint target, int level, int internalformat, int width, int height, int depth, int border, uint format, uint type, IntPtr pixels)
12 
13             GL.TexImage3D(GL.GL_TEXTURE_3D, 0, (int)GL.GL_RGBA, m_uImageWidth, m_uImageHeight, m_uImageCount, 0,
14                 GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pRGBABuffer.Header);
15             GL.BindTexture(GL.GL_TEXTURE_3D, 0);

 

 

1D紋理是若干個點排成一排的一個線段。2D紋理是若干個1D紋理那樣的線段排成的一個矩形。3D紋理是若干個2D紋理排成的一個長方體。如果理解了2D紋理,就可以推論到3D紋理上了。

 

Legacy OpenGL如何調用3D紋理渲染體數據?

OpenGL是不管什么體數據、volume rendering之類的,它只知道你設定了一個3D紋理,然后使用了這個紋理。

 
 1             GL.Clear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
 2 
 3             GL.Enable(GL.GL_ALPHA_TEST);
 4             GL.AlphaFunc(GL.GL_GREATER, alphaThreshold);
 5 
 6             GL.Enable(GL.GL_BLEND);
 7             GL.BlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
 8 
 9             GL.MatrixMode(GL.GL_TEXTURE);
10             GL.LoadIdentity();
11 
12             GL.Enable(GL.GL_TEXTURE_3D);
13             GL.BindTexture(GL.GL_TEXTURE_3D, m_pRawDataProc.GetTexture3D());
14             for (float fIndx = -1; fIndx <= 1; fIndx += 0.01f)
15             {
16                 GL.Begin(GL.GL_QUADS);
17 
18                 GL.TexCoord3f(0.0f, 0.0f, ((float)fIndx + 1.0f) / 2.0f);
19                 GL.Vertex3f(-dOrthoSize, -dOrthoSize, fIndx);
20 
21                 GL.TexCoord3f(1.0f, 0.0f, ((float)fIndx + 1.0f) / 2.0f);
22                 GL.Vertex3f(dOrthoSize, -dOrthoSize, fIndx);
23 
24                 GL.TexCoord3f(1.0f, 1.0f, ((float)fIndx + 1.0f) / 2.0f);
25                 GL.Vertex3f(dOrthoSize, dOrthoSize, fIndx);
26 
27                 GL.TexCoord3f(0.0f, 1.0f, ((float)fIndx + 1.0f) / 2.0f);
28                 GL.Vertex3f(-dOrthoSize, dOrthoSize, fIndx);
29 
30                 GL.End();
31             }
32             GL.BindTexture(GL.GL_TEXTURE_3D, 0);

 

 

Modern OpenGL如何調用3D紋理渲染體數據?

Modern OpenGL渲染一個最簡單的三角形都是很繁瑣的(好處是執行效率高)。這里正好整理一下這個過程,以后我打算做個GUI的向導,讓計算機自動生成那些模式化的代碼,既避免低級錯誤,又加快開發效率,還利于新手學習。

首先寫出shader

為什么要先寫shader?

因為shader雖小,五臟俱全,渲染一個模型所需的各路英雄都在里面露臉了。敲定了shader,之后就可以據此來逐步完成其他零散的部分。

最基本的2個shader

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

 1 #version 150 core
 2 
 3 in vec3 in_Position;
 4 in vec3 in_uv;
 5 out vec3 pass_uv;
 6 
 7 uniform mat4 MVP;
 8 
 9 void main(void) 
10 {
11     gl_Position = MVP * vec4(in_Position, 1.0);
12 
13     pass_uv = in_uv;
14 }

 

 

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

 
 1 #version 150 core
 2 
 3 out vec4 out_Color;
 4 in vec3 pass_uv;
 5 
 6 uniform sampler3D tex;
 7 
 8 void main(void) 
 9 {
10     vec4 color = texture(tex, pass_uv);
11     out_Color = color;
12 }

 

分析shader

shader敲定后,我們要從這里找到這樣一些信息:

頂點屬性

頂點屬性都在vertex shader里。

這個例子中,有in_Position和in_uv兩個屬性。所以后面會有2個VBO。

其他

這個例子里還有一個' uniform sampler3D tex',所以后面會有1個3D紋理。

 

總的來說,shader說的是如何渲染數據,它包含了數據和處理過程(即算法),所以在邏輯上是完整的。我們先寫出shader,就可以以此為指導方針,創建VBO、紋理了。

然后初始化shader

這是比較固定的一個過程。在初始化過程中這個要靠前,因為其他部分是依賴它的。

 
 1         ShaderProgram InitializeShader()
 2         {    
 3             var vertexShaderSource = ManifestResourceLoader.LoadTextFile(@"VolumeRendering.DemoVolumeRendering01.vert");
 4             var fragmentShaderSource = ManifestResourceLoader.LoadTextFile(@"VolumeRendering.DemoVolumeRendering01.frag");
 5 
 6             var shaderProgram = new ShaderProgram();
 7             shaderProgram.Create(vertexShaderSource, fragmentShaderSource);
 8 
 9             shaderProgram.AssertValid();
10 
11             return shaderProgram;
12         }

 

然后初始化各個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*類型來賦值。例如:

 

 1                 //創建位置VBO,并綁定到shader里的in_Position
 2                 VR01PositionBuffer positionBuffer = new VR01PositionBuffer(strin_Position);
 3                 //在CPU內存中申請VBO需要的內存空間(非托管數組)
 4                 positionBuffer.Alloc(zFrameCount);
 5                 //獲取非托管數組的首地址,并轉換為struct QuadPosition*類型
 6                 QuadPosition* array = (QuadPosition*)positionBuffer.FirstElement();
 7                 //設定VBO里的數值
 8                 for (int i = 0; i < zFrameCount; i++)
 9                 {
10                     array[i] = new QuadPosition(
11                         new vec3(-xLength, -yLength, (float)i / (float)zFrameCount - 0.5f),
12                         new vec3(xLength, -yLength, (float)i / (float)zFrameCount - 0.5f),
13                         new vec3(xLength, yLength, (float)i / (float)zFrameCount - 0.5f),
14                         new vec3(-xLength, yLength, (float)i / (float)zFrameCount - 0.5f)
15                         );
16                 }
17                 //上傳VBO數據到GPU內存,并獲取renderer(用于渲染)。此時VR01PositionBuffer positionBuffer已經不再需要。
18                 this.positionBufferRenderer = positionBuffer.GetRenderer();
19                 //釋放CPU內存(剛剛申請的非托管數組)
20                 positionBuffer.Dispose();

 

+BIT祝威+悄悄在此留下版了個權的信息說:

初始化VAO

初始化VAO實際上就是把渲染過程執行一遍。

 

 

 1         public void Create(RenderEventArgs e, Shaders.ShaderProgram shaderProgram)
 2         {
 3             uint[] buffers = new uint[1];
 4             GL.GenVertexArrays(1, buffers);
 5 
 6             this.ID = buffers[0];
 7 
 8             this.Bind();
 9             foreach (var item in this.bufferRenderers)
10             {
11                 item.Render(e, shaderProgram);
12             }
13             this.Unbind();
14         }

 

+BIT祝威+悄悄在此留下版了個權的信息說:

遇到的問題

在legacy OpenGL里完全沒有問題的渲染方式,換成modern OpenGL就出現問題了。

Volume rendering是需要開啟blend的,這樣才能畫出半透明的效果。但是在modern OpenGL下,開啟blend時,各個頂點的渲染順序不同就會改變渲染出的結果。(legacy OpenGL則沒有出現這個問題)

所以下一步需要對VBO里的頂點進行排序,使靠近camera的頂點先被渲染。

 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()