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

索引

  • 1. 给Unity内置的基础shader添加自定义属性
  • 2. 免费插件QuickOutline
  • 3. 整合:把QuickOutline的描边功能写进Unity内置的shader
  • 4. LWRP实现multi-pass

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 FrontZwrite OffCGPROGRAM#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{// Propertiesprivate LitGUI.LitProperties litProperties;//添加了变量【outlineCol, outlineFactor】 变量【showRenderQueue】private MaterialProperty outlineCol, outlineFactor;       protected MaterialProperty showRenderQueue { get; set; }// collect properties from the material propertiespublic 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 checkpublic 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 guibase.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 PipelineTags{"RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "IgnoreProjector" = "True"}LOD 300// ------------------------------------------------------------------//  Forward pass. Shades all light in a single pass. GI + emission + FogUsePass "Universal Render Pipeline/Lit/ForwardLit"//UsePass "Custom/Outline Mask/Mask"  //如果使用UsePass记得修改shader添加TagsPass { //copy from the unity addon QuickOutlineName "Mask"Tags{ "LightMode" = "LightweightForward" } //This line is important!Cull OffZTest [_ZTest4Mask]ZWrite OffColorMask 0Stencil {Ref 1Pass Replace}}//UsePass "Custom/Outline Fill/Fill"Pass { //copy from the unity addon QuickOutlineName "Fill"Cull OffZTest [_ZTest4Fill]ZWrite OffBlend SrcAlpha OneMinusSrcAlphaColorMask RGBStencil {Ref 1Comp NotEqual}CGPROGRAM#include "UnityCG.cginc"#pragma vertex vert#pragma fragment fragstruct 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{// Propertiesprivate 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 propertiespublic 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 checkpublic 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 optionspublic override void DrawSurfaceOptions(Material material){if (material == null)throw new ArgumentNullException("material");// Use default labelWidthEditorGUIUtility.labelWidth = 0f;// Detect any changes to the materialEditorGUI.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 inputspublic override void DrawSurfaceInputs(Material material){base.DrawSurfaceInputs(material);LitGUI.Inputs(litProperties, materialEditor, material);DrawEmissionProperties(material, true);DrawTileOffset(materialEditor, baseMapProp);}// material main advanced optionspublic 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 shaderif (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 modesurfaceType = 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);}#endregionpublic override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props){// render the default guibase.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 renderersrenderers = GetComponentsInChildren<Renderer>();//为了定义数组originalShaders,统计物体及其子物体共有多少个材质球int count = 0; foreach(var renderer in renderers){ count += renderer.materials.Length; }originalShaders = new Shader[count];//存储每个材质球原来的shaderrenderQueues = new int[count];int i = 0;foreach (var renderer in renderers){foreach (var mat in renderer.materials){originalShaders[i] = mat.shader; //存储每个材质球原来的shaderrenderQueues[i] = mat.renderQueue;i++;}   }// Retrieve or generate smooth normalsLoadSmoothNormals();}//物体必须有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=50mat.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 normalsforeach (var meshFilter in GetComponentsInChildren<MeshFilter>()){// Skip if smooth normals have already been adoptedif (!registeredMeshes.Add(meshFilter.sharedMesh)){continue;}// Retrieve or generate smooth normalsvar index = bakeKeys.IndexOf(meshFilter.sharedMesh);var smoothNormals = (index >= 0) ? bakeValues[index].data : SmoothNormals(meshFilter.sharedMesh);// Store smooth normals in UV3meshFilter.sharedMesh.SetUVs(3, smoothNormals);}// Clear UV3 on skinned mesh renderersforeach (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 locationvar groups = mesh.vertices.Select((vertex, index) => new KeyValuePair<Vector3, int>(vertex, index)).GroupBy(pair => pair.Key);// Copy normals to a new listvar smoothNormals = new List<Vector3>(mesh.normals);// Average normals for grouped verticesforeach (var group in groups){// Skip single verticesif (group.Count() == 1){continue;}// Calculate the average normalvar smoothNormal = Vector3.zero;foreach (var pair in group){smoothNormal += mesh.normals[pair.Value];}smoothNormal.Normalize();// Assign smooth normal to each vertexforeach (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 corruptedif (!precomputeOutline && bakeKeys.Count != 0 || bakeKeys.Count != bakeValues.Count){bakeKeys.Clear();bakeValues.Clear();}// Generate smooth normals when baking is enabledif (precomputeOutline && bakeKeys.Count == 0){Bake();}}void Bake(){// Generate smooth normals for each meshvar bakedMeshes = new HashSet<Mesh>();foreach (var meshFilter in GetComponentsInChildren<MeshFilter>()){// Skip duplicatesif (!bakedMeshes.Add(meshFilter.sharedMesh)){continue;}// Serialize smooth normalsvar 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

————————————————————————————————————————

还没来得及看的东西:

  • https://answers.unity.com/questions/1660050/stencil-shader-no-longer-working-with-lightweight.html?childToView=1660770#answer-1660770
  • https://www.techort.com/creating-an-outline-for-lwrp-in-unity-habrahabr/
  • https://blog.csdn.net/nxl76450106/article/details/101290283

待学习:

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/LWRP Shader实现描边效果相关推荐

  1. [Unity]Shader利用Geometry处理实现描边效果

    1.原理 利用几何着色器,生成新的顶点,用新生成的顶点,构建描边,在用模板测试剔除原模型,如下图 灰色三角形是模型原来的一个三角面,用几何着色器,延顶点法线向外,创建出新的顶点A,B,C.已知一个面的 ...

  2. 【Unity Shader 描边效果_案例分享】

    1.实现逻辑 描边效果Shader有多种实现方式,可以通过后处理和MatCap实现. 这次主要想展示的是通过两个Pass实现. 当Shader中有多个Pass时,渲染流程会安装顺序依次执行,于是后面的 ...

  3. Unity使用Shader实现3D模型外描边效果ObjectOutline.shader

    一.前言 有同学问我3D模型的外描边怎么弄,其实网上有很多文章写了实现方式,我就再写个简单的实现和操作流程吧~ 二.3D模型外描边效果 三.如何制作 将最下面的shader代码保存为ObjectOut ...

  4. Unity使用Shader实现3D模型外描边效果

    文章目录 一.前言 二.3D模型外描边效果 三.如何制作 三.shader代码 一.前言 有同学问我3D模型的外描边怎么弄,其实网上有很多文章写了实现方式,我就再写个简单的实现和操作流程吧~ 二.3D ...

  5. Unity Shader - 描边效果

    原文链接:http://blog.csdn.net/puppet_master/article/details/54000951 简介 描边效果是游戏里面非常常用的一种效果,一般是为了凸显游戏中的某个 ...

  6. URP/LWRP学习入门

    要了解什么是URP,首先得先了解什么是SRP.而要了解什么是SRP,又得先了解什么是RenderPipeline.我们先来看看RenderPipeline究竟是什么. RenderPipeline 在 ...

  7. unity 3d物体描边效果_从零开始的卡通渲染描边篇

    序言: 一直对卡通渲染非常感兴趣,前后翻找了不少的文档,做了一些工作.前段时间<从零开始>的手游上线了,试着渲染了一下的其中模型,觉得效果很不错.打算写一个专栏记录其中的渲染技术.在后面的 ...

  8. Unity Shader-Command Buffer的使用(景深与描边效果重置版)

    Unity Shader-Command Buffer的使用(景深与描边效果重置版) https://blog.csdn.net/puppet_master/article/details/72669 ...

  9. Unity HilightingSystem屏幕后实现物体外发光描边效果

    Unity实现物体外发光描边效果方式有好几种,如重叠放大模型描边Pass.卷积核描边.屏幕后处理等. HIightingSytem使用了屏幕后期效果实现,效果如下: 整理出核心代码如下,主要分为4个步 ...

最新文章

  1. jQuery Mobile发展新闻阅读器,适应iphone和android打电话
  2. bim推荐计算机配置,BIM建模推荐电脑配置清单 适合Revit软件的BIM建模电脑主机配置(2)...
  3. c语言使用函数累加由n个a构成的整数之和,c 语言使用函数累加由n个a构成的整数之和...
  4. Linux提权:常用三种方法
  5. 研究生开题报告需要注意的几点
  6. linux终端怎么设置monaco,Monaco Editor 使用指南
  7. PID参数整定法(2)
  8. m.2接口和nvme区别_M.2接口硬盘当真速度就快吗?这些不懂就别乱买!今天再说一遍...
  9. 人人都在谈的图数据库到底是个啥?
  10. java数组 方法_Java数组的十大方法
  11. 认知心理学告诉你什么才是高效学习
  12. Geotrellis系列文章链接
  13. Notes for Linux Administration Handbook (1) : Booting and Shutting Down
  14. 表格求和和计算机不一致6,(电子行业企业管理)计算机电子表格公式应用常见错误及处理(6页)-原创力文档...
  15. 如何在你朋友面前伪装黑客3(程序代码)
  16. centos7进程限制、打开文件限制等
  17. windows命令行下schtasks创建定期任务
  18. html仿网易云网站,GitHub - Hdoove/music-webapp: 仿网易云webapp
  19. HTML5实现3D校园地图思路
  20. hackrf前期安装日志

热门文章

  1. linux的影子系统,Linux_利用Ubuntu卸掉影子系统2008试用版, 俺的xp系统装在I盘,可影 - phpStudy...
  2. gicv3 spi register
  3. netflow generator: 开源工具nfgen的源码研读和修改(上)
  4. 谈谈java中封装的那点事
  5. Python条码识别:使用Python进行条形码识别的详细教程
  6. 税费申报计算机代码,C语言编写一个计算个人所得税的程序,要求输入收入金额,能够输...
  7. 抓包工具 charles 在线破解方法,支持4.2.6版本
  8. wubi安装Ubuntu正确方法及无限等待下载amd64.tar.xz解决方法
  9. 转 给SSD(固态硬盘)编程
  10. Webpack安装与配置