UnityShader描边实现整理

描边

1、基于观察角度和表面法线的轮廓线渲染

在这里插入图片描述

  1. 基本思路:以归一化的顶点到摄像机的向量和归一化的顶点法线向量的点乘为参考值,该值越小(越接近于0),该像素就越解决于边缘。
  2. 优点:简单快速,只需要一个Pass。
  3. 缺点:局限性大,很多模型渲染出的效果不尽人意。
Shader "Unlit/RimLight"
{
    Properties
    {
        _RimColor("RimColor",Color) = (1,1,1,1)
        _RimPower("Power",Range(0,10)) = 0.1
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"


            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _RimColor;
            float _RimPower;


            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float2 uv : TEXCOORD1;
                float3 worldViewDir : TEXCOORD2;
            };



            v2f vert (appdata_base v)
            {
                v2f o;
                //顶点坐标从模型空间转换到世界空间
                o.pos = UnityObjectToClipPos(v.vertex);
                //调整uv坐标,必须定义_MainTex_ST变量
                o.uv = TRANSFORM_TEX(v.texcoord,_MainTex); 
                //将法线向量从模型空间变换到世界空间
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                //获取世界空间,顶点到摄像机的方向向量
                o.worldViewDir = WorldSpaceViewDir(v.vertex );
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                //获取归一化的法线向量
                fixed3 worldNormal = normalize(i.worldNormal);

                //根据贴图,环境光,和漫反射,计算颜色
                fixed4 color = tex2D(_MainTex,i.uv);

                //获取归一化的顶点到摄像机的方向向量
                float3 worldViewDir = normalize(i.worldViewDir);
                //根据摄像机方向和法线,计算边缘光权重
                float rim = 1-saturate(dot(worldViewDir,worldNormal));

                //使用_RimPower控制强度
                fixed3 rimColor = _RimColor * pow(rim,_RimPower);

                //将边缘光叠加到最终颜色
                color.rgb = color.rgb+rimColor;
                return color;
            }
            ENDCG
        }
    }
}

2、过程式几何轮廓线

背面顶点偏移

  1. 基本思路:用两个Pass渲染,一个Pass正常渲染模型正面,另一个Pass将背面顶点延法线方向向外偏移。
    在这里插入图片描述

    1. 优点:快速有效,适用于大多数边缘平滑的模型。
    2. 缺点:不适用于棱角分明的模型,边缘会出现断裂。
  2. 基本思路:在模型空间下将背面顶点延原点到顶点的向量偏移。
    在这里插入图片描述

    1. 优点:不会出现断裂,适用于简单的几何体
    2. 切点:对于复杂的模型,效果不佳
Shader "Unlit/OutlineModel"
{
    Properties
    {
        _OutlineCol("OutlineCol",Color) = (1,0,0,1)
        _OutlineFactor("OutlineFactor",Range(0,1)) = 0.1
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            //裁剪模型的正面
            Cull Front
            Offset 1,1  
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            fixed4 _OutlineCol;
            float _OutlineFactor;

            struct v2f
            {
                float4 pos : SV_POSITION;
            };


            v2f vert (appdata_base v)
            {
                v2f o;


                o.pos = UnityObjectToClipPos(v.vertex);

                


                //方法一:将法线转换到投影空间,在投影阶段进行偏移
                //有断裂
                float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV,v.normal);
                float3 offset = TransformViewToProjection(viewNormal);
				//调整法线z坐标,防止遮挡正面渲染
				offset.z = -0.5f;
				o.pos.xyz+=offset * _OutlineFactor;
                
                //方法二:将顶点坐标作为方向矢量,转换到投影空间,以此为基础偏移
                //没有断裂
                 float3 dir = normalize(v.vertex.xyz);
				 float4 newPos = v.vertex;
				 newPos.xyz += dir * _OutlineFactor;
				 o.pos = UnityObjectToClipPos(newPos);
                


                
                
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return _OutlineCol;
            }
            ENDCG
        }


        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD1;
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                return o;
            }

            fixed4 frag(v2f i):SV_TARGET
            {
                return tex2D(_MainTex,i.uv);
            }


            ENDCG
        }
    }
}

添加新三角面+模板测试

详细内容博客

  1. 基于几何着色器和模板检测的描边
    在这里插入图片描述

  2. 思路:将所有三角面的顶点延法线方向向外一段距离添加一个顶点,每个三角面添加6个三角形作为描边。使用模板测试丢弃和正常渲染的模型重合的像素。

  3. 优点:描边没有断裂,不需要模型具有背面模型,只会描画出模型真正的边缘。

  4. 缺点:耗费性能

    Shader "Unlit/几何着色器+模板"
    {
        Properties
        {
            _MainTex ("Texture", 2D) = "white" {}
    		_OutlineColor("LineColor", Color) = (1,1,1,1)
    		_OutlineWidth("LineWidth",Range(0.001,0.01)) = 0.001
        }
        SubShader
        {
            Tags { "RenderType"="Opaque" }
            LOD 100
    
            Pass
            {
    
    			Stencil{
    				Ref 1
    				Comp Always
    				Pass Replace
    			}
    
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    
                struct appdata
                {
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                struct v2f
                {
                    float2 uv : TEXCOORD0;
                    float4 vertex : SV_POSITION;
                };
    
                sampler2D _MainTex;
                float4 _MainTex_ST;
    
                v2f vert (appdata v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    // sample the texture
                    fixed4 col = tex2D(_MainTex, i.uv);
                    return col;
                }
                ENDCG
            }
    
    		Pass{
    			
    			Stencil{
    				Ref 1
    				Comp NotEqual
    				
    			}
    
    			CGPROGRAM
    			
    			#pragma vertex vert
    			#pragma geometry geom
    			#pragma fragment frag
    
    			#include "UnityCG.cginc"
    
    			struct v2gf
    			{
    				float4 vertex : TEXCOORD0;
    				float3 normal : NORMAL;
    				float4 pos : SV_POSITION;
    			};
    
    
    			
    			fixed4 _OutlineColor;
    			fixed _OutlineWidth;
    
    			v2gf vert(appdata_base i)
    			{
    				v2gf o;
    				o.vertex = i.vertex;
    				o.normal = i.normal;
    				o.pos = UnityObjectToClipPos(i.vertex);
    
    				return o;
    			}
    
    			//获取外扩后的顶点
    			v2gf Offset( v2gf i)
    			{
    				v2gf o = i;
    				//将法线从模型空间转换到观察空间
    				float3 vNormal = mul((float3x3)UNITY_MATRIX_IT_MV,o.normal);
    				//投影空间版本
    				//将法线的xy分量,转换到投影空间
    				float2 vOffset = TransformViewToProjection(vNormal.xy);
    				//归一化
    				vOffset = normalize(vOffset);
    				
    				o.pos = UnityObjectToClipPos(o.vertex);
    				o.pos.xy += vOffset  * _OutlineWidth;
    
    				//观察空间版本
    				//float4 vPos = mul(UNITY_MATRIX_MV,o.vertex);
    				
    				//vPos = vPos + float4(normalize(vNormal),0)* _OutlineWidth;
    				//o.pos = mul(UNITY_MATRIX_P,vPos);
    
    
    				return o;
    			}
    
    			[maxvertexcount(18)]
    			void geom(triangle v2gf inputTriangle[3], inout TriangleStream<v2gf> outStream)
    			{
    				//获取拓展的三个顶点
    				v2gf outTriangle0 = Offset(inputTriangle[0]);
    				v2gf outTriangle1 = Offset(inputTriangle[1]);
    				v2gf outTriangle2 = Offset(inputTriangle[2]);
    			
    				//添加扩展的三角形
    				outStream.Append(inputTriangle[0]);
    				outStream.Append(inputTriangle[1]);
    				outStream.Append(outTriangle1);
    				//outStream.RestartStrip();
    
    				outStream.Append(outTriangle0);
    				outStream.Append(outTriangle1);
    				outStream.Append(inputTriangle[0]);
    				//outStream.RestartStrip();
    
    				outStream.Append(outTriangle1);
    				outStream.Append(outTriangle2);
    				outStream.Append(inputTriangle[2]);
    				//outStream.RestartStrip();
    
    				outStream.Append(inputTriangle[1]);
    				outStream.Append(inputTriangle[2]);
    				outStream.Append(outTriangle1);
    				//outStream.RestartStrip();
    
    
    				outStream.Append(inputTriangle[0]);
    				outStream.Append(inputTriangle[2]);
    				outStream.Append(outTriangle2);
    				//outStream.RestartStrip();
    
    				outStream.Append(outTriangle0);
    				outStream.Append(outTriangle2);
    				outStream.Append(inputTriangle[0]);
    				//outStream.RestartStrip();
    			}
    
    
    			fixed4 frag(v2gf i) : SV_Target{
    				return _OutlineColor;
    			}
    			ENDCG
    
    		}
        }
    }
    
    

3、基于图像处理

来自UnityShader入门精要

基于颜色

  1. 原理:使用屏幕后处理,基于颜色,法线,深度进行轮廓线检测。

  2. 使用卷积操作:使用一个卷积核对每个像素进行操作。卷积核是一个正方形的结构,例如2X2,3X3,每一格有一个权重值,将中心放在像素上,将每一格权重和其覆盖的像素值相乘,将所有结果相加作为该像素的权值。

  3. 轮廓识别常用的卷积核:
    $$
    \left[ \begin{matrix}
    -1 &0
    &\0 &1
    \end{matrix} \right]

    \left[ \begin{matrix}
    -1 &-1 &-1
    &\0 &0 &0
    &\1 &1 &1
    \end{matrix} \right]

    \left[ \begin{matrix}
    -1 &-2 &-1
    &\0 &0 &0
    &\1 &2 &1
    \end{matrix} \right]
    $$

  4. 基于颜色的轮廓检测

在这里插入图片描述

public class OutLines : PostEffectBase
{
    [Range(0f, 1f)]
    //是否只保留轮廓
    public float edgesOnly = 0.0f;
    //轮廓和背景的颜色
    public Color edgeColor = Color.black;
    public Color backgroundColor = Color.white;

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if(_Material != null)
        {
            _Material.SetFloat("_EdgeOnly", edgesOnly);
      
  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在Unity中实现边效果,可以通过编写Shader来实现。具体实现方法如下: 1. 首先,在Shader中需要先声明一个边颜色的变量: ```csharp // 边颜色 _Color ("Outline Color", Color) = (0,0,0,1) ``` 2. 接着,需要在Shader中将表面进行放大,然后将边颜色填充到放大后的表面上: ```csharp // 放大表面,并填充边颜色 float2 borderOffset[4] = {float2(1,0), float2(-1,0), float2(0,1), float2(0,-1)}; float border = _OutlineWidth * (1.0 / _ScreenParams.z); float4 borderColor = _Color; float4 c; for (int i = 0; i < 4; i++) { c = tex2D(_MainTex, i.uv + border * borderOffset[i]); borderColor.a *= c.a; o.Normal += borderColor.a * borderOffset[i]; } ``` 这里,`borderOffset`表示边的偏移量,`border`表示边的宽度,`borderColor`表示边的颜色,`c`表示当前像素点的颜色值。在循环中,依次对当前像素点的上、下、左、右四个方向进行采样,并将采样到的颜色值和边颜色进行叠加。 3. 最后,在Shader中将表面的颜色和边颜色进行混合: ```csharp // 混合表面颜色和边颜色 o.Albedo = lerp(_Color.rgb, o.Albedo, o.Normal); ``` 这里使用了`lerp`函数来对表面颜色和边颜色进行混合,根据边的程度不同,可以调整`_Color`的alpha值来控制边的显示程度。 综上所述,完整的边Shader代码如下: ```csharp Shader "Custom/Outline" { Properties { _MainTex ("Texture", 2D) = "white" {} _Color ("Outline Color", Color) = (0,0,0,1) _OutlineWidth ("Outline Width", Range(0, 2)) = 1 _ScreenParams ("Screen Params", Vector) = (0,0,0,0) } SubShader { Tags {"Queue"="Transparent" "RenderType"="Transparent"} LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float4 Normal : TEXCOORD1; }; sampler2D _MainTex; float4 _Color; float _OutlineWidth; float4 _ScreenParams; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; o.Normal = float4(0,0,0,0); return o; } fixed4 frag (v2f i) : SV_Target { float2 borderOffset[4] = {float2(1,0), float2(-1,0), float2(0,1), float2(0,-1)}; float border = _OutlineWidth * (1.0 / _ScreenParams.z); float4 borderColor = _Color; float4 c; for (int j = 0; j < 4; j++) { c = tex2D(_MainTex, i.uv + border * borderOffset[j]); borderColor.a *= c.a; i.Normal += borderColor.a * borderOffset[j]; } i.Normal.a = 1.0; i.Normal = normalize(i.Normal); fixed4 col = tex2D(_MainTex, i.uv); col.rgb = lerp(_Color.rgb, col.rgb, i.Normal); col.a = col.a * i.Normal.a; return col; } ENDCG } } FallBack "Diffuse" } ``` 使用该Shader,你可以在游戏中对需要边的物体进行材质替换,从而实现边效果。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值