shader 非真实感渲染(Non-Photorealistic Rendering)

卡通风格渲染

渲染轮廓线

方法

  • 基于观察角度和表面法线的轮廓线渲染 ,视角方向和表面法线点乘结果来得到轮廓线。这种方法简单迅速可以在一个 pass 中就得到渲染结果,但时局限性很大,很多模型得不到满意的描边效果

  • 过程式几何轮廓线渲染,使用两个 pass,一个渲染正面,一个渲染背面,并让轮廓可见。优点快速有效,适用于大部分表面平滑模型,不适合类似于正方体这样平整模型

  • 基于图像处理的轮廓线渲染 ,例如使用 sobel 算子的边缘检测,缺点是无法检测深度和法线变化很小的轮廓线,例如桌上的一张纸

  • 基于轮廓边检测的轮廓线渲染,上面的方法无法控制轮廓线的渲染风格。用于精确检测轮廓边并控制渲染风格
    $$
    \left( \vec{n}_0 \cdot \vec{v} > 0 \right) \neq \left( \vec{n}_1 \cdot \vec{v} > 0 \right)
    $$

    n0 n1 分别表示这条边相邻两个三角面皮的法线,v 是视角到该变任意顶点的方向

顶点扩张

在视角空间下把顶点沿法线向往外扩张一段距离,以此来让背部轮廓线可见。但是如果直接对顶点进行扩张,对于一些内凹模型,就会发生背面面片遮挡正面面片的情况
为了尽量避免这种情况,在扩张背面顶点之前,先对顶点法线的 z 分量进行一定处理,使他们等于一个定值,然后把法线归一化后进行扩张。

    viewNormal.z=-0.5;
    viewNormal = normalize(viewNormal);
    viewPos = viewPos +viewNormal*_Outline;

添加高光

卡通风格渲染中的高光往往是一块分界明显的纯色区域,对于卡通渲染需要的高光反射光照模型,我们会 normalDir 和 halfdir 的点乘结果和一个阈值比较,若小于则高光反射系数 返回 0,否则返回 1。

$$
\mathtt{c}{specular}=(\mathtt{c}\cdot\mathtt{m}{specular})max(0,\vec n\cdot\vec h)^{m{gloss}}
$$

实现

	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_Ramp ("Ramp Texture", 2D) = "white" {}
		_Outline ("Outline", Range(0, 1)) = 0.1
		_OutlineColor ("Outline Color", Color) = (0, 0, 0, 1)
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_SpecularScale ("Specular Scale", Range(0, 0.1)) = 0.01
	}
    SubShader {
		Tags { "RenderType"="Opaque" "Queue"="Geometry"}

		Pass {
			NAME "OUTLINE"

			//剔除前面,只渲染背面
			Cull Front

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

			float _Outline;
			fixed4 _OutlineColor;

			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};

			struct v2f {
			    float4 pos : SV_POSITION;
			};

			v2f vert (a2v v) {
				v2f o;
				//视角空间下顶点
				float4 pos = mul(UNITY_MATRIX_MV, v.vertex);
				//法线
				float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
				//扩展前指定 z 的值
				normal.z = -0.5;
				//延法线方向扩展 顶点
				pos = pos + float4(normalize(normal), 0) * _Outline;
				//顶点变换到裁剪空间
				o.pos = mul(UNITY_MATRIX_P, pos);

				return o;
			}

			float4 frag(v2f i) : SV_Target {
				//背面颜色指定为轮廓颜色
				return float4(_OutlineColor.rgb, 1);
			}

			ENDCG
		}

		Pass {
			Tags { "LightMode"="ForwardBase" }
			//背面已经在上一pass着色
			Cull Back

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag

			#pragma multi_compile_fwdbase

			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			#include "UnityShaderVariables.cginc"

			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			//渐变纹理
			sampler2D _Ramp;
			fixed4 _Specular;
			fixed _SpecularScale;

			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
				float4 tangent : TANGENT;
			};

			struct v2f {
				float4 pos : POSITION;
				float2 uv : TEXCOORD0;
				float3 worldNormal : TEXCOORD1;
				float3 worldPos : TEXCOORD2;
				SHADOW_COORDS(3)
			};

			v2f vert (a2v v) {
				v2f o;

				o.pos = UnityObjectToClipPos( v.vertex);
				o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
				o.worldNormal  = UnityObjectToWorldNormal(v.normal);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

				TRANSFER_SHADOW(o);

				return o;
			}

			float4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
				fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir);

				fixed4 c = tex2D (_MainTex, i.uv);
				fixed3 albedo = c.rgb * _Color.rgb;

				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

				fixed diff =  dot(worldNormal, worldLightDir);
				diff = (diff * 0.5 + 0.5) * atten;

				fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff, diff)).rgb;

				fixed spec = dot(worldNormal, worldHalfDir);
				//邻像素之间近似导数值
				fixed w = fwidth(spec) * 2.0;
				fixed3 specular = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale);

				return fixed4(ambient + diffuse + specular, 1.0);
			}

			ENDCG
		}
	}
	FallBack "Diffuse"

image

posted @ 2025-07-22 14:16  晓叔  阅读(17)  评论(0)    收藏  举报