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

news/2025/9/26 15:42:30/文章来源:https://www.cnblogs.com/horeaper/p/19111274

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;//选定为当前渲染的Shadervar shader = quadMaterial.GetShaderForce("imgui");var pass = quadMaterial.GetRenderPass("imgui");Renderer.SetShaderParameters(pass, shader, quadMaterial, false);//选定为当前渲染MeshquadMesh.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二者只需实现其一,前面选择了哪个模式就实现哪个模式即可。

如此一来渲染的部分就实现完成了。这并不是效率最高的实现方式,但概念上最简单。目前先这么做,先让程序跑起来再优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/918473.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

net公司网站开发框架源代码中国网直播

一&#xff0c;基于ANYCAST 设计骨干RP热备1&#xff0c;RP选举说明组播网络RP设置分为动态&#xff0c;静态2种。但和很多网络协议不一样&#xff0c;RP的选择默认是动态优先。也就是在路由器上同时存在动态和静态RP的时候会优选动态&#xff0c;除非定义强制静态优选动态才不…

手机网站 底部菜单一起做网站17杭州女装

我们在开发程序过程中&#xff0c;时常会使用到第三方组件&#xff0c;比如一些通信、UI组件等。常用的引用方法有下面几种。 01 NuGet引用 NuGet是.NET的一个包管理平台&#xff0c;很多开源组件会通过NuGet进行管理和发布。比如我们常用的S7NetPlus等。 从NuGet中引用组件…

服装网站开发方案网站设计博客

一、JQuery是什么&#xff1f; JQuery 是一套JavaScript库&#xff0c; 使用它&#xff0c;可以很方便的进行 JavaScript的编程。比如&#xff1a; 获取页面元素&#xff0c; 修改页面元素的CSS样式等等都可以以很简单的语法完成。节省代码行数和减少开发的时间。 物理上来看就…

元人文AI:为价值创新构筑“舞台”、订立“契约”、预演“未来”

元人文AI:为价值创新构筑“舞台”、订立“契约”、预演“未来” 在人工智能日益深入人类价值腹地的今天,我们面临着一个核心困境:一方面,科技力量要求高效、确定的决策;另一方面,人类价值领域充满难以量化的模糊…

设计一个关于python的if 语句的练习题

当然,以下是一个关于Python中if语句的练习题,旨在帮助理解和应用条件判断: 练习题:天气预测系统 假设你正在开发一个简单的天气预测系统,该系统基于用户输入的温度值来判断并输出相应的天气状况。请编写一个Pytho…

基于CSMA/CA协议的V2X通信MATLAB仿真

基于CSMA/CA协议的V2X通信MATLAB仿真,包含动态车辆移动、信道竞争和性能分析模块: %% MAC协议仿真框架 (CSMA/CA实现) clear; clc;%% 系统参数设置 simTime = 10; % 仿真时间(秒) timeStep = 0.001; % 时间步长…

【招聘专场】想斩获高含金量名企Offer?京东、微芯研究院等企业 软件测试开发岗,虚位以待!

⬆️ 关注霍格沃兹测试学院公众号,回复「面试」,领取测试开发面试真题合集!学社近期有多位同学拿到阿里巴巴、字节跳动、百度、58同城、微博、京东等大厂 Offer 并拿到学社奖学金。关注公众号,回复「面试」获取互联…

练习题:成绩评定系统

当然可以,以下是一个与if语句相关的练习题,它结合了简单的数学计算和条件判断: 练习题:成绩评定系统 假设你正在为一个学校开发一个简单的成绩评定系统。系统需要根据学生输入的分数来判断并输出学生的成绩等级。请…

Day22方法重写

重写首先是对方法的重写,与属性无关,其是在子类中定义与父类的方法名,参数列表,放回值完全相同的非静态方法 因此,重写需要有继承关系的前提下才能调用,子类修饰符的范围可以扩大,但不可以比父类小,而抛出的异…

python调试方法:验证安装路径与路径配置

根据搜索结果和您的环境信息,问题根源已明确:delphivcl模块的安装路径未正确包含在Python的搜索路径中。以下是系统性解决方案: 1️⃣ 验证安装路径与路径配置检查安装位置: pip show delphivcl查看Location字段是…

深入解析:list的学习

深入解析:list的学习2025-09-26 15:31 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-…

CentOS将软件源修改为国内源

在使用CentOS系统时,yum(Yellowdog Updater Modified)是最常用的包管理工具,无论是安装软件、更新系统还是修复依赖,都离不开它。但CentOS默认的官方yum源服务器位于国外,国内用户在使用时常常会遇到下载速度慢、…

【C++进阶】C++11 的新特性 | lambda | 包装器 - 实践

【C++进阶】C++11 的新特性 | lambda | 包装器 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&qu…

实用指南:用Playwright实现接口自动化测试:从基础到实战

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

易路一站式智能招聘,开启人才管理新纪元

在全球招聘市场经历深刻变革的当下,人工智能正以前所未有的速度重塑着企业的人才获取策略。作为人力资源科技领域的领军企业,易路人力资源科技集团(以下简称“易路”)凭借其深厚的行业积累与前瞻性的AI布局,于202…

温州微网站制作电话伪类网站

目录 一.Python 进程池 multiprocessing.Pool 介绍二.Python 进程池 multiprocessing.Pool 使用三.猜你喜欢 零基础 Python 学习路线推荐 : Python 学习目录 >> Python 基础入门 Python 进程池 Pool 和前面讲解的** Python 线程池** 类似&#xff0c;虽然使用多进程能提…

常宁市住房和城乡建设局网站官网设计模板

题意&#xff1a;给出三个数的gcd,lcm&#xff0c;求这三个数的全部的可能 思路 &#xff1a;设x,y,z的gcd为d&#xff0c;那么设xd*a&#xff0c;yd*b&#xff0c;zd*c。a&#xff0c;b。c肯定是互质的。那么lcmd*a*b*c,所以我们能够得到a*b*clcm/gcdans,将ans分解因数后&…

kingbase数据库SHELL端迁移工具访问及使用

SHELL端迁移工具存放位置: $KINGBASE_HOME/ClientTools/guitools/KDts/KDTS-CLI SHELL工具中使用KDTS与Web端存在差异的部分在于:迁移任务配置上,Web方式在启动脚本之后,通过网页可视化表单直接修改配置项;SHELL方…

数据质量定胜负:Apple 如何破解双语大模型性能鸿沟

在大型语言模型(LLM)向多语言能力迈进的征程中,一个被称为“多语言诅咒”(Curse of Multilinguality)的现象长期困扰着整个行业:在预训练数据中加入更多语言,有时非但不能提升所有语言的性能,反而会导致英语等…