Unity学习shader笔记[三十三]Unity3D ShaderLab 之 BoxProjectedCubemapDirection解读

转载自 Unity3D ShaderLab 之 BoxProjectedCubemapDirection解读

Unity 的反射,依赖于布置在场景中的Reflection Probe(当然,会有一个全场景默认的)

而这个组件上有一个开关“Box Projection”

在这里插入图片描述

Box Projection开关与否的区别请参见 官方文档

在这里插入图片描述
这里简要的说明一下

反射实现本质上就是Cubemap采样,和天空盒一个原理。

Box Projection Off/On 区别简述

  • 当Box Projection
    Off时,和天空盒就更像了,把Cubemap想象成一个大盒子,你处在盒子的正中心,你在世界里移动的时候,这个大盒子会跟着你一起动,造成的结果就是无论你往哪个方向看,结果并不会随你在世界中的位置变化而变化——对于确定的观察方向,确定的采样结果,与位置无关。
  • 当Box Projection On时,好比这个大盒子在世界中就固定下来了,你往特定方向观察到的结果会随着你在世界中跑动而变化。

在Shader中如何体现

Unity的CGIncludes中已经给出了答案,我们可以先看 UnityGI_IndirectSpecular 函数。这个函数中还处理了两个Reflection Probe混合的情况,这不是我们关心的重点,简化之后代码如下:

inline half3 UnityGI_IndirectSpecular(UnityGIInput data, half occlusion, Unity_GlossyEnvironmentData glossIn)
{
    half3 specular;

    #ifdef UNITY_SPECCUBE_BOX_PROJECTION
        // we will tweak reflUVW in glossIn directly (as we pass it to Unity_GlossyEnvironment twice for probe0 and probe1), so keep original to pass into BoxProjectedCubemapDirection
        half3 originalReflUVW = glossIn.reflUVW;
        glossIn.reflUVW = BoxProjectedCubemapDirection (originalReflUVW, data.worldPos, data.probePosition[0], data.boxMin[0], data.boxMax[0]);
    #endif

    half3 env0 = Unity_GlossyEnvironment (UNITY_PASS_TEXCUBE(unity_SpecCube0), data.probeHDR[0], glossIn);
    specular = env0;

    return specular * occlusion;
}

简化后的代码非常清晰,从上往下看,

首先定义了half3 specular存储返回结果。
然后是预定义宏UNITY_SPECCUBE_BOX_PROJECTION,代表了Tier Settings中是否启用了Box Projection
如果启用了Box Projection,则通过BoxProjectedCubemapDirection计算变换后的方向
通过调用Unity_GlossyEnvironment采样Reflection Cube,计算HDR。
结果乘以材质上传递过来的遮蔽值,返回

BoxProjectedCubemapDirection一探究竟

这里同样简化一下代码(剔除Optimized version分支)贴出来看

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);

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

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

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

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

上来一个if (cubemapCenter.w > 0.0),cubemapCenter数据怎么来的呢?

float4 unity_SpecCube0_ProbePosition;

或者

float4 unity_SpecCube1_ProbePosition;

在处理反射混合的情况下,第二次调用会用后者。

  • cubemapCenter 的xyz分量存储了Reflection Probe中心的世界坐标
  • w分量代表了当前Reflection Probe是否使用Box Projection,对应Reflection Probe
    Inspector中的开关

但w为0代表并不需要变化反射向量,将传入的值直接返回。如果要计算呢?

这就要看if 内的实现了,但是我们抛开Unity的实现,先想想

如果Box Projection让你来实现该如何做呢?

我们可以简化考虑二维顶视的情况,

在这里插入图片描述

你在点A,选任意方向,假设选了一个方向后,发现,你看到的应该是矩形盒子上的点B,

于是用于采样的方向应该如何表示呢?

在这里插入图片描述
用于Cubemap采样的向量就应该是(B - C)

C就是Reflection Probe的 center

A就是某个片元的World Position

都是已知量,

于是问题就转变成了如何求一个方向与Box边界的交点B

这里的方向就是worldRefl参数 —— 反射向量

如何做向量与Box的碰撞检测?

在这里插入图片描述
考虑上图中的情况,从起点A出发,沿着d方向,检测碰撞。

根据d的方向,在这里我们只需要考虑Max X边界以及Max Z边界的碰撞。

相应的产生碰撞点B与D。

这两种情况用方程该如何表示呢?

假设沿着单位d方向经过t0距离后,遇到点B

假设沿着单位d方向经过t1距离后,遇到点D

A + t0 * d = B

A + t1 * d = D

B.x = Max X

D.z = Max Z

如此可以知道

A.x + t0 * d.x = Max X

A.z + t1 * d.z = Max Z

在这两个式中,A是已知量,d是已知量, Max X 和 Max Z也是已知量。

因此我们可以求出t0 和 t1

t0 和 t1 该如何取舍的呢,根据图显而易见我们要的是B点碰撞,有何特征呢?

我们需要的碰撞点应该 t = min(t0, t1)

总结一下就是,从起点出发,沿着方向前进,发生碰撞的时候,经过的距离最短的点就是我们要的点。

如何得知碰撞边界

在之前的例子中,我们通过观察,得知两个潜在的碰撞边界Max X 与 Max Z。

如果方向变成任意方向该如何处理呢?

在这里插入图片描述
可以单独考虑各个轴向,

先考虑X轴,

当方向的x分量大于0时,即d.x > 0 时,我们关心Max X

当方向的x分量小于0时,即d.x < 0 时,我们关心Min X

再考虑Z轴,

当方向的z分量大于0时,即d.z > 0 时,我们关心Max Z

当方向的z分量小于0时,即d.z < 0 时,我们关心Min Z

同理,可以推广到三维空间

当方向的y分量大于0时,即d.y > 0 时,我们关心Max Y

当方向的y分量小于0时,即d.y < 0 时,我们关心Min Y

用Shader伪代码我们可如下表示

float3 collisionEdge = (d > 0.0f) ? boxMax : boxMin;

collisionEdge 中存储了三个轴向上我们关心的边界

确定了碰撞边界后如何计算碰撞点

首先,在三维空间中我们有三个碰撞边界,因此也就存在三个潜在的碰撞点。

可以列出三个方程 (起点坐标为A)

A.x + d.x * tx = collisionEdge.x

A.y + d.y * ty = collisionEdge.y

A.z + d.z * tz = collisionEdge.z

可以求得三个量 tx, ty, tz。 分别是与三个边界碰撞要经过的距离。

哪一个t 才是我们需要的t 呢?

先前已经有了结论,最小的t 就是我们需要的t

因此我可以知道

float3 t = (collisonEdge - worldPosition) / d;
float collisionDist = min(t.x, min(t.y, t.z));
float3 collisionDir = d * collisionDist;

worldPosition就是我们方程中的A点位置

在这里插入图片描述

以上图为例,

我们最终要求的是蓝色的向量,现在已经求得绿色向量 collisionDir,

蓝色向量 = 红色向量 + 绿色向量

于是只要求得红色向量,就能得出最终结果。

红色向量 = 片元位置 - Reflection Probe中心

float3 localPosInProbe = worldPosition - probeCenter;
float3 resultDir = localPosInProbe + collisionDir;

组装零件

合并我们的伪代码后

float3 BoxProjectedCubemapDirection (float3 worldRefl, float3 worldPosition, float4 probeCenter, float4 boxMin, float4 boxMax)
{
    float3 d = normalize(worldRefl);

    float3 collisionEdge = (d > 0.0f) ? boxMax : boxMin;
    float3 t = (collisonEdge - worldPosition) / d;

    float collisionDist = min(t.x, min(t.y, t.z));
    float3 collisionDir = d * collisionDist;

    float3 localPosInProbe = worldPosition - probeCenter;
    float3 resultDir = localPosInProbe + collisionDir;

    return resultDir;
}

如何把这段代码变成Unity的实现形式呢?

重命名+类型修改+变化计算顺序

inline half3 BoxProjectedCubemapDirection (half3 worldRefl, float3 worldPos, float4 cubemapCenter, float4 boxMin, float4 boxMax)
{
    // nrdir对应于我们的d
    half3 nrdir = normalize(worldRefl);

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

    // rbminmax对应t
    half3 rbminmax = (nrdir > 0.0f) ? rbmax : rbmin;

    // fa对应collisionDist
    half fa = min(min(rbminmax.x, rbminmax.y), rbminmax.z);

    worldPos -= cubemapCenter.xyz;

    // 下面的 worldPos 对应 localPosInProbe 
    // nrdir * fa 等于 collisionDir
    worldRefl = worldPos + nrdir * fa;

    return worldRefl;
}

最后加上if判断就得到了最初的代码

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);

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

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

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

        worldPos -= cubemapCenter.xyz;
        worldRefl = worldPos + nrdir * fa;
    }
    return worldRefl;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
学习Unity3D时,以下是一些重要的笔记: 1. Unity3D基础知识: - 游戏对象(Game Objects)和组件(Components):了解游戏对象的层次结构和组件的作用。 - 场景(Scenes)和摄像机(Cameras):学会如何创建场景并设置摄像机视角。 - 材质(Materials)和纹理(Textures):掌握如何创建和应用材质和纹理。 - 动画(Animations):学习如何创建和控制游戏对象的动画。 2. 脚本编程: - C#语言基础:了解C#语言的基本语法和面向对象编程概念。 - Unity脚本编写:学习如何编写脚本来控制游戏对象的行为和交互。 - 常见组件和功能:掌握常见的Unity组件和功能,如碰撞器(Colliders)、刚体(Rigidbodies)、触发器(Triggers)等。 3. 游戏开发流程: - 设计游戏关卡:了解如何设计游戏场景和关卡,包括布局、道具、敌人等。 - 游戏逻辑实现:将游戏规则和玩家交互转化为代码实现。 - UI界面设计:学习如何设计游戏中的用户界面,包括菜单、计分板等。 - 游戏优化和调试:优化游戏性能,解决常见的错误和问题。 4. 学习资源: - Unity官方文档和教程:官方提供了大量的文档和教程,逐步引导你学习Unity3D。 - 在线教程和视频教程:网上有很多免费和付费的Unity教程和视频教程,可根据自己的需求选择学习。 - 社区论坛和博客:加入Unity开发者社区,与其他开发者交流并获取帮助。 通过系统地学习这些内容,你将能够掌握Unity3D的基础知识并开始开发自己的游戏项目。记得不断实践和尝试,不断提升自己的技能!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

染指流年丨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值