【Unity Shader】Unity中利用GrabPass实现玻璃效果 |
您所在的位置:网站首页 › unity玻璃材质球 › 【Unity Shader】Unity中利用GrabPass实现玻璃效果 |
《入门精要》中模拟玻璃是用了Unity里的一个特殊的Pass来实现的,这个Pass就是GrabPass,比起上一篇博客实现镜子的方法,这个方法我认为相对复杂,因此在实现之前需要对GrabPass及实现原理做一个更加详细的介绍。 1 效果及代码 1.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 {}),可以发现右边消耗明显比左边大: 这里是为了提一提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 |