Shader实验室:灰度化实践(一)

Number of views 316

上篇文章我们分析了图片灰度化的四种方式,这篇我们开始以实际案例来深入介绍图片灰度化的应用

介绍

上篇文章中我们已经介绍了图片灰度化实现的四种方式,现在我们继续做个简单的实验并分析该实验内部原理与算法。文章开头我们贴出了一张彩色图(如上图所示),但在彩色图中有一圆形区域,该区域内部的像素进行了灰度化处理,我们如何做到该效果?甚至可以控制该圆形区域的位置或大小?

原理

image.png

如上图所示,假设贴图位于矩形区域,在矩形区域外有一点P,点P到矩形中某顶点的距离为distance,点P自身Radius的值是自定义大小,那么只有当Radius大于distance时才能在矩形区域中进行像素处理或其它操作(可以想象点光源如果照射不到物体是无法对该物体产生影响的),也就是当$Radius>distance$才会对矩形区域中满足该条件的像素进行处理,这里的处理方式可以由用户针对不同项目中所需要的效果进行适当改变(如:可以做溶解等效果),而本篇是对满足该条件的像素颜色进行灰度化处理,那么我们就需要用到Unity ShaderLab为我们提供的lerp函数,lerp函数相当于Opengl shader中的mix函数(会在扩展篇中对比Opengl Shader中内置的函数与Unity ShaderLab中的内置函数,方便Opengl Shader开发者更快上手ShaderLab,后续也会有更多实验深入分析内置函数的内部算法并作出更多好看的效果), lerp(a,b,w)函数有三个参数a b w,它的内部的实现通过(1-w)a+wb (当w等于0时返回a,当w等于1时返回b)。

实验

打开Unity新建Unlit Shader,打开该Shader文件后,我们在Properties中新增_Radius与_Position属性,代码如下:

Properties {   
    _MainTex ("Texture", 2D) = "white" {}   
    // 点p位置,该位置是在世界空间中   
    _Position ("World Position", Vector) = (0,0,0,1)   
    // 点p所影响的半径,对应图中的Radius   
    _Radius("Radius", Range(0,100)) = 3 
}

由于_Position属性是世界空间中的坐标,所以要计算点p到模型顶点的距离,就要求模型顶点的坐标也处于世界空间中,因此我们在struct v2f 中定义了顶点在世界空间坐标的寄存器:

struct v2f {    
    float2 uv : TEXCOORD0;    
    UNITY_FOG_COORDS(1)    
    float4 vertex : SV_POSITION;    
    // 各顶点在世界空间坐标的寄存器,用于在frag函数中做distance计算    
    float4 w_p: TEXCOORD1; 
};

定义了寄存器后,需要在顶点着色器中对该寄存器进行赋值:

v2f vert (appdata v) {     
    v2f o;     
    o.vertex = UnityObjectToClipPos(v.vertex);     
    // unity_ObjectToWorld是内置的物体空间转为世界空间的矩阵     
    o.w_p = mul(unity_ObjectToWorld, v.vertex),     
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);     
    UNITY_TRANSFER_FOG(o,o.vertex);     
    return o; 
}

有了顶点在世界空间中的坐标后就可以在片元着色器中通过distance函数计算点p到各顶点的距离:

// 在Properties中定义的属性,需要相应的申明 
float4 _Position; half _Radius; 
fixed4 frag (v2f i) : SV_Target {    
    // sample the texture    
    fixed4 col = tex2D(_MainTex, i.uv);    
    // 灰度化(加权平均法)不清楚的朋友可以回头看Shader实验室:图片灰度化    
    fixed val = 0.299*col.r+0.578*col.g+0.114*col.b;    
    // 灰度化    
    fixed3 grayScale = fixed3(val, val, val);    
    // _Radius - distance的最小值为0    
    half d = saturate(_Radius - distance(_Position.xyz, i.w_p.xyz));    
    // 当Radius大于distance时,才处理灰度化,否则就为图片原来的颜色    
    col = lerp(col,fixed4(grayScale,1.0), d);    
    // apply fog    
    UNITY_APPLY_FOG(i.fogCoord, col);    
    return col; 
}

可以试着对World Position与Radius进行更改,效果如下:

如果想要背景是灰度化后的图片,圆形区域是原来图片的颜色,可对lerp函数进行更改:

col = lerp(fixed4(grayScale,1.0), col, d);

效果如下:

现在整个效果是出来了,但是如果我们需要从圆形中心点到边缘有由深到浅的过渡需要如何做到呢?这时候需要在Properties中再定义一个属性,我们暂且命名为_Softness:

_Softness("Softness", Range(0,100)) = 5

在片元着色器中新增的处理如下:

half d = saturate((_Radius - distance(_Position.xyz, i.w_p.xyz))/_Softness);

即可看到我们想要的效果:

试着调整World Position,Radius以及Softness中的值,看看其效果有什么样的变化。

本篇就到这里了,如果觉得有用,可以用微信扫描下面的二维码,关注我们!

0 Answers