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的顶点格式):- [StructLayout(LayoutKind.Sequential, Pack = 1)]
- struct VertexLayout(VertexPositionColorTexture vertexData)
- {
- public vec2 Position = new(vertexData.Position.X, vertexData.Position.Y);
- public vec2 TexCoord = new(vertexData.TextureCoordinate.X, vertexData.TextureCoordinate.Y);
- public uint Color = vertexData.Color.PackedValue;
- }
复制代码 VertexPositionColorTexture是Myra传递过来的顶点数据格式。
接下来声明几个会用到的常量和变量:- const int MaxSprites = 2048;
- const int MaxVertices = MaxSprites * 4;
- const int MaxIndices = MaxSprites * 6;
- readonly MeshDynamic quadMesh;
- readonly Material quadMaterial;
- Texture? lastTexture;
- readonly VertexLayout[] vertexData = new VertexLayout[MaxVertices];
- int vertexCount;
复制代码 指定一次最多绘制2048个图元,这个数量已经很多了,再多会导致Mesh的Index尺寸超过65536,效率就会有所降低(Unigine的Index是4字节int)。
MeshDynamic是Unigine的动态Mesh对象,创建并指定顶点格式的过程也很简单:- quadMesh = new MeshDynamic(MeshDynamic.USAGE_DYNAMIC_VERTEX);
- var vertexFormat = new MeshDynamic.Attribute[3];
- vertexFormat[0].type = MeshDynamic.TYPE_FLOAT;
- vertexFormat[0].offset = 0;
- vertexFormat[0].size = 2;
- vertexFormat[1].type = MeshDynamic.TYPE_FLOAT;
- vertexFormat[1].offset = 8;
- vertexFormat[1].size = 2;
- vertexFormat[2].type = MeshDynamic.TYPE_UCHAR;
- vertexFormat[2].offset = 16;
- vertexFormat[2].size = 4;
- quadMesh.SetVertexFormat(vertexFormat);
复制代码 注意在创建的时候,指定USAGE_DYNAMIC_VERTEX,而不是USAGE_DYNAMIC_ALL。由于Myra会让我们绘制的全都是单纯的Quad,因此Index可以完全不动,提前创建好就不再更改了:- var indexData = new int[MaxIndices];
- for (int i = 0, j = 0; i < MaxIndices; i += 6, j += 4) {
- indexData[i + 0] = j + 0;
- indexData[i + 1] = j + 1;
- indexData[i + 2] = j + 2;
- indexData[i + 3] = j + 3;
- indexData[i + 4] = j + 2;
- indexData[i + 5] = j + 1;
- }
- quadMesh.SetIndicesArray(indexData);
- quadMesh.FlushIndices();
复制代码 顺便把Material也创建好:- quadMaterial = Materials.FindManualMaterial("imgui").Inherit();
复制代码 基本的数据都准备好了之后,开始制作绘制Quad的过程。这里先采用和Xna的SpriteBatch类似的Begin/Draw/End结构。首先是Begin:- public void Begin(TextureFiltering textureFiltering)
- {
- //设置渲染状态
- RenderState.SaveState();
- RenderState.ClearStates();
- RenderState.SetBlendFunc(RenderState.BLEND_ONE, RenderState.BLEND_ONE_MINUS_SRC_ALPHA);
- RenderState.PolygonCull = RenderState.CULL_NONE;
- RenderState.DepthFunc = RenderState.DEPTH_NONE;
- //用正交投影矩阵渲染
- var clientRenderSize = WindowManager.MainWindow.ClientRenderSize;
- float left = 0;
- float right = clientRenderSize.x;
- float top = 0;
- float bottom = clientRenderSize.y;
- var orthoProj = new mat4 {
- m00 = 2.0f / (right - left),
- m03 = (right + left) / (left - right),
- m11 = 2.0f / (top - bottom),
- m13 = (top + bottom) / (bottom - top),
- m22 = 0.5f,
- m23 = 0.5f,
- m33 = 1.0f
- };
- Renderer.Projection = orthoProj;
- //选定为当前渲染的Shader
- var shader = quadMaterial.GetShaderForce("imgui");
- var pass = quadMaterial.GetRenderPass("imgui");
- Renderer.SetShaderParameters(pass, shader, quadMaterial, false);
- //选定为当前渲染Mesh
- quadMesh.Bind();
- }
复制代码 一目了然,没什么好说的。要注意的就是RenderState.SetBlendFunc()这里,是One加上OneMinusSrcAlpha的模式,和传统Alpha混合的SrcAlpha加OneMinusSrcAlpha模式不同。因为Myra使用的是Pre-Multiplied Alpha。
顺便把End也写了:- public void End()
- {
- Flush();
- //恢复渲染状态
- quadMesh.Unbind();
- RenderState.RestoreState();
- }
复制代码 之后是和Myra对接的部分:- public void DrawQuad(Texture texture, ref VertexPositionColorTexture topLeft, ref VertexPositionColorTexture topRight, ref VertexPositionColorTexture bottomLeft, ref VertexPositionColorTexture bottomRight)
- {
- if (texture != lastTexture || vertexCount >= MaxVertices) {
- Flush();
- lastTexture = texture;
- }
- vertexData[vertexCount++] = new VertexLayout(topLeft);
- vertexData[vertexCount++] = new VertexLayout(topRight);
- vertexData[vertexCount++] = new VertexLayout(bottomLeft);
- vertexData[vertexCount++] = new VertexLayout(bottomRight);
- }
复制代码 其实就是将Myra传递过来的数据缓存起来,当Texture发生了改变,或者顶点数量超过缓冲区上限了之后,再输出。
最后就是最重要的输出部分了,然而这部分反而代码很简单:- public void Flush()
- {
- if (vertexCount == 0 || lastTexture == null) {
- return;
- }
- //应用顶点数据
- quadMesh.ClearVertex();
- unsafe {
- fixed (void* pVertexData = vertexData) {
- quadMesh.SetVertexArray((nint)pVertexData, vertexCount);
- }
- }
- quadMesh.FlushVertex();
- //绘制
- RenderState.SetTexture(RenderState.BIND_FRAGMENT, 0, lastTexture);
- quadMesh.RenderSurface(MeshDynamic.MODE_TRIANGLES, 0, 0, vertexCount / 4 * 6);
- //重置计数
- vertexCount = 0;
- }
复制代码 Unigine提供的SetVertexArray不完整,因此这里多了一块unsafe。
绘制部分没啥好说的:设置纹理,输出三角形,通过vertexCount / 4 * 6计算得到绘制的Index总数量。
IMyraRenderer
MyraRenderer支持两种模式,Sprite模式:给Xna的SpriteBatch类似物使用。Quad模式:直接绘制顶点。Unigine自然要使用Quad模式:- RendererType IMyraRenderer.RendererType => RendererType.Quad;
复制代码 之后声明几个后面要用到的变量,并将其初始化:- readonly TextureQuadBatcher quadBatcher = new();
- Rectangle currentScissor;
- bool isBeginCalled;
- public MyraRenderer()
- {
- var clientRenderSize = WindowManager.MainWindow.ClientRenderSize;
- currentScissor = new Rectangle(0, 0, clientRenderSize.x, clientRenderSize.y);
- }
复制代码 然后实现Myra的Scissor:- Rectangle IMyraRenderer.Scissor
- {
- get => currentScissor;
- set {
- if (value != currentScissor) {
- Flush();
- currentScissor = value;
- var clientRenderSize = WindowManager.MainWindow.ClientRenderSize;
- int y = clientRenderSize.y - (currentScissor.Y + currentScissor.Height); //ScissorTest是右手坐标系,Y轴从屏幕下方往上数
- RenderState.SetScissorTest((float)currentScissor.X / clientRenderSize.x, (float)y / clientRenderSize.y, (float)currentScissor.Width / clientRenderSize.x, (float)currentScissor.Height / clientRenderSize.y);
- }
- }
- }
复制代码 每次Scissor变化的时候,都要将已有的缓存刷新,再调用RenderState.SetScissorTest。由于Unigine是右手坐标系,屏幕左下角是(0.0f,0.0f),右上角是(1.0f,1.0f)。而Myra传递过来的是传统的屏幕像素坐标,左上角为(0,0)右下角是(ClientRenderSize.x,ClientRenderSize.y),因此这里要对坐标系进行转换。
剩下的几个接口就很简单了,把相应的参数传给TextureQuadBatcher就可以。- void IMyraRenderer.Begin(TextureFiltering textureFiltering)
- {
- quadBatcher.Begin(textureFiltering);
- isBeginCalled = true;
- }
- void IMyraRenderer.End()
- {
- quadBatcher.End();
- isBeginCalled = false;
- }
- void IMyraRenderer.DrawSprite(object texture, Vector2 pos, Rectangle? src, FSColor color, float rotation, Vector2 scale, float depth)
- {
- //ignored
- }
- void IMyraRenderer.DrawQuad(object texture, ref VertexPositionColorTexture topLeft, ref VertexPositionColorTexture topRight, ref VertexPositionColorTexture bottomLeft, ref VertexPositionColorTexture bottomRight)
- {
- quadBatcher.DrawQuad((Texture)texture, ref topLeft, ref topRight, ref bottomLeft, ref bottomRight);
- }
- void Flush()
- {
- if (isBeginCalled) {
- quadBatcher.Flush();
- }
- }
复制代码 DrawSprite/DrawQuad二者只需实现其一,前面选择了哪个模式就实现哪个模式即可。
如此一来渲染的部分就实现完成了。这并不是效率最高的实现方式,但概念上最简单。目前先这么做,先让程序跑起来再优化。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |