UnityShader(七)卡通着色

一、基本漫反射

首先新建一个Shader,同步创建对应的材质球。使用新的材质替换掉模型原有的材质。

接下来开始着手编写基本的漫反射Shader

声明纹理和漫反射颜色属性,并在CG中声明对应的变量

Properties
{
	_MainTex ("Texture", 2D) = "white" {}
	_Diffuse("Diffuse Color",COLOR) = (1,1,1,1)
}

引入「UnityCG」和「Lighting」两个Shader库

#include "UnityCG.cginc"  
#include "Lighting.cginc"

定义输出结构体

struct v2f
{
	float4 vertex : SV_POSITION;
	float2 uv : TEXCOORD0;
	fixed3 worldNormal:TEXCOORD1;
	fixed3 worldPos:TEXCOORD2;
};

在顶点着色器中计算世界空间下的法线坐标、顶点坐标

v2f vert(appdata_base v)
{
	v2f o;
	o.vertex = UnityObjectToClipPos(v.vertex);
	o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
	o.worldNormal = UnityObjectToWorldNormal(v.normal);
	o.worldPos = mul(unity_ObjectToWorld, v.vertex);
	return o;
}

在片元着色器中计算漫反射

fixed4 frag(v2f i) : SV_Target
{
	// 环境光
	const fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
	// 纹理采样
	const fixed4 albedo = tex2D(_MainTex, i.uv);
	// 漫反射
	const fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
	const float difLight = dot(i.worldNormal, worldLightDir) * 0.5 + 0.5;
	const fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * difLight;
	return float4(diffuse + ambient, 1);
}

代码很简单,都是之前学过的内容。我们直接来看效果

二、描边效果

要实现描边效果,可以添加一个新的Pass通道。在新的Pass通道中,用纯色对模型进行渲染。将顶点沿法线方向向外扩展。然后在另一个Pass通道中进行其他的渲染,即可实现描边效果。

首先定义描边宽度和颜色属性

_Outline("Outline",RANGE(0,0.05)) = 0
_OutlineColor("Outline Color",COLOR) = (0,0,0,0)

新创建一个Pass通道,需要裁剪掉正面,只渲染背面防止描边将物体遮挡住。然后在顶点着色器中将顶点沿法线方向扩展

Pass
{
	Name "Outline"
	Cull Front
	
	CGPROGRAM
	#pragma vertex vert
	#pragma fragment frag
	#include "UnityCG.cginc"

	float _Outline;
	float4 _OutlineColor;

	struct v2f
	{
		float4 vertex:SV_POSITION;
	};

	v2f vert(appdata_base v)
	{
		v2f o;
		// 顶点坐标沿法线方向扩展
		v.vertex.xyz += v.normal*_Outline;
		o.vertex = UnityObjectToClipPos(v.vertex);
		return o;
	}
	fixed4 frag(v2f i):SV_Target
	{
		return _OutlineColor;
	}
	ENDCG
}

效果如下

上面这种扩展方式是在世界空间中进行的,我们也可以在视角空间中进行

v2f vert(appdata_base v)
{
	v2f o;
	// 将顶点变换到视角空间
	float4 pos = mul(UNITY_MATRIX_V, mul(unity_ObjectToWorld,v.vertex));
	// 将法线变换到视角空间
	float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV,v.normal));
	pos = pos + float4(normal,0)*_Outline;
	// 将顶点变换到裁剪空间
	o.vertex = mul(UNITY_MATRIX_P,pos);
	return o;
}

或是在裁剪空间中进行

v2f vert(appdata_base v)
{
	v2f o;
	// 将顶点变换到裁剪空间
	o.vertex = UnityObjectToClipPos(v.vertex);
	// 将法线变换到视角空间
	float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV,v.normal));
	// 再转换到裁剪空间
	float2 viewNormal = TransformViewToProjection(normal.xy);
	o.vertex.xy += viewNormal*_Outline;
	return o;
}

效果与在世界空间中扩展几乎相同,这里不再展示。

三、颜色处理

我们知道,卡通渲染效果的颜色比较简单,有种色块拼接的感觉。要实现这种效果就需要对顶点的颜色进行离散化。

首先定义两个属性,用来设置层次和色块之间的过渡效果

_Step("Step",Range(1,30)) = 1  
_CartoonEffect("Cartoon Effect",Range(0,1)) = 0.5

然后在计算漫反射之前进行颜色的离散化。

fixed4 frag(v2f i) : SV_Target
{
	// 环境光
	const fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
	// 纹理采样
	const fixed4 albedo = tex2D(_MainTex, i.uv);
	// 漫反射
	const fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
	float difLight = dot(i.worldNormal, worldLightDir) * 0.5 + 0.5;
	
	// 颜色离散化
	difLight=smoothstep(0,1,difLight);
	const float cartoonColor = floor(difLight*_Step)/_Step;
	difLight = lerp(difLight,cartoonColor,_CartoonEffect);
	
	const fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * difLight;
	return float4(diffuse + ambient, 1);
}

效果如下

四、边缘光

要实现物体边缘发光的效果,就要确定哪部分才属于边缘。其实也非常简单,当视角方向与切线平行,也就是与法线垂直时,表示这个顶点在当前视角下处于边缘位置。也就是说只要将视角方向与法线方向进行点积,结果越接近0,这个顶点就越靠近边缘。

首先定义两个属性用来控制边缘光的颜色和强度

_RimColor("Rim Color",COLOR) = (0,0,0,0)  
_RimPower("Rim Power",Range(0,1)) = 0

然后在片元着色器返回的结果中加上边缘光的计算结果

fixed4 frag(v2f i) : SV_Target
{
	// ...
	// 边缘光
	const fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
	const float rim = 1-dot(i.worldNormal,viewDir);  
	const fixed3 rimColor = _RimColor*pow(rim,1/_RimPower);
	return float4(diffuse + ambient+rimColor, 1);
}

效果如下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值