平面反射通常指的是在镜子或者光滑地面的反射效果上,如下图所示,

上图是一个光滑的平面,平面上的物体在平面上有对称的投影。

一、平面反射的原理

对于光照射到物体表面然后发生完美镜面反射的示意图,如下所示,

对于平面反射,假设平面上任意一点都会发生完美的镜面反射。因此,眼睛看到物体的一点的反射信息是从反射向量处得到的,这个可以用下图来表示,

这个实际上相当于,眼睛从平面的下面看向反射向量,如下图所示,

因此,如上图所示,我们可以把摄像机根据平面对称变换到A点所示的位置,然后再渲染一遍场景到RenderTexture中。当我们渲染点O的反射信息时候,就可以到这张RT中去采样了。那么如何去采样反射信息了?使用点O的屏幕空间坐标。因为,RT是从A点看到的场景,视线和平面的交点O是当前渲染的像素点,因此用O的屏幕空间坐标去采样RT就可以得到其反射信息。

1.1 平面反射矩阵

1.1.1 平面方程的计算

我们现在来推导一下把摄像机关于平面对称的反射矩阵。
我们知道一个平面可以表示为P∗N+d=0P*N+d=0P∗N+d=0。P是平面上任意一点,N是平面的法向量,d是一个常数。我们首先需要求出平面方程。对于平面,其世界空间的法向量就是N。用平面的世界空间位置带入P即可求出d的值。

plane = new Vector4(planeNormal.x, planeNormal.y, planeNormal.z, -Vector3.Dot(planeNormal, planePosition) - Offset);

我们可以用以上的一个Vector4表示一个平面,前三个分量表示normal,第四个分量表示d。

1.1.2 平面反射矩阵的计算


如上图所示,我们需要计算点A关于平面的对称点A’。关键在于计算出点A到平面的距离AO的大小。那么A′=A−2∗n∗∣AO∣A'=A-2*n*|AO|A′=A−2∗n∗∣AO∣,负号是因为方向和法线相反。所以,关键是求出|AO|。因为AO实际上是AP在法线相反方向的投影向量,那么∣AO∣=dot(AP,n)=dot(A−P,n)=dot(A,n)−dot(P,n)|AO|=dot(AP,n)=dot(A-P,n)=dot(A,n)-dot(P,n)∣AO∣=dot(AP,n)=dot(A−P,n)=dot(A,n)−dot(P,n)。由于P满足平面方程,因此dot(P,n)=ddot(P,n)=ddot(P,n)=d,因此∣AO∣=dot(A,n)+d|AO|=dot(A,n)+d∣AO∣=dot(A,n)+d,因此A′=A−2∗n∗(dot(A,n)+d)A'=A-2*n*(dot(A,n)+d)A′=A−2∗n∗(dot(A,n)+d)。

假设n为(nx,ny,nz),已知d的值,A是(x,y,z)点作为我们要变换的点,A’为(x’,y’,z‘),那么我们可以得到:
x’=x−2(x∗nx+y∗ny+z∗nz+d)∗nx=(1−2nx∗nx)x+(−2nx∗ny)y+(−2nx∗nz)z+(−2dnx)x’ = x - 2(x * nx + y * ny + z * nz + d)* nx = (1 - 2nx * nx)x +(-2nx * ny)y + (-2nx * nz)z + (-2dnx)x’=x−2(x∗nx+y∗ny+z∗nz+d)∗nx=(1−2nx∗nx)x+(−2nx∗ny)y+(−2nx∗nz)z+(−2dnx),
y’=y−2(x∗nx+y∗ny+z∗nz+d)∗ny=(−2nx∗ny)x+(1−2ny∗ny)y+(−2ny∗nz)z+(−2dny)y’ = y - 2(x * nx + y * ny + z * nz + d)* ny = (-2nx * ny)x + (1 - 2ny * ny)y + (-2ny * nz)z + (-2dny)y’=y−2(x∗nx+y∗ny+z∗nz+d)∗ny=(−2nx∗ny)x+(1−2ny∗ny)y+(−2ny∗nz)z+(−2dny),
z’=z−2(x∗nx+y∗ny+z∗nz+d)∗nz=(−2nx∗nz)x+(−2ny∗nz)y+(1−2nz∗nz)z+(−2dnz)z’ = z - 2(x * nx + y * ny + z * nz + d)* nz = (-2nx * nz)x + (-2ny * nz)y + (1 - 2nz * nz)z + (-2dnz)z’=z−2(x∗nx+y∗ny+z∗nz+d)∗nz=(−2nx∗nz)x+(−2ny∗nz)y+(1−2nz∗nz)z+(−2dnz),
改写成矩阵形式可以得到平面反射的矩阵为:

1.2 斜裁剪矩阵

上面我们已经推导出平面反射矩阵,不过还有一种特殊情况需要处理。

如上图所示,我们的平面是P,将摄像机从C点对称到C’点。从C’可以看到的区域包括A和B,但是B是在平面P的下部,我们从C是无法看到的。因此,从C’点渲染场景RT的时候必须排除B区域,也就是需要将平面P作为裁剪平面,裁剪掉区域B。
这个东西叫做斜裁剪矩阵,我们可以推导出具体的斜裁剪矩阵或者使用Unity提供的接口直接计算出来。
计算斜裁剪矩阵需要两个步骤,第一步是计算出摄像机空间下的平面表示,第二步是用摄像机空间下的平面和原投影矩阵一起计算斜投影矩阵。
具体推导可以参考文章,【图形与渲染】相机平面镜反射与斜裁剪矩阵(上)-镜像矩阵。
第二步也可以使用Unity的camera中的接口CalculateObliqueMatrix来计算,参数就是第一步得到的平面。

二、平面反射的实现

2.1 平面反射的脚本

这里的脚本指的是生成RenderTexture需要的脚本,脚本继承自MonoBehaviour。

2.1.1 默认管线下的实现

默认管线下需要在函数OnWillRenderObject中,基本步骤是先计算反射平面,然后计算反射矩阵和斜投影矩阵,设置反射相机的斜投影矩阵,然后将反射相机变换到平面对面,调用相机的Render函数渲染RT。需要注意的是,渲染的时候需要修改物体正反旋向,即GL.invertCulling设置为true。

2.1.2 Urp管线下的实现

Urp管线下,需要绑定 RenderPipelineManager.beginCameraRendering的回调,然后在回调中实现。回调中会接收到当前渲染的相机,反射相机就是该相机关于平面的镜像。同时,渲染RT的函数需要改成UniversalRenderPipeline.RenderSingleCamera,传入context和反射相机。其余步骤,跟默认管线的区别不大。

2.2 平面反射的Shader

平面反射的shader可以使用普通的场景shader做修改。关键在于如何采样平面反射信息和平面反射强度以及模糊等。

2.2.1 平面反射信息的采样

这个之前已经解释过用屏幕空间坐标来采样RT。

2.2.1 平面反射强度

这个可以用菲涅尔效应计算,不过关键点在于强度必须是NdotV的函数,最简单的方式是计算出NdotV,对NdotV取反或者1-NdotV,因为入射角越大反射光越强,同时提供一个最大最小值来限制强度范围。

2.2.1 模糊和Mipmap

可以采样周围多个像素然后做平均模糊或者高斯模糊。不过,最简单的方式是对RT强制生成Mipmap,采样RT的时候指定Mipmap级别。那么,mipmap级别如何计算了。我们可以根据shader的粗糙度来转行为mipmap级别,这个参考unity的urp内置shader函数PerceptualRoughnessToMipmapLevel的实现。

最终得到的反射效果如图,

三、平面反射的优化

平面反射由于需要对场景镜像渲染一遍, DrawCall会翻倍,而且由于原理限制,没有有效的优化手段,因此平面反射通常是应用在特定的场合下。
优化的手段,主要是降低生成反射RT的消耗。

3.1 控制反射层级

我们可以在反射脚本中增加层级控制,然后设置反射相机的cullingMask,指定层级的物体才会被渲染到RT中。

3.2 控制反射RT的尺寸

可以根据反射平面的大小来调整RT的尺寸,同样我们可以在脚本中开放这个尺寸设置来方便美术来调整RT大小。

3.3 降低RT的shader复杂度

我们可以使用Unity的shader replacement将生成RT的shader都替换为一个简单的shader,然后再渲染生成RT,这样可以大幅度降低shader计算复杂度。不过,DrawCall是无法降低的。

参考资料

Unity Shader-反射效果(CubeMap,Reflection Probe,Planar Reflection,Screen Space Reflection)
图形与渲染】相机平面镜反射与斜裁剪矩阵(下)-斜裁剪矩阵

Unity下平面反射实现相关推荐

  1. 平面反射builltinURP—— UnityShader学习记笔记

    文章目录 自言自语 一.C# builltin 二.URP 总结 自言自语 又是好久没有更新笔记了.最近项目真的很忙.一直想更新的笔记现在才有空梳理.今天要记载的就是平面反射. 一.C# buillt ...

  2. UE4-(反射)平面反射

    一.创建 注意:平面反射拖拽到场景后,会创建一大块平面,这个平面是临时创建的,当程序运行后,我们不会看到这个平面. 平面反射会捕获反射信息,只能用于平面反射效果,所以只适用于平整的对象,例如镜子,水池 ...

  3. Vulkan_平面反射

    平面反射 屏幕空间反射是利用屏幕空间数据进行计算反射的一种技术.它通常用于创建更精细的反射,如在潮湿的地板表面或水坑. 一.实现思路 主要分两次进行基本的离屏渲染.第一遍将镜像的场景渲染到具有颜色和深 ...

  4. OpenGL渲染纹理和平面反射

    OpenGL渲染纹理和平面反射 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <stdio.h> #include "GL ...

  5. Unity下如何实现RTMP或RTSP流播放和录制

    技术背景 在探讨Unity平台RTMP或RTSP直播流数据播放和录制之前,我们先简单回顾下RTSP或RTMP直播流数据在Unity平台的播放流程: 通过Native RTSP或RTSP直播播放SDK回 ...

  6. unity下图灵机器人的使用

    unity下图灵机器人的使用 这里json的解析与建立使用的是LitJson.dll 一.机器人建立 首先到图灵机器人的官网http://www.tuling123.com/注册一个号并建立机器人(有 ...

  7. Unity下计步器Pedometer算法的实现

    Unity下计步器Pedometer的实现 1.环境 unity2018,Android(小米5) 2.原理 加速度传感器的计步算法Pedometer 3.实现 注释写的很清楚了应该 using Un ...

  8. Unity下的UDP客户端

    Unity下简洁有效的UDP客户端 前言 代码 资源 其他 中文乱码问题 前言 本来就想从网上找一个Unity的UDP客户端,百度上试了好多教程,问题百出,让人气不打一处来. 就几行代码的事情,浪费时 ...

  9. unity中的反射探头和光照反射探头

    今天我们来学习一下unity里的反射探头和是光照反射探头 要设置反射探头,一开始我们就要做好准备工作,第一步也是我们unity里如果要渲染和烘焙灯光都一个要做的第一步就是趁我们的场景里东西并不多的时候 ...

最新文章

  1. 【Python】百度首页GIF动画的爬虫
  2. 开发笔记13 | 部署 Node.js 应用程序到云 ECS
  3. Oracle 存储过程中查询序列值并用变量接收
  4. 掌握 Ajax,第 6 部分: 建立基于 DOM 的 Web 应用程序
  5. ITK:创建高斯导数内核
  6. python输入序列语句_Python基础教程(一) - 序列:字符串、列表和元组
  7. 5 个 IDEA 必备插件,让效率成为习惯
  8. oppo5.0以上系统怎么样不Root激活Xposed框架的经验
  9. 通过示例学 Golang 2020 中文版【翻译完成】
  10. linux看硬件配置命令,Linux查看硬件配置命令
  11. Sharepoint定制的时候应该注意的事项
  12. 互联网公司招聘奇葩黑历史:不要学日语,不要信中医,不要黄泛区……
  13. 这群程序员工作日竟然不用上班?
  14. 使用FZip创建压缩文件保存到桌面
  15. 用户故事与敏捷方法笔记---搜集故事
  16. 《概率论与数理统计》(浙大第四版)第五章总结笔记(纯手写)
  17. Axure版PRD产品需求文档(教程+下载)
  18. PDF不能编辑怎么办?捷速PDF编辑器快速编辑!
  19. 设计稿 自动html,简单的登陆页面PSD设计稿来演示转化为HTML页面的全部过程
  20. c语言飞机源代码,C语言写的飞机源码

热门文章

  1. Asus EeePC X101上网本为MeeGo带来新的生机
  2. 新网科普:网站备案成功后,为什么还会被注销?
  3. OWC生成统计报表(柱形图)
  4. 大数据缓存管理系统设计与实现
  5. 2019年的如今iPhone4S还能这么用
  6. java中什么是外层实例,ew MyAsyncTask(); 报错
  7. 利用Hyperledger fabric-config库进行通道配置更新
  8. 圣天诺HL加密锁(原HASP加密锁)快速入门
  9. html5+文本间距,html怎么设置文字的间距
  10. HTML5期末大作业:宠物主题网站设计——酷酷动物主题响应式网页(5页) HTML+CSS+JavaScript...