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
————————————————————————————————————————
还没来得及看的东西:
- 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