Niagara_Advanced内容示例 1.3 Communicate with External Render Targets

在这里插入图片描述

粒子效果

场景中有一个纹理和一个模型(应用了Unreal Engine 4的logo),但是没有Niagara粒子的标识符。这次展示出来的是一个Actor蓝图类,里面包含了两个组件,UNiagaraComponent和StaticMeshComponent,分别对应了我们看见的纹理和模型。

在这里插入图片描述

Actor蓝图类的构造脚本(Construction Script)里,首先创建Render Target 2D(256*256),然后将这个返回值设置为Niagara中一个变量(Variable)的引用,这个变量名字是TextureRenderTargetObject;随后为我们的StaticMesh(是一个平面)创建动态材质实例,最后再以Render Target 2D的值作为材质实例中纹理参数的值。

在这里插入图片描述

这里执行的操作其实就是将Render Target 2D作为外部变量传入Niagara系统(之前都是在Niagara内部创建Render Target 2D,而这里是要让Niagara内部创建的Render Target 2D引用外部创建的这个),在Niagara系统里对它进行一顿操作,再把用一个材质把操作过后的Render Target 2D在静态网格体上显示出来。

我们打开引用的Niagara System来看看内部发生了什么。

Niagara蓝图部分

可以看到,发射器部分和之前1.2中的例子在结构上非常相似,都是用Emitter级别的属性进行渲染。在Simulation Stage里,亦是三个阶段:Fill Grid with Texture,Blur Grid,Fill Render Target with Grid。其中Blur Grid是对纹理(Gird中的数据)的模糊化处理。

在这里插入图片描述

效果实现分析

前面说过,Actor蓝图类在Niagara外部创建了一个Render Target 2D(256*256),并将返回值传入Niagara系统中的变量TextureRenderTargetObject。这个变量是我们自己创建的,位于USER命名空间下的一个只读的属性,所以我们才需要用一个Render Target 2D作为接口,从而可编辑其中的值。需要注意的是,Niagara里创建的这个Render Target 2D尺寸是512,与传入的尺寸不同,Niagara会自动做这个适应性的调整(即以512为准)。

在这里插入图片描述

Fill Grid with Texture,用纹理数据填充Grid2D Collection。值得注意的是,这里的Emitter Reset Only勾选,意味着这个阶段只会在每次Emitter重置之后才会运行,也就不会让这里的采样结果在后续干扰模糊操作的显示。

在这里插入图片描述

Execution Index to Unit构建UV,乘以1.5将范围拉伸到01.5(原先采1的跑去采1.5了,原先采0.66的跑来采1了),减去0.25范围下降为-0.251.25(最原先采1到跑去采1.25了,最原先采0.83的跑来采1了),再Clamp一下取到0~1(掐头去尾可以吃了,最原先采0.83的跑去采1了,最远先采0.16的跑来采0了),这样相当于对纹理进行了缩小(可以理解成一张图片,画框大小不变,你把它从中心缩小,从中心用画框截取)。之后再作一个灰度处理,得到两个float值(一个代表RGB,一个代表A,即透明度值),存储在该Grid2D Collection的属性中(次级属性,命名空间是STACKCONTENT)。

在这里插入图片描述

未缩小的画和缩小的画(画框大小不变)。

在这里插入图片描述

在这里插入图片描述

Blur Grid,是对纹理进行的模糊处理。我们先看看这段HLSL的代码各个输入:Blurred Grid即是Grid2D Collection,TickCount是系统的帧计数,Unit是用来描述当前正在处理Grid2D Collection上的哪一个Cell,Index X和Index Y分别是当前整个Cell的横纵Index,dxdy是横向宽度和纵向长度的倒数,StageIterations代表了当前所处的迭代数。

在这里插入图片描述

细看HLSL代码片段,我尽可能得添加了注释(中文部分)

//每次迭代进来要重新初始化这两个值
OutputUnitGrid =  float2(0,0);
OutputBlurGrid =  float2(0,0);

#if GPU_SIMULATION

//一些局部变量(作用域就是这个片段)
float2 Offsets = float2(0.5, -0.5);
StageIterations += 1;//
float2 Outputs[4];
float2 Taps[4];
float2 CornerTap;

//是对前一帧的值进行采样,即取到Grid2D Collection前一帧的FloatPlusAlpha属性值,怎么采呢?根据Unit传入的位置信息,最后输出到OutputUnitGrid
Grid2DCollection.SampleGridVector2Value<Attribute=FloatPlusAlpha>(Unit,  OutputUnitGrid);

//就是(1/512,1/512) * StageIterations
// For each iteration, reach a little further out to neighboring texels
CornerTap = dxdy * StageIterations;

//随着迭代层数的增加,StageIterations越来越大,这四个位置的Taps值也会越来越大(意味着越来越深/长/距离采样点越远)

//左上角,(1/512,1/512) * StageIterations * (-0.5,-0.5)
Taps[0] = CornerTap * Offsets.yy;
//右上角,(1/512,1/512) * StageIterations * (0.5,-0.5)
Taps[1] = CornerTap * Offsets.xy;
//左下角,(1/512,1/512) * StageIterations * (-0.5,0.5)
Taps[2] = CornerTap * Offsets.yx;
//右下角,(1/512,1/512) * StageIterations * (0.5,0.5)
Taps[3] = CornerTap * Offsets.xx;

// Sample our four corners, average the samples.
//向Grid2D Collection中正在进行的采样点的四个边角进行采样(FloatPlusAlpha),得到的值输出到Output里
Grid2DCollection.SampleGridVector2Value<Attribute=FloatPlusAlpha>(Unit+Taps[0],  Outputs[0]);
Grid2DCollection.SampleGridVector2Value<Attribute=FloatPlusAlpha>(Unit+Taps[1],  Outputs[1]);
Grid2DCollection.SampleGridVector2Value<Attribute=FloatPlusAlpha>(Unit+Taps[2],  Outputs[2]);
Grid2DCollection.SampleGridVector2Value<Attribute=FloatPlusAlpha>(Unit+Taps[3],  Outputs[3]);

//取四个边角采样值的平均值,输出到OutputBlurGrid里
OutputBlurGrid += (Outputs[0]+Outputs[1]+Outputs[2]+Outputs[3]) / 4;
if (TickCount <= 1)
{//TickCount是系统的计时器,
}
else
{
OutputBlurGrid = OutputUnitGrid;
}

//如果是从点下Save/Apply那一刻起的第一帧,以OutputBlurGrid(总共走8个循环后)设置FloatPlusAlpha属性;后面的每帧输出OutputUnitGrid(对第八帧的FloatPlusAlpha采样,是经历了8层模糊循环后的结果),以保持一个恒定的模糊度。
//这也就是为什么Fill Grid with Texture要勾选Emitter Reset Only,否则如果Fill Grid with Texture每帧更新的话,会刷掉已近写过的FloatPlusAlpha值,就意味着要输出未被模糊的结果
Grid2DCollection.SetVector2Value<Attribute=FloatPlusAlpha>(IndexX, IndexY,  OutputBlurGrid);

#endif

Fill Render Target with Grid,将Grid2D Collection的数据写入到Render Target 2D里。

在这里插入图片描述

最后再通过材质参数绑定写入到材质纹理中(这样就得到了最开始那副效果图中左侧的那个纹理(实际上Niagara Emitter级的Sprite粒子))。

在这里插入图片描述

而右侧的那个StaticMesh上的纹理则是通过读到其上的材质纹理参数,再用Render Target对这个参数进行赋值。还记得开局Actor蓝图中的那两个节点吗(下图)?此时蓝图中创建的Render Target已经经过了Niagara System里的一些列计算并赋了模糊化后的值,这些值又要传入NiagaraRT这个参数里。

在这里插入图片描述

材质中的这个参数的B值决定了这个材质的Emissive Color和顶点的偏移值,所以才会有凸起的效果。

在这里插入图片描述

总结

本例中涉及的知识点相当得驳杂,当时我还是尽力将其全部梳理了出来(蓝图和Niagara数据传递,材质,模糊卷积算法(不算是完全的卷积),系统数据更新等等)。其中甚至一些点我之前都没有了解过,比如说我在TickCount的作用上纠结了比较久。最后还是多个概念的综合理解、结合蓝图和代码的上下文才将流程梳理通畅。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Claude的羽毛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值