写在前面
既然想要实现云的消散效果,那么边缘光如何计算也是一个重点。
在Unity Shader入门精要的14章,介绍轮廓线渲染就介绍了——轮廓边检测,而边缘光也是需要先检测出轮廓边再进行的。
Unity3D Shader系列之边缘光RimLight
这篇博客给了我很大的启发,引言部分说了为什么花了很长时间把渲染管线、坐标变换这些基础的东西学清楚了,想要实现某种效果的时候还是要重新学习——欠缺算法知识。感觉还是要去主动想实现某种东西再有针对性的学习,这样学习得才更加深刻。
...有些扯远了,这篇文章简简单单实现边缘光,为之后实现云消散做铺垫,当然,卡通渲染边缘光还有很多讲究,包括描边也有很多需要学习的,之后再涉及。
1 简单的算法介绍
判断边缘
边缘光跟轮廓线渲染异曲同工,其核心都是找到边缘,而找边缘的算法原理很简单:当视线看向物体边缘的时候,视线与边缘的顶点法线是垂直的(很好理解吧!),那么我们依靠视线方向和模型面片法线的点积就能实现边缘判断,所以一切的核心最终凝炼成了:
// 点积
dot(worldNormal, worldViewDir);
判断是边缘后,叠一层边缘光颜色就行。
2 shader中实现
写过一点点shader的或许都有这样一种感觉:有时候明明道理很简单,但在frag里实现时又需要添加一些范围限定的函数(pow/saturate那些),才能得到最终的效果,这里也不例外:
// 1.点积
dot(worldNormal, worldViewDir);
// 2.(1 - 点积结果)&&(加个saturate控制到0,1之间)
saturate(1 - dot(worldNormal, worldViewDir));
叠加必要的_RimColor和_RimIntensity,这样的话在shader中就是:
fixed4 rimLight = _RimColor * saturate(1 - dot(worldNormal, worldViewDir));
Shader中代码:
Shader "Unlit/rim"
{
Properties
{
_BaseColor ("Base Color", Color) = (0,0,0,0)
_RimColor ("Rim Color", Color) = (0,0,0,0)
_RimPower ("Rim Power", Range(0.0001, 5.0)) = 1.0
_RimIntensity ("Rim Intensity", Range(0, 100.0)) = 1.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD1;
float3 worldViewDir : TEXCOORD2;
};
fixed4 _BaseColor;
fixed4 _RimColor;
float _RimIntensity;
float _RimPower;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldViewDir = WorldSpaceViewDir(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldViewDir = normalize(i.worldViewDir);
fixed4 diffuse = _BaseColor;
fixed4 rimLight = _RimColor * saturate(1 - dot(worldNormal, worldViewDir)) * _RimIntensity;
//fixed4 rimLight = _RimColor * pow(saturate(1 - dot(worldNormal, worldViewDir)), 1.0 / _RimPower) * _RimIntensity;
fixed3 col = diffuse.rgb + rimLight.rgb;
return fixed4(col,1.0);
}
ENDCG
}
}
}
效果:
衰减效果
原本的效果其实是没有什么衰减而言的,但是边缘光其实应该有个过渡效果,用上pow()就能实现啦。
算上pow:
fixed4 rimLight = _RimColor * pow(saturate(1 - dot(worldNormal, worldViewDir)), 1.0 / _RimPower) * _RimIntensity;
效果:
【Cel-Shading】边缘光的实现 | Invictus maneo (x-wflo.github.io) 中有说遇到的问题