Unity Shader-反射效果(CubeMap,Reflection Probe,Planar Reflection,Screen Space Reflection)

Unity环境渲染与反射探针的深入探索 在Unity游戏开发过程中,环境渲染和物体反射效果对于提升游戏画面的真实感和沉浸感至关重要。本文将详细介绍如何在Unity中实现高质量的环境渲染以及如何使用反射探针(Reflection Probes)来模拟物体的反射效果,帮助开发者创建更加生动和逼真的游戏场景。 阅读详情
AI摘要

本文详细介绍了Unity中实现反射效果的多种技术,包括Cube Map、Reflection Probe、Planar Reflection和Screen Space Reflection(SSR)。通过原理分析和代码示例,阐述了各种方法的优缺点。反射效果对于提升场景的真实感至关重要,而SSR作为实时反射的一种解决方案,尽管存在性能和效果限制,但在延迟渲染下仍具有广泛应用。文章还探讨了SSR的基本原理、优化方法,如二分搜索和Dither RayMarching,以及在延迟渲染中的应用。

简介

反射效果,是一个渲染中很重要的一个效果。在表现光滑表面(金属,光滑地面),水面(湖面,地面积水)等材质的时候,加上反射,都可以画面效果有很大的提升。来看几张图:

先来张最近比较火爆的国产大作《逆水寒》的镜湖,我木有钱钱进副本,只好从网上找了个截图,感觉很漂亮哈。

《罗马之子》主角的金属盔甲反射&远处水面倒影反射。

《耻辱-外魔之死》地面积水的反射,只反射了静态场景,没有反射动态物体,似乎是一个比较省的做法。

《底特律-变人》神作,画面和剧情都相当给力,就像看电影一样,比如上面图的地面积水的效果,实时反射效果,包括场景的动态物体。

可见,反射效果对于场景整体提升有很大作用,今天本人就来学习一下几种常见的反射的实现。如有错误还望各位高手批评指正。

 

反射的相关基本内容

反射的原理及分类

反射,应该属于间接光照的范畴,而非直接光照。我们正常计算光的dot(N,L)或者dot(H,N)时计算的均为直接光照,光源出发经过物体表面该像素点反射进入眼睛的光照。然而这只是一部分,该点还可以接受来自场景中所有其他点反射的光照,如果表面光滑,则表面就可以反射周围的环境(如镜面,金属),到那时这个计算相当复杂,相当于需要在该点法线方向对应的半球空间上做积分运算才可能计算完全的间接光照,而且光线与物体碰撞后并不会消亡,而是经过反射或折射,改变方向后继续传递,相当于无限递归。实时计算现在来看应该还是不太可能的。正好最近在玩这个,尝试了一下使用RayTracing,屏幕空间发射射线碰撞,反弹次数限制在5次,渲染几个球体,分辨率很低的情况下,在PC平台离线渲染用了将近一个小时(虽然是cpu计算,没有并行)

既然真的反射如此之费,但是反射的效果又如此诱人,前辈们就开始想各种办法来模拟反射。于是乎,各种性能友好的反射方法就应运而生了。最常见的就是环境贴图的方案,目前使用最多的应该是cube map,将对象周围环境烘焙到贴图上进行采样,进阶版其实就是功能更强大的Reflection Probe了。另外平面反射(Planar Reflection)可以用一个反转的相机再渲染一次场景模拟,更复杂一些的是屏幕空间反射Screen Space Reflection(SSR,额,不是某游戏抽的那个)。

Reflect方法推导

我们在各种反射效果的实现中几乎都会用到一个函数:reflect函数,cg语言自带了这个函数,不过还是要来看一下这个函数的实现,之前本人在《图形学相关数学》这篇blog里面推导过,此处就只贴出一张原理图了:

需要注意的就是Reflect的计算过程要求入射方向和法向量都是单位向量,否则结果是不对的。cg的reflect函数自身有没有在计算前做normalize就不得而知了(normalize相对还是一个比较费的操作,无谓增加消耗可能不是很值得,不知道会不会像C++ std的检查一样交给使用者去做喽,如果有知道的大佬可以告诉我哈)。

环境反射

先来看一发最简单的反射,环境反射。本文中的环境反射,指的是静态的环境反射,也就是预先烘焙好的环境贴图。主要优点是性能较好,可以用于任意表面。环境反射包含极坐标映射(效率低,变换非线性),球面映射(生成复杂,非线性),立方体映射(即Cube Map,简单,线性,需要存储6个面),八面体映射(线性,一张图)。

最常用的应该就是Cube Map了,Cube Map其实是上个世纪90年代左右就已经提出的技术了,应用也很广泛,比如天空盒,点光源的Shadow Map,模拟反射等等。所谓Cube Map,顾名思义就是一个立方体的贴图,六个面分别对应一张贴图,由相机朝向6个方向分别渲染得到的。

Cube Map生成(旧版)

先看一下最基本的生成Cubemap的方法,Unity已经为我们提供好了接口(注意,这种方式已经不推荐了,没有HDR效果,但是对Cube Map比较直观的认识):

/********************************************************************
 FileName: CubeMapTool.cs
 Description: 生成Cube Map 工具
 history: 27:6:2018 by puppet_master
 https://blog.youkuaiyun.com/puppet_master
*********************************************************************/
using UnityEngine;
using UnityEditor;

public class CubeMapTool : EditorWindow
{
    private Cubemap cubeMap = null;

    [MenuItem("Tools/Cube Map Generate")]
	public static void GenerateCubeMap()
    {
        GetWindow<CubeMapTool>();
    }

    private void OnGUI()
    {
        cubeMap = EditorGUILayout.ObjectField(cubeMap, typeof(Cubemap), false, GUILayout.Width(400)) as Cubemap;
        if (GUILayout.Button("Render To Cube Map"))
        {
            SceneView.lastActiveSceneView.camera.RenderToCubemap(cubeMap);
        }
    }
}

Project面板下右键可以创建一个Cube Map资源,然后勾选Readable(否则写不进去),拖入槽中,然后在编辑器下移动相机到合适位置,点击按钮,就可以将当前场景渲染到Cube Map中了:

Cube Map使用

shader代码如下:

//puppet_master
//https://blog.youkuaiyun.com/puppet_master  
//2018.6.27  
//基本的Cube Map反射效果
Shader "Reflection/CubeMapReflection"
{
	Properties
	{
		_CubeTex ("Cube Tex", Cube) = ""{}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct v2f
			{
				float4 vertex : SV_POSITION;
				float3 reflectionDir : TEXCOORD0;
			};
			
			uniform samplerCUBE _CubeTex;
			
			v2f vert (appdata_base v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				float3 worldNormal = UnityObjectToWorldNormal(v.normal);
				float3 worldViewDir = WorldSpaceViewDir(v.vertex);
				o.reflectionDir = reflect(-worldViewDir, worldNormal);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 col = texCUBE(_CubeTex, i.reflectionDir);
				return col;
			}
			ENDCG
		}
	}
}

效果如下:

 

Reflection Probe

Reflection Probe实际上就是环境映射,可以说是进阶版的环境映射了(此处与环境映射并列并非表示它是一种新类型的反射方式,Reflection Probe就是环境映射,只不过是环境映射的扩展,包含环境映射的全部属性)。传统意义的环境映射,更多的时候是对应材质上的一个属性(想象一下,在每个场景里的材质球上拖一个cube map是多么的蛋疼),但是实际上,用环境映射表示反射,表示的是当前的环境属性,属于当前场景的信息,而不再是属于某个材质,这才更对得起环境映射这个名字。这个环境属性实际上就相当于一个全局的变量,Unity已经为我们设置好了相关的属性信息,我们在shader中可以直接使用。

Reflection Probe配置

Reflection Probe分为几种模式,RealTime,Bake,Custom模式。RealTime是实时生成反射贴图,虽然可以设置成每帧更新一个面,不过效率也比较堪忧;Custom模式可以放进去一个自定义的Cube Map;Bake模式需要提前烘焙,Linghting面板烘焙时可以生成场景自身的Reflection Probe,将Reflection Probe Static标签的对象+天空盒进行烘焙。我们可以在场景中放置ReflectionProbe,但是如果不放置,也不至于完全没有效果,当场景有天空盒并且开启了Refrection,Unity默认会给我们一个天空盒的ReflectionProbe的效果,在烘焙的时候就会生成这个信息,与lightmap在同级目录。

上文中提到过相机渲染场景到CubeMap中,比较麻烦,所以新版本的方式就是直接设置Reflection Probe,然后烘焙。Unity直接为我们提供了这个功能,在Reflection Probe面板或者Lighting面板均可以进行烘焙。

关于Reflection Probe的设置方法,可以参考Unity官方文档:Reflection Probe,Lightting中Environment Reflection相关。

Reflection Probe使用

直接使用Reflection Probe比较简单,Unity已经为我们定义好了Reflection Probe的相关shader uniform变量,而且会根据各种设置把合适的Reflection Probe填充到shader变量中。

先看一下Unity中定义的Reflection Probe相关变量:

// ----------------------------------------------------------------------------
// Reflection Probes

UNITY_DECLARE_TEXCUBE(unity_SpecCube0);
UNITY_DECLARE_TEXCUBE_NOSAMPLER(unity_SpecCube1);

CBUFFER_START(UnityReflectionProbes)
    float4 unity_SpecCube0_BoxMax;
    float4 unity_SpecCube0_BoxMin;
    float4 unity_SpecCube0_ProbePosition;
    half4  unity_SpecCube0_HDR;

    float4 unity_SpecCube1_BoxMax;
    float4 unity_SpecCube1_BoxMin;
    float4 unity_SpecCube1_ProbePosition;
    half4  unity_SpecCube1_HDR;
CBUFFER_END

定义,采样,传递CubeMap的函数建议也使用Unity官方提供的函数(HLSLSupport,此处贴出非DX11定义):

// Cubemaps
#define UNITY_DECLARE_TEXCUBE(tex) samplerCUBE tex
#define UNITY_ARGS_TEXCUBE(tex) samplerCUBE tex
#define UNITY_PASS_TEXCUBE(tex) tex
#define UNITY_PASS_TEXCUBE_SAMPLER(tex,samplertex) tex
#define UNITY_DECLARE_TEXCUBE_NOSAMPLER(tex) samplerCUBE tex
#define UNITY_SAMPLE_TEXCUBE(tex,coord) texCUBE (tex,coord)

这样,在使用Reflection Probe就很简单啦,上面的CubeMap采样方法的fragment shader修改一下即可,使用Reflection Probe渲染时支持HDR格式,勾选之后可以将贴图编码为HDR格式,所以在使用之前,需要先进行一步解码操作才能得到正确的结果(与宏和配置有关)。

half4 frag (v2f i) : SV_Target
{
	half4 rgbm = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, i.reflectionDir);
	half3 color = DecodeHDR(rgbm, unity_SpecCube0_HDR);
	return half4(color, 1.0);
}

直接使用天空盒作为Reflection Probe效果如下:

使用环境反射+高光+Bloom+HDR套餐,可以做出金属效果(不由得让我想起《终结者2》里面的液态机器人了)。

Box Projection Reflection Probe

Cube Map正常来说是不考虑物体与被反射物体之间距离的,类似人物盔甲,圆球等不容易看出穿帮的物体效果较好,而如果反射的物体距离被反射的物体很远时,例如无限远的天空盒,反射效果也比较好。比如上图中的反射天空盒效果。但是当反射对象与被反射对象距离较近时,用Reflection Probe(Cube Map)效果就有些差了。我们把反射材质放在地面上,下图中天空效果反射较好,但是栅栏的反射就有些奇怪了:

如果离近看,效果就不对了,在地面的反射完全看不到栅栏:

不过,我们可以用一种叫做Box Projection Cube Map的技术,通过重新修正反射采样方向,得到相对较好的效果,如下图:

使用Box Projection Cube Map很简单,因为Unity已经为我们写好了函数以及所需的全部数据已经传递给了内置shader变量中,我们可以很容易地通过修改几句代码得到上图的效果:

half4 frag (v2f i) : SV_Target
{
	float3 reflectDir = i.reflectionDir;
	//通过BoxProjectedCubemapDirection函数修正reflectDir
	reflectDir = BoxProjectedCubemapDirection(reflectDir, i.worldPos, unity_SpecCube0_ProbePosition, unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax);
	half4 rgbm = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, reflectDir);
	half3 color = DecodeHDR(rgbm, unity_SpecCube0_HDR);
	return half4(color, 1.0);
}

为何直接采样效果不对,而用了这个Magic的函数修改了反射方向后,反射效果就大大提升了呢。

来分析一下,反射时,我们采样Reflection Probe时的向量对应的起点是Reflection Probe的中心点R,方向是在当前像素点计算的视线方向对应法线方向的反射向量PK,如下图所示:

ABCD为Reflection Probe对应的Cube,P为当前像素点,E为相机位置,N为当前像素点对应的法线方向,PK为视线方向计算得到的反射方向。如果我们按照PK方向采样Reflection Probe的话,那么就会是RL(平行于PK)方向。如果R与P点重合,采样结果是正确的,但是只有这一个点正确,其他所有点的结果都是不正确的。为了保证采样效果正确,需要求得真正的采样方向RK。

Reflection Probe的位置R,采样位置P已知,RK = RP+PK。RP向量已知,PK方向已知,所以问题就简化成了求PK方向距离。

首先,K是在Cube包围盒上的点,所以第一个问题就是在于我们需要求得包围盒边界的坐标值才好计算采样碰撞点K,Unity在unity_SpecCube0_BoxMin,unity_SpecCube0_BoxMax这两个内置的shader uniform变量中为我们存储了包围盒的BoundingBox最大最小值,不仅仅在划定Reflection Probe影响范围上有用,还有一个重要的作用就在于Box Projection上。一个Cube,八个顶点,分为八个象限,我们只需要关心PK所在方向上的边界值即可。

这里我们需要用到一个很好玩的运算符,? :运算符。与C中作用一致,但是在shader中,对一个向量使用这个运算符,会分别对向量的每个分量进行? :运算。比如下面的计算:

float3 test = float3(0,0,0.5);
fixed3 col1 = fixed3(1,1,1);
fixed3 col2 = fixed3(0,0,0);
color = (test > 0.0f) ? col1 : col2;
//result : fixed3(0, 0, 1);

有了这个运算符,我们就可以通过(PK > 0.0f) ? rbmax : rbmin得到在PK方向上的包围盒坐标值了,我们假设其为Bound。

不过还有一个问题,在三维空间中,一个向量可能与三维的面三个面相交,如下图所示(这里只画了一个二维方向的示意图,横向表示x轴,纵向表示y轴,结论可以扩展到三维空间):

我们用正常表示射线的方式表达一个向量,已知起始点P,向量方向PK表示为Dir,P + t *Dir = K。t就为该方向上的距离也就是我们最终要求得的PK长度。上图中,我们可能有两个交点坐标,分别为K和H:

K = P + t1 * Dir

H = P + t2 * Dir

Z = P + t3 * Dir(扩展到三维方向)

K和H的具体位置我们不知道,但是我们知道它们在一个方向上的值,即K.x = Bound.x,H.y = Bound.y,Z.z = Bound.z,分别考虑各自方向:

K.x = P.x + t1 * Dir.x

H.y = P.y + t2 * Dir.y

Z.z = P.z + t3 * Dir.z(扩展到三维方向)

再重新整合到一个向量来表示的话,就可以表示成Bound = P + T * Dir,其中T = (t1,t2,t3)。T = (Bound - P) / Dir

不过实际上,我们最终只需要一个碰撞点,从上图很容易看出,我们需要的碰撞点是距离P点最近的点,即沿着P + t * Dir方向行进t最短距离即可,也就是说,我们最终需要的t值只需要从T向量的(t1,t2,t3)中取得最小的值,就是最终PK的距离了。

代码如下:

 half3 nrdir = normalize(worldRefl); //PK方向
 half3 bounding = (nrdir > 0.0f) ? rbmax : rbmin //求得PK方向上包围盒的坐标
 half3 T = (bounding - worldPos) / ndir; //求得三方向上的T值
 half t = min(min(T.x, T.y), T.z); //求得最小t
 
 half3 rp = worldPos - cubeMapCenterPos; //向量RP
 half3 pk = worldPos + t * nrdir; //向量pk
 half3 rk = rp + pk; //向量rk为最终采样方向

再来看一下官方源码的BoxProjectedCubemapDirection函数中的计算:

inline half3 BoxProjectedCubemapDirection (half3 worldRefl, float3 worldPos, float4 cubemapCenter, float4 boxMin, float4 boxMax)
{
    // Do we have a valid reflection probe?
    UNITY_BRANCH
    if (cubemapCenter.w > 0.0)
    {
        half3 nrdir = normalize(worldRefl);

        #if 1
            half3 rbmax = (boxMax.xyz - worldPos) / nrdir;
            half3 rbmin = (boxMin.xyz - worldPos) / nrdir;

            half3 rbminmax = (nrdir > 0.0f) ? rbmax : rbmin;

        #else // Optimized version
            half3 rbmax = (boxMax.xyz - worldPos);
            half3 rbmin = (boxMin.xyz - worldPos);

            half3 select = step (half3(0,0,0), nrdir);
            half3 rbminmax = lerp (rbmax, rbmin, select);
            rbminmax /= nrdir;
        #endif

        half fa = min(min(rbminmax.x, rbminmax.y), rbminmax.z);

        worldPos -= cubemapCenter.xyz;
        worldRefl = worldPos + nrdir * fa;
    }
    return worldRefl;
}

从Box Projection Cube Map的实现来看,的确可以通过修正采样的方向,找到正确的采样方向,达到较好的反射效果。不过买家秀和卖家秀总是有区别的,Box Projection方法在边界有畸变(上图的山有些扭曲),而且,最重要的问题在于,反射的范围要和Reflection Probe的范围一致,否则结果是不对的。所以个人认为,室内场景等边界与Reflection Probe边界一致的这种场景,使用这个技术比较合适,可以用一个比较cheap的方案达到近似plannar reflection的效果。

Box Projection Cube Map似乎也是个挺新的技术,关于BoxProjectionCubeMap的原理可以参考GameDev上发明这个的帖子。

 

Planar Reflection

Planar Reflection,平面反射。顾名思义,就是在平面上运用的反射,名字也正是这种反射方式的限制,只能用于平面,而且是高度一致的平面,多个高度不一的平面效果也不正确(除非针对每个平面单独计算,消耗嘛,你懂得)。一般情况下,一个平面整体反射效果基本可以满足需求,而且对于实时渲染反射效果,需要将要反射的物体多渲染一次,控制好层级数量的话,性能至少是在可以接受的范围,并且相对于其他几种反射效果来说,平面反射的效果是最好的,所以Planar Reflection目前是实时反射效果中使用得比较多的一个方案。

平面方程以及平面反射相关推导

既然说到平面反射,自然,我们得先找到一个方法表示一个平面。常见的两种平面表示方法:

一般式:可以表示为Ax + By + Cz + D = 0(其中A,B,C,D为已知常数,并且A,B,C不同时为零)。

点法式:平面可以由平面上任意一点和垂直于平面的任意一个向量确定,这个垂直于平面的向量称之为平面的法向量。假设平面上一个确定点P0(x0,y0,z0),法向量为N(a,b,c),那么平面上任意点P(x,y,z),满足PP0 · N = 0,即(x - x0, y - y0,z - z0) · (a,b,c) = 0。

两种方式各有优点,一般式变量数量少,但是没有什么有用信息,点法式包含面法线信息,但是需要变量数较多。那么把二者结合一下,或者说变形一下即可。比如点法式点乘展开后得到ax + by + cz + (-ax0 - by0 - cz0) = 0,即为一般式。其中-ax0 - by0 - cz0为常数,表示为d。那么点法式方程就可以表示为N·P + d = 0的形式,其中P为平面上任意一点,N为法向量。那么,d表示神魔恋?要是硬说几何意义的话,可以表示为原点到平面上任意点在法线上的投影长度。所以要表示一个面,我们知道面的法向量N,然后再知道面上任意一点P0,就可以求得d = -Dot(N,P0)。

知道了平面方程的表示,我们还需要再温故一下反射的基本原理,开头我们推导Reflect函数时有过一个示意图,这里面我们再画一张针对平面的:

我们要想求得一个点A相对于平面的反射点A‘,根据反射定律可知,|AB| = |A'B|,BA与N同向,已知A点坐标的话,我们就可以求得A’ = A - 2*|AB| * N。

所以,问题就变成了求空间中任意一点A,到平面N·P + d = 0的距离|AB|,再来一张图,推导一下AB距离:

P为平面上任意一点,A为空间中一点,B为点A在平面上的投影点,N为平面法线(为简化问题,N视为单位向量)。PAB构成三角形,BAP夹角θ。从三角形余弦定理易得|BA| = |PA|cosθ;再通过向量点乘的公式,BA与N同向,PA与N夹角也为θ,所以dot(PA,N)= |PA||N|cosθ => cosθ = dot(PA,N)/ (|PA||N|),代入|BA| = |PA|cosθ,|BA| = dot(PA,N)/ |N|,由于N为单位向量,故|BA| = dot(PA,N)。将PA拆分,|BA| = dot(A - P, N) = dot(A,N) - dot(P,N)。这时候就需要我们的平面方程推导公式了,根据点法式的结果我们知道,对于平面上任意一点P,可以表示为P·N + d = 0的形式,即dot(P,N) = -d.所以最终|BA| = dot(A,N)+ d。进而得到A对于平面的对称点A’ = A - 2(dot(A,N) + d)N。

我们已知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)

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)

如此复杂的计算公式,我们已经把x,y,z对应的系数和常数抽取出来了,是时候看一下矩阵的威力了,把上述计算公式改为矩阵的形式,Unity是OpenGL风格的矩阵,即矩阵 * 列向量的形式:

\begin{bmatrix} 1-2nx * nx&-2nx * ny & -2nx *nz&-2dnx \\ -2nx * ny& 1-2ny*ny &-2ny*nz &-2dny \\ -2nx*nz&-2ny*nz &1-2nz*nz &-2dnz \\ 0&0 &0 &1 \end{bmatrix}\begin{bmatrix} x\\ y\\ z\\ 1 \end{bmatrix}

至此,我们就得到了用于变换空间中任意一点A相对于平面P·N + d = 0的反射变换矩阵,我们假设其为R。

注:关于平面方程的表示,可以参考这篇文章。

平面反射效果实现

下面看一下要怎样用这个矩阵来实现平面反射的效果。首先,也是最重要的,我们需要一个相机,与当前正常相机关于平面对称,也就是说,我们把正常相机变换到这个对称的位置,然后将这个相机的渲染结果输出到一张RT上,就可以得到对称位置的图像了。我们得到了平面反射的矩阵R,下面我们需要考虑的就是在哪个阶段使用这个反射矩阵R。我们知道,渲染物体需要通过MVP变换(可以参考本人之前关于软渲染的blog),物体首先通过M矩阵,从物体空间变换到世界空间,然后通过V矩阵,从世界空间变换到视空间,最后通过投影矩阵P变换到裁剪空间。我们把R矩阵插在V之后,在一个物体在进行MV变换后,变到正常相机坐标系下,然后再进行一次反射变换,就相当于变换到了相对于平面对称的相机坐标系下,然后再进行正常的投影变换,就可以得到反射贴图了。代码如下:

/********************************************************************
 FileName: PlanarReflection.cs
 Description: 平面反射效果
 history: 15:8:2018 by puppet_master
 https://blog.youkuaiyun.com/puppet_master
*********************************************************************/
using UnityEngine;

[ExecuteInEditMode]
public class PlanarReflection : MonoBehaviour
{
    private Camera reflectionCamera = null;
    private RenderTexture reflectionRT = null;
    private static bool isReflectionCameraRendering = false;
    private Material reflectionMaterial = null;

    private void OnWillRenderObject()
    {
        if (isReflectionCameraRendering)
            return;

        isReflectionCameraRendering = true;
       
        if (reflectionCamera == null)
        {
            var go = new GameObject("Reflection Camera");
            reflectionCamera = go.AddComponent<Camera>();
            reflectionCamera.CopyFrom(Camera.current);
        }
        if (reflectionRT == null)
        {
            reflectionRT = RenderTexture.GetTemporary(1024, 1024, 24);
        }
        //需要实时同步相机的参数,比如编辑器下滚动滚轮,Editor相机的远近裁剪面就会变化
        UpdateCamearaParams(Camera.current, reflectionCamera);
        reflectionCamera.targetTexture = reflectionRT;
        reflectionCamera.enabled = false;

        //根据上文平面定义,需要平面法向量和平面上任意点,此处使用transform.u
Shader实例:Planar Reflection 平面反射 目前采用比较多的反射,最终效果示例: 代码已经中文注解,有2部分需扩展:反射矩阵、歪截头体矩阵。注解中有来源链接可以去理解推导过程。 可用于镜面和水面。 咱还是直接看注解过的代码 MirrorReflection.cs using UnityEngine; using System.Collections; using UnityEditor; [ExecuteInEditMode] public class MirrorReflection : MonoBehaviour { //public Ma 阅读详情

相关推荐

unity3d学习笔记-光照(6.反射探测器Reflection Probes)

一、什么是反射探测器Reflection Probes? Reflection Probes 将场景中给定点的视觉环境和光照记录为立方体贴图——您可以将其视为具有六个纹理的立方体,这些纹理记录了场景中特定点在其内表面上的反射。 二、放置和配置反射探头 1. 在 Hierarchy 中,选择Add ( + ),然后选择Light > Reflection Probe 。这将创建一个添加了反射探针组件的新游戏对象。 2. 将反射探头放在苹果前面的底座上。越接近苹果的实际位置,反射.

Amateur_master的博客 4772

Unity平面反射探针:从原理到实战的性能优化指南

在实时渲染中,反射效果是提升场景真实感的关键技术之一。其核心原理是通过模拟光线在光滑表面的物理行为,将环境信息映射到物体表面。传统方法如立方体贴图反射,虽然性能开销较低,但在处理大型平面(如水面、地板)时会产生扭曲失真,无法满足高精度场景的需求。平面反射技术通过镜像摄像机实时渲染场景,实现了物理准确的镜面反射,技术价值在于为水面、光滑地面等大型平面提供了无失真的高质量反射解决方案。在游戏开发、建筑可视化、汽车展示等应用场景中,平面反射能极大增强视觉沉浸感。本文聚焦于Unity引擎中的平面反射探针,深入探讨其

chupu2979的博客 341

Unity镜面效果实现:从反射探针到平面反射的实战指南

在计算机图形学中,反射是模拟真实世界光照交互的核心技术之一,它通过模拟光线在物体表面的反弹行为来增强场景的真实感。其原理主要基于环境贴图、屏幕空间反射和平面反射等算法,通过预计算或实时捕捉周围环境信息来模拟反射效果。这项技术的价值在于显著提升视觉沉浸感,广泛应用于游戏开发、虚拟现实和建筑可视化等领域。在Unity引擎中,开发者可以通过反射探针快速实现环境反射,或使用渲染纹理创建精确的平面镜面效果。本文结合Shader编程和性能优化策略,深入探讨镜面效果在游戏开发中的实践应用,帮助开发者平衡视觉效果与运行效率

weixin_30325971的博客 315

Unity平面反射插件实战:免费方案实现高质量水面与镜面效果

在实时渲染中,反射技术是提升场景真实感的关键。其核心原理是通过模拟光线在表面的物理行为,生成对称的虚拟影像。从技术价值看,精确的反射能极大增强材质表现力与空间沉浸感,尤其在模拟水面、镜面、光滑地板等场景时不可或缺。常见的实现方案包括基于立方体贴图的反射探针、屏幕空间反射以及基于渲染到纹理的镜像摄像机方法。其中,平面反射技术专为平坦表面设计,通过计算镜像矩阵与渲染到纹理,能生成边缘锐利、动态精确的倒影,有效解决了传统方案在平面上的失真问题。在应用层面,它广泛适用于游戏开发中的水体渲染、室内环境表现及载具模拟。

weixin_33716154的博客 391

Unity ShaderGraph反射节点:从原理到实战,掌握环境反射核心技术

在实时渲染中,环境反射是模拟物体与周围环境光照交互的关键技术,它基于光学反射原理,通过计算反射向量来采样环境贴图,从而高效地实现逼真的镜面反射效果。这项技术的核心价值在于,它能够在保证实时性能的前提下,显著提升材质的物理真实感和视觉表现力,广泛应用于游戏开发、影视特效和数字孪生等领域。在Unity ShaderGraph中,反射节点将复杂的反射向量计算封装为可视化操作,极大地降低了技术门槛。通过深入理解其数学原理——即经典的反射向量公式,开发者可以灵活操控表面法线和视角方向,实现从基础的金属材质到风格化的卡

stevenbill的博客 393

Unity动态水面反射实战:从ShaderGraph到移动端优化的完整方案

实时渲染中的反射技术是提升场景真实感的关键,其核心原理是通过模拟光线在光滑表面的镜面反射行为,生成动态的环境映像。在游戏开发与实时图形领域,这项技术对于营造沉浸式水面、镜面等效果具有重要价值,广泛应用于开放世界、模拟仿真等场景。实现高质量反射常面临性能开销与视觉稳定性的双重挑战,尤其是在移动端等性能受限平台。本文聚焦于Unity引擎,深入探讨如何利用ShaderGraph构建高性能的动态水面反射系统,并针对“Unity切换Video视频时闪了一下”等常见的渲染状态切换问题,以及“移动端性能优化”需求,提供从

weixin_30562507的博客 402

Unity URP移动端屏幕空间平面反射(SSPR)实现与优化指南

屏幕空间反射(SSR)是一种基于当前帧已渲染信息的实时反射技术,它通过采样深度和颜色纹理重建反射效果,避免了传统镜像摄像机方案的全场景重复渲染开销。其核心原理是利用GPU并行计算能力,在着色器中进行射线步进或镜像投影计算,在性能与效果间取得平衡。这一技术对移动端游戏和XR应用具有重要价值,能显著提升水面、冰面等场景的真实感,同时保持可接受的性能开销。在Unity URP管线中,通过自定义Renderer Feature实现SSPR,需重点优化纹理采样、射线步进算法,并针对Tile-Based GPU架构进行

412

Unity URP屏幕空间反射(SSR)实现与优化:从原理到移动端部署

实时渲染中的反射效果是提升场景真实感的关键技术。其核心原理是通过模拟光线与表面的交互,计算观察者看到的反射影像。在游戏与实时图形领域,实现高效动态反射具有重要技术价值,它直接影响材质表现(如水面、金属)和环境沉浸感。传统方案如反射探针(Reflection Probe)适合静态环境,而平面反射(Planar Reflection)开销较大。屏幕空间反射(Screen Space Reflection, SSR)作为一种折中方案,通过复用当前帧已渲染的屏幕信息(颜色与深度纹理)进行简化“光线追踪”,在视图空间

weixin_30883311的博客 373

移动端水面Shader开发实战:从Gerstner波到性能优化

在移动图形开发中,Shader是实现复杂视觉效果的核心技术,它通过一系列算法指令控制GPU渲染像素的最终表现。其原理在于对光照、纹理、几何变形等基础图形学概念进行数学建模与编程实现。对于移动平台而言,Shader的技术价值尤为突出,它需要在有限的GPU算力、带宽和功耗约束下,最大化视觉表现力,这是决定游戏画面品质与运行流畅度的关键。尤其在开放世界、RPG等手游类型中,水面效果作为重要的环境与交互元素,其Shader的实现方案直接影响到玩家的沉浸感与设备的续航能力。因此,针对移动端特性进行深度优化的专用水面S

weixin_30487201的博客 397

URP屏幕空间反射(SSR)从原理到性能优化的完整实践指南

屏幕空间反射(Screen Space Reflection, SSR)是一种基于实时渲染图像的动态反射技术,它利用当前帧的深度和颜色缓冲区信息,通过光线步进算法在屏幕空间内计算反射光线与场景的交点。相较于传统的立方体贴图或反射探针等预计算方案,SSR能够实时捕捉动态物体、变化光照和粒子特效的反射,显著提升场景的真实感和沉浸感,尤其适用于水面、金属表面、抛光地板等需要高动态反射细节的应用场景。然而,这项技术也带来了显著的性能开销,特别是在移动端平台,其密集的光线步进和纹理采样操作容易成为性能瓶颈。本文聚焦于

weixin_30919571的博客 329

URP渲染管线中实现高质量水面反射:从原理到工程实践

在实时渲染领域,水面反射是提升场景真实感的关键技术之一。其核心原理是通过模拟光线在水面的反射行为,将环境信息映射到水面材质上。传统实现方案包括反射探针和屏幕空间反射,前者基于预计算的立方体贴图,后者则通过屏幕空间光线追踪实现。在Unity的通用渲染管线中,由于管线架构的模块化设计,传统的GrabPass等方案需要适配。本文聚焦于URP下构建平面反射系统的工程实践,通过反射相机与Renderer Feature的协同工作,解决了渲染纹理集成、渲染层控制等挑战。该系统支持动态物体反射,并能通过法线扰动、菲涅尔效

weixin_33912246的博客 320

Unity游戏仪表盘开发:从物理模拟到性能优化的全流程实践

在游戏开发中,数据可视化是提升沉浸感的关键技术,其核心在于将抽象的游戏状态转化为直观的视觉反馈。通过物理模拟原理,开发者可以构建出具有真实动态行为的交互组件,如带有惯性和阻尼效果的指针运动。这种技术不仅增强了用户体验,更在赛车模拟、飞行模拟等对反馈精度要求高的场景中具有重要价值。开源解决方案通过模块化设计,将复杂的数学运算和渲染逻辑封装,大幅降低了开发门槛。本文以Unity引擎为例,深入探讨了如何利用数据驱动模型和分层架构,结合SolidWorks模型导入与DoTween动画系统,高效实现专业级游戏仪表盘,

cumo3681的博客 395

Unity Planar Reflection平面反射

Unity Planar Reflection平面反射

金蝶云苍穹-插件开发(一)加载数据

此系列博客是进行金蝶云苍穹开发时的插件开发的教程,一是在明年要是还要参加软件杯金蝶A6赛题的话,可以看此系列教程的博客来进行复习,同时如果要是我实验室的学弟学妹要参加的话,我这个系列的博客可以给他们提供学习参考,因为插件开发学习的路上很艰难,我学习的时候,能参照的讲解少,要花费大量工夫去猜测去调试才能学会那些语法。在插件开发上,希望我这个系列的博客能帮助到别人。这个系列的博客只专门讲解插件开发,基本的那些金蝶云苍穹的知识点在这里不多作讲解。

二狗mao的博客 3584

Ubuntu单系统安装全攻略:从删除Windows到UEFI引导设置(避坑指南)

本文提供Ubuntu单系统安装的完整指南,涵盖从安全删除Windows系统到UEFI引导设置的详细步骤。通过专业工具准备、分区方案设计、引导加载器配置等关键环节,帮助用户避免常见安装问题,实现纯净高效的Ubuntu单系统环境。特别强调UEFI引导的深度配置与安装后的性能优化,适合希望彻底转向Linux的用户。

weixin_29210727的博客 409

HDLbits找茬实战:5个Verilog仿真Bug修复案例,新手避坑指南

本文通过HDLbits平台上的5个典型Verilog仿真Bug案例,详细解析了多路选择器位宽不匹配、模块实例化端口映射错误等常见问题,提供实用的修复方案和调试技巧,帮助Verilog新手快速掌握代码验证与仿真技能,避免常见陷阱。

weixin_42653691的博客 162

VCS HVP与验证计划planner:数字IC验证环境搭建与收敛实践

在数字IC验证中,VCS 是广泛使用的编译型仿真器,HVP 通常指面向硬件验证平台的高性能编译选项集,用于开启 SystemVerilog 类、随机约束和 NTB 等能力;planner 则指验证计划管理,核心是把功能点与测试用例、覆盖点建立映射。理解 VCS 先编译后仿真的原理,有助于优化增量编译、调试访问和覆盖率采集,提升回归收敛效率。规划驱动让激励不再散落,设计改版时能快速定位受影响用例,交接和覆盖率收敛更有依据。无论是 UVM 环境搭建、编译参数选择,还是验证计划表维护,都需要把工具选项与规划方法结

weixin_42543046的博客 336

告别手动拍照!用Python+JAKA机械臂+D455相机实现全自动手眼标定(附完整代码)

本文详细介绍了如何利用Python脚本实现JAKA机械臂与Intel RealSense D455相机的全自动手眼标定流程,提供从硬件配置到代码实现的完整解决方案。通过自动化标定,显著提升工业自动化中的精度和效率,适用于机器人视觉领域的二次开发需求。

weixin_30555125的博客 521

SMPLify-X关键点映射与OpenPose格式转换实战

本文深入解析了SMPLify-X与OpenPose之间关键点映射与格式转换的实战方法。针对SMPL、SMPL-H、SMPLX等参数化人体模型与OpenPose 2D姿态估计标准之间的数据鸿沟,文章详细对比了双方的关键点定义差异,并提供了核心的Python转换代码与映射表构建思路。通过清晰的步骤讲解与调试技巧,帮助开发者实现3D关节数据到OpenPose标准格式的高效、准确转换,为动作捕捉、3D姿态估计等应用奠定数据基础。

weixin_29046035的博客 166

人形机器人触觉革命:压电式电子皮肤的多模态感知与自修复技术突破

本文深入探讨了压电式电子皮肤如何推动人形机器人的触觉革命。文章分析了其基于压电效应的多模态感知原理,能够灵敏检测压力、振动与温度,并重点介绍了赋予电子皮肤自修复能力的动态共价网络等前沿材料技术,从而极大提升了机器人与物理世界进行精细、安全交互的可靠性与智能化水平。

weixin_29251567的博客 194

OpenClaw + Qwen 模型集成飞书完整教程

1、环境要求:- **操作系统**: Windows- **Node.js**: v18+ (推荐 v20+)- **飞书账号**: 需要企业管理员权限(创建应用用)或者个人飞书账号创建个人应用- **网络**: 能访问 GitHub 和飞书开放平台2、安装Node.js下载完成后验证是否安装成功,cmd窗口输入“node --version”,显示版本号即为安装成功(如下图所示)3、获取大模型APIKEY(这里以Qwen为例)

qq_29609427的博客 475

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

上一篇: 图形学相关数学(反射,折射公式)
下一篇: UnityShader-BilateralFilter(双边滤波,磨皮滤镜)
puppet_master
puppet_master 领域专家: C/C++技术领域 领域专家: C/C++技术领域
博客等级 码龄12年 2695粉丝 161原创
评论 33
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值