Unity3d shader之卡通着色Toon Shading
卡通着色的目的是为了让被着色物体显得过渡的不那么好,明暗交界线很明显,等等卡通风格的一系列特征,
也叫Non-photorealisticrendering非真实渲染
重点要做到两点:
1. 描边
2. 着色
另:本片中cg函数均用绿色标明,想了解函数作用和函数内部构成请看这篇文章NVIDIA CG语言 函数之所有数学类函数(Mathematical Functions)
就从最初的描边开始
首先声明变量
_Outline挤出描边的粗细
_Factor挤出多远
Properties {
_Color("Main Color",color)=(1,1,1,1)
_Outline("Thick of Outline",range(0,0.1))=0.02
_Factor("Factor",range(0,1))=0.5
}
我们的挤出操作在第一个pass中进行
Cull Front 裁剪了物体的前面(对着相机的),把背面挤出
ZWrite On 像素的深度写入深度缓冲,如果关闭的话,物体与物体交叠处将不会被描边,因为此处无z值后渲染的物体会把此处挤出的描边“盖住”
在处理顶点的函数vert中把点挤出
dir=normalize(v.vertex.xyz);
建立一个float3方向变量dir
把该点的位置作为距离几何中心的方向的单位向量
float3 dir2=v.normal;
建立一个float3方向变量dir
dir2为法线方向
D=dot(dir,dir2);
D为计算该点位置朝向和法线方向的点积,通过正负值可以确定是指向还是背离几何中心的,正为背离,负为指向
dir=dir*sign(D);
乘上正负值,真正的方向值
dir=dir*_Factor+dir2*(1-_Factor);
把该点位置朝向与法线方向按外部变量_Factor的比重混合,来控制挤出多远
v.vertex.xyz+=dir*_Outline;
把物体背面的点向外挤出
v2f vert (appdata_full v) {
v2f o;
float3 dir=normalize(v.vertex.xyz);
float3 dir2=v.normal;
float D=dot(dir,dir2);
dir=dir*sign(D);
dir=dir*_Factor+dir2*(1-_Factor);
v.vertex.xyz+=dir*_Outline;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
return o;
}
顶点函数结束,
接下来为描边上色
在frag函数中
挤出轮廓的颜色,此处颜色随意
效果如下:
清楚地描出了轮廓,可以在材质中改变_Outline的值来改变粗细
描边shader如下:
Shader "Tut/Shader/Toon/miaobian" {
Properties {
_Color("Main Color",color)=(1,1,1,1)
_Outline("Thick of Outline",range(0,0.1))=0.02
_Factor("Factor",range(0,1))=0.5
}
SubShader {
pass{
Tags{"LightMode"="Always"}
Cull Front
ZWrite On
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _Outline;
float _Factor;
float4 _Color;
struct v2f {
float4 pos:SV_POSITION;
};
v2f vert (appdata_full v) {
v2f o;
float3 dir=normalize(v.vertex.xyz);
float3 dir2=v.normal;
float D=dot(dir,dir2);
dir=dir*sign(D);
dir=dir*_Factor+dir2*(1-_Factor);
v.vertex.xyz+=dir*_Outline;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
return o;
}
float4 frag(v2f i):COLOR
{
float4 c = _Color / 5;
return c;
}
ENDCG
}
pass{
Tags{"LightMode"="ForwardBase"}
Cull Back
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float4 _LightColor0;
float4 _Color;
float _Steps;
float _ToonEffect;
struct v2f {
float4 pos:SV_POSITION;
float3 lightDir:TEXCOORD0;
float3 viewDir:TEXCOORD1;
float3 normal:TEXCOORD2;
};
v2f vert (appdata_full v) {
v2f o;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
o.normal=v.normal;
o.lightDir=ObjSpaceLightDir(v.vertex);
o.viewDir=ObjSpaceViewDir(v.vertex);
return o;
}
float4 frag(v2f i):COLOR
{
float4 c=1;
float3 N=normalize(i.normal);
float3 viewDir=normalize(i.viewDir);
float3 lightDir=normalize(i.lightDir);
float diff=max(0,dot(N,i.lightDir));
diff=(diff+1)/2;
diff=smoothstep(0,1,diff);
c=_Color*_LightColor0*(diff);
return c;
}
ENDCG
}//
}
}
开始卡通着色旅程
描边之后就是重头戏着色了
简单的举几个例子
说明一下卡通着色
把diffuse漫反射颜色变成了很明显的几个色阶(本例为四个)
普通的diffuse漫反射龙,没有色阶层,颜色过渡的很好,没有卡通的感觉
普通的漫反射材质
上图的可爱的大河酱就是由二阶颜色着色而成,再加上边缘黑色的描边,这个是真正的卡通,不是渲染出来的= =
《深渊传说》是传说系列的早期作品之一,用的也是卡通渲染
感觉大部分都是卡通式纹理贴图在出力
《仙乐传说》的战斗结束画面
有明显的明暗交界线(两个色阶),并随摄像头(view direction)的变化而变化,人物有明显的描边处理,卡通着色起了很大作用
《无尽传说2》,是传说系列比较近的作品,画面明显比前做好了许多,但万变不离其宗,还是用的卡通着色,(= =没玩过这作)
人物有着明显的描边处理
另外我感觉泛光的效果很好啊,应该是bloom或者是hdr之类的,跑题了= =
开始动手操刀卡通着色
第一个pass就是上面的描边pass
对漫反射的卡通着色在第二个pass中
先声明变量
_Color物体的颜色
_Outline挤出描边的粗细
_Factor挤出多远
_ToonEffect卡通化程度(二次元与三次元的交界线)
_Steps色阶层数
Properties {
_Color("Main Color",color)=(1,1,1,1)//物体的颜色
_Outline("Thick of Outline",range(0,0.1))=0.02//挤出描边的粗细
_Factor("Factor",range(0,1))=0.5//挤出多远
_ToonEffect("Toon Effect",range(0,1))=0.5//卡通化程度(二次元与三次元的交界线)
_Steps("Steps of toon",range(0,9))=3//色阶层数
}
卡通着色主要在着色函数frag中进行
float diff=max(0,dot(N,i.lightDir));
求出正常的漫反射颜色
diff=(diff+1)/2;
做亮化处理
diff=smoothstep(0,1,diff);
使颜色平滑的在[0,1]范围之内
float toon=floor(diff*_Steps)/_Steps;
把颜色做离散化处理,把diffuse颜色限制在_Steps种(_Steps阶颜色),简化颜色,这样的处理使色阶间能平滑的显示
diff=lerp(diff,toon,_ToonEffect);
根据外部我们可控的卡通化程度值_ToonEffect,调节卡通与现实的比重
c=_Color*_LightColor0*(diff);
把最终颜色混合
第二个pass结束,
float4 frag(v2f i):COLOR
{
float4 c=1;
float3 N=normalize(i.normal);
float3 viewDir=normalize(i.viewDir);
float3 lightDir=normalize(i.lightDir);
float diff=max(0,dot(N,i.lightDir));//求出正常的漫反射颜色
diff=(diff+1)/2;//做亮化处理
diff=smoothstep(0,1,diff);//使颜色平滑的在[0,1]范围之内
float toon=floor(diff*_Steps)/_Steps;//把颜色做离散化处理,把diffuse颜色限制在_Steps种(_Steps阶颜色),简化颜色,这样的处理使色阶间能平滑的显示
diff=lerp(diff,toon,_ToonEffect);//根据外部我们可控的卡通化程度值_ToonEffect,调节卡通与现实的比重 c=_Color*_LightColor0*(diff);//把最终颜色混合
return c;
}
第三个pass就是在第二个pass的基础之上加上离散化的高光
建立float变量dist为求出距离光源的距离float
float atten=1/(dist);
根据距光源的距离求出衰减;
漫反射部分与第二个pass相同;
half3 h = normalize (lightDir + viewDir);
求出半角向量
float nh = max (0, dot (N, h));
float spec = pow (nh, 32.0);
求出正常情况下的高光强度
float toonSpec=floor(spec*atten*2)/ 2;
把高光也离散化
spec=lerp(spec,toonSpec,_ToonEffect);
调节卡通与现实高光的比重
float4 frag(v2f i):COLOR
{
float4 c=1;
float3 N=normalize(i.normal);
float3 viewDir=normalize(i.viewDir);
float dist=length(i.lightDir);//求出距离光源的距离
float3 lightDir=normalize(i.lightDir);
float diff=max(0,dot(N,i.lightDir));
diff=(diff+1)/2;
diff=smoothstep(0,1,diff);
float atten=1/(dist);//根据距光源的距离求出衰减
float toon=floor(diff*atten*_Steps)/_Steps;
diff=lerp(diff,toon,_ToonEffect); half3 h = normalize (lightDir + viewDir);//求出半角向量
float nh = max (0, dot (N, h));
float spec = pow (nh, 32.0);//求出高光强度
float toonSpec=floor(spec*atten*2)/ 2;//把高光也离散化
spec=lerp(spec,toonSpec,_ToonEffect);//调节卡通与现实高光的比重 c=_Color*_LightColor0*(diff+spec);//求出最终颜色
return c;
}
就可以得到这种卡通效果:
shader如下:
Shader "Tut/Shader/Toon/toon" {
Properties {
_Color("Main Color",color)=(1,1,1,1)//物体的颜色
_Outline("Thick of Outline",range(0,0.1))=0.02//挤出描边的粗细
_Factor("Factor",range(0,1))=0.5//挤出多远
_ToonEffect("Toon Effect",range(0,1))=0.5//卡通化程度(二次元与三次元的交界线)
_Steps("Steps of toon",range(0,9))=3//色阶层数
}
SubShader {
pass{//处理光照前的pass渲染
Tags{"LightMode"="Always"}
Cull Front
ZWrite On
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _Outline;
float _Factor;
struct v2f {
float4 pos:SV_POSITION;
};
v2f vert (appdata_full v) {
v2f o;
float3 dir=normalize(v.vertex.xyz);
float3 dir2=v.normal;
float D=dot(dir,dir2);
dir=dir*sign(D);
dir=dir*_Factor+dir2*(1-_Factor);
v.vertex.xyz+=dir*_Outline;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
return o;
}
float4 frag(v2f i):COLOR
{
float4 c=0;
return c;
}
ENDCG
}//end of pass
pass{//平行光的的pass渲染
Tags{"LightMode"="ForwardBase"}
Cull Back
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float4 _LightColor0;
float4 _Color;
float _Steps;
float _ToonEffect;
struct v2f {
float4 pos:SV_POSITION;
float3 lightDir:TEXCOORD0;
float3 viewDir:TEXCOORD1;
float3 normal:TEXCOORD2;
};
v2f vert (appdata_full v) {
v2f o;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);//切换到世界坐标
o.normal=v.normal;
o.lightDir=ObjSpaceLightDir(v.vertex);
o.viewDir=ObjSpaceViewDir(v.vertex);
return o;
}
float4 frag(v2f i):COLOR
{
float4 c=1;
float3 N=normalize(i.normal);
float3 viewDir=normalize(i.viewDir);
float3 lightDir=normalize(i.lightDir);
float diff=max(0,dot(N,i.lightDir));//求出正常的漫反射颜色
diff=(diff+1)/2;//做亮化处理
diff=smoothstep(0,1,diff);//使颜色平滑的在[0,1]范围之内
float toon=floor(diff*_Steps)/_Steps;//把颜色做离散化处理,把diffuse颜色限制在_Steps种(_Steps阶颜色),简化颜色,这样的处理使色阶间能平滑的显示
diff=lerp(diff,toon,_ToonEffect);//根据外部我们可控的卡通化程度值_ToonEffect,调节卡通与现实的比重
c=_Color*_LightColor0*(diff);//把最终颜色混合
return c;
}
ENDCG
}//
pass{//附加点光源的pass渲染
Tags{"LightMode"="ForwardAdd"}
Blend One One
Cull Back
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float4 _LightColor0;
float4 _Color;
float _Steps;
float _ToonEffect;
struct v2f {
float4 pos:SV_POSITION;
float3 lightDir:TEXCOORD0;
float3 viewDir:TEXCOORD1;
float3 normal:TEXCOORD2;
};
v2f vert (appdata_full v) {
v2f o;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
o.normal=v.normal;
o.viewDir=ObjSpaceViewDir(v.vertex);
o.lightDir=_WorldSpaceLightPos0-v.vertex;
return o;
}
float4 frag(v2f i):COLOR
{
float4 c=1;
float3 N=normalize(i.normal);
float3 viewDir=normalize(i.viewDir);
float dist=length(i.lightDir);//求出距离光源的距离
float3 lightDir=normalize(i.lightDir);
float diff=max(0,dot(N,i.lightDir));
diff=(diff+1)/2;
diff=smoothstep(0,1,diff);
float atten=1/(dist);//根据距光源的距离求出衰减
float toon=floor(diff*atten*_Steps)/_Steps;
diff=lerp(diff,toon,_ToonEffect);
half3 h = normalize (lightDir + viewDir);//求出半角向量
float nh = max (0, dot (N, h));
float spec = pow (nh, 32.0);//求出高光强度
float toonSpec=floor(spec*atten*2)/ 2;//把高光也离散化
spec=lerp(spec,toonSpec,_ToonEffect);//调节卡通与现实高光的比重
c=_Color*_LightColor0*(diff+spec);//求出最终颜色
return c;
}
ENDCG
}//
}
}
这样已经能做出很好的卡通效果了,各位可以在此基础上研制加强版
加强版1:
加上了边缘光Rim
漂亮的水蓝色星球(= =;纹理)
就是在第二个pass中加上rim值
添加的外部变量;
_RimPower边缘光亮度程度
_ToonRimStep边缘光色阶数
在frag函数中
float rim = 1.0 - saturate(dot(N, normalize (viewDir)));
求出正常的边缘光程度
rim = rim+1;
使之加亮
rim = pow(rim, _RimPower);
外部变量_RimPower控制边缘光亮度大小
float toonRim = floor(rim * _ToonRimStep) / _ToonRimStep;
再对边缘光进行离散化
rim = lerp(rim, toonRim, _ToonEffect);
调节卡通与现实的比重
c=_Color*_LightColor0*(diff) * rim * mc*2;
进行最终颜色混合
float4 frag(v2f i):COLOR
{
half4 mc = tex2D (_MainTex, i.uv_MainTex);
float4 c=1;
float3 N=normalize(i.normal);
float3 viewDir=normalize(i.viewDir);
float3 lightDir=normalize(i.lightDir);
float diff=max(0,dot(N,i.lightDir));
diff=(diff+1)/2;
diff=smoothstep(0,1,diff);
float toon=floor(diff*_Steps)/_Steps;
diff=lerp(diff,toon,_ToonEffect);
float rim = 1.0 - saturate(dot(N, normalize (viewDir)));//求出正常的边缘光程度
rim = rim+1;//使之加亮
rim = pow(rim, _RimPower);//外部变量_RimPower控制边缘光亮度大小
float toonRim = floor(rim * _ToonRimStep) / _ToonRimStep;//再对边缘光进行离散化
rim = lerp(rim, toonRim, _ToonEffect);//调节卡通与现实的比重
c=_Color*_LightColor0*(diff) * rim * mc*2;//进行最终颜色混合
return c;
}
活在三次元世界的3d布料和活在二次元世界的3d布料
加强版2:
带纹理贴图版
加强版3:
带纹理贴图Rim版
带纹理贴图Rim版变种
不建议把纹理贴图也离散化,效果实在是不好,= =;
接下来就有待各位看官们继续开发了,有什么更好的效果一定要告诉我
-----------------------by wolf96
Unity3d shader之卡通着色Toon Shading的更多相关文章
- unity3d shader 学习
[浅墨Unity3D Shader编程] 着色器参考 [Unity Shaders]
- 【译】Unity3D Shader 新手教程(4/6) —— 卡通shader(入门版)
本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 暗黑系 动机 如果你满足以下条件,我建议你阅读这篇教程: 你想了解更多有关表面着色器的细节知识. 你想实现一个入门 ...
- 【浅墨Unity3D Shader编程】之三 光之城堡篇:子着色器、通道与标签的写法 & 纹理混合
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://hpw123.net/a/C__/kongzhitaichengxu/2014/1117/120.html 作者:毛星云 ...
- Shader专题:卡通着色(一)控制颜色的艺术
什么是 Shader? 关于什么是 Shader ,各种百科各种教程都有说过,但是今天我们就从一个另一个角度去试着理解什么是 Shader? 我们先看下 Shade 的英文意思,如下: v.给...遮 ...
- 【译】Unity3D Shader 新手教程(1/6)
本文为翻译,附上原文链接. 转载请注明出处--polobymulberry-博客园. 刚开始接触Unity3D Shader编程时,你会发现有关shader的文档相当散,这也造成初学者对Unity3D ...
- Unity3D shader简介
Unity3D shader简介 可以肯定的说Unity3D使得很多开发者开发游戏更容易.毫无疑问,shader(着色器)编码,仍有很长的路要走.shader是一个专门运行在GPU的程序,经常被神秘包 ...
- 【浅墨Unity3D Shader编程】之一 夏威夷篇:游戏场景的创建 & 第一个Shader的书写
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/40723789 作者:毛星云(浅墨) ...
- 转 猫都能学会的Unity3D Shader入门指南(二)
猫都能学会的Unity3D Shader入门指南(二) 关于本系列 这是Unity3D Shader入门指南系列的第二篇,本系列面向的对象是新接触Shader开发的Unity3D使用者,因为我本身自己 ...
- Unity3D Shader入门指南(二)
关于本系列 这是Unity3D Shader入门指南系列的第二篇,本系列面向的对象是新接触Shader开发的Unity3D使用者,因为我本身自己也是Shader初学者,因此可能会存在错误或者疏漏,如果 ...
随机推荐
- ASP.NET MVC学习系列 WebAPI初探
转自http://www.cnblogs.com/babycool/p/3922738.html 一.无参数Get请求 一般的get请求我们可以使用jquery提供的$.get() 或者$.ajax( ...
- Android 读取txt文件并以utf-8格式转换成字符串
博客: 安卓之家 微博: 追风917 CSDN: 蒋朋的家 简书: 追风917 博客园: 追风917 # 使用EncodingUtils 今天用到了城市选择三级联动的库,用的这个:https://gi ...
- xml处理相关文章收藏
XPath语法 在C#中使用XPath示例:http://blog.csdn.net/yukaizhao/article/details/6630613 .Net那点事儿系列:C#操作Xml:通过Xm ...
- java开发规范总结_代码编码规范
规范需要平时编码过程中注意,是一个慢慢养成的好习惯 1.基本原则 强制性原则: 1.字符串的拼加操作,必须使用StringBuilder: 2.try…catch的用法 try{ }c ...
- css文件加载:@import和link的区别
刚看了一个百度试题:请简述CSS加载方式link和@import的区别? 平时一般都用link,也知道css的加载方式,但还真的没有仔细研究过其之间的差别,查了一些资料,大致总结如下: @impot使 ...
- 校省选赛第一场A题Cinema题解
今天是学校省选的第一场比赛,0战绩收工,死死啃着A题来做,偏偏一直WA在TES1. 赛后,才发现,原来要freopen("input.txt","r",stdi ...
- 通过javascript实现页面的横竖屏固定
javascript是不能固定页面是横屏还是竖屏的,但是我们可以通过另外一种思路来监听window.orientation状态,假设我们要固定页面为横屏显示,则当window.orientation返 ...
- Android APK安装过程介绍
课题路径:从Myfile中点击应用进行安装,到安装完成,过程分析 思想方法:在研究PreloadInstaller的时候我们直接从整个apk的文件结构入手,由整体到部分的分析:但现在整个PMS非常庞大 ...
- ASP.NET MVC轻教程 Step By Step 11——数据注解
将验证规则写在Cotroller里不是一个好办法,这样会显得代码很啰嗦,更重要的是将业务逻辑写入Controller,使得Controller变得更“重”,不符合设计原则.更好的办法是使用验证注解属性 ...
- WebApi(二)-重新封装返回结果
先创建要返回的结果类型: /// <summary> /// 返回类型 /// </summary> public class ApiResultModel { private ...