This commit is contained in:
2025-08-02 12:09:34 +08:00
commit e70b01cdca
2785 changed files with 575579 additions and 0 deletions

Binary file not shown.

BIN
03-UnrealEngine/VisualEffect/FlowMap/水流效果.mp4 (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -0,0 +1,102 @@
---
title: Niagara Module笔记
date: 2024-02-20 11:14:46
excerpt:
tags:
rating: ⭐
---
# 前言
- UE4 Niagara源码解析:https://zhuanlan.zhihu.com/p/362638250
不论Niagara Emitter有多少的ModuleModule里有多少的脚本脚本中写了多少东西。我们当然要抓住本质数据其都是更新某个渲染器所需要的必要参数也就是如下图所示的信息。
![](https://pic4.zhimg.com/80/v2-46e1c558d019994e189f44cb96bd3e3b_720w.webp)
也就是说只有这些信息是每个渲染器所需要的其会传递给渲染线程并最终提交渲染。任何其他参数仅仅为中间变量。因此我们首先关心的是这个数据是存储在什么地方的。由于一个Emitter可以拥有多个不同的Renderer。因此其本身存储在Emitter级别而不是在Renderer级别。
因此我们最终传递给渲染线程的数据存储在每个Emitter实例FNiagaraEmitterInstance的FNiagaraDataSet身上。不论是GPU粒子还是CPU粒子。
![](https://pic3.zhimg.com/80/v2-6ec8d1bc1de249f2c872f9439b5782d6_720w.webp)
# Module 变量类型
- InputModule输入的变量。
- Local单帧内存在的变量。
- Particle粒子级别**持久存在**的变量。
- Emitter发射器级别**持久存在**的变量。
- Engine引擎提供的只读变量。
# 粒子属性读取
在Module中在ParticleAttributeReader中可以读取对应的例子属性。引用的数据的方式有两种粒子的ID或者Index索引。但是难就难在最初的我不太了解这两个是啥。大致知道是某种编号。ID的话倒是了解一点但不知道它结果分为Index 和Acquire Tag组合在一起的。
PS.**Niagara Debugger**工具可以用于检查粒子ID以及对应变量的变化情况。
# 其他
1. **发射器固定粒子ID**:勾选发射器上**RequiresPersistentIDs**选项即可固定。
2. ExportParticleDataToBlueprint可以输出CPU或者GPU变量
1. 在该节点上添加蓝图变量名。之后在蓝图的BeginPlay()设置这个变量为该蓝图类。之后实现ReceiveParticleData接口。
2. GPU 粒子数据导出是通过从 GPU 内存回读的方式进行的。这需要不可预知的非固定帧数,通常为 1-2 帧。
3. GPU 回读性能受限于回读缓冲区的大小。建议使用最小值的 GPU 固定大小分配来捕捉特定帧中可能发生的所有事件。任何超过固定大小的事件都将无法发送。
3. Window - GeneratedCode可以看到生成出的HLSL代码。
#
Particle的Module都是c++级别写死的。基本位于`Engine\Source\Runtime\Engine\Classes\Particles`目录。基类为**UParticleModule**Niagara的Module都是蓝图资产引擎自带的都在 `Engine\Plugins\FX\Niagara\Content\Modules`。基类为**UNiagaraScript**。
- UNiagaraScriptBase定义了一些编译相关的接口。ModifyCompilationEnvironment()、ShouldCompile()以及获取模拟元数据接口GetSimulationStageMetaData()。
- UNiagaraScript排除掉Editor相关函数主要的函数为
- IsXXXScript()系列判断函数。
- 相关接口PreSave、Serialize、PostLoad基类接口实现。
- FNiagaraVMExecutableData相关
- 编译相关。
- 其他。
## Niagara里面的相关类型
### DataInterface
基类为UNiagaraDataInterface。
- UNiagaraDataInterfaceTextureNiagaraDataInterfaceTextureTemplate.ush
- LoadTexture2D()
- GatherRedTexture2D()
- SampleTexture2D()
- SamplePseudoVolumeTexture()
- GetTextureDimensions()
- GetNumMipLevels()
- UNiagaraDataInterface2DArrayTextureNiagaraDataInterfaceTexture2DArrayTemplate.ush
- LoadTexture()
- GatherRedTexture()
- SampleTexture()
- TextureDimension()
- UNiagaraDataInterfaceVirtualTextureNiagaraDataInterfaceVirtualTextureTemplate.ush
- GetAttributesValid()
- SampleRVTLayer()
- SampleRVT()
## Texture相关Module
- Textures
- SampleTexture
- SamplePseudoVolumeTexture
- SubUV_TextureSample
- WorldAlignedTextureSample
- SubUV
- SubUVAnimation
- V2
- SubUVAnimation
里面的一些节点调用一些函数,这些函数都在对应的**UNiagaraDataInterface**中的**GetFunctions()** 定义,具体的逻辑位于 对应的**xxxTemplate.ush**
## 生成的代码
高斯3D里Niagara采用PositionTexture生成的相关代码
```c++
int2 Emitter_SampleTexture_Texture_TextureSize;
int Emitter_SampleTexture_Texture_MipLevels;
Texture2D Emitter_SampleTexture_Texture_Texture;
SamplerState Emitter_SampleTexture_Texture_TextureSampler;
void SampleTexture2D_Emitter_SampleTexture_Texture(in float2 UV, in float MipLevel, out float4 OutValue)
{
OutValue = Emitter_SampleTexture_Texture_Texture.SampleLevel(Emitter_SampleTexture_Texture_TextureSampler, UV, MipLevel);
}
void SampleTexture_Emitter_Func_(inout FSimulationContext Context)
{
float4 SampleTexture2D_Emitter_SampleTexture_TextureOutput_Value;
SampleTexture2D_Emitter_SampleTexture_Texture(Context.MapSpawn.SampleTexture.UV, Constants_Emitter_SampleTexture_MipLevel, SampleTexture2D_Emitter_SampleTexture_TextureOutput_Value);
Context.MapSpawn.OUTPUT_VAR.SampleTexture.SampledColor = SampleTexture2D_Emitter_SampleTexture_TextureOutput_Value;
Context.MapSpawn.OUTPUT_VAR.SampleTexture.SamplerUV = Context.MapSpawn.SampleTexture.UV;
Context.MapSpawn.Particles.SampleTexture.SampledColor = SampleTexture2D_Emitter_SampleTexture_TextureOutput_Value;
Context.MapSpawn.Particles.SampleTexture.SamplerUV = Context.MapSpawn.SampleTexture.UV;
}
```

View File

@@ -0,0 +1,114 @@
## Emitter Properties-Deteminsim
勾选之后,重复播放的粒子都会是相同(随机值不会起作用)。可以用在制作阶段的效果预览,但需要在制作完后去掉勾选。
## 查看粒子各项数值与Debug技巧
在Niagara编辑器中的Window选项卡中开启Attribute Spreadsheet。即可对粒子数据进行捕获并查看。
你可以在给粒子设置Debug材质的方式来进行指定粒子的查看。具体的方式为通过NiagaraBindings传递参数之后材质中使用Debug节点进行显示。
### 按下键盘上的Pause键可以暂停
同时再按F8可以自由转换视角或者在编辑器中点击Eject结束对Character的控制以此切换为自由观察视角来进行观察。
### 在play或者Simulate下按下;开启DebugCamera装下
在这个状态下有着各种调试功能。还可以检查场景剔除情况。
## Niagara系统的数据传递
https://zhuanlan.zhihu.com/p/74796515
1. 从外界把数据传进来<br>
主要通过ParameterCollection或者直接对NiagaraSystem进行数据设置。这个数据需要加到Niagara变量中的User标签下
2. Emitter内部数据传递<br>
本质上是使用MapGet和MapSet通过名字来找到数据的。
3. NiagaraModuleScript之间的数据传递<br>
4. Emitter之间的数据传递<br>
在ParticleUpdate里添加事件。因为需要跟踪粒子Id所以需要勾选RequiresPersistentIds<br>
在需要监听事件的Emitter中的EventHandler中添加监听事件即可。
## 调节场景中的Niagara粒子播放速度
命令行输入Slomo [倍率]即可调节播放速度例如输入Slomo 0.1,就可以慢动作观察粒子播放了。
## 使用CurveAsset与CurveAtlasAsset将渐变信息导入材质中
CurveAtlas可以用于制作渐变贴图它支持的渐变条数与TextureSize有关一行一条渐变。之后再材质编辑器是使用CurveAtlasRowParameter节点即可读取渐变信息。
给CurveTime输入节点连上上UV节点的R通道即可观察渐变效果。如果把黑白渐变图连上那结果就会被染色成渐变效果。
CurveAtlas除了可以调整颜色值还可以通过调整Alpha值曲线来调整边缘效果。
另一种思路就是使用材质编辑器中的Gradient系列节点。
## 让特效具有立体感
粒子模型使用穿插面片:
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/Niagara学习笔记_让粒子具有立体感.png)
使用Fresnal或者DotProduct节点记得使用abs与黑白Ramp贴图相乘以此来减弱接近平行的面片渲染效果。记得勾选双面渲染
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/Niagara学习笔记_让粒子具有立体感_材质部分.png)
## Ribbon与Beam
Beam也是使用Ribbon进行渲染的。差别在于Ribbon是一个一个发射粒子之后再渲染飘带模型。 Beam是一下就生成完所有粒子的之后再渲染飘带模型。
### 设置步骤
1. 在EmitterUpdate中添加BeamEmitterUpdate
2. 在ParticleSpawn中添加SpawnBeam
3. 在Render中添加RibbonRenderer
记得勾选AbsoluteBeamEnd将本地坐标转化为世界坐标。
### 调整宽度
在ParticleSpawn中添加Initialize Ribbon
### 控制Ribbon UV
1. 在UV0Tiling Distance设置数值
2. 在材质编辑器中的对UV进行求余数运算。
个人认为或许使用WorldPosition应该可以把。
## 另一种给材质传递传递数据的方法
对一个StaticMesh调用SetCustom Primtive Data XXX。可以是float、Vec2、vec3、vec4之后在材质编辑器中新建一个变量节点之后在变量节点的Details-CustomPrimitiveData选项卡中勾选UseCustomPrimitveData。
它的消耗比Dynamic Material Instance少的多而且不同的Component都是相互独立的不像材质集是全局的。
## 性能调试
Shift+Ctrl+,开启GPU Visualizer
限制其基类必须是Primtive。
## 直接控制SubImageIndex变量
通过修改SubImageIndex变量的方式来直接控制SubMaterial渲染结果。
### 使用随机值进行采样
可以在Niagara里设置一个变量在使用SetParameter节点对这个变量赋予随机值之后使用这个随机值变量对曲线进行采样。将这个值赋予CurveIndex
### 碰撞
碰撞类型需要与粒子类型相同GPU与CPU。同时使用的粒子材质需要是Translucent使用Mask会影响场景深度使得Depth不正确从而导致模拟出问题。
下雨逻辑GPU判定碰撞产生事件计算碰撞时间与法线之后生成水花SubImage
If you open the Material function "Imposter_ThreeFrameBlend" you will have to go to each "World to Loc" node and edit the shader code by replacing Primitive by GetPrimitiveData(Parameters.PrimitiveId).
In the material editor go to Window and then "Find results"
There you can search for "Custom" this will look for every custom node with custom code. Cycle through every result until you find a World to Loc node.
Then you can proceed to edit the code. There are a total of 5 nodes which you need to edit.
The edited code for me looks like this :
```c++
#if USE_INSTANCING || IS_MESHPARTICLE_FACTORY
return mul(InWorldVector, transpose(Parameters.InstanceLocalToWorld));
#else
return mul(InWorldVector, (MaterialFloat3x3)GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal);
#endif
```
And like this :
```c++
#if USE_INSTANCING || IS_MESHPARTICLE_FACTORY
float3 temp;
temp.x = length(TransformLocalVectorToWorld(Parameters, float3(1,0,0)));
temp.y = length(TransformLocalVectorToWorld(Parameters, float3(0,1,0)));
temp.z = length(TransformLocalVectorToWorld(Parameters, float3(0,0,1)));
return mul(InWorldVector, (MaterialFloat3x3)transpose(Parameters.InstanceLocalToWorld)) / (temp*temp);
#else
return mul(InWorldVector, (MaterialFloat3x3)GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal);
#endif
```
---
- https://answers.unrealengine.com/questions/882887/index.html
- https://docs.unrealengine.com/en-US/Programming/Rendering/MeshDrawingPipeline/4_22_ConversionGuide/index.html
primitive replace by GetPrimitiveData(Parameters.PrimitiveId)
```c++
#if USE_INSTANCING || IS_MESHPARTICLE_FACTORY
return mul(InWorldVector, transpose(Parameters.InstanceLocalToWorld));
#else
return mul(InWorldVector, (MaterialFloat3x3)Primitive.WorldToLocal);
#endif
```