接上文:UnityShader20:CommandBuffer初见(上)
四、复杂一点的例子……
CommandBuffer代码参考如下:
using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;
[ExecuteInEditMode]
//加上ExecuteInEditMode后,下面的代码在编辑模式中就会运行,无需每次测试都要play了
public class CommandBufferBlurRefraction: MonoBehaviour
{
public Shader m_BlurShader;
private Material m_Material;
private Camera m_Cam;
private Dictionary<Camera,CommandBuffer> m_Cameras = new Dictionary<Camera, CommandBuffer>();
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();
}
public void OnWillRenderObject()
{
var act = gameObject.activeInHierarchy && enabled;
if (!act)
{
Cleanup();
return;
}
var cam = Camera.current;
if (!cam)
return;
CommandBuffer buf = null;
if (m_Cameras.ContainsKey(cam))
return;
if (!m_Material)
{
m_Material = new Material(m_BlurShader);
m_Material.hideFlags = HideFlags.HideAndDontSave;
}
//创建一个CommandBuffer
buf = new CommandBuffer();
buf.name = "Grab screen and blur";
m_Cameras[cam] = buf;
//新建一张纹理,得到对应的纹理ID
int screenCopyID = Shader.PropertyToID("_ScreenCopyTexture");
//初始化这张纹理,其中纹理的大小为摄像机视野大小
buf.GetTemporaryRT(screenCopyID, -1, -1, 0, FilterMode.Bilinear);
//将当前的渲染目标纹理拷贝到你的纹理"_ScreenCopyTexture"上
buf.Blit(BuiltinRenderTextureType.CurrentActive, screenCopyID);
//再新建两张纹理
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);
//开始表演
buf.Blit(screenCopyID, blurredID);
buf.ReleaseTemporaryRT(screenCopyID);
//进行多次的高斯模糊,一次横向,一次纵向,纹理相互拷贝
buf.SetGlobalVector("offsets", new Vector4(2.0f/Screen.width, 0, 0, 0));
buf.Blit(blurredID, blurredID2, m_Material);
buf.SetGlobalVector("offsets", new Vector4(0, 2.0f/Screen.height, 0, 0));
buf.Blit(blurredID2, blurredID, m_Material);
buf.SetGlobalVector("offsets", new Vector4(4.0f/Screen.width, 0, 0, 0));
buf.Blit(blurredID, blurredID2, m_Material);
buf.SetGlobalVector("offsets", new Vector4(0, 4.0f/Screen.height, 0, 0));
buf.Blit(blurredID2, blurredID, m_Material);
//到这里,纹理blurredID已经就是最终我们要的:进行过高斯模糊的结果,将它赋到对应着色器中的 _RefractionTex 属性上
buf.SetGlobalTexture("_RefractionTex", blurredID);
cam.AddCommandBuffer(CameraEvent.AfterSkybox, buf);
}
//是的,这整个过程就是为了得到一张你想要的纹理,并传递给Shader
}
- CommandBuffer.GetTemporaryRT:获取一张临时的 RenderTexture,第1个参数为纹理主键,由 Shader.PropertyToID 创建;第2, 3个参数为纹理长宽,传负数 表示取相机像素的 ;第4个参数为深度缓冲位宽(0, 16, 24);第5个参数位纹理过滤模式。这个纹理可以由 ReleaseTemporaryRT 释放,也会由 Unity 在摄像机完成渲染后自动释放
-
CommandBuffer.ReleaseTemporaryRT:释放指定的的临时渲染纹理
- Shader.PropertyToID:获得着色器属性名对应的唯一ID,这个属性名随意
- BuiltinRenderTextureType.CurrentActive:获取当前激活的渲染目标
- CommandBuffer.Blit:将一个纹理复制到另一个纹理,可使用自定义着色器,第1个参数为源纹理 src;第2个参数为目标纹理 target;第三个参数为材质 mat,其内部使用 mat 材质用 src 做 mainTex,clear 为 black 后渲染到 target 上,mat 留空可以理解为直接拷贝纹理。执行这个操作后,target 将会成为新的渲染目标
- CommandBuffer.SetGlobalVector:全局设置着色器向量属性
-
CommandBuffer.SetGlobalTexture:全局设置着色器纹理属性
五、更好的玻璃效果(官网案例)
1):着色器代码部分:这个着色器实现了最简单的高斯模糊
- 关于高斯模糊:OpenGL基础51:泛光(文章中的第三节)
Shader "Jaihk662/GaussBlurred1"
{
Properties
{
_MainTex ("Base(RGB)", 2D) = "" {}
}
CGINCLUDE
#include "UnityCG.cginc"
float4 offsets;
sampler2D _MainTex;
/*可以直接用appdata_img代替
struct _2vert
{
float4 vertex: POSITION;
half2 texcoord: TEXCOORD0;
};*/
struct vert2frag
{
float4 pos: SV_POSITION;
float2 uv: TEXCOORD0;
float4 uv01: TEXCOORD1;
float4 uv23: TEXCOORD2;
float4 uv45: TEXCOORD3;
};
vert2frag vert(appdata_img v)
{
vert2frag o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord.xy;
//采样范围为7x7(周围3圈),offests的值在.cs中设置
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(vert2frag i): SV_Target
{
//高斯模糊
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
//使用低精度(FP16)以提升fragment着色器的运行速度,减少时间
#pragma fragmentoption ARB_precision_hint_fastest
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
CGINCLUDE 关键字:和之前常用的 CGPROGRAM 不同,在 CGINCLUDE 和 ENDCG 范围内插入的 shader 代码会被插入到所有 Pass 中
- #pragma fragmentoption ARB_precision_hint_fastest:使用低精度(FP16)以提升fragment着色器的运行速度,减少时间
- #pragma fragmentoption ARB_precision_hint_nicest:使用高精度(FP32)
代码中还设置了 Fog { Mode off }:这是为了关闭雾效,暂时可以不去了解
2):然后对于玻璃本身材质的着色器如下:
Shader "Jaihk662/Glass2"
{
Properties
{
_MainTex ("Main Tex", 2D) = "white" {} //玻璃材质纹理
_NormalMap ("Normal Map", 2D) = "white" {} //玻璃法线
_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {} //模拟环境映射
_Distortion ("Distortion", Range(0, 100)) = 10 //模拟折射时,图像扭曲程度
_RefractAmount ("Refract Amount", Range(0.0, 1.0)) = 1.0 //折射程度,为0只反射,为1只折射
}
SubShader
{
Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }
//Queue设置为Transparent可以保证该物体渲染时,所有的不透明物体都已经被渲染到屏幕上了
LOD 200
PASS
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D _MainTex;
sampler2D _NormalMap;
samplerCUBE _Cubemap;
float _Distortion;
fixed _RefractAmount;
sampler2D _RefractionTex; //来自CommandBuffer
float4 _RefractionTex_TexelSize; //对应纹理每一像素的大小
float4 _NormalMap_ST;
float4 _MainTex_ST;
struct _2vert
{
float4 vertex: POSITION;
float3 normal: NORMAL;
float4 tangent: TANGENT;
float2 texcoord: TEXCOORD0;
};
struct vert2frag
{
float4 pos: SV_POSITION;
float4 scrPos: TEXCOORD0;
float4 uv: TEXCOORD1;
float4 TtoW1: TEXCOORD2;
float4 TtoW2: TEXCOORD3;
float4 TtoW3: TEXCOORD4;
};
vert2frag vert(_2vert v)
{
vert2frag o;
o.pos = UnityObjectToClipPos(v.vertex);
o.scrPos = ComputeGrabScreenPos(o.pos); //获得屏幕图像的采样坐标,考虑过平台差异,输入裁剪空间坐标,输出齐次坐标系下的屏幕坐标值(就是屏幕坐标乘上w,只计算xy,zw不变)
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _NormalMap);
float3 wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
float3 wNormal = UnityObjectToWorldNormal(v.normal);
float3 wTangent = UnityObjectToWorldDir(v.tangent);
float3 wBinormal = cross(wNormal, wTangent) * v.tangent.w;
o.TtoW1 = float4(wTangent.x, wBinormal.x, wNormal.x, wPos.x);
o.TtoW2 = float4(wTangent.y, wBinormal.y, wNormal.y, wPos.y);
o.TtoW3 = float4(wTangent.z, wBinormal.z, wNormal.z, wPos.z);
return o;
}
fixed4 frag(vert2frag i): SV_Target
{
float3 wPos = float3(i.TtoW1.w, i.TtoW2.w, i.TtoW3.w);
fixed3 wViewDir = normalize(UnityWorldSpaceViewDir(wPos));
fixed3 normal = UnpackNormal(tex2D(_NormalMap, i.uv.zw));
//计算折射,考虑玻璃材质
float2 offest = normal.xy * _Distortion * _RefractionTex_TexelSize.xy;
i.scrPos.xy = i.scrPos.xy + offest * i.scrPos.z;
fixed3 refractCol = tex2D(_RefractionTex, i.scrPos.xy / i.scrPos.w).rgb; //根据偏移后的坐标进行采样,得到折射颜色
//不再计算反射
//normal = normalize(half3(dot(i.TtoW1.xyz, normal), dot(i.TtoW2.xyz, normal), dot(i.TtoW3.xyz, normal)));
fixed4 texColor = tex2D(_MainTex, i.uv.xy);
//fixed3 reflectionDir = reflect(-wViewDir, normal);
//fixed3 reflectionCol = texCUBE(_Cubemap, reflectionDir).rgb * texColor.rgb;
fixed3 finalColor = texColor * (1 - _RefractAmount) + refractCol * _RefractAmount;
return fixed4(finalColor, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}
这个代码就是《UnityShader19.1:渲染纹理(下)之 GrabPass》中代码的改版,原先 GrabPass 的纹理现在由 CommandBuffer 提供,并且不再计算反射
3):最后 CommandBuffer 部分代码就是上一章中的代码
效果如下:
参考资料: