You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity ARFoundation中如何在C#脚本中识别屏幕阴影像素?

解决方案:ARFoundation URP中获取AR平面阴影纹理的方法

一、为什么unity_ShadowMasks为空?

unity_ShadowMasks是URP的全局内置纹理,不属于单个材质的属性,而是由渲染管线在运行时全局绑定的。直接通过material.GetTexture()访问自然拿不到——这个属性不会出现在材质的暴露参数里,也不会被实例化到材质对象上。

另外,你用的Shadow Receiver URP透明阴影接收器着色器,本质是通过采样**阴影贴图(Shadow Atlas)**渲染阴影,而非依赖Shadow Mask技术(后者是用于静态物体的预烘焙阴影),所以你找错了属性方向。


二、用渲染纹理捕获AR平面阴影的具体实现步骤

这里提供两种适配ARFoundation+URP场景的可行方案:

方案1:修改Shadow Receiver着色器,输出阴影到RenderTexture

  1. 编辑Shadow Receiver的Shader Graph
    在项目中找到你使用的“透明阴影接收器”Shader Graph,添加一个Render Target节点,指定一个自定义Render Texture作为输出目标(命名为_ShadowCaptureRT)。

    • 调整着色器逻辑:将原本用于显示阴影的颜色输出到这个Render Target,同时保留原有的透明平面渲染逻辑。
    • 在Shader Graph的Blackboard中添加Texture2D _ShadowCaptureRT属性并设置为“暴露(Exposed)”,方便脚本访问。
  2. 编写C#脚本绑定并读取RenderTexture
    替换你原有的脚本,改用以下代码:

    using UnityEngine;
    
    public class ShadowCaptureScript : MonoBehaviour
    {
        private Renderer _planeRenderer;
        private RenderTexture _shadowRT;
    
        void Awake()
        {
            _planeRenderer = GetComponent<Renderer>();
            // 创建适配屏幕分辨率的RenderTexture
            _shadowRT = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.ARGB32);
            _shadowRT.name = "ARPlaneShadowRT";
            // 绑定到材质的自定义属性
            _planeRenderer.material.SetTexture("_ShadowCaptureRT", _shadowRT);
        }
    
        void Update()
        {
            // 按需读取(示例:按空格键触发,避免每帧读取影响性能)
            if (_shadowRT != null && Input.GetKeyDown(KeyCode.Space))
            {
                Texture2D shadowTex = new Texture2D(_shadowRT.width, _shadowRT.height, TextureFormat.ARGB32, false);
                RenderTexture.active = _shadowRT;
                shadowTex.ReadPixels(new Rect(0, 0, _shadowRT.width, _shadowRT.height), 0, 0);
                shadowTex.Apply();
                // 此处可处理shadowTex的像素数据(如CV后处理)
                Debug.Log($"捕获到阴影纹理:{shadowTex.width}x{shadowTex.height}");
                // 销毁临时Texture2D释放内存
                Destroy(shadowTex);
            }
        }
    
        void OnDestroy()
        {
            if (_shadowRT != null)
                Destroy(_shadowRT);
        }
    }
    
  3. 性能优化提示

    • 不要每帧读取RenderTexture像素,仅在CV后处理需要时触发。
    • 可降低RenderTexture分辨率(如取屏幕尺寸的1/2),减少内存占用和读取耗时。

方案2:用CommandBuffer捕获阴影渲染结果

若不想修改Shader Graph,可借助URP的CommandBuffer在渲染AR平面时捕获阴影:

  1. 编写脚本注册CommandBuffer
    using UnityEngine;
    using UnityEngine.Rendering;
    using UnityEngine.Rendering.Universal;
    
    public class CommandBufferShadowCapture : MonoBehaviour
    {
        private RenderTexture _shadowRT;
        private CommandBuffer _cmdBuffer;
        private Camera _mainCamera;
    
        void Awake()
        {
            _mainCamera = Camera.main;
            _shadowRT = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.ARGB32);
            _cmdBuffer = new CommandBuffer();
            _cmdBuffer.name = "CaptureARPlaneShadow";
        }
    
        void OnEnable()
        {
            // 在透明物体渲染后执行捕获(适配AR平面的透明属性)
            var cameraData = _mainCamera.GetComponent<UniversalAdditionalCameraData>();
            cameraData.scriptableRenderer.EnqueuePass(new CustomShadowCapturePass(_cmdBuffer, _shadowRT, GetComponent<Renderer>()));
        }
    
        void OnDisable()
        {
            var cameraData = _mainCamera.GetComponent<UniversalAdditionalCameraData>();
            cameraData.scriptableRenderer.RemovePass(typeof(CustomShadowCapturePass));
            _cmdBuffer.Release();
        }
    
        // 自定义渲染Pass,负责捕获AR平面阴影
        private class CustomShadowCapturePass : ScriptableRenderPass
        {
            private CommandBuffer _cmd;
            private RenderTexture _targetRT;
            private Renderer _planeRenderer;
    
            public CustomShadowCapturePass(CommandBuffer cmd, RenderTexture targetRT, Renderer planeRenderer)
            {
                _cmd = cmd;
                _targetRT = targetRT;
                _planeRenderer = planeRenderer;
                renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
            }
    
            public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
            {
                _cmd.BeginSample("CaptureShadow");
                // 设置渲染目标为自定义RT
                _cmd.SetRenderTarget(_targetRT);
                // 清除RT内容
                _cmd.ClearRenderTarget(true, true, Color.clear);
                // 仅渲染AR平面的阴影Pass(需替换为Shader实际的阴影Pass索引)
                _cmd.DrawRenderer(_planeRenderer, _planeRenderer.material, 0, 1);
                _cmd.EndSample("CaptureShadow");
                context.ExecuteCommandBuffer(_cmd);
                _cmd.Clear();
            }
        }
    
        // 读取阴影纹理的方法
        public void ReadShadowTexture()
        {
            if (_shadowRT == null) return;
            Texture2D shadowTex = new Texture2D(_shadowRT.width, _shadowRT.height);
            RenderTexture.active = _shadowRT;
            shadowTex.ReadPixels(new Rect(0, 0, _shadowRT.width, _shadowRT.height), 0, 0);
            shadowTex.Apply();
            // 处理像素数据
            Destroy(shadowTex);
        }
    }
    
  2. 注意事项
    • 需要确认Shadow Receiver着色器的阴影Pass索引:打开Shader Graph生成的Shader代码,找到对应阴影渲染的Pass,修改DrawRenderer的最后一个参数为对应索引。
    • 该方案无需修改Shader,但需要对URP渲染流程有基础了解。

三、与ARFoundation原始帧结合的建议

你已通过CpuImageSample.cs获取原始AR帧,可将捕获到的阴影纹理与原始帧做像素级运算或叠加,用于CV后处理。注意两者的分辨率和坐标系对齐——AR原始帧分辨率可能与屏幕不同,需做缩放或坐标转换。

内容的提问来源于stack exchange,提问作者salam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 21:18:18