当前位置: 首页 >  在线学习 >  UE 油画滤镜

UE 油画滤镜

导读:前言.非真实感渲染的风格不经相同,其中一种便是油画风格,本文总结了如何实现油画滤镜的方法.Kuwahara Filter.为什么使用Kuwahara Filter?.一般对图像进行模糊处理,会使用低通滤波器,但往往模糊后图像会失去它们的硬边,但Kuwahara Filter可以在

前言

  • 非真实感渲染的风格不经相同,其中一种便是油画风格,本文总结了如何实现油画滤镜的方法

Kuwahara Filter

  • 为什么使用Kuwahara Filter?

一般对图像进行模糊处理,会使用低通滤波器,但往往模糊后图像会失去它们的硬边,但Kuwahara Filter可以在平滑图像的同时也能保留其硬边
unreal engine
paint

  • 如何实现Kuwahara Filter?

    • Kuwahara Filter也是使用卷积,但不同之处是Kuwahara Filter需要四个卷积核

    • 过程:计算每个卷积核的平均值(平滑噪点)和方差(衡量一个内核的颜色变化率),一共四个。找出方差最小的卷积核并输出其平均值

    • 例子

对于上图的计算动图如下

右边的颜色变化率太大了,不会选它,这里选择的是最左边的卷积核,因为它的颜色最均匀,最后输出它的平均值

实现油画滤镜

  • 根节点需要选择”Material Domain”为”Post Process”

  • 总体实现框架

  • “Global”自定义节点计算平均值和方差

方差计算公式

    int32 SceneTextureLookup
(
    int32 ViewportUV,		// 纹理坐标
    uint32 SceneTextureId,	// 节点sceneTexture中的Scene Texture Id索引值
    bool bFiltered	//是否使用双线性插值
)

  • 计算四个卷积核

  • 效果对比

实现方向性油画滤镜

  • 为什么需要方向性油画滤镜
    从上图可以看出该滤镜某些地方有点奇怪,过于方正,而方向性油画滤镜可以解决这个问题

  • 如何实现

    • 方向性油画滤镜和之前的差别在于它的卷积核和像素的局部朝向相同

    • 计算局部朝向的方法是Sobel

Sobel需要两个卷积核,Gx提供水平方向的梯度信息,Gy提供垂直方向的梯度信息。使用这两个卷积核分别对像素做一次卷积,再使用atan()求角度,随后以该角度对卷积核进行旋转

* 例子  

对上图进行Sobel,得到的结果如下

使用atan()求角度

  • 具体实现

    • 求角度

    • 修改GetKernelMeanAndVariance()

          float4 GetKernelMeanAndVariance(float2 uv, float4 range, float2x2 rotationMatrix)
      

      { //… float2 offset = mul(float2(x, y) * textelSize, rotationMatrix);

    • 计算旋转矩阵

    • 效果对比

源代码

  • global

    float4 GetKernelMeanAndVariance(float2 uv, float4 range, float2x2 rotationMatrix)
    

    { float2 textelSize = View.ViewSizeAndInvSize.zw; //纹素大小 const int ppInput0 = 14; //对应SceneTexture的节点索引值 float3 mean = 0; //平均值 float3 variance = 0; //方差 int sampleNums = 0; //采样次数

    for(int x = range.x; x <= range.y; ++x)
    {
        for(int y = range.z; y <= range.w; ++y)
        {
            float2 offset = mul(float2(x, y) * textelSize, rotationMatrix);
            float3 pixelColor = SceneTextureLookup(uv + offset, ppInput0, false).rgb;
    
    
            mean += pixelColor;
            variance = pixelColor * pixelColor;
            sampleNums++;
        }
    }
    mean /= sampleNums;
    variance = variance / sampleNums - mean * mean;
    
    
    float totalVariance = variance.r + variance.g + variance.b;
    return float4(mean.r, mean.g, mean.b, totalVariance);
    

    }

    // 求角度 float4 GetAngle(float2 uv) { float2 textelSize = View.ViewSizeAndInvSize.zw; //纹素大小 const int ppInput0 = 14; //对应SceneTexture的节点索引值

    float gradientX = 0.f;  // 水平方向的梯度值
    float gradientY = 0.f;  // 竖直方向的梯度值
    float sobelX[9] = {-1, -2, -1, 0, 0, 0, 1, 2, 1};   // 水平方向的卷积核
    float sobelY[9] = {-1, 0, 1, -2, 0, 2, -1, 0, 1};   // 垂直方向的卷积核
    int i = 0;  //访问sobel的索引
    
    
    for(int x = -1; x <= 1; ++x)
    {
        for(int y = -1; y <= 1; ++y)
        {
            float2 offset = float2(x, y) * textelSize;
            float3 pixelColor = SceneTextureLookup(uv + offset, ppInput0, false).rgb;
            float pixelValue = dot(pixelColor, float3(0.3,0.59,0.11));  // 转化为灰度值。用于将图像看作一个整体来计算梯度,比计算单个颜色值的梯度快
    
    
            // 计算梯度值
            gradientX += pixelValue * sobelX[i];
            gradientY += pixelValue * sobelY[i];
            i++;
        }
    }
    
    
    return atan(gradientY / gradientX);
    
  • Kuwahara

    const int ppInput0 = 14;
    

    float2 uv = GetDefaultSceneTextureUV(Parameters, ppInput0); //目标像素点 float4 range; //卷积核范围.xy表示x的范围,zw表示y的范围 float4 meanAndVariance[4]; //算得的平均值和方差

    float angle = GetAngle(uv); float2x2 rotationMatrix = float2x2(cos(angle), -sin(angle), sin(angle), cos(angle));

    // 计算四个卷积核 range = float4(-RadiusX, 0, -RadiusY, 0); meanAndVariance[0] = GetKernelMeanAndVariance(uv, range, rotationMatrix);

    range = float4(-RadiusX, 0, 0, RadiusY); meanAndVariance[1] = GetKernelMeanAndVariance(uv, range, rotationMatrix);

    range = float4(0, RadiusX, 0, RadiusY); meanAndVariance[2] = GetKernelMeanAndVariance(uv, range, rotationMatrix);

    range = float4(0, RadiusX, -RadiusY, 0); meanAndVariance[3] = GetKernelMeanAndVariance(uv, range, rotationMatrix);

    // 求方差最小值的颜色 float3 finalColor = meanAndVariance[0].rgb; float minVariance = meanAndVariance[0].a;

    for(int i = 1; i < 4; ++i) { if(minVariance > meanAndVariance[i].a) { minVariance = meanAndVariance[i].a; finalColor = meanAndVariance[i].rgb; } }

    return finalColor;

reference

UE4卡通渲染基础教程 Part4:Paint Filter - 知乎 (zhihu.com)

Unreal Engine 4 Paint Filter Tutorial | Kodeco

内容
  • Unity3D学习记录03——Navigation智能导航地图烘焙
    Unity3D学习记录03——N
    2023-12-01
    首先还是在Package Manager中安装AI Navigation.接着选择我们场景的地面,右键,找到AI的Nav
  • UE5 材质 运动的扭曲效果
    UE5 材质 运动的扭曲效果
    2023-12-06
    前言.本篇使用UE5的材质系统实现运动的扭曲效果,并解决他的重复性.纹理变换.总结思路.为uv坐标添加time节点.实现
  • 可落地的DDD(7)-战术设计上的一些误区
    可落地的DDD(7)-战术设计上
    2023-12-01
    背景.几年前我总结过DDD战术设计的一些落地经验可落地的DDD(5)-战术设计,和一次关于聚合根的激烈讨论最近两年有些新
  • 在MacOS下使用Unity3D开发游戏
    在MacOS下使用Unity3D
    2023-12-03
    第一次发博客,先发一下我的游戏开发环境吧。.去年2月份买了一台MacBookPro2021 M1pro(以下简称mbp)
  • 驱动开发:内核封装WFP防火墙入门
    驱动开发:内核封装WFP防火墙入
    2023-12-02
    WFP框架是微软推出来替代TDIHOOK传输层驱动接口网络通信的方案,其默认被设计为分层结构,该框架分别提供了用户态与内
  • MySQL面试题全解析:准备面试所需的关键知识点和实战经验
    MySQL面试题全解析:准备面试
    2023-12-03
    MySQL有哪几种数据存储引擎?有什么区别?.MySQL支持多种数据存储引擎,其中最常见的是MyISAM和InnoDB引