立即注册
登录
搜索
前端开发
后端开发
虚幻引擎
U3D引擎
体感研发
数据库
论坛
BBS
本版
帖子
用户
麒麟软控
»
论坛
›
麒麟软控
›
虚幻引擎
›
虚幻引擎 自定义VertexFactory(二)
返回列表
发新帖
虚幻引擎 自定义VertexFactory(二)
敬東
敬東
当前离线
积分
4
1
主题
3
帖子
4
积分
新手上路
新手上路, 积分 4, 距离下一级还需 46 积分
新手上路, 积分 4, 距离下一级还需 46 积分
积分
4
发消息
发表于 2022-12-3 19:22:57
|
显示全部楼层
引言
在上一篇文章中,我介绍了如何自定义一个最简单的VertexFactory。在这篇文章中,我们将拓展这个VertexFactory来实现阴影、法线、光照、材质的绘制。文章代码基于4.26.2。
一、阴影
我们先来看阴影的绘制。在虚幻引擎中搜索ShadowV(ertexShader),我们发现只有三个usf文件。
我们需要找的是ShadowDepthVertexShader。我们先来看ShadowDepthVertexShader的Main函数的参数。
void PositionOnlyMain(
in FPositionAndNormalOnlyVertexFactoryInput Input,
out FShadowDepthVSToPS OutParameters,
out float4 OutPosition : SV_POSITION
)
{
ResolvedView = ResolveView();
float4 WorldPos = VertexFactoryGetWorldPosition(Input);
float3 WorldNormal = VertexFactoryGetWorldNormal(Input);
float ShadowDepth;
SetShadowDepthOutputs(
ShadowDepthPass_ProjectionMatrix ,
ShadowDepthPass_ViewMatrix ,
WorldPos,
WorldNormal,
OutPosition,
ShadowDepth
);
OutParameters.Dummy = 0;
}
那么这个里面我们发现需要一个FPositionAndNormalOnlyVertexFactoryInput。同样之前定义的函数VertexFactoryGetWorldPosition和VertexFactoryGetWorldNormal也需要一个这个struct做参数的版本。同样在算SceneDepth的时候,在PositionOnlyDepthVertexShader中,我们也需要一个FPositionOnlyVertexFactoryInput。
void Main(
FPositionOnlyVertexFactoryInput Input,
out float4 OutPosition : SV_POSITION
)
{
ResolvedView = ResolveView();
float4 WorldPos = VertexFactoryGetWorldPosition(Input);
{
OutPosition = mul(WorldPos, ResolvedView.TranslatedWorldToClip)
}
}
在虚幻引擎中,除去默认的顶点定义,还有两种特殊的顶点定义:PositionOnly和PositionAndNormalOnly。这两种都是给刚才我们看到的渲染Depth的Vertex Shader使用的。
VertexFactory.cpp
我们需要在Shader端实现这几个结构体和对应的函数,并在C++端开启渲染阴影的开关即可。
Shader端实现
在之前写好的Shader文件中,我们添加需要的结构体。和上次写法一样,只是在FPositionAndNormalOnlyVertexFactoryInput中我们需要加一个Normal Attribute。和LocalVertexFactory一样,我们将这个Normal放在Attribute2。
我们同时也需要重载VertexFactoryGetWorldPosition。
重载VertexFactoryGetWorldNormal。
这样我们在Shader文件端的修改就写完了。但是我们现在直接运行还是看不到阴影,是因为C++端没有走阴影pass。
C++实现
在C++端,我们还需要设置一些值让阴影pass生效。首先,我们需要将ViewRelevance改成阴影Relevant。
接着,我们需要将GetDynamicMeshElements的MeshBatchElement改成可以投射阴影。
具体含义可以看MeshBatch.h里对应每一个项的解释。其中我们看到CastShadow表示是否可以在Shadow RenderPass里使用。上面的注释也解释了这些flag的作用,例如Depth-Only pass可以无视UV的差异,将顶点根据Position的信息合并到一起,因为深度Only只关心位置。那么Proxy通过这些Flags就可以快速地只提交需要的信息。
MeshBatch.h
Implement顶点工厂的宏,我们也需要支持PositionOnly。最后一个boolean是bSupportPositionOnly,我们在我们的cpp文件将最后一个变量设置成true即可。
这样我们就添加了阴影pass的渲染。打开虚幻引擎查看效果,可以看到我们的Cube已经投射了阴影。
二、法线
为了下面光照的效果,我们先添加法线信息。法线信息我们跟Position一样,通过顶点buffer输入进来。这里我们参考虚幻引擎StaticMesh的顶点法线Buffer来处理。
LocalVertexFactory.ush
以图中所示为例,我们可以看到TangentX和TangentZ是虚幻从FVertexFactoryInput里传过来的。那么在StaticMesh的VertexBuffer里这些数据是怎么存放的呢?我们看上面VertexFetch_PackedTangentsBuffer中关于Instance的代码,就会发现这个Tangent数据是从同一个Buffer中Offset出来的。我们再截帧验证一下我们的想法。渲染一个Cube并截帧,我们可以看到Vertex Shader阶段使用的Buffer为VertexFetch_PackedTangentsBuffer,根据代码,偶数Index为X,奇数Index为Z。
那我们这里根据LocalVertexFactory的实现把这些函数抄过来,基本就可以了。
Shader端实现
首先我们的Input要添加两个Attribute,TangentX和TangentZ,分别放在Attribute1和Attribute2即可。
然后我们在FVertexFactoryInterpolantsVSToPS中,添加两个作为输出的float4,来作为PixelShader的输出。
在FVertexFactoryIntermediates中我们添加上几个需要在VertexFactory中进行传递的矩阵,和一个符号位。
添加一个CalcTangentToLocal用于计算TangentToLocal矩阵。计算过程如下,其中我们跟虚幻引擎一致,最后重新计算一下TangentX,用来消除误差。
添加如下功能函数,计算TangentToWorld。
GetVertexFactoryIntermediates中我们调用刚才的函数,给对应的Intermediate参数赋值。这里我们顺道把Color设置成白色,为了之后更好的和官方的结果作对比。
GetMaterialPixelParameters中,同样我们给Result中对应的参数赋值。其中AssembleTangentToWorld这个函数是虚幻引擎MaterialTemplate中定义好的函数。
GetMaterialVertexParameters中也需要给Result赋值。
给FVertexFactoryInterpolantsVSToPS中的Tangent相关参数赋值。
最后将这两个函数修改成对应的参数输出即可,之前我们使用的是一些临时数据。
C++端
我们在C++端添加对应的数据来让shader中计算生效。回到我们的FCustomVertexFactory类,加一个Public成员变量TangentVertexBuffer。
然后我们在UpdateStaticMesh的时候,设置一下这个buffer的值。
在顶点工厂的InitRHI中,我们Bind一下Buffer。注意我们刚才观察从截帧中观察TangentVertexBuffer的结构,TangentX是从0开始,8为stride,即跳8个值是下一个值的开头。同理TangentZ是从4开始,也是8为stride进行读取。
然后我们在顶点定义里添加上新的TangentX和TangentZ。
我们设置之后好Mesh之后截帧,拿一个官方的Cube做对比,可以看到Buffer的信息在计算出来之后结果是一模一样的,证明我们的Normal计算正确了。
上:我们计算的,下:虚幻官方的
如果我们想在场景里看到计算生效,我们就需要在Component里添加一个lit的材质。我是在Component初始化的时候直接赋值一个DefaultMaterial。
现在我们在场景里打开法线Buffer观察,我们可以看到法线已经正确计算。
三、光照
这里我为了简单,就只实现了动态光照,而动态光照不需要单独计算LightMap和ShadowMap坐标,所以Shader端不需要改动。
C++端
在虚幻引擎中,Mesh和场景灯光交互的类型是通过一个枚举来定义的。
SceneManagement.h
而描述灯光和场景交互的信息则是一个LightInteraction类里的静态函数来完成的。
SceneManagement.h
我们先在FCustomPrimitiveSceneProxy里实现一个嵌套的FCustomPrimitiveLCI,这个类继承自FLightCacheInterface。
为了让光照生效,我们要实现GetLightRelevance这个函数。这个其实和GetViewRelevance是一个意思,即获取和每种光照有关系的数据。
我们在FCustomPrimitiveSceneProxy中再加一个Private成员变量用于访问里面的函数和变量。
在cpp文件中,GetInteraction中我们这样实现。
再定义一下GetLightRelevance,从代码中我们可以看出,每个flag对应的都是一种光照的计算方式。
GetDynamicMeshElements中,我们将刚才创建的ComponentLightInfo赋值到MeshBatch.LCI上。
最后我们在构造函数里,将这个component初始化一下。
我们注意到刚才在.h文件中有一个FMeshMapBuildData类。
我们观察之后发现,这个类里存储的就是一些用于构建LightMap和ShadowMap的一些数据。
那么我们需要在UCustomPrimitiveComponent类中也创建一个对应的FMeshMapBuildData。回到我们的Component类中,我们新创建如下几个成员。
我们再override一下PostInitProperties。我们需要在这个函数里来创建我们的Guid,否则这个Guid会在序列化阶段被覆盖。
我们定义一下GetMeshMapBuildData,这段代码我都是从Landscape.cpp中抄过来的,里面的内容也是很明了,就是针对不同情况来创建我们的BuildData供光照函数使用。(题外话,这也证明学习引擎最好的途径就是源代码,多尝试,多抄,就能理解。)
在引擎中,我们拖拽一盏点光源,注意我们需要将光源的类型改成Movable。
可以看到我们的cube和光源正确地交互了。
四、材质
目前我们还是用的default material,现在我们来修改代码,让我们的cube可以使用我们指定的材质。我们还需要添加一套uv坐标,从而可以让贴图生效。
Manual Vertex Fetch
这里我们使用Manual Vertex Fetch的方式,来获取uv坐标。第一是因为虚幻引擎也是通过这种方式来获取相关信息,我们之后支持Instancing渲染模式;第二是因为这种方式相对原来的通过硬件Input获取数据更自由。
C++端
这部分我们先来看C++端的实现,先定义一下我们的UniformBuffer的结构,我们需要1.一个用于读取Buffer的一些Index信息,2.一个Buffer用于我们存储TexCoord。
在我们的VertexFactory类里添加一个UniformBuffer。
添加Get,Set函数。
最后我们创建一个继承自FVertexFactoryShaderParameters的类。这个类是顶点工厂用来进行参数绑定的接口,可以看到我们在GetElementShaderBindings这个函数里面,我们将UniformBuffer进行一个Shader的参数绑定。
在.cpp文件中,我们在开头先通过宏来Implement一下我们新添加的两个类。第一个是UniformBuffer的结构。后面的名称"CustomVF",是用于在Shader里面访问该UniformBuffer的名称。
然后我们Implement一下VertexFactoryShaderParameter类。
在CreateRenderThreadResources的时候,先创建一个FCustomVertexFactoryParameters对象,然后将对象中对应的buffer进行初始化。我们可以看到Parmeters里面有四个参数,我们这次主要使用NumTexCoords。然后我们将LOD[0]的TexCoordsSRV绑到UniformBuffers对应的TexCoordBuffer上。而当StaticMesh为空,即无法读取这些数据的时候,我们需要给定一个GNullColorVertexBuffer。最后我们调用SetParameters来赋值即可。
最后我们定义一下SetParameters。至此C++端的定义就完成了,接下来我们在Shader端将对应的数据交给PixelShader就可以了。
Shader端
我们先在开头定义一下几个变量,用于访问VertexFetch_Parameter中的每一项。
这里的NUM_TEX_COORD_INTERPOLATORS是在MaterialTemplate.ush里定义的。在虚幻引擎中,当我们使用材质编辑器进行材质Shader的编辑之后,编译出来的hlsl文件,就是基于MaterialTemplate再加上编译器编译出来的。
FVertexFactoryInterpolantsVSToPS里我们定义TexCoords。
当我们的材质编辑器里有INTERPOLATORS的时候,我们就定义获取材质UV坐标的函数。
GetMaterialPixelParameters函数中,我们也需要获取TexCoord给FMaterialPixelParameters。
GetMaterialVertexParameters也需要做相应的改动,从而可以给Result的UV坐标赋值。
最后我们修改VertexFactoryGetInterpolantsVSToPS函数,给Intepolants赋值。
这样我们就可以在虚幻引擎中获取到我们Mesh上的UV坐标了。我们再修改Component,让我们可以通过面板修改渲染用的材质。
GetUsedMaterials里我们将这个Material返回即可。
这样我们就可以使用我们自己的材质了。在虚幻引擎中创建一个带材质的物体,例如我这里使用Houdini插件里的一个cube来做实验。
可以看到我们的材质和UV坐标都可以成功读取了。
这里我创建了一个材质输出uv到颜色,验证了uv坐标也是正确的。
五、结语
至此我们已经成功将一个StaticMesh通过我们自己的VertexFactory渲染到屏幕上了,接下来我们将继续拓展这个VertexFactory,进行Instancing渲染,最后我们将使用DrawIndirect来进行GPU Instancing的绘制流程。
上一篇:
法拉利用实时技术向元界Metaverse发起挑战
下一篇:
初探 Cesium for Unreal
回复
举报
使用道具
分享
小燕燕
小燕燕
当前离线
积分
5
1
主题
2
帖子
5
积分
新手上路
新手上路, 积分 5, 距离下一级还需 45 积分
新手上路, 积分 5, 距离下一级还需 45 积分
积分
5
发消息
发表于 2025-2-27 14:51:37
|
显示全部楼层
纯粹路过,没任何兴趣,仅仅是看在老用户份上回复一下
回复
举报
使用道具
返回列表
发新帖
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
|
立即注册
快速回复
返回顶部
返回列表