文章目录
- 一、 介绍
- 二、 素材准备
- 三、 步骤
- 四、 shader代码
- 五、工程链接
一、 介绍
NPR是计算机图形学中的一类,即非真实感绘制(Non-photorealistic rendering),主要用于模拟艺术式的绘制风格,也用于发展新绘制风格,形式一般是卡通造影。
NPR是Unity中的一种非真实渲染技术,它使用一种称为"NPR"的算法来模拟非真实渲染效果。这种技术可以用于制作各种类型的视觉效果,包括卡通效果、手绘效果、水彩画效果等等。
NPR算法的核心思想是将3D模型表面的每个点映射到一个二维平面上,这个平面上的每个像素点对应一个颜色。然后,NPR算法会根据这个颜色和周围像素点的颜色来计算这个像素点的最终颜色。这个过程可以看作是在这个二维平面上进行一次扫描,并且根据扫描到的每个像素点的颜色和周围像素点的颜色来生成最终的渲染结果。
NPR技术的优点在于它可以在保证画面美观的前提下,极大地降低了渲染的计算量和硬件要求,使得游戏能够在较低配置的设备上运行。此外,NPR技术还可以用于制作各种类型的视觉效果,使得游戏画面更加丰富和多样化。
本文将介绍如何将一个原始的3D角色通过NPR渲染技术转换成类似《原神》中的卡通角色。
二、 素材准备
三、 步骤
前期角色外观:
显示纹理:
描边:
高光和边缘光:
最终渲染效果:
四、 shader代码
Shader "Custom/ToonShader"
{
Properties {
[NoScaleOffset] _MainTex ("MainTex", 2D) = "white" {}
// 主纹理,用于对象的颜色贴图
_MainColor ("MainColor", Color) = (1,1,1,1)
// 主颜色,用于设置对象的主要颜色
_OutLineWidth ("OutlLineWidth", Range(0, 0.1)) = 0.002
// 描边宽度
_OutLineColor ("OutLineColor", Color) = (0,0,0,1)
// 描边颜色
[NoScaleOffset] _GradientTex ("GradientTex", 2D) = "white" {}
// 用于阴影梯度采样的纹理,暂时就叫这个名词吧
_GradientIntensity ("GradientIntensity", Range(0,1)) = 1
// 阴影梯度采样纹理强度
_SpecularPower ("SpecularPower", Range(1,100)) = 80
// 高光平滑度
_SpecularIntensity ("SpecularItensity", Range(0,1)) = 1
// 高光强度
_SpecularThreshold ("SpecularThreshold", Range(0,1)) = 0.3
// 高光阈值
_SpecularBrightness ("SpecularBrightness", Range(0,1)) = 0.1
// 高光添加的亮度量
_SpecularValue ("SpecularValue", Range(0,1)) = 1
// 高光值
_RimIntensity ("RimIntensity", Range(0,5)) = 1
// 边缘光强度
[Toggle] _RimShowAtBackToLight ("RimShowAtBackToLight", Float) = 0
// 边缘光是否被光照时才显示的开关
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 100
Pass { // solid
Name "Solid"
Tags { "LightMode"="ForwardBase" }
// 阴影需要,正向渲染光照基础pass
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "AutoLight.cginc"
#pragma multi_compile_fwdbase_fullshadows
// 阴影需要
#pragma fragmentoption ARB_precision_hint_fastest
// 最快速精度
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
half3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
half3 worldNormal : TEXCOORD1;
float3 worldPos : TEXCOORD2;
UNITY_LIGHTING_COORDS(3,4)
// 阴影需要
};
sampler2D _MainTex;
fixed4 _MainColor;
sampler2D _GradientTex;
fixed _GradientIntensity;
fixed _SpecularPower;
fixed _SpecularIntensity;
fixed _SpecularThreshold;
fixed _SpecularBrightness;
fixed _SpecularValue;
fixed _RimIntensity;
fixed _RimShowAtBackToLight;
v2f vert (appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
TRANSFER_VERTEX_TO_FRAGMENT(o)
// 阴影需要
return o;
}
fixed4 frag (v2f i) : SV_Target {
i.worldNormal = normalize(i.worldNormal);
half3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
half3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
atten = atten * 0.5 + 0.5;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
fixed LdotN = dot(lightDir, i.worldNormal);
fixed halfLambert = LdotN * 0.5 + 0.5;
fixed lightShadowCoef = halfLambert * atten;
fixed3 diffuse = tex2D(_MainTex, i.uv).rgb * _MainColor;
fixed3 dGradient = tex2D(_GradientTex, float2(lightShadowCoef, 0)).rgb;
diffuse = lerp(diffuse, diffuse * dGradient, _GradientIntensity);
half3 hDir = normalize(viewDir + lightDir);
fixed HdotN = max(0, dot(hDir, i.worldNormal));
fixed specular = pow(HdotN, _SpecularPower) * _SpecularIntensity;
specular *= atten;
specular = step(_SpecularThreshold, specular) * _SpecularValue;
fixed rimFactor = (1 - dot(viewDir, i.worldNormal)) * _RimIntensity;
rimFactor = lerp(rimFactor, rimFactor * max(0, dot(-lightDir, viewDir)), _RimShowAtBackToLight);
rimFactor *= atten;
rimFactor = step(_SpecularThreshold, rimFactor) * _SpecularValue;
specular = max(specular, rimFactor);
return fixed4(
ambient +
diffuse +
diffuse * specular + specular * _SpecularBrightness
, 1);
}
ENDCG
}
Pass { // outline
Name "Outline"
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#pragma fragmentoption ARB_precision_hint_fastest
fixed _OutLineWidth;
fixed4 _OutLineColor;
float4 vert (float4 vertex : POSITION, float3 normal : NORMAL) : SV_POSITION {
// 投影空间
float4 pos = UnityObjectToClipPos(vertex);
// 转换到视图空间的法线
fixed3 vNormal = mul((float3x3)UNITY_MATRIX_IT_MV, normal);
fixed2 offset = TransformViewToProjection(vNormal.xy);
// 由于在顶点后处理会有透视除法,所以我们首先乘上pos.w以抵消透视,这样无论多远多近都可以按恒定的描边边宽来显示
pos.xy += offset * _OutLineWidth * pos.w;
return pos;
}
fixed4 frag () : SV_Target { return _OutLineColor; }
ENDCG
}
}
Fallback "Diffuse"
}
这个Unity卡通渲染的Shader代码使用了以下技术和功能:
-
主纹理和颜色属性:使用了主纹理
_MainTex
和主颜色_MainColor
来控制对象的颜色。 -
描边效果:通过
_OutLineWidth
和_OutLineColor
控制描边的宽度和颜色。 -
阴影梯度采样:使用
_GradientTex
和_GradientIntensity
控制阴影梯度采样纹理和强度。 -
高光效果:通过
_SpecularPower
、_SpecularIntensity
、_SpecularThreshold
、_SpecularBrightness
和_SpecularValue
控制高光的平滑度、强度、阈值和亮度。 -
边缘光效果:使用
_RimIntensity
控制边缘光的强度,以及_RimShowAtBackToLight
控制边缘光是否受光照方向影响。 -
正向渲染光照:使用
Tags
和CGPROGRAM
中的相关指令来支持正向渲染光照,包括阴影的处理。 -
顶点着色器和片元着色器:定义了顶点着色器
vert
和片元着色器frag
,并在这里执行了主要的渲染计算。 -
光照计算:计算了光照方向、光照强度、阴影、漫反射、高光等光照效果。
-
描边效果的后处理:在
Outline
Pass 中,实现了对象的描边效果。 -
Shader Fallback:如果渲染模式不支持这个Shader,会回退到使用 “Diffuse” Shader。
这个Shader包含了多种效果,包括卡通风格的渲染、高光、描边和阴影梯度,以及一些参数控制这些效果的强度和外观。这些效果可以用于实现卡通风格的游戏或动画渲染。
五、工程链接
https://download.csdn.net/download/qq_20179331/88454844
内含exe文件
1.滑动鼠标中键滚轮,可以放大、缩小视角
2.按一下q键,场景相机顺时针旋转90度;按一下w键,场景相机逆时针旋转90度。