找回密码
 立即注册
首页 业界区 业界 Unigine整合Myra UI Library全纪录(2):渲染 ...

Unigine整合Myra UI Library全纪录(2):渲染

于映雪 7 天前
TextureQuadBatcher

由于Unigine没有SpriteBatch类似物,需要手动实现一个。当然用Unigine.Ffp直接来搞也可以,只不过效率就会差一些了。
因为我打算同时用Myra和ImGui.NET,因此这里偷了个懒,去借用Unigine示例里整合ImGui.NET用的Shader/Material了。不打算用ImGui的可以去把unigine-imgui-csharp-integration-sample\data\imgui.basemat拷贝到自己项目的data目录下。
接下来按照它这个Shader使用顶点的方式,定义顶点格式(其实就是ImGui的顶点格式):
  1. [StructLayout(LayoutKind.Sequential, Pack = 1)]
  2. struct VertexLayout(VertexPositionColorTexture vertexData)
  3. {
  4.         public vec2 Position = new(vertexData.Position.X, vertexData.Position.Y);
  5.         public vec2 TexCoord = new(vertexData.TextureCoordinate.X, vertexData.TextureCoordinate.Y);
  6.         public uint Color = vertexData.Color.PackedValue;
  7. }
复制代码
VertexPositionColorTexture是Myra传递过来的顶点数据格式。
接下来声明几个会用到的常量和变量:
  1. const int MaxSprites = 2048;
  2. const int MaxVertices = MaxSprites * 4;
  3. const int MaxIndices = MaxSprites * 6;
  4. readonly MeshDynamic quadMesh;
  5. readonly Material quadMaterial;
  6. Texture? lastTexture;
  7. readonly VertexLayout[] vertexData = new VertexLayout[MaxVertices];
  8. int vertexCount;
复制代码
指定一次最多绘制2048个图元,这个数量已经很多了,再多会导致Mesh的Index尺寸超过65536,效率就会有所降低(Unigine的Index是4字节int)。
MeshDynamic是Unigine的动态Mesh对象,创建并指定顶点格式的过程也很简单:
  1. quadMesh = new MeshDynamic(MeshDynamic.USAGE_DYNAMIC_VERTEX);
  2. var vertexFormat = new MeshDynamic.Attribute[3];
  3. vertexFormat[0].type = MeshDynamic.TYPE_FLOAT;
  4. vertexFormat[0].offset = 0;
  5. vertexFormat[0].size = 2;
  6. vertexFormat[1].type = MeshDynamic.TYPE_FLOAT;
  7. vertexFormat[1].offset = 8;
  8. vertexFormat[1].size = 2;
  9. vertexFormat[2].type = MeshDynamic.TYPE_UCHAR;
  10. vertexFormat[2].offset = 16;
  11. vertexFormat[2].size = 4;
  12. quadMesh.SetVertexFormat(vertexFormat);
复制代码
注意在创建的时候,指定USAGE_DYNAMIC_VERTEX,而不是USAGE_DYNAMIC_ALL。由于Myra会让我们绘制的全都是单纯的Quad,因此Index可以完全不动,提前创建好就不再更改了:
  1. var indexData = new int[MaxIndices];
  2. for (int i = 0, j = 0; i < MaxIndices; i += 6, j += 4) {
  3.         indexData[i + 0] = j + 0;
  4.         indexData[i + 1] = j + 1;
  5.         indexData[i + 2] = j + 2;
  6.         indexData[i + 3] = j + 3;
  7.         indexData[i + 4] = j + 2;
  8.         indexData[i + 5] = j + 1;
  9. }
  10. quadMesh.SetIndicesArray(indexData);
  11. quadMesh.FlushIndices();
复制代码
顺便把Material也创建好:
  1. quadMaterial = Materials.FindManualMaterial("imgui").Inherit();
复制代码
基本的数据都准备好了之后,开始制作绘制Quad的过程。这里先采用和Xna的SpriteBatch类似的Begin/Draw/End结构。首先是Begin:
  1. public void Begin(TextureFiltering textureFiltering)
  2. {
  3.         //设置渲染状态
  4.         RenderState.SaveState();
  5.         RenderState.ClearStates();
  6.         RenderState.SetBlendFunc(RenderState.BLEND_ONE, RenderState.BLEND_ONE_MINUS_SRC_ALPHA);
  7.         RenderState.PolygonCull = RenderState.CULL_NONE;
  8.         RenderState.DepthFunc = RenderState.DEPTH_NONE;
  9.         //用正交投影矩阵渲染
  10.         var clientRenderSize = WindowManager.MainWindow.ClientRenderSize;
  11.         float left = 0;
  12.         float right = clientRenderSize.x;
  13.         float top = 0;
  14.         float bottom = clientRenderSize.y;
  15.         var orthoProj = new mat4 {
  16.                 m00 = 2.0f / (right - left),
  17.                 m03 = (right + left) / (left - right),
  18.                 m11 = 2.0f / (top - bottom),
  19.                 m13 = (top + bottom) / (bottom - top),
  20.                 m22 = 0.5f,
  21.                 m23 = 0.5f,
  22.                 m33 = 1.0f
  23.         };
  24.         Renderer.Projection = orthoProj;
  25.         //选定为当前渲染的Shader
  26.         var shader = quadMaterial.GetShaderForce("imgui");
  27.         var pass = quadMaterial.GetRenderPass("imgui");
  28.         Renderer.SetShaderParameters(pass, shader, quadMaterial, false);
  29.         //选定为当前渲染Mesh
  30.         quadMesh.Bind();
  31. }
复制代码
一目了然,没什么好说的。要注意的就是RenderState.SetBlendFunc()这里,是One加上OneMinusSrcAlpha的模式,和传统Alpha混合的SrcAlpha加OneMinusSrcAlpha模式不同。因为Myra使用的是Pre-Multiplied Alpha。
顺便把End也写了:
  1. public void End()
  2. {
  3.         Flush();
  4.         //恢复渲染状态
  5.         quadMesh.Unbind();
  6.         RenderState.RestoreState();
  7. }
复制代码
之后是和Myra对接的部分:
  1. public void DrawQuad(Texture texture, ref VertexPositionColorTexture topLeft, ref VertexPositionColorTexture topRight, ref VertexPositionColorTexture bottomLeft, ref VertexPositionColorTexture bottomRight)
  2. {
  3.         if (texture != lastTexture || vertexCount >= MaxVertices) {
  4.                 Flush();
  5.                 lastTexture = texture;
  6.         }
  7.         vertexData[vertexCount++] = new VertexLayout(topLeft);
  8.         vertexData[vertexCount++] = new VertexLayout(topRight);
  9.         vertexData[vertexCount++] = new VertexLayout(bottomLeft);
  10.         vertexData[vertexCount++] = new VertexLayout(bottomRight);
  11. }
复制代码
其实就是将Myra传递过来的数据缓存起来,当Texture发生了改变,或者顶点数量超过缓冲区上限了之后,再输出。
最后就是最重要的输出部分了,然而这部分反而代码很简单:
  1. public void Flush()
  2. {
  3.         if (vertexCount == 0 || lastTexture == null) {
  4.                 return;
  5.         }
  6.         //应用顶点数据
  7.         quadMesh.ClearVertex();
  8.         unsafe {
  9.                 fixed (void* pVertexData = vertexData) {
  10.                         quadMesh.SetVertexArray((nint)pVertexData, vertexCount);
  11.                 }
  12.         }
  13.         quadMesh.FlushVertex();
  14.         //绘制
  15.         RenderState.SetTexture(RenderState.BIND_FRAGMENT, 0, lastTexture);
  16.         quadMesh.RenderSurface(MeshDynamic.MODE_TRIANGLES, 0, 0, vertexCount / 4 * 6);
  17.         //重置计数
  18.         vertexCount = 0;
  19. }
复制代码
Unigine提供的SetVertexArray不完整,因此这里多了一块unsafe。
绘制部分没啥好说的:设置纹理,输出三角形,通过vertexCount / 4 * 6计算得到绘制的Index总数量。
IMyraRenderer

MyraRenderer支持两种模式,Sprite模式:给Xna的SpriteBatch类似物使用。Quad模式:直接绘制顶点。Unigine自然要使用Quad模式:
  1. RendererType IMyraRenderer.RendererType => RendererType.Quad;
复制代码
之后声明几个后面要用到的变量,并将其初始化:
  1. readonly TextureQuadBatcher quadBatcher = new();
  2. Rectangle currentScissor;
  3. bool isBeginCalled;
  4. public MyraRenderer()
  5. {
  6.         var clientRenderSize = WindowManager.MainWindow.ClientRenderSize;
  7.         currentScissor = new Rectangle(0, 0, clientRenderSize.x, clientRenderSize.y);
  8. }
复制代码
然后实现Myra的Scissor:
  1. Rectangle IMyraRenderer.Scissor
  2. {
  3.         get => currentScissor;
  4.         set {
  5.                 if (value != currentScissor) {
  6.                         Flush();
  7.                         currentScissor = value;
  8.                         var clientRenderSize = WindowManager.MainWindow.ClientRenderSize;
  9.                         int y = clientRenderSize.y - (currentScissor.Y + currentScissor.Height); //ScissorTest是右手坐标系,Y轴从屏幕下方往上数
  10.                         RenderState.SetScissorTest((float)currentScissor.X / clientRenderSize.x, (float)y / clientRenderSize.y, (float)currentScissor.Width / clientRenderSize.x, (float)currentScissor.Height / clientRenderSize.y);
  11.                 }
  12.         }
  13. }
复制代码
每次Scissor变化的时候,都要将已有的缓存刷新,再调用RenderState.SetScissorTest。由于Unigine是右手坐标系,屏幕左下角是(0.0f,0.0f),右上角是(1.0f,1.0f)。而Myra传递过来的是传统的屏幕像素坐标,左上角为(0,0)右下角是(ClientRenderSize.x,ClientRenderSize.y),因此这里要对坐标系进行转换。
剩下的几个接口就很简单了,把相应的参数传给TextureQuadBatcher就可以。
  1. void IMyraRenderer.Begin(TextureFiltering textureFiltering)
  2. {
  3.         quadBatcher.Begin(textureFiltering);
  4.         isBeginCalled = true;
  5. }
  6. void IMyraRenderer.End()
  7. {
  8.         quadBatcher.End();
  9.         isBeginCalled = false;
  10. }
  11. void IMyraRenderer.DrawSprite(object texture, Vector2 pos, Rectangle? src, FSColor color, float rotation, Vector2 scale, float depth)
  12. {
  13.         //ignored
  14. }
  15. void IMyraRenderer.DrawQuad(object texture, ref VertexPositionColorTexture topLeft, ref VertexPositionColorTexture topRight, ref VertexPositionColorTexture bottomLeft, ref VertexPositionColorTexture bottomRight)
  16. {
  17.         quadBatcher.DrawQuad((Texture)texture, ref topLeft, ref topRight, ref bottomLeft, ref bottomRight);
  18. }
  19. void Flush()
  20. {
  21.         if (isBeginCalled) {
  22.                 quadBatcher.Flush();
  23.         }
  24. }
复制代码
DrawSprite/DrawQuad二者只需实现其一,前面选择了哪个模式就实现哪个模式即可。
如此一来渲染的部分就实现完成了。这并不是效率最高的实现方式,但概念上最简单。目前先这么做,先让程序跑起来再优化。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

昨天 23:38

举报

感谢,下载保存了
您需要登录后才可以回帖 登录 | 立即注册