首页 > 其他分享 >UE 油画滤镜

UE 油画滤镜

时间:2023-05-29 20:44:34浏览次数:47  
标签:卷积 油画 range rotationMatrix meanAndVariance float4 滤镜 UE

前言

  • 非真实感渲染的风格不经相同,其中一种便是油画风格,本文总结了如何实现油画滤镜的方法
    宫崎骏动漫场景油画iPad壁纸 - tt98图片网

Kuwahara Filter

  • 为什么使用Kuwahara Filter?

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

  • 如何实现Kuwahara Filter?

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

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

    • 例子
      unreal engine paint

      对于上图的计算动图如下
      unreal engine paint

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

实现油画滤镜

  • 根节点需要选择"Material Domain"为"Post Process"
    image-20230529191334893

  • 总体实现框架
    image-20230529191315543

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

    方差计算公式unreal engine paint

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

    image-20230529191627854

  • 计算四个卷积核
    image-20230529203543123

  • 效果对比
    image-20230529191955806
    image-20230529191942119

实现方向性油画滤镜

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

  • 如何实现

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

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

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

    • 例子
      unreal engine paint

      对上图进行Sobel,得到的结果如下
      unreal engine paint
      使用atan()求角度
      unreal engine paint

  • 具体实现

    • 求角度
      image-20230529200212596

    • 修改GetKernelMeanAndVariance()

      float4 GetKernelMeanAndVariance(float2 uv, float4 range, float2x2 rotationMatrix)
      {
          //...
          float2 offset = mul(float2(x, y) * textelSize, rotationMatrix);
      
      
    • 计算旋转矩阵
      image-20230529200407948

    • 效果对比
      image-20230529191942119
      image-20230529200450006

源代码

  • 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

标签:卷积,油画,range,rotationMatrix,meanAndVariance,float4,滤镜,UE
From: https://www.cnblogs.com/chenglixue/p/17441617.html

相关文章

  • Vue3+TS后台项目笔记
    Date:2023-05-2917:56:27Author:GavinPS:不喜欢做复制粘贴,这篇笔记只是简写关键P1~12Vue3中的通信方式props父=>子传的为不可变数据自定义事件子=>父全局事件总线任意组件mitt实现v-model父<=>子写在组件标签上,实现props和自定义事件ref子=>父子组件需e......
  • 文献阅读-Inferring Networks of Diffusion and Influence
    Authors: ManuelGomez-Rodriguez, JureLeskovec, AndreasKrause AuthorsInfo&ClaimsACMTransactionsonKnowledgeDiscoveryfromDataVolume5Issue4ArticleNo.:21pp1–37https://doi.org/10.1145/2086737.2086741  Abstract......
  • 删除指定内容行与删除指定行(Power Query)
    问题:删除指定内容(丙)行 VS 删除指定行(第3行)删除指定内容(丙)行=Table.SelectRows(步骤名,each([列名]<>"丙"))删除指定行(第3行)=Table.AlternateRows(步骤名,2,1,Table.RowCount(步骤名)) ......
  • 如何通过 request 寻找指定的文件
    1.工具:notepad++2.步骤:  在发起请求时,查看请求的路径,找到对应的controller地址;在notepat++中搜索,设置对应的参数;再打开对应xml的文件,在其中就能发现该controller的class;再去工程中查找,......
  • flask Request
    flaskRequest1.Request服务器在接收到客户端的请求后,会自动创建Request对象由Flask框架创建,Request对象不可修改属性url 完整请求地址base_url 去掉GET参数的URLhost_url 只有主机和端口号的URLpath 路由中的路径method 请求方法remote_addr 请求的客户端地址a......
  • vue-cli 的配置和使用
    vue-cli介绍vue-cli是Vue.js开发的标准工具。它简化了程序员基于webpack创建工程化的Vue项目的过程。中文官网在工程化的项目中,vue要做的事情很单纯:通过main.js把App.vue渲染到index.html的指定区域中。配置和使用Vue-CLI的安装、使用及环境配置(超详细)vue项......
  • UEFI升级固件版本
    uefi可以升级bios版本,也可以升级一些扩展卡的fw版本,方便实用。 1、 开机按提示进入bios,将boot启动项中的EFIShell设置为第一启动项,保存,重启;也可以启动的时候根据提示进入启动项管理界面,选择EFIShell启动。 2、 将升级文件解压之后拷进U盘,将U盘插入机器USB口,第一步......
  • vue导航吸顶
    所说的吸顶效果就是在页面没有滑动之前,当页面向上滑动之后,导航栏始终固定在页面的上方。具体代码:写入事件监听,监听滚动条。mounted(){//事件监听滚动条window.addEventListener('scroll',this.watchScroll,true)}然后在methods中写入watchScroll方法。......
  • RollingFileAppender[FILE] - openFile(null,true) call failed. java.io.FileNotFoun
          2023-05-2916:25:31[main]ERRORo.s.boot.SpringApplication-Applicationrunfailedjava.lang.IllegalStateException:Logbackconfigurationerrordetected:ERRORinch.qos.logback.core.rolling.RollingFileAppender[FILE]-openFile(null,true)......
  • 线上环境如何开启vue devtool
    varVue,walker,node;walker=document.createTreeWalker(document.body,1);while((node=walker.nextNode())){if(node.__vue__){Vue=node.__vue__.$options._base;if(!Vue.config.devtools){Vue.config.devtools=true;if(windo......