Chapter14_非真实感渲染

非真实感渲染(Non Photorealistic Rendering,NPR),用于卡通,水彩风格渲染。

卡通风格渲染(黑色描边,分明的明暗变化)

基于色调的着色技术(tone-based shading),使用蓝色来模拟冷色调,使用黄色来模拟黄色调,来实现从冷到暖的色调变化,为了更加真实的模拟,还可以模型本身的漫反射颜色进行混合来得到最终的冷暖色调。

实现中一般使用漫反射系数对一张一维纹理进行采样,以控制漫反射色调。

1.渲染轮廓线(5种方式)

  • 基于观察角度和表面法线的轮廓线渲染。这种方法使用视角方向和表面法线的点乘结果来得到轮廓线的信息。快速简单,使用一个Pass就可得到结果。但是描边效果不好。
  • 过程式几何轮廓线渲染。使用两个Pass渲染,第一个Pass渲染背面,并使用它的轮廓可见。第二个Pass渲染正面,这种方法快速,效果好,但是不适合类似立方体这样的平整模型。
  • 基于图像处理的轮廓线渲染。屏幕后处理。适用于任何模型,但是深度和法线变化比较小的轮廓无法检测出来。比如桌子上的纸张。
  • 基于轮廓边检测的轮廓线渲染。本质是判断相邻两个三角面片是否朝一个朝正面,一个朝背面。由于是逐帧提取轮廓线,帧与帧之间会出现跳跃性。
  • 基于上述几种渲染方法,先找到轮廓边,把模型和轮廓渲染到纹理中。再使用图像处理方法识别出轮廓线,并在图像空间进行风格化渲染。

基于过程式几何轮廓线渲染:

第一个Pass中,使用轮廓线颜色渲染整个背面的面片,并在视角空间下把模型顶点沿着法线方向向外扩张一段距离,以此让背部轮廓线可见。代码如下:
                                            viewPos = viewPos + viewNormal * _Outline

对于内凹模型,直接用顶点法线进行扩展,可能会背面面片遮挡正面面片,为了防止出现这种情况,扩张背面顶点,先把顶点法线等于一个定值,然后归一化法线,再对顶点进行扩张。是扩张后的背面更加扁平化,降低遮挡正面面片的可能性。

viewNormal.z = -0.5

viewNormal = normalize(viewNormal)

viewPos = viewPos + viewNormal * _Outline;

尽量让顶点沿xy平面扩张,不扩张z,然后背面面片z值大于前面面片,导致背面面片,遮住前面面片。

2.添加高光

为了让模型有分界明显的纯色区域,使用法线点乘(光照方向+视角方向)的一半,再和一个阈值进行比较,小于该阈值,高光反射系数为0,大於则为1。

float spec = dot(worldNormal,worldHalfDir);

spec = step(阈值,spec);//    step函数  spec小于阈值返回0,大于等于返回1

这种方式会让高光区域有锯齿,因为高光区域边缘从0突变到1,没有渐变。因此高光边缘区域加个渐变,进行抗锯齿处理。

float spec = dot(worldNormal,worldHalfDir);

spec = lerp(0,1,smothstep(-w,w,spec - 阈值)) //spec - 阈值 < -w,返回0,大于w返回1,否则在0到1之间进行插值。

这样[-w,w]区间内,就有个渐变插值,消除锯齿。

Shader "Chan/Chapter14_ToonShading" {
	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) = (1,1,1,1)
		//高光反射颜色
		_Specular("Specular",Color) = (1,1,1,1)
		//高光反射计算使用到的阈值
		_SpecularScale("Specular Scale",Range(0,0.1)) = 0.01
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" "Queue"="Geometry"}

		Pass
		{
			//定义此Pass名称,其他Shader可复用
			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);
				//法线从模型空间变换到摄像机空间  为什么不能用UNITY_MATRIX_MV?
				//https://blog.csdn.net/a133900029/article/details/80558765
				float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV,v.normal);
				//防止背后面片顶点扩张,z值比前面面片顶点的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"}

			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);
				//材质反射率albedo
				fixed3 albedo = c.rgb * _Color.rgb;
			
				//环境光
				fixed ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
				//对阴影纹理进行采样
				UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);

				//漫反射光照计算
				fixed diff = dot(worldNormal,worldLightDir);
				//半兰伯特漫反射光照模型计算漫反射系数diff,使diff在【0,1】区间内。
				//渐变纹理纵轴颜色不变,直接用[diff,diff]对渐变纹理采样
				diff = (diff * 0.5 + 0.5) * atten;
				//半兰伯特漫反射 * 阴影 = 最终的漫反射
				fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp,float2(diff,diff)).rgb;

				//高光反射
				fixed3 spec = dot(worldNormal,worldHalfDir);
				//邻域像素之间的近似导数值
				fixed w = fwidth(spec) * 2.0;
				//高光颜色 * 高光反射系数 = 高光反射颜色
				//smoothstep在[-w,w]之间进行插值
				//step(0.0001,_SpecularScale)) _SpecularScale < 0.0001,返回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"
}

注解:

第一个Pass,在摄像机空间下,将顶点从处理过的法线方向,外扩。

第二个Pass,计算 环境光+漫反射+高光反射 = 最终颜色。


素描风格的渲染

Shader "Chan/Chapter14_Hatching" {
	Properties
	{
		_Color("Color Tint",Color) = (1,1,1,1)
		//模型上的纹理平铺系数,值越大,模型素描线条越密
		_TileFactor("Tile Factor",Float) = 1
		//轮廓线线控参数
		_Outline("Outline",Range(0,1)) = 0.1
		//素描渲染使用的6张纹理
		_Hatch0("Hatch 0",2D) = "white"{}
		_Hatch1("Hatch 1",2D) = "white"{}
		_Hatch2("Hatch 2",2D) = "white"{}
		_Hatch3("Hatch 3",2D) = "white"{}
		_Hatch4("Hatch 4",2D) = "white"{}
		_Hatch5("Hatch 5",2D) = "white"{}	
	}
	SubShader
	{
		Tags{"RenderType" = "Opaque" "Queue" = "Geometry"}
		
		//使用Chan/Chapter14_ToonShading Shader中的Pass渲染模型轮廓
		UsePass "Chan/Chapter14_ToonShading/OUTLINE"

		Pass
		{			
			//为了获得各个光照变量,需要设置Pass的标签和相关编译指令					
			Tags{"lightMode" = "ForwardBase"}
			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;
			float _TileFactor;
			sampler2D _Hatch0;
			sampler2D _Hatch1;
			sampler2D _Hatch2;
			sampler2D _Hatch3;
			sampler2D _Hatch4;
			sampler2D _Hatch5;

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

			struct v2f
			{
				float4 pos:SV_POSITION;
				float2 uv:TEXCOORD0;
				//6张纹理 需要6个混合权重,存储在以下两个变量中
				fixed3 hatchWeights0:TEXCOORD1;
				fixed3 hatchWeights1:TEXCOORD2;
				//为了添加阴影效果,还声明了worldPos变量
				float3 worldPos:TEXCOORD3;
				//申明阴影纹理的采样座标 (其实就是TEXCOORD4)
				SHADOW_COORDS(4)
			};

			v2f vert(a2v v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				//
				o.uv = v.texcoord.xy * _TileFactor;

				//世界空间下,dot(光照方向,法线方向) = 漫反射系数
				fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex));
				fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
				fixed diff = max(0,dot(worldLightDir,worldNormal));

				//6张纹理权重初始化为0
				o.hatchWeights0 = fixed3(0,0,0);
				o.hatchWeights1 = fixed3(0,0,0);

				float hatchFactor = diff * 7.0;

				//根据hatchFactor决定纹理混合权重
				if(hatchFactor > 6.0)
				{
				 //不处理,光照最亮部分 后边做纯白处理
				 //六个纹理的权重,存储在hatchWeights0 hatchWeights1中的xyz分量中
				}else if (hatchFactor > 5.0) {
					o.hatchWeights0.x = hatchFactor - 5.0;
				} else if (hatchFactor > 4.0) {
					o.hatchWeights0.x = hatchFactor - 4.0;
					o.hatchWeights0.y = 1.0 - o.hatchWeights0.x;
				} else if (hatchFactor > 3.0) {
					o.hatchWeights0.y = hatchFactor - 3.0;
					o.hatchWeights0.z = 1.0 - o.hatchWeights0.y;
				} else if (hatchFactor > 2.0) {
					o.hatchWeights0.z = hatchFactor - 2.0;
					o.hatchWeights1.x = 1.0 - o.hatchWeights0.z;
				} else if (hatchFactor > 1.0) {
					o.hatchWeights1.x = hatchFactor - 1.0;
					o.hatchWeights1.y = 1.0 - o.hatchWeights1.x;
				} else {
					o.hatchWeights1.y = hatchFactor;
					o.hatchWeights1.z = 1.0 - o.hatchWeights1.y;
				}

				o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
				//计算阴影纹理的采样座标(TEXCOORD4)
				TRANSFER_SHADOW(o);
				return o;
			}

			fixed4 frag(v2f i):SV_Target
			{
				//对六张纹理进行采样 * 权重 = 采样的颜色
				fixed4 hatchTex0 = tex2D(_Hatch0, i.uv) * i.hatchWeights0.x;
				fixed4 hatchTex1 = tex2D(_Hatch1, i.uv) * i.hatchWeights0.y;
				fixed4 hatchTex2 = tex2D(_Hatch2, i.uv) * i.hatchWeights0.z;
				fixed4 hatchTex3 = tex2D(_Hatch3, i.uv) * i.hatchWeights1.x;
				fixed4 hatchTex4 = tex2D(_Hatch4, i.uv) * i.hatchWeights1.y;
				fixed4 hatchTex5 = tex2D(_Hatch5, i.uv) * i.hatchWeights1.z;	

				//纯白颜色 * 纯白颜色的混合权重 = 纯白颜色的采样颜色
				fixed4 whiteColor = fixed4(1, 1, 1, 1) * (1 - i.hatchWeights0.x - i.hatchWeights0.y - i.hatchWeights0.z - 
							i.hatchWeights1.x - i.hatchWeights1.y - i.hatchWeights1.z);	
				//经过权重采样后的6张纹理采样颜色 和 纯白颜色
				fixed4 hatchColor = hatchTex0 + hatchTex1 + hatchTex2 + hatchTex3 + hatchTex4 + hatchTex5 + whiteColor;
				//计算阴影和光照衰减
				UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
				return fixed4(hatchColor.xyz * _Color.rgb * atten,1.0);
			}

			ENDCG
		}
	}
	FallBack "Diffuse"
}

注解:

                //uv范围放大,然后跟每个纹理对应的权重对比,在权重范围内,采样对应的纹理

                o.uv = v.texcoord.xy * _TileFactor;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章