啃了《unity shader 入门精要》的非真实渲染之后,对卡通渲染感兴趣了,下载了unity官网上的unit酱,看了他的衣服和皮肤的shader,虽说不复杂,但我的水平还需要慢慢理解。。。不懂的地方百度后发现冯乐乐竟然16年的时候也解读过这个渲染,哈哈,我算走对路了吗,话不多说,女神的帖子链接:https://blog.csdn.net/candycat1992/article/details/51050591
她解读的很细致了,但是东西要变成自己的要自己嚼过后吐出来 才算真正吸收了,以下仅是个人理解,如有错误的地方感谢指正~~
颜色
衰减的光照颜色:combinedColor
// Falloff. Convert the angle between the normal and the camera direction into a lookup for the gradient
//衰减。将法线和相机方向之间的角度转换为梯度查找
// 【漫反射系数 】n•v(实际上是n•l,这里用v代替了l)
float_t normalDotEye = dot( normalVec, i.eyeDir.xyz );
//【截取漫反射系数】clamp函数,截取0.02到0.98之间的漫反射系数绝对值取反
float_t falloffU = clamp( 1.0 - abs( normalDotEye ), 0.02, 0.98 );
//【采样衰减纹理】用截取的漫反射系数做X轴采样衰减纹理,乘以衰减度
float4_t falloffSamplerColor = FALLOFF_POWER * tex2D( _FalloffSampler, float2( falloffU, 0.25f ) );
//【阴影颜色】原贴图的平方作为阴影,就是加深了的原贴图颜色
float3_t shadowColor = diffSamplerColor.rgb * diffSamplerColor.rgb;
//【C=混合了阴影的原贴图】用采样后的梯度贴图的R通道 插值 原贴图 和 阴影颜色
float3_t combinedColor = lerp( diffSamplerColor.rgb, shadowColor, falloffSamplerColor.r );
//【C=有阴影,有衰减度的原贴图】带阴影的原贴图 *(1+带透明度的衰减纹理)
combinedColor *= ( 1.0 + falloffSamplerColor.rgb * falloffSamplerColor.a );
高光:specularColor
// Specular 高光反射
// Use the eye vector as the light vector 用视角方向作为光向量 v->l
//【采样高光反射贴图】
float4_t reflectionMaskColor = tex2D( _SpecularReflectionSampler, i.uv.xy );
//【采样 原贴图】
float4_t diffSamplerColor = tex2D( _MainTex, i.uv.xy );
//【高光反射系数】 n•v(实际上应该是 n•h)
float_t specularDot = dot( normalVec, i.eyeDir.xyz );
// 【漫反射系数】 n•v(实际上是n•l,这里用v代替了l)
float_t normalDotEye = dot( normalVec, i.eyeDir.xyz );
// 【计算各个光照系数】。为了得到高光反射光照 lit(n • l, n • h, m) 返回一个光照向量(环境,漫反射,高光,1)
float4_t lighting = lit( normalDotEye, specularDot, _SpecularPower );
//【高光反射颜色】和原贴图颜色相乘即可得到最后的高光反射颜色
float3_t specularColor = saturate( lighting.z ) * reflectionMaskColor.rgb * diffSamplerColor.rgb;
//【C=带阴影,衰减度,高光的原贴图】
combinedColor += specularColor;
反射 :reflection
// Reflection 反射
//【反射方向】reflect(i,n)函数 入射光线:视线,法线,返回反射方向(这里输出的是 .xzy)
float3_t reflectVector = reflect( -i.eyeDir.xyz, normalVec ).xzy;
//这里取了反射方向的xy,其实就是把贴图放倒了一样,具体原因。。。不清楚
//对于坐标加1乘以0.5就是把范围[-1,1]转换到[0,1]
//【坐标映射】
float2_t sphereMapCoords = 0.5 * ( float2_t( 1.0, 1.0 ) + reflectVector.xy );
//【采样贴图】采样一张二维纹理
float3_t reflectColor = tex2D( _EnvMapSampler, sphereMapCoords ).rgb;
//【混合颜色】GetOverlayColor()函数,混合(反射颜色,之前的颜色)
reflectColor = GetOverlayColor( reflectColor, combinedColor );
//【C=带阴影,衰减度,高光,反射 的原贴图】反射遮罩的透明通道 插值 C 和 反射颜色
combinedColor = lerp( combinedColor, reflectColor, reflectionMaskColor.a );
combinedColor *= _Color.rgb * _LightColor0.rgb;
float opacity = diffSamplerColor.a * _Color.a * _LightColor0.a;
投影:shadow
//阴影颜色
float3_t shadowColor = diffSamplerColor.rgb * diffSamplerColor.rgb;
#ifdef ENABLE_CAST_SHADOWS//如果开启了投影
// Cast shadows 投影
//【投影颜色】阴影颜色(漫反射颜色的平方)* C(C包含了阴影,衰减,高光,反射的原贴图)
shadowColor = _ShadowColor.rgb * combinedColor;
//【投影衰减值】映射到(-1,1)之间,然后只取(0,1)之间
float_t attenuation = saturate( 2.0 * LIGHT_ATTENUATION( i ) - 1.0 );
//【C=阴影,衰减,高光,反射,投影的原贴图】用投影衰减值,插值阴影颜色和C(C包含了。。。)
combinedColor = lerp( shadowColor, combinedColor, attenuation );
#endif
边缘光:
主要代码截取
//计算边缘光大小(1-n.v)
float_t falloffU = clamp( 1.0 - abs( normalDotEye ), 0.02, 0.98 );
//计算边缘光方向 (0.5*(n.l+1))
float_t rimlightDot = saturate( 0.5 * ( dot( normalVec, i.lightDir ) + 1.0 ) );
//边缘光大小,方向相乘,为采样作为X轴使用
falloffU = saturate( rimlightDot * falloffU );
//在边缘光贴图上采样
falloffU = tex2D( _RimLightSampler, float2( falloffU, 0.25f ) ).r;
大体思路
单侧有边缘光,效果
描边
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
// Outline shader
// Material parameters
float4 _Color;
float4 _LightColor0;
float _EdgeThickness = 1.0;
float4 _MainTex_ST;
// Textures
sampler2D _MainTex;
// Structure from vertex shader to fragment shader
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
// Float types
#define float_t half
#define float2_t half2
#define float3_t half3
#define float4_t half4
// Outline thickness multiplier轮廓厚度乘数
#define INV_EDGE_THICKNESS_DIVISOR 0.00285 //INV_边缘_厚度_除数
// Outline color parameters 轮廓的颜色参数
#define SATURATION_FACTOR 0.6 //饱和因素
#define BRIGHTNESS_FACTOR 0.8 //亮度因素
// Vertex shader
v2f vert( appdata_base v )
{
v2f o;
o.uv = TRANSFORM_TEX( v.texcoord.xy, _MainTex );
//【裁剪空间的顶点】转换顶点
half4 projSpacePos = UnityObjectToClipPos( v.vertex );
//【裁剪空间的法线】把法线从模型空间转化到裁剪空间
half4 projSpaceNormal = normalize( UnityObjectToClipPos( half4( v.normal, 0 ) ) );
//【缩放的法线】边缘厚度*INV_边缘_厚度_除数*裁剪空间的法线 (其实就是沿着法线方向放大了一圈)
half4 scaledNormal = _EdgeThickness * INV_EDGE_THICKNESS_DIVISOR * projSpaceNormal; // * projSpacePos.w;
//【后移】法线Z轴加一点点
scaledNormal.z += 0.00001;
//顶点+缩放后的扁平的法线
o.pos = projSpacePos + scaledNormal;
return o;
}
// Fragment shader
float4 frag( v2f i ) : COLOR
{
//【漫反射贴图】对原贴图采样
float4_t diffuseMapColor = tex2D( _MainTex, i.uv );
//【最大通道】比较原贴图的三个通道,取值最大的
float_t maxChan = max( max( diffuseMapColor.r, diffuseMapColor.g ), diffuseMapColor.b );
//获取漫反射贴图
float4_t newMapColor = diffuseMapColor;
//最大通道减小1
maxChan -= ( 1.0 / 255.0 );
//【获取最高通道】取(0,1)之间((漫反射贴图-小于最大通道一点)*255),最后只有最大通道是1,其他通道都是0
float3_t lerpVals = saturate( ( newMapColor.rgb - float3( maxChan, maxChan, maxChan ) ) * 255.0 );
//【最高通道不变,加深其他通道】用最大通道 插值 饱和因素*漫反射贴图 和 漫反射贴图,值最高的分量颜色保持不变,其他分量通常是对原分量乘以变暗系数SATURATION_FACTOR后的结果
newMapColor.rgb = lerp( SATURATION_FACTOR * newMapColor.rgb, newMapColor.rgb, lerpVals );
//【返回最终】(亮度因素*加深后的通道*漫反射贴图,漫反射贴图的透明通道)*颜色*逐像素光源颜色
return float4( BRIGHTNESS_FACTOR * newMapColor.rgb * diffuseMapColor.rgb, diffuseMapColor.a ) * _Color * _LightColor0;
}
完整的main.cg代码
CharaMain | 角色使用的最主要的shader,包含了一些漫反射、阴影、高光、边缘高光、反射等通用的vs和fs的实现。用于渲染衣服和头发。 |
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
// Character shader
// Includes falloff shadow and highlight, specular, reflection, and normal mapping
#define ENABLE_CAST_SHADOWS
// Material parameters
float4 _Color;
float4 _ShadowColor;
float4 _LightColor0;
float _SpecularPower;
float4 _MainTex_ST;
// Textures
sampler2D _MainTex;
sampler2D _FalloffSampler;
sampler2D _RimLightSampler;
sampler2D _SpecularReflectionSampler;
sampler2D _EnvMapSampler;
sampler2D _NormalMapSampler;
// Constants
#define FALLOFF_POWER 0.3
#ifdef ENABLE_CAST_SHADOWS
// Structure from vertex shader to fragment shader
struct v2f
{
float4 pos : SV_POSITION;
LIGHTING_COORDS( 0, 1 )
float2 uv : TEXCOORD2;
float3 eyeDir : TEXCOORD3;
float3 normal : TEXCOORD4;
float3 tangent : TEXCOORD5;
float3 binormal : TEXCOORD6;
float3 lightDir : TEXCOORD7;
};
#else
// Structure from vertex shader to fragment shader
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 eyeDir : TEXCOORD1;
float3 normal : TEXCOORD2;
float3 tangent : TEXCOORD3;
float3 binormal : TEXCOORD4;
float3 lightDir : TEXCOORD5;
};
#endif
// Float types
#define float_t half
#define float2_t half2
#define float3_t half3
#define float4_t half4
// Vertex shader
v2f vert( appdata_tan v )
{
v2f o;
o.pos = UnityObjectToClipPos( v.vertex );
o.uv.xy = TRANSFORM_TEX( v.texcoord.xy, _MainTex );
o.normal = normalize( mul( unity_ObjectToWorld, float4_t( v.normal, 0 ) ).xyz );
// Eye direction vector
half4 worldPos = mul( unity_ObjectToWorld, v.vertex );
o.eyeDir.xyz = normalize( _WorldSpaceCameraPos.xyz - worldPos.xyz ).xyz;
// Binormal and tangent (for normal map)
o.tangent = v.tangent.xyz;
o.binormal = cross( v.normal, v.tangent.xyz ) * v.tangent.w;
o.lightDir = WorldSpaceLightDir( v.vertex );
#ifdef ENABLE_CAST_SHADOWS
TRANSFER_VERTEX_TO_FRAGMENT( o );
#endif
return o;
}
// Overlay blend
inline float3_t GetOverlayColor( float3_t inUpper, float3_t inLower )
{
float3_t oneMinusLower = float3_t( 1.0, 1.0, 1.0 ) - inLower;
float3_t valUnit = 2.0 * oneMinusLower;
float3_t minValue = 2.0 * inLower - float3_t( 1.0, 1.0, 1.0 );
float3_t greaterResult = inUpper * valUnit + minValue;
float3_t lowerResult = 2.0 * inLower * inUpper;
half3 lerpVals = round(inLower);
return lerp(lowerResult, greaterResult, lerpVals);
}
// Compute normal from normal map
inline float3_t GetNormalFromMap( v2f input )
{
float3_t normalVec = normalize( tex2D( _NormalMapSampler, input.uv ).xyz * 2.0 - 1.0 );
normalVec = input.tangent * normalVec.x + input.binormal * normalVec.y + input.normal * normalVec.z;
return normalVec;
}
// Fragment shader
float4 frag( v2f i ) : COLOR
{
float4_t diffSamplerColor = tex2D( _MainTex, i.uv.xy );
float3_t normalVec = i.normal;// GetNormalFromMap( i );
// Falloff. Convert the angle between the normal and the camera direction into a lookup for the gradient
float_t normalDotEye = dot( normalVec, i.eyeDir.xyz );
float_t falloffU = clamp( 1.0 - abs( normalDotEye ), 0.02, 0.98 );
float4_t falloffSamplerColor = FALLOFF_POWER * tex2D( _FalloffSampler, float2( falloffU, 0.25f ) );
float3_t shadowColor = diffSamplerColor.rgb * diffSamplerColor.rgb;
float3_t combinedColor = lerp( diffSamplerColor.rgb, shadowColor, falloffSamplerColor.r );
combinedColor *= ( 1.0 + falloffSamplerColor.rgb * falloffSamplerColor.a );
// Specular
// Use the eye vector as the light vector
float4_t reflectionMaskColor = tex2D( _SpecularReflectionSampler, i.uv.xy );
float_t specularDot = dot( normalVec, i.eyeDir.xyz );
float4_t lighting = lit( normalDotEye, specularDot, _SpecularPower );
float3_t specularColor = saturate( lighting.z ) * reflectionMaskColor.rgb * diffSamplerColor.rgb;
combinedColor += specularColor;
// Reflection
float3_t reflectVector = reflect( -i.eyeDir.xyz, normalVec ).xzy;
float2_t sphereMapCoords = 0.5 * ( float2_t( 1.0, 1.0 ) + reflectVector.xy );
float3_t reflectColor = tex2D( _EnvMapSampler, sphereMapCoords ).rgb;
reflectColor = GetOverlayColor( reflectColor, combinedColor );
combinedColor = lerp( combinedColor, reflectColor, reflectionMaskColor.a );
combinedColor *= _Color.rgb * _LightColor0.rgb;
float opacity = diffSamplerColor.a * _Color.a * _LightColor0.a;
#ifdef ENABLE_CAST_SHADOWS
// Cast shadows
shadowColor = _ShadowColor.rgb * combinedColor;
float_t attenuation = saturate( 2.0 * LIGHT_ATTENUATION( i ) - 1.0 );
combinedColor = lerp( shadowColor, combinedColor, attenuation );
#endif
// Rimlight
float_t rimlightDot = saturate( 0.5 * ( dot( normalVec, i.lightDir ) + 1.0 ) );
//float_t rimlightDot = saturate( 0.5 * ( dot( normalVec, i.lightDir ) + 0.5 ) );
falloffU = saturate( rimlightDot * falloffU );
falloffU = tex2D( _RimLightSampler, float2( falloffU, 0.25f ) ).r;
float3_t lightColor = diffSamplerColor.rgb; // * 2.0;
combinedColor += falloffU * lightColor;
return float4( combinedColor, opacity );
}
衣服shader的完整代码
Shader "UnityChan/Clothing - Double-sided"
{
Properties
{
_Color ("Main Color", Color) = (1, 1, 1, 1)
_ShadowColor ("Shadow Color", Color) = (0.8, 0.8, 1, 1)
_SpecularPower ("Specular Power", Float) = 20
_EdgeThickness ("Outline Thickness", Float) = 1
_MainTex ("Diffuse", 2D) = "white" {}
_FalloffSampler ("Falloff Control", 2D) = "white" {}
_RimLightSampler ("RimLight Control", 2D) = "white" {}
_SpecularReflectionSampler ("Specular / Reflection Mask", 2D) = "white" {}
_EnvMapSampler ("Environment Map", 2D) = "" {}
_NormalMapSampler ("Normal Map", 2D) = "" {}
}
SubShader
{
Tags
{
"RenderType"="Opaque"
"Queue"="Geometry"
"LightMode"="ForwardBase"
}
Pass
{
Cull Off
ZTest LEqual
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "AutoLight.cginc"
#include "CharaMain.cg"
ENDCG
}
Pass
{
Cull Front
ZTest Less
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "CharaOutline.cg"
ENDCG
}
}
FallBack "Transparent/Cutout/Diffuse"
}