zoukankan      html  css  js  c++  java
  • CSharpGL(33)使用uniform块来优化对uniform变量的读写

    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布局也是有所了解。

  • 相关阅读:
    【WinHec启示录】透过Windows 10技术布局,谈微软王者归来
    管中窥豹,物联网之我见
    微软借力.NET开源跨平台支持,布局物联网平台开发
    面向对象开发方式的开源硬件--.NET Gadgeteer
    【物联网智能网关-18】多通道远程安全升级
    vim 多文件编辑【超实用】
    debian下samba配置
    制作根文件系统的经验
    c语言: inline(gcc)
    Cramfs、JFFS2、YAFFS2的全面对比
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/CSharpGL-33-how-to-use-uniform-blocks-in-CSharpGL.html
Copyright © 2011-2022 走看看