URP/LWRP Shader实现描边效果

在这里插入图片描述
在这里插入图片描述——我在URP基础的Lit.shader上添加了描边属性(描边颜色和宽度),并且重新定义了Priority区间(从±50扩展为±1000),显示材质球RenderQueue的大小(省去了你切换到Debug模式查看CustomRenderQueue的功夫)
(对于shader我刚刚入门,写这个就想整理下这三天捣鼓的一点东西,可能有愚蠢错误的地方,还望指教)

描边Pass可以参考这篇教程,非常详实https://blog.csdn.net/puppet_master/article/details/54000951

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

1
2
3
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脚本会让面板好看点罢了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//这是一个在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?
    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开始修改:

  • 添加MaterialProperty类型的变量,
  • 然后用FindProperty()函数将变量与shader中的属性对应,
  • 最后在OnGUI()函数里用 materialEditor.ShaderProperty()将变量显示到面板上
  • 为了修改priority的区间,在BaseShaderGUI脚本里查找了一下它是在DrawAdvancedOptions()函数里诞生的,所以重写了这个函数
  • 为了能时刻显示当前的RenderQueue,在BaseShaderGUI脚本里发现它是由MaterialChanged()函数更新的,所以也修改了下这个函数
1
2
3
4
5
6
7
8
9
10
11
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()函数

这里就复制黏贴一下我修改和添加的代码行,其他照抄的就点点点表示了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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 = 1000; //和原先相比就添加了这一行
                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"));
        }
      }
    }

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

1
在这里插入代码片

LWRP与URP的shader/shaderGUI脚本都是通用的,只需要把出现Lightweight Render Pipeline的地方替换Universal Render Pipeline即可。

待学习:
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