type
status
date
slug
summary
tags
category
icon
password
fullWidth
fullWidth
NPR渲染是什么
NPR渲染(Non-Photorealistic Rendering),也称为“三渲二”,是一种非真实感渲染技术。它通过在三维物体上应用平面颜色,使物体看起来具有三维透视效果,同时保持二维的视觉风格。
Built-in管线手搓Shader
- 以下是NPR渲染的简单实践,涉及到的要点包含:
- 色彩根据明度离散化,创建清晰的二分
- 在裁剪空间空间法线扩,创建均匀的描边
- 使用剔除后面降低渲染开销
- 菲涅尔边缘光以增加亮度
- 渐变纹理用于微妙的皮肤渐变
- 剪裁纹理中的透明像素

支持UV贴图的Base Shader
- 创建v2f结构体
- 顶点着色器
- 片元着色器
漫反射叠加Ramp Texture
- Ramp Texture(渐变纹理)是一种用于控制光照和阴影效果的纹理技术,常用于卡通渲染中;它通过使用一维或二维的渐变纹理来融合或替代传统的光照模型,实现更加风格化的视觉效果。
- 在片元着色器中可混合漫反射和渐变贴图的颜色,用_RampEffect控制渐变贴图的权重
- 不同Ramp Texture的效果

均匀的描边Pass
- 描边可以分为贴图描边、着色器描边和后处理描边;贴图描边即“本村线”,直接把描边绘制在贴图中;着色器描边一般指的“法线外扩+剔除正面”,也有用深度图检测边缘做描边的;后处理描边则是通过高通滤波等图像处理方法提取出色块的边缘做描边。
- 这里使用比较常用的法线外扩,法线外扩可以分为以下几类:
- 世界空间法线扩展
- 视角空间法线扩展
- 裁剪空间法线扩展
- 推荐使用裁剪空间法线扩展,因为它能确保描边的厚度在不同的相机深度下保持一致。世界空间和视图空间法线扩展会导致轮廓线厚度不均匀的原因是:受透视摄像机近大远小规律的影响,模型靠近相机的部分轮廓线较厚,而远离相机的部分轮廓线较薄。

提前深度测试(Early-Z):光栅化之后,片元着色器之前进行一次深度测试,没有通过测试的片元则不进行之后的片元着色器计算,因此来提高性能。
- 描边Pass放在Base Pass之后:Base Pass首先渲染物体的主要部分,并将深度信息写入深度缓冲区。这样,当描边Pass进行时,可以利用这些深度信息进行深度测试,跳过被遮挡的片元。
注意事项
- Base Pass一般选择剔除背面,即Cull Back;渲染双面的衣服可以选择Cull Off

- 描边Pass只渲染背面,即Cull Front;否则描边将覆盖物体,效果显得奇怪

主渲染 Pass 拆解
顶点着色器(vert)
- 输入:顶点的位置、UV 坐标、法线和颜色。
- 输出:裁剪空间下的顶点位置、UV 坐标、世界空间下的法线和位置、顶点颜色。
- 主要操作:
- 将顶点从对象空间转换到裁剪空间(
UnityObjectToClipPos
)。 - 转换 UV 坐标(
TRANSFORM_TEX
)。 - 计算世界空间下的法线(
mul(v.normal, (float3x3)unity_WorldToObject)
)。 - 计算世界空间下的顶点位置(
mul(unity_ObjectToWorld, v.vertex).xyz
)。 - 传递顶点颜色和雾效坐标。
片元着色器(frag)
- 输入:顶点着色器输出的数据。
- 输出:最终的颜色值。
- 主要操作:
- 计算光照和视角相关向量
- 法线(N):
worldNormal
,世界空间下的法线。 - 光照方向(L):
worldLightDir
,世界空间下的光照方向。 - 视角方向(V):
worldViewDir
,世界空间下的视角方向。 - 半程向量(H):
worldHalfDir
,光照方向和视角方向的中间向量,用于高光计算。 - 采样主纹理
- 使用 UV 坐标采样主纹理
_MainTex
,得到基础颜色col
。 - 计算高光(Specular)
- 计算高光值
spec
:dot(worldNormal, worldHalfDir) + (i.color.g - 0.5) * 2
。 dot(worldNormal, worldHalfDir)
是法线和半程向量的点积,表示高光的强度。(i.color.g - 0.5) * 2
是基于顶点颜色的偏移,用于调整高光范围。- 使用
fwidth(spec)
计算高光值在屏幕空间中的变化率,结合_EdgeSmoothness
控制平滑过渡的范围。 - 使用
smoothstep
实现高光的平滑过渡,并结合_SpecularScale
控制高光的强度。 - 计算漫反射(Diffuse)
- 计算漫反射值
diffValue
:dot(worldNormal, worldLightDir)
。 - 将
diffValue
从[-1, 1]
映射到[0, 1]
,得到diffValue_01
。 - 使用
diffValue_01
采样渐变贴图_RampTex
,得到渐变颜色rampColor
。 - 使用
smoothstep
计算diffStep
,结合_ShadowThreshold
控制阴影的范围。 - 混合兰伯特模型和渐变贴图的效果,得到最终的漫反射颜色
diffuse
。 - 计算边缘光(Rim Light)
- 计算边缘光值
rimValue
:pow(1 - dot(worldNormal, worldViewDir), _RimPower)
。 - 使用
smoothstep
实现边缘光的平滑过渡,结合_RimThreshold
控制边缘光的范围。 - 结合光照颜色和阴影过渡
diffStep
,计算最终的边缘光颜色rim
。 - 混合最终颜色
- 将漫反射、边缘光和高光效果混合,得到最终颜色
final
。
描边 Pass 拆解
顶点着色器(vert)
- 输入:顶点的位置、UV 坐标和法线。
- 输出:裁剪空间下的顶点位置。
- 主要操作:
- 将顶点从对象空间转换到裁剪空间(
UnityObjectToClipPos
)。 - 计算视角空间下的法线(
COMPUTE_VIEW_NORMAL
)。 - 将法线转换到裁剪空间,并扩展顶点位置,生成描边效果。
片元着色器(frag)
- 输入:顶点着色器输出的数据。
- 输出:描边颜色
_OutlineColor
。
- 主要操作:
- 直接返回描边颜色
_OutlineColor
,不进行额外的计算。
完整代码
呈现效果

- Author:Yuki
- URL:http://shirakoko.xyz/article/built-in-npr-shader
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts