URP管线下使用Dither做像素化风格

什么是Dither

基本概念

  • v. 犹豫不决;对(录音)进行噪声处理;抖色
  • n. 犹豫不决;紧张,颤抖

 

 

用有限的颜色来表示出多种色阶变化的技术就叫做Dither

上面那句话其实并不严谨,只是在此方便理解,Dither不是新型技术,它在视频,信号处理,音频等领域都有应用

图形中的应用

基于上述的简单且并不严谨的描述,下图是在一个2x2的像素格中,从左到右依次排列出一个黑白色阶的效果

对应黑白色阶

没看出来?我们将像素格缩小平铺

这下就对味了!

现在我们对Dither在图形上的应用已经有了初步的了解,并且我们可以看得到,2x2的像素格只有5种颜色过渡,那么4x4呢?

  • 2x2像素格有5种颜色
  • 4x4像素格有17种颜色
  • 8x8像素格有65种颜色

有序抖动与无序抖动

这里我们不涉及更深的底层逻辑,直观一些,直接看图

这张图展示了不同算法呈现出的结果,其中下面

这张,就是有序Dither的效果,也是我们下面要实现的效果(有点像素画的感觉哈)

Dither的实现

这里我们的主要目标是先绘制一个2x2像素格的矩阵,然后再想办法将这些像素格填充平铺到屏幕中去

有了目标,下面我们开干!

像素格矩阵

首先我们想要绘制下面这样一个2x2的像素格,对应的矩阵应该是什么样的?

显而易见

\begin{pmatrix}0&1\\1&0\\ \end{pmatrix}

平铺到屏幕中

方法其实很简单,只需要将上面那个2x2矩阵作为一个贴图一样目标,再用0,1,0,1,0,1不断重复的坐标值采样它就行了

说起来有点抽象,首先我们先看采样它的值从哪来

i.positionCS.xy

没错,就是它,我们知道裁剪空间下的坐标值,从顶点着色器输出到片段着色器后,它的值就是屏幕上每一个像素的值

且值非常大,不是简单的0-1,而且我们的矩阵只能取到0和1,即0行0列、0行1列.....所以必须将这些值映射为0和1,只需要除以2取余即可

直接输出 i.position.y - 400

所以我们就是用它的值,来采样2x2像素矩阵(与其说是采样,不如说是2x2像素矩阵的下标)

下面我们用代码实现一下

[4]先声明一个4x4的矩阵,我们知道这种排列方式是一个中灰色

[10]声明uint2二维整数类型,再将屏幕坐标值除以2取余

[12]通过屏幕坐标的x轴与y轴,查找矩阵的值


            half4 frag(Varyings i) : SV_TARGET 
            {    
                float2x2 M2x2 = float2x2
                (
                0,1,
                1,0
                );

                uint2 uv = (uint2)i.positionCS.xy%2;

                return M2x2[uv.x][uv.y];
            }

 

最终效果

放大看

效果出现了,我们还可以改变矩阵的排列方式,呈现出不同的颜色状态

  half4 frag(Varyings i) : SV_TARGET 
            {    
                float2x2 M2x2 = float2x2
                (
                0,1,
                0,0
                );

                uint2 uv = (uint2)i.positionCS.xy%2;
                return M2x2[uv.x][uv.y];
            }

黑色会更多

优化代码实现

上面我们已经知道了核心代码如何实现,因为还需要用到不同的矩阵数量以及排列方式(颜色),为了方便,我们直接创建自己的方法便于使用

 

 			// 2x2矩阵
            half Dither2x2_Matrix(uint2 uv )
            {
                uv %= 2;
                float2x2 M2x2 = float2x2
                (
                0,1,
                0,0
                );
                return M2x2[uv.x][uv.y];
            }


            half4 frag(Varyings i) : SV_TARGET 
            {    

                uint2 uv_gray = (uint2)i.positionCS.xy;
                half4 c = Dither2x2_Matrix(uv_gray);
                return c;
                return 1;
            }

趁热再写一个4x4的

  // 4x4矩阵
            half Dither4x4_Matrix(uint2 uv )
            {
                uv %= 4;
                float4x4 M4x4 = float4x4
                (
                1,0,0,0,
                0,1,0,0,
                0,0,1,0,
                0,0,0,1
                );
                return M4x4[uv.x][uv.y];
            }
 

			half4 frag(Varyings i) : SV_TARGET 
            {    

                uint2 uv = (uint2)i.positionCS.xy;
                half4 c1 = Dither4x4_Matrix(uv);
                return c1;
            }

4x4矩阵采样的效果

这时候我们发现,我们写的4x4矩阵明明是从左往右的斜线呀,为什么显示出来后,是相反的?

主要是因为y轴,屏幕的坐标是以左下角为(0,0)点(OpenGL),但逐行采样矩阵时,却是从左上角开始的,所以就导致了这种情况发生

矩阵转化为数组

像素格元素不但可以用矩阵表示,也可以声明数组,不同的是,矩阵我们可以指定行与列,数组如何指定?

我们只需要用另一种算法作为数组的下标

 例如3x3的矩阵

\begin{pmatrix}0&1&2\\3&4&5\\6&7&8\\ \end{pmatrix}

我们可以指定其几行几列,例如2行1列(从0开始),是7

数组

如果用上面那个算法呢? 

注意,这里的值仅仅是它们的下标,或者索引值,数组或矩阵内部的值当然可以所以更改

但它们所指定的某一个数是一致的

它们的写法有所不同

 

   			// 4x4数组
            half Dither4x4_Array(uint2 uv )
            {
                uv %= 4;
                float A4x4[16]=
                {
                    1,0,0,0,
                    0,1,0,0,
                    0,0,1,0,
                    0,0,0,1
                };
                
                return A4x4[uv.x*4+uv.y];
            }



            half4 frag(Varyings i) : SV_TARGET 
            {    

                uint2 uv = (uint2)i.positionCS.xy;

                half4 c1 = Dither4x4_Array(uv);
                return c1;
            }

Dither有序抖动

什么是有序抖动?

其实就是利用这样的矩阵,但看起来很乱不是吗?

它有一个规律,就是每两个数字之间都会相差较大,仔细看,还真是这样

下面我们就模仿这个矩阵的写法,创建一个出来

 // 8x8数组
            half Dither8x8_Array(uint2 uv )
            {
                uv %= 8;
                float A4x4[64]=
                {
                    0,32,8,40,2,34,10,42,
                    48,16,56,24,50,18,58,26,
                    12,44,4,36,14,46,6,38,
                    60,28,52,20,62,30,54,22,
                    3,35,11,43,1,33,9,41,
                    51,19,59,27,49,17,57,25,
                    15,47,7,39,13,45,5,37,
                    63,31,55,23,61,29,53,21
                };
                
                return A4x4[uv.x*8+uv.y];
            }

 

等等

数组里这些数值,除了0以外,其他的超过1的不都是白色?

别担心,我们可以用除法将他们映射到0-1区间,除以64看看

// 8x8数组
            half Dither8x8_Array(uint2 uv )
            {
                uv %= 8;
                float A4x4[64]=
                {
                    0,32,8,40,2,34,10,42,
                    48,16,56,24,50,18,58,26,
                    12,44,4,36,14,46,6,38,
                    60,28,52,20,62,30,54,22,
                    3,35,11,43,1,33,9,41,
                    51,19,59,27,49,17,57,25,
                    15,47,7,39,13,45,5,37,
                    63,31,55,23,61,29,53,21
                };
                
                return A4x4[uv.x*8+uv.y]/64;
            }



            half4 frag(Varyings i) : SV_TARGET 
            {    

                uint2 uv = (uint2)i.positionCS.xy;
                half4 c = Dither2x2_Matrix(uv);
                return c2;
            }

这样我们就获得颜色不均的有序Dither效果了

有序抖动Dither的应用

有啥用??????

先给出答案:我们可以配合Clip方法用它来做半透明效果,也就是通过AlphaTest达到半透明效果

相信你已经知道改怎么写了

 			// 8x8数组
            half Dither8x8_Array(uint2 uv )
            {
                uv %= 8;
                float A4x4[64]=
                {
                    0,32,8,40,2,34,10,42,
                    48,16,56,24,50,18,58,26,
                    12,44,4,36,14,46,6,38,
                    60,28,52,20,62,30,54,22,
                    3,35,11,43,1,33,9,41,
                    51,19,59,27,49,17,57,25,
                    15,47,7,39,13,45,5,37,
                    63,31,55,23,61,29,53,21
                };
                
                return A4x4[uv.x*8+uv.y]/64;
            }



            half4 frag(Varyings i) : SV_TARGET 
            {    

                uint2 uv = (uint2)i.positionCS.xy;
                half4 c = Dither2x2_Matrix(uv);
                clip(c2 - _Clip);
                return c2;
            }

这样我想到了小米透明电视。。。

放大看看

嘿!像不像MineCraft的玻璃!

 

像素风格

有序Dither不但可以作为廉价的半透明实现,最明显的作用就是将图像变成像素风格

实现原理

将目标图像(贴图,或者光照等)也传入Dither矩阵的方法中去,并将目标图像的数值与Dither矩阵中的数值做对比(Step/SmoothStep),然后输出即可

代码实现

[18]使用Step方法将有序Dither 与贴图颜色进行对比

[27]将RGB颜色转换为灰度的方法

 // 8x8数组
            half Dither8x8_Array(uint2 uv , float color)
            {
                uv %= 8;
                float A4x4[64]=
                {
                    0,32,8,40,2,34,10,42,
                    48,16,56,24,50,18,58,26,
                    12,44,4,36,14,46,6,38,
                    60,28,52,20,62,30,54,22,
                    3,35,11,43,1,33,9,41,
                    51,19,59,27,49,17,57,25,
                    15,47,7,39,13,45,5,37,
                    63,31,55,23,61,29,53,21
                };
                
                half pixel = A4x4[uv.x*8+uv.y]/64;
                return step(pixel,color);
            }



            half4 frag(Varyings i) : SV_TARGET 
            {    

                half4 mainTex = SAMPLE_TEXTURE2D(_MainTex,smp,i.uv);
                half maintexGray = Luminance(mainTex.rgb);
                uint2 uv = (uint2)i.positionCS.xy;
                half4 c2 = Dither8x8_Array(uv,maintexGray);
                return c2;
            }

有点意思了

但是现在只是黑白的,毕竟受该方法的限制,我们不能一次性传入RGB进行对比

但是我们可以对每个通道比较最后再合成到一起!

 

 half4 frag(Varyings i) : SV_TARGET 
            {    

                half4 mainTex = SAMPLE_TEXTURE2D(_MainTex,smp,i.uv);
                uint2 uv = (uint2)i.positionCS.xy;

                half c2 = Dither8x8_Array(uv,mainTex.r);
                half c3 = Dither8x8_Array(uv,mainTex.g);
                half c4 = Dither8x8_Array(uv,mainTex.b);
                half4 c = half4(c2,c3,c4,1);

                return c;
            }

效果一般,真正使用的话,还是需要再做修改

Houdini生成Dither纹理

使用Houdini可以将不同类型的Dither矩阵纹理填入同一张贴图的通道中

这里稍复杂,但节点不多,首先是B通道中填充8x8有序DIther

注意grid节点,需要先把网格设置为8x8的大小,这样每个面都为1个单位,并且x,z的位置也设定好,使得左上角为(0,0)点

Wrangle中的代码

注意Run Over设置为面,我们要取每个面的数值,并使他们为从0到63的排列方式,作为有序Dither矩阵的索引

最后一行输出到b通道

maps_baker节点

输出后的贴图B通道

 

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Unity中使用Universal Render Pipeline(URP管线,请按照以下步骤操作: 1. 创建一个新项目或打开现有项目。 2. 在Unity的Package Manager中,找到Universal RP并安装它。 3. 在Project视图中创建一个新的URP Asset资源。此资源包含了所有URP管线的设置,包括渲染设置、光照设置和材质设置。 4. 在Project视图中创建一个新的Material资源。将其渲染模式设置为使用URP管线,并将其属性调整为您所需的外观。 5. 在Scene视图中创建一个新的GameObject并将其添加到场景中。将该GameObject的Renderer组件的材质设置为您刚刚创建的Material资源。 6. 在Project视图中创建一个新的Light资源。将其类型设置为Directional,并将其属性调整为您所需的光照效果。 7. 在Scene视图中将该Light资源添加到场景中。 8. 在Project视图中创建一个新的Camera资源。将其属性调整为您所需的摄像机设置。 9. 在Scene视图中将该Camera资源添加到场景中。 10. 在Project视图中创建一个新的Post-processing Profile资源。此资源包含了所有后期处理效果的设置。 11. 在Scene视图中将该Post-processing Profile资源添加到您的Camera组件的Post-processing Settings中。 12. 在Unity的菜单栏中选择Window > Rendering > Lighting Settings。在其中,将Scene视图中的Environment Lighting属性设置为使用Skybox,并将其Skybox属性设置为您所需的天空盒材质。 13. 最后,按下Play按钮,您将看到使用URP管线的场景开始渲染。 这些步骤将为您提供使用URP管线的基础知识。根据您的需求,您可以使用URP管线的其他功能和设置来进一步优您的场景。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值