Unity Shader入门精要 | 渲染纹理和程序纹理

目录

  • 渲染纹理
    • 镜子效果
    • 玻璃效果
    • 渲染纹理 vs GrabPass
  • 程序纹理

渲染纹理

渲染目标纹理(RTT):把整个三维场景渲染到一个中间缓冲中,而不是传统的帧缓冲

多重渲染目标(MRT):把场景同时渲染到多个渲染目标纹理中,不需要为每个渲染目标纹理单独渲染完整的场景,比如延迟渲染

在Unity中定义为渲染纹理(Render Texture),有两种实现方式:一种是在Project目录下创建一个渲染纹理,然后把某个摄像机的渲染目标设置为该纹理;另一种是在屏幕后处理时使用GrabPass命令或OnRenderImage函数来获得当前屏幕图像,放在一张等大的纹理中。


镜子效果

新建一个小房间的场景,里面放置一些球形和立方体,并且放一个四边形作为镜子。

Unity Shader入门精要 | 渲染纹理和程序纹理_第1张图片

放置一个新的摄像机,并把它的渲染目标设置为新建的渲染纹理Mirror Texture。编辑应用于镜子材质的shader,只需要处理一个纹理属性,并且在顶点着色器中翻转纹理x坐标,并在片元着色器中输出:

v2f vert(a2v v) {
     
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    
    o.uv = v.texcoord;
    // Mirror needs to filp x
    o.uv.x = 1 - o.uv.x;
    
    return o;
}

fixed4 frag(v2f i) : SV_Target {
     
    return tex2D(_MainTex, i.uv);
}

把前面的渲染纹理应用于这个shader,效果如下:

Unity Shader入门精要 | 渲染纹理和程序纹理_第2张图片


玻璃效果

这部分使用GrabPass的方法。

声明玻璃的纹理、法线纹理,_Cubemap模拟的是反射的环境,_Distortion控制折射的扭曲程度,_RefractAmount控制反射程度。

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
}

定义相应渲染队列,用GrabPass获取图像。

SubShader {
     
	Tags {
      "Queue"="Transparent" "RenderType"="Opaque" }
	
	GrabPass {
      "_RefractionTex" }

变量中除了之前的属性,还有_RefractionTex(对应上面GrabPass的抓取结果)和它的纹素大小。

sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
samplerCUBE _Cubemap;
float _Distortion;
fixed _RefractAmount;
sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize;

顶点着色器中,使用ComputeGrabScreenPos()函数获得屏幕图像采样坐标。因为需要在片元着色器中把法线方向从切线空间变换到世界空间,以便对Cubemap进行采样,这个变换矩阵存放在TtoW的xyz分量里,世界坐标存放在w轴。

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, _BumpMap);
	
	float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;  
	fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);  
	fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);  
	fixed3 worldBinormal = cross(worldNormal, worldTangent) * 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;
}

TtoW得到世界坐标,提取法线bump,利用_Distortion得到坐标偏移量,计算模拟的折射颜色refrCol。接着变换切线空间获取法线、反射方向,然后对Cubemap采样,和主纹理相乘得到反射颜色reflCol

最后使用_RefractAmount对折射和反射颜色进行混合。

fixed4 frag (v2f i) : SV_Target {
     		
	float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
	fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
	
	// Get the normal in tangent space
	fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));	
	
	// Compute the offset in tangent space
	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;
	
	// Convert the normal to world space
	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);
}

设置纹理、法线和环境Cubemap:

Unity Shader入门精要 | 渲染纹理和程序纹理_第3张图片

可以得到下图类似玻璃的效果:


渲染纹理 vs GrabPass

从效率上讲,使用渲染纹理比较好,尤其在移动设备上。我们可以调整摄像机的渲染层等信息,来控制纹理的大小;而GrabPass获取的是屏幕分辨率大小的图像,在高分辨率的设备上会有严重的带宽影响。



程序纹理

程序纹理是由计算机生成的,使用这种纹理的好处是可以方便控制纹理属性,得到丰富的动画和视觉效果。

你可能感兴趣的:(UnityShader)