文章出處

CSharpGL(33)使用uniform塊來優化對uniform變量的讀寫

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

Uniform塊

如果shader程序變得比較復雜,那么其中用到的uniform變量數量也會上升。通常會在多個shader程序中用到同一個uniform變量。而uniform buffer object就是一種優化uniform變量訪問,以及在不同的shader程序間共享uniform數據的方法。

寫法

首先了解一下uniform塊的寫法。

1 uniform b { // ‘b’ 對應于外部訪問時的名稱
2     vec4 v1;// 塊中的變量列表
3     bool v2;//
4 }; // 訪問成員時使用v1、v2

或者

1 uniform b { // ‘b’ 對應于外部訪問時的名稱
2     vec4 v1;// 塊中的變量列表
3     bool v2;//
4 } name; // 訪問成員時使用name.v1、name.v2

注意,shader程序中的數據類型有兩種:不透明的和透明的;其中不透明的包括sampler、image和atomic counter。一個uniform塊中只能包含透明類型的變量。

另外,在同一個shader程序里的兩個uniform塊,里面的變量名都不能相同。

下面我們以具體例子的編寫過程來說明在如何使用uniform塊,順便了解一下CSharpGL是如何簡化對uniform塊的使用的。

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

Shader

我認為用Modern OpenGL渲染,首先要寫shader。我們先看一個簡單的vertex shader。

 1 #version 330 core
 2 
 3 uniform mat4 projectionMatrix;
 4 uniform mat4 viewMatrix;
 5 uniform mat4 modelMatrix;
 6 
 7 in vec3 vPos;
 8 in vec3 vColor;
 9 out vec3 fColor;
10 
11 void main(void) {
12  
13     gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(vPos, 1.0);
14     
15     fColor = vColor;
16 }

我們就把這里面的uniform變量換作塊,如下,只是把原來的uniform變量包了起來,并命名為“Uniforms”。

 1 #version 330 core
 2 
 3 uniform Uniforms {
 4     mat4 projectionMatrix;
 5     mat4 viewMatrix;
 6     mat4 modelMatrix;
 7 };
 8 
 9 in vec3 vPos;
10 in vec3 vColor;
11 out vec3 fColor;
12 
13 void main(void) {
14  
15     gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(vPos, 1.0);
16     
17     fColor = vColor;
18 }

而fragment shader則更簡單:

1 #version 330 core
2 
3 in vec3 fColor;
4 
5 out vec4 out_Color;
6 
7 void main(void) {
8     out_Color = vec4(fColor, 1.0f);
9 }
+BIT祝威+悄悄在此留下版了個權的信息說:

準備工作

傳送一個自定義的struct

Uniform塊,實際上對應一個在應用程序客戶端的struct類型。對于示例中的‘Uniforms’塊,我們可以定義如下的結構體。為了方便對照,我們也用‘Uniforms’作為 struct 名,其實你可以用任意你喜歡的名字。

 1     struct Uniforms : IEquatable<Uniforms>
 2     {
 3         public mat4 projection;
 4         public mat4 view;
 5         public mat4 model;
 6 
 7         public Uniforms(mat4 projection, mat4 view, mat4 model)
 8         {
 9             this.projection = projection;
10             this.view = view;
11             this.model = model;
12         }
13 
14         public bool Equals(Uniforms other)
15         {
16             return this.projection == other.projection
17                 && this.view == other.view
18                 && this.model == other.model;
19         }
20     }

今后我們就將數據準備好后保存到一個 Uniforms 對象,最終傳送到shader。

 

uniform塊結構

這里是重點了。傳送float類型的uniform變量,我們有 UniformFloat ;傳送vec3類型的uniform變量,我們有 UniformVec3 。但是uniform塊傳送的是一個個可以任意自定義的不同的結構體(例如上面的struct Uniforms),因此最好用一個泛型的 UniformBlock<T> 。

 1     public class UniformBlock<T> : UniformSingleVariableBase where T : struct, IEquatable<T>
 2     {
 3         protected T value;
 4 
 5         public T Value
 6         {
 7             get { return this.value; }
 8             set
 9             {
10                 if (!value.Equals(this.value))
11                 {
12                     this.value = value;
13                     this.Updated = true;
14                 }
15             }
16         }
17 
18         public UniformBlock(string blockName) : base(blockName) { }
19 
20         public UniformBlock(string blockName, T value) : base(blockName) { this.Value = value; }
21 
22         protected override void DoSetUniform(ShaderProgram program)
23         {
24             // ...
25         }
26     }

 UniformBlock<T> 更新uniform塊的操作比較復雜:它要創建一個uniform buffer object,并與之綁定;以后它只需更新這個buffer里的數據,就可以實現對uniform塊的更新。

 1        protected override void DoSetUniform(ShaderProgram program)
 2         {
 3             if (uniformBufferPtr == null)
 4             {
 5                 uniformBufferPtr = Initialize(program);
 6             }
 7             else
 8             {
 9                 IntPtr pointer = uniformBufferPtr.MapBuffer(MapBufferAccess.WriteOnly, bind: true);
10                 unsafe
11                 {
12                     var array = (byte*)pointer.ToPointer();
13                     byte[] bytes = this.value.ToBytes();
14                     for (int i = 0; i < bytes.Length; i++)
15                     {
16                         array[i] = bytes[i];
17                     }
18                 }
19                 uniformBufferPtr.UnmapBuffer(unbind: true);
20             }
21 
22             this.Updated = false;
23         }
24 
25         /// <summary>
26         /// Initialize and setup uniform block's value.
27         /// </summary>
28         /// <param name="program"></param>
29         /// <returns></returns>
30         private UniformBufferPtr Initialize(ShaderProgram program)
31         {
32             uint uboIndex = glGetUniformBlockIndex(program.ProgramId, this.VarName);
33             var uboSize = new uint[1];
34             glGetActiveUniformBlockiv(program.ProgramId, uboIndex, OpenGL.GL_UNIFORM_BLOCK_DATA_SIZE, uboSize);
35             UniformBufferPtr result = null;
36             using (var buffer = new UniformBuffer<byte>(BufferUsage.StaticDraw, noDataCopyed: false))
37             {
38                 byte[] bytes = this.value.ToBytes();
39                 buffer.Create(bytes.Length);
40                 unsafe
41                 {
42                     var array = (byte*)buffer.Header.ToPointer();
43                     for (int i = 0; i < bytes.Length; i++)
44                     {
45                         array[i] = bytes[i];
46                     }
47                 }
48 
49                 result = buffer.GetBufferPtr() as UniformBufferPtr;
50             }
51 
52             // 將此uniform塊與此uniform buffer object綁定。
53             glBindBufferBase(OpenGL.GL_UNIFORM_BUFFER, uboIndex, result.BufferId);
54 
55             return result;
56         }
57 
58         private UniformBufferPtr uniformBufferPtr = null;
DoSetUniform 

SetUniform()

對于普通的uniform變量,CSharpGL用 Renderer.SetUniform(string varName, T value) where T : struct 即可(無論是什么類型的uniform都可以處理)。對于Uniform塊,也可以用這個方法!

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

UniformBlockRenderer

有了上述準備,我們就可以使用uniform塊了。

創建渲染器

按照CSharpGL的傳統,下面來創建一個UniformBlockRenderer,負責加載shader、模型數據和渲染工作。

 1     class UniformBlockRenderer : Renderer
 2     {
 3         public static UniformBlockRenderer Create()
 4         {
 5             var model = new Teapot();// model
 6             var shaderCodes = new ShaderCode[2];// shaders
 7             shaderCodes[0] = new ShaderCode(File.ReadAllText(@"shaders \UniformBlock.vert"), ShaderType.VertexShader);
 8             shaderCodes[1] = new ShaderCode(File.ReadAllText(@"shaders \UniformBlock.frag"), ShaderType.FragmentShader);
 9             var map = new AttributeNameMap();// mapping relation between model and shaders
10             map.Add("vPos", Teapot.strPosition);
11             map.Add("vColor", Teapot.strColor);
12             var renderer = new UniformBlockRenderer(model, shaderCodes, map);// renderer
13 
14             return renderer;
15         }
16     }

設置uniform塊的值

就像普通的uniform變量一樣,我們也在 Renderer.DoRender() 方法里更新uniform塊。

 1         protected override void DoRender(RenderEventArgs arg)
 2         {
 3             mat4 projection = arg.Camera.GetProjectionMatrix();
 4             mat4 view = arg.Camera.GetViewMatrix();
 5             mat4 model = this.GetModelMatrix();
 6             // 設置uniform塊,只需這一行。
 7             this.SetUniform("Uniforms", new Uniforms(projection, view, model));
 8 
 9             base.DoRender(arg);
10         }

完成的效果如圖,能夠正常渲染,說明我們成功地更新了uniform塊里的數據。

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

下載

CSharpGL已在GitHub開源,歡迎對OpenGL有興趣的同學加入(https://github.com/bitzhuwei/CSharpGL

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

總結

借助C#的struct與byte[]的相互轉換,加上CSharpGL對Modern Rendering的封裝,實際上我們不需要調用 glGetUniformIndices 、 glGetActiveUniformsiv (用于獲取shader中uniform塊里的各個變量的偏移量)這些接口,就可以使用uniform塊了。

當shader中寫了一個uniform塊時,你只需在應用程序客戶端也寫一個對應的 struct ,然后用 Renderer.SetUniform(blockName, structObj); 一行即可實現對uniform塊數據的更新。

PS:測試過程中發現對于vec3結果正常,但是vec4卻有詭異的情況,后來想到應用程序客戶端里的vec4與shader里的vec4的xyzw布局不一樣,可能因此導致原本屬于w的數據最終傳送到了shader里的y上。于是在調整了CSharpGL里的vec4的字段順序后就一切正常了。為了避免以后我或者其他人在忘記\不知情的情況下擅自改動了vec4的字段順序,我用 [FieldOffset(...)] 特性標注了vec4的各個字段。這真是一個很深的bug。多虧我對C#的struct布局也是有所了解。

 


文章列表


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

    IT工程師數位筆記本

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