第一种是冯乐乐版的

多附上了阴影和光照
基本原理:渲染队列为Transparent,也就是在所有不透明物体渲染完成之后再渲染毛玻璃,但不用开启混合,因为我们用GrabPass直接获取到渲染前的画面了,使用GrabPass保存玻璃渲染前的屏幕画面。
在shader中,首先在像素着色器中计算出当前像素在屏幕中的位置(范围[0,1]),然后将该位置沿着切线空间的(x,y)偏移,将偏移的结果作为在GrabPass中采样的位置,以实现画面扭曲的效果,得到扭曲后的像素,保存为一个颜色值。为什么不沿z偏移呢,因为世界空间下的高模中大部分顶点的法线不需要扰动,所以将其降为低模并映射到切线空间下的法线纹理后,大部分法线纹理的像素都趋近于(0, 0, 1),所以这种纹理大多偏向蓝色。使用z偏移屏幕坐标的话,效果不好。
此外,保存当前位置的立方体贴图,然后使用立方体映射得到另一个颜色值,然后用参数对以上两个颜色值做插值得到最后的扭曲结果。
最后可以再加上相应的光照和阴影,光照做加法即可。

  1. 计算屏幕坐标
    (我自己理解的二者关系)
 // VSo.pos = UnityObjectToClipPos(v.vertex);float4 srcPos = o.pos * 0.5f;srcPos.xy = float2(srcPos.x, srcPos.y * _ProjectionParams.x) + srcPos.w;srcPos.zw = o.pos.zw;o.srcPos = srcPos;// PS当前像素的实际uv是o.srcPos.xy / o.srcPos.w

_ProjectionParams.x表示directx和opengl的uv坐标系差异,两者的y轴正好相反,可以认为是投影矩阵反转的结果,这个x取值为1或者-1,对应着投影矩阵是否反转。
顶点着色器里实际上是做了①

为什么要这样呢?这个式子①代表什么呢?实际上,对于透视投影来说,将顶点做MVP变换的结果,就是将视锥挤压(缩放)成一个立方体,连带着所有顶点坐标一起缩放,坐标中心是摄像机,结果的每一个分量就是顶点在这个裁剪空间中的坐标。(裁剪是硬件做的,不用管)

但是这个值的w分量不再是1,所以要做齐次除法将w变为1,也就意味着这个坐标的xyz三个分量都要除以w。齐次除法之后,xyz的范围会变成[-1,1](unity),也就是NDC空间下的坐标,接下来只需要将这个范围进行视口变换放到屏幕空间下即可。也就是除以2加0.5。写出齐次除法和视口变换公式即:

那么再看式子①,在着色器中我们可以忽略屏幕宽度和长度,也就是不考虑乘pixelWidth和pixelHeight,但是问题来了,在顶点着色器中进行齐次除法的话,会破坏在像素着色器中的插值结果,因为裁剪空间是非线性空间,而插值是基于三角形重心坐标的线性插值,因此,如果在VS中不做齐次除法的话,插值的对象就是x,而做了的话,插值的对象就是x/w,引入了w这个新的变量,破坏了线性插值的条件,因此等在PS中插值完了再做齐次除法。
因此,①式在像素着色器中除以w,即屏幕坐标(uv)。

Shader "Custom/MyGlassShader"
{Properties{_MainTex ("Main Tex", 2D) = "white" {}_BumpTex ("Bump Tex", 2D) = "bump" {}_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {}_Distortion ("Distortion", Range(0, 100)) = 10_RefractAmount ("Refract Amount", Range(0.0, 1.0)) = 1.0_LightAmount ("Light Amount", Range(0.0, 1.0)) = 1.0}SubShader{Tags { "LightMode" = "ForwardBase" "Queue" = "Transparent" "RenderType"="Opaque" }GrabPass { "_RefractionTex" }Pass{Cull OffCGPROGRAM#pragma multi_compile_fwdbase#pragma vertex VS#pragma fragment PS#include "UnityCG.cginc"#include "Lighting.cginc"#include "AutoLight.cginc"sampler2D _MainTex;float4 _MainTex_ST;sampler2D _BumpTex;float4 _BumpTex_ST;samplerCUBE _Cubemap;float _Distortion;float _RefractAmount;sampler2D _RefractionTex;float4 _RefractionTex_TexelSize;float _LightAmount;struct Input{float3 vertex : POSITION;float3 normal : NORMAL;float4 tangent : TANGENT;float2 texcoord : TEXCOORD0;};struct Output{float4 pos : SV_POSITION;float4 srcPos : TEXCOORD0;float4 uv : TEXCOORD1;float3 posW : TEXCOORD2;float3 tanW : TEXCOORD3;float3 bitanW : TEXCOORD4;float3 normalW : TEXCOORD5;SHADOW_COORDS(6)};Output VS(Input v){Output o;o.pos = UnityObjectToClipPos(v.vertex);o.posW = mul((float3x3)UNITY_MATRIX_M, v.vertex);//o.srcPos = ComputeGrabScreenPos(o.pos);  // 得到该顶点在屏幕上的采样坐标float4 srcPos = o.pos * 0.5f;srcPos.xy = float2(srcPos.x, srcPos.y * _ProjectionParams.x) + srcPos.w;srcPos.zw = o.pos.zw;o.srcPos = srcPos;o.uv.xy = v.texcoord * _MainTex_ST.xy + _MainTex_ST.zw;o.uv.zw = v.texcoord * _BumpTex_ST.xy + _BumpTex_ST.zw;o.normalW = normalize(mul((float3x3)unity_WorldToObject, v.normal));o.tanW = normalize(mul((float3x3)UNITY_MATRIX_M, v.tangent));o.bitanW = cross(o.normalW, o.tanW) * v.tangent.w;TRANSFER_SHADOW(o)return o;}fixed4 PS(Output o) : SV_TARGET{fixed3 bump = UnpackNormal(tex2D(_BumpTex, o.uv.zw));// Compute the offset in tangent spacefloat2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;o.srcPos.xy = offset + o.srcPos.xy;  // 对屏幕采样坐标进行偏移fixed3 refrCol = tex2D(_RefractionTex, o.srcPos.xy / o.srcPos.w).rgb;  //_RefractionTex就是屏幕画面o.normalW = normalize(o.normalW);o.tanW = normalize(o.tanW - dot(o.normalW, o.tanW) * o.normalW);o.bitanW = normalize(o.bitanW);float3x3 t2w = float3x3(o.tanW, o.bitanW, o.normalW);bump = normalize(mul(bump, t2w));fixed3 f2Cam = normalize(_WorldSpaceCameraPos - o.posW);fixed3 reflDir = reflect(-f2Cam, bump);fixed4 texColor = tex2D(_MainTex, o.uv.xy);fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb;fixed3 finalColor = reflCol * (1 - _RefractAmount) + refrCol * _RefractAmount;fixed3 f2Light = normalize(_WorldSpaceLightPos0.xyz);fixed3 albedo = tex2D(_MainTex, o.uv.xy);fixed3 refDir = normalize(reflect(-f2Light, bump));fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo.rgb;fixed3 diffuse = _LightColor0.rgb * albedo.rgb * max(0, dot(bump, f2Light));UNITY_LIGHT_ATTENUATION(atten, o, o.posW);float4 lightColor = float4(ambient + diffuse * atten, 1.0);return fixed4(finalColor + lightColor.rgb * _LightAmount, 1.0);}ENDCG}// Other light source casterPass{Cull OffTags {"LightMode" = "ForwardAdd"}Blend One OneCGPROGRAM#pragma multi_compile_fwdadd_fullshadows#pragma vertex VS#pragma fragment PS#include "Lighting.cginc"#include "AutoLight.cginc"#include "UnityCG.cginc"sampler2D _MainTex;float4 _MainTex_ST;sampler2D _BumpTex;float4 _BumpTex_ST;float _LightAmount;struct Input{float4 vertex : POSITION;float2 uv : TEXCOORD0;float3 normal : NORMAL;float4 tan : TANGENT;};struct Output{float4 pos : SV_POSITION;float3 posW : TEXCOORD0;float4 uv : TEXCOORD1;float3 tanW : TEXCOORD2;float3 bitanW : TEXCOORD3;float3 normalW : TEXCOORD4;SHADOW_COORDS(5)};Output VS(Input v){Output o;o.pos = UnityObjectToClipPos(v.vertex);o.posW = mul((float3x3)UNITY_MATRIX_M, v.vertex);o.uv.xy = v.uv * _MainTex_ST.xy + _MainTex_ST.zw;o.uv.zw = v.uv * _BumpTex_ST.xy + _BumpTex_ST.zw;o.tanW = normalize(mul((float3x3)UNITY_MATRIX_M, v.tan));o.normalW = normalize(mul((float3x3)unity_WorldToObject, v.normal));o.bitanW = normalize(cross(o.tanW, o.normalW)) * v.tan.w;TRANSFER_SHADOW(o);return o;}fixed4 PS(Output o) : SV_Target{o.normalW = normalize(o.normalW);o.tanW = normalize(o.tanW - dot(o.normalW, o.tanW) * o.normalW);o.bitanW = normalize(o.bitanW);float3x3 t2w = float3x3(o.tanW, o.bitanW, o.normalW);float3 bump = UnpackNormal(tex2D(_BumpTex, o.uv.zw));bump = normalize(mul(bump, t2w));#ifdef USING_DIRECTIONAL_LIGHTfixed3 f2Light = normalize(_WorldSpaceLightPos0.xyz);#elsefixed3 f2Light = normalize(_WorldSpaceLightPos0.xyz - o.posW.xyz);#endiffixed3 albedo = tex2D(_MainTex, o.uv.xy);fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, f2Light));UNITY_LIGHT_ATTENUATION(atten, o, o.posW);return fixed4(diffuse * atten * _LightAmount, 1.0);}ENDCG}//  ShadowMapCasterPass{  Tags {"LightMode" = "ShadowCaster"}ZWrite OnZTest LEqualCull OffCGPROGRAM#pragma vertex VS#pragma fragment PS#pragma multi-compile_shadowcaster#include "UnityCG.cginc"struct Output{V2F_SHADOW_CASTER;//float4 pos : SV_POSITION;};Output VS(appdata_base v){Output o;TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);return o;}fixed4 PS(Output o) : COLOR{SHADOW_CASTER_FRAGMENT(o);}ENDCG}}
}

第二种是Commend Buffer版的


这个实现方法有点类似后处理,通过将屏幕的RenderTexture拷贝到临时缓冲中进行画面的操作,进行2次高斯模糊后,将该缓冲作为渲染玻璃的一个纹理资源,然后沿着法线扰动uv后采样模糊纹理,最后与漫反射纹理做插值,大体与第一种方法大体类似,不过手动模拟了前向渲染的多次缓存方式。
CommandBuffer的操作方式是将代码加到OnWillRenderObject中,在每个摄像机(不包括预览摄像机)渲染某物体前调用,所以代码里用了一个字典来存所有的摄像机,毕竟场景里不只有一个Camera.main。

代码

using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;[ExecuteInEditMode]
public class MyGlassCommandBuffer : MonoBehaviour
{public Shader BlurShader;private Material material;private Dictionary<Camera, CommandBuffer> camsDic = new Dictionary<Camera, CommandBuffer>();private void Cleanup(){foreach (var cam in camsDic){if (cam.Key){cam.Key.RemoveCommandBuffer(CameraEvent.AfterSkybox, cam.Value);}}camsDic.Clear();DestroyImmediate(material);}public void OnEnable(){Cleanup();}public void OnDisable(){Cleanup();}// Whenever any camera will render us, add a command buffer to do the work on itpublic void OnWillRenderObject(){var act = gameObject.activeInHierarchy && enabled;if (!act){Cleanup();return;}var cam = Camera.current;if (!cam)return;CommandBuffer buf = null;// Did we already add the command buffer on this camera? Nothing to do then.if (camsDic.ContainsKey(cam))return;if (!material){material = new Material(BlurShader);material.hideFlags = HideFlags.HideAndDontSave;}buf = new CommandBuffer();buf.name = "Grab screen and blur";camsDic[cam] = buf;// copy screen into temporary RTint screenCopyID = Shader.PropertyToID("_ScreenCopyTexture");buf.GetTemporaryRT(screenCopyID, -1, -1, 0, FilterMode.Bilinear);buf.Blit(BuiltinRenderTextureType.CurrentActive, screenCopyID);// get two smaller RTsint blurredID = Shader.PropertyToID("_Temp1");int blurredID2 = Shader.PropertyToID("_Temp2");buf.GetTemporaryRT(blurredID, -2, -2, 0, FilterMode.Bilinear);buf.GetTemporaryRT(blurredID2, -2, -2, 0, FilterMode.Bilinear);// downsample screen copy into smaller RT, release screen RTbuf.Blit(screenCopyID, blurredID);buf.ReleaseTemporaryRT(screenCopyID);// horizontal blurbuf.SetGlobalVector("offsets", new Vector4(2.0f / Screen.width, 0, 0, 0));buf.Blit(blurredID, blurredID2, material);// vertical blurbuf.SetGlobalVector("offsets", new Vector4(0, 2.0f / Screen.height, 0, 0));buf.Blit(blurredID2, blurredID, material);// horizontal blurbuf.SetGlobalVector("offsets", new Vector4(4.0f / Screen.width, 0, 0, 0));buf.Blit(blurredID, blurredID2, material);// vertical blurbuf.SetGlobalVector("offsets", new Vector4(0, 4.0f / Screen.height, 0, 0));buf.Blit(blurredID2, blurredID, material);buf.SetGlobalTexture("_GrabBlurTexture", blurredID);cam.AddCommandBuffer(CameraEvent.AfterSkybox, buf);}
}
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'Shader "Custom/SeparableGlassBlur"
{Properties{_MainTex("Base (RGB)", 2D) = "" {}}Subshader {Pass{ZTest Always Cull Off ZWrite OffCGPROGRAM#pragma fragmentoption ARB_precision_hint_fastest  // 牺牲表现换性能//#pragma ARB_precision_hint_nicest   // 牺牲性能换表现#pragma vertex VS#pragma fragment PS#include "UnityCG.cginc"struct Output{float4 pos : POSITION;float2 uv : TEXCOORD0;float4 uv01 : TEXCOORD1;float4 uv23 : TEXCOORD2;float4 uv45 : TEXCOORD3;};float4 offsets;sampler2D _MainTex;Output VS(appdata_img v){Output o;o.pos = UnityObjectToClipPos(v.vertex);o.uv.xy = v.texcoord.xy;o.uv01 = v.texcoord.xyxy + offsets.xyxy * float4(1, 1, -1, -1);o.uv23 = v.texcoord.xyxy + offsets.xyxy * float4(1, 1, -1, -1) * 2.0;o.uv45 = v.texcoord.xyxy + offsets.xyxy * float4(1, 1, -1, -1) * 3.0;return o;}half4 PS(Output o) : COLOR{half4 color = float4 (0,0,0,0);color += 0.40 * tex2D(_MainTex, o.uv);color += 0.15 * tex2D(_MainTex, o.uv01.xy);color += 0.15 * tex2D(_MainTex, o.uv01.zw);color += 0.10 * tex2D(_MainTex, o.uv23.xy);color += 0.10 * tex2D(_MainTex, o.uv23.zw);color += 0.05 * tex2D(_MainTex, o.uv45.xy);color += 0.05 * tex2D(_MainTex, o.uv45.zw);return color;}ENDCG}}Fallback off
}
Shader "Custom/MyGlassCommandBuffer"
{Properties{_BumpAmt("Distortion", range(0,64)) = 10_TintAmt("Tint Amount", Range(0,1)) = 0.1_MainTex("Tint Color (RGB)", 2D) = "white" {}_BumpMap("Normalmap", 2D) = "bump" {}}Category{Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }SubShader {Pass {Tags { "LightMode" = "Always" }CGPROGRAM#pragma vertex VS#pragma fragment PS#include "UnityCG.cginc"struct Input {float4 vertex : POSITION;float2 texcoord: TEXCOORD0;};struct Output {float4 vertex : POSITION;float4 uvgrab : TEXCOORD0;float2 uvbump : TEXCOORD1;float2 uvmain : TEXCOORD2;};float _BumpAmt;half _TintAmt;float4 _BumpMap_ST;float4 _MainTex_ST;Output VS(Input v){Output o;o.vertex = UnityObjectToClipPos(v.vertex);o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y * _ProjectionParams.x) + o.vertex.w) * 0.5;o.uvgrab.zw = o.vertex.zw;o.uvbump = v.texcoord * _BumpMap_ST.xy + _BumpMap_ST.zw;o.uvmain = v.texcoord * _MainTex_ST.xy + _MainTex_ST.zw;return o;}sampler2D _GrabBlurTexture;float4 _GrabBlurTexture_TexelSize;sampler2D _BumpMap;sampler2D _MainTex;half4 PS(Output i) : SV_Target{half2 bump = UnpackNormal(tex2D(_BumpMap, i.uvbump)).rg;float2 offset = bump * _BumpAmt * _GrabBlurTexture_TexelSize.xy;i.uvgrab.xy = offset * i.uvgrab.z + i.uvgrab.xy;  // 比第一种方法多加了一个z的扰动half4 col = tex2D(_GrabBlurTexture, i.uvgrab.xy / i.uvgrab.w);half4 tint = tex2D(_MainTex, i.uvmain);col = lerp(col, tint, _TintAmt);return col;}ENDCG}}}
}

那么问题来了,正如前面所说的,要渲染的摄像机不止一个,比如我们的渲染纹理相机

从这张图里来看,包括官方示例一样,Transparent物体的渲染结果在玻璃中是错误的。
为什么呢,因为代码中BlurTexture的渲染时机是在渲染Skybox之后、渲染Transparent物体之前,渲染次序图如下:

那么当CommandBuffer的执行时机为AfterSkybox或者Before Forward Alpha(也就是Skybox和Transparencies之间),透明物体都没有渲染,那么模糊纹理中自然不会出现透明物体。
而第一种方法里的GrabPass截取到的屏幕纹理是所有透明物体(除了自己)渲染完毕后的屏幕缓存,因此透明物体可以正常显示。

此外,通过ComputeGrabScreenPos(o.pos)得到的屏幕坐标在像素着色器中可以使用代码tex2Dproj(_BlurTexture, o.srcPos),这个函数会帮我们做齐次除法,而且据网上说这样速度更快。

我还碰上了一个奇怪的bug,当CamerEvent设为After Forward Alpha时,_BlurTexture的渲染相机变成了Scene相机。。。。

Shaderlab 玻璃效果相关推荐

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

    <入门精要>中模拟玻璃是用了Unity里的一个特殊的Pass来实现的,这个Pass就是GrabPass,比起上一篇博客实现镜子的方法,这个方法我认为相对复杂,因此在实现之前需要对GrabP ...

  2. DevExpress中透明玻璃效果

    Aero玻璃效果 下图左是DevExpress无玻璃效果,图右是Windows自带玻璃效果. Windows Aero 是从 Windows Vista 开始使用的新型用户界面,透明玻璃感让用户一眼贯 ...

  3. Windows 7 扩展玻璃效果(Aero Glass)

    Windows 7 操作系统默认具有一款玻璃效果主题(Aero Glass).如果选择了该款主题,所有的应用程序标题栏都会处于玻璃透明效果(如下图).这个功能是由Desktop Window Mana ...

  4. win7下 窗体玻璃效果的实现和WindowStyle None模式下的移动 wpf

    这些技术在上一篇文章的介绍的软件里有用到,现在单独摘出来说明一下. 添加 using System.Runtime.InteropServices; [StructLayout(LayoutKind. ...

  5. Unity shader学习之Grab Pass实现玻璃效果

    GrabPass可将当前屏幕的图像绘制在一张纹理中,可用来实现玻璃效果. 转载请注明出处:http://www.cnblogs.com/jietian331/p/7201324.html shader ...

  6. 使用canvas实现擦玻璃效果

    体验效果:http://hovertree.com/texiao/html5/25/ 效果图: 代码如下: <!DOCTYPE html> <html> <head la ...

  7. iphone app 的图标上被自动添加一层半透明遮罩(玻璃效果),小米3这样的高分屏icon不生效,怎么破?

    ios/info.plist 添加一行: Icon already includes gloss effects = YES 意思就是告诉苹果我提供的图标已经自带玻璃效果了,不用你添加一层遮罩了! 有 ...

  8. Unity Shader之磨砂玻璃与水雾玻璃效果

    导读 玻璃效果是游戏场景中常见的效果之一,除却普通的透明玻璃外,磨砂玻璃也是较为常见的效果.玻璃与场景中的其他物体也会有交互,例如,浴室中的玻璃.雨天的窗户会在水汽的作用下带有一定差别的雾效.本文以U ...

  9. QT-磨砂玻璃效果实现

    QT-磨砂玻璃效果实现 前言 开始 前言 接触QT都挺多年了,今天开始打算把这几年攒下的QT资源记录下来,留个存档吧.今天记录的是windows下(只有win vista|win7支持)的玻璃磨砂效果 ...

  10. UnityShader入门精要-渲染纹理 镜子 玻璃 效果

    通常模式是一个摄像机的渲染结果输出到颜色缓冲内并显示到屏幕上,现代GPU允许我们把场景渲染到一个中间缓冲(渲染目标纹理RTT),与之相关的是多重渲染目标MRT允许我们把场景同时渲染到多个渲染目标中而不 ...

最新文章

  1. Bzoj3060 [Poi2012]Tour de Byteotia
  2. java存款程序_JAVA实现账户取款和存款操作
  3. Linux系统内存管理之伙伴系统分析 - 旭东的博客 - 博客园
  4. MySQL-proxy实现读写分离详细步骤
  5. centos7.8源码编译安装nginx1.17.10
  6. iqn怎么查 linux_程序员必备:46个Linux面试常见问题!收藏!
  7. 第18章 Linux集群架构
  8. java set hashcode_Java学习笔记_180724_HashSet_hashCode()
  9. docker 镜像 增删改查
  10. PostgreSQL安装异常:Problem running post-install step。
  11. 如何给 ReactJS 应用增加配置文件?
  12. ThinkPHP教程
  13. 计算机网络教程实验二——静态路由配置实验心得
  14. 2020暑期实习 总结
  15. 第5章 CUDA存储器
  16. 梦幻西游热门服务器卡顿延迟,梦幻西游:周末活动卡顿的最总原因,是人真的多还是服务器问题?...
  17. icpc徐州网络预赛后的感悟
  18. why哥这里有一道Dubbo高频面试题,请查收。
  19. 文件重命名后缀名没法改,教你轻松解决方法
  20. php字符串6,6.PHP字符串

热门文章

  1. Android快捷方式解密
  2. 《我的团长我的团》书及电视剧观后感
  3. Android 图片查看器选择器 PictureSelector
  4. linux 命令行 双引号,每天一个Linux命令之shell单引号和双引号的经典解释
  5. 多极神经元切片手绘图,神经组织切片手绘图片
  6. 使用excel2007做聚光灯
  7. 程序员必看电影片单,高分烧脑假期必备!
  8. 如何在多个iOSapp里共享数据
  9. 如何用计算机做样本抽样,抽样设计和样本抽选.doc
  10. php网站视频播放外链,视频直接上传到七牛上,在浏览器中输入外链为什么不能直接播放?...