虚幻4渲染系统结构解析

本文根据小米互娱 VR 技术专家 房燕良在 MDCC 2016 移动开发者大会上的演讲整理而成,PPT 下载地址:http://download.csdn.net/detail/sinat_14921509/9639244。

 

 

 

小米互娱 VR 技术专家 房燕良

 

房燕良,从 2001 年开始,自主研发 3 代游戏引擎,发布游戏超过 10 款。代表作品有《仙剑3》、《功夫世界》、《龙online》、《神兵传奇》等。从 2007 年开始接触虚幻引擎,对虚幻引擎有深入的研究和实践。目前就职于小米,从事 VR 方面的研发工作。

【以下为演讲实录】

大家上午好!今天,我和大家分享的主题是虚幻 4 渲染系统结构解析。 内容主要包含以下几个模块:

  • 从 3D 引擎架构的角度讲解渲染系统在架构层面所处的位置以及与其他模块之间的关系;
  • 重点讲述虚幻 4 渲染系统的架构,主要从三个方面讲解: 
    • 渲染线程跟主线程的基础架构;
    • 场景管理;
    • 渲染流程控制角度详解该架构是如何设计和实现。
  • 最后分析虚幻 4 的 VR 在引擎层实现的流程。并以谷歌 VR HMD 插件为例进行讲解。

3D 引擎渲染系统


下图相当于一个 3D 引擎与渲染系统相关的几个模块。一个是资源系统,一个是材质系统,还有场景的管理,渲染相关的就是渲染管线的管理。这几个模块在下层都会调用图形 API 实现渲染功能。整个 3D 引擎包括渲染系统最核心面临的问题主要是两个:管理复杂度和效率。

 

渲染系统架构

 

复杂度是现在整个 3D 引擎包括渲染难度系数最高的,要实现各种各样的渲染效果、渲染算法以及各种各样优化算法。

“对游戏来说,效率就是生命”。——卡马克

效率,一是从图形算法方面,可变性的判定、流程控制的优化、平衡 CPU 跟 GPU 的工作。二是软件开发者一定是关心硬件的,意味着另一个核心问题是如何高效发挥 GPU 的高并发流水线的架构以及 GPU 上各种 Cache 如何能够帮助 Driver 提高命中率。

虚幻4渲染系统架构


渲染系统模块:

  • Engine/Source/Runtime,主要存放模块的源代码;
  • 核心代码模块 
    • RenderCore
    • Renderer
  • RHI 抽象层 
    • RHI(Render Hardware Interface)
    • 虚幻 4 的版本 RHI 的设计最初是基于 D3D 11 设计的,
  • RHI 实现层,现在对于主流的平台和主流的推荐 API 都有相应的实现包括: 
    • EmptyRHI
    • Windows 上 D3D11RHI
    • 苹果上 Metal
    • OpenGLdRV、VulkanRHI

 

模块

 

接下来从数据和逻辑两个方面解析虚幻 4 渲染系统,在论及引擎的数据管理与渲染的流程控制之前,我们先理解何为渲染线程。渲染线程机制是从虚幻 3 开始引入的,当时有一个开发代号叫做 Gemini,为什么要引入渲染线程,当然主要是从效率方法考虑。一个游戏最终开发出来之后实际上有三个大的模块是占每一帧时间最多,分别是渲染、游戏逻辑包括脚本更新、以及物理模拟。因此,如果把渲染和游戏逻辑更新并行起来,就可以得到一个显著的效率提升,如下图所示。如果没有渲染线程,游戏逻辑的更新和渲染是串行的,一帧所占的时间是两块执行的总和。如果使用了渲染线程之后,一帧的时间就是两者耗时最长的那个时间,这是一个理想情况,理想情况会有一个显著的渲染提升。

 

渲染线程

 

既然多了一个重要的线程,就会涉及到两个线程之间同步的问题。线程之间同步分两方面:

1、因为游戏有运行的速率控制问题,意味着对于游戏来说,往往游戏线程负载是低一些,渲染线程是控制一些,游戏线程疯狂往前跑也没有太大的意义,所以它有一个 Render Command Fence,防止游戏线程跑得太快。好比前台我们正在看的画面,如果是第N帧,渲染线程可以渲染第 N+1 帧,游戏线程可以渲染第 N+2 帧。

2、游戏线程同步场景管理增加了渲染线程后,整个游戏的复杂度大大提升了。游戏线程要修改它的数据,渲染线程也要修改它的数据,也很麻烦,容易出错。所以在虚幻情景下,使用了一个 Proxy 对象的模式去处理它,在游戏逻辑里面处理的一个游戏对象会在渲染线程里面对应一个 Proxy 对象,该Proxy 对象的游戏更新完全在渲染线程里面做。另外在渲染线程里,因为每一帧会有特定的状态数据,这些状态数据每一帧都在变,这个其实也没有太好的办法,在每一帧的时候,要把独特的数据进行拷贝。

下图是渲染线程跟主线程的基本关系,主线程会通过渲染命令的队列往渲染线程发消息,渲染线程会从命令队列里读取命令,它们之间有一个 Render Command Fence 这样一个机制。

 

场景数据管理

 

接下来看一下虚幻引擎场景的数据管理的一些核心类。虚幻引擎场景的数据管理分了两层,一层是比较熟悉的 UWorld,主要面向游戏逻辑开发,为了在上层做逻辑控制时较为方便去管理,比较方便实现上层控制逻辑。

 

核心类

 

对于渲染来说,UWorld 对应 FScene 对象,这个数据接口的设计主要面向浏览器,由FSceneRenderer 这一类,实现了两个派生类,一个是 FForwardShadingSceneRenderer 前置渲染,还有一个 FDeferredShadingSceneRenderer 就是延迟渲染。在 model 4.0 以下的,是逻辑渲染。如果是在 Shader Model 4.0 以上会选择延迟渲染。

另外有一核心的类是 FSceneViewFamily,在这一帧可以渲染的多个 view,个人理解最早是在单机游戏多人同时玩的分屏游戏,主要是游戏机上的游戏,比如极品飞车,可以选择两个人同时玩,两个人是在同一台游戏机上玩,在屏幕上就会分两个视图,比如我的游戏视图是再上一版,你的游戏视图是在下面一版。这是分类的一个出发点。现在 VR 兴起之后,要做 VR 渲染,正好也要分屏,左眼的图象在图片左边,右眼的图象在图片右边。

另外还有一类是 FViewInfo,有一个新的 view,FViewInfo 是定义在 Render 的模块里面,在新的 view 里面又渲染了一些新的模块的特定数据,每一帧会有一些自己的状态,要进行一些拷贝,这里面有一部分数据保存在这个新 view 这一类里面。

 

静态结构

 

刚才讲了场景整体,还有单个对象的数据管理,接下来就看一下渲染的流程。这里是一个伪代码,把引擎里渲染相关的一些关键步骤提取出来,这个不是全面的,只是为了突出重点,只是一些重点步骤。

  • Game线程
void UGameEngine::Tick( float DeltaSeconds, bool bIdleMode )
{
UGameEngine::RedrawViewports()
{
void FViewport::Draw( bool bShouldPresent)
{
void UGameViewportClient::Draw()
{//-- 计算ViewFamily、View的各种属性ULocalPlayer::CalcSceneView();
//-- 发送渲染FRendererModu命le令:::BFeDgrianwRSecnedneerCionmgmVainedwFamily()
//-- Draw HUD
PlayerController->MyHUD->PostRender();
}
}}}FrameEndSynvoid FRendererModule::BeginRenderingViewFamily()
{
// render proxies update
World->SendAllEndOfFrameUpdates();// Construct the scene renderer.
// This copies the view family attributes
// into its own structures.
FSceneRenderer* SceneRenderer =
FSceneRenderer::CreateSceneRenderer(ViewFamily);ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(
FDrawSceneCommand,
FSceneRenderer*,SceneRenderer,SceneRenderer,
{
RenderViewFamily_RenderThread(RHICmdList, SceneRenderer);
FlushPendingDeleteRHIResources_RenderThread();
});
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

接下来用伪代码的方式来看一下渲染主干的流程,首先入口还是 RenderViewFamily_RenderThread() 这个函数,第一步进行 InitViews(),首先调用 Primitive Visibility Determination 进行剪裁,然后是透明物的排序,然后是灯光的可见性,然后就是不透明物体的排序。

接下来通过很多的 pass 来实现整个渲染。首先会有一个 base pass,建立一个 base 缓冲,然后通过 base pass,填充 GBuffer 的缓冲,然后是渲染所有的灯光,后面就是渲染天光,渲染大气效果,渲染透明对象,渲染屏幕区特效,所有这些渲染完之后, SceneColor() 就完成了,最后进行后处理,最后是调用 RenderFinish()。

 

延迟渲染

 

RenderLights 粗略的逻辑是,场景所有的灯光都要调用 RenderLights() 函数,在该函数里面调用两个 Shader 去画灯光在屏幕空间的影响区域。

 

场景

 

void FDeferredShadingSceneRenderer::Render()
{
bool FDeferredShadingSceneRenderer::InitViews()
{
//-- Visibility determination.
void FSceneRenderer::ComputeViewVisibility()
{
FrustumCull();
OcclusionCull();
}//-- 透明对象排序:back to front
FTranslucentPrimSet::SortPrimitives();//determine visibility of each light
DoFrustumCullForLights();//-- Base Pass对象排序:front to backvoid FDeferredShadingSceneRenderer::SortBasePassStaticData();
}
}void FDeferredShadingSceneRenderer::{ Render()
{
//-- EarlyZPass
FDeferredShadingSceneRenderer::RenderPrePass();
RenderOcclusion();//-- Build Gbuffers
SetAndClearViewGBuffer(); FDeferredShadingSceneRender::RenderBasePass();
FSceneRenderTargets::FinishRenderingGBuffer();//-- Lighting stage
RenderDynamicSkyLighting();
RenderAtmosphere();
RenderFog();
RenderTranslucency();
RenderDistortion();
//-- post processing
SceneContext.ResolveSceneColor();
FPostProcessing::Process();
FDeferredShadingSceneRenderer::RenderFinish();
}void FDeferredShadingSceneRenderer::RenderLights()
{
foreach(FLightSceneInfoCompact light IN Scene->Lights)
{
void FDeferredShadingSceneRenderer::RenderLight(Light)
{
RHICmdList.SetBlendState(Additive Blending);
// DeferredLightVertexShaders.usf VertexShader = TDeferredLightVS; // DeferredLightPixelShaders.usf PixelShader = TDeferredLightPS;
switch(Light Type)
{
case LightType_Directional:
DrawFullScreenRectangle();
case LightType_Point:
StencilingGeometry::DrawSphere();
case LightType_Spot:
StencilingGeometry::DrawCone();
}
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

虚幻 4 的 VR 渲染


接下来主要分析虚幻 4 的 VR 渲染是如何实现的。虚幻 4 的渲染或者整个引擎实现的思路跟 Unity 差距还是很大,Unity 个人理解最大的好处就是统一性做得非常好,包括 Camera 这块也是做得非常好,Camera 不光代表一个视点,而且也管理一个渲染管线。因为里面如果实现 VR 渲染,相对来说好理解一些,很直接相当于可以放两个摄像机,一个是放左眼图象,一个放右眼图象。这样的结构非常清晰,但是不太好做一些深层次的优化。在虚幻 4 引擎里面,实际上把整个 VR 整合到整个引擎各个逻辑流程,各个模块里面,所以它能够比较好实现优化。新的 VR 主要是 Scene View Family 和Scene View 为基础的。

首先看一下代码目录,在Plugins/Runtime/GoogleVR/GoogleVRHMD等等里面。

插件有两个主要类,一个就是 GoogleVRHMD,另外是 GoogleVR HMDCustomPersent,前面讲了 VR 是把流程整合到每一步的逻辑里面去,所以它会选出来一些接口。这里只列了一些重点函数,接口都挺大的,里面的函数都非常多。

 

接口

 

谷歌 VR HMD 主要实现了两个 interface,一个是 AdjustViewRect(),这一类比较简单,上述讲每一帧开始渲染的时候,会计算新 view 的一些状态和参数,相当于有一些函数在不同的时机可以参与计算或者新的 SceneViewFamily 还有 SceneView。这个比较简单,就是模块的起始、停止。

另外还有一个就是 CalculateStereoViewOffset() 接口,这个是实现立体渲染的一些核心操作,都要实现这个接口的一些方法。这两类实际上起到一个包装 VR SDK 和黏合层的作用。

接下来从代码流程来看一下 VR 渲染相关的一些步骤。首先在引擎 Init() 的时候,会查找所有 HMD 的模块,一旦启动了这个插件,它在引擎 Init()的时候,就会创建 HMDDevice,在启动的时候才会启动 VR 渲染。

  • 创建HMD Device
//-- 在引擎启动时,会创建所有的HMD设备void UEngine::Init()
{
bool UEngine::InitializeHMDDevice()
{
for (auto HMDModuleIt = HMDModules.CreateIterator();
HMDModuleIt; ++HMDModuleIt)
{
IHeadMountedDisplayModule* HMDModule = *HMDModuleIt;
HMDDevice = HMDModule->CreateHeadMountedDisplay();
}
} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

SceneViewFamily 和SceneView是如何启动VR渲染的,首先要看是不是启动立体渲染,如果是立体渲染,view会被强制设定成两个,然后会在每一帧,有一个接口,给你机会做这么几件事,一个是调整那个视口的范围,还有一个就是因为VR渲染两个摄像机的相应眼睛的位置是有一定距离的,可以去调整view的视点的距离。

  • 绘制流程入口
//-- 在View绘制时,如果是Stereo则绘制两个View
void UGameViewportClient::Draw()
{
const bool bEnableStereo =
GEngine->IsStereoscopic3D(InViewport);
int32 NumViews = bEnableStereo ? 2 : 1;
for (int32 i = 0; i < NumViews; ++i)
{
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • IStereoRendering接口调用
void UGameViewportClient::Draw() 
{
ULocalPlayer::CalcSceneView()
{ULocalPlayer::GetProjectionData() 
{
GEngine->StereoRenderingDevice->AdjustViewRect(StereoPass);
GEngine->StereoRenderingDevice->CalculateStereoViewOffset(StereoPass);
ProjectionData.ProjectionMatrix=GEngine->StereoRenderingDevice->GetStereoProjectionMatrix(StereoPass);
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

接下来看一下谷歌 VR HMD 里面插件的代码。首先通过刚才的AdjustViewRect,把viewport作一个调整,如果是左眼pass,就会调整左边的一版,如果是右边pass而就会调整右边的一版。另外通过CalculateStereoViewOffset()的方法去调整试点的位置,首先它是调用了SDK里面取得两眼同距的方法,通过计算算出眼睛view的location的偏离量。

最后是在Render,这个也是接口函数,在RenderThread里调用的一个方法,这个方法最终会调用谷歌 VR 的 API,会把普遍图象和专业图象调到VR SDK,再有它进行操作反映到手机屏幕上。

void FGoogleVRHMD::AdjustViewRect(StereoPass, int32& X,
int32& Y, uint32& SizeX, uint32& SizeY) const
{
SizeX = SizeX / 2;
if( StereoPass == eSSP_RIGHT_EYE )
X += SizeX;
}void FGoogleVRHMD::CalculateStereoViewOffset()
{
const float EyeOffset = (GetInterpupillaryDistance() * 0.5f)
* WorldToMeters;
const float PassOffset = (StereoPassType == eSSP_LEFT_EYE) ?
-EyeOffset : EyeOffset;
ViewLocation +=
ViewRotation.Quaternion().RotateVector(FVector(0,PassOffset,0
));
}void FGoogleVRHMD::RenderTexture_RenderThread()
{
gvr_distort_to_screen(GVRAPI,
SrcTexture->GetNativeResource(),
CachedDistortedRenderTextureParams,
&CachedPose,
&CachedFuturePoseTime);
}

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

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

相关文章

Windows FFMPEG开发环境配置

1.去FFMPEG网站上下载Dev版本的库&#xff0c;里面有我们需要的头文件和lib文件&#xff0c;然后下载Shared版本的库&#xff0c;里面有我们需要的dll文件 http://ffmpeg.zeranoe.com/builds/ 记得区分32位和64位的库&#xff0c;这里碰到一个大坑&#xff0c;就是我下载的是6…

Ant命令行操作

Ant命令行操作 Ant构建文件可以将项目编译&#xff0c;打包&#xff0c;測试&#xff0c;它是Apache软件基金会jakarta文件夹中的一个子项目&#xff0c;具有跨平台性&#xff0c;操作简单&#xff0c;并且非常easy上手。 关于Ant执行&#xff0c;能够在项目中找到build.xml直接…

在vlan2用计算机名访问,计算机是如何访问一个网页的?vlan间如何实现通信?

昨天我们发布了关于一文讲弄懂什么是vlan、三层交换机、网关、子网掩码&#xff0c;有很多朋友问到关于网络通信的原理&#xff0c;今天我们这一篇文章&#xff0c;算是对昨天文章进行一个补充。首先我们要访问互联网&#xff0c;必须自己电脑上面有ip地址、子网掩码、网关、dn…

使用ffmpeg将BMP图片编码为x264视频文件,将H264视频保存为BMP图片,yuv视频文件保存为图片的代码

ffmpeg开源库&#xff0c;实现将bmp格式的图片编码成x264文件&#xff0c;并将编码好的H264文件解码保存为BMP文件。 实现将视频文件yuv格式保存的图片格式的测试&#xff0c;图像格式png,jpg, gif等等测试均OK 自己根据博客的代码&#xff0c;vs2010搭建的测试环境。资源下载…

川职院单招计算机考什么专业,四川单招考什么科目

2021年高职单招升学一对一咨询小艺老师:18290437291(微信)四川单招考什么科目2019年四川单招考试科目是什么&#xff0c;四川单招考试大概在几月份&#xff1f;四川单招考试都考什么内容&#xff0c;考试会不会很难&#xff1f;不同高校四川单招时间是不同的&#xff0c;一般都…

PHP编写命令行脚本和后台运行程序的注意事项

在一些场合(如开发,测试), 可能需要使用PHP编写一些命令行的处理脚本,或者是长时间后台运行的任务, 需要注意以下准则: 准则1. 尽量避免使用PHP编写后台运行程序, 尤其是类似while(true){….} 这种循环的处理脚本. 比如,有时候我们需要定期检查数据库,然后有数据进行处理,没有数…

通过live555实现H264 RTSP直播

前面的文章中介绍了《H264视频通过RTMP流直播》&#xff0c;下面将介绍一下如何将H264实时视频通过RTSP直播。 实现思路是将视频流发送给live555, 由live555来实现H264数据流直播。 视频采集模块通过FIFO队列将H264数据帧发送给live555. live555 在收到客户端的RTSP播放请求后&…

计算机网络设置端口转发,网件NETGEAR几款路由器端口转发功能设置方法

WPN824, RP614v2&#xff0c;MR814v2&#xff0c;WGR614&#xff0c;WGT624 端口转发设置实例。(以 RP614v2 为例)1. WPN824, RP614v2&#xff0c;MR814v2&#xff0c;WGR614&#xff0c;WGT624 如何设置端口转发&#xff1f; 先登陆到设备的配置截面 在‘高级选项(Advanced)’…

移位操作符

移位操作符操作运算对象是 位(bit) 它处理的数据类型只能是 整数类型(int) 先大概看一下图,了解它是怎么移动的,下面会解释规则 移位的时候关注两点, 1.移动的方向 2.空缺位置的填补形式. "有符号"左移位操作符(<<) 将二进制数据左移(在低位补0). int i 124…

【OpenGL】详解第一个OpenGL程序

写在前面 OpenGL能做的事情太多了&#xff01;很多程序也看起来很复杂。很多人感觉OpenGL晦涩难懂&#xff0c;原因大多是被OpenGL里面各种语句搞得头大&#xff0c;一会gen一下&#xff0c;一会bind一下&#xff0c;一会又active一下。搞到最后都不知道自己在干嘛&#xff0c;…

基于 Editor.js 开发富文本编辑器库

开始 Editor.js 提供了简单而直观的用户界面&#xff0c;根据需求可以灵活添加自定义的编辑工具&#xff0c;通过插件扩展功能 Editorjs 使用 js 开发&#xff0c;脱离框架依赖&#xff0c;因此可以基于它封装富文本编辑器&#xff0c;用于 Vue 和 React 项目 editor-js-com…

dell服务器从硬盘引导,就是折腾 篇三:戴尔H710 mini(D1版本)阵列卡刷直通模式 附硬盘引导和还原IR模式办法...

就是折腾 篇三&#xff1a;戴尔H710 mini(D1版本)阵列卡刷直通模式 附硬盘引导和还原IR模式办法2021-07-24 10:00:201点赞13收藏12评论首先断开电池&#xff0c;确保阵列卡牢牢插入主板&#xff0c;没有松动。否则可能像我一样启动后识别不了raid卡。经实际测试&#xff0c;机器…

硬件服务器采购指南,硬件组装_服务器采购指南_太平洋电脑网PConline

这个机箱不支持普通大光驱&#xff0c;要用超薄光驱&#xff0c;超薄光驱是不可以直接用IDE数据线连接&#xff0c;必须用一个很小光驱转接卡&#xff0c;当然电源接口是和软驱电源接口通用的。光驱转接板这次我们采用的电源&#xff0c;也比较突出。电源是一个不能马虎的东西&…

360剑灵洪门崛起服务器维护,剑灵洪门崛起————【维护】8月1日更新维护公告...

亲爱的玩家&#xff1a;大家好&#xff01;为了更新游戏内容&#xff0c;提升游戏体验&#xff0c;7k7k《剑灵洪门崛起》将于8月1日7:00-8:00对所有服务器进行更新维护&#xff0c;维护期间无法登陆游戏&#xff0c;维护时间预计1小时。如果在维护期间无法完成维护相关事宜&…

易票365显示连接服务器失败,易票365服务器地址参数

易票365服务器地址参数 内容精选换一换查看指定VPC通道的弹性云服务器列表。您可以在API Explorer中调试该接口。GET /v2/{project_id}/apic/instances/{instance_id}/vpc-channels/{vpc_channel_id}/members状态码&#xff1a; 200状态码&#xff1a; 400状态码&#xff1a; 4…

语言统计学中的几个定律,可作为设计检索的参考

30定律&#xff1a;出现频率最高的30个词占全文本总词数的30&#xff05;如果剔除150个最高频率的词&#xff08;由于df过大被认为是停用词&#xff09;&#xff1a;倒排表记录总个数会减少25&#xff0d;30&#xff05;Zipf定律&#xff1a; 在自然语料库中所有term的freq&…

Linux makefile 教程 很具体,且易懂

近期在学习Linux下的C编程&#xff0c;买了一本叫《Linux环境下的C编程指南》读到makefile就越看越迷糊&#xff0c;可能是我的理解能不行。 于是google到了下面这篇文章。通俗易懂。然后把它贴出来&#xff0c;方便学习。 后记&#xff0c;看完发现这篇文章和《Linux环境下的C…

如何改善虚幻引擎中的游戏线程CPU性能表现

您游戏中的帧频率是不是太低&#xff1f; 您了解为什么会发生这种现象吗&#xff1f; 这是不是由于您同时生成了太多敌人&#xff1f;还是由于某个特定敌人过于消耗系统资源&#xff1f; 是由于您设置了过多的视觉特效&#xff0c;还是由于您所设计的战斗系统所造成的&#xff…

UE 光影参数

平行光的光影效果参数 天光的光影效果参数 让材质不反射光&#xff0c;也就是材质本身的颜色不起作用&#xff0c;只能使用自发光 去掉模型光影效果