基于Unity引擎的Bent Normal实现

这是侑虎科技第433篇文章,感谢作者钱康来供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群465082844)

个人主页:http://qiankanglai.me
作者也是U Sparkle活动参与者,UWA欢迎更多开发朋友加入U Sparkle开发者计划,这个舞台有你更精彩!


最近半年赶项目的事情一直很忙,好不容易有空做点渲染的东西,于是尝试了一下 Bent Normal Maps

这是Unreal 4.17发布的功能之一,可以拿来解决间接光照漏光。

工具部分 Substaince Designer已经支持利用高模烘焙Bent Normal

效果图对比来自Unreal 4文档:

请输入图片描述

 

算法原理

为了实现这个,首先要理解一下思路。GPU Gems Chap. 17 Ambient Occlusion 里面是这么描述的:

The approach can also be extended to produce the average unoccluded
direction, or bent normal. We can use a shader to calculate the
direction to the light multiplied by the shadow value, and then copy
the result to the RGB output color. The occlusion information can be
stored in the alpha channel. We accumulate these RGB normal values in
the same way as the occlusion value, and then we normalize the final
result to get the average unoccluded normal. Note that a half (16-bit)
floating-point accumulation buffer may not have sufficient precision
to represent the summation of these vectors accurately.

游戏里的低模带上法线代表的是高模的法线,但是这里其实没有考虑周围Mesh的遮蔽影响。如果间接光照直接使用普通法线,就可能出现『暗部漏光』的现象。

请输入图片描述

 

偷懒的同学可以参考一份中文介绍 Bent Normal (https://blog.csdn.net/BugRunner/article/details/7272902)。

基于Unity的Bent Normal Baker

虽然Substaince Designer可以直接烘焙(话说也可以用SSAO后处理,不过弄手游的暂时就不贪心了…),但出于做着玩的角度,肯定是尝试在Unity里实现烘焙。

GPU Gems里提到的是:

This method is based on a view-independent preprocess that computes
occlusion information with a ray tracer and then uses this information
at runtime to compute a fast approximation to diffuse shading in the
environment.

我脑洞了一下:是否可以用Unity的ShadowMap来替代可见性计算:
1.生成一个球状平行光分布(真正烘焙的时候会比这个密很多)

请输入图片描述

2.每次从不同角度渲染物体,利用Shadow Map可以得到每个像素可见性。有几个需要注意的地方:

  • 输出到2UV(这个技巧可以参考博客扩展Unity模型编辑器),记得关掉Cull/ZTest等;
  • Light上用Hard Shadow,需要的话调整下Bias等参数;
  • 不要用Screen Space Shadow;
  • 注意相机位置、模型大小,让Shadow Map利用率最高;

请输入图片描述

 

PS. 我一开始是使用Graphics.DrawMeshNow直接绘制到RenderTexture的,后来发现很多变量引擎不会自动传过去,最后换了个路子,直接设置 Camera.targetTexture 然后Camera.Render 省心多了。

新建一张float的RenderTexture,然后Blend One One情况下各个角度渲染一遍叠加:如果没有影子,RGB输出光源方向,A输出1计数;最后RGB/A保存下来即可。

最后生成的结果需要做几遍Dilation 解决边缘采样问题。

Bake结果对比

和SD出的图比了下,只能说方向上没问题,但是还有不少细节差距很大。

请输入图片描述
左:我的方法处理效果
右:Substaince Designer处理效果

  • 有些奇怪的噪声来自于采样严重不足;
  • 严重怀疑 Substaince Designer 做了一些图像空间的操作,因为它生成的 BentNormal
    有些地方根本没有2UV对应也有值,这就很有意思了;
  • Substaince Designer 使用的是ZB高模,这个Unity导入就麻烦。

不过好处也是有的:Substaince Designer 导出的是 normalized Bent Normal;我自己生成的时候B通道拿来存了 AO Strength,还可以当成 Mesh AO 使用。另外就是在Unity 里烘焙确实流程简单+迭代起来快。 放在自己项目里比较了下背光时候 Diffuse IBL 部分的效果(暂时只用了天光地光),因为法线平滑了很多所以漏光好了很多:

请输入图片描述
左:没有bent normal效果
右:bent normal效果

 

未来工作

目前的结论是在手游上可以尝试使用一下的,反正低配一个 Keyword 关掉就行了。

接下来有精力的话还要好好迭代下预处理烘焙这块。 Unity 这套 Baker 方案扩展一下其实有非常多二次开发的空间,譬如 AO、Normal 等完全可以实现自定义的烘焙(当然另外一条路就是写C++的 Ray Tracing 来搞 Baker)。

SD/SP/Max 等美术 DCC 工具最大问题是 Public API 不是很多,如果想定制输出不是很方便;若只是最终结果的 Encode 还好办,如果想拿中间结果就很费劲。在 Unity 里搞 Baker 的最大意义即在于此。

顺便Po一张图:三个面光源下,Max 里 Vray 烘焙到贴图和 Unity 里 Enlighten 烘焙到贴图的对比,看上去还是比较有搞头的(左边模型鞋子/腰带上的问题其实是 Max 里哪里出错了)。

请输入图片描述


这样最大的好处是提高定制性和整合工作流顺畅度。

文末,再次感谢钱康来的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:465082844)。
也欢迎大家来积极参与U Sparkle开发者计划,简称"US",代表你和我,代表UWA和开发者在一起!

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
您好!基于Unity引擎实现第一人称移动需要以下步骤: 1. 创建一个新的Unity项目,并在场景中添加一个空对象作为主摄像机。 2. 创建一个新的C#脚本文件,并将其添加到主摄像机上。 3. 在脚本中使用Input.GetAxis函数获取玩家输入的水平和垂直轴向的值。 4. 使用CharacterController组件的Move函数来移动主摄像机,该函数需要一个Vector3类型的参数来表示移动的方向和速度。 5. 在Update函数中调用Move函数,并传入计算后的移动向量。 6. 使用Cursor.lockState属性和Cursor.visible属性来控制鼠标显示和锁定状态,以实现第一人称视角。 下面是一个示例代码: ``` using UnityEngine; public class FirstPersonMovement : MonoBehaviour { public float speed = 6.0f; public float jumpSpeed = 8.0f; public float gravity = 20.0f; private Vector3 moveDirection = Vector3.zero; private CharacterController controller; void Start() { controller = GetComponent<CharacterController>(); } void Update() { float horizontal = Input.GetAxis("Horizontal"); float vertical = Input.GetAxis("Vertical"); if (controller.isGrounded) { moveDirection = new Vector3(horizontal, 0, vertical); moveDirection = transform.TransformDirection(moveDirection); moveDirection *= speed; if (Input.GetButton("Jump")) { moveDirection.y = jumpSpeed; } } moveDirection.y -= gravity * Time.deltaTime; controller.Move(moveDirection * Time.deltaTime); Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; } } ``` 希望可以帮到您!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值