Unity下平面反射实现_unity 平面反射-程序员宅基地

技术标签: unity  平面  Unity  图形渲染  渲染  

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

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

一、平面反射的原理

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

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

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

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

1.1 平面反射矩阵

1.1.1 平面方程的计算

我们现在来推导一下把摄像机关于平面对称的反射矩阵。
我们知道一个平面可以表示为 P ∗ N + d = 0 P*N+d=0 PN+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 ∗ ∣ A O ∣ A'=A-2*n*|AO| A=A2nAO,负号是因为方向和法线相反。所以,关键是求出|AO|。因为AO实际上是AP在法线相反方向的投影向量,那么 ∣ A O ∣ = d o t ( A P , n ) = d o t ( A − P , n ) = d o t ( A , n ) − d o t ( P , n ) |AO|=dot(AP,n)=dot(A-P,n)=dot(A,n)-dot(P,n) AO=dot(AP,n)=dot(AP,n)=dot(A,n)dot(P,n)。由于P满足平面方程,因此 d o t ( P , n ) = d dot(P,n)=d dot(P,n)=d,因此 ∣ A O ∣ = d o t ( A , n ) + d |AO|=dot(A,n)+d AO=dot(A,n)+d,因此 A ′ = A − 2 ∗ n ∗ ( d o t ( A , n ) + d ) A'=A-2*n*(dot(A,n)+d) A=A2n(dot(A,n)+d)

假设n为(nx,ny,nz),已知d的值,A是(x,y,z)点作为我们要变换的点,A’为(x’,y’,z‘),那么我们可以得到:
x ’ = x − 2 ( x ∗ n x + y ∗ n y + z ∗ n z + d ) ∗ n x = ( 1 − 2 n x ∗ n x ) x + ( − 2 n x ∗ n y ) y + ( − 2 n x ∗ n z ) z + ( − 2 d n x ) x’ = x - 2(x * nx + y * ny + z * nz + d)* nx = (1 - 2nx * nx)x +(-2nx * ny)y + (-2nx * nz)z + (-2dnx) x=x2(xnx+yny+znz+d)nx=(12nxnx)x+(2nxny)y+(2nxnz)z+(2dnx)
y ’ = y − 2 ( x ∗ n x + y ∗ n y + z ∗ n z + d ) ∗ n y = ( − 2 n x ∗ n y ) x + ( 1 − 2 n y ∗ n y ) y + ( − 2 n y ∗ n z ) z + ( − 2 d n y ) y’ = y - 2(x * nx + y * ny + z * nz + d)* ny = (-2nx * ny)x + (1 - 2ny * ny)y + (-2ny * nz)z + (-2dny) y=y2(xnx+yny+znz+d)ny=(2nxny)x+(12nyny)y+(2nynz)z+(2dny)
z ’ = z − 2 ( x ∗ n x + y ∗ n y + z ∗ n z + d ) ∗ n z = ( − 2 n x ∗ n z ) x + ( − 2 n y ∗ n z ) y + ( 1 − 2 n z ∗ n z ) z + ( − 2 d n z ) z’ = z - 2(x * nx + y * ny + z * nz + d)* nz = (-2nx * nz)x + (-2ny * nz)y + (1 - 2nz * nz)z + (-2dnz) z=z2(xnx+yny+znz+d)nz=(2nxnz)x+(2nynz)y+(12nznz)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)
图形与渲染】相机平面镜反射与斜裁剪矩阵(下)-斜裁剪矩阵

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/csuyuanxing/article/details/122937297

智能推荐

制作AR换装游戏(上篇AR识图)#1024程序员节#_ar换装 产品-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏14次。EasyAR制作AR游戏的方法我之前的文章讲过,只是当时用的旧版的,链接放上:Unity和Easy AR制作一个AR的APP_alayeshi的专栏-程序员宅基地这个不是什么正规的项目。。。就是觉得AR好玩,研究了一下,很早之前就玩过了,现在再做的时候竟然忘记了。看来还是要记录一下的。。。功能就是:用手机扫描一个图片,然后会出现一个AR物体,当然这物体是你自己设置的模型。首先说如何制作一个AR的APP需要准备什么,easy ar这个插件,我用的版本是2.2.0的;unity我用的是unity2018.._ar换装 产品

vue实现图片放大缩小及拖拽_vue图片放大缩小拖动-程序员宅基地

文章浏览阅读3.8k次。vue实现图片放大缩小及拖拽_vue图片放大缩小拖动

@EnableAutoConfiguration和@SpringbootApplication注解_@enableautoconfiguration与@springbootapplication-程序员宅基地

文章浏览阅读2.4k次。一、@EnableAutoConfigurationThe second class-level annotation is @EnableAutoConfiguration. This annotation tells Spring Boot to “guess” how you will want to configure Spring, based on the jar dependenci_@enableautoconfiguration与@springbootapplication

canal 全量/增量数据同步说明_canal 全量同步数据-程序员宅基地

文章浏览阅读7.4k次,点赞3次,收藏8次。一、日志文件完整1、全量数据同步1、修改\canal.deployer-1.1.5\conf\example下的instance.properties通过以下三个配置实现canal全量数据同步# mysql日志文件canal.instance.master.journal.name=mysql-bin.000001# 获取日志的起始位置canal.instance.master.position=0# 获取日志的起始时间戳canal.instance.master.timestamp=16_canal 全量同步数据

法语电话面试程序员面试问题和汇总-程序员宅基地

文章浏览阅读885次,点赞24次,收藏18次。法语电话面试程序员面试问题和汇总

全金属vivaldi天线设计----学习笔记_(1)vivaldi天线(ava)设计-程序员宅基地

文章浏览阅读1.1k次,点赞3次,收藏19次。全金属 Vivaldi 天线单元为宽频带单元,其在自由空间中孤立单元的特性与构成阵列的阵元的特性是不同的。_(1)vivaldi天线(ava)设计

随便推点

大一作业HTML网页作业 HTML CSS制作二十四节气网页-程序员宅基地

文章浏览阅读118次。网站布局方面:计划采用目前主流的、能兼容各大主流浏览器、显示效果稳定的浮动网页布局结构。网站程序方面:计划采用最新的网页编程语言HTML5+CSS3+JS程序语言完成网站的功能设计。并确保网站代码兼容目前市面上所有的主流浏览器,已达到打开后就能即时看到网站的效果。网站素材方面:计划收集各大平台好看的图片素材,并精挑细选适合网页风格的图片,然后使用PS做出适合网页尺寸的图片。网站文件方面:网站系统文件种类包含:html网页结构文件、css网页样式文件、js网页特效文件、images网页图片文件;

基于springboot的试卷管理系统 毕业设计开题报告_试卷管理系统摘要-程序员宅基地

文章浏览阅读1.5k次,点赞20次,收藏23次。基于springboot的试卷管理系统 毕业设计开题报告,大学生本科专科专升本成人教育毕业设计毕设开题报告模板,研究背景与意义、国内外研究现状、、研究思路与方法、研究内客和创新点、后台功能需求分析和前端功能需求分析、研究思路与研究方法、可行性、研究进度安排、论文(设计)写作提纲、主要参考文献_试卷管理系统摘要

一、C语言基础——C语言介绍及数据——十进制、二进制、八进制和十六进制的数据表示与转换_c语言二进制八进制十进制十六进制-程序员宅基地

文章浏览阅读2.9k次,点赞4次,收藏19次。本文介绍了C语言的基本概念和数据表示。在C语言中,程序是存放在磁盘上的一系列有序的指令代码集合,而数值数据是可以被输入计算机并直接参与运算的数据。文章详细介绍了十进制、二进制、八进制和十六进制这几种常用的数值数据的表示方法,并介绍了它们之间的相互转换方法。同时,文章还提到了十六进制数的表示方法和常用的表示符号。ASCII的理解和分析。_c语言二进制八进制十进制十六进制

手机python编程软件哪个好,手机python编程软件app-程序员宅基地

文章浏览阅读885次,点赞16次,收藏20次。正确的用法,应该就是学编程的时候,用来练习练习,倒是一个不错的好选择,或者自己有些小项目,拿来码码代码什么的。由于内置了SL4A,可以很方便的调用安卓操作系统的一些API做些有趣的事情,比如可以通过SL4A获取手机地理位置,打开蓝牙,发送手机短信,打开手机摄像头等等。Qpython是一个Python引擎,只能运行在安卓系统上,内置了一个Python编辑器,可以直接在手机上写Python代码,支持缩进,语法高亮等特性。这是编程IDE的标配,可以执行一些代码片段,不过写手机上输入代码还是挺麻烦的。

Day01:Web应用&架构搭建&站库分离&路由访问&配置受限&DNS解析_架构搭建-站库分离-路由访问-配置受限-dns解析-程序员宅基地

文章浏览阅读891次,点赞14次,收藏17次。小迪安全-Day01:Web应用&架构搭建&站库分离&路由访问&配置受限&DNS解析_架构搭建-站库分离-路由访问-配置受限-dns解析