渲染纹理
现代的GPU允许把整个三维场景渲染到一个中间缓冲中,即渲染目标纹理(Render Target Texture RTT),而不是传统的帧缓冲或是后备缓冲(backbuffer)。与之相关的是多重渲染目标(Multiple Render Target, MRT),这种技术指的是GPU允许我们把场景同时渲染到多个渲染目标纹理中,而不再需要为每个渲染目标纹理单独渲染完整的场景。延迟渲染就是使用多重渲染目标的一个应用。
Unity为渲染目标纹理定义了一种专门的纹理类型——渲染纹理(Render Texture)。Unity渲染纹理有两种使用方法:
1、在Project下创建一个渲染纹理,然后将其赋给某个摄像机,创建的渲染纹理可以调整分标率,滤波模式。这样摄像机渲染的结果会实时更新到这张纹理下。(这种方式也可以通过写代码的方式)
2、在屏幕后处理时使用GrabPass,命令,或是OnRenderImage函数获取当前屏幕图像。Unity会把这个屏幕图像放到一张和屏幕分辨率等同的渲染纹理中,然后就可以在Pass中把它当成普通的纹理使用就可以,用来实现各种屏幕特效。
镜子效果
其实镜子效果的实现就是在“镜子”的后面放置一个摄像机,然后把这个摄像机看到的东西画在“镜子”上(就是讲这个摄像机的RenderTexure当成正常的纹理画在某个面上),需要注意的是“镜子”与主相机看到的实际是镜面的,也就是反过来的,所以UV坐标需要在X方向上翻转一下。
Shader "Unlit/Mirror"
{
Properties
{
_MainTex ("Render Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos:SV_POSITION;
float2 uv:TEXCOORD0;
};
sampler2D _MainTex;
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
o.uv.x = 1-o.uv.x;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
玻璃效果
当我们在Shader中定义一个GrabPass之后,Unity会把当前屏幕的图像绘制在一张纹理中,我们可以再后续的Pass中访问这张纹理。通常会使用GrabPass来实现诸如玻璃等透明材质的模拟,使用GrabPass可以对该物体后面的图像进行更复杂的操作,如使用法线模拟折射效果。
在使用GrabPass时需要注意物体的渲染队列设置,GrabPass通常用于渲染透明物体,尽管代码里面并不包含混合指令,但仍需要把渲染队列设置成透明队列:
Tags{"RenderType"="Opaque" "Queue"="Transparent"}
这样能保证在画这个物体时其他不透明的物体已经绘制完了。
Shader "Unlit/GlassRefraction"
{
Properties{
_MainTex("Main Tex",2D)="white"{}
_BumpTex("Bump Tex",2D)="bump"{}
_Cubemap("Envirenment Tex",Cube)="_SkyBox"{}
_Distortion("Distortion",Range(0,100))=50
_RefractionAmount("Refraction Amount",Range(0,1))=0.5
}
SubShader{
Tags{"RenderType"="Opaque" "Queue"="Transparent"}
GrabPass{"_RefractionTex"}
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpTex;
float4 _BumpTex_ST;
samplerCUBE _Cubemap;
float _Distortion;
fixed _RefractionAmount;
sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float4 scrPos : TEXCOORD0;
float4 uv : TEXCOORD1;
float4 TtoW0 : TEXCOORD2;
float4 TtoW1 : TEXCOORD3;
float4 TtoW2 : TEXCOORD4;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.scrPos = ComputeGrabScreenPos(o.pos);
o.uv.xy = TRANSFORM_TEX(v.texcoord,_MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord,_BumpTex);
float3 worldPos = mul(unity_ObjectToWorld,v.vertex);
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 worldTangent = UnityObjectToWorldDir(v.tangent);
float3 worldBinormal = cross(normalize(worldTangent),normalize(worldNormal))*v.tangent.w;
o.TtoW0 = float4(worldTangent.x,worldBinormal.x,worldNormal.x,worldPos.x);
o.TtoW1 = float4(worldTangent.y,worldBinormal.y,worldNormal.y,worldPos.y);
o.TtoW2 = float4(worldTangent.z,worldBinormal.z,worldNormal.z,worldPos.z);
return o;
}
fixed4 frag(v2f i):SV_TARGET
{
float3 worldPos = float3(i.TtoW0.z,i.TtoW1.z,i.TtoW2.z);
float3 worldViewDir = UnityWorldSpaceViewDir(worldPos);
//切线空间
fixed3 bump = UnpackNormal(tex2D(_BumpTex,i.uv.zw));
//在切线空间对法线进行偏移,得出偏移的像素数量,_RefractionTex_TexelSize代表素纹大小
//如256X512的纹理,素纹大小就是(1/256,1/512)
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
//然后对屏幕空间XY值进行偏移,很疑惑为什么还要乘一次Z值,感觉不乘也可以
i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
fixed3 refractColor = tex2D(_RefractionTex,i.scrPos.xy/i.scrPos.w).rgb;
fixed3 diffuseColor = tex2D(_MainTex,i.uv.xy).rgb;
fixed3 worldBump = normalize(half3(dot(bump,i.TtoW0.xyz),dot(bump,i.TtoW1.xyz),dot(bump,i.TtoW2.xyz)));
fixed3 reflectDir = reflect(-worldViewDir,worldBump);
fixed3 reflectColor = texCUBE(_Cubemap,reflectDir).rgb * diffuseColor;
fixed3 finalColor = reflectColor * (1-_RefractionAmount) + refractColor * _RefractionAmount;
return fixed4(finalColor,1.0);
}
ENDCG
}
}
}