您当前的位置: 首页 > 

开发游戏的老王

暂无认证

  • 2浏览

    0关注

    803博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Unreal Engine 4 使用HLSL自定义着色器(Custom Shaders)教程(下)

开发游戏的老王 发布时间:2021-03-27 19:07:11 ,浏览量:2

本文是《Unreal Engine 4 自定义着色器(Custom Shaders)教程》的下半部分,上半部分请见《Unreal Engine 4 自定义着色器(Custom Shaders)教程(上)》 作者|Tommy Tran Apr 2 2018 | 翻译 开发游戏的老王

文章目录
      • 实现高斯模糊(Gaussian Blur)
      • 创建半径参数
      • 创建全局函数
      • 创建高斯函数
      • 多像素采样
      • 局限性
        • 渲染访问
        • 引擎版本匹配
        • 优化

实现高斯模糊(Gaussian Blur)

和前面教程中的卡通描边一样,高斯模糊的实现也需要使用卷积运算。最终输出结果是一个核(kernel)中的所有像素的平均值。

在传统的矩形模糊(Box Blur)中,每个像素的权重相同,这就容易导致“横向模糊”问题;高斯模糊中 ,像素离中心的距离越远它的权重就越小,从而消除了“横向模糊”问题。这也使得中心像素更加重要了。

在这里插入图片描述

使用材质节点实现卷积运算并不是理想的方法,因为不同大小的核需要采样的像素数量不同,比如:5×5的核需要采样25个像素,而10×10的核需要采样100个像素!此时,你的节点图会看起来像一碗意大利面。

这时候自定义节点就隆重登场了。使用自定义节点,我们可以用一个for循环来实现对核中每个像素的采样。首先我们需要设定一个参数来控制采样半径。

创建半径参数

回到材质编辑器创建一个标量参数(ScalarParameter)并命名为Radius,将其默认值设为1.

在这里插入图片描述

这个Radius将决定图像的模糊程度。

接下来为Gaussian Blur创建一个新的输入引脚并命名为Radius,然后添加一个Round节点并如下图所示链接:

在这里插入图片描述 Round节点的作用是确保核的各维度永远是整数。

好!开始写代码了!因为我们需要分别从纵横两个方向进行高斯处理,所以最好把这个计算放到函数中。

当使用自定义节点时,我们无法使用标准方式定义函数。这是因为编译器会把你的代码直接复制粘贴到一个函数中。我们无法将一个函数定义到另一个函数里,否则将产生错误。

幸运的是,我们可以利用这个复制粘贴行为创建全局函数。

创建全局函数

如上文所述,编译器就是简单粗暴地把自定义节点中的文本复制粘贴到一个函数里。举个例子:我们的自定义节点是下面的内容:

return 1;

那么编译器会把它直接复制到CustomExpressionX函数中,甚至连缩进都不改一改!

MaterialFloat3 CustomExpression0(FMaterialPixelParameters Parameters)
{
return 1;
}

那么我们看一下如果我们使用下面的代码会出现什么情况

    return 1;
}

float MyGlobalVariable;

int MyGlobalFunction(int x)
{
    return x;

然后生成地HLSL代码就变成了:

MaterialFloat3 CustomExpression0(FMaterialPixelParameters Parameters)
{
    return 1;
}

float MyGlobalVariable;

int MyGlobalFunction(int x)
{
    return x;
}

如你所见,MyGlobalVariableMyGlobalFunction()不隶属于任何函数,因此它们是全局的,我们可以在任何地方调用他们。

注:输入的代码中没有最后的大括号,因为编译器会在末尾插入一个},如果你非要写个}在代码里,那么最后就会因为有两个}而报错了。

好,现在我们利用这个小技巧实现高斯函数吧。

创建高斯函数

简化版的一维高斯函数:

在这里插入图片描述

用钟形曲线来表示它,如果输入值在-1到1之间,那么输出值会在0到1之间。

在这里插入图片描述 本教程中,我们会把高斯函数放到独立的节点中,创建一个新的自定义节点并将其命名为Global。

然后,将下面的代码填到里面:

    return 1;
}

float Calculate1DGaussian(float x)
{
    return exp(-0.5 * pow(3.141 * (x), 2));

Calculate1DGaussian()就是简化版的一维高斯函数的代码实现。

为了使该函数可用,我们得在材质节点图中的某处调用Global一下才行。最简单的办法就是让Global和第一个节点相乘一下。这可以确保我们在自定义节点中调用全局函数之前,它们已经被定义了。

首先,将Global的Output Type设为CMOT Float 4,之所以这样做是因为我们得让它乘以同样是float4的SceneTexture

在这里插入图片描述 接下来,如下图所示链接各个节点:

在这里插入图片描述 点击应用编译材质。现在,任何后续节点都可以使用定义在Global的中的函数了。

多像素采样

打开Gaussian.usf使用下面的代码替换现有内容:

static const int SceneTextureId = 14;
float2 TexelSize = View.ViewSizeAndInvSize.zw;
float2 UV = GetDefaultSceneTextureUV(Parameters, SceneTextureId);
float3 PixelSum = float3(0, 0, 0);
float WeightSum = 0;

解释一下各个变量的意义:

  • SceneTextureId: 保存要抽样的场景纹理index。这样我们就不用把index写死到函数调用中了。本例中index对应为Post Process Input 0。
  • TexelSize: 保存纹理影像元件(Texel)的大小。用于转换成UV空间中的偏移量。
  • UV: 当前像素的UV坐标。
  • PixelSum: 用于累加核中每个像素的颜色值。
  • WeightSum: 用于累加核中每个像素的权重值。

接下来,我们需要创建2个for循环。一个用于垂直方向一个用于水平方向。将下方代码添加到变量列表下面:

for (int x = -Radius; x Mul by parameter > Add,将会被UE4塌陷成一个指令 这是可能的,因为所有的表达式输入(Time, parameter)对于本次draw call来说是一个常量,它们并不随像素而改变。然而UE4无法在自定义节点中折叠它们,这样就无法达到和内置节点同样的效率。 所以最佳实践就是,只有当你的已有的节点实在无法满足你的需要时再使用自定义节点。

关注
打赏
1656935939
查看更多评论
立即登录/注册

微信扫码登录

0.0380s