1. 项目概述:当AIGC遇见Unity天空盒
在游戏开发中,天空盒是构建沉浸感的第一道关卡。一个高质量的天空盒,无论是壮丽的日落、深邃的星空还是奇幻的异世界云层,都能瞬间将玩家拉入游戏世界。然而,传统制作流程要么依赖美术手绘、拍摄实景,要么使用有限的资产商店资源,不仅成本高、周期长,而且难以实现动态、个性化的环境需求。
最近,我在一个需要快速生成大量不同风格场景原型的项目中,尝试将AIGC(人工智能生成内容)的API能力与Unity3D的渲染管线结合,实现了一套动态生成360°全景天空盒的工作流。简单来说,就是通过代码,向AI描述你想要的天空,比如“赛博朋克都市的黄昏,带有霓虹光晕和飞行载具”,然后AI在几秒内生成一张等距柱状投影的全景图,Unity再将其无缝转换为天空盒材质,应用到场景中。整个过程自动化,极大地解放了美术生产力,也为程序化内容生成打开了新思路。
这套方案特别适合独立开发者、小型团队,或是需要快速迭代场景概念验证的项目。你不需要是AI专家,只需要了解基本的Unity C#脚本编写和HTTP请求,就能将Stable Diffusion、Midjourney(通过第三方服务)或DALL-E 3等模型的强大生成能力,直接集成到你的编辑器甚至运行时游戏中。接下来,我将详细拆解从原理到实现的每一步,包括如何选择API、处理图像、在Unity中正确配置,以及如何避开我踩过的那些坑。
2. 核心原理与方案选型
在动手写代码之前,我们必须搞清楚两件事:AIGC API能给我们什么,以及Unity的天空盒系统需要什么。只有两者精准对接,才能避免生成一堆用不了的“废图”。
2.1 全景天空盒的纹理格式解析
Unity支持多种天空盒类型,我们这里聚焦于全景(Panoramic)天空盒。它本质上是一个球体(或半球体)包裹住整个场景,其着色器使用一张特殊的2D纹理——等距柱状投影(Equirectangular Projection)图。
你可以把等距柱状投影图想象成一张世界地图:经度(水平方向)均匀展开,纬度(垂直方向)也均匀展开。这种投影方式虽然在高纬度地区会有拉伸变形,但它有一个巨大的优点:它是一张连续、无缝的矩形图片。对于AIGC模型来说,它接收的提示词(Prompt)和输出的都是一张普通的2D图像,而等距柱状投影图正是最容易被模型理解和生成的“全景图”格式。
在Unity中,创建一个全景天空盒材质时,关键设置在于Mapping属性。我们需要选择Latitude Longitude Layout(经纬度布局),这正是等距柱状投影。同时,Image Type通常设置为360度,表示这张图包裹了整个球体。如果设置为180度,则只包裹半球,背面会是黑色或镜像。
注意:许多AIGC服务默认生成的“全景图”可能只是宽幅图,并非真正的360x180度等距柱状投影。真正的等距柱状投影图,其宽高比必须是2:1(例如4096x2048)。这是后续所有处理的基础,务必在向AI描述时明确要求。
2.2 AIGC API的能力边界与选择
目前,能够通过API生成图像的AIGC服务很多,但并非所有都适合生成天空盒。
- OpenAI DALL-E 3 API:生成质量高,对提示词理解精准,支持生成指定尺寸(包括2:1比例)。缺点是价格相对较高,且对于“无缝衔接”的纹理生成控制力较弱,图像边缘可能不连续。
- Stable Diffusion via API(如Replicate、Stability AI):开源模型,社区庞大,有大量专门针对风景、概念艺术训练的微调模型(如DreamShaper、Realistic Vision)。通过
Replicate等平台调用,可以指定负面提示词(Negative Prompt)来抑制不良内容,并且有些工作流支持“平铺”(Tiling)生成,这对减少天空盒接缝有帮助。性价比通常更高。 - Midjourney:目前官方未提供直接API,需要通过逆向工程或使用第三方封装服务,稳定性与合规性存疑,不推荐用于生产流程。
- 国内大模型API(如文心一言、通义千问):部分已开放图像生成API,访问速度快,符合本地化需求。但需要仔细测试其生成等距柱状投影图的能力和图像质量。
我的选型建议是:对于追求稳定、高质量且预算充足的原型阶段,使用DALL-E 3。对于需要大量生成、风格化要求强且希望控制成本的项目,使用Stable Diffusion API(特别是Replicate平台)。在本指南中,我将以Replicate的Stable Diffusion API为例进行演示,因为它更贴近开发者,参数可控性强。
2.3 系统架构设计
整个动态生成流程可以拆解为一个清晰的管道(Pipeline):
[Unity Editor/Game] -> [HTTP请求] -> [AIGC API服务] -> [返回图像URL] -> [Unity下载并处理] -> [创建材质球] -> [应用到场景天空盒]核心环节在于提示词工程和图像后处理。提示词决定了生成内容的质量与相关性,而后处理则确保图像能完美适配Unity的渲染要求。我们需要在Unity中编写一个SkyboxGenerator脚本来串联这一切,它应该能在编辑器模式下运行(用于快速迭代),也能在运行时有限度地调用(例如为游戏中的天气系统或随机关卡生成天空)。
3. 实战准备:配置环境与获取API密钥
理论清晰后,我们开始搭建实战环境。这里假设你已有一个Unity项目(建议使用2021.3 LTS或更新版本)。
3.1 在Replicate平台设置
- 注册与获取API Key:访问 Replicate 官网注册账号。进入账户设置(Account Settings),在
API Tokens部分,你可以生成一个新的API密钥。妥善保管这个密钥,它相当于你调用服务的密码。 - 选择模型:在Replicate的探索页面,搜索“stable diffusion”。你会看到很多模型,例如
stability-ai/stable-diffusion-3.5-large、sgrigory/stable-diffusion-xl-lightning等。每个模型都有唯一的标识符,如stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b。点击模型页面,可以查看其所需的输入参数和示例。对于天空盒生成,推荐使用在风景、宏大场景上表现较好的模型。
3.2 Unity项目设置
- 导入必要包:我们需要通过Unity的
UnityWebRequest进行网络通信。这通常是内置的。为了更方便地处理JSON和图像,我们可以使用Newtonsoft.Json(需通过Package Manager的Unity Registry搜索安装)或Unity自带的JsonUtility。这里为了轻量,我们使用JsonUtility。 - 创建脚本与目录结构:在项目中创建如下目录结构,便于管理:
Assets/ ├── Scripts/ │ └── SkyboxGenerator.cs ├── Editor/ (可选,用于创建编辑器工具窗口) │ └── SkyboxGeneratorWindow.cs ├── Resources/ (存放提示词模板等) ├── GeneratedSkyboxes/ (运行时生成天空盒的存放路径) └── Materials/ (存放天空盒材质球)
3.3 编写核心提示词模板
提示词是AI生成的灵魂。对于天空盒,我们需要构建一个结构化的模板,确保生成内容符合360度全景视角。
一个基础的提示词模板可以这样设计:
{style} skybox, {time_of_day}, {weather}, {main_elements}, panoramic view, equirectangular projection, 360 degree, seamless, sky dome, environment map, wide angle, ultra detailed, 8k{style}: 风格,如fantasy,cyberpunk,realistic,oil painting,low poly。{time_of_day}: 时间,如sunrise,bright daylight,golden hour sunset,night with milky way。{weather}: 天气,如clear sky,cloudy,stormy with lightning,aurora borealis。{main_elements}: 核心元素,如floating islands and waterfalls,futuristic city skyline,vast desert with mesas。
负面提示词(Negative Prompt)同样重要,用于排除不想要的内容:
blurry, distorted, ugly, duplicate, text, logo, watermark, signature, people, buildings (unless specified), border, frame, lowres, bad anatomy, deformed, extra limbs, disfigured我们可以将这些模板存储在ScriptableObject或简单的文本文件中,方便在Unity中动态组合和调用。
4. 核心实现:构建SkyboxGenerator
现在进入核心环节,我们将一步步构建SkyboxGenerator类。
4.1 定义数据模型与配置
首先,创建一个可序列化的配置类,用于存储API信息和生成参数。
using System; using UnityEngine; [Serializable] public class SkyboxGenerationConfig { [Header("API Configuration")] public string replicateApiKey = "your-api-key-here"; // 务必不要硬编码,后续会讲安全存储 public string modelVersion = "stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b"; [Header("Generation Parameters")] [TextArea(3, 5)] public string promptTemplate = "fantasy skybox, {0}, {1}, panoramic view, equirectangular projection, 360 degree, seamless, ultra detailed"; [TextArea(2, 5)] public string negativePrompt = "blurry, distorted, text, logo, watermark, people, border, frame, lowres"; public int width = 2048; // 建议宽度,2:1比例 public int height = 1024; // 建议高度 public int numOutputs = 1; // 每次生成数量 public float guidanceScale = 7.5f; // 提示词相关性强度 public int numInferenceSteps = 30; // 扩散步数,影响质量和速度 }4.2 实现API调用与异步处理
核心方法是发起一个HTTP POST请求到Replicate API。我们需要处理异步操作,避免阻塞主线程。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Networking; using System.Text; public class SkyboxGenerator : MonoBehaviour { public SkyboxGenerationConfig config; private string apiUrl = "https://api.replicate.com/v1/predictions"; public void GenerateSkybox(string timeOfDay, string weather, string mainElement) { // 1. 组合提示词 string finalPrompt = string.Format(config.promptTemplate, timeOfDay, weather, mainElement); // 2. 构建请求数据 var requestData = new ReplicateRequest { version = config.modelVersion, input = new InputData { prompt = finalPrompt, negative_prompt = config.negativePrompt, width = config.width, height = config.height, num_outputs = config.numOutputs, guidance_scale = config.guidanceScale, num_inference_steps = config.numInferenceSteps } }; string jsonData = JsonUtility.ToJson(requestData); // 3. 启动协程发送请求 StartCoroutine(PostGenerationRequest(jsonData)); } private IEnumerator PostGenerationRequest(string jsonData) { using (UnityWebRequest request = new UnityWebRequest(apiUrl, "POST")) { byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonData); request.uploadHandler = new UploadHandlerRaw(bodyRaw); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); request.SetRequestHeader("Authorization", $"Token {config.replicateApiKey}"); yield return request.SendWebRequest(); if (request.result != UnityWebRequest.Result.Success) { Debug.LogError($"API Request Failed: {request.error}"); Debug.LogError($"Response: {request.downloadHandler.text}"); } else { // 解析响应,获取预测ID var response = JsonUtility.FromJson<ReplicateResponse>(request.downloadHandler.text); string predictionId = response.id; Debug.Log($"Generation started! Prediction ID: {predictionId}"); // 启动另一个协程轮询结果 StartCoroutine(PollForResult(predictionId)); } } } // 轮询结果,因为生成是异步的 private IEnumerator PollForResult(string predictionId) { string statusUrl = $"https://api.replicate.com/v1/predictions/{predictionId}"; bool isCompleted = false; string outputUrl = ""; while (!isCompleted) { using (UnityWebRequest pollRequest = UnityWebRequest.Get(statusUrl)) { pollRequest.SetRequestHeader("Authorization", $"Token {config.replicateApiKey}"); yield return pollRequest.SendWebRequest(); if (pollRequest.result == UnityWebRequest.Result.Success) { var statusResponse = JsonUtility.FromJson<ReplicateStatusResponse>(pollRequest.downloadHandler.text); if (statusResponse.status == "succeeded") { // 假设只生成一个输出 if (statusResponse.output != null && statusResponse.output.Length > 0) { outputUrl = statusResponse.output[0]; isCompleted = true; Debug.Log($"Generation succeeded! Image URL: {outputUrl}"); // 下载图像 StartCoroutine(DownloadAndApplySkybox(outputUrl)); } } else if (statusResponse.status == "failed") { Debug.LogError($"Generation failed: {statusResponse.error}"); isCompleted = true; } else { // 还在处理中,等待几秒再轮询 Debug.Log($"Status: {statusResponse.status}. Waiting..."); yield return new WaitForSeconds(2f); } } else { Debug.LogError($"Polling failed: {pollRequest.error}"); isCompleted = true; } } } } // 辅助数据类 [System.Serializable] private class ReplicateRequest { public string version; public InputData input; } [System.Serializable] private class InputData { public string prompt; public string negative_prompt; public int width; public int height; public int num_outputs; public float guidance_scale; public int num_inference_steps; } [System.Serializable] private class ReplicateResponse { public string id; } [System.Serializable] private class ReplicateStatusResponse { public string id; public string status; public string[] output; public string error; } }4.3 下载图像并创建天空盒材质
获取到图像URL后,我们需要下载它,并在Unity中创建纹理和材质。
private IEnumerator DownloadAndApplySkybox(string imageUrl) { using (UnityWebRequest imageRequest = UnityWebRequestTexture.GetTexture(imageUrl)) { yield return imageRequest.SendWebRequest(); if (imageRequest.result == UnityWebRequest.Result.Success) { Texture2D downloadedTexture = DownloadHandlerTexture.GetContent(imageRequest); // 关键步骤:设置纹理导入格式(如果是运行时,需动态创建材质) CreateSkyboxMaterial(downloadedTexture); } else { Debug.LogError($"Failed to download image: {imageRequest.error}"); } } } private void CreateSkyboxMaterial(Texture2D panoramaTexture) { // 1. 创建一个新的材质球,使用Unity内置的“Skybox/Panoramic”着色器 Material skyboxMat = new Material(Shader.Find("Skybox/Panoramic")); // 2. 将下载的纹理赋值给材质的_MainTex属性 skyboxMat.SetTexture("_MainTex", panoramaTexture); // 3. 关键:设置材质参数以匹配等距柱状投影 // 映射方式:经纬度布局 (对应Latitude Longitude) skyboxMat.SetFloat("_Mapping", 1); // 0是6面体,1是经纬度 // 图像类型:360度 skyboxMat.SetFloat("_ImageType", 1); // 0是180度,1是360度 // 可以根据需要调整曝光和色调 skyboxMat.SetFloat("_Exposure", 1.0f); skyboxMat.SetColor("_Tint", Color.white); // 4. 将材质保存为资产(编辑器模式下)或直接应用到渲染设置 #if UNITY_EDITOR string assetPath = $"Assets/GeneratedSkyboxes/Skybox_{System.DateTime.Now:yyyyMMdd_HHmmss}.mat"; UnityEditor.AssetDatabase.CreateAsset(skyboxMat, assetPath); UnityEditor.AssetDatabase.SaveAssets(); UnityEditor.AssetDatabase.Refresh(); Debug.Log($"Skybox material saved to: {assetPath}"); #endif // 5. 应用到当前场景的天空盒 RenderSettings.skybox = skyboxMat; // 强制环境光更新,以匹配新天空盒 DynamicGI.UpdateEnvironment(); }5. 进阶优化与编辑器集成
基础功能实现后,我们可以从易用性、稳定性和效果上进行深度优化。
5.1 构建可视化编辑器工具
在Editor文件夹下创建SkyboxGeneratorWindow.cs,提供一个友好的界面。
using UnityEditor; using UnityEngine; public class SkyboxGeneratorWindow : EditorWindow { private SkyboxGenerationConfig config; private string timeOfDay = "sunset"; private string weather = "clear"; private string mainElement = "mountains and lake"; [MenuItem("Tools/AIGC Skybox Generator")] public static void ShowWindow() { GetWindow<SkyboxGeneratorWindow>("Skybox Generator"); } private void OnGUI() { GUILayout.Label("Skybox Generation Settings", EditorStyles.boldLabel); // 加载或创建配置 if (config == null) { config = AssetDatabase.LoadAssetAtPath<SkyboxGenerationConfig>("Assets/Resources/SkyboxConfig.asset"); if (config == null) { config = CreateInstance<SkyboxGenerationConfig>(); AssetDatabase.CreateAsset(config, "Assets/Resources/SkyboxConfig.asset"); AssetDatabase.SaveAssets(); } } SerializedObject serializedConfig = new SerializedObject(config); EditorGUILayout.PropertyField(serializedConfig.FindProperty("replicateApiKey")); EditorGUILayout.PropertyField(serializedConfig.FindProperty("modelVersion")); EditorGUILayout.Space(); EditorGUILayout.PropertyField(serializedConfig.FindProperty("promptTemplate")); EditorGUILayout.PropertyField(serializedConfig.FindProperty("negativePrompt")); EditorGUILayout.PropertyField(serializedConfig.FindProperty("width")); EditorGUILayout.PropertyField(serializedConfig.FindProperty("height")); serializedConfig.ApplyModifiedProperties(); EditorGUILayout.Space(10); GUILayout.Label("Quick Generate", EditorStyles.boldLabel); timeOfDay = EditorGUILayout.TextField("Time of Day", timeOfDay); weather = EditorGUILayout.TextField("Weather", weather); mainElement = EditorGUILayout.TextField("Main Element", mainElement); if (GUILayout.Button("Generate Skybox Now!")) { // 这里需要找到场景中的SkyboxGenerator组件并调用方法 // 或者直接实例化一个临时GameObject来执行 var generator = FindObjectOfType<SkyboxGenerator>(); if (generator == null) { GameObject go = new GameObject("TempSkyboxGenerator"); generator = go.AddComponent<SkyboxGenerator>(); generator.config = config; } generator.GenerateSkybox(timeOfDay, weather, mainElement); } } }5.2 图像后处理与接缝修复
AIGC生成的图像边缘往往不连续,直接用作天空盒会在接缝处产生明显的断裂线。我们需要一个后处理步骤。
- 在提示词中强调:在提示词末尾加上
seamless texture, tileable,引导AI生成可平铺的图像。 - 在Unity中进行后期处理:生成纹理后,使用
Texture2D.Apply配合一个简单的像素混合算法,对纹理的左右边缘进行混合。下面是一个简单的边缘混合示例方法:
private Texture2D BlendTextureEdges(Texture2D sourceTex, int blendWidth = 64) { Texture2D blendedTex = new Texture2D(sourceTex.width, sourceTex.height); Color[] pixels = sourceTex.GetPixels(); // 混合左右边缘 for (int y = 0; y < sourceTex.height; y++) { for (int x = 0; x < blendWidth; x++) { // 左边缘像素 Color leftPixel = pixels[y * sourceTex.width + x]; // 对应的右边缘像素(从最右侧开始) Color rightPixel = pixels[y * sourceTex.width + (sourceTex.width - blendWidth + x)]; // 计算混合因子(越靠近边缘,混合越多) float factor = (float)x / blendWidth; Color blendedColor = Color.Lerp(leftPixel, rightPixel, factor * 0.5f); // 只混合一半强度,避免中心扭曲 pixels[y * sourceTex.width + x] = blendedColor; pixels[y * sourceTex.width + (sourceTex.width - blendWidth + x)] = Color.Lerp(rightPixel, leftPixel, factor * 0.5f); } } blendedTex.SetPixels(pixels); blendedTex.Apply(); return blendedTex; } // 在CreateSkyboxMaterial中调用: Texture2D processedTexture = BlendTextureEdges(panoramaTexture); skyboxMat.SetTexture("_MainTex", processedTexture);5.3 性能优化与资源管理
- 纹理尺寸:4096x2048的纹理对于移动端可能过大。可以根据目标平台动态调整生成请求中的
width和height参数,或生成后使用Texture2D.Resize进行缩放。 - 缓存机制:为生成的天空盒建立本地缓存(使用
predictionId或提示词哈希作为文件名)。再次请求相同描述的 skybox 时,优先从本地加载,节省API调用成本和时间。 - 异步加载与进度反馈:在编辑器工具或游戏UI中显示生成进度(“正在提交请求”、“生成中”、“下载图像”),提升用户体验。
- API密钥安全:绝对不要将API密钥硬编码在脚本中或提交到版本控制系统。应使用Unity的
PlayerPrefs(仅用于开发)或更安全的方案如环境变量,或创建一个不提交的配置文件config.private.json,在启动时读取。
6. 常见问题排查与实战心得
在实际操作中,你肯定会遇到各种问题。以下是我踩过坑后总结的排查清单和心得。
6.1 图像生成相关
生成的图像不是2:1比例,在Unity中拉伸变形
- 原因:API调用时指定的
width和height不是2:1比例,或者AI模型没有遵循指令。 - 解决:确保请求参数中
width和height严格为2:1(如2048x1024)。在提示词中反复强调equirectangular projection, 360 degree, 2:1 aspect ratio。如果模型不听话,可以尝试换一个更听话的模型,或在生成后使用图像处理库(如Unity的Texture2D方法)进行裁剪和缩放。
- 原因:API调用时指定的
天空盒有明显的接缝
- 原因:AI生成的图像左右边缘内容不连续。
- 解决:
- 提示词优化:加入
seamless, tileable horizontally。 - 后处理:务必使用上文提到的边缘混合算法。
- 模型选择:有些Stable Diffusion的微调模型专门擅长生成无缝纹理,可以尝试搜索
seamless texture相关的模型。
- 提示词优化:加入
生成的内容与提示词不符(例如出现了人物或建筑)
- 原因:负面提示词不够强力,或正面提示词权重不足。
- 解决:加强负面提示词,明确排除
people, buildings, cars等。对于Stable Diffusion API,可以调整guidance_scale参数(提高它,如调到10-12,让AI更严格遵守提示词)。对于DALL-E 3,可以在提示词中更精确地描述视角,如a view from the center of a sphere looking out, no foreground objects。
6.2 Unity集成相关
应用天空盒后,场景变暗或曝光过度
- 原因:天空盒的
Exposure(曝光)值未调整,或生成的HDR信息与Unity的色调映射不匹配。 - 解决:在
CreateSkyboxMaterial方法中,动态调整_Exposure值。可以写一个简单的辅助方法,分析纹理的平均亮度,然后自动计算一个合适的曝光值。或者,提供一个滑块在编辑器工具中手动微调。
- 原因:天空盒的
运行时生成天空盒导致游戏卡顿
- 原因:下载大尺寸纹理(如4K)和创建材质是同步操作,阻塞主线程。
- 解决:确保所有网络请求和纹理处理都在协程(
IEnumerator)中进行。对于移动端,考虑在后台线程完成图像下载和初步处理,再在主线程应用材质。另外,纹理尺寸不要盲目求大,1080p(2048x1024)在大多数情况下已足够清晰。
在WebGL或移动平台上报错,无法调用API
- 原因:跨域问题(CORS)或平台网络权限限制。
- 解决:对于Replicate等主流API服务,通常已配置好CORS。问题可能出在Unity WebGL的
UnityWebRequest上。确保在Player Settings->Publishing Settings->WebGL中启用了WebGL 2.0,并尝试调整WebGL Template。对于移动端,确保AndroidManifest.xml或Info.plist中包含了网络权限。
6.3 成本与效率优化心得
- 批量生成与预览:在项目初期,可以写一个脚本,用一组不同的风格、时间、天气组合成批量的提示词,然后一次性提交多个生成任务。生成后,在Unity中做一个简单的图库浏览器,快速预览并选择最合适的,避免反复手动修改提示词。
- 使用LoRA或模型融合:如果你有特定的艺术风格需求(例如你的游戏是水墨风),可以训练一个LoRA(Low-Rank Adaptation)模型,然后在API调用时指定该模型,这样生成的所有天空盒都会带有统一的风格,极大地提升美术一致性。
- 分层生成与合成:对于特别复杂的天空盒(比如既有星空又有极光还有特定的云形),可以尝试分层次生成:先生成基础天空,再生成云层(作为透明PNG),最后在Unity中通过多层Shader或后期处理合成。这比让AI一次性理解所有复杂指令更可靠。
- 本地部署Stable Diffusion:如果生成需求极大,API成本会成为问题。可以考虑在本地或公司服务器部署Stable Diffusion WebUI及其API。虽然初期设置复杂,但长期来看成本可控,且数据隐私性更高。本地API的调用方式与Replicate类似,只是端点(endpoint)和参数略有不同。
将AIGC集成到游戏开发管线中,不是一个“一劳永逸”的魔法,而是一个需要不断调试和优化的“协作过程”。你需要像对待一位有才华但理解力有时会偏差的美术同事一样,用精确的“需求文档”(提示词)和有效的“反馈机制”(参数调整、后处理)来引导它。一旦这个流程跑通,你会发现它为环境艺术创作带来的灵活性和速度是革命性的。从根据游戏内时间动态生成黎明到黄昏的天空序列,到为每一个自动生成的星球创建独一无二的天穹,可能性才刚刚展开。