Unity Shader 案例之 镜面材质制作
打开镜像世界
在Unity引擎中开启镜子世界似乎非常简单呢!只需添加一个Mirror Camera即可完成这一操作。具体来说,在镜子表面设置当前相机的镜像映射(Transform)。
- 镜像矩阵

这个概念比较复杂,记公式就可以了。
有了公式,代码就好写了
public static void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, Vector4 plane)
{
reflectionMat.m00 = (1F - 2F * plane[0] * plane[0]);
reflectionMat.m01 = (-2F * plane[0] * plane[1]);
reflectionMat.m02 = (-2F * plane[0] * plane[2]);
reflectionMat.m03 = (-2F * plane[0] * plane[3]);
reflectionMat.m10 = (-2F * plane[1] * plane[0]);
reflectionMat.m11 = (1F - 2F * plane[1] * plane[1]);
reflectionMat.m12 = (-2F * plane[1] * plane[2]);
reflectionMat.m13 = (-2F * plane[1] * plane[3]);
reflectionMat.m20 = (-2F * plane[2] * plane[0]);
reflectionMat.m21 = (-2F * plane[2] * plane[1]);
reflectionMat.m22 = (1F - 2F * plane[2] * plane[2]);
reflectionMat.m23 = (-2F * plane[2] * plane[3]);
reflectionMat.m30 = 0F;
reflectionMat.m31 = 0F;
reflectionMat.m32 = 0F;
reflectionMat.m33 = 1F;
}
将Mirror Camera放置于镜像点上,并对以下属性进行调整以配置其变换矩阵:reflectionCamera.worldToCameraMatrix = cam.worldToCameraMatrix * reflection;
剪切矩阵我们要把MirrorCamera与镜面间的内容剪切掉。

Unity的Camera确实提供了现成的方法来实现某些功能,但在这一场景下,我们采用了另一种计算方式,如下面链接所述,无需过多赘述具体实现细节.
public static class Matrix4x4
{
public static Matrix4x4 MultiplyMatrix4x4(Matrix4x4 lhs, Matrix4x4 rhs) {
// 矩阵乘法的具体实现
// 由于篇幅限制,此处省略详细步骤
return new Matrix4x4(...);
}
}
public static void CalculateObliqueMatrix(ref Matrix4x4 projection, Vector4 clipPlane)
{
Vector4 q = projection.inverse * new Vector4(sgn(clipPlane.x), sgn(clipPlane.y), 1.0f, 1.0f);
Vector4 c = clipPlane * (2.0F / (Vector4.Dot(clipPlane, q)));
projection[2] = c.x - projection[3];
projection[6] = c.y - projection[7];
projection[10] = c.z - projection[11];
projection[14] = c.w - projection[15];
}
Mirror Shader
获取MirrorCamera的RenderTarget,即MirrorCamera的截图效果。
inline float4 ComputeNonStereoScreenPos1(float4 pos)
{
float4 o = pos * 0.5f;
o.xy = float2(o.x, o.y * _ProjectionParams.x) + o.w;
o.zw = pos.zw;
return o;
}
在Unity Shader库中定义了ComputeScreenPos函数的内部实现,在其之后使用tex2Dproj方法对纹理进行采样。

其中uniform float4 _ProjectionParams;
投影参数各值分别为如下含义:
- x 等于 1;当投影翻转时,则 x 等于 -1。
- y 即为 camera 的近裁剪平面。
- z 即为 camera 的远裁剪平面。
- w 等于 1 除以 远裁剪平面。
inline float4 ComputeScreenPos1(float4 pos, float d)
{
float4 o = pos *0.5f;
o.xy = float2(o.x / d, o.y / d * _ProjectionParams.x) + 0.5f;
//o.xy = float2(o.x / o.w, o.y / o.w * 1) + 0.5f;
o.zw = pos.zw;
return o;
}
为了实现纹理采样:
fixed4 refl = tex2D(_ReflectionTex, i.refl.xy);
结果表明:

可以看到采样的有抖动的问题。
打开Wireframe

如下图:我们就可以看到其中的规律了。

如果应用 tex2D 函数进行采样,并且纹理贴图在面附近发生伸展的情况下,则会出现不规则且不确定的贴图采样扭曲现象。这是因为 tex2Dproj 在处理这类问题时更为稳定的原因之一。最后查阅一些 UNITY_PROJ_COORD 的定义。
#if defined(SHADER_API_PSP2)
#define UNITY_BUGGY_TEX2DPROJ4
#define UNITY_PROJ_COORD(a) (a).xyw
#else
#define UNITY_PROJ_COORD(a) a
#endif
后续处理
uv扰动参数和噪声贴图

这里还要说明一种深度模糊的效果
附录:
uniform float4 _ScreenParams:屏幕参数:
-
x值代表屏幕宽度
-
y值代表屏幕高度
-
z值等于1加上1除以屏幕宽度
-
w值等于1加上1除以height(表示像素数量)
- Unity内置变换矩阵

- Githup 源码位置
https://github.com/bennychao/UnityShaders
