在unity实现毛玻璃(磨砂玻璃)效果,则需要三要素:抓取屏幕、根据渲染体在屏幕上的坐标获取屏幕像素、模糊处理。本文基于Unity官方的实现进行说明,并对一些节点进行适当的展开说明。
抓取屏幕方法有多种,在此不介绍原理了,只介绍方法。
借助于RenderTexture和ReadPixels接口,如此文所示
借助于CommandBuffer就可以在合适的时机将屏幕数据抓取下来,如下所示:
buf = new CommandBuffer();
buf.name = "Grab screen and blur";
m_Cameras[cam] = buf;
// copy screen into temporary RT
int screenCopyID = Shader.PropertyToID("_ScreenCopyTexture");
buf.GetTemporaryRT (screenCopyID, -1, -1, 0, FilterMode.Bilinear);
buf.Blit (BuiltinRenderTextureType.CurrentActive, screenCopyID);
在shader中可以通过sampler2D _GrabBlurTexture;进行屏幕数据采样。这也是官方demo中给的示例。
可以在shader中通过Grabpass直接抓取屏幕:
作为一个pass模块来抓取屏幕,在shader中通过定义GrabPass{}即可实现。GrabPass 根据里面的内容,有两种形式:
1)如果有提供纹理名,如GrabPass{“_TextureName”},则在每一帧为第一个使用名为 “_TextureName” 的纹理的物体进行一次屏幕抓取操作,可以在其它 Pass 中被访问。
2) 没有提供纹理名,Pass 内留空,此时对于每一个使用了 GrabPass 的物体,都会进行一次屏幕抓取操作,内部使用 “_GrabTexture” 来访问屏幕图像
GrabPass 使用方便,但对性能不友好,尤其是移动端。使用demo可以参考此文。
有多种方法可以获取当前渲染对象每个片元在屏幕上的uv坐标。
即在shader中使用ComputeScreenPos就可以获取屏幕坐标点。在顶点着色器中调用 o.screenPos = ComputeScreenPos (o.pos);,然后在片元着色器中通过float2 screenPos = i.screenPos.xy/i.screenPos.w;即可获得uv坐标。
通过此方法也可以实现,但是必须是shader model 3.0,而且在同一个vertex-fragment结构中VPOS语义与SV_POSITION语义无法共存,所以需要特殊处理,如下unity用户手册中例子所示:
Shader "Unlit/Screen Position"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
// note: no SV_POSITION in this struct
struct v2f {
float2 uv : TEXCOORD0;
};
v2f vert (
float4 vertex : POSITION, // vertex position input
float2 uv : TEXCOORD0, // texture coordinate input
out float4 outpos : SV_POSITION // clip space position output
)
{
v2f o;
o.uv = uv;
outpos = UnityObjectToClipPos(vertex);
return o;
}
sampler2D _MainTex;
fixed4 frag (v2f i, UNITY_VPOS_TYPE screenPos : VPOS) : SV_Target
{
// screenPos.xy will contain pixel integer coordinates.
// use them to implement a checkerboard pattern that skips rendering
// 4x4 blocks of pixels
// checker value will be negative for 4x4 blocks of pixels
// in a checkerboard pattern
screenPos.xy = floor(screenPos.xy * 0.25) * 0.5;
float checker = -frac(screenPos.r + screenPos.g);
// clip HLSL instruction stops rendering a pixel if value is negative
clip(checker);
// for pixels that were kept, read the texture and output it
fixed4 c = tex2D (_MainTex, i.uv);
return c;
}
ENDCG
}
}
}
这种方式也是偶然间看到的,第一次处理屏幕uv坐标时层考虑过这种方法,但保险起见未用。不确定是否会有其他影响
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
float4 frag (v2f i) : SV_Target
{
float2 screenPos = i.pos.xy;
return tex2D(_MainTex, i.uv.xy);
}
此方法与ComputeScreenPos有点类似,在一些情况下或者大部分情况下是相同的,但是如果是抓取屏幕GrabPass,那么尽量使用ComputeGrabScreenPos,如果是只是在屏幕空间进行一些计算使用ComputeScreenPos,两者的区别参考此文
抓取屏幕背景后进行模糊的方法有太多,此处不多特殊解释,直接上unity实现。
1)CS脚本文件
using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;
// See _ReadMe.txt for an overview
[ExecuteInEditMode]
public class CommandBufferBlurRefraction : MonoBehaviour
{
public Shader m_BlurShader;
private Material m_Material;
private Camera m_Cam;
// We'll want to add a command buffer on any camera that renders us,
// so have a dictionary of them.
private Dictionary<Camera,CommandBuffer> m_Cameras = new Dictionary<Camera,CommandBuffer>();
// Remove command buffers from all cameras we added into
private void Cleanup()
{
foreach (var cam in m_Cameras)
{
if (cam.Key)
{
cam.Key.RemoveCommandBuffer (CameraEvent.AfterSkybox, cam.Value);
}
}
m_Cameras.Clear();
Object.DestroyImmediate (m_Material);
}
public void OnEnable()
{
Cleanup();
}
public void OnDisable()
{
Cleanup();
}
// Whenever any camera will render us, add a command buffer to do the work on it
public 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 (m_Cameras.ContainsKey(cam))
return;
if (!m_Material)
{
m_Material = new Material(m_BlurShader);
m_Material.hideFlags = HideFlags.HideAndDontSave;
}
buf = new CommandBuffer();
buf.name = "Grab screen and blur";
m_Cameras[cam] = buf;
// copy screen into temporary RT
int screenCopyID = Shader.PropertyToID("_ScreenCopyTexture");
buf.GetTemporaryRT (screenCopyID, -1, -1, 0, FilterMode.Bilinear);
buf.Blit (BuiltinRenderTextureType.CurrentActive, screenCopyID);
// get two smaller RTs
int 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 RT
buf.Blit (screenCopyID, blurredID);
buf.ReleaseTemporaryRT (screenCopyID);
// horizontal blur
buf.SetGlobalVector("offsets", new Vector4(2.0f/Screen.width,0,0,0));
buf.Blit (blurredID, blurredID2, m_Material);
// vertical blur
buf.SetGlobalVector("offsets", new Vector4(0,2.0f/Screen.height,0,0));
buf.Blit (blurredID2, blurredID, m_Material);
// horizontal blur
buf.SetGlobalVector("offsets", new Vector4(4.0f/Screen.width,0,0,0));
buf.Blit (blurredID, blurredID2, m_Material);
// vertical blur
buf.SetGlobalVector("offsets", new Vector4(0,4.0f/Screen.height,0,0));
buf.Blit (blurredID2, blurredID, m_Material);
buf.SetGlobalTexture("_GrabBlurTexture", blurredID);
cam.AddCommandBuffer (CameraEvent.AfterSkybox, buf);
}
}
2)SeparableGlassBlur
Shader "Hidden/SeparableGlassBlur" {
Properties {
_MainTex ("Base (RGB)", 2D) = "" {}
}
CGINCLUDE
#include "UnityCG.cginc"
struct v2f {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
float4 uv01 : TEXCOORD1;
float4 uv23 : TEXCOORD2;
float4 uv45 : TEXCOORD3;
};
float4 offsets;
sampler2D _MainTex;
v2f vert (appdata_img v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, 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 frag (v2f i) : COLOR {
half4 color = float4 (0,0,0,0);
color += 0.40 * tex2D (_MainTex, i.uv);
color += 0.15 * tex2D (_MainTex, i.uv01.xy);
color += 0.15 * tex2D (_MainTex, i.uv01.zw);
color += 0.10 * tex2D (_MainTex, i.uv23.xy);
color += 0.10 * tex2D (_MainTex, i.uv23.zw);
color += 0.05 * tex2D (_MainTex, i.uv45.xy);
color += 0.05 * tex2D (_MainTex, i.uv45.zw);
return color;
}
ENDCG
Subshader {
Pass {
ZTest Always Cull Off ZWrite Off
Fog { Mode off }
CGPROGRAM
#pragma fragmentoption ARB_precision_hint_fastest
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
Fallback off
} // shader
3)Stained BumpDistort
// Similar to regular FX/Glass/Stained BumpDistort shader
// from standard Effects package, just without grab pass,
// and samples a texture with a different name.
Shader "FX/Glass/Stained BumpDistort (no grab)" {
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 {
// We must be transparent, so other objects are drawn before this one.
Tags { "Queue"="Transparent" "RenderType"="Opaque" }
SubShader {
Pass {
Name "BASE"
Tags { "LightMode" = "Always" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
float2 texcoord: TEXCOORD0;
};
struct v2f {
float4 vertex : POSITION;
float4 uvgrab : TEXCOORD0;
float2 uvbump : TEXCOORD1;
float2 uvmain : TEXCOORD2;
UNITY_FOG_COORDS(3)
};
float _BumpAmt;
half _TintAmt;
float4 _BumpMap_ST;
float4 _MainTex_ST;
v2f vert (appdata_t v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
#if UNITY_UV_STARTS_AT_TOP
float scale = -1.0;
#else
float scale = 1.0;
#endif
o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
o.uvgrab.zw = o.vertex.zw;
o.uvbump = TRANSFORM_TEX( v.texcoord, _BumpMap );
o.uvmain = TRANSFORM_TEX( v.texcoord, _MainTex );
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
sampler2D _GrabBlurTexture;
float4 _GrabBlurTexture_TexelSize;
sampler2D _BumpMap;
sampler2D _MainTex;
half4 frag (v2f i) : SV_Target
{
// calculate perturbed coordinates
// we could optimize this by just reading the x & y without reconstructing the Z
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;
half4 col = tex2Dproj (_GrabBlurTexture, UNITY_PROJ_COORD(i.uvgrab));
half4 tint = tex2D(_MainTex, i.uvmain);
col = lerp (col, tint, _TintAmt);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
}
关于详细的解析,参考此文
最后附一下unity的原文,并且最后有源工程。