URP/LWRP Shader实现描边效果

2021.1.11 更新:
我觉得我写得比较老了,可以看看下面新整理的文章
LWRP/URP/HDRP中的描边shader:https://zhuanlan.zhihu.com/p/354190065

1. 给Unity内置的基础shader添加自定义属性

在这里插入图片描述
(不要被第一张图劝退,最终效果图在最后面>.< 很完美的~~)
我尝试的这种描边方法,边缘有断开的bug,解决方法①法线做插值???②据说用RenderTexture可以解决 ③用别人写的插件④使用Stencil测试
描边Pass可以参考这篇教程,非常详实
https://blog.csdn.net/puppet_master/article/details/54000951
在这里插入图片描述——我在URP内置的Lit.shader上添加了描边属性(描边颜色和宽度),并且重新定义了Priority区间(从±50扩展为±1500),显示材质球RenderQueue的大小(省去了你切换到Debug模式查看CustomRenderQueue的功夫)
(对于shader我刚刚入门,写这个就想整理下这三天捣鼓的一点东西,可能有愚蠢错误的地方,还望指教)

第一个Pass实现描边效果,第二个Pass实现基础着色。第二个Pass不想自己写的话,也可以用UsePass来借用别的shader。
但是在URP/LWRP里默认只走一个Pass,如果想要运行多个Pass,可以使用Tags:

Tags{"LightMode" = "UniversalForward"} //LWRP不可用此Tag
Tags{"LightMode" = "LightweightForward" } 
Tags{"LightMode" = "SRPDefaultUnlit"} 

一个Pass里放一个Tags,这样看的话似乎URP-shader最多3个Pass,LWRP-shader最多2个Pass?

“To do a multi pass shader in the Lightweight pipeline you have to have one pass have no lightmode defined (or be using the default, which is “LightMode” = “SRPDefaultUnlit”), and one pass use “LightMode” = “LightweightForward”. The lightweight pipeline appears to only use the first pass it finds of each tag, so no more 3+ pass shaders.”——来自 https://forum.unity.com/threads/transparency-using-the-lwrp.550711/

在这里插入图片描述

先来看看URP内置的Lit.shader:↑上图

  • 把它的Properties全部复制黏贴进我们的描边shader
  • 使用UsePass调用它的5个Pass(只用第一个Pass好像也够了)(?)
  • 它第一个Pass已经使用了Tags{“LightMode” = “UniversalForward”} ,所以如果我们想再加一个描边Pass,就必须在Pass里加上其他Tags,或者什么都不加也可以(什么都不加就是默认"LightMode" = “SRPDefaultUnlit” )(?)
  • 为了获得和原Lit.shader一致的GUI布局,注意最后一行【CustomEditor “UnityEditor . Rendering . Universal . ShaderGUI . LitOutlineShaderAddQueue”】,当然这也不是必须的,就是写一个shaderGUI脚本会让面板好看点罢了

新shader命名为LitOutline:↓

//这是一个在URP/Lit基础上加入了描边Pass的shader
//如果看不到描边效果,是因为被后渲染的天空盒覆盖了,需要设置Render Queue=Transparent
//请配合使用脚本 LitOutlineShaderAddQueue.cs 以获得和原shader一致的GUI布局
Shader "Universal Render Pipeline/LitOutline"
{
  Properties
  {
    _OutlineCol("OutlineCol", Color) = (1,1,0,1)
    _OutlineFactor("OutlineFactor", Range(0, 1)) = 0.1
    _ShowRenderQueue("ShowRenderQueue",Int)= -1
    //...
    //下面照搬Lit.shader的Properties
    //复制粘贴大段代码页面会崩溃,我这里就……懒一下
  }
  SubShader
  {
    Tags{"RenderType" = "Transparent" "RenderPipeline" = "UniversalPipeline" "IgnoreProjector" = "True"}
    LOD 300 
    UsePass "Universal Render Pipeline/Lit/ForwardLit"
    //我也不知道后面四个pass具体什么作用,这样引用是否能完整拷贝Lit-shader?
    //如果你用Frame Debug查看的话会发现下面4个Pass并没有用到,所以…大概可以删除?
    UsePass "Universal Render Pipeline/Lit/ShadowCaster"
    UsePass "Universal Render Pipeline/Lit/DepthOnly"
    UsePass "Universal Render Pipeline/Lit/Meta"
    UsePass "Universal Render Pipeline/Lit/Universal2D"

    Pass
    {
    	Name "Outline"
    	Cull Front
    	Zwrite Off
    	CGPROGRAM
    	#pragma vertex vert
        #pragma fragment frag
    	#pragma multi_compile_fog
    	#include "UnityCG.cginc"
    	struct v2f
    	{
    	    UNITY_FOG_COORDS(0)
    	    float4 vertex : SV_POSITION;
    	}
    	fixed4 _OutlineCol;
    	float _OutlineFactor;
    	
    	v2f vert (appdata_base v)
    	{
    	    v2f o;
    	    o.vertex = UnityObjectToClipPos(v.vertex);
    	    float3 vnormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);//将法线方向转换到视空间
    	    vnormal = normalize(vnormal);//为了_OutlineFactor不受物体scale影响
    	    float2 offset = TransformViewToProjection(vnormal.xy);//将视空间法线xy坐标转化到投影空间
    	    o.vertex.xy += offset * _OutlineFactor;//在最终投影阶段输出进行偏移操作
    	    UNITY_TRANSFER_FOG(o,o.vertex);
    	    return o;
    	}
    	fixed4 frag (v2f i) : SV_Target
    	{
    	    return _OutlineCol;
    	}
    	ENDCG
    }
  }
  FallBack "Hidden/Universal Render Pipeline/FallbackError"
  CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.LitOutlineShaderAddQueue"
}

现在,我们写完了LitOutline.shader后还要再仿照原来的LitShader.cs写一个shaderGUI脚本
——直接复制一份LitShader.cs然后重命名为LitOutlineShaderAddQueue.cs开始修改:

  • 想要实现的功能1:在面板里显示描边参数
    添加MaterialProperty类型的变量,outlineCol, outlineFactor,showRenderQueue
    然后用FindProperty()函数将变量与shader中的属性对应,
    最后在OnGUI()函数里用 materialEditor.ShaderProperty()将变量显示到面板上
  • 想要实现的功能2:将Priority的区间从±50扩展为±1500
    为了修改priority的区间,在BaseShaderGUI脚本里查找了一下它是在DrawAdvancedOptions()函数里诞生的,所以重写了这个函数
  • 想要实现的功能3:在面板里显示当前的RenderQueue
    为了能时刻显示当前的RenderQueue,在BaseShaderGUI脚本里发现它是由MaterialChanged()函数更新的,所以也修改了下这个函数

这个GUI脚本命名为LitOutlineShaderAddQueue.cs:↓ 这里就粘贴了我修改和添加的代码行,其他照搬的就点点点表示了

using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEditor.Rendering.Universal;
//这个脚本一定要保存在Editor文件夹下,负责提供 LitOutline.shader的GUI布局
//这个脚本以LitShader.cs为基础,将priority区间扩大至±1000,并且显示RenderQueue数值(RenderQueue不可编辑)
//添加了变量【outlineCol, outlineFactor】 变量【showRenderQueue】
//修改了FindProperties()函数
//修改了MaterialChanged()函数
//修改了DrawAdvancedOptions()函数(使queueOffsetRange = 1000)
//添加了public override void OnGUI()函数

namespace UnityEditor.Rendering.Universal.ShaderGUI
{
    internal class LitOutlineShaderAddQueue : BaseShaderGUI
    {
        // Properties
        private LitGUI.LitProperties litProperties;
        //添加了变量【outlineCol, outlineFactor】 变量【showRenderQueue】
        private MaterialProperty outlineCol, outlineFactor;       
        protected MaterialProperty showRenderQueue { get; set; }
        
        // collect properties from the material properties
        public override void FindProperties(MaterialProperty[] properties)
        {
            base.FindProperties(properties);
            litProperties = new LitGUI.LitProperties(properties);
            //修改了FindProperties()函数
            showRenderQueue = FindProperty("_ShowRenderQueue", properties, false);
            outlineCol = FindProperty("_OutlineCol", properties);
            outlineFactor = FindProperty("_OutlineFactor", properties);
        }
        // material changed check
        public override void MaterialChanged(Material material)
        {
            if (material == null)
                throw new ArgumentNullException("material");
            SetMaterialKeywords(material, LitGUI.SetMaterialKeywords);
            //MaterialChanged()函数触发SetMaterialKeywords()函数触发SetupMaterialBlendMode(),从而更新了 material.renderQueue
            //修改了MaterialChanged()函数
            showRenderQueue.floatValue = material.renderQueue;
        }
        public override void DrawSurfaceOptions(Material material){...}
        public override void DrawSurfaceInputs(Material material){...}
        public override void DrawAdvancedOptions(Material material)
        {
            if (litProperties.reflections != null && litProperties.highlights != null)
            {
                EditorGUI.BeginChangeCheck();
                materialEditor.ShaderProperty(litProperties.highlights, LitGUI.Styles.highlightsText);
                materialEditor.ShaderProperty(litProperties.reflections, LitGUI.Styles.reflectionsText);
                if (EditorGUI.EndChangeCheck())
                {
                    MaterialChanged(material);
                }
            }
            //修改了DrawAdvancedOptions()函数(使queueOffsetRange = 1000)
            //base.DrawAdvancedOptions(material);
            materialEditor.EnableInstancingField();
            if (queueOffsetProp != null)
            {
                EditorGUI.BeginChangeCheck();
                EditorGUI.showMixedValue = queueOffsetProp.hasMixedValue;
                int queueOffsetRange = 1500; //和原先相比就添加了这一行
                var queue = EditorGUILayout.IntSlider(Styles.queueSlider, (int)queueOffsetProp.floatValue, -queueOffsetRange, queueOffsetRange);
                if (EditorGUI.EndChangeCheck())
                {
                    queueOffsetProp.floatValue = queue;
                }                 
                EditorGUI.showMixedValue = false;
            }            
        }
        public override void AssignNewShaderToMaterial(...){...}
        
        //添加了public override void OnGUI()函数
        public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props)
        {
            // render the default gui
            base.OnGUI(materialEditor, props);
            materialEditor.ShaderProperty(showRenderQueue, new GUIContent("RenderQueue(uneditable,controlled by Priority)"));
            materialEditor.ShaderProperty(outlineCol, new GUIContent("OutlineColor"));
            materialEditor.ShaderProperty(outlineFactor, new GUIContent("OutlineFactor"));
        }
      }
    }

LWRP与URP的shader/shaderGUI脚本都是通用的,只需要把出现Lightweight Render Pipeline的地方替换Universal Render Pipeline即可。(还有FallBack里的备用shader也要修改)

2. 免费插件QuickOutline

在这里插入图片描述
打开AssetStore,下载QuickOutline
给你的物体添加Outline.cs脚本,设置描边模式、颜色和粗细,点击运行即可。
原理:脚本会为你的物体添加2个新的材质球,一个是Mask(使用Stencil将物体本体区域标记为1),一个是Fill(将物体本体扩展填充,然后根据Stencil测试把标记为1的区域剔除),注意渲染队列是Fill在Mask后面渲染。

  • 如果你使用Stencil测试,Pass的渲染顺序就是很重要的——关于渲染顺序的问题,请多多用Frame Debug窗口查看~

优点:不会出现明显的断裂;凹面处不会描边;描边功能丰富;描边不会近粗远细
缺点:无法使用于多材质球(有多个sub-mesh)的物体(你会发现只有最后一个sub-mesh得到了描边)
原因

如果物体只有一个single-mesh,在unity里为它添加多个材质球,相当于添加了不同的shader-Pass(你还可以通过控制材质球的渲染队列RenderQueue来控制Pass的渲染顺序)。
但是如果物体有多个submesh(在建模软件里物体已经被赋予多种材质),在unity里会显示材质列表,这个列表是submesh和材质球之间的对应关系,所以你不能通过在列表里追加一个材质球就让它应用给所有的submesh。事实上,你追加的材质球只会被应用给最后一个submesh。 来自https://forum.unity.com/threads/blending-with-all-materials-of-submeshes.51677/
Noisecrime给出了4种方案:
A. 丢弃原来的材质球,赋予新的材质球
B. 重新写shader,把新的属性添加进原来的shader里
D. 复制一份物体,把他的材质球全部替换为新的材质球,这相当于原来的物体负责原来的着色,新复制的物体负责新的材质效果

D→解决方法1
原物体保留原有的材质球,复制一份物体全部替换为Fill材质球,再复制一份物体全部替换为Mask材质球~完结撒花
在这里插入图片描述
B→解决方法2
为Mask的Pass添加标签Tags{“LightMode” = “LightweightForward” },Fill的描边Pass可以不添加标签,然后把这两个Pass整合进你的shader,你可以复制粘贴代码,也可以用UsePass这样的语句。

具体URP的代码看下面↓

3. 整合:把QuickOutline的描边功能写进Unity内置的shader

  • 在Properties里添加了属性_ZTest4Mask,_ZTest4Fill
  • 第1个Pass,走URP/Lit,主着色
  • 第2个Pass,Mask,使用Stencil测试将要剔除的区域标记为1
  • 第3个Pass,Fill,使用Stencil测试保留剔除区域外的填充,即描边
  • 最后更新了GUI脚本【CustomEditor “UnityEditor.Rendering.Universal.ShaderGUI.LitOutlineShaderAddQueueZTest”】

shader完整代码:↓

//这是一个在URP/Lit基础上使用Stencil测试做遮罩实现描边效果的shader
//第1个Pass,走URP/Lit,主着色
//第2个Pass,Mask,使用Stencil测试将要剔除的区域标记为1
//第3个Pass,Fill,使用Stencil测试保留剔除区域外的填充,即描边
//请配合使用脚本 LitOutlineShaderAddQueueZTest.cs 以获得和原shader一致的GUI布局

Shader "Universal Render Pipeline/LitOutline3"
{   
	Properties
    {
        [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest4Mask("ZTest_mask", Float) = 8 //8表示Always
        [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest4Fill("ZTest_fill", Float) = 8
		_OutlineCol("OutlineCol", Color) = (1,1,0,1)
		_OutlineFactor("OutlineFactor", Range(0, 10)) = 2
        _ShowRenderQueue("ShowRenderQueue",Int)= -1
        //使用脚本 LitOutlineShaderAddQueue.cs:Priority参数±1500,RenderQueue参数只读

        // Specular vs Metallic workflow
        [HideInInspector] _WorkflowMode("WorkflowMode", Float) = 1.0

        [MainColor] _BaseColor("Color", Color) = (1,1,1,1)
        [MainTexture] _BaseMap("Albedo", 2D) = "white" {}

        _Cutoff("Alpha Cutoff", Range(0.0, 1.0)) = 0.5

        _Smoothness("Smoothness", Range(0.0, 1.0)) = 0.5
        _GlossMapScale("Smoothness Scale", Range(0.0, 1.0)) = 1.0
        _SmoothnessTextureChannel("Smoothness texture channel", Float) = 0

        [Gamma] _Metallic("Metallic", Range(0.0, 1.0)) = 0.0
        _MetallicGlossMap("Metallic", 2D) = "white" {}

        _SpecColor("Specular", Color) = (0.2, 0.2, 0.2)
        _SpecGlossMap("Specular", 2D) = "white" {}

        [ToggleOff] _SpecularHighlights("Specular Highlights", Float) = 1.0
        [ToggleOff] _EnvironmentReflections("Environment Reflections", Float) = 1.0

        _BumpScale("Scale", Float) = 1.0
        _BumpMap("Normal Map", 2D) = "bump" {}

        _OcclusionStrength("Strength", Range(0.0, 1.0)) = 1.0
        _OcclusionMap("Occlusion", 2D) = "white" {}

        _EmissionColor("Color", Color) = (0,0,0)
        _EmissionMap("Emission", 2D) = "white" {}

        // Blending state
        [HideInInspector] _Surface("__surface", Float) = 0.0
        [HideInInspector] _Blend("__blend", Float) = 0.0
        [HideInInspector] _AlphaClip("__clip", Float) = 0.0
        [HideInInspector] _SrcBlend("__src", Float) = 1.0
        [HideInInspector] _DstBlend("__dst", Float) = 0.0
        [HideInInspector] _ZWrite("__zw", Float) = 1.0
        [HideInInspector] _Cull("__cull", Float) = 2.0

        _ReceiveShadows("Receive Shadows", Float) = 1.0
        // Editmode props
        [HideInInspector] _QueueOffset("Queue offset", Float) = 0.0

        // ObsoleteProperties
        [HideInInspector] _MainTex("BaseMap", 2D) = "white" {}
        [HideInInspector] _Color("Base Color", Color) = (1, 1, 1, 1)
        [HideInInspector] _GlossMapScale("Smoothness", Float) = 0.0
        [HideInInspector] _Glossiness("Smoothness", Float) = 0.0
        [HideInInspector] _GlossyReflections("EnvironmentReflections", Float) = 0.0
    }

    SubShader
    {
        // Universal Pipeline tag is required. If Universal render pipeline is not set in the graphics settings
        // this Subshader will fail. One can add a subshader below or fallback to Standard built-in to make this
        // material work with both Universal Render Pipeline and Builtin Unity Pipeline
        Tags{"RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "IgnoreProjector" = "True"}
		LOD 300
        
        // ------------------------------------------------------------------
        //  Forward pass. Shades all light in a single pass. GI + emission + Fog
        UsePass "Universal Render Pipeline/Lit/ForwardLit"

        //UsePass "Custom/Outline Mask/Mask"  //如果使用UsePass记得修改shader添加Tags
        Pass { //copy from the unity addon QuickOutline
          Name "Mask"
          
          Tags{ "LightMode" = "LightweightForward" } //This line is important!
       
          Cull Off
          ZTest [_ZTest4Mask]
          ZWrite Off
          ColorMask 0
       
          Stencil {
            Ref 1
            Pass Replace
          }
        }

        //UsePass "Custom/Outline Fill/Fill"
        Pass { //copy from the unity addon QuickOutline
           Name "Fill"
           Cull Off
           ZTest [_ZTest4Fill]
           ZWrite Off
           Blend SrcAlpha OneMinusSrcAlpha
           ColorMask RGB
      
           Stencil {
             Ref 1
             Comp NotEqual
           }
      
           CGPROGRAM
           #include "UnityCG.cginc"
      
           #pragma vertex vert
           #pragma fragment frag
      
           struct appdata {
             float4 vertex : POSITION;
             float3 normal : NORMAL;
             float3 smoothNormal : TEXCOORD3;
             UNITY_VERTEX_INPUT_INSTANCE_ID
           };
      
           struct v2f {
             float4 position : SV_POSITION;
             fixed4 color : COLOR;
             UNITY_VERTEX_OUTPUT_STEREO
           };
      
           uniform fixed4 _OutlineCol;
           uniform float _OutlineFactor;
      
           v2f vert(appdata input) {
             v2f output;
      
             UNITY_SETUP_INSTANCE_ID(input);
             UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
      
             float3 normal = any(input.smoothNormal) ? input.smoothNormal : input.normal;
             float3 viewPosition = UnityObjectToViewPos(input.vertex);
             float3 viewNormal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, normal));
      
             output.position = UnityViewToClipPos(viewPosition + viewNormal * -viewPosition.z * _OutlineFactor / 1000.0);
             output.color = _OutlineCol;
      
             return output;
           }
      
           fixed4 frag(v2f input) : SV_Target {
             return input.color;
           }
           ENDCG
         }
    }
    FallBack "Hidden/Universal Render Pipeline/FallbackError"
    //请配合使用脚本 LitOutlineShaderAddQueueZTest.cs 以获得和原shader一致的GUI布局
    CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.LitOutlineShaderAddQueueZTest"// Priority参数±1500,RenderQueue参数只读,增加了ZTest参数
}

因为新加了2个ZTest属性,所以shaderGUI脚本也要小小改动一下:

  • 添加MaterialProperty类型的变量,ztest_mask, ztest_fill
  • 然后用FindProperty()函数将变量与shader中的属性对应
  • 最后在OnGUI()函数里用 materialEditor.ShaderProperty()将变量显示到面板上

shaderGUI完整代码:↓

using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEditor.Rendering.Universal;

//这个脚本一定要保存在Editor文件夹下,负责提供LitOutline3.shader的GUI布局
//这个脚本以LitShader.cs为基础
//LitOutlineShaderAddQueueZTest.cs 将priority区间扩大至±1500,并且显示RenderQueue数值(RenderQueue不可编辑)
//添加了【outlineCol, outlineFactor】
//添加了【showRenderQueue】
//添加了【ztest_mask, ztest_fill】
//修改了FindProperties()函数
//修改了MaterialChanged()函数
//修改了DrawAdvancedOptions()函数(使queueOffsetRange = 1000)
//添加了【public override void OnGUI()】

namespace UnityEditor.Rendering.Universal.ShaderGUI
{
    internal class LitOutlineShaderAddQueueZTest : BaseShaderGUI
    {
        // Properties
        private LitGUI.LitProperties litProperties;

        private MaterialProperty outlineCol, outlineFactor;       

        protected MaterialProperty showRenderQueue { get; set; }

        private MaterialProperty ztest_mask, ztest_fill;

        #region  // LitShader.cs
        // collect properties from the material properties
        public override void FindProperties(MaterialProperty[] properties)
        {
            base.FindProperties(properties);
            litProperties = new LitGUI.LitProperties(properties);

            showRenderQueue = FindProperty("_ShowRenderQueue", properties, false);
            outlineCol = FindProperty("_OutlineCol", properties);
            outlineFactor = FindProperty("_OutlineFactor", properties);

            ztest_mask = FindProperty("_ZTest4Mask", properties);
            ztest_fill = FindProperty("_ZTest4Fill", properties);
        }

        // material changed check
        public override void MaterialChanged(Material material)
        {
            if (material == null)
                throw new ArgumentNullException("material");

            SetMaterialKeywords(material, LitGUI.SetMaterialKeywords);
            //MaterialChanged()函数触发SetMaterialKeywords()函数触发SetupMaterialBlendMode(),从而更新了 material.renderQueue
            //material.renderQueue = renderqueue; //material.renderQueue = (int)renderqueueProp.floatValue;
            showRenderQueue.floatValue = material.renderQueue;
        }

        // material main surface options
        public override void DrawSurfaceOptions(Material material)
        {
            if (material == null)
                throw new ArgumentNullException("material");

            // Use default labelWidth
            EditorGUIUtility.labelWidth = 0f;

            // Detect any changes to the material
            EditorGUI.BeginChangeCheck();
            if (litProperties.workflowMode != null)
            {
                DoPopup(LitGUI.Styles.workflowModeText, litProperties.workflowMode, Enum.GetNames(typeof(LitGUI.WorkflowMode)));
            }
            if (EditorGUI.EndChangeCheck())
            {
                foreach (var obj in blendModeProp.targets)
                    MaterialChanged((Material)obj);
            }
            base.DrawSurfaceOptions(material);
        }

        // material main surface inputs
        public override void DrawSurfaceInputs(Material material)
        {
            base.DrawSurfaceInputs(material);
            LitGUI.Inputs(litProperties, materialEditor, material);
            DrawEmissionProperties(material, true);
            DrawTileOffset(materialEditor, baseMapProp);
        }

        // material main advanced options
        public override void DrawAdvancedOptions(Material material)
        {
            if (litProperties.reflections != null && litProperties.highlights != null)
            {
                EditorGUI.BeginChangeCheck();
                materialEditor.ShaderProperty(litProperties.highlights, LitGUI.Styles.highlightsText);
                materialEditor.ShaderProperty(litProperties.reflections, LitGUI.Styles.reflectionsText);
                if (EditorGUI.EndChangeCheck())
                {
                    MaterialChanged(material);
                }
            }

            //base.DrawAdvancedOptions(material);
            materialEditor.EnableInstancingField();

            if (queueOffsetProp != null)
            {
                EditorGUI.BeginChangeCheck();
                EditorGUI.showMixedValue = queueOffsetProp.hasMixedValue;
                int queueOffsetRange = 1500; //和原先相比就添加了这一行
                var queue = EditorGUILayout.IntSlider(Styles.queueSlider, (int)queueOffsetProp.floatValue, -queueOffsetRange, queueOffsetRange);
                if (EditorGUI.EndChangeCheck())
                {
                    queueOffsetProp.floatValue = queue;
                }
                    
                EditorGUI.showMixedValue = false;
            }          
        }

        public override void AssignNewShaderToMaterial(Material material, Shader oldShader, Shader newShader)
        {
            if (material == null)
                throw new ArgumentNullException("material");

            // _Emission property is lost after assigning Standard shader to the material
            // thus transfer it before assigning the new shader
            if (material.HasProperty("_Emission"))
            {
                material.SetColor("_EmissionColor", material.GetColor("_Emission"));
            }

            base.AssignNewShaderToMaterial(material, oldShader, newShader);

            if (oldShader == null || !oldShader.name.Contains("Legacy Shaders/"))
            {
                SetupMaterialBlendMode(material);
                return;
            }

            SurfaceType surfaceType = SurfaceType.Opaque;
            BlendMode blendMode = BlendMode.Alpha;
            if (oldShader.name.Contains("/Transparent/Cutout/"))
            {
                surfaceType = SurfaceType.Opaque;
                material.SetFloat("_AlphaClip", 1);
            }
            else if (oldShader.name.Contains("/Transparent/"))
            {
                // NOTE: legacy shaders did not provide physically based transparency
                // therefore Fade mode
                surfaceType = SurfaceType.Transparent;
                blendMode = BlendMode.Alpha;
            }
            material.SetFloat("_Surface", (float)surfaceType);
            material.SetFloat("_Blend", (float)blendMode);

            if (oldShader.name.Equals("Standard (Specular setup)"))
            {
                material.SetFloat("_WorkflowMode", (float)LitGUI.WorkflowMode.Specular);
                Texture texture = material.GetTexture("_SpecGlossMap");
                if (texture != null)
                    material.SetTexture("_MetallicSpecGlossMap", texture);
            }
            else
            {
                material.SetFloat("_WorkflowMode", (float)LitGUI.WorkflowMode.Metallic);
                Texture texture = material.GetTexture("_MetallicGlossMap");
                if (texture != null)
                    material.SetTexture("_MetallicSpecGlossMap", texture);
            }

            MaterialChanged(material);
        }

        #endregion

        public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props)
        {
            // render the default gui
            base.OnGUI(materialEditor, props);

            materialEditor.ShaderProperty(showRenderQueue, new GUIContent("RenderQueue(uneditable,controlled by Priority)"));

            materialEditor.ShaderProperty(outlineCol, new GUIContent("OutlineColor"));
            materialEditor.ShaderProperty(outlineFactor, new GUIContent("OutlineFactor"));

            materialEditor.ShaderProperty(ztest_mask, new GUIContent("ZTest of Pass-Mask"));
            materialEditor.ShaderProperty(ztest_fill, new GUIContent("ZTest of Pass-Fill"));
        }
    }
}

补充:(补充也很重要)
QuickOutline插件提供的Outline.cs脚本还具有【光滑法线】【Precompute】等功能
所以我们还需要参考重写这个脚本。

在这里插入图片描述

最后附一个鼠标悬停触发物体高亮的脚本:

//【光滑法线】【预先计算】的内容来自于插件QuickOutline的Outline.cs脚本
//20210111更新,追加 private int[] renderQueues,透明物体也可以享用这个描边shader啦
[DisallowMultipleComponent]
public class HoverHighlight0618 : MonoBehaviour
{
    #region ...鼠标悬停触发高亮描边
    [SerializeField]
    private Color outlineColor = Color.white;
    [SerializeField, Range(0f, 10f)]
    private float outlineFactor = 2f;
    private float outlineFactor_temp = 2f;
    public Shader shader_highlighted;
    public int my_showRenderQueue = 3100;//要在渲染天空盒之后
    private Shader[] originalShaders;
    private Renderer[] renderers;
    private int[] renderQueues;

    void Awake()
    {
	    shader_highlighted = Shader.Find("Universal Render Pipeline/LitOutline3");
	    outlineMode = Mode.OutlineVisible;
        // Cache renderers
        renderers = GetComponentsInChildren<Renderer>();
        //为了定义数组originalShaders,统计物体及其子物体共有多少个材质球
        int count = 0; 
        foreach(var renderer in renderers)
        { count += renderer.materials.Length; }
        originalShaders = new Shader[count];//存储每个材质球原来的shader
        renderQueues = new int[count];
        int i = 0;
        foreach (var renderer in renderers)
        {
            foreach (var mat in renderer.materials)
            {
                originalShaders[i] = mat.shader; //存储每个材质球原来的shader
                renderQueues[i] = mat.renderQueue;
                i++;
            }   
        }
        // Retrieve or generate smooth normals
        LoadSmoothNormals();
    }

    //物体必须有collider 下面的语句才有效
    private void OnMouseEnter()//替换shader;更改Priority来改变mat的render queue
    {
        foreach (var renderer in renderers)
        {
            foreach (var mat in renderer.materials)
            {
                outlineFactor_temp = outlineFactor;
                OutlineMode();
                mat.shader = shader_highlighted;
                mat.SetColor("_OutlineCol", outlineColor);
                mat.SetFloat("_OutlineFactor", outlineFactor_temp);
                int myPriority = mat.renderQueue - my_showRenderQueue + 50;//BaseShaderGUI.queueOffsetRange=50
                mat.SetInt("_QueueOffset", myPriority);//改变priority参数
                mat.renderQueue = my_showRenderQueue;//加写这行是因为,如果inspector面板的材质球不展开,LitOutlineShaderAddQueueZTest.cs这个GUI脚本里的内容就不会执行,也就没有代码会执行renderQueue的更改                
                mat.SetFloat("_ZTest4Mask", ztest_mask);
                mat.SetFloat("_ZTest4Fill", ztest_fill);
            }
        }
    }

    private void OnMouseExit()
    {
        int i = 0;
        foreach (var renderer in renderers)
        {
            foreach (var mat in renderer.materials)
            {
                mat.shader = originalShaders[i];
                mat.renderQueue = renderQueues[i];
                mat.SetInt("_QueueOffset", 0);//改变priority参数 ???
                i++;
            }
        }
    }
    #endregion

    #region ...5种描边模式
    public enum Mode
    {
        OutlineAll,
        OutlineVisible,
        OutlineHidden,
        OutlineAndSilhouette,
        SilhouetteOnly
    }  

    [SerializeField]
    private Mode outlineMode;

    private float ztest_mask, ztest_fill;

    void OutlineMode()
    {
        switch (outlineMode)
        {
            case Mode.OutlineAll:
                ztest_mask = (float)UnityEngine.Rendering.CompareFunction.Always;
                ztest_fill = (float)UnityEngine.Rendering.CompareFunction.Always;
                break;
            case Mode.OutlineVisible:
                ztest_mask= (float)UnityEngine.Rendering.CompareFunction.Always;
                ztest_fill= (float)UnityEngine.Rendering.CompareFunction.LessEqual;
                break;

            case Mode.OutlineHidden:
                ztest_mask = (float)UnityEngine.Rendering.CompareFunction.Always;
                ztest_fill = (float)UnityEngine.Rendering.CompareFunction.Greater;
                break;

            case Mode.OutlineAndSilhouette:
                ztest_mask = (float)UnityEngine.Rendering.CompareFunction.LessEqual;
                ztest_fill = (float)UnityEngine.Rendering.CompareFunction.Always;
                break;

            case Mode.SilhouetteOnly:
                ztest_mask = (float)UnityEngine.Rendering.CompareFunction.LessEqual;
                ztest_fill = (float)UnityEngine.Rendering.CompareFunction.Greater;
                outlineFactor_temp = 0;
                break;
        }
    }
    #endregion

    #region ...光滑法线
    private static HashSet<Mesh> registeredMeshes = new HashSet<Mesh>();
    [Serializable]
    private class ListVector3
    {
        public List<Vector3> data;
    }

    [SerializeField, HideInInspector]
    private List<Mesh> bakeKeys = new List<Mesh>();

    [SerializeField, HideInInspector]
    private List<ListVector3> bakeValues = new List<ListVector3>();
    
    void LoadSmoothNormals()
    {

        // Retrieve or generate smooth normals
        foreach (var meshFilter in GetComponentsInChildren<MeshFilter>())
        {

            // Skip if smooth normals have already been adopted
            if (!registeredMeshes.Add(meshFilter.sharedMesh))
            {
                continue;
            }

            // Retrieve or generate smooth normals
            var index = bakeKeys.IndexOf(meshFilter.sharedMesh);
            var smoothNormals = (index >= 0) ? bakeValues[index].data : SmoothNormals(meshFilter.sharedMesh);

            // Store smooth normals in UV3
            meshFilter.sharedMesh.SetUVs(3, smoothNormals);
        }

        // Clear UV3 on skinned mesh renderers
        foreach (var skinnedMeshRenderer in GetComponentsInChildren<SkinnedMeshRenderer>())
        {
            if (registeredMeshes.Add(skinnedMeshRenderer.sharedMesh))
            {
                skinnedMeshRenderer.sharedMesh.uv4 = new Vector2[skinnedMeshRenderer.sharedMesh.vertexCount];
            }
        }
        Debug.Log("LoadSmoothNormals()");
    }
    List<Vector3> SmoothNormals(Mesh mesh)
    {

        // Group vertices by location
        var groups = mesh.vertices.Select((vertex, index) => new KeyValuePair<Vector3, int>(vertex, index)).GroupBy(pair => pair.Key);

        // Copy normals to a new list
        var smoothNormals = new List<Vector3>(mesh.normals);

        // Average normals for grouped vertices
        foreach (var group in groups)
        {

            // Skip single vertices
            if (group.Count() == 1)
            {
                continue;
            }

            // Calculate the average normal
            var smoothNormal = Vector3.zero;

            foreach (var pair in group)
            {
                smoothNormal += mesh.normals[pair.Value];
            }

            smoothNormal.Normalize();

            // Assign smooth normal to each vertex
            foreach (var pair in group)
            {
                smoothNormals[pair.Value] = smoothNormal;
            }
        }
        Debug.Log("SmoothNormals");
        return smoothNormals;
    }

    #endregion

    /// <summary>
    /// 如果勾选【PreCompute】
    /// </summary>
    #region ...预先计算
    [Header("Optional")]

    [SerializeField, Tooltip("Precompute enabled: Per-vertex calculations are performed in the editor and serialized with the object. "
  + "Precompute disabled: Per-vertex calculations are performed at runtime in Awake(). This may cause a pause for large meshes.")]
    private bool precomputeOutline;


    void OnValidate()
    {

        // Clear cache when baking is disabled or corrupted
        if (!precomputeOutline && bakeKeys.Count != 0 || bakeKeys.Count != bakeValues.Count)
        {
            bakeKeys.Clear();
            bakeValues.Clear();
        }

        // Generate smooth normals when baking is enabled
        if (precomputeOutline && bakeKeys.Count == 0)
        {
            Bake();
        }
    }

    void Bake()
    {
        // Generate smooth normals for each mesh
        var bakedMeshes = new HashSet<Mesh>();

        foreach (var meshFilter in GetComponentsInChildren<MeshFilter>())
        {

            // Skip duplicates
            if (!bakedMeshes.Add(meshFilter.sharedMesh))
            {
                continue;
            }

            // Serialize smooth normals
            var smoothNormals = SmoothNormals(meshFilter.sharedMesh);

            bakeKeys.Add(meshFilter.sharedMesh);
            bakeValues.Add(new ListVector3() { data = smoothNormals });
        }
        Debug.Log("Bake()");
    }

    #endregion
}

4. LWRP实现multi-pass

上面的shader有3个pass,在URP里分配3个tags刚刚好够用,但是LWRP最多只允许2个Pass。
——我想到一种解决方法是:把Mask的stencil测试添加进负责主着色的Pass,用Fill的描边Pass替换先前自己写的描边Pass,这样2个Pass就可以搞定了。尝试结果:失败。因为我不会移植主着色…我写不来代码替换这句【UsePass “Lightweight Render Pipeline/Lit/ForwardLit”】
——第二种方法是添加ShaderTagId(参考:LWRP(URP)学习笔记二多PASS的使用)我使用的Unity版本是2019.1.3f1,在这里DrawObjectsPass这个脚本似乎叫做RenderOpaqueForwardPass和RenderTransparentForwardPass,把这两个脚本都添加新的shaderTag【“ForFill”】,然后shader的Fill-Pass添加【Tags{ “LightMode” = “ForFill” }】,

 //RenderOpaqueForwardPass和RenderTransparentForwardPass.cs添加
 m_ShaderTagIdList.Add(new ShaderTagId("ForFill"));
 //LitOutline.shader的Fill-Pass添加
 Tags{ "LightMode" = "ForFill" }

这样shader就可以运行多Pass了,但是!!!关闭项目再打开,源码脚本又被覆盖回去了,我猜添加新的shaderTag的思路是对的,就是直接改源码这种操作不允许吧。
——最后一种方法:好好学渲染管线吧……
https://gist.github.com/Elringus/69f0da9c71306f1ea0f575cc7568b31a

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.LWRP;

// Inheriting from `ScriptableRendererFeature` will add it to the
// `Renderer Features` list of the custom LWRP renderer data asset.
public class RenderMyCustomPass : ScriptableRendererFeature
{
    private class MyCustomPass : ScriptableRenderPass
    {
        // Just a tag used to pick up a buffer from the pool.
        private const string commandBufferName = nameof(MyCustomPass);
        // Corresponds to `Tags { "LightMode" = "MyCustomPass" }` in the shaders.
        // You have to add this tag for the corresponding shaders to associate them with this pass.
        private static readonly ShaderTagId shaderTag = new ShaderTagId(nameof(MyCustomPass));
        // An arbitrary name to store temporary render texture.
        private static readonly int tempRTPropertyId = Shader.PropertyToID("_TempRT");
        // Name of the grab texture used in the shaders.
        private static readonly int grabTexturePropertyId = Shader.PropertyToID("_MyGrabTexture");

        public MyCustomPass ()
        {
            renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
        }

        public override void Execute (ScriptableRenderContext context, ref RenderingData renderingData)
        {
            // Grab screen texture and assign it to a global texture property.
            var cmd = CommandBufferPool.Get(commandBufferName);
            cmd.GetTemporaryRT(tempRTPropertyId, renderingData.cameraData.cameraTargetDescriptor);
            cmd.Blit(BuiltinRenderTextureType.RenderTexture, tempRTPropertyId);
            cmd.SetGlobalTexture(grabTexturePropertyId, tempRTPropertyId);
            context.ExecuteCommandBuffer(cmd);
            cmd.Clear();
            CommandBufferPool.Release(cmd);

            // Draw the objects that are using materials associated with this pass.
            var drawingSettings = CreateDrawingSettings(shaderTag, ref renderingData, SortingCriteria.CommonTransparent);
            var filteringSettings = new FilteringSettings(RenderQueueRange.transparent);
            context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings);
        }

        public override void FrameCleanup (CommandBuffer cmd)
        {
            base.FrameCleanup(cmd);

            cmd.ReleaseTemporaryRT(tempRTPropertyId);
        }
    }

    private MyCustomPass grabScreenPass;

    public override void Create ()
    {
        grabScreenPass = new MyCustomPass();
    }

    public override void AddRenderPasses (ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(grabScreenPass);
    }
}

在这里插入图片描述

搬运大佬的脚本,配置LWRP Asset,然后在shader的Fill-pass里加上

Tags { "LightMode" = "MyCustomPass" }

我终于在LWRP里实现了multi-pass……虽然看渲染管线的脚本还是犹如天书……
在这里插入图片描述
更新:
你以为这样就能在LWRP里实现描边效果了吗?!?还没完……
坑爹的LWRP,物体RenderQueue一旦大于2500,模型就Transparent了,它就会把它后方的阴影都展示出来
在这里插入图片描述
这样的话,我就必须限制物体RenderQueue小于等于2500,排在Opaque队列里。尝试了好久,最后回到了前面照抄搬运的脚本,配合着FrameDebugger窗口,大概看出个意思,新添加的Tags { “LightMode” = “MyCustomPass” }是定义在Transparent队列之后的,那么我现在就把所有出现“transparent”的语句都替换成opaque,于是哈带有Tags { “LightMode” = “MyCustomPass” }的Pass会运行在Opaque队列之后,天空盒之前。最后注意Fill-Pass的ZWrite要改为On
在这里插入图片描述
————————————————————————————————————————

还没来得及看的东西:

待学习:

CommandBuffer.DrawRenderer
https://docs.unity3d.com/ScriptReference/Rendering.CommandBuffer.DrawRenderer.html?_ga=2.156913403.138523371.1592461566-1907809638.1591859566
这个应该学的~~~

https://www.ronja-tutorials.com/2018/08/18/stencil-buffers.html

Multiple Materials per Object- is it really “bad”?
https://forum.unity.com/threads/multiple-materials-per-object-is-it-really-bad.44712/

https://unity.cn/projects/unity-hdrp-custom-pass-post-processing-hou-chu-li-te-xiao-xue-xi-yi-zong-jie
Unity HDRP Custom Pass (Post processing) 后处理特效学习(一)总结,文字版,
主要介绍如何获取各种延迟渲染的缓冲数据。
比起之前发的文字版,进行了更新,补充了很多内容和图,如果对这个感兴趣的务必重新看一下。

https://unity.cn/projects/unity-hdrp-custom-pass-post-processing-hou-chu-li-te-xiao-xue-xi-er-zong-jie
Unity HDRP Custom Pass (Post processing) 后处理特效学习(二)总结,文字版,
主要介绍如何使用ddx、ddy进行勾边,,如何手动计算进行勾边。
比起视频,补充了很多内容和图。比如说补充了为什么ddx、ddy计算出来的勾边会断线,是GPU是如何计算的。

URP也可以做后处理,用RendererFeature,但是URP的后处理,你就没有GBuffer的数据了,那你只能从别的方面着手。就像官方的RendererFeature例子里的就有一个SobelFilter的勾边处理。
https://github.com/Unity-Technologies/UniversalRenderingExamples

在使用URP(Universal Render Pipeline)时,如果将Lit Shader设置为Transparent,意味着你希望该材质渲染为半透明效果。在这种情况下,渲染顺序变得非常重要,因为它会影响到对象的视觉叠加效果。 在Unity中,半透明物体通常是按照从后到前(由远离摄像机到接近摄像机)的顺序进行渲染的,以确保正确的透明度混合。然而,URP默认的渲染顺序可能并不总是符合特定场景的需求。以下是一些解决URP/Lit Shader设置为Transparent时渲染顺序问题的方法: 1. **使用渲染队列**: - 在Shader文件中,可以通过`Tags`来设置渲染队列。例如,你可以将队列设置为`Transparent`来确保它在不透明物体之后绘制。 ```csharp Tags { "Queue" = "Transparent" } ``` - 这样做可以确保透明物体在大多数不透明物体渲染完毕后再渲染,但不保证它们之间的相对顺序。 2. **使用渲染排序层(Render Layers)**: - 如果需要更细粒度的控制,你可以使用Unity的渲染排序层功能。在需要控制的物体上设置一个特定的Layer,并在相机的渲染设置中为这些Layer指定特定的渲染顺序。 3. **使用相机排序设置(CameraSortingLayer)**: - 通过为相机设置一个排序层,你可以控制相机渲染特定Layer上的物体的顺序。这样,你可以优先渲染某些半透明物体。 4. **自定义渲染顺序**: - 如果以上方法仍不能满足需求,可以通过编写自定义的渲染逻辑来强制特定的渲染顺序。这可能需要使用到Unity的事件回调,如`OnRenderObject`,在这些回调中你可以手动指定哪些物体先渲染。 需要注意的是,URP通常会根据渲染队列自动对材质进行排序,但有时候一些特殊的Shader或者使用了特殊技术(如实例化、遮挡剔除等)的场景可能需要额外处理。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值