一切混乱开端的透明效果——ShaderCp8

2023-03-10,,

——20.8.18

Unity中,通常用两种方法来实现透明效果 1)透明度测试 2)透明度混合 这两个分别是什么呢

1.透明度测试指的只要不符合条件(即在物体颜色中的alpha通道的值小于某一个阈值)对应片元会被直接舍弃。(该片元不会对颜色缓冲有任何影响)否则就按照不透明物体处理。进行深度测试和深度写入。特点:不关闭深度写入 没法做到半透明要么不透明要么全透明

2.透明度混合指的是使用当前片元透明度为混合因子于已经储存的在颜色缓存中的颜色进行混合。要关闭深度写入不关闭深度测试。通过深度测试能够正确的确定非透明物体于透明物体之间的覆盖关系。 特点:关闭深度写入 做到半透明

在我们对上面两个定义进行下一步解读之前先回顾两个概念 深度写入以及深度测试 两者发生的过程是在GPU渲染的时候发生的,即下面这张图的关系

深度测试就是通过摄像机与该片元的距离(也就是深度)于之前存在深度缓存区的深度值进行比较 对应的条件来决定是否舍弃该片元 ZTest

深度写入则是是否要将深度测试的结果覆盖到深度缓存区中 因为半透明物体的存在是不可以作为深度的标准来舍弃因为半透明物体与其他物体是叠加的关系的 ZWrite

接着我们就可以引入一个新的重要性的问题 就是渲染顺序 这是在Tags中的一个元素 “Queue”

因为我们对半透明物体关闭了深度写入的操作 则对我们的渲染顺序有一定的要求也就是在关闭了深度写入的条件下渲染顺序的正确与否便是十分重要因为会引发两个问题 1)对非透明物体与半透明物体之间 因为关闭了深度写入,当半透明物体先渲染由于深度缓存为空则非透明物体直接覆盖 2)半透明物体与半透明物体之间 渲染顺序如果与深度一致(由近到远)则导致看上去位置相反

常用方法:1.先渲染所有不透明物体开启深度测试与写入(“Queue”=“Geometry”) 2.把半透明物体由远到近顺序渲染 并开启深度测试关闭写入(“Queue”=“AlphaTest“)

但依旧还是有问题所以才叫做混乱开端 因为存在重叠现象一个物体有两个渲染顺序(每个像素都有深度,逐像素去遍历无法选取) 便采取分割网格 并且务必使用凸面体 需要具体问题具体分析

下面就是久违的ShaderLab部分了,我的想法是坚决不做搬运工,下面的部分直接选取部分代码说自己的理解 还是要自己敲一遍 最后再上源码 虽然上面部分就是书上的用我的理解包括一些大佬的理解顺序也很像书上的顺序 就是慢慢改进吧 嘿嘿

一、透明度测试

void Clip(x) x:float4,float3,flaot2,float
void Clip(float4 x)
{
  if(any(x < 0))
    discard;
}

  参数x是裁剪时所使用的标量或矢量

  如果给定参数的任何一个分量是负数则直接舍弃该像素

Tags { "RenderType"="TransparentCutOut" "Queue"="AlphaTest" "IgnoreProjector"="True" }

  样例给的要点主要有要在SubShader中设置”Queue“为了透明度测试 ”IgnoreProjector“为了设置不受到投影器的影响

clip(texColor.a - _Cutoff);

  内部参数为原图像的透明度减去_Cutoff(为能接受的阈值) 当这个值小于0则直接discard

源码:

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Unlit/8-1AlphaTest"
{
Properties
{
_Color("Color", Color) = (1,1,1,1)
_MainTex ("Texture", 2D) = "white" {}
_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
}
SubShader
{
Tags { "RenderType"="TransparentCutOut" "Queue"="AlphaTest" "IgnoreProjector"="True" }
Pass
{
Tags{ "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "UnityCG.cginc" struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
}; struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
}; sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
fixed _Cutoff; v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
} fixed4 frag (v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
clip(texColor.a - _Cutoff);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0 * albedo * saturate(dot(worldNormal, worldLightDir));
return fixed4(diffuse + ambient, 1.0);
}
ENDCG
}
}FallBack "Transparent/Cutout/VertexLit"
}

  

二、透明度混合

说到混合就要讲混合指令Blend

采用的是以下的公式 源颜色就是该片元的颜色 目标颜色就是颜色缓存的颜色

ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha

  要关闭深度写入 同时开启混合(设置的时候即开启)如果不打开blend默认是关闭的就以深度测试的结构决定直接覆盖A=1(?没找到依据但实验结果如此)

源码:

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Unlit/8-2AlphaBlend"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color ("Color", Color) = (1,1,1,1)
_AlphaScale ("AlphaScale", Range(0,1)) = 1
}
SubShader
{
Tags { "RenderType"="Transparent" "IgnoreProjector"="True" "Queue"="Transparent" }
Pass
{
Tags { "LightMode"="ForwardBase" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "UnityCG.cginc" struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
}; struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
}; sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
fixed _AlphaScale; v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
} fixed4 frag (v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(worldNormal, worldLightDir));
return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
}
ENDCG
}
}FallBack "Transparent/VertexLit"
}

三、开启深度写入的半透明

我们可以看一下上面这个图可以发现这个图全露出就是当模型存在互相交叉的时候会得到这样错误的效果 本质的原因就是没有打开深度写入导致的单物体内部错误 那有什么解决方方能就是用双Pass 第一个Pass把最前面的输出片元的深度值通过比较后存入深度缓存中但不输出颜色 第二个Pass再关闭深度写入 可以直接和正确的比较直接discard

ColorMask 0 | RGB | G |

  设置颜色通道的写掩码就是指定输出的颜色通道 0就是不输出

源码:

Shader "Unlit/8-3AlphaBlendZWrite"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color ("Color", Color) = (1,1,1,1)
_AlphaScale ("AlphaScale", Range(0, 1)) = 1
}
SubShader
{
Tags { "RenderType"="Transparent" "IgnoreProjector"="True" "Queue"="Transparent" } Pass{
ZWrite On
ColorMask 0
} Pass
{
Tags { "LightMode"="ForwardBase" }
ZWrite Off
//Alpha Blend
Blend SrcAlpha OneMinusSrcAlpha
//Soft Additive
//Blend OneMinusDstColor One
//Multiply
//Blend DstColor Zero
//Multiply 2X
//Blend DstColor SrcColor
//Darken
//BlendOp min
//Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "UnityCG.cginc" struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
}; struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
}; sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
fixed _AlphaScale; v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
} fixed4 frag (v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(worldNormal, worldLightDir));
return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
}
ENDCG
}
}FallBack "Diffuse"
}

  

这张图有一点奇怪就是存在投影。可以看Cp9的半透明投影部分主要是FallBack设置。

四、双面渲染的透明效果

这个是最后的内容啦 加加油 也就是剔除渲染图元的关系默认的情况下为了节约资源只渲染证明不渲染背面我们则需要用指令打开

Cull off | back | Front

  设置back不渲染back 设置off就是都渲染

那么对应的透明度测试的双目渲染我直接放图代码便是在以上基础上在Pass里面添加以上指令

可以明显对比

然后就是透明度混合的双目渲染 比上述的麻烦了一点就是双Pass 先渲染背面后渲染正面因为从视角看上去避免了之前说的半透明物体与半透明物体之间因为渲染顺序的导致的问题可以直接比较一下 毕竟图形学是效果的学科看看啥效果就知道了

可以很明显看出第二个比第一个有了对面的背面(奇妙的比喻)也就是双目渲染 第三个则是渲染顺序相反背面先渲染 摄像机进入物体能看到颜色鲜艳的正面

最后最后留个小坑就是BlendOp 我也没太看懂可能需要具体例子才会用吧 只有一些效果

谢谢你看到最后 cheers!

ps.刚刚看完Shader入门Cp8记录记录一些细碎的知识点以及相关深度研究学习

一切混乱开端的透明效果——ShaderCp8的相关教程结束。

《一切混乱开端的透明效果——ShaderCp8.doc》

下载本文的Word格式文档,以方便收藏与打印。