UnityShader(六)透明效果

[UnityShader学习笔记] UnityShader透明效果及相关原理(深度缓存、深度测试、深度写入和面片剔除等) 阅读详情

一、如何实现透明效果

在Unity中实现透明效果的方式有两种,其一是透明度测试,其二是透明度混合

  • 透明度测试:这种方式不需要关闭深度写入,且实现机制非常简单粗暴。只要一个片元的透明度不满足条件(比如小于某个值),则该片元会被直接舍弃,否则就按照不透明物体的处理方式来处理。它产生的效果要么是完全不透明,要么是完全透明,并不是真正的半透明效果。
  • 透明度混合:这种方式会使用当前片元的透明度作为混合因子,与颜色缓冲中的颜色进行混合。这就需要关闭深度写入。而关闭深度写入意味着我们需要非常小心物体的渲染顺序,否则可能出现渲染问题。

为什么要关注渲染顺序

在之前的Shader中我们并没有关心过物体渲染顺序的问题。这是因为深度缓冲的存在。当渲染一个片元时,需要把它的深度值与已经存在于深度缓冲中的值进行比较,如果它的深度值更大,说明有物体挡住了它,这个片元就不应该被渲染到屏幕上;反之,这个片元就会覆盖掉颜色缓冲中的像素值,它的深度值也会更新到深度缓冲中。也就是说,有深度缓冲在,即便我们先渲染了前面的物体A,再渲染了后面的物体B,也不用担心B会覆盖掉A。因为已经在深度缓冲中判断出B在A的后面。

那么进行透明度混合时为什么要关闭深度写入呢?对于一个半透明物体来讲,其后面的物体应该是可以透过它看到的。但如果开启了深度写入,距离摄像机更近的透明物体的深度就会覆盖掉后面的物体。从而后面物体的表面将会被剔除,我们也就无法通过透明物体观察到后面的物体了。

一旦关闭了深度写入,事情就会变得复杂起来。


比如上面这两个物体,A是半透明物体,B是不透明物体。

  • 如果我们先渲染B,再渲染A。B首先将颜色和深度写入缓冲区。然后渲染A时进行深度测试,发现A离相机更近。因此A的透明度会和缓冲区中的B的颜色进行混合,得到正确的半透明效果。
  • 如果我们先渲染A,再渲染B。A会将颜色写入颜色缓冲,但由于透明物体关闭了深度写入,因此A不会将深度值写入深度缓冲。等到渲染B时,由于深度缓冲中没有数据,所以B会直接将颜色和深度写入缓冲中。结果就是B会覆盖A的颜色,结果是错误的。

那么如果是两个半透明物体又会怎样呢?下图中A和B都是半透明物体。

  • 先渲染B再渲染A。B会正常写入颜色缓冲,然后A会和颜色缓冲中的B的颜色进行混合,得到正确结果。
  • 先渲染A再渲染B。A会先写入颜色缓冲,然后B会与颜色缓冲中A的颜色进行混合。这样混合结果会反过来,看起来像B在A的前面。结果错误。

渲染引擎在渲染前一般会先对物体进行排序。先渲染所有不透明物体,并开启它们的深度测试和深度写入。然后再把半透明物体按从后往前的顺序进行渲染,并开启深度测试,关闭深度写入。尽管如此,仍然会有些不理想的情况:

像上面几种无法得到正确排序顺序的情况,就只能将物体进行拆分或是让透明通道更加柔和,让穿插不是这么明显。

Unity的渲染队列

Unity为了解决渲染顺序的问题,提供了渲染队列这一解决方案。我们可以在SubShader的Tags中决定模型使用哪个渲染队列。

二、透明度测试

接下来我们实现透明度测试效果。首先将之前的纹理采样的Shader复制过来。这里不考虑高光,所以将计算高光的部分删掉

Shader "Unlit/AlphaTest"
{
    Properties
    {
        _Diffuse("Diffuse",Color) = (1,1,1,1)
        _Gloss("Gloss",Range(1,256)) = 20
        _MainTex("MainTex",2D) = "white" {}
    }
    SubShader
    {
        Tags
        {
            "LightMode"="ForwardBase"
        }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            fixed4 _Diffuse;
            float _Gloss;
            sampler2D _MainTex;
            float4 _MainTex_ST;

            struct v2f
            {
                float4 vertex: SV_POSITION;
                fixed3 worldNormal: TEXCOORD0;
                float2 uv:TEXCOORD1;
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                // 将顶点坐标从物体空间转换到裁剪空间
                o.vertex = UnityObjectToClipPos(v.vertex);
                // 将法线从物体空间转换到世界空间
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                // 计算uv坐标
                o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
                return o;
            }

            fixed4 frag(v2f i):SV_Target
            {

                // 获取归一化的光源方向
                const fixed3 worldLight = normalize(_WorldSpaceLightPos0);
                const fixed3 albedo = tex2D(_MainTex,i.uv).rgb;
                // 环境光
                const fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                // 计算漫反射
                const fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * (0.5*dot(i.worldNormal, worldLight)+0.5);
                
                fixed3 color = ambient + diffuse;
                return fixed4(color, 1);
            }
            ENDCG
        }
    }
}

首先定义一个属性作为透明度测试时的判断条件,并在CG中定义与之匹配的变量

_Cutoff("Alpha Cutoff",Range(0,1)) = 0.5

在Tags中将渲染队列设置为「AlphaTest」,并忽略投影器的影响

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

最后在片元着色器中根据纹理的alpha通道的值进行剔除

fixed4 frag(v2f i):SV_Target
{
	// 获取归一化的光源方向
	const fixed3 worldLight = normalize(_WorldSpaceLightPos0);
	const fixed4 texColor = tex2D(_MainTex,i.uv);
	// 根据条件剔除
	if(texColor.a - _Cutoff < 0)
	{
		discard;
	}
	// 环境光
	const fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
	// 计算漫反射
	const fixed3 diffuse = _LightColor0.rgb * texColor.rgb * _Diffuse.rgb * (0.5*dot(i.worldNormal, worldLight)+0.5);
	
	fixed3 color = ambient + diffuse;
	return fixed4(color, 1);
}

找一张alpha值不同的图片作为纹理,得到如下效果

三、透明度混合

Unity提供了设置混合模式的命令——Blend。混合时使用的函数就由该指令决定。Unity提供的Blend指令如下

对于「Blend SrcFactor DstFactor」和「Blend SrcFactor DstFactor,SrcFactorA DstFactorA」两个命令,它们实际上表达的是两个混合公式
Orgb=SrcFactor×Srgb+DstFactor×Drgb O_{rgb}=SrcFactor×S_{rgb}+DstFactor×D_{rgb} Orgb=SrcFactor×Srgb+DstFactor×Drgb
Oa=SrcFactorA×Sa+DstFactorA×Da O_{a}=SrcFactorA×S_{a}+DstFactorA×D_{a} Oa=SrcFactorA×Sa+DstFactorA×Da
其中Orgb、OaO_{rgb}、O_{a}OrgbOa代表混合后的rgb通道和a通道,Srgb、SaS_{rgb}、S_{a}SrgbSa代表源颜色通道,Drgb、DaD_{rgb}、D_{a}DrgbDa代表目标颜色通道。第一个命令只提供了两个因子,意味着rgb通道和a通道的混合因子都是SrcFactor和DstFactor。

那么这些混合因子可以有哪些值呢?

另外,前面的公式将源颜色和目标颜色与混合因子的乘积相加得出最终结果。我们当然也可以自定义其他的混合操作。「BlendOP BlendOperation」的作用就是如此。Unity提供的混合操作如下

下面列出了常见的混合类型,类似于PS中对应的混合效果

// 正常(Normal),即透明度混合
Blend  SrcAlpha  OneMinusSrcAlpha

// 柔和相加(Soft Additive)
Blend  OneMinusDstColor  One

// 正片叠底(Multiply),即相乘
Blend  DstColor  Zero

// 两倍相乘(2x Multiply)
Blend  DstColor  SrcColor

// 变暗(Darken)
BlendOp  Min
Blend  One  One

// 变亮(Lighten)
BlendOp  Max
Blend  One  One

// 滤色(Screen)
Blend  OneMinusDstColor  One

// 等同于
Blend  One  OneMinusSrcColor

// 线性减淡(Linear Dodge)
Blend  One  One

透明度混合实现

接下来我们尝试实现透明度混合。依然是基于之前的纹理采样的Shader进行修改。
首先定义一个属性用来控制整体的透明度,并在CG中声明对应变量

_AlphaScale("Alpha Scale",Range(0,1)) = 1

在Tags中指定渲染队列,忽略投影器影响,将「RenderType」设置为「Transparent」

Tags
{
	"Queue"="Transparent"
	"IgnoreProjector"="True"
	"RenderType"="Transparent"
}

在Pass通道中,将光照模式设置为前项渲染路径,关闭深度写入,并设置混合模式

Tags{ "LightMode"="ForwardBase" }

ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha

最后设置片元着色器返回值中的透明通道

fixed4 frag(v2f i):SV_Target
{
	const fixed3 worldLight = normalize(_WorldSpaceLightPos0);
	const fixed4 texColor = tex2D(_MainTex,i.uv);
	const fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
	const fixed3 diffuse = _LightColor0.rgb * texColor.rgb * _Diffuse.rgb * (0.5*dot(i.worldNormal, worldLight)+0.5);
	fixed3 color = ambient + diffuse;
	
	// 修改返回值中的透明通道
	return fixed4(color, texColor.a * _AlphaScale);
}

效果如下

但假如我们换一个模型,就会发现这样做有些许问题

很明显,当模型网格相互交叉时,会得到错误的半透明效果。这是因为我们关闭了深度写入,无法对模型进行像素级别的深度排序。解决方案是增加一个Pass通道,在新的Pass通道中开启深度写入,但不输出颜色。这样就会先把模型的深度值写入缓冲中。当第二个Pass再进行透明度混合时,由于上一个Pass已经获得了逐像素的正确深度信息,因而该Pass就可以按照深度排序结果进行透明渲染。当然,这样做的缺点也很明显:多使用了一个Pass会对性能造成影响。

// 新增一个Pass  
Pass  
{  
    ZWrite On  
    // 不写入任何颜色通道  
    ColorMask 0  
}

效果如下

Unity 控制物体透明度变化 Unity 物体透明 阅读详情

相关推荐

Unity Shader】(8)透明效果 透明度测试 和 透明度混合 控制 深度缓冲 与 颜色缓冲 并调整 渲染顺序

Unity Shader实现全透明和半透明效果 实现透明物体的两个方法: ※ 对于不透明物体,我们不必考虑它们的渲染顺序也可以得到正确的渲染结果。因为Unity中强大的深度缓冲(Depth Buffer 或称作 Z-Buffer)可以帮助我们完成物体的前后效果。 ※ 对于半透明物体则需要使用透明度混合,且需要正确的渲染顺序。 透明度测试(Alpha Test) 使用的是一个极端霸道的机制——只要一个片元的透明度不满足设定的阈值,那么直接丢弃该片元。被丢弃的片元不会再进行任何处理(包括颜色缓冲);但

1eonChan的博客 4127

unity3d实现物体透明渐变效果

unity3d实现物体透明渐变效果,只需要将脚本放在物体上即可

UnityShader学习——透明效果

文章目录原理Unity Shader的渲染顺序分类实现透明度测试透明度混合其他混合双面渲染 原理 透明是游戏中经常要使用的一种效果。在实时渲染中要实现透明效果,通常会在渲染模型时控制它的透明通道(Alpha Channel)。当开启透明混合后,当一个物体被渲染到屏幕上时,每个片元除了颜色值和深度值之外,它还有另一个属性——透明度。当透明度为1时,表示该像素是完全不透明的,而当其为0时,则表示该像...

方寸想法,编码宇宙 1166

Unity Shader学习3:透明效果

如图8.1中,若先渲染A,由于关闭了半透明物体的深度写入,此时深度缓冲中不会写入A的深度值,再渲染B的时候,由于B是不透明物体,且深度缓冲中没有值,那么B的片元则会通过深度测试,写入深度值,同时覆盖颜色缓冲中A之前渲染得到的颜色值,效果就变成了A被B遮挡。如图8.2中,若先渲染A, 那么A的颜色会先写入颜色缓冲,再渲染B,B片元的颜色值和颜色缓冲的值混合时,混合后的效果会反过来,看起来像B覆盖在A上。它会使用当前片元的透明度做为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。

Little_Double_E的博客 2635

Unity Shader入门精要学习——透明效果

要么完全透明,要么完全不透明。实现简单,实质上是一种剔除机制,通过将不满足条件(通常使用小于某个阈值来判定,一般使用clip方法)的片元舍弃的方法来达到完全透明效果。这些被舍弃的片元不会再进行任何的处理,也不会对颜色缓冲产生任何影响,其余满足条件的片元则会继续按普通的不透明物体的处理方式继续处理。...

番茄猿的博客 3110

UnityShader入门精要学习笔记】第八章 透明效果

想要在实时渲染中实现透明效果,我们通常需要在渲染模型时控制它的**透明通道(Alpha Channel)** 。当开启透明混合之后,物体在渲染时除了颜色值和深度值之外,还有另一个属性——透明度。当透明度为1的时候,表示该像素是完全不透明的,而当其为0代表完全透明

milu_ELK的博客 1455

UnityShader-透明效果

透明度测试 透明度测试: 只要一个片元的透明度不满足条件(通常小于某个值),那么他对应的片元就会舍弃。被舍弃的片元不会进行任何处理,也不会对颜色缓冲产生任何影响;否则,就会按照普通的不透明物体的处理方式来处理它。 关键函数:clip,clip(float4 x)、clip(float3 x)、clip(float2 x)、clip(float x),x的任何一个分量为负数就会舍弃当前像素的输出颜色。 测试代码如下: Shader "cc/shader10" { Properties {

cchoop的博客 576

UnityShader14.1:透明效果实现(下)

接上文:UnityShader14:透明效果实现(上) 四、开启深度写入的透明效果 对比下面两种渲染效果: 前面提到过,透明物体的渲染顺序非常重要,对于一个复杂的透明物体,若不进行网格分割,那么几乎不可能得到正确的渲染结果,就如图一 但是也有一个折中的方案,那就是使用两个 Pass 来渲染模型,第一个 Pass 开启深度写入,把所有被遮挡的片元剔除掉,第二个 Pass 正常渲染,这样就可以得到图二的效果,代码在下: Shader "Jaihk662/AlphaTest1" { ...

加载中... 485

UnityShader(十七)透明效果

在实时渲染中实现透明效果,通常会在渲染模型时控制它的透明通道(Alpha Channel)。当开启透明混合后,一个物体被渲染到屏幕上时每个片元除了颜色值和深度值外,还有一个透明值,1表示像素完全不透明,0表示完全透明

米芝鱼的博客 1213

UnityShader透明效果和渲染顺序

Unity 中实现透明效果的方法: 透明度测试; 透明度混合; 深度缓冲 深度缓冲,根据深度缓存中的值来判断该片元距离摄像机的距离,当渲染一个片元时,需要把它的深度值和已经存在于深度缓冲中的进行比较(如果开启了深度测试),如果它的值距离摄像机更远,说明这个片元不应该被渲染到屏幕上(有物体挡住了它);否则,这个片元应该覆盖掉此时颜色缓冲中的像素值,并把它的深度值更新到深度缓冲中(如果开启了深度写入)。 渲染的顺序 渲染引擎一般都会先对物体进行排序,在渲染。渲染的顺序: 先渲染所有不透明物体,并开启它们

小蜗牛 2845

UnityShader】双面渲染的透明效果

目录总述透明度测试的双面渲染透明度混合的双面渲染透明度测试双面渲染的实现透明度混合双面渲染的实现 总述 在现实生活中,如果一个物体透明的,意味着我们不仅仅可以透过它看到其他物体的样子,也可以看到它内部的结构。但是在前面实现的透明效果中,无论是透明度测试还是透明度混合,都无法观察到正方体内部及其背面的形状,导致物体看起来好像只有半个一样。这是因为,默认情况下渲染引擎剔除了物体背面(相对于摄像机的方向)的渲染图元,而只渲染了物体的正面。如果想要得到双面渲染的效果,可以使用Cull指令来控制需要剔除哪个面的渲染

小蜗牛 1221

Unity Shader入门精要 第八章——透明效果

透明度测试:只要有一个片元的透明度不满足条件(通常是小于某个阈值),那么其对应的片元将会被舍弃。被舍弃的片元将不会再进行任何处理,也不会对颜色缓冲产生任何影响,否则就会按照普通的不透明物体的处理方式来处理它。根据描述,我们发现透明度测试是不需要关闭深度写入,但产生的效果也很极端,要么完全透明,要么完全不透明。我们一般使用clip函数来进行透明度测试。clip是CG当中的一个函数:void clip(float4 x),里面的参数还可以是float3、float2、float2、float1、float。

buzhengli的博客 3110

UnityShader基础(三)——基础透明

为了实现真正意义上的透明,必须关闭模型剔除和深度写入,否则模型本身就会出现遮挡信息以及背面没有透明效果,至于是单Pass还是双Pass,看实际情况。

SliverAsh_的博客 1545

Unity自定义半透明surface材质shader

Shader "Custom/StencilStandardTransparentCull" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _NormalMap("NormalMap", 2D) = "bump" {} _BumpScale("Bump Scale", Float) = 1...

1240

UnityShader学习笔记 ---- 透明效果

在渲染模型时控制它的透明通道(Alpha Channel)。对于不透明(opaque)物体,不考虑它们的渲染顺序也能得到正确的排序结果,这是由于强大的深度缓冲的存在(,也被称为如果要渲染的片元,深度值和深度缓冲的比较,距离摄像机更远,就不应该渲染在屏幕上(有物体遮挡住了)。: “霸道极端”,只要一个片元的透明度不满足条件(通常是小于某个阈值),就舍弃。不需要关闭深度写入(),要么完全透明看不见,要么完全不透明像不透明物体

Wen_Long7834的博客 1684

Unity入门精要03---透明效果

UnityShader入门精要之半透明效果

qq_24917263的博客 2961

unity中如何将物体透明

① 找到物体的material: ② 打开shader右边的下拉栏,点击Legacy Shaders--> 点击 Transparent ③ 点击Diffuse ④ 点击Main Color的颜色设置 ⑤ 将Color中的A设为0 ⑥ 物体透明,over 注意:如果material同时应用于多个物体,此时将material变透明的话,所有应用该material的物体都会变透明; 若是只想将某一个物体透明的话,建议新建一个material,将此mater...

qq_42654827的博客 2万+

unity shader-透明效果

unity shader透明效果

weixin_46525412的博客 1404
上一篇: UnityShader(五)纹理采样
下一篇: UnityShader(七)卡通着色
夜槿笙歌
博客等级 码龄7年 173粉丝 82原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值