• CSharpGL(2)设计和使用场景元素及常用接口


    CSharpGL(2)设计和使用场景元素及常用接口

    2016-08-13

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

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

    主要内容

    描述在OpenGL中绘制场景的思路。

    设计场景元素的抽象基类SceneELementBase。

    以PyramidElement为例演示SceneELementBase的用法。

    下载

    您可以在(https://github.com/bitzhuwei/CSharpGL)找到最新的源码。欢迎感兴趣的同学fork之。

     

    在OpenGL中绘制场景的思路

    规范用词

    首先我们明确2个关键的用词。

    场景

    场景是指用OpenGL渲染出来的所有东西,包括每个模型和背景。

    元素

    元素是指场景中的一个模型。

    所有元素的基类

    上一篇里我们已经体验了用legacy OpenGL和modern OpenGL渲染3D场景的过程。这一过程对任意简单的或复杂的场景都适用。我们将任意场景的渲染过程抽象出共性来,就是"初始化"和"渲染"这两点。也就是说,一个在场景中进行渲染的元素,必须具有"初始化"和"渲染"这两个功能。这就找到了场景元素的基类。

     
     1     /// <summary>
     2     /// 用OPENGL初始化和渲染一个元素。
     3     /// </summary>
     4     public abstract class SceneElementBase
     5     {
     6         protected bool initialized = false;
     7 
     8         /// <summary>
     9         /// 初始化此Element
    10         /// </summary>
    11         public void Initialize()
    12         {
    13             if (!initialized)
    14             {
    15                 DoInitialize();
    16 
    17                 initialized = true;
    18             }
    19         }
    20 
    21         /// <summary>
    22         /// 初始化此Element
    23         /// </summary>
    24         protected abstract void DoInitialize();
    25 
    26         /// <summary>
    27         /// 渲染
    28         /// </summary>
    29         /// <param name="renderMode"></param>
    30         public abstract void Render(RenderEventArgs e);
    31     }

    这个抽象基类告诉我们,任何一个场景中的元素,必须实现"初始化"和"渲染"这两个方法,即必须知道如何初始化自己的数据,如何渲染自己。

    常用接口

    有了上面的SceneElementBase,整个渲染的蓝图就定型了。下一个问题是,modern OpenGL需要加载很多东西,不同的元素要实现的功能的多少、种类也各不相同,这如何实现?方法是:为每项功能设计相应的接口,让具有此功能的元素继承此接口。下面是几个常用的功能接口的例子。

    IMVP

    定义

    这是最常用的接口。对于用modern OpenGL方式渲染的元素,这是一个必选项。(当然,不实现此接口也可以,但是本质上仍然是实现了此接口的功能)

     
     1     /// <summary>
     2     /// 通过此接口设置元素的MVP矩阵
     3     /// </summary>
     4     public interface IMVP
     5     {
     6         /// <summary>
     7         /// 更新此元素的MVP值。
     8         /// </summary>
     9         /// <param name="mvp">三个矩阵的乘积(Projection * View * Model)</param>
    10         void SetShaderProgram(mat4 mvp);
    11 
    12         /// <summary>
    13         /// 解绑当前shader program。
    14         /// </summary>
    15         void ResetShaderProgram();
    16 
    17         /// <summary>
    18         /// 
    19         /// </summary>
    20         /// <returns></returns>
    21         Shaders.ShaderProgram GetShaderProgram();
    22 
    23     }

    MVP是投影矩阵(Projection) * 视图矩阵(View) * 模型矩阵(Model)的简写。由于在很多GLSL的shader里都有"uniform mat4 mvp;"这样的命名方式,所以我将此接口命名为IMVP。

    IMVP的用处,是在渲染前设置mvp矩阵。任何一个元素都应该有位置(Position)这个属性(否则就没有可画的东西了),而输入的位置VBO里存储的是模型自身的位置,要想变换到窗口合适的位置上,就必然用到mvp矩阵。所以说这个IMVP接口是任何一个用modern OpenGL方式渲染的元素必须实现的。

    如何使用

    要调用此接口,就必须与SceneElementBase.Render()配合,在SceneElementBase.Render()渲染之前调用IMVP.SetShaderProgram()。

     1 public override void Render(RenderEventArgs e)
     2 {
     3 // 绑定shader,设置MVP
     4     mat4 projectionMatrix = e.Camera.GetProjectionMat4();
     5     mat4 viewMatrix = e.Camera.GetViewMat4();
     6     mat4 modelMatrix = mat4.identity();
     7    mat4 mvp = projectionMatrix * viewMatrix * modelMatrix;
     8     IMVP element = this as IMVP;
     9 element.SetShaderProgram(mvp);
    10 
    11 // 此时进行渲染
    12 // ...
    13 
    14 // 解绑shader
    15 element.ResetShaderProgram();
    16 }

    你注意到,此时RenderEventArgs参数里需要有一个Camera字段,Camera需要实现获取投影矩阵和视图矩阵的方法GetProjectionMat4()和GetViewMat4()。关于Camera的实现我们以后再详述。

    改进

    再思考一下这个Render()方法,它有2个问题:

    A:设置mvp矩阵的代码写死到元素的Render方法里,灵活性不够。如果以后我希望用别的方式指定mvp值,就必须修改Camera。在此处对Camera的改动就牵涉过多了。

    B:如果场景中的元素很多,那么每个元素内部的Render方法都要计算一遍mvp值,这显然是重复计算。更好的做法是:提前计算出mvp值,然后依次喂给每个元素的SetShaderProgram(mvp);方法。

    为解决这2个问题,我们对SceneElementBase进行改造,使得元素外部代码可以动态改变指定mvp的方式。

     1     /// <summary>
     2     /// 用OPENGL初始化和渲染一个元素。
     3     /// </summary>
     4     public abstract class SceneElementBase : IRenderable
     5     {
     6         protected bool initialized = false;
     7 
     8         /// <summary>
     9         /// 初始化此Element
    10         /// </summary>
    11         public void Initialize()
    12         {
    13             if (!initialized)
    14             {
    15                 DoInitialize();
    16 
    17                 initialized = true;
    18             }
    19         }
    20 
    21         /// <summary>
    22         /// 初始化此Element
    23         /// </summary>
    24         protected abstract void DoInitialize();
    25 
    26         /// <summary>
    27         /// 渲染
    28         /// </summary>
    29         /// <param name="renderMode"></param>
    30         public void Render(RenderEventArgs e)
    31         {
    32             if (!initialized) { Initialize(); }
    33 
    34             EventHandler<RenderEventArgs> beforeRendering = this.BeforeRendering;
    35             if (beforeRendering != null)
    36             {
    37                 beforeRendering(this, e);
    38             }
    39 
    40             DoRender(e);
    41 
    42             EventHandler<RenderEventArgs> afterRendering = this.AfterRendering;
    43             if (afterRendering != null)
    44             {
    45                 afterRendering(this, e);
    46             }
    47         }
    48 
    49         /// <summary>
    50         /// 执行渲染操作
    51         /// </summary>
    52         /// <param name="renderMode"></param>
    53         protected abstract void DoRender(RenderEventArgs e);
    54 
    55         /// <summary>
    56         /// 在渲染前进行某些准备(更新camera矩阵信息等)
    57         /// </summary>
    58         public event EventHandler<RenderEventArgs> BeforeRendering;
    59 
    60         /// <summary>
    61         /// 在渲染后进行某些善后(恢复OpenGL状态等)
    62         /// </summary>
    63         public event EventHandler<RenderEventArgs> AfterRendering;
    64 
    65     }
     

    改进后的使用

    现在,我们可以在元素外部通过为BeforeRendering和AfterRendering添加自定义事件函数的方式自由指定mvp。

     1 PyramidElement[] elements = new PyramidElement[10]; 
     2 mat4 mvp; //每次渲染场景前被更新
     3 
     4         public InitElements()
     5         {
     6             for (int i = 0; i < 10; i++)
     7             {
     8                 var element = new PyramidElement();
     9                 element.Initialize();
    10                 element.BeforeRendering += element_BeforeRendering;
    11                 element.AfterRendering += element_AfterRendering;
    12 
    13                 this.elements[i] = element;
    14             }
    15         } 
    16 
    17         void element_AfterRendering(object sender, Objects.RenderEventArgs e)
    18         {
    19             IMVP element = sender as IMVP;
    20             element.ResetShaderProgram();
    21         }
    22 
    23         void element_BeforeRendering(object sender, Objects.RenderEventArgs e)
    24         {
    25             IMVP element = sender as IMVP;
    26             element.SetShaderProgram(mvp);
    27         }
    28         void glCanvas1_OpenGLDraw(object sender, PaintEventArgs e)
    29         {
    30             mat4 modelMatrix = glm.identity();
    31             mat4 viewMatrix = this.camera.GetViewMat4();
    32             mat4 projectionMatrix = this.camera.GetProjectionMat4();
    33             mvp = projectionMatrix * viewMatrix * modelMatrix;
    34 
    35             GL.Clear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
    36 
    37             var arg = new RenderEventArgs(RenderModes.Render, this.camera);
    38             for (int i = 0; i < 10; i++)
    39             {
    40                 this.elements[i].Render(arg);
    41             }
    42         }
     

    这样一来,上面2个问题都解决了。

    用Helper实现最常用的IMVP

    说了这么多,还没有说明如何实现IMVP。

     

     

     1 class PyramidElement : SceneElementBase, IMVP 
     2 {
     3     // other stuff
     4 
     5 ShaderProgram shaderProgram;
     6 
     7         void IMVP.SetShaderProgram(mat4 mvp)
     8         {
     9             IMVPHelper.DoUpdateMVP(this, mvp);
    10         }
    11 
    12         void IMVP.ResetShaderProgram()
    13         {
    14             IMVPHelper.DoUnbindShaderProgram(this);
    15         }
    16 
    17         ShaderProgram IMVP.GetShaderProgram()
    18         {
    19             return this.shaderProgram;
    20         }
    21     }
    22 
    23     public static class IMVPHelper
    24     {
    25         /// <summary>
    26         /// public static string strMVP = "MVP";
    27         /// <para>使用此<see cref="IMVPHelper"/><see cref="SceneElement"/>所使用的Vertex Shader必须含有<code>uniform mat4 MVP;</code>并使其作为变换矩阵。</para>
    28         /// </summary>
    29         public static string strMVP = "MVP";
    30 
    31         /// <summary>
    32         /// 请确保此元素的GLSL中含有uniform mat4 MVP;并作为位置转换矩阵。
    33         /// </summary>
    34         /// <param name="element"></param>
    35         /// <param name="mvp"></param>
    36         public static void DoUpdateMVP(this IMVP element, mat4 mvp)
    37         {
    38             ShaderProgram shaderProgram = element.GetShaderProgram();
    39             shaderProgram.Bind();
    40             shaderProgram.SetUniformMatrix4(strMVP, mvp.to_array());
    41         }
    42 
    43         /// <summary>
    44         /// 请确保此元素的GLSL中含有uniform mat4 MVP;并作为位置转换矩阵。
    45         /// </summary>
    46         /// <param name="element"></param>
    47         public static void DoUnbindShaderProgram(this IMVP element)
    48         {
    49             ShaderProgram shaderProgram = element.GetShaderProgram();
    50             shaderProgram.Unbind();
    51         }
    52     }

    IColorCodedPicking

    这是为实现在VBO中拾取一个图元而设计的接口。继承此接口的SceneElementBase的子类能够告诉你你用鼠标拾取了哪个图元。

    具体使用方法请参考(http://www.cnblogs.com/bitzhuwei/p/modern-opengl-picking-primitive-in-VBO.html)。不再重述。

    IUILayout

    定义

    实现IUILayout接口的元素能够在窗口固定位置显示,类似Winform里的控件那样,设置其长度、宽度,指定其Anchor(绑定到上下左右)。

     
     1     /// <summary>
     2     /// 实现在OpenGL窗口中的UI布局
     3     /// </summary>
     4     public interface IUILayout
     5     {
     6         IUILayoutParam Param { get; set; }
     7 
     8 }
     9     public struct IUILayoutParam
    10     {
    11         /// <summary>
    12         /// the edges of the <see cref="GLCanvas"/> to which a UI’s rect is bound and determines how it is resized with its parent.
    13         /// <para>something like AnchorStyles.Left | AnchorStyles.Bottom.</para>
    14         /// </summary>
    15         public System.Windows.Forms.AnchorStyles Anchor;
    16 
    17         /// <summary>
    18         /// Gets or sets the space between viewport and SimpleRect.
    19         /// </summary>
    20         public System.Windows.Forms.Padding Margin;
    21 
    22         /// <summary>
    23         /// Stores width when <see cref="OpenGLUIRect.Anchor"/>.Left &amp; <see cref="OpenGLUIRect.Anchor"/>.Right is <see cref="OpenGLUIRect.Anchor"/>.None.
    24         /// <para> and height when <see cref="OpenGLUIRect.Anchor"/>.Top &amp; <see cref="OpenGLUIRect.Anchor"/>.Bottom is <see cref="OpenGLUIRect.Anchor"/>.None.</para>
    25         /// </summary>
    26         public System.Drawing.Size Size;
    27 
    28         public int zNear;
    29 
    30         public int zFar;
    31 
    32         public IUILayoutParam(AnchorStyles anchorStyle, Padding padding, System.Drawing.Size size,
    33             int zNear = -1000, int zFar = 1000)
    34         {
    35             this.Anchor = anchorStyle;
    36             this.Margin = padding;
    37             this.Size = size;
    38             this.zNear = zNear;
    39             this.zFar = zFar;
    40         }
    41 }

    当然, 仅仅一个接口是不能"实现"这个功能的。还需要一些辅助类型。

    如何实现UI布局

    最核心的是下面这个能够让元素像UI一样布局的Helper类型。

    这个Helper类型会根据IUILayout接口提供的此UI元素的布局参数,计算出它应该使用的透视矩阵、投影矩阵和模型矩阵。所以,本质上,UI元素也是场景中的一种元素,只不过由于其mvp值比较特殊,使其看起来像Winform里的控件而已。

      1     public static class IUILayoutHelper
      2     {
      3         /// <summary>
      4         /// 获取此UI元素的投影矩阵、视图矩阵和模型矩阵
      5         /// </summary>
      6         /// <param name="uiElement"></param>
      7         /// <param name="projectionMatrix"></param>
      8         /// <param name="viewMatrix"></param>
      9         /// <param name="modelMatrix"></param>
     10         /// <param name="camera">如果为null,会以glm.lookAt(new vec3(0, 0, 1), new vec3(0, 0, 0), new vec3(0, 1, 0))计算默认值。</param>
     11         /// <param name="maxDepth">UI元素的外接球半径的倍数。</param>
     12         public static void GetMatrix(this IUILayout uiElement,
     13             out mat4 projectionMatrix, out mat4 viewMatrix, out mat4 modelMatrix,
     14             IViewCamera camera = null, float maxDepth = 2.0f)
     15         {
     16             IUILayoutArgs args = uiElement.GetArgs();
     17             float max = (float)Math.Max(args.UIWidth, args.UIHeight);
     18 
     19             {
     20                 projectionMatrix = glm.ortho((float)args.left / 2, (float)args.right / 2, (float)args.bottom / 2, (float)args.top / 2,
     21                     uiElement.Param.zNear, uiElement.Param.zFar);
     22                 projectionMatrix = glm.translate(projectionMatrix, new vec3(0, 0, uiElement.Param.zFar - max / 2 * maxDepth));
     23             }
     24             {
     25                 // UI元素不在三维场景中,所以其Camera可以是null。
     26                 if (camera == null)
     27                 {
     28                     //viewMatrix = glm.lookAt(new vec3(0, 0, 1), new vec3(0, 0, 0), new vec3(0, 1, 0));
     29                     viewMatrix = glm.lookAt(
     30                         ScientificCamera.defaultPosition, 
     31                         ScientificCamera.defaultTarget, 
     32                         ScientificCamera.defaultUpVector);
     33                 }
     34                 else
     35                 {
     36                     vec3 position = camera.Position - camera.Target;
     37                     position.Normalize();
     38                     viewMatrix = glm.lookAt(position, new vec3(0, 0, 0), camera.UpVector);
     39                 }
     40             }
     41             {
     42                 modelMatrix = glm.scale(mat4.identity(), new vec3(args.UIWidth / 2, args.UIHeight / 2, max / 2));
     43             }
     44         }
     45 
     46         const AnchorStyles leftRightAnchor = (AnchorStyles.Left | AnchorStyles.Right);
     47         const AnchorStyles topBottomAnchor = (AnchorStyles.Top | AnchorStyles.Bottom);
     48         
     49         /// <summary>
     50         /// 获取为UI元素布局所需的参数对象。
     51         /// </summary>
     52         /// <param name="uiElement"></param>
     53         /// <returns></returns>
     54         public static IUILayoutArgs GetArgs(this IUILayout uiElement)
     55         {
     56             var args = new IUILayoutArgs();
     57 
     58             CalculateViewport(args);
     59 
     60             CalculateCoords(uiElement, args.viewportWidth, args.viewportHeight, args);
     61 
     62             return args;
     63         }
     64 
     65         /// <summary>
     66         /// 计算opengl画布的大小。
     67         /// </summary>
     68         /// <param name="args"></param>
     69         static void CalculateViewport(IUILayoutArgs args)
     70         {
     71             int[] viewport = new int[4];
     72             GL.GetInteger(GetTarget.Viewport, viewport);
     73             args.viewportWidth = viewport[2];
     74             args.viewportHeight = viewport[3];
     75         }
     76 
     77         /// <summary>
     78         /// 根据UI元素的布局设定,计算其应有的宽高及其在ortho()中应有的参数。
     79         /// </summary>
     80         /// <param name="uiElement"></param>
     81         /// <param name="viewportWidth"></param>
     82         /// <param name="viewportHeight"></param>
     83         /// <param name="args"></param>
     84         static void CalculateCoords(IUILayout uiElement, int viewportWidth, int viewportHeight, IUILayoutArgs args)
     85         {
     86             IUILayoutParam param = uiElement.Param;
     87 
     88             if ((param.Anchor & leftRightAnchor) == leftRightAnchor)
     89             {
     90                 args.UIWidth = viewportWidth - param.Margin.Left - param.Margin.Right;
     91                 if (args.UIWidth < 0) { args.UIWidth = 0; }
     92             }
     93             else
     94             {
     95                 args.UIWidth = param.Size.Width;
     96             }
     97 
     98             if ((param.Anchor & topBottomAnchor) == topBottomAnchor)
     99             {
    100                 args.UIHeight = viewportHeight - param.Margin.Top - param.Margin.Bottom;
    101                 if (args.UIHeight < 0) { args.UIHeight = 0; }
    102             }
    103             else
    104             {
    105                 args.UIHeight = param.Size.Height;
    106             }
    107 
    108             if ((param.Anchor & leftRightAnchor) == AnchorStyles.None)
    109             {
    110                 args.left = -(args.UIWidth / 2
    111                     + (viewportWidth - args.UIWidth)
    112                         * ((double)param.Margin.Left / (double)(param.Margin.Left + param.Margin.Right)));
    113             }
    114             else if ((param.Anchor & leftRightAnchor) == AnchorStyles.Left)
    115             {
    116                 args.left = -(args.UIWidth / 2 + param.Margin.Left);
    117             }
    118             else if ((param.Anchor & leftRightAnchor) == AnchorStyles.Right)
    119             {
    120                 args.left = -(viewportWidth - args.UIWidth / 2 - param.Margin.Right);
    121             }
    122             else // if ((Anchor & leftRightAnchor) == leftRightAnchor)
    123             {
    124                 args.left = -(args.UIWidth / 2 + param.Margin.Left);
    125             }
    126 
    127             if ((param.Anchor & topBottomAnchor) == AnchorStyles.None)
    128             {
    129                 args.bottom = -viewportHeight / 2;
    130                 args.bottom = -(args.UIHeight / 2
    131                     + (viewportHeight - args.UIHeight)
    132                         * ((double)param.Margin.Bottom / (double)(param.Margin.Bottom + param.Margin.Top)));
    133             }
    134             else if ((param.Anchor & topBottomAnchor) == AnchorStyles.Bottom)
    135             {
    136                 args.bottom = -(args.UIHeight / 2 + param.Margin.Bottom);
    137             }
    138             else if ((param.Anchor & topBottomAnchor) == AnchorStyles.Top)
    139             {
    140                 args.bottom = -(viewportHeight - args.UIHeight / 2 - param.Margin.Top);
    141             }
    142             else // if ((Anchor & topBottomAnchor) == topBottomAnchor)
    143             {
    144                 args.bottom = -(args.UIHeight / 2 + param.Margin.Bottom);
    145             }
    146         }
    147     }
    IUILayoutHelper
     

    如何使用

    我们以画一个简单的边框为例说明如何使用IUILayout。这个边框画出了IUILayout元素本身的范围,在调试期间也是很有用的。

      1     /// <summary>
      2     /// Draw a rectangle on OpenGL control like a <see cref="Windows.Forms.Control"/> drawn on a <see cref="windows.Forms.Form"/>.
      3     /// Set its properties(Anchor, Margin, Size, etc) to adjust its behaviour.
      4     /// </summary>
      5     public class SimpleUIRect : SceneElementBase, IUILayout, IMVP//, IRenderable, IHasObjectSpace
      6     {
      7         /// <summary>
      8         /// shader program
      9         /// </summary>
     10         public ShaderProgram shaderProgram;
     11         const string strin_Position = "in_Position";
     12         const string strin_Color = "in_Color";
     13 
     14         /// <summary>
     15         /// VAO
     16         /// </summary>
     17         protected uint[] vao;
     18 
     19         /// <summary>
     20         /// 图元类型
     21         /// </summary>
     22         protected DrawMode axisPrimitiveMode;
     23 
     24         /// <summary>
     25         /// 顶点数
     26         /// </summary>
     27         protected int axisVertexCount;
     28 
     29         vec3 rectColor;
     30 
     31         /// <summary>
     32         /// 
     33         /// </summary>
     34         /// <param name="anchor">the edges of the viewport to which a SimpleUIRect is bound and determines how it is resized with its parent.
     35         /// <para>something like AnchorStyles.Left | AnchorStyles.Bottom.</para></param>
     36         /// <param name="margin">the space between viewport and SimpleRect.</param>
     37         /// <param name="size">Stores width when <see cref="OpenGLUIRect.Anchor"/>.Left &amp; <see cref="OpenGLUIRect.Anchor"/>.Right is <see cref="OpenGLUIRect.Anchor"/>.None.
     38         /// <para> and height when <see cref="OpenGLUIRect.Anchor"/>.Top &amp; <see cref="OpenGLUIRect.Anchor"/>.Bottom is <see cref="OpenGLUIRect.Anchor"/>.None.</para></param>
     39         /// <param name="zNear"></param>
     40         /// <param name="zFar"></param>
     41         /// <param name="rectColor">default color is red.</param>
     42         public SimpleUIRect(IUILayoutParam param, GLColor rectColor = null)
     43         {
     44             IUILayout layout = this;
     45             layout.Param = param;
     46 
     47             if (rectColor == null)
     48             { this.rectColor = new vec3(0, 0, 1); }
     49             else
     50             { this.rectColor = new vec3(rectColor.R, rectColor.G, rectColor.B); }
     51         }
     52 
     53         protected override void DoInitialize()
     54         {
     55             this.shaderProgram = InitializeShader();
     56 
     57             InitVAO();
     58 
     59             base.BeforeRendering += this.GetSimpleUI_BeforeRendering();
     60             base.AfterRendering += this.GetSimpleUI_AfterRendering();
     61         }
     62 
     63         private void InitVAO()
     64         {
     65             this.axisPrimitiveMode = DrawMode.LineLoop;
     66             this.axisVertexCount = 4;
     67             this.vao = new uint[1];
     68 
     69             GL.GenVertexArrays(1, vao);
     70 
     71             GL.BindVertexArray(vao[0]);
     72 
     73             //  Create a vertex buffer for the vertex data.
     74             {
     75                 UnmanagedArray<vec3> positionArray = new UnmanagedArray<vec3>(4);
     76                 positionArray[0] = new vec3(-0.5f, -0.5f, 0);
     77                 positionArray[1] = new vec3(0.5f, -0.5f, 0);
     78                 positionArray[2] = new vec3(0.5f, 0.5f, 0);
     79                 positionArray[3] = new vec3(-0.5f, 0.5f, 0);
     80 
     81                 uint positionLocation = shaderProgram.GetAttributeLocation(strin_Position);
     82 
     83                 uint[] ids = new uint[1];
     84                 GL.GenBuffers(1, ids);
     85                 GL.BindBuffer(BufferTarget.ArrayBuffer, ids[0]);
     86                 GL.BufferData(BufferTarget.ArrayBuffer, positionArray, BufferUsage.StaticDraw);
     87                 GL.VertexAttribPointer(positionLocation, 3, GL.GL_FLOAT, false, 0, IntPtr.Zero);
     88                 GL.EnableVertexAttribArray(positionLocation);
     89 
     90                 positionArray.Dispose();
     91             }
     92 
     93             //  Now do the same for the colour data.
     94             {
     95                 UnmanagedArray<vec3> colorArray = new UnmanagedArray<vec3>(4);
     96                 vec3 color = this.rectColor;
     97                 for (int i = 0; i < colorArray.Length; i++)
     98                 {
     99                     colorArray[i] = color;
    100                 }
    101 
    102                 uint colorLocation = shaderProgram.GetAttributeLocation(strin_Color);
    103 
    104                 uint[] ids = new uint[1];
    105                 GL.GenBuffers(1, ids);
    106                 GL.BindBuffer(BufferTarget.ArrayBuffer, ids[0]);
    107                 GL.BufferData(BufferTarget.ArrayBuffer, colorArray, BufferUsage.StaticDraw);
    108                 GL.VertexAttribPointer(colorLocation, 3, GL.GL_FLOAT, false, 0, IntPtr.Zero);
    109                 GL.EnableVertexAttribArray(colorLocation);
    110 
    111                 colorArray.Dispose();
    112             }
    113 
    114             //  Unbind the vertex array, we've finished specifying data for it.
    115             GL.BindVertexArray(0);
    116         }
    117 
    118         protected ShaderProgram InitializeShader()
    119         {
    120             var vertexShaderSource = ManifestResourceLoader.LoadTextFile(@"UIs.SimpleUIRect.vert");
    121             var fragmentShaderSource = ManifestResourceLoader.LoadTextFile(@"UIs.SimpleUIRect.frag");
    122 
    123             shaderProgram = new ShaderProgram();
    124             shaderProgram.Create(vertexShaderSource, fragmentShaderSource, null);
    125 
    126             shaderProgram.AssertValid();
    127 
    128             return shaderProgram;
    129         }
    130 
    131         protected override void DoRender(RenderEventArgs e)
    132         {
    133             GL.BindVertexArray(vao[0]);
    134 
    135             GL.DrawArrays(this.axisPrimitiveMode, 0, this.axisVertexCount);
    136 
    137             GL.BindVertexArray(0);
    138         }
    139 
    140         public IUILayoutParam Param { get; set; }
    141 
    142 
    143         void IMVP.SetShaderProgram(mat4 mvp)
    144         {
    145             IMVPHelper.DoUpdateMVP(this, mvp);
    146         }
    147 
    148 
    149         void IMVP.ResetShaderProgram()
    150         {
    151             IMVPHelper.DoUnbindShaderProgram(this);
    152         }
    153 
    154         ShaderProgram IMVP.GetShaderProgram()
    155         {
    156             return this.shaderProgram;
    157         }
    158     }
    SimpleUIRect
     

    这段代码关注如下几点:

    A:实现IUILayout只需一句" public IUILayoutParam Param { get; set; }"。

    B:实现IUILayout的元素也必须实现IMVP。实际上任何用modern OpenGL方式进行渲染的元素都应该实现IMVP。

    C:其他方面与普通元素无异。

    D:此元素借助了2个扩展方法:

     1     public static class IUILayoutRenderingHelper
     2     {
     3         private static readonly object synObj = new object();
     4         private static EventHandler<RenderEventArgs> simpleUIAxis_BeforeRendering = null;
     5         private static EventHandler<RenderEventArgs> simpleUIAxis_AfterRendering = null;
     6 
     7         /// <summary>
     8         /// 对Xxx : SceneElementBase, IUILayout, IMVP有效的After事件。
     9         /// <para>此处用泛型方法是为了让编译器检测where约束条件,这样就没有“坑”了。</para>
    10         /// </summary>
    11         /// <typeparam name="T"></typeparam>
    12         /// <param name="element"></param>
    13         /// <returns></returns>
    14         public static EventHandler<RenderEventArgs> GetSimpleUI_AfterRendering<T>(this T element) 
    15             where T : SceneElementBase, IUILayout, IMVP
    16         {
    17             if (simpleUIAxis_AfterRendering == null)
    18             {
    19                 lock (synObj)
    20                 {
    21                     if (simpleUIAxis_AfterRendering == null)
    22                     {
    23                         simpleUIAxis_AfterRendering = new EventHandler<RenderEventArgs>(SimpleUI_AfterRendering);
    24                     }
    25                 }
    26             }
    27 
    28             return simpleUIAxis_AfterRendering;
    29         }
    30 
    31         /// <summary>
    32         /// 对Xxx : SceneElementBase, IUILayout, IMVP有效的Before事件。
    33         /// <para>此处用泛型方法是为了让编译器检测where约束条件,这样就没有“坑”了。</para>
    34         /// </summary>
    35         /// <typeparam name="T"></typeparam>
    36         /// <param name="element"></param>
    37         /// <returns></returns>
    38         public static EventHandler<RenderEventArgs> GetSimpleUI_BeforeRendering<T>(this T element)
    39             where T : SceneElementBase, IUILayout, IMVP
    40         {
    41             if (simpleUIAxis_BeforeRendering == null)
    42             {
    43                 lock (synObj)
    44                 {
    45                     if (simpleUIAxis_BeforeRendering == null)
    46                     {
    47                         simpleUIAxis_BeforeRendering = new EventHandler<RenderEventArgs>(SimpleUI_BeforeRendering);
    48                     }
    49                 }
    50             }
    51 
    52             return simpleUIAxis_BeforeRendering;
    53         }
    54 
    55         static void SimpleUI_AfterRendering(object sender, RenderEventArgs e)
    56         {
    57             IMVP element = sender as IMVP;
    58             element.ResetShaderProgram();
    59         }
    60 
    61         static void SimpleUI_BeforeRendering(object sender, RenderEventArgs e)
    62         {
    63             mat4 projectionMatrix, viewMatrix, modelMatrix;
    64             {
    65                 IUILayout element = sender as IUILayout;
    66                 element.GetMatrix(out projectionMatrix, out viewMatrix, out modelMatrix, e.Camera);
    67             }
    68 
    69             {
    70                 IMVP element = sender as IMVP;
    71                 element.SetShaderProgram(projectionMatrix * viewMatrix * modelMatrix);
    72             }
    73         }
    74     }
    IUILayoutRenderingHelper
     

    借助扩展方法、类型约束等等机制,编写OpenGL程序效率高了很多。

    下面是效果图。下图中,在窗口的四个角落各安排了1个SimpUIRect。无论Camera如何改变,窗口大小如何改变,这四个蓝色矩形框的大小、边距都不会改变。

    总结

    本篇是写起来最有难度的一篇。本篇所实现的类型、接口,都是在上一篇的基础上设计的。上一篇里讲的渲染过程,隐含着本篇的设计方案的前提条件。

    本篇里的类型、接口都有各自的一套辅助类型构成一套实现某种功能的机制。但愿这不太复杂难用。我已经用Demo详细演示了各个功能是如何实现的。

  • 相关阅读:
    Oracle:SQL语句--对表的操作——删除表
    Oracle:SQL语句--对表的操作——修改表名
    Oracle:SQL语句--对表的操作——修改表名
    Oracle:SQL语句--对表的操作—— 删除字段(即删除列)
    网络配置4:vlan间通信配置
    网络配置3:动态路由配置
    网络配置2:静态路由配置
    网络配置0:网络设备基础知识
    网络配置1:VLAN配置
    T-SQL之数据操作(一):增删改
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/CSharpGL-2-how-to-design-scene-element-base-class-and-common-interfaces.html
Copyright © 2020-2023  润新知