自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(127)
  • 收藏
  • 关注

原创 UE5中的四元数

我们知道,四元数是除了欧拉角,旋转矩阵之外,主要用来描述旋转的量。四元数直观的定义就是q=[cos(θ2),sin(θ2)N]q = [cos(\frac{\theta}{2}), sin(\frac{\theta}{2})N]q=[cos(2θ​),sin(2θ​)N]其中,N表示旋转轴单位向量,θ\thetaθ表示旋转的角度。那么,给定一个任意向量V,绕N旋转θ\thetaθ角度之后的V’可以用四元数乘法表示。令四元数v = [0, V],v’ = [0, V’],那么v′=qvq∗v' =

2025-02-15 16:20:47 712

原创 Unity DOTS中的share component

share component是DOTS中一类比较特殊的component,顾名思义,它是全局共享的component,所有具有相同component值的entity,共享一个component,这就避免了数据的冗余。但是相应地,如果修改了某个entity上的share component值,则会触发structural change,Unity不得不将entity挪到使用新值的chunk中去。

2024-12-21 17:11:40 1295

原创 UE5的TRS矩阵

UE5的TRS矩阵matrixtranslaterotatescaleTRS顺序绕任意轴旋转Reference我们知道,常用的transform主要有三种,分别是translate,rotate,scale。接下来我们逐一看下在UE5中,如何分别使用TRS构造相应的矩阵,以及如何从矩阵中提取TRS。matrixUE中的矩阵是行主序,x,y,z基向量会以行向量的形式存储在矩阵中。矩阵的数据结构定义很简单,就是一个4x4的template二维数组:/** * 4x4 matrix of floatin

2024-12-15 16:26:04 1191 1

原创 UE5相机系统初探(二)

上一章节我们实现了一个基本的第三人称相机。这一节我们为相机添加一些功能。

2024-12-01 20:41:31 1612

原创 Unity DOTS中的Entity

这里的Index,并非是表示entity在chunk中的存储位置,它是一个二级索引,指向管理entity的block。另外,从代码中的注释可知,entity的Index是会循环利用的,当一个entity销毁时,另一个创建的entity可能会复用到之前entity的Index,所以entity还包含一个Version字段。管理entity的block是全局唯一的,它是一个长度为16K的64位指针数组,其中每个指针指向一个更小的data block,data block最多可记录8K数量的entity状态。

2024-11-23 18:21:27 1306

原创 UE5的线程同步机制

fopen基于以上,我们来讨论一下UE5中形形色色的线程同步机制。

2024-11-16 14:19:08 1285

原创 LuaJIT源码分析(六)语法分析

通过语法描述,我们可以知道,lua代码是以chunk为单位组成的,chunk又是由若干语句(statement)组成的,statement又分为普通的statement和最后结尾的last statement组成。上述EBNF描述中,{}包住的部分表示可以出现0次或者多次,[]包住的部分表示可以出现0次或者1次,大写字母开头(如Name,String,Number)和字体加粗的部分表示lua词法分析的token,它们不能继续用子表达式来进行描述。除此之外的情况,就都按赋值语句的规则进行解析。

2024-11-10 16:14:00 1109

原创 UE5相机系统初探(一)

首先注意到camera在世界坐标系下的rotation为0,而玩家当前的rotation为(Y=51.04),右边视图显示的是camera component的transform,它表示camera相对于root component也就是玩家的旋转,因此是-51.04。如果此时运行游戏,按方向键,会出现反直觉的奇怪现象,玩家除了location会变化,rotation也会变化,而我们明明只是调用了AddMovementInput,并没有设置玩家的rotation。那么实际上,这个选项还是应当勾上的。

2024-11-02 16:16:36 1668

原创 LuaJIT源码分析(五)词法分析

c表示当前扫描到的字符;剩下的逻辑其实就比较简单了,如果当前字符是数字,那么就走假设token是数字字面常量的逻辑;这些处理逻辑都比较简单,如果只通过当前字符无法判断token类型,就会去读取下一个字符甚至更多字符来进行判断。会返回当前扫描的token类型,LuaJIT只对那些不能用单字符表示的token,进行了定义,如果token本身就是单字符的,比如( + - / )之类,就直接用该字符作为它的token类型。lua的token可以分为若干不同的类型,比如关键字,标识符,字面量,运算符,分隔符等等。

2024-10-27 15:42:27 749

原创 Unity DOTS中的Archetype与Chunk

chunk中的数据是紧密连续的,chunk的每个entity都存储在每个数组连续的索引中。例如,在具有component类型 A 和 B 的archetype中,每个chunk都有三个数组:一个用于component A的数组,一个用于component B的数组,以及一个用于entity ID的数组。这个函数来创建entity。随后,Unity会在该chunk中为要创建的entity分配索引,索引为当前chunk的entity数量,因为之前提到chunk的数据都是连续排布的,中间并不存在空隙。

2024-10-14 00:26:59 1654 1

原创 LuaJIT源码分析(四)table

LuaJIT源码分析(四)tablelua的table是lua唯一的数据结构,可以用来表示所有的数据。它非常好用而且简洁,但是背后的实现却是十分的复杂。

2024-09-01 16:13:54 1361

原创 Unity DOTS中的world

如果不是editor world,unity允许用户自定义创建world,负责创建的类需要继承自接口,并实现Initialize方法。该方法返回值类型为bool,如果为true则会跳过default world的初始化。

2024-07-22 23:55:26 1177 1

原创 CinemachineBrain的属性简介

CinemachineBrain是Unity Cinemachine的核心组件,它和Camera组件挂载在一起,监控场景中所有的virtual camera。CinemachineBrain在inspector中暴露的属性如下:Live Camera和Live Blend分别表示当前active的virtual camera以及blend的进度(如果有的话)。相关的代码可以在return;= null?});return ux;

2024-07-10 23:52:44 1021

原创 UE5的引擎初始化流程

另外,为了保证引擎的退出逻辑一定会被调用到,UE在这里定义了一个局部的struct,这个struct定义了一个析构函数,析构函数的实现就是调用引擎的退出逻辑。有了这个局部struct,再定义一个局部的对象,这个对象不是动态分配的,那么C++会保证它离开作用域时就会销毁它,从而调用到引擎的退出逻辑。顺便一提,PreInit,Init,Tick,Exit都是简单的包装函数,它们的实现就是调用全局变量GEngineLoop的相应接口。自此,引擎的初始化流程就宣告结束了,后面就进入到了tick的阶段。

2024-06-27 23:30:54 1583 2

原创 LuaJIT源码分析(三)字符串

由于每次新建字符串时都需要计算一次hash,这个函数调用会比较频繁,因此它是一个稀疏的hash函数,内部实现使用了lookup3 hash算法,只会取字符串中的常数个字节进行计算,所以函数的运行是一个常数的时间。lua的字符串是内化不可变的,也就是lua字符串变量存放的不是字符串的拷贝,而是字符串的引用。tab字段保存了所有字符串,它是个链表数组,每个新建的字符串先根据hash算法计算出自身的hash值,再根据当前StrInternState的mask,得到数组的索引,如果已经有元素,就插入到链表的头部。

2024-05-02 16:36:15 1196

原创 Unity DOTS中的baking(五)prefabs

LoadedPrefabs则是一对一的HashMap,它保存了当前已经加载完成的prefab信息,包含引用计数,prefab对应的Entity。的Entity,这与前面恰好相反,它表示所有需要取消加载prefab的Entity。可以看到,这里保存了prefab对应的guid,后续是通过guid来进行prefab加载的。发起加载请求,该方法返回一个表示prefab当前加载状态的Entity,如果prefab加载完成,Entity上会添加一个。它负责prefab引用计数的管理,加载和卸载prefab的操作。

2024-04-24 00:47:08 1541

原创 LuaJIT源码分析(二)数据类型

nil和bool类型是值类型,无需gc管理,而light userdata的定义就是外部管理的对象,只是将指针传给了lua,所以也不受lua的gc管理。的实现也比较巧妙,它不是直接去判断TValue是否为一个非NaN的double,而是尝试取出它的itype,如果itype比最后一个定义的LJ_TISNUM都还要小(注意itype的定义都是取反过的),那么说明它必定不是一个合法定义的TValue类型,也就只能是个double了。换句话说,只要满足这个条件,剩下的51位尾数,完全可以用来编码其他的数据。

2024-04-01 23:14:22 1227

原创 Unity DOTS中的baking(四)blob assets

举个通俗的例子,假如第一次分配了12字节的struct内存,而第二次要分配的struct,总共64字节,但它是8字节对齐的,那么这里就不会紧挨着从12字节的位置继续分配,而是计算8字节对齐的整数倍,也就是16字节处,开始分配这个struct。allocIndex和offset表示本次分配的结果,allocIndex表示分配的内存在第0号chunk上,offset表示分配的内存首地址相对于chunk的首地址偏移量,这里为0。直到此时此刻才会被填充。那如果需要分配的内存,超过一个chunk的大小,要怎么办呢?

2024-03-26 22:48:30 1249

原创 LuaJIT源码分析(一)搭建调试环境

浏览这个脚本,可以发现LuaJIT的编译分为三个部分,首先是build出一个minilua,它是lua原生代码的一个子集,用来判断当前平台是32位还是64位,并执行lua脚本并生成平台相关的指令;最后编译lua的各种lib,然后生成最终的LuaJIT。另外,脚本在编译结束时,会执行一些清理工作,把中间生成的代码,以及编译产生的obj都会删除掉。然后,我们在src同级目录下新建一个目录,用来存放vs工程,在vs中新建一个empty console C++工程,然后把src目录的头文件和源文件都添加进去。

2024-03-03 14:22:09 1732

原创 Games 103 作业四

先要找到水面与方块相交的区域,然后计算出它们的low_h,这里计算相交可以使用Unity内置的Bounds.IntersectRay,每个水面格子从底部发射一条射线,然后判断是否和方块的包围盒相交,相交的距离就是low_h。第二步,当玩家按下r键时,需要在水面随机落下一个水滴,那么该位置的水面高度需要增加,为了避免水面溢出,还需要把该位置附近的水面高度减小,这样保证整体的水量不变。首先第一步,在update函数的开头,加载水面mesh的高度,然后在update的结束时,把计算后的高度更新到mesh中。

2024-02-24 15:52:26 533

原创 xlua源码分析(六) C#与lua的交互总结

我们分析了xlua对struct类型所做的优化,本节我们系统性地梳理一下xlua中C#与lua的交互。所谓C#与lua的交互,其实主要就分为两部分,第一是往lua层中传数据,第二则是从lua层中取数据。

2024-02-21 23:54:17 1425

原创 Unity DOTS中的baking(三)过滤baking的输出

从代码中可以猜测出,这里的变化指的是conversation world和shadow world之间的diff,conversation world就是baker和baking system运行的地方,而shadow world则是上一次baking环节输出的拷贝,Unity使用这个shadow world,与当前baking的输出进行对比,只把不同的components和entities拷贝到main world,然后再更新shadow world为当前的conversation world。

2024-02-03 21:34:47 1857

原创 xlua源码分析(五) struct类型优化

而数据传输的逻辑,稍微不太相同,tolua是使用lua函数进行数据传输,例如Vector3,tolua可以通过一个get函数直接返回3个float*给C#层,也可以通过一个new函数直接使用x,y,z三个参数构造出一个lua层的struct,pack和unpack的逻辑都放在了lua层里。函数是在C层实现的,那其实很简单了,就是把userdata作为要访问内存的首地址,加上偏移量offset,执行memcpy即可,如果是get,就是从userdata拷贝到value,再push到lua栈;

2024-01-15 21:03:01 1445

原创 STL tuple源码分析

STL还提供了两个便捷的函数创建tuple,一个是make_tuple,一个是tie,两者的区别在于返回的tuple类型有差异,一个返回参数类型为值类型的tuple(除非传入的参数是reference_wrapper),一个返回的是引用类型的tuple。如果tuple的元素个数不止一个,那结果直接就是true,而只有一个元素时,需要进行一系列的判断,首先两个tuple的类型不能相同,然后,传入的tuple参数不能直接构造tuple的参数类型,也不能直接convert到tuple的参数类型。

2024-01-14 13:17:38 1003

原创 Unity DOTS中的baking(二)Baker的触发

这个事情是必要的,比如我们给当前挂有MyAuthoring脚本的GameObject建立一个父节点,如果父节点的transform发生变化,或者层级关系发生了变化,那么子节点的world transform必然也发生了变化,理应就要再次触发baking。,可以看到这里会再去获取一下记录的GameObject身上最新的component,如果和之前记录的component instance id不同,说明component经历了从无到有或者从有到无的过程,它已经不是之前的那个对象了,此时就会返回false。

2024-01-03 23:55:15 1277

原创 xlua源码分析(四) lua访问C#的值类型

与tolua相比,两者都实现了无gc的值类型传递。而tolua的值类型,默认会在lua层实现一份类似的代码,lua层在调用时,完全是走的lua层的逻辑,不会涉及拷贝数据到C#层的逻辑,只有作为函数调用参数和返回值时,才涉及到数据的拷贝。这样做的好处,就是避免了在函数调用过程的频繁的数据拷贝开销,不方便的地方就是需要在lua层自己实现一遍C#的值类型,而使用wrap则只需要自动生成代码即可,没有额外的开发负担。比如这里,把一个C#对象push到lua层,意味着lua层需要知道对象的类型id,也就是C#层的。

2023-12-25 23:37:53 742

原创 Unity DOTS中的baking(一) Baker简介

baking是DOTS ECS工作流的一环,大概的意思就是将原先Editor下的GameObject数据,全部转换为Entity数据的过程。baking是一个不可逆的过程,原先的GameObject在运行时不复存在,都会变成Entity。baking只会在Editor环境下运行,而且只会对SubScene中包含的GameObject进行烘焙。

2023-12-10 22:08:34 652

原创 Games 103 作业三

模拟很不稳定,房子直接飞了。作业中为了避免这一情况,使用了拉普拉斯平滑。作业三的内容主要就是实现一下FVM。我们按照文档中的步骤,第一步就是去独立地更新mesh的速度和位置,在初始化每个顶点的受力时,需要考虑到重力的影响。下面再看一下附加题部分,其实就是根据PPT的第35页,换一种方法计算P。接下来要考虑mesh的碰撞处理。在作业给的场景中,就只有一个简单的floor。其实这个时候房子已经可以跳动起来了,只是。那么问题就只剩计算这三个偏导数。的函数(注意,这里PPT给的公式有误)根据PPT的第25页,有。

2023-12-06 21:20:22 200

原创 STL pair源码分析

不难发现,只要传入pair的两个类型任意一个的默认构造函数是explicit的,那么pair的这个默认构造函数就是explicit的,这一点也很好理解。首先是一开始template的声明,这里加了一个enable_if_t<bool, T>,它用来进行编译检查,即只有第一个模板参数推导出来结果为true时,后面的T才生效,也就是说,如果检查失败,就不存在对应的T,这里的template声明就是非法,编译期间就会报错。有点令人意外的是p2,它传入的参数明明是int&,但pair的参数类型却是int。

2023-11-25 15:57:49 539

原创 xlua源码分析(三)C#访问lua的映射

XLuaTestInvokeLuaICalcBridge是继承自ICalc接口的类,它负责实现ICalc的功能,也就是我们一开始提到的一个PropertyChanged的event +=和-=操作,一个Add方法,一个Multi属性,以及下标操作符。代码逻辑很简单,就是准备调用环境,然后把C#的参数push到lua层,然后pcall调用,然后从lua栈中取出返回的结果,由于lua是弱类型的,无法事先知道返回值的类型,所以这里只能使用通用的GetObject函数对lua的返回值进行类型转换。

2023-11-18 11:52:31 1495

原创 xlua源码分析(二)lua Call C#的无wrap实现

从命名中也可看出,field对应的是C#的字段和方法,getter对应的是C#的get属性,setter对应的是set属性,meta就是对外设置的metatable了。和tolua一样,xlua也会把C#对象当作userdata来处理,每个要push到lua层的C#类型都有唯一的type_id,对应到不同的metatable,用来定义userdata的行为。并且,除了值类型和枚举类型之外,所有push到lua层的C#对象,都会在C#层缓存,这一点也是和tolua一样的,甚至缓存的数据结构也大差不差。

2023-11-04 11:16:36 1641

原创 Games 103 作业二

难度相对于作业一来说要简单一些,在文档中基本把步骤都写清楚了。这里的f就是重力和弹簧间的弹力。然后我们按照文档的步骤一步一步地来。注意0号顶点和20号顶点是不参与更新的,它们相当于就是定死了位置。作业中是固定的32次迭代次数,这里的break判断就可以省略掉;然后我们再看下PBD的实现。迭代完别忘记更新下V,这里要使用+=,因为一开始算。如果你觉得我的文章有帮助,欢迎关注我的微信公众号。第二步就是计算梯度。这里的梯度就是PPT里的。作业文档中给了近似求解的方法,我们就不用算。

2023-10-28 14:13:18 422

原创 xlua源码分析(一) C# Call lua的实现

与tolua一样,lua函数被C#引用时,都会存放到registry表中缓存,同时C#获取到该函数的reference之后,会生成一个负责生成具体委托的包装类对象,这里就是DelegateBridge,它是以弱引用的形式存放在delegate_bridges里的,这里使用弱引用,因为delegate_bridges只负责查询,真正引用它的是具体的委托。另外,xlua的DoString是有返回值的,返回类型是object数组,也就是C#可以执行任意的lua代码,并且直接获取到lua代码的返回值。

2023-10-21 13:45:04 569

原创 Step 1 搭建一个简单的渲染框架

另外,由于GPU指令的执行对CPU来说是异步的,因此还需要一个ID3D12Fence用于同步。在渲染结束之后,同样我们要把当前back buffer的状态切回渲染前的,如果是多缓冲要切到下一个可用的back buffer,执行掉中间产生的所有渲染指令,显示到屏幕上。初始化过程中我们需要创建若干back buffer和一个depth buffer,这两个buffer的创建方式有区别,但它们本质上都属于资源,因此给它们各自一个类,然后共同继承D3D12Resource这个类,这个类包含一些对资源的通用操作。

2023-10-14 17:04:31 295

原创 tolua源码分析(十一)代码生成

接下来调用的是GenRegisterFuncItems函数,这个函数就是扫描类的导出方法,过滤掉类的重载操作符方法,这部分需要特殊处理。第一部分就是普通导出类的注册过程,第二部分是对所有用到的委托类型增加导出函数,这个与上文类似,就是方便lua层直接构造委托,最后一个部分是preload,实际上是一个延迟加载逻辑,这里面的导出类,不会在Bind的时候就注册,而是在lua层调用require的时候,才会注册进来。接下来就是遍历这个BindType类型的list,对每个type生成对应的wrap文件。

2023-09-17 19:17:01 693

原创 ComPtr源码分析

可以看到,使用ComPtr之后,我们无需对A类指针p进行计数管理,ComPtr会帮我们维护好p的引用计数。当p1和p2离开作用域时,会对p的引用计数减一,当为0时触发真正的release,这里就是打印一句log。对于参数为右值引用的构造函数,根据语义,需要把传入ComPtr的原始指针进行转移。可以看到InternalRelease函数会将持有的原始指针置为空,并调用原始指针的Release函数返回当前的引用计数。这里B类型继承A类型,那么B类型的指针就可以安全地转换为A类型的指针,编译就能顺利通过了。

2023-09-10 14:20:56 276

原创 Games 103 作业一

首先,我们需要对兔子mesh中的所有顶点进行遍历,找到与墙平面发生碰撞的所有顶点,检查任意顶点是否与墙平面发生碰撞也很简单,就是计算点到平面的距离,若为负值即发生碰撞,也就是PPT中的。方法了,它的原理就是让每个顶点先分别自由地计算各自的速度和位置,然后再根据刚体的约束,对顶点进行调整,得到每个顶点最终的速度和位置。下一步,我们计算发生碰撞的点的平均值,作为。场景中有个兔子的刚体,我们要模拟的就是给兔子一个初始的速度,让其在重力的影响下,与两堵墙发生碰撞的效果。之后,就可以根据它计算出当前的冲量。

2023-08-19 12:44:07 214

原创 tolua源码分析(十)struct

向lua层传递struct,就是将C#的struct拆分成若干基本类型,依次push到lua栈,然后调用lua层对应struct的new函数,创建lua层的struct;这么做的原因是在lua层使用struct的方法时,不需要跑到C#层去调用C#的接口,对于struct来说,它往往只是一个保存数据的结构,包含的方法都比较简单,lua层去实现完全可以胜任。函数,所以这里的push实际上是把C#的Rect的每个数据成员都拆出来,作为基本类型挨个push到lua层,然后调用lua层的。

2023-07-30 15:07:27 356 1

原创 tolua源码分析(九)反射

由上文可知,typeof(Vector3)返回的是C#的Type类型,代表C#的Vector3,那么调用MakeArrayType得到的就是Vector3[]的Type类型。这里lua代码第2行也调用了全局的typeof,而这里传入的不是string,Vector3我们之前提到过是lua层自己实现的一个table,对于table类型来说,如果type缓存中不存在,则调用的是C#的。由于这里反射的函数是静态无参并且没有返回值的,因此lua层并不需要提供额外的参数给C#,也不需要从C#层获取返回值信息。

2023-07-08 16:19:00 509

原创 tolua源码分析(八)lua扩展继承C#类

之所以对所有的vptr table都设置同样一个这样的metatable,其原因是它们的逻辑是完全相同的,唯一不同就是各自绑定的userdata不同,因此只需要在metatable的元方法中,把vptr table绑定的userdata传进去就行了,没必要为每个vptr table都设置一个单独的metatable。当然,如果访问的是子类没有重写过的父类方法,则这里的判断都不会命中,会进而获取userdata的metatable,也就是前面我们所说过的class table,进行递归查找。

2023-06-17 15:55:41 455

空空如也

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除