unity3d - Shader ZTest not behaving as expected
问题描述
Shader "Custom/Selected Object" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Emission ("EmissionColor", Color) = (0.5, 0.5, 0.5, 1)
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
_Mode ("Blend Mode", Range(0,3)) = 0
_ZWrite ("ZWrite", Int) = 0
_SrcBlend ("SrcBlend", Int) = 0
_DstBlend ("DstBlend", Int) = 0
_Cutoff ("Alpha Cutoff", Float) = 0.05
}
SubShader{
//Behind other geometry
Pass
{
ZTest GEqual
ZWrite [_ZWrite]
Blend [_SrcBlend] [_DstBlend]
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float2 uv : TEXCOORD0;
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 viewDir : TEXCOORD1;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.normal = UnityObjectToWorldNormal(v.normal);
o.viewDir = normalize(UnityWorldSpaceViewDir(o.pos));
o.uv = v.uv;
return o;
}
sampler2D _MainTex;
fixed4 frag(v2f i) : SV_Target
{
//Note you have to normalize these again since they are being interpolated between vertices
float rim = 1 - dot(normalize(i.normal), normalize(i.viewDir));
fixed4 rimLight = lerp(half4(.95, .95, .95, 1), half4(0.65, 0.65, .95, 1), rim);
fixed4 t = tex2D(_MainTex, i.uv);
clip(t.a < 0.2);
return t * rimLight;
}
ENDCG
}
ZTest Less
ZWrite On
Blend [_SrcBlend][_DstBlend]
//Front geometry
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
// And generate the shadow pass with instancing support
#pragma surface surf Standard fullforwardshadows addshadow alphatest:_Cutoff //alpha:blend //keepalpha
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
#pragma multi_compile __ EMISSIVE_ON
sampler2D _MainTex;
fixed4 _Emission;
fixed4 _Color;
//float _Mode;
struct Input {
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
void surf(Input IN, inout SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
#if EMISSIVE_ON //Glowing
o.Emission = _Emission;
#endif
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Instanced/InstancedSurfaceShader"
}
This shader is applied to an object which the user has selected. It adds a pass to draw parts of the object that may be occluded, like an x-ray effect. The shader is swapped in at runtime, and works as it should if the starting shader is the Unity Standard shader.
Problem: I'm making a shader to use in place of the standard shader in order to enable GPU instancing. When swapping from the instanced shader, the effect is only drawn once the object has completely passed its occluder.
The ZTest GEqual
appears to be failing unless the object is completely past the occluding object. In the images above, the effect seen on the second should also be seen on the first where the object is occluded.
The fact that it works fine with the Standard shader leads me to believe the problem is something I've left out of the instanced shader, here:
Shader "Custom/Instanced/InstancedSurfaceShader - Glow" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Emission ("EmissionColor", Color) = (1, 1, 1, 1)
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
_Mode ("Blend Mode", Range(0,3)) = 0
_Cutoff("Alpha Cutoff", Float) = 0.05
}
SubShader {
ZTest Less
ZWrite On
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
// And generate the shadow pass with instancing support
#pragma surface surf Standard fullforwardshadows addshadow alphatest:_Cutoff
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
// Enable instancing for this shader
#pragma multi_compile_instancing
// Config maxcount. See manual page.
// #pragma instancing_options
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
fixed4 _Emission;
half _Glossiness;
half _Metallic;
// Declare instanced properties inside a cbuffer.
// Each instanced property is an array of by default 500(D3D)/128(GL) elements. Since D3D and GL imposes a certain limitation
// of 64KB and 16KB respectively on the size of a cubffer, the default array size thus allows two matrix arrays in one cbuffer.
// Use maxcount option on #pragma instancing_options directive to specify array size other than default (divided by 4 when used
// for GL).
// https://docs.unity3d.com/Manual/GPUInstancing.html
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color) // Make _Color an instanced property (i.e. an array)
#define _Color_arr Props
UNITY_INSTANCING_BUFFER_END(Props)
void surf (Input IN, inout SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(_Color_arr, _Color);
o.Albedo = c.rgb;
o.Emission = _Emission;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Instanced/InstancedSurfaceShader"
}
And here's the code snippet that directly affects the material/shader at the time of shader switching:
material.SetOverrideTag("RenderType", "");
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
material.SetInt("_ZWrite", 1);
material.DisableKeyword("_ALPHATEST_ON");
material.DisableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = -1;
What in the instanced shader could be causing the x-ray shader's effect to be different?
解决方案
Before answering the actual issue here I need to point out this:
If you have more than two passes for multi-pass Shaders, only the first passes can be instanced. This is because Unity forces the later passes to be rendered together for each object, forcing Material changes.
https://docs.unity3d.com/Manual/GPUInstancing.html
So in essence, with your current implementation using Passes, you won't be able to use GPU instancing anyway.
now, the problem with your shader is that all your passes are opaque, so there is no guarantee in the draw order of the meshes. If the occluders are rendered after the occludee, there won't be any information in the Zbuffer to test against. The solution is simply to increase the render order of the material, for it the be drawn right after all other opaque (ZWrite On) pixels.
推荐阅读
- reactjs - 如何确保材料ui中的按钮都具有相同的长度和高度
- docker - Apache Kafka,发送消息时出现 NotLeaderOrFollowerException
- c# - C#从httplistener发送图片
- java - 运行时未添加 NetBeans Gradle 影子依赖项
- javascript - 即使输入不为空也无法提交表单
- python - 如何在标题旁边列出价格数据?例如:Pepsi Pepsi Max - 330 毫升 LBP 17,999
- python - pycharm django 安装 mysqlclient 失败
- sql-server - 使用别名从一列中为所有行填写数据
- java - 顶点中的文本未居中
- pdf - 如何在 Edge 中打开 pdf,例如在 Internet-Explorer 中使用命令行中的 args 搜索标签?