UnityShader20.1:CommandBuffer初见(下)

 

接上文: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
}
  1. CommandBuffer.GetTemporaryRT:获取一张临时的 RenderTexture,第1个参数为纹理主键,由 Shader.PropertyToID 创建;第2, 3个参数为纹理长宽,传负数 UnityShader20.1:CommandBuffer初见(下) 表示取相机像素的  UnityShader20.1:CommandBuffer初见(下);第4个参数为深度缓冲位宽(0, 16, 24);第5个参数位纹理过滤模式。这个纹理可以由 ReleaseTemporaryRT 释放,也会由 Unity 在摄像机完成渲染后自动释放
  2. CommandBuffer.ReleaseTemporaryRT:释放指定的的临时渲染纹理

  3. Shader.PropertyToID:获得着色器属性名对应的唯一ID,这个属性名随意
  4. BuiltinRenderTextureType.CurrentActive:获取当前激活的渲染目标
  5. CommandBuffer.Blit:将一个纹理复制到另一个纹理,可使用自定义着色器,第1个参数为源纹理 src;第2个参数为目标纹理 target;第三个参数为材质 mat,其内部使用 mat 材质用 src 做 mainTex,clear 为 black 后渲染到 target 上,mat 留空可以理解为直接拷贝纹理。执行这个操作后,target 将会成为新的渲染目标
  6. CommandBuffer.SetGlobalVector:全局设置着色器向量属性
  7. CommandBuffer.SetGlobalTexture:全局设置着色器纹理属性

 

五、更好的玻璃效果(官网案例)

1):着色器代码部分:这个着色器实现了最简单的高斯模糊

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 部分代码就是上一章中的代码

效果如下:

UnityShader20.1:CommandBuffer初见(下)

 

参考资料:

 

上一篇:练习项目(四):深度图基础及应用


下一篇:UnityShader-属性介绍Properties