【图形与渲染】相机平面镜反射与斜裁剪矩阵(上)-镜像矩阵

版权声明:本文为博主原创文章,未经博主允许不得转载 https://blog.csdn.net/mobilebbki399/article/details/79491825

平面镜反射是一种常用的实时渲染效果,且在许多游戏中都可以见到它的身影。



通常游戏中实现实时反射的方法有如下几种:

1.使用cubemap或预先烘焙反射探针

2.平面镜反射

3。屏幕空间反射(SSR)


三种方式进行比较,使用cubemap或反射探针可以用在反射物体不要求实时更新或变换的情况,且可以用在非平面物体上,如金属材质对天空的反射,或对一些静态场景物件的反射等。而屏幕空间反射虽然可以做实时更新反射物件,且也可以用在非平面物体上,但其性能要求也是最高的。相比之下,平面镜反射尽管在介质上仅局限于平面物体,但其可以做到反射物体的实时更新和变换,效果和性能都不错。


本文主要介绍平面镜反射的实现方式。

考虑到平面镜反射的实现的主要难点集中在相机矩阵的计算,因此分成两篇文章:

【图形与渲染】相机平面镜反射与斜裁剪矩阵(上)

【图形与渲染】相机平面镜反射与斜裁剪矩阵(下)


实现平面镜反射的主要原理如下:

1.根据已知平面,计算出该平面的镜像矩阵

2.对当前摄像机进行拷贝,得到一个新的相机,并对该相机使用镜像矩阵变换到镜像位置

3.根据平面向量计算镜像相机的斜裁剪矩阵,以裁剪平面负方向的不可视区域

4.渲染镜像相机得到RenderTexture并投影到平面的材质shader,在shader中计算投影坐标最终得到镜面反射效果


第一篇文章首先介绍镜像矩阵的计算:


首先考虑以下情况:

假设存在平面P=<n,d>,其法线为n,其中d=-n·p,p表示平面上的任意一点

在平面的正方向存在点Q,要求计算点Q相对于平面P镜像后的坐标Q’。



可知向量Q’Q垂直于平面,即与法线n同向,假设Q到平面P的垂直距离为k,则有Q=Q’+2*k*n/|n|。


即Q’=Q-2*k*n/|n|


接下来就需要求点Q到平面的距离k


假设平面上存在任意点S=(x0,y0,z0):


则有 (Q-S)·n =|Q-S||n|cosθ

其中cosθ可由k和Q到S的距离求得:

cosθ = k/|Q-S|

因此上述公式转换为(Q-S)·n=|n|*k


由于点S为平面上的任意一点,因此需要满足S·n=-d,所以上述公式还可以转换为Q·n+d=|n|*k,

最后得到点Q到平面的距离k = (Q·n+d)/|n|


结合以上结果,可以得到镜像点Q’的坐标:

Q’=Q-2*n/|n|*((Q·n+d)/|n|)


为了便于计算,我们只考虑法线n为单位向量的情况,则上述公式变为:

Q’=Q-2*n*(Q·n+d)


分解上述结果,最终得到点Q’坐标的各个分量如下:


Q’x = (1-2*nx*nx)*Qx-2*nx*ny*Qy-2*nx*nz*Qz-2*nx*d

Q’y = -2*nx*ny*Qx+(1-2*ny*ny)*Qy-2*ny*nz*Qz-2*ny*d

Q’z = -2*nx*nz*Qx-2*ny*nz*Qy+(1-2*nz*nz)*Qz-2*nz*d


根据以上信息即可求得镜面反射矩阵


在unity中的实现:

void OnWillRenderObject()
{
    if (m_IsRenderering)
        return;
    m_IsRenderering = true;
    m_MirrorCamera.CopyFrom(camera);
    m_MirrorCamera.targetTexture = m_RenderTexture;
     
    //计算平面向量:注意这里使用的平面模型为unity引擎自带的平面,其法线方向为模型的up轴方向,如果考虑使用其它平面模型,需要注意平面的法线朝向
    m_Plane = new Vector4(plane.transform.up.x, plane.transform.up.y, plane.transform.up.z, -Vector3.Dot(plane.transform.up, plane.transform.position));
 
    //将计算得到的镜像矩阵应用到镜像摄像机
    m_MirrorCamera.worldToCameraMatrix = camera.worldToCameraMatrix * ReflectMatrix(m_Plane);
         
    //注意当相机镜像后,其渲染的模型的顶点绕序也会镜像,需要将背面裁剪设置为正面裁剪,渲染结束后再修改回来
    GL.invertCulling = true;
    m_MirrorCamera.Render();
    GL.invertCulling = false;
    m_IsRenderering = false;
}
 
private Matrix4x4 ReflectMatrix(Vector4 plane)
{
    Matrix4x4 m = default(Matrix4x4);
    m.m00 = -2*plane.x*plane.x + 1;
    m.m01 = -2 * plane.x * plane.y;
    m.m02 = -2 * plane.x * plane.z;
    m.m03 = -2 * plane.x * plane.w;
 
    m.m10 = -2 * plane.x * plane.y;
    m.m11 = -2 * plane.y * plane.y + 1;
    m.m12 = -2 * plane.y * plane.z;
    m.m13 = -2 * plane.y * plane.w;
 
    m.m20 = -2 * plane.z * plane.x;
    m.m21 = -2 * plane.z * plane.y;
    m.m22 = -2 * plane.z * plane.z + 1;
    m.m23 = -2 * plane.z * plane.w;
 
    m.m30 = 0; m.m31 = 0;
    m.m32 = 0; m.m33 = 1;
    return m;
}

最终渲染效果如图:



此时对于位于平面以下的物体部分不会再被渲染到反射贴图中了。


此时的结果几乎看不出有什么问题,但仔细考虑如下情况:



当我们镜像相机C的到相机C’后,相机C’的视锥体裁剪区域为A+B,而实际上B区域位于平面以下,应该不属于被反射的部分,实际应该只能渲染A区域,因此如果上述效果中出现位于平面以下的部分,则渲染即会出现错误:




这种情况就需要使用平面P来计算斜裁剪矩阵了,详细内容请参考下一篇文章:


【图形与渲染】相机平面镜反射与斜裁剪矩阵(下)



更多内容请浏览我的博客原文:http://www.lsngo.net/2018/01/07/graphics_mirrorcamera_1/




猜你喜欢

转载自blog.csdn.net/mobilebbki399/article/details/79491825