UNITY崩坏3角色渲染实践
最近二次元手游,卡通渲染都挺火的。虽然公司没开这类型项目,但是渲染来玩一下也好,原理都是一样,比较简单。在日式卡通中,《罪恶装备》、《崩坏3》的效果都很不错,都是几年前的产品,两者的渲染方式是类似的。
这里用的是《崩坏3》的手游模型,仅是学习,侵权必删。
shader贴图
崩坏3主要用到两张贴图:albedotexilmtex,其中ilmtex,R通道表示高光的强弱,G通道表示阴影区域,B通道控制高光区域的大小(大概)
基础模型
我们先看看崩坏3的基础模型。崩坏3模型设计,贴图配色,法线都做的很好,如果你们游戏模型效果不好,可能你们的基础模型做的比较差。
基础模型
轮廓线
轮廓线的制作方式有很多,网上都很齐全,这里就不多说了,我这里采用把backfaces方式
o.pos = UnityObjectToClipPos(v.vertex.xyz);
float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal));
float2 offset = TransformViewToProjection(normal.xy);
o.pos.xy += offset * o.pos.z * _Outline;
拓展的话,可以用模型顶点色通道控制勾边粗细,控制z-offset去掉多余勾边,效果如下
轮廓线
阴影
卡通渲染的漫反射,用的是halfLambert,然后smoothstep控制暗面与亮面的过渡平滑程度,加上ilm B通道中的阴影范围
float wrapLambert = (ndotl * 0.5 + 0.5) + ilmTex.g;
float shadowStep = saturate(smoothstep(_ShadeEdge0, _ShadeEdge1, wrapLambert )); 阴影
但是脸部阴影比较脏,所以我们用wrap diffuse将脸部因为halfLambert产生的阴影去掉
float wrapLambert = (ndotl * _WrapDiffuse + 1 - _WrapDiffuse) + ilmTex.g;再加上自身阴影
float wrapLambert = (ndotl * _WrapDiffuse + 1 - _WrapDiffuse) + ilmTex.g;
float shadow = SHADOW_ATTENUATION(i);
float shadowStep = saturate(smoothstep(_ShadeEdge0, _ShadeEdge1, wrapLambert * shadow)); 阴影部分+环境光+自身贴图效果
阴影部分效果
高光
高光现在采用Blinn-Phong光照模型,再加上step 函数控制明暗
float3 halfVector = normalize(_WorldSpaceLightPos0 + viewDir);
float ndoth = dot(normal, halfVector);
float specularIntensity = pow(ndoth, _Glossiness) * ilmTex.r;高光
边缘光
主要用smoothstep函数风格化,再乘以光照方向
float rimDot = pow(1 - dot(viewDir, normal), _RimThreshold);
float rimIntensity = rimDot * ndotl;
rimIntensity = smoothstep(_RimAmount - 0.01, _RimAmount + 0.01, rimIntensity);
half4 rim = rimIntensity * _RimColor; 边缘光
然后整体效果加起来,如下
整体效果
整体效果
https://www.zhihu.com/video/1243954287291822080
不足
头发shader,可以改为各向异性shader
高光部分,如果我做的话,还是考虑用金属度,粗糙度贴图,再风格化高光
皮肤部分,加上sss效果,例如用PreIntergated贴图渲染
附上全部代码
Shader "Custom/ToonShader"
{
Properties
{
_Color("Color", Color) = (1,1,1,1)
_AmbientColor("Ambient Color", Color) = (0.4,0.4,0.4,1)
_MainTex("Main Texture", 2D) = "white" {}
_IlmTex("Ilm Texture", 2D) = "black" {}
_WrapDiffuse("WrapDiffuse",Range(0, 1)) = 0.5
_ShadeEdge0("ShadeEdge0",Range(0, 1)) = 0.925
_ShadeEdge1("ShadeEdge1",Range(0, 1)) = 1
_SpecularColor("Specular Color", Color) = (0.9,0.9,0.9,1)
_SpecularRange("SpecularRange",Range(0, 1)) = 0.15
_Glossiness("Glossiness", Range(0.01, 256)) = 8
_RimColor("Rim Color", Color) = (1,1,1,1)
_RimAmount("Rim Amount", Range(0, 1)) = 0.042
_RimThreshold("Rim Threshold", Range(0, 10)) =6
_OutlineColor("Outline Color", Color) = (1,1,1,1)
_Outline("Outline Width", Range(0, 5)) = 0.1
_EmisstionColor("Emisstion Color", Color) = (0,0,0,0)
}
SubShader
{
Pass
{
Tags
{
"LightMode" = "ForwardBase"
"PassFlags" = "OnlyDirectional"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct appdata
{
float4 vertex : POSITION;
float4 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : NORMAL;
float2 uv : TEXCOORD0;
float3 viewDir : TEXCOORD1;
SHADOW_COORDS(2)
};
sampler2D _MainTex;
sampler2D _IlmTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.viewDir = WorldSpaceViewDir(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
TRANSFER_SHADOW(o)
return o;
}
half4 _Color;
half4 _AmbientColor;
half4 _SpecularColor;
float _WrapDiffuse, _ShadeEdge0, _ShadeEdge1;
float _Glossiness, _SpecularRange;
half4 _RimColor;
float _RimAmount;
float _RimThreshold;
half4 _EmisstionColor;
half4 frag (v2f i) : SV_Target
{
half4 color = tex2D(_MainTex, i.uv) * _Color;
half4 ilmTex = tex2D(_IlmTex, i.uv);
float3 normal = normalize(i.worldNormal);
float3 viewDir = normalize(i.viewDir);
float ndotl = max(0,dot(_WorldSpaceLightPos0, normal));
float wrapLambert = (ndotl * _WrapDiffuse + 1 - _WrapDiffuse) + ilmTex.g;
float shadow = SHADOW_ATTENUATION(i);
float shadowStep = saturate(smoothstep(_ShadeEdge0, _ShadeEdge1, wrapLambert * shadow));
half4 diffuse = lerp(_AmbientColor,_LightColor0, shadowStep);//float4(ShadeSH9(float4(normal, 1)), 1)
float3 halfVector = normalize(_WorldSpaceLightPos0 + viewDir);
float ndoth = dot(normal, halfVector);
float specularIntensity = pow(ndoth, _Glossiness) * ilmTex.r;
float specularRange = step(_SpecularRange, specularIntensity + ilmTex.b);
half4 specular = specularRange * _SpecularColor;
float rimDot = pow(1 - dot(viewDir, normal), _RimThreshold);
float rimIntensity = rimDot * ndotl;
rimIntensity = smoothstep(_RimAmount - 0.01, _RimAmount + 0.01, rimIntensity);
half4 rim = rimIntensity * _RimColor;
return (diffuse + rim + specular + _EmisstionColor * color.a) * color;
}
ENDCG
}
Pass
{
Tags{ "LightMode" = "ForwardBase" }
Cull Front
Zwrite On
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
half _Outline;
half4 _OutlineColor;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 vertColor : COLOR;
};
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert(a2v v)
{
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f, o);
o.pos = UnityObjectToClipPos(v.vertex);
float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal));
float2 offset = TransformViewToProjection(normal.xy);
o.pos.xy += offset * o.pos.z * _Outline;
return o;
}
half4 frag(v2f i) : SV_TARGET
{
return _OutlineColor;
}
ENDCG
}
}
Fallback "Legacy Shaders/VertexLit"
}
页:
[1]