赞
踩
镜面反射(Planar reflection)是游戏中比较常用的效果,例如角色的展示界面的地面倒影,物体在水面的倒影,光滑的表面等。
在URP中常规的镜面反射一般使用镜头方案来实现,也就是创造一个反射镜头,按照反射平面的位置翻转镜头,渲染一遍反射物体到RT,在绘制水面的时候,采样这个RT进行着色。
SSR目前在URP中一般使用步进或compute shader来进行实现,步进的性能在移动端基本无法接受。而compute shader存在一定兼容性问题,需要配合例如vulkan之类的现代图形api才能获得较好的效果。
相对于前两种方案而言,这个方案更为简单,相比镜头方案也有更好的性能,易于理解,本文主要分析这个方案,探讨一下该方案的实现。
我们知道镜头方案的反射是通过翻转摄像机得到的,那么利用相同的思路我们也可以通过翻转要反射的物体来实现镜面反射。
在一开始实现这个render feature之前,也尝试了直接翻转镜头来实现,也就是在画反射平面之前,翻转一次镜头,绘制要反射的物体,再恢复摄像机原来的transform正常绘制其他物体和反射平面。但在URP下实现非常不理想,在render feature中我们能做的就是在camera setup和execute等回调函数中实现相应的翻转逻辑,而这样实现会出现闪烁的现象,猜测是一帧之内由引擎传入的各种矩阵是确定的,在一帧之内改变同一个镜头的位置不被urp支持,或是由于command buffer的渲染命令出于优化目的并非是立即执行的,所以翻转镜头的逻辑时序并不奏效。
因此,我们这里采用翻转要反射的物体进行实现。
通过绘制需要反射物体的render feature比较容易,我们首先需要一个绘制物体的render feature本身,然后在shader上额外增加一个pass来专门处理绘制反射物体。
首先render feature需要能够绘制翻转的物体,因此他的参数应该和urp内置的render objects的render feature相似,功能上比较简单:
这个render feature具有根据layer mask来过滤绘制物体的特征,也可以通过降采样和改变RT的格式来进行优化。而他绘制的层级中的物体应当具有一个reflection pass来处理反射的绘制,在这个绘制中我们对物体的坐标根据平面的位置进行翻转,再将高于平面的部分clip掉,即可得到一张反射的RT了。
对于需要被反射的物体,我们需要额外增加一个reflection pass来处理。
首先需要明确的是我们需要在reflection pass中实现这两个功能:
根据反射平面反转物体
去除大于反射平面的部分
下面我们直接来看shader的结构:
- Shader "ExampleShaderStructure"
- {
- Properties
- {
- //Properties...
- }
-
- SubShader
- {
- //Global tags...
- //Variants...
-
- Pass
- {
- Name "ForwardPass"
- Tags{"LightMode" = "UniversalForward"}
-
- //Render state....
-
- HLSLPROGRAM
-
- //Variants...
-
- #include "ForwardPass.hlsl"
- ENDHLSL
- }
-
- Pass
- {
- Name "ReflectionPass"
- Tags{"LightMode" = "ReflectionPass"}
-
- Cull Front
- //Render state....
-
- HLSLPROGRAM
-
- //Variants...
-
- #define REFLECTION_PASS 1
-
- #include "ForwardPass.hlsl"
-
- ENDHLSL
- }
-
- //Othes passes...
- }
-
- FallBack "Hidden/Universal Render Pipeline/FallbackError"
- //Shader editor...
- }
这里我们重用了forward pass的include代码forwardpass.hlsl,而在reflectionpass中额外做了一个宏定义来在forwardpass中增加镜面翻转的逻辑。
下面我们来看forwardpass中如何实现镜面翻转:
- half _ReferencePlaneY;
-
- struct Attributes
- {
- float4 positionOS : POSITION;
- //Other parameters...
- }
-
- struct Varyings
- {
- #if defined(REFLECTION_PASS)
- float3 reversedPositionWS : TEXCROOD8;
- #endif
- float4 positionCS : SV_POSITION;
- //Other parameters...
- }
-
- Varying VertexShader()
- {
- Varyings output = (Varyings)0;
-
- //...
-
- #if defined(REFLECTION_PASS)
- float3 positionWS = output.positionWS;
- positionWS.y = _ReferencePlaneY - (positionWS.y - _ReferencePlaneY);
- output.reversedPositionWS = positionWS;
- output.positionCS = TransformWorldToHClip(positionWS);
- #else
- output.positionCS = vertexInput.positionCS;
- #endif
-
- return output;
- }
-
- half4 FragmentShader()
- {
- half4 finalColor;
- #if defined(REFLECTION_PASS)
- clip(_ReferencePlaneY - input.reversedPositionWS.y);
- #endif
-
- //....
-
- return finalColor;
- }
这里仅考虑了最简单的情况,也就是镜面反射的平面和XOZ平面平行的情况,这个时候我们仅需要根据Y对世界坐标进行修改即可。把翻转后的世界空间坐标作为参数传入fragment shader来处理高于镜面反射平面剔除的逻辑。而真正的世界空间坐标并没有改变,因此正常的光照依然可以执行。
需要注意reflection pass这里需要cull front来正确的执行剔除。
从frame buffer中可以看到渲染的RT
对比原来的场景
相比于传统镜头方案,相比于镜头上可能存在的冗余的render feature挂载,多余的blit或者管线上的镜头逻辑设置,这个方案更为轻量级,仅适用一个pass就可以实现。
同时他可以通过降采样,修改rt格式,简化reflection pass中的光照实现来进一步优化性能。
而相比于compute shader实现的平面镜面反射,他又可以绘制出不在镜头中的部分,同时又有更好的兼容性。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。