【Unity Shader】Unity中利用GrabPass实现玻璃效果

您所在的位置:网站首页 unity玻璃材质球 【Unity Shader】Unity中利用GrabPass实现玻璃效果

【Unity Shader】Unity中利用GrabPass实现玻璃效果

2023-09-19 08:16| 来源: 网络整理| 查看: 265

《入门精要》中模拟玻璃是用了Unity里的一个特殊的Pass来实现的,这个Pass就是GrabPass,比起上一篇博客实现镜子的方法,这个方法我认为相对复杂,因此在实现之前需要对GrabPass及实现原理做一个更加详细的介绍。

1 效果及代码 1.1 效果

1.2 Shader完整代码 Shader "Unity Shaders Book/Chapter 10/GlassRefraction" { //Properties Properties { _MainTex ("Main Tex", 2D) = "white" {} //玻璃材质纹理 _Cubemap ("EM", Cube) = "_Skybox" {} _BumpMap ("Bump Map", 2D) = "bump" {} //玻璃法线纹理 //control the distortion of refraction _Distortion ("Distortion", range(0, 100)) = 10 _RefractAmount ("Refract Amount", Range(0.0, 1.0)) = 1.0 } SubShader { //Queue must be transparent, opaque objects will be drawn before Tags { "Queue"="Transparent" "RenderType"="Opaque" } //define a pass to grab the screen behind the object, //see the result by using "_RefractionTex" GrabPass {"_RefractionTex"} Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" #include "AutoLight.cginc" //Properties sampler2D _MainTex; float4 _MainTex_ST; samplerCUBE _Cubemap; sampler2D _BumpMap; float4 _BumpMap_ST; float _Distortion; fixed _RefractAmount; //remember to add: sampler2D _RefractionTex; //get the texel size: float4 _RefractionTex_TexelSize; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; float4 tangent : TANGENT; }; struct v2f { float4 pos : SV_POSITION; float4 uv : TEXCOORD0; float4 TtoW0 : TEXCOORD1; float4 TtoW1 : TEXCOORD2; float4 TtoW2 : TEXCOORD3; float4 srcPos : TEXCOORD4; }; v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); //抓取屏幕图像的采样坐标 o.srcPos = ComputeGrabScreenPos(o.pos); o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex); o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap); float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; float3 worldNormal = UnityObjectToWorldNormal(v.normal).xyz; float3 worldTangent = UnityObjectToWorldNormal(v.tangent).xyz; float3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; //计算切线空间 -> 世界空间的矩阵,只需要3x3 //按列摆放 o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x); o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y); o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z); return o; } fixed4 frag(v2f i) :SV_Target { float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w); //计算光照需要参数: fixed3 worldlightDir = normalize(UnityWorldSpaceLightDir(worldPos)); fixed3 worldviewDir = normalize(UnityWorldSpaceViewDir(worldPos)); //对纹理采样+解码,得到法线方向 fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw)); //由于这里需要模拟的是玻璃的折射效果,因此不能是这种常规的法线纹理的偏移: //bump.xy *= _BumpScale; //bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy))); //开始实现折射效果 float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy; i.srcPos.xy = offset + i.srcPos.xy; //采样得到“折射”颜色: fixed3 refractColor = tex2D(_RefractionTex, i.srcPos.xy/i.srcPos.w).rgb; bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump))); //开始环境映射: fixed3 reflectDir = reflect(-worldviewDir, bump); fixed4 texColor = tex2D(_MainTex, i.uv.xy); fixed3 reflectColor = texCUBE(_Cubemap, reflectDir).rgb * texColor.rgb; fixed3 finalColor = reflectColor * (1 - _RefractAmount) + refractColor * _RefractAmount; return fixed4(finalColor, 1.0); } ENDCG } } FallBack "Diffuse" } 2 一些重点过程 2.1 制作场景

场景物体拜访和贴图完全参考《入门精要》:

以及当前场景的Cubemap的创建:由于相同项目下之前已经在Assets -> Edit加了一个可以获取场景中某个GameObject角度的Cubemap,直接按照相似的方法创建了创建过程可以参考【Unity Shader】Unity中如何创建Cubemap?

2.2 关于渲染队列设置

乍一看,SubShader的标签设置好像是前后矛盾的, 渲染队列Queue是Transparent透明的,而当前shader的渲染类型RenderType确是不透明:

Tags { "Queue"="Transparent" "RenderType"="Opaque" } GrabPass {"_RefractionTex"} 设置Queue的作用

我们上效果,如果不加上"Queue"="Transparent",效果如下:

 当前的Shader是挂在外面的Cube上的,从上图效果和1.1的效果对比可以看出,Queue设置为Transparent是为了保证当前屏幕空间画面里的比Cube深度大、但是是不透明的物体(默认的Queue就是不透明)也能渲染并呈现出来,达到“透过玻璃观察”的效果。

设置RenderType的作用

还跟上面一样,假设把"RenderType"="Opaque"去掉,会发现效果跟1.1的没有任何变化。这是因为!RenderType其实是提前给当前的Shader归类了,为了方便之后使用着色器替换(Shader Relacement)的时候,当前Shader能被正确的使用。至于什么是着色器替换,后面会涉及到,这里就先不解释了,挖个坑以后填。

2.3 2种GrabPass的使用方法

参考ShaderLab:GrabPass - Unity 手册,从官方文档中可知GrabPass是ShaderLab语法中的一员,是包含在SubShader内的一种特殊的通道类型,它直接定义了一个额外的抓取屏幕图像的Pass,把即将绘制对象时的屏幕内容抓取到某个纹理中,这个纹理可以在后面的Pass中被使用去做一些效果。它的使用方式通常有两种:

GrabPass {}

即直接在Pass语义块前添加GrabPass {},{}里啥也不写,那么后续抓取屏幕图像的Pass会使用_GrabTexture来访问屏幕图像。这种方法看似方便,省去了定义一个新texture的麻烦。但当场景中多个物体都需要这种形式来抓取屏幕时(我理解的是有多个物体需要做出类似“玻璃”的效果),Unity都会为每个物体单独执行一次这个Pass的抓取操作,每个物体都会生成属于自己的_GrabTexture,这样的效果虽好,但代价是很大的。

GrabPass {"TextureName"}

就像上述代码中的:

GrabPass {"_RefractionTex"}

给我们Pass抓取屏幕图像定义一个专属的、名为"_RefractionTex"的纹理,后续的Pass中如果需要使用,就可以通过这个名称来访问抓取屏幕图像的纹理啦!比如上述代码中的:

fixed3 refractColor = tex2D(_RefractionTex, i.srcPos.xy/i.srcPos.w).rgb;

就是直接使用了定义的纹理名称来访问这个纹理。这样使用方法的好处是,同一个屏幕下多个需要GrabPass的物体都使用同一次渲染出的纹理,也就是仅进行一次GrabPass的抓取图像操作,这样就可以大大节省消耗!而且大部分情况下,都使用一张抓取的图像已经能满足效果需求了。

二者的对比

 这里我们还是用到了Unity提供的Frame Debug,同时为了更好的对比效果,我在场景中多添加了一个想实现透明效果的Cube,这里仅看透明物体的渲染步骤。

使用GrabPass {"_RefractionTex"}时,可以发现步骤中两个Cube是共用同一张Texture的,仅Grab了一次:

而当使用GrabPass {}时,Grab了两次:

两次的RenderTexuter分别是:

二者的消耗对比(左GrabPass {"_RefractionTex"};右GrabPass {}),可以发现右边消耗明显比左边大:

2.4 获取纹素大小:_TexelSize

这里是为了提一提Shader中定义的:

//get the texel size: float4 _RefractionTex_TexelSize;

以后的使用中如果想要获取某张纹理的纹素大小,就可以在纹理名称后加上_TexelSize啦!这个有点类似_MainTex_ST,都是Unity Shader的内置属性~

2.5 ComputerGrabScreenPos函数

这是一个Unity Shader的内置函数,ComputerGrabScreenPos()括号中输入裁剪空间下的顶点位置坐标,可以得到当前被抓取的屏幕图像的屏幕坐标。关于屏幕坐标获得好像有另一个函数?——ComputerScreenPos,那么问题来了:为什么不用ComputerScreenPos?关于这个问题,可以先保留着,我将在接下来的博客中仔细说明(又给自己挖了一个坑。。。)这里仅需要知道ComputerGrabScreenPos()的作用就行!

2.6 如何实现折射效果?

关于折射,我们好像真的学过并使用过一个折射相关的Unity内置函数——Refract(i, n, ri),但这里实现折射并不是真的要实现折射光的效果(太消耗啦!),而是采用GrabPass+给屏幕坐标一个偏移的方式实现玻璃的折射效果。

GrabPass在前面已经介绍过了,这里过一遍如何给屏幕坐标偏移,主要体现在如下代码:

fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw)); //由于这里需要模拟的是玻璃的折射效果,因此不能是这种常规的法线纹理的偏移: //bump.xy *= _BumpScale; //bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy))); //开始实现折射效果 float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy; i.srcPos.xy = offset + i.srcPos.xy; //采样得到“折射”颜色: fixed3 refractColor = tex2D(_RefractionTex, i.srcPos.xy/i.srcPos.w).rgb; 对bump偏移

如果不记得如何在世界空间使用法线纹理了,可以先去看看【Unity Shader】纹理实践5.0:世界空间下使用法线纹理,如果仅实现法线纹理但并不考虑玻璃效果,直接给bump一个传统的变化就行,但这里需要加上玻璃的折射效果,因此还需要结合定义的_Distortion变量和_RefractionTex_TexelSize变量对bump“做手脚”。

_Distorion——控制折射的扭曲程度,其实它的道理跟传统应用中的“_BumpScale”是一样的_RefractionTex_TexelSize——偏移量的大小,当然是根据纹理坐标而偏移 获取折射颜色

tex2D(_RefractionTex, i.srcPos.xy/i.srcPos.w)这里用了一个透视除法!这里涉及到了如何在Unity中获取片元在屏幕上的像素位置,这一点之前在学习基础理论时忽略了,后期会再补上,这里就不赘述(好家伙,又挖了一个坑。。。)。

最终呈现的颜色 fixed3 finalColor = reflectColor * (1 - _RefractAmount) + refractColor * _RefractAmount;

这里用了一个_RefractAmount巧妙地控制了折射和反射的占比(跟之前的环境映射中实现折射效果一样的操作),其实就是一个自行给定的菲涅尔项。

关于实现玻璃效果的过程梳理到这就结束啦!

后面会再出一个关于Unity中GrabPass的使用、包括GrabPass和AlphaBlend的区别、URP下的GrabPass实现方案等等,以及《入门精要》中提到的一个Command Buffers这个新概念,这些点真的有太多太多可以学习的内容了。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3