本文主要是介绍【Shader笔记】【NPR】卡通渲染-轮廓线的渲染,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
- 写在前面
- 【NPR】轮廓线的渲染
- Surface Angle Silhouette
- 参数&纹理控制
- Procedural Geometry Silhouette
- z-bias和vertex normal的方法
写在前面
本文借鉴大佬的《【NPR】漫谈轮廓线的渲染》
地址:https://blog.csdn.net/candycat1992/article/details/45577749
【NPR】轮廓线的渲染
Surface Angle Silhouette
利用viewpoint和surface normal的点乘结果得到轮廓线信息,结果越接近0,说明离轮廓线越近。在实际应用中,我们通常使用一张一维纹理来模拟,即使用视角方向和顶点法向的点乘对该纹理进行采样。使用了两种方法实现这种技术:
-
一种是使用一个参数_Outline来控制轮廓线的宽度
-
另一种方式是使用了一张一维纹理来控制
参数&纹理控制
使用纹理为:
使用纹理控制的轮廓效果很难控制,有的地方轮廓很宽,有些地方又捕捉不到。
这种方法的优点在于简单快速,可以在一个Pass里得到结果,而且还可以使用texture filtering对轮廓线进行抗锯齿。
不过也有很多的局限性,比如只适用于某些模型,对于像cube这样的模型就会有问题。虽然我们可以使用变量来控制轮廓线的宽度(如果使用纹理的话就是纹理中黑色的宽度),但实际的效果是依赖于表面的曲率(curvature)的。对于像cube这样表面非常平坦的物体,它的轮廓线回发生突变,要么没有,要么全黑。
Shader "NPR/Tex_Surface Angle Sihouetting"
{Properties{ _MainTex ("Base(RGB)", 2D) = "white" {}_Outline("Outline",Range(0,1))=0.4_SilhouetteTex("Silhouette Texture",2D)="white"{} //一维纹理}SubShader{Tags { "RenderType"="Opaque" }LOD 200Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"#include "Lighting.cginc"#include "AutoLight.cginc"sampler2D _MainTex;float _Outline;sampler2D _SilhouetteTex;struct v2f{float4 pos : SV_POSITION;float2 uv : TEXCOORD0;float3 worldLightDir:TEXCOORD1;float3 worldNormal:TEXCOORD2;float3 worldViewDir:TEXCOORD3;};v2f vert (appdata_full v) //appdata_full:包含位置、法线、切线、顶点色和两个纹理坐标{v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv=v.texcoord;o.worldLightDir = UnityWorldSpaceLightDir(v.vertex);o.worldNormal = UnityObjectToWorldNormal(v.normal);o.worldViewDir = UnityWorldSpaceViewDir(v.vertex);TRANSFER_VERTEX_TO_FRAGMENT(o); //宏,在顶点着色器中计算上一步中声明的阴影纹理坐标//TRANSFER_SHADOW(o); 好像现在都是这样了return o;}//用view和normal点乘计算轮廓线的函数fixed3 GetSilhouetteUseConstant(fixed3 normal , fixed3 vierDir){//saturate将数控制在【0,1】内,大于1为1,小于0为0,0-1则为本身fixed edge = saturate ( dot (normal ,vierDir)); edge =edge < _Outline ? edge/4 :1; return fixed3 (edge ,edge,edge);}//使用view和normal的点乘,对一维纹理进行采样的函数fixed3 GetSilhouetteUseTexture(fixed3 normal , fixed3 vierDir){fixed edge = dot(normal ,vierDir);edge = edge *0.5 +0.5;return tex2D(_SilhouetteTex , fixed2(edge,edge)).rgb;}fixed4 frag (v2f i) : SV_Target{fixed3 worldNormal = normalize(i.worldNormal);fixed3 worldLightDir = normalize(i.worldLightDir);fixed3 worldViewDir = normalize(i.worldViewDir);fixed3 col = tex2D(_MainTex , i.uv).rgb;//Use a constant to render Silhouette使用参数来控制轮廓线的宽度//fixed3 silhouetteColor = GetSilhouetteUseConstant (worldNormal ,worldViewDir);//Or use a one dime silhouette Texture使用了一维纹理来控制轮廓线fixed3 silhouetteColor = GetSilhouetteUseTexture( worldNormal ,worldViewDir);fixed4 fragColor;fragColor.rgb = col * silhouetteColor ;fragColor.a =1.0; // 等于return fixed4(fragColor , 1.0)吧return fragColor;}ENDCG}}
}
Procedural Geometry Silhouette
这种方法的核心是用两个Pass渲染:
- 第一个Pass中正常渲染frontfaces
- 第二个Pass中再渲染backfaces,并使用某些技术来让它的轮廓可见。
当然,渲染背面的方法有很多,比如shell or halo method,即沿着法线方向移动backfaces中的顶点
下面列举一些其他渲染背面的方法: - 只渲染backfaces的edges(可以理解把渲染模式设置为DRAW_EDGE),然后使用一些biasing等技术来保证这些线会在frontfaces的前面渲染。
- Z-bias方法。把backfaces渲染成黑色,然后在屏幕空间的z方向上向前移动它们,使其可见。移动的距离可以是一个固定值,或其他适应后的值。
缺点:不能创建宽度相同的轮廓,因为frontface和backface的夹角不一样,可控性很弱。 - Triangle Fatting。把每个backface triangle的edges都“变胖”一定程度,使其在视角空间中看起来宽度是一致的。
缺点:对于一些瘦长的triangles(三角形)来说,它的corner(角)也会变得很细长,一种解决的方法就是把拓展后的edges链接在一起形成斜接在一起的corners。
而且这种方法也无法应用在GPU生成的一些curved surfaces上(因为是弯曲的面,没有edges)。
- Shell or halo method,把backface的顶点沿着顶点法向向外扩张。
优点:很快速,可以在vertex shader中就完成,而且具有一定的健壮性。不需要任何关于相邻顶点/边等信息,所有的处理都是独立的,因此从速度上来说很快。
缺点:像cube这样的模型,它的同一个顶点在不同面上具有不同的顶点法向,所以向外扩张后会形成一个gaps(间隙),一种解决方法是,强迫同一个位置的顶点具有相同的法向。另一个方法是在这些轮廓处创建额外的网格结构。
上面所列出的所有方法都有一个共同的缺点,那就是对轮廓线的外观可控性很少,而且如果没有进行一些反锯齿操作,轮廓线看起来锯齿比较严重。
z-bias和vertex normal的方法
有两个地方需要注意
- 首先对顶点只会在xy方向上扩张,这种操作在模型全是外凸的情况下基本没有什么问题,但是一个模型有内凹的部分就有可能会出现轮廓线挡住frontfaces的情况;
- 另一点是,在扩张顶点时考虑了顶点在投影矩阵中的深度值,这意味着模型轮廓线的宽度会随摄像机移动而改变
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'Shader "NPR/Procedural Geometry Silhouette"
{Properties{_MainTex ("Base(RGB)", 2D) = "white" {}_Outline("Outline",Range(0,1))=0.1}SubShader{Tags { "RenderType"="Opaque" }LOD 200Pass{Tags{"LightMode" = "ForwardBase"}Cull BackLighting OnCGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"#include "Lighting.cginc"#include "AutoLight.cginc"sampler2D _MainTex;struct v2f{float2 uv : TEXCOORD0;float4 pos : SV_POSITION;};v2f vert (appdata_full v) //appdata_full:包含位置、法线、切线、顶点色和两个纹理坐标{v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv = v.texcoord;TRANSFER_VERTEX_TO_FRAGMENT(o);return o;}fixed4 frag (v2f i) : COLOR{fixed3 col = tex2D(_MainTex ,i.uv).rgb;fixed4 fragColor;fragColor.rgb =col;fragColor.a = 1.0;return fragColor;}ENDCG}Pass{Tags{"LightMode" = "ForwardBase"}Cull FrontLighting OffCGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"#include "Lighting.cginc"#include "AutoLight.cginc"sampler2D _MainTex;float _Outline;struct v2f{float2 uv : TEXCOORD0;float4 pos : SV_POSITION;};void ZBiaMethod(appdata_full i , inout v2f o) //顶点沿着法线方向偏移方法一{//简单粗暴,直接与参数_Outline相加float4 viewPos = mul(UNITY_MATRIX_MV , i.vertex);viewPos.z +=_Outline;o.pos = mul(UNITY_MATRIX_P , viewPos);}void VertexNormalMethod0(appdata_full i , inout v2f o) //顶点沿着法线方向偏移方法二{ //将法线变换到视角空间后,再计算offset的程度,之后与顶点相加//缺点:如果直接使用顶点法线进行拓展,对于一些内凹的模型,就可能发生背面面片遮挡正面面片的情况o.pos =UnityObjectToClipPos( i.vertex);//法线转换到视角空间,为了让描边可以在观察空间达到最好的效果float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV , i.normal);//将视空间法线xy坐标转化到投影空间,只有xy需要,z深度不需要了 float2 offset = TransformViewToProjection(normal.xy); //在最终投影阶段输出进行偏移操作o.pos.xy +=offset * o.pos.z * _Outline; }void VertexNormalMethod1(appdata_full i , inout v2f o) //顶点沿着法线方向偏移方法三{ //同理法线转换到视角空间,利用法线的方向与_Outline相乘,得出偏移程度//首先对顶点法线的z分量进行处理,使它们等于一个定值//然后把法线归一化后再对顶点进行扩张。这样的好处在于,拓展后的背面更加扁平化,从而降低了遮挡正面面片的可能性。float4 viewPos = mul(UNITY_MATRIX_MV , i.vertex);float3 normal = mul ( (float3x3) UNITY_MATRIX_IT_MV , i.normal);//设置法线的z分量为定值,对其归一化后再将顶点沿其方向扩张normal.z = -1.0;viewPos = viewPos + float4 (normalize ( normal) ,0) * _Outline;o.pos = mul(UNITY_MATRIX_P , viewPos);}v2f vert ( appdata_full i ){v2f o;//ZBiaMethod (i , o);//VertexNormalMethod0 (i , o);VertexNormalMethod1 (i ,o);o.uv = i.texcoord;TRANSFER_VERTEX_TO_FRAGMENT(o); return o;}fixed4 frag (v2f i) : COLOR{return fixed4 (0,0,0,1);}ENDCG}}FallBack "DIFFUSE"
}
这篇关于【Shader笔记】【NPR】卡通渲染-轮廓线的渲染的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!