实现思路
大致思路就是在屏幕空间对亮度值进行采样映射,将亮度值离散化为若干个区域,之后对应成不同的颜色,之后还需要再进行一遍处理,让画面有一种呈现在特殊漫画纸上的效果,比如网点纸。
有一个要点就是亮度值如何计算,我们都知道漫反射光照模型可以得出亮度值,有一些非真实感渲染就是使用这个值来进行离散化处理的。
如Unity shader卡通化着色器的实现
但是我们这里需要注意的是屏幕空间对亮度值获取和用漫反射光照模型获取亮度值,两者出来的效果是完全不同的。
根据漫反射光照模型获取某个顶点的亮度值,是不考虑贴在这个模型上的纹理的(但是我们知道有些模型上面贴的纹理本身就隐含了亮度值)。而且也不考虑阴影之类的效果。
而在屏幕空间进行这种处理,会考虑进纹理本身的亮度值,因此最后出来的效果并不会像漫反射模型出来的那么干净,反而会有很多’杂点’,当然这两者效果并没有优劣之分,只是看你需要什么样的效果。
代码实现
具体的实现都在片元着色器中,顶点着色器只是做了一个顶点转换到裁剪空间下的基本操作。
我们首先用Luminance函数提取像素的亮度。
(注意Luminance取亮度函数不是简单的求颜色向量的模,每个颜色分量的权值是不同的,这涉及到人眼对不同颜色亮度感知的不同,建议直接用Luminance来获取一个颜色向量对应的亮度)
half lum = Luminance(tex2D(_MainTex, i.uv).rgb);
接下来判断这个亮度值落在哪一个区域,我们这个shader设置了三个亮度区域,这个判断我们没有使用if来做,虽然shader里是可以执行if语句的,但是为了效率起见,最好还是不要用。
我们用一个小技巧就可以用step函数来得到和if语句相同的效果了。
首先判断lum是不是小于_StripLimitsMin,用一次step函数即可,如果低于则underMin设为1。
step函数可以这样理解,左边的参数是不是小于右边的参数,是的话返回1,不然返回0。
half underMin = step(lum, _StripLimitsMin);
接着要判断lum是不是满足_StripLimitsMin<lum<_StripLimitsMax
这里使用两个step函数来判断。
注意参数的位置。
这里使用乘法其实是模拟了&&运算符,两个step都返回1的时候最终才是1,否则为0,大家可以自己体会这种用法。
half betweenLimit = step(_StripLimitsMin, lum) * step(lum, _StripLimitsMax);
然后我们就把亮度分为了三个区域,分别对应上不同的颜色就可以了。同样我们这里不使用if语句,而是使用两次lerp函数。
half3 color = lerp( _FillColor,lerp(_BackgroundColor, _StripInnerColor, betweenLimit), underMin);
然后我们就可以得到类似这样的效果了。
当然还需要添加一些效果,这个shader既然叫漫画纸张效果,那么就来模拟一些特殊的纸张效果好了。一些漫画可能用的是特殊的网点纸,我们也可以模拟网点纸效果。(至于什么是网点纸大家可以百度去看看效果)
我们这里选择只在中间色上添加网点纸效果。添加一个strip_color函数,并修改之前的lerp函数。
half3 color = lerp(lerp(_BackgroundColor, strip_color(i.uv), betweenLimit), _FillColor, underMin);
网点效果的实现很容易,我们只要想到网点的出现消失是一个周期的效果,因此只要用一个周期的函数,没错就是sin或者cos函数,之后调整输入参数和输出的范围就可以了。
我们这里直接简单粗暴的将uv传入到sin函数里相加,需要说明的是这个公式并不是什么经过精心推导之后得出的公式,因此不用去深究它的意义。只是因为我们需要一个周期效果,所以需要一个周期函数,然后加几个调整参数,调整起来看起来差不多就行了。
我们这里采用的是分别将uv的x,y传入sin函数并相乘,此时就可以得到根据xy变化的周期效果了。然后因为sin函数是一个连续函数,而格点的出现消失是离散化的,因此还要使用step函数进行离散化。
half3 strip_color(half2 uv)
{
fixed passStep=step(_StripThickness,sin(uv.x*_StripDensity)*sin(uv.y*_StripDensity));
return lerp(_StripInnerColor, _StripOuterColor, passStep);
}
加了格点之后的效果
也可以调整参数得到这种效果