大家好,我是阿赵。
继续介绍屏幕后处理效果的做法。这次介绍一下用偏导数求图形边缘的技术。
一、原理介绍
先来看例子吧。
这个例子看起来好像是要给模型描边。之前其实也介绍过很多描边的方法,比如沿着法线方向放大模型,或者用NdotV来求边缘,之类。
不过这篇文章所说的内容,其实和模型描边是没有关系的。因为这是屏幕后处理,他针对的并不是模型,所以也不会有法线方向,有观察空间的计算。用偏导数,求的是一张图片的颜色变化。
简单来说,我们要求的是连续像素点之间的颜色变化。
听起来好像很复杂,不过由于已经提供了现成的方法,所以我们直接用就行了。方法就是ddx和ddy。
ddx是求横向像素之间的变化的,可以理解成是当前像素点和横向前一个像素点颜色的变化。
ddy就是纵向像素之间的变化了。
通过ddx和ddy,我们可以求出一张图片颜色变化比较强烈的一些边缘位置。
当求出了这些范围之后,我们可以给他填充不同的颜色,也可以指定背景色,发挥想象力之后,就可以做出一些有趣的效果了。
二、代码实现
1、C#代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OutlineCtrl : MonoBehaviour
{
private Material outlineMat;
public float lineStrength = 1;
public Color baseColor = Color.white;
public Color lineColor = Color.black;
public float powVal = 1;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if(outlineMat == null)
{
outlineMat = new Material(Shader.Find("Hidden/azhaoOutline"));
}
outlineMat.SetFloat("_lineStrength", lineStrength);
outlineMat.SetColor("_baseColor", baseColor);
outlineMat.SetColor("_lineColor", lineColor);
outlineMat.SetFloat("_powVal", powVal);
Graphics.Blit(source, destination,outlineMat);
}
}
2、Shader
Shader "Hidden/azhaoOutline"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_lineStrength("LineStrength",Float) = 1
_lineColor("LineColor",Color) = (0,0,0,1)
_baseColor("baseColor", Color) = (1,1,1,0)
_powVal("powVal",Float) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _lineStrength;
float4 _lineColor;
float3 _baseColor;
float _powVal;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
half4 frag (v2f i) : SV_Target
{
// sample the texture
half4 col = tex2D(_MainTex, i.uv);
float grayscale = col.r * 0.2126729f + col.g * 0.7151522f + col.b * 0.0721750f;
grayscale = pow(grayscale, _powVal);
float ddVal = saturate(ddx(grayscale) + ddy(grayscale))*_lineStrength;
half3 finalCol = _baseColor.rgb * (1.0 - ddVal) + _lineColor.rgb * ddVal;
return half4(finalCol, 1);
}
ENDCG
}
}
}
三、和原图的叠加
稍微做的一点点扩展,之前描绘出来的是纯背景色和线条色,其实我们也不一定要用纯背景色的,比如把偏导数得到的结果,和原图做叠加,就可以做出类似模型描边的效果。
代码很简单,修改一下shader的片段着色器程序就可以:
half4 frag (v2f i) : SV_Target
{
// sample the texture
half4 col = tex2D(_MainTex, i.uv);
float grayscale = col.r * 0.2126729f + col.g * 0.7151522f + col.b * 0.0721750f;
grayscale = pow(grayscale, _powVal);
float ddVal = saturate(ddx(grayscale) + ddy(grayscale))*_lineStrength;
half3 finalCol = col.rgb*(1 - ddVal) + _lineColor.rgb * ddVal;
return half4(finalCol, 1);
}
可以看出,描边的效果其实没有使用法线计算那么干净清晰。这是因为偏导数依赖于颜色的变化,越分明的变化,结果是越清晰,然后图片的分辨率如果不够大,得出的效果也会比较的模糊。
不过由于它并不依赖于其他数据,只要有颜色,就能计算,所以在屏幕后处理上,就刚好可以做出一些特殊的效果了。