Unity Shader 学习笔记(16) 渲染纹理(Render Texture)

Unity Shader 学习笔记(16) 渲染纹理(Render Texture)

参考书籍:《Unity Shader 入门精要》
Unity 制作小地图(Minimap)系统 两种方法
官网文档:Graphics Command Buffers


渲染纹理(Render Texture)

GPU允许把三维场景渲染到一个中间缓冲中,即渲染目标纹理(Render Target Texture, RTT),而不是传统的帧缓冲或后备缓冲。延迟渲染使用多重渲染目标(Multiple Render Target, MRT),即同时渲染到多个渲染目标纹理。
  Unity中使用渲染纹理通常有两种方式:

  1. 在Project目录创建渲染纹理,摄像机中设置渲染目标为该纹理。可以模拟镜子效果、投影(如制作游戏的小地图)。
  2. 屏幕后处理时使用GrabPass命令或OnRenderImage函数来获取当前屏幕图像,实现各种屏幕特效。

镜子效果(Mirror)

Unity Shader 学习笔记(16) 渲染纹理(Render Texture)_第1张图片

Properties {
	_MainTex ("Main Tex", 2D) = "white" {}	// 传入自己创建的RenderTexture
}
	
			
v2f vert(a2v v) {
	v2f o;
	o.pos = UnityObjectToClipPos(v.vertex);
	
	o.uv = v.texcoord;
	o.uv.x = 1 - o.uv.x;		// 反转x分量
	
	return o;
}

fixed4 frag(v2f i) : SV_Target {
	return tex2D(_MainTex, i.uv);		// 直接输出反转后的纹理
}
			

玻璃效果(Glass)

反射从1到0,折射从0到1。玻璃后面的图像被“扭曲”。

通过GrabPass获取屏幕图像,通常用于渲染透明物体。及对物体后面的图像做更复杂的处理,模拟折射效果等。虽然代码不包含混合指令,但往往需要把物体的渲染队列设置成透明队列(“Queue” = “Transparent”)。

实现原理:结合法线纹理修改法线信息,然后使用上一篇的Cubemap来模拟玻璃的反射,再用GrabPass获取玻璃后面的屏幕图像,最后用切线空间下的法线对屏幕做偏移。

Properties {
	_MainTex ("Main Tex", 2D) = "white" {}
	_BumpMap ("Normal Map", 2D) = "bump" {}						// 法线纹理
	_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {}		// 反射纹理
	_Distortion ("Distortion", Range(0, 100)) = 10				// 折射扭曲程度
	_RefractAmount ("Refract Amount", Range(0.0, 1.0)) = 1.0	// 控制折射程度(0:只反射,1:只折射)
}

SubShader中:

// Transparent:在所有不透明物体渲染后再渲染。Opaque:确保在着色器替换时,该物体在需要时正确渲染。
Tags { "Queue"="Transparent" "RenderType"="Opaque" }

// 抓取屏幕图像的Pass,存入到纹理变量_RefractionTex中。
GrabPass { "_RefractionTex" }

Pass {
	...

	sampler2D _MainTex;
	float4 _MainTex_ST;
	sampler2D _BumpMap;
	float4 _BumpMap_ST;
	samplerCUBE _Cubemap;
	float _Distortion;
	fixed _RefractAmount;
	sampler2D _RefractionTex;			// 对应GrabPass指定的纹理名
	float4 _RefractionTex_TexelSize;	// 纹理大小。如256x512的纹理,值为(1/256, 1/512)

	...

	struct v2f {
		float4 pos : SV_POSITION;
		float4 scrPos : TEXCOORD0;		// 屏幕图像的采样坐标
		...
	};

	v2f vert (a2v v) {
		v2f o;
		o.pos = UnityObjectToClipPos(v.vertex);
		
		o.scrPos = ComputeGrabScreenPos(o.pos);			// 计算抓取的屏幕图像采样坐标,在UnityCG.cginc中定义。
		
		... // 法线纹理采样,切线空间转换到世界空间等
		return o;
	}
	
	fixed4 frag (v2f i) : SV_Target {		
		float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
		fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
		
		// 获取切线空间下的法线方向
		fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));	
		
		// 对屏幕图像采样坐标进行偏移,模拟折射
		float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
		i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
		fixed3 refrCol = tex2D(_RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;		// 透射除法 计算得到屏幕坐标
		
		// 法线再转到世界空间,原理见前面第8篇
		bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
		
		// 法线再转到世界空间
		bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
		// 计算反射方向
		fixed3 reflDir = reflect(-worldViewDir, bump);
		// 主纹理的采样,和立方体纹理的采样
		fixed4 texColor = tex2D(_MainTex, i.uv.xy);
		fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb;
				
		fixed3 finalColor = reflCol * (1 - _RefractAmount) + refrCol * _RefractAmount;
		
		return fixed4(finalColor, 1);
	}
}

GrabPass支持的形式

  • 直接使用GrabPass{ },在后面Pass中直接使用_GrabTexture来访问图像。如果多个物体都这样抓取屏幕,Unity都会单独抓取屏幕,消耗较大。但每个物体屏幕图像不同。
  • 使用GrabPass {“TextureName”},如上例子。但Unity只会在每一帧时,为第一个使用TextureName纹理的物体抓一次屏幕,同时意味着所有物体都用这一张屏幕图像。

渲染纹理 和 GrabPass的比较

- 渲染纹理 GrabPass
实现难度 复杂,要创建纹理,额外相机,设置相机渲染目标,最后传入Shader或者Raw Image。 简单,只需几行代码即可抓取屏幕。
效率 高,尤其是在移动设备。 低,在移动设备上,GrabPass虽然不会重新渲染场景,但要从CPU读取后备缓冲的数据,破坏了CPU和GPU的并行性,比较耗时。
自由度 高,虽然要重新渲染,但可以控制哪些进行渲染,分辨率大小等等。如最上面链接的游戏小地图制作。 低,获取的图形分辨率和屏幕一致。

除了上面两种方法,还可以使用命令缓冲(Command Buffers)实现抓取屏幕效果。见最上面链接。

你可能感兴趣的:(Unity,Shader,图形学,Unity,Shader,学习笔记,unity,shader)