实践篇:简单透明度
前言:本篇博客只是一个简单的实现透明度功能的例子,主要是当做笔记使用。
核心要点:如下所示:
1.要使模型具有透明度效果,必须在Tags中将Queue指定为Transparent,并且在pass里面使用blend srcalpha oneminussrcalpha。
2.透明度渲染队列(Queue = Transparent)在渲染模型时,会先渲染不透明模型再渲染透明模型。并且在渲染时会按照深度值从大到小(也就是离摄像机由远到近)的顺序渲染。
3.模型的每个像素在渲染时会先进行深度测试,也就是将像素的深度值与深度缓冲区中该像素的同位置处的深度值进行比较。当满足深度测试条件时,会将该像素写入到颜色缓冲区中。如果pass里面开启了深度写入的话,就会将颜色的深度值写入到深度缓冲区中对应位置处。
4.如果pass里面开启了裁剪功能的话,当开启了深度写入时,距离摄像机远的透明模型会被裁剪掉,如果我们不想被裁剪的话,此时就需要关闭深度写入。所以一般情况下,对于不透明模型要开启深度写入,对于透明模型要关闭深度写入。但也不是绝对的,具体情况具体分析,可以写多个pass来控制深度写入和深度测试,从而达到需要的效果。
5.当像素深度值相同时,往往会产生错位混乱的效果。所以当同一个模型的网格上的顶点深度值相同时,可以采用分割网格的方式来使深度值不同。当不同的模型上的顶点深度值相同时,可以采用分割模型的方式来使深度值不同。
6.摄像机会将不透明的模型渲染到一张深度纹理中,所以我们可以借助这一点来做透明模型和不透明模型之间的深度处理。
7.摄像机可以调用SetReplacementShader接口,并传递替换着色器对象和替换标签来对场景中所有满足替换着色器中指定的替换标签和替换标签值的着色器进行替换,按照替换着色器进行渲染,场景中其他不满足替换条件的着色器将不会进行渲染。
制作流程:如下所示:
1.创建一个场景,里面建立两个立方体作为墙面,一个胶囊体作为角色。如图所示:
2.编写一个具有透明度并输出蓝色的着色器,并挂载Wall1上,如上图所示。核心代码如下所示:
Shader "Custom/Transparent_2" {
SubShader {
// 队列为透明度类型。rendertype为transparent目的是让替换着色器进行替换
Tags { "Queue"="Transparent" "rendertype" = "transparent" }
pass {
// 使用透明度混合:当前颜色与颜色缓冲区中颜色混合
blend srcalpha oneminussrcalpha
// 关闭深度写入
ZWrite off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "unitycg.cginc"
struct v2f {
float4 pos:POSITION0;
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
return o;
}
fixed4 frag(v2f IN):COLOR0
{
// 具有透明度的蓝色
fixed4 col = fixed4(0, 0, 1, 0.5);
return col;
}
ENDCG
}
}
}
3.创建一个具有透明度并遮罩部分输出黄色,不遮罩部分输出红色的着色器,并挂载Role上,如上图所示。核心代码如下所示:
Shader "Custom/Transparent_3" {
SubShader {
// 渲染队列为透明度类型
Tags{ "Queue" = "Transparent" }
pass {
// 和颜色缓冲区中的颜色进行透明度混合
blend SrcAlpha OneMinusSrcAlpha
// 由于墙是不透明的,所以深度值写入到深度缓冲区中了。而角色的深度值大于墙的深度值,所以就会满足深度
// 测试从而将墙遮挡部分的像素渲染处理,并和墙渲染到颜色缓冲区的颜色进行透明度混合。
ZTest greater
// 开启深度写入,从而墙遮挡部分(角色下部分)的深度值会写入到深度缓冲区中对应像素所在位置处。
ZWrite on
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "unitycg.cginc"
struct v2f {
float4 pos:POSITION0;
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
return o;
}
fixed4 frag(v2f IN):COLOR0
{
// 遮挡部分为透明黄色
return fixed4(1, 1, 0, 0.5);
}
ENDCG
}
pass {
// 由于角色下部分已经输出颜色并写入深度值,此时没有必要再处理角色下部分。
// 所以使用less做深度测试,这样角色下部分由于深度值相同,深度测试失败不会再做角色下部分渲染。
// 由于角色上部分没有其他模型遮挡,深度值小于天空深度值1的,满足深度测试,此时直接将角色上部分
// 输出到颜色缓冲区中
ZTest less
// 开启深度写入,从而角色不被遮挡部分(角色上部分)的深度值会由写入到深度缓冲区对应像素所在位置处。
ZWrite on
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "unitycg.cginc"
struct v2f {
float4 pos:POSITION0;
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
return o;
}
fixed4 frag(v2f IN):COLOR0
{
// 输出不透明的红色
return fixed4(1, 0, 0, 1);
}
ENDCG
}
}
}
4.创建一个替换着色器,用来对具有透明效果的墙按照替换着色器进行处理。而该替换着色器就是一个按照不透明方式输出模型的深度值。
替换着色器核心代码如下所示:
Shader "Custom/ReplementTransparentShader" {
SubShader {
// 场景中需要被替换的shader类型:标签类型为rendertype,值为transparent
Tags { "rendertype" = "transparent" }
pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "unitycg.cginc"
struct v2f {
float4 pos:POSITION0;
// 齐次空间下的深度和宽度值
float2 depth:TEXCOORD0;
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.depth = o.pos.zw;
return o;
}
fixed4 frag(v2f IN):COLOR0
{
// 获取透视除法后的深度值,并限定在0~1之间
float depth = Linear01Depth(IN.depth.x / IN.depth.y);
// 将深度值写入到颜色r分量中
fixed4 col = fixed4(depth, 0, 0, 0);
return col;
}
ENDCG
}
}
}
替换脚本核心代码如下所示:
using UnityEngine;
using System.Collections;
public class ReplementTransparentShader : MonoBehaviour {
// Use this for initialization
void Start () {
// 使用Custom/ReplementTransparentShader路径的shader中的标签为rendertype,值为该shader该标签对应值,来对
// 场景中所有的同标签同标签值的shader进行置换,由于置换shader是不透明的,所以该置换shader的渲染结果会存放
// 在一张深度纹理中。其他标签和标签值不同的shader不会进行替换渲染。
Camera.main.SetReplacementShader(Shader.Find("Custom/ReplementTransparentShader"), "rendertype");
}
// Update is called once per frame
void Update () {
}
}
5.将替换脚本挂在角色对象上,运行游戏会产生一张记录透明墙的深度纹理,如下图所示:
6.将替换着色器对透明墙生成的深度纹理与角色遮挡部分(角色下部分)进行深度测试,从而得到和不透明墙遮罩部分一样的效果。
7.运行结果如下图所示: