第十一章 掌握Unity渲染技术:从立方体纹理到程序化材质的实战指南

第十一章 掌握Unity渲染技术:从立方体纹理到程序化材质的实战指南

11.1 深入理解立方体贴图技术及应用

立方体贴图是3D图形渲染中的重要技术,它使用六个正方形纹理构成一个立方体,用于表现环境反射、天空背景等效果。这种技术在现代游戏中广泛应用,为场景提供了更加真实的视觉体验。

11.1.1 天空盒实现与优化方案

天空盒是立方体贴图最常见的应用之一,它为游戏场景提供了远处的背景环境。无论玩家如何移动,天空盒都保持在相对位置不变,创造出广阔空间的错觉。

理论基础:

天空盒本质上是一个围绕摄像机的立方体,内部贴有环境纹理。它具有以下特点:

  1. 位置始终跟随摄像机,给玩家一种无限远的感觉
  2. 渲染优先级低,确保场景中所有其他对象绘制在天空盒前面
  3. 不受场景光照影响,通常拥有自发光属性

下面是一个简单而高效的天空盒管理器实现:

csharp

using UnityEngine;

[ExecuteInEditMode]
public class SkyboxManager : MonoBehaviour
{
    [Header("Skybox Settings")]
    public Material skyboxMaterial;
    [Range(0, 2)]
    public float skyboxExposure = 1.0f;
    [Range(0, 360)]
    public float skyboxRotation = 0f;
    public Color skyboxTint = Color.white;
    
    [Header("Time Settings")]
    public bool autoRotate = false;
    public float rotationSpeed = 1.0f;
    
    [Header("Reflection Settings")]
    public bool updateReflectionProbes = true;
    public ReflectionProbe[] reflectionProbes;
    public float reflectionUpdateInterval = 1.0f;
    
    // 内部变量
    private Material originalSkybox;
    private float lastUpdateTime = 0f;
    
    void Start()
    {
        // 保存原始天空盒材质,以便后续恢复
        originalSkybox = RenderSettings.skybox;
        
        // 应用设置
        ApplySkyboxSettings();
        
        // 查找场景中的反射探针
        if (reflectionProbes == null || reflectionProbes.Length == 0)
        {
            reflectionProbes = FindObjectsOfType<ReflectionProbe>();
        }
    }
    
    void Update()
    {
        // 处理天空盒旋转
        if (autoRotate)
        {
            skyboxRotation = (skyboxRotation + rotationSpeed * Time.deltaTime) % 360f;
            RenderSettings.skybox.SetFloat("_Rotation", skyboxRotation);
        }
        
        // 更新反射探针
        if (updateReflectionProbes && Time.time - lastUpdateTime > reflectionUpdateInterval)
        {
            UpdateReflectionProbes();
            lastUpdateTime = Time.time;
        }
    }
    
    [ContextMenu("Apply Skybox Settings")]
    public void ApplySkyboxSettings()
    {
        if (skyboxMaterial != null)
        {
            // 应用天空盒材质
            RenderSettings.skybox = skyboxMaterial;
            
            // 设置天空盒参数
            if (skyboxMaterial.HasProperty("_Exposure"))
            {
                skyboxMaterial.SetFloat("_Exposure", skyboxExposure);
            }
            
            if (skyboxMaterial.HasProperty("_Rotation"))
            {
                skyboxMaterial.SetFloat("_Rotation", skyboxRotation);
            }
            
            if (skyboxMaterial.HasProperty("_Tint"))
            {
                skyboxMaterial.SetColor("_Tint", skyboxTint);
            }
            
            // 强制更新光照
            DynamicGI.UpdateEnvironment();
        }
    }
    
    void UpdateReflectionProbes()
    {
        if (reflectionProbes == null || reflectionProbes.Length == 0)
            return;
        
        foreach (ReflectionProbe probe in reflectionProbes)
        {
            if (probe != null && probe.enabled)
            {
                probe.RenderProbe();
            }
        }
        
        Debug.Log($"Updated {reflectionProbes.Length} reflection probes at time {Time.time}");
    }
    
    void OnDestroy()
    {
        // 恢复原始天空盒设置
        if (originalSkybox != null && Application.isPlaying)
        {
            RenderSettings.skybox = originalSkybox;
            DynamicGI.UpdateEnvironment();
        }
    }
    
    // 编辑器下的控制面板增强
    #if UNITY_EDITOR
    [ContextMenu("Create HDR Cubemap")]
    void CreateHDRCubemap()
    {
        // 编辑器工具函数,用于创建新的HDR天空盒资源
        string path = UnityEditor.EditorUtility.SaveFilePanelInProject(
            "Save HDR Cubemap",
            "NewHDRSkybox",
            "asset",
            "Save HDR Cubemap asset"
        );
        
        if (string.IsNullOrEmpty(path))
            return;
        
        // 创建新的立方体贴图
        Cubemap cubemap = new Cubemap(512, TextureFormat.RGBAHalf, true);
        UnityEditor.AssetDatabase.CreateAsset(cubemap, path);
        UnityEditor.AssetDatabase.SaveAssets();
        
        Debug.Log("Created new HDR Cubemap at: " + path);
        
        // 选择新资源
        UnityEditor.Selection.activeObject = cubemap;
    }
    #endif
}

这个脚本实现了一个全面的天空盒管理系统,包括以下功能:

  1. 设置天空盒材质和相关参数
  2. 支持自动旋转,创造动态天空效果
  3. 自动更新反射探针,确保场景中的反射与天空盒同步
  4. 提供编辑器工具,方便创建新的HDR天空盒

要创建自定义的天空盒效果,我们可以使用以下着色器:

glsl

Shader "Custom/GradientSkybox"
{
    Properties
    {
        _TopColor ("Top Color", Color) = (0.4, 0.6, 0.9, 1)
        _BottomColor ("Bottom Color", Color) = (0.1, 0.1, 0.3, 1)
        _ExposureAdjustment ("Exposure Adjustment", Range(0, 2)) = 1.0
        _SunSize ("Sun Size", Range(0, 0.2)) = 0.04
        _SunColor ("Sun Color", Color) = (1, 0.95, 0.9, 1)
        _SunDirection ("Sun Direction", Vector) = (0, 0.5, 1, 0)
        _StarTexture ("Star Texture", 2D) = "black" {}
        _StarPower ("Star Power", Range(0, 10)) = 2.0
        _StarIntensity ("Star Intensity", Range(0, 5)) = 1.0
    }
    
    SubShader
    {
        Tags { "Queue"="Background" "RenderType"="Background" "PreviewType"="Skybox" }
        Cull Off ZWrite Off
        
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
            
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldPos : TEXCOORD0;
                float2 uv : TEXCOORD1;
            };
            
            fixed4 _TopColor;
            fixed4 _BottomColor;
            float _ExposureAdjustment;
            float _SunSize;
            fixed4 _SunColor;
            float4 _SunDirection;
            sampler2D _StarTexture;
            float _StarPower;
            float _StarIntensity;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldPos = normalize(v.vertex.xyz);
                o.uv = v.uv;
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // 计算天空渐变
                float gradientFactor = saturate(i.worldPos.y * 0.5 + 0.5);
                fixed4 gradientColor = lerp(_BottomColor, _TopColor, gradientFactor);
                
                // 添加太阳
                float3 sunDir = normalize(_SunDirection.xyz);
                float sunDot = saturate(dot(i.worldPos, sunDir));
                float sunFactor = pow(sunDot, 1.0 / (_SunSize * _SunSize));
                fixed3 sunColor = _SunColor.rgb * min(sunFactor, 1.0);
                
                // 添加星星
                fixed4 starTex = tex2D(_StarTexture, i.uv);
                float starVisibility = 1.0 - gradientFactor; // 顶部星星更少,底部星星更多
                fixed3 starColor = pow(starTex.rgb, _StarPower) * _StarIntensity * starVisibility;
                
                // 合并所有颜色
                fixed3 finalColor = gradientColor.rgb + sunColor + starColor;
                
                // 应用曝光调整
                finalColor *= _ExposureAdjustment;
                
                return fixed4(finalColor, 1);
            }
            ENDCG
        }
    }
    FallBack "Unlit/Color"
}

这个自定义天空盒着色器创建了一个从底部到顶部的渐变天空,并添加了可自定义的太阳和星星效果。它完全在GPU上生成,不需要预先绘制的立方体贴图,非常适合创建风格化的天空效果。

11.1.2 创建环境反射立方体贴图系统

环境反射是实现逼真材质效果的关键技术之一。立方体贴图通过存储从六个方向观察到的环境,可以为物体提供准确的反射信息。

实现环境反射系统:

以下是一个动态环境反射管理器,它可以实时更新场景中的环境反射:

csharp

using UnityEngine;
using System.Collections.Generic;

public class DynamicReflectionManager : MonoBehaviour
{
    [System.Serializable]
    public class ReflectionZone
    {
        public string zoneName = "New Zone";
        public Transform zonePosition;
        public float zoneRadius = 10f;
        public ReflectionProbe reflectionProbe;
        public float updateInterval = 1.0f;
        [Range(16, 2048)]
        public int resolution = 256;
        public bool realtime = true;
        public LayerMask cullingMask = -1;
        
        [HideInInspector]
        public float lastUpdateTime = 0f;
    }
    
    [Header("Global Settings")]
    public bool enableDynamicReflections = true;
    public float globalUpdateInterval = 0.5f;
    public int defaultResolution = 256;
    
    [Header("Reflection Zones")]
    public List<ReflectionZone> reflectionZones = new List<ReflectionZone>();
    
    [Header("Performance Settings")]
    public int maxUpdatesPerFrame = 1;
    public bool lowerResolutionInBackground = true;
    [Range(0.1f, 1.0f)]
    public float backgroundResolutionScale = 0.5f;
    
    [Header("Debug")]
    public bool showDebugVisuals = true;
    public Color zoneColor = new Color(0, 1, 0, 0.2f);
    public bool logReflectionUpdates = false;
    
    // 内部变量
    private Camera mainCamera;
    private int updatesThisFrame = 0;
    private int frameCount = 0;
    
    void Start()
    {
        mainCamera = Camera.main;
        
        // 初始化反射探针
        InitializeReflectionProbes();
    }
    
    void Update()
    {
        if (!enableDynamicReflections)
            return;
            
        // 每帧重置计数器
        updatesThisFrame = 0;
        frameCount++;
        
        // 处理反射区域更新
        foreach (ReflectionZone zone in reflectionZones)
        {
            // 检查是否需要更新
            if (zone.reflectionProbe != null && Time.time - zone.lastUpdateTime >= zone.updateInterval)
            {
                // 检查区域是否处于视图中
                bool isInView = IsZoneInView(zone);
                
                // 考虑性能设置
                if (updatesThisFrame < maxUpdatesPerFrame)
                {
                    if (zone.realtime || (frameCount % 10 == 0)) // 非实时探针较少更新
                    {
                        // 如果需要根据可见性调整分辨率
                        if (lowerResolutionInBackground && !isInView)
                        {
                            int originalResolution = zone.reflectionProbe.resolution;
                            zone.reflectionProbe.resolution = Mathf.Max(16, Mathf.RoundToInt(originalResolution * backgroundResolutionScale));
                            zone.reflectionProbe.RenderProbe();
                            zone.reflectionProbe.resolution = originalResolution;
                        }
                        else
                        {
                            // 正常更新
                            zone.reflectionProbe.RenderProbe();
                        }
                        
                        zone.lastUpdateTime = Time.time;
                        updatesThisFrame++;
                        
                        if (logReflectionUpdates)
                        {
                            Debug.Log($"Updated reflection probe for zone '{zone.zoneName}' at {Time.time}");
                        }
                    }
                }
            }
        }
    }
    
    bool IsZoneInView(ReflectionZone zone)
    {
        if (mainCamera == null || zone.zonePosition == null)
            return false;
            
        // 检查是否在视锥体内
        Vector3 zonePos = zone.zonePosition.position;
        Vector3 dirToCamera = mainCamera.transform.position - zonePos;
        
        // 简单检查:如果在相机后面,则不可见
        if (Vector3.Dot(mainCamera.transform.forward, dirToCamera.normalized) > 0.1f)
            return false;
            
        // 距离检查
        float distance = dirToCamera.magnitude;
        if (distance > 100f) // 距离太远
            return false;
            
        // 视锥体检查
        Plane[] frustumPlanes = GeometryUtility.CalculateFrustumPlanes(mainCamera);
        Bounds zoneBounds = new Bounds(zonePos, Vector3.one * zone.zoneRadius * 2);
        
        return GeometryUtility.TestPlanesAABB(frustumPlanes, zoneBounds);
    }
    
    void InitializeReflectionProbes()
    {
        foreach (ReflectionZone zone in reflectionZones)
        {
            if (zone.reflectionProbe == null && zone.zonePosition != null)
            {
                // 为区域创建新的反射探针
                GameObject probeObj = new GameObject(zone.zoneName + "_ReflectionProbe");
                probeObj.transform.SetParent(zone.zonePosition);
                probeObj.transform.localPosition = Vector3.zero;
                
                ReflectionProbe probe = probeObj.AddComponent<ReflectionProbe>();
                
                // 设置探针属性
                probe.size = Vector3.one * zone.zoneRadius * 2;
                probe.resolution = zone.resolution > 0 ? zone.resolution : defaultResolution;
                probe.hdr = true;
                probe.cullingMask = zone.cullingMask;
                probe.mode = zone.realtime ? UnityEngine.Rendering.ReflectionProbeMode.Realtime : UnityEngine.Rendering.ReflectionProbeMode.Baked;
                probe.refreshMode = UnityEngine.Rendering.ReflectionProbeRefreshMode.ViaScripting;
                
                // 分配到区域
                zone.reflectionProbe = probe;
                
                // 立即渲染一次
                probe.RenderProbe();
            }
            else if (zone.reflectionProbe != null)
            {
                // 更新现有探针的设置
                zone.reflectionProbe.size = Vector3.one * zone.zoneRadius * 2;
                zone.reflectionProbe.resolution = zone.resolution > 0 ? zone.resolution : defaultResolution;
                zone.reflectionProbe.cullingMask = zone.cullingMask;
                zone.reflectionProbe.mode = zone.realtime ? UnityEngine.Rendering.ReflectionProbeMode.Realtime : UnityEngine.Rendering.ReflectionProbeMode.Baked;
            }
        }
    }
    
    [ContextMenu("Add Reflection Zone At Position")]
    void AddReflectionZoneAtPosition()
    {
        ReflectionZone newZone = new ReflectionZone();
        newZone.zoneName = "Zone_" + reflectionZones.Count;
        newZone.zonePosition = transform;
        newZone.resolution = defaultResolution;
        
        reflectionZones.Add(newZone);
        
        // 初始化新区域的探针
        InitializeReflectionProbes();
    }
    
    void OnDrawGizmos()
    {
        if (!showDebugVisuals)
            return;
            
        foreach (ReflectionZone zone in reflectionZones)
        {
            if (zone.zonePosition != null)
            {
                // 绘制区域范围
                Gizmos.color = zoneColor;
                Gizmos.DrawSphere(zone.zonePosition.position, zone.zoneRadius);
                
                // 绘制线框
                Gizmos.color = new Color(zoneColor.r, zoneColor.g, zoneColor.b, 1.0f);
                Gizmos.DrawWireSphere(zone.zonePosition.position, zone.zoneRadius);
                
                // 绘制标签
                #if UNITY_EDITOR
                UnityEditor.Handles.color = Color.white;
                UnityEditor.Handles.Label(zone.zonePosition.position + Vector3.up * zone.zoneRadius, 
                    zone.zoneName + "\nRes: " + zone.resolution + (zone.realtime ? " (Realtime)" : " (Baked)"));
                #endif
            }
        }
    }
}

这个管理器实现了以下功能:

  1. 在场景中创建多个反射区域,每个区域有独立的反射探针
  2. 基于距离、视锥体可见性和性能设置智能更新反射
  3. 提供可视化工具,方便在编辑器中管理反射区域
  4. 实现了性能优化,如降低背景反射分辨率和限制每帧更新数量

对于环境反射材质,我们可以使用以下着色器来实现可控制的反射效果:

glsl

Shader "Custom/EnvironmentReflection"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        [NoScaleOffset] _BumpMap ("Normal Map", 2D) = "bump" {}
        _BumpScale ("Normal Scale", Range(0, 2)) = 1.0
        
        [Header(Reflection)]
        _ReflectionStrength ("Reflection Strength", Range(0, 1)) = 0.5
        _ReflectionFresnel ("Reflection Fresnel", Range(0, 10)) = 1.0
        [NoScaleOffset] _ReflectionMask ("Reflection Mask", 2D) = "white" {}
        [Toggle] _UseProbeBlending ("Use Probe Blending", Float) = 1
        _ReflectionRoughness ("Reflection Roughness", Range(0, 1)) = 0.0
        [Toggle] _UnityReflectionControl ("Use Unity Reflection Control", Float) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // 使用物理光照模型和环境反射
        #pragma surface surf Standard fullforwardshadows
        #pragma shader_feature _USEPROBEBLENDING_ON
        #pragma shader_feature _UNITYREFLECTIONCONTROL_ON
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _BumpMap;
        sampler2D _ReflectionMask;

        struct Input
        {
            float2 uv_MainTex;
            float3 worldPos;
            float3 viewDir;
            INTERNAL_DATA
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        float _BumpScale;
        float _ReflectionStrength;
        float _ReflectionFresnel;
        float _ReflectionRoughness;

        // 重写反射来添加自定义控制
        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // 基本材质属性
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            
            // 法线贴图
            fixed3 normal = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex));
            normal.xy *= _BumpScale;
            o.Normal = normalize(normal);
            
            // 反射掩码
            fixed4 reflMask = tex2D(_ReflectionMask, IN.uv_MainTex);
            
            // 计算菲涅尔因子 (边缘更反光)
            float3 worldViewDir = normalize(IN.viewDir);
            float fresnel = pow(1.0 - saturate(dot(worldViewDir, WorldNormalVector(IN, o.Normal))), _ReflectionFresnel);
            
            #ifdef _UNITYREFLECTIONCONTROL_ON
                // 使用标准的Unity反射控制
                o.Metallic = _Metallic;
                o.Smoothness = _Glossiness;
                
                // 仅调整反射掩码和菲涅尔
                o.Smoothness *= reflMask.r * (1.0 + fresnel * 0.5);
            #else
                // 使用自定义反射控制
                o.Metallic = _Metallic;
                o.Smoothness = _Glossiness;
                
                // 保存光滑度到额外通道
                o.Occlusion = reflMask.r * _ReflectionStrength * (1.0 + fresnel);
                
                // 设置粗糙度以影响反射模糊
                o.Smoothness = lerp(o.Smoothness, 0.0, _ReflectionRoughness);
                
                #ifdef _USEPROBEBLENDING_ON
                    // 在表面属性上启用探针混合标志
                    o.Emission = fixed3(0.01, 0, 0); // 用于标记使用探针混合
                #endif
            #endif
        }
        ENDCG
    }
    FallBack "Diffuse"
}

这个着色器提供了对环境反射的精细控制,包括:

  1. 反射强度和菲涅尔效应控制,使边缘更反光
  2. 反射掩码纹理,允许对材质不同部分设置不同的反射强度
  3. 可选的探针混合功能,在多个反射探针之间平滑过渡
  4. 反射粗糙度控制,可以模拟磨砂表面的模糊反射

11.1.3 高效实现环境反射系统

环境反射是创建逼真材质的关键技术,下面我们将深入探讨如何在Unity中实现高效的反射系统。

理论基础:

反射的计算基于入射角和法线的关系,遵循公式:反射方向 = 入射方向 - 2 * (入射方向 · 法线) * 法线

在实际实现中,我们需要考虑以下因素:

  1. 反射向量的计算
  2. 采样环境贴图的策略
  3. 反射强度的控制
  4. 菲涅尔效应的模拟

以下是一个实时反射控制器,可以动态调整物体的反射属性:

csharp

using UnityEngine;

[RequireComponent(typeof(Renderer))]
public class ReflectionController : MonoBehaviour
{
    [Header("Reflection Settings")]
    [Range(0, 1)]
    public float reflectivity = 0.5f;
    [Range(0, 5)]
    public float fresnelPower = 1.0f;
    [Range(0, 1)]
    public float reflectionBlur = 0.0f;
    public bool useScreenSpaceReflection = false;
    
    [Header("Environment Map")]
    public Cubemap customReflectionCubemap;
    public bool useProbeBlending = true;
    
    [Header("Animation")]
    public bool animateReflection = false;
    [Range(0, 2)]
    public float pulseSpeed = 0.5f;
    [Range(0, 1)]
    public float pulseAmount = 0.2f;
    
    // 内部变量
    private Renderer rend;
    private Material[] materials;
    private float initialReflectivity;
    private float time = 0f;
    
    void Start()
    {
        rend = GetComponent<Renderer>();
        materials = rend.materials; // 创建材质实例
        initialReflectivity = reflectivity;
        
        ApplyReflectionSettings();
    }
    
    void Update()
    {
        if (animateReflection)
        {
            time += Time.deltaTime;
            reflectivity = initialReflectivity + Mathf.Sin(time * pulseSpeed * Mathf.PI) * pulseAmount;
        }
        
        ApplyReflectionSettings();
    }
    
    void ApplyReflectionSettings()
    {
        foreach (Material mat in materials)
        {
            // 设置反射参数
            if (mat.HasProperty("_ReflectionStrength"))
            {
                mat.SetFloat("_ReflectionStrength", reflectivity);
            }
            else if (mat.HasProperty("_Metallic") && mat.HasProperty("_Glossiness"))
            {
                // 对于标准着色器,调整金属度和光滑度以控制反射
                mat.SetFloat("_Glossiness", Mathf.Lerp(0.1f, 1.0f, reflectivity));
            }
            
            // 设置菲涅尔参数
            if (mat.HasProperty("_ReflectionFresnel"))
            {
                mat.SetFloat("_ReflectionFresnel", fresnelPower);
            }
            
            // 设置反射模糊
            if (mat.HasProperty("_ReflectionRoughness"))
            {
                mat.SetFloat("_ReflectionRoughness", reflectionBlur);
            }
            
            // 启用/禁用探针混合
            if (mat.HasProperty("_UseProbeBlending"))
            {
                if (useProbeBlending)
                    mat.EnableKeyword("_USEPROBEBLENDING_ON");
                else
                    mat.DisableKeyword("_USEPROBEBLENDING_ON");
            }
            
            // 设置自定义反射贴图
            if (customReflectionCubemap != null && mat.HasProperty("_ReflectionCubemap"))
            {
                mat.SetTexture("_ReflectionCubemap", customReflectionCubemap);
                mat.EnableKeyword("_USECUSTOMCUBEMAP_ON");
            }
            else if (mat.HasProperty("_ReflectionCubemap"))
            {
                mat.DisableKeyword("_USECUSTOMCUBEMAP_ON");
            }
            
            // 控制屏幕空间反射
            if (mat.HasProperty("_UseScreenSpaceReflection"))
            {
                if (useScreenSpaceReflection)
                    mat.EnableKeyword("_SCREENSPACEREFLECTION_ON");
                else
                    mat.DisableKeyword("_SCREENSPACEREFLECTION_ON");
            }
        }
    }
    
    void OnDestroy()
    {
        // 清理材质实例
        if (Application.isPlaying)
        {
            foreach (Material m in materials)
            {
                Destroy(m);
            }
        }
    }
    
    // 编辑器辅助方法
    [ContextMenu("Capture Environment Reflection")]
    void CaptureEnvironmentReflection()
    {
        #if UNITY_EDITOR
        // 创建反射探针
        GameObject probeObj = new GameObject("TempReflectionProbe");
        probeObj.transform.position = transform.position;
        
        ReflectionProbe probe = probeObj.AddComponent<ReflectionProbe>();
        probe.resolution = 256;
        probe.hdr = true;
        probe.RenderProbe();
        
        // 等待渲染完成并保存
        UnityEditor.EditorApplication.delayCall += () => {
            string path = UnityEditor.EditorUtility.SaveFilePanelInProject(
                "Save Environment Cubemap",
                gameObject.name + "_EnvMap",
                "asset",
                "Save environment cubemap"
            );
            
            if (!string.IsNullOrEmpty(path))
            {
                Cubemap cubemap = new Cubemap(256, TextureFormat.RGBAHalf, true);
                Graphics.CopyTexture(probe.texture, cubemap);
                
                UnityEditor.AssetDatabase.CreateAsset(cubemap, path);
                UnityEditor.AssetDatabase.SaveAssets();
                
                customReflectionCubemap = cubemap;
                Debug.Log("Saved environment cubemap to: " + p