AR/MR实时光照阴影开发教程

一、效果演示

1、PICO4 Ultra MR 发光的球

2、AR实时光照

二、实现原理

PICO4 Ultra MR开发时,通过空间网格能力扫描周围环境,然后将扫描到的环境网格材质替换为一个透明材质并停止扫描;基于Google ARCore XR Plugin和ARFoundation进行安卓手机的AR开发,通过扫描周围环境进行平面识别,然后将扫描到的平面的材质替换为一个透明材质并停止扫描。此处透明材质可以接收光照和阴影。

三、实现过程

本教程使用Unity版本为2022.3LTS,渲染管线为URP,Built-In同样可用。

1、创建透明材质

透明材质着色器来源于Meta的MRUK Sample,点击此处进入其Github页。

克隆项目至本地,用Unity国际版本打开项目,需要复制着色器及三个依赖文件至计算机本地路径下。

(1)着色器资源准备

找到下图所示Packages路径下的着色器,右键在资源管理器中打开,复制至本地文件夹下。

找到下图所示Packages路径下的EnvironmentDepth文件夹,右键在资源管理器中打开,将EnvironmentOcclusion.cginc、URP中的EnvironmentOcclusionURP.hlsl和BiRP中的EnvironmentOcclusionBiRP.cginc复制至本地文件夹下。

资源复制完成明细如下图所示。

(2)项目导入着色器

将复制出的着色器及其依赖资源文件拖动至Unity项目合适路径下。此时,着色器会报错。

此报错为资源引用路径所致,双击打开HighlightsAndShadows着色器脚本进行修改。

1)找到以下引用:

修改为:

cpp 复制代码
#include "EnvironmentOcclusionURP.hlsl" //occl

2)找到以下引用:

修改为:

cpp 复制代码
#include "EnvironmentOcclusionBiRP.cginc" //occl

注意:此引用有两处,都需修改

修改完着色器脚本后,其依赖文件也会因资源引用路径而报错。

双击打开EnvironmentOcclusionBiRP文件进行修改。

找到以下引用:

修改为:

cpp 复制代码
#include "EnvironmentOcclusion.cginc"

双击打开EnvironmentOcclusionURP文件进行修改。

找到以下引用:

修改为:

cpp 复制代码
#include "EnvironmentOcclusion.cginc"

(3)创建材质

按照以上修改完相关文件后,便可以使用HighlightsAndShadows着色器。有两种方式可以将此着色器应用于项目材质。

1)通过着色器直接创建材质

选中HighlightsAndShadows着色器,鼠标右键-->Create--> Material。

2)材质引用着色器

选中项目材质,在Shader设置中,选择Meta/MRUK/Scene/HighlightsAndShadows。

(4)材质效果

可以将材质应用于Plane物体,查看光影效果。

2、PICO4 Ultra MR 发光的球实现

此处需要使用空间网格,具体配置不再详述,参见:PICO4 Ultra MR开发 空间网格扫描 模型导出及预览或者PICO官网空间网格

(1)材质替换及停止扫描

需要在扫描完成后,将网格材质替换为HighlightsAndShadows材质。首先需要找到所有网格对象,扫描生成的网格一般会作为挂载PXR_SpatialMeshManager脚本的物体的子物体,所以只需以此查找网格对象即可。具体代码如下:

cs 复制代码
public Transform spatialMeshParent;  //空间网格生成的父物体,即挂载PXR_SpatialMeshManager脚本的物体
public Material meshMaterial;        //接收光照和阴影的材质

private MeshRenderer[] spatialMeshes;//空间网格上的MeshRenderer数组
private XRMeshSubsystem meshSubsystem;//网格子系统


void Start()
{
    meshSubsystem = XRGeneralSettings.Instance.Manager.ActiveLoaderAs<PXR_Loader>().meshSubsystem;
}

void Update()
{
    if (Controller.UXR_GetKeyDown(0, XR_KeyCode.APP))
    {
        spatialMeshes = spatialMeshParent.GetComponentsInChildren<MeshRenderer>();

        foreach(MeshRenderer renderer in spatialMeshes)
        {
            renderer.material = meshMaterial;
        }

        //停止扫描网格
        meshSubsystem.Stop();
    }
}

(2)灯光设置

创建一个点光源作为小球的子物体,并置于小球中心处。

(3)小球弹跳

若使小球不会穿过墙面并可以在地面上弹跳,需要给网格预制件添加MeshCollider组件并赋予物理材质,物理材质设置合适弹性参数;小球上则需添加SpereCollider组件和Rigidbody组件,同样设置物理材质。

3、AR实时光照实现

基于ARFoundation进行手机AR开发,若为安卓手机,额外导入Google ARCore XR Plugin,若为苹果手机,额外导入Apple ARKit XR Plugin。

(1)AR环境搭建

创建XR Origin,并删除场景里的MainCamera物体。

按照下图所示设置并添加组件。

其中,ARPlaneManager的PlanePrefab需要手动创建并制作为预制件,赋予此处。

创建ARSession。

(2)材质替换及停止扫描

完整代码如下。

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;

public class ARDanceManager : MonoBehaviour
{
    public Material transparentMat;         //接收光照和阴影的材质
    public GameObject dancerPrefab;         //虚拟人物预制件
    public ARRaycastManager raycastManager; //射线碰撞检测器
    public ARPlaneManager m_ARPlaneManager; //平面生成器

    private List<ARRaycastHit> hits;
    private GameObject dancerObject = null;
    private bool hasSpawned = false;

    // Start is called before the first frame update
    void Start()
    {
        hits = new List<ARRaycastHit>();
    }

    // Update is called once per frame
    void Update()
    {
        if (dancerObject == null)
        {
            if (Input.touchCount == 0)
                return;
        }

        if (!hasSpawned)
        {
            var touch = Input.GetTouch(0);
            if (raycastManager.Raycast(touch.position, hits, TrackableType.PlaneWithinPolygon | TrackableType.PlaneWithinBounds))
            {
                var hitPose = hits[0].pose;
                if (dancerObject == null)//没有虚拟物体则生成
                {
                    dancerObject = Instantiate(dancerPrefab, hitPose.position, /*hitPose.rotation*/dancerPrefab.transform.rotation);
                    dancerObject.name = "Dancer";
                    m_ARPlaneManager.enabled = false;
                    SetAllPlanesTransparent();
                }
                else                      //有虚拟物体将虚拟物体锚定到射线碰撞点处
                {
                    dancerObject.transform.position = hitPose.position;
                }
            }
            hasSpawned = true;
        }
    }

    //设置渲染的平面为透明
    void SetAllPlanesTransparent()
    {
        foreach (var plane in m_ARPlaneManager.trackables)
        {
            plane.gameObject.GetComponent<MeshRenderer>().material = transparentMat;
            plane.gameObject.GetComponent<LineRenderer>().material = transparentMat;
        }
    }
}

以上脚本挂载于场景物体,赋值相应变量即可。

四、视频教程

视频教程参见:

相关推荐
小赖同学啊10 分钟前
物联网中的Unity/Unreal引擎集成:数字孪生与可视化控制
物联网·unity·游戏引擎
Zlzxzw3 小时前
使用unity创建项目,进行动画制作
unity·游戏引擎
X_StarX8 小时前
【Unity笔记01】基于单例模式的简单UI框架
笔记·ui·unity·单例模式·游戏引擎·游戏开发·大学生
九班长8 小时前
Golang服务端处理Unity 3D游戏地图与碰撞的详细实现
3d·unity·golang
ysn1111111 小时前
NGUI实现反向定位到层级面板结点
unity
Thomas_YXQ18 小时前
Unity3D DOTS场景流式加载技术
java·开发语言·unity
道可云18 小时前
道可云人工智能每日资讯|浦东启动人工智能创新应用竞赛
人工智能·百度·ar·xr·deepseek
KhalilRuan1 天前
Unity-MMORPG内容笔记-其一
unity·游戏引擎
广州华锐视点1 天前
广州华锐互动:以创新科技赋能教育,开启沉浸式学习
ar·vr·虚拟现实
爱看科技1 天前
苹果AR/VR头显路线图曝光,微美全息推进AI/AR智能眼镜新品开启视觉体验篇章
人工智能·ar·vr