unity 无头模式启动

一、实现步骤

1. 前提条件:安装 NVIDIA 驱动(无桌面版)

首先要确保 Linux 系统正确安装了 NVIDIA 显卡驱动(不是桌面版的 nouveau 开源驱动),且驱动支持 Egl/GLX 离屏渲染。

c 复制代码
# 1. 卸载原有开源驱动(若有)
sudo apt purge nvidia* nouveau-firmware
sudo apt autoremove

# 2. 添加NVIDIA官方源(以Ubuntu 22.04为例)
sudo apt install -y software-properties-common
sudo add-apt-repository ppa:graphics-drivers/ppa
sudo apt update

# 3. 安装对应版本的NVIDIA驱动(推荐470+以上版本)
# 先查看适配的驱动版本:ubuntu-drivers devices
sudo apt install -y nvidia-driver-550 nvidia-utils-550

# 4. 验证驱动是否安装成功(无桌面也能执行)
nvidia-smi  # 能输出显卡信息即表示驱动正常

# 5.安装 OpenGL/EGL 开发库
sudo apt install libegl1-mesa-dev libgles2-mesa-dev libopengl-dev

如果使用vulkan + unity

c 复制代码
# 安装基础 OpenGL 库(即使无桌面) 
sudo apt install libgl1 libegl1 libvulkan1

# 赋予执行权限 
chmod +x YourGame.x86_64

# 后台运行 
sudo nohup ./YourGame.x86_64 -batchmode -screen-width 1920 -screen-height 960 > unity.log 2>&1 &

2. Unity 程序启动参数

无需 xvfb 虚拟驱动,直接通过 NVIDIA 的 EGL/GLX 离屏渲染上下文启动 Unity 程序

c 复制代码
#无桌面Linux + NVIDIA显卡启动Unity程序(启用GPU渲染,无物理窗口) 
./YourGame.x86_64 -batchmode -no-window -force-vulkan -gpu-device-id 0
./YourGame.x86_64 -batchmode -screen-width 1920 -screen-height 960`

参数说明:

-batchmode:批处理模式,禁用交互;

-no-window:禁用物理窗口(核心!只保留 GPU 离屏渲染);

-force-vulkan:强制使用 Vulkan API(NVIDIA 对 Vulkan 离屏渲染支持更好,也可改用-force-glcore);

-gpu-device-id 0:指定使用第 0 块 NVIDIA 显卡(多显卡场景必备)。

即使没有 X11/Wayland,只要 NVIDIA 驱动支持 EGL,Unity 也能创建 OpenGL 上下文进行离屏渲染。

3. Unity 代码实现 RenderTexture 渲染(和普通环境一致)

c# 复制代码
using UnityEngine;
using System.IO;

public class NVIDIAOffscreenRender : MonoBehaviour
{
    public Camera renderCamera;
    public int width = 1920;
    public int height = 1080;
    private RenderTexture rt;

    void Start()
    {
        // 1. 初始化RenderTexture(底层是NVIDIA显卡的FBO)
        rt = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32);
        rt.enableRandomWrite = true; // 启用随机读写(NVIDIA显卡优化)
        rt.Create(); // 显式创建FBO(无桌面环境建议显式调用)

        // 2. 将相机渲染目标指向RenderTexture
        renderCamera.targetTexture = rt;
        
        // 3. 执行渲染并提取图像数据
        CaptureFrame();
    }

    void CaptureFrame()
    {
        // 强制相机渲染一帧到FBO(RenderTexture)
        renderCamera.Render();

        // 4. 读取FBO中的像素数据到CPU
        RenderTexture.active = rt;
        Texture2D tex = new Texture2D(width, height, TextureFormat.ARGB32, false);
        tex.ReadPixels(new Rect(0, 0, width, height), 0, 0);
        tex.Apply();

        // 5. 输出图像(保存为文件/网络传输/其他处理)
        byte[] pngData = tex.EncodeToPNG();
        string savePath = Path.Combine(Application.persistentDataPath, "nvidia_capture.png");
        File.WriteAllBytes(savePath, pngData);

        Debug.Log($"图像已保存到:{savePath}");

        // 6. 清理资源
        RenderTexture.active = null;
        Destroy(tex);
        rt.Release(); // 释放FBO资源
    }
}
//////////////////////////////////////////////////////////////
//第二个例子
using UnityEngine;
using UnityEngine.Rendering;
using System.Threading.Tasks;

public class HeadlessRenderer : MonoBehaviour
{
    public int width = 1920;
    public int height = 960;
    private RenderTexture renderTex;
    private Camera renderCam;

    async void Start()
    {
        // 创建离屏渲染目标
        renderTex = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32);
        renderTex.Create();

        // 创建专用相机(或复用主相机)
        GameObject camObj = new GameObject("RenderCamera");
        renderCam = camObj.AddComponent<Camera>();
        renderCam.targetTexture = renderTex;
        renderCam.clearFlags = CameraClearFlags.Color;
        renderCam.backgroundColor = Color.blue;

        // 示例:每秒渲染一帧并保存
        while (true)
        {
            RenderPanorama();
            byte[] jpg = await CaptureAsJPG(renderTex);
            System.IO.File.WriteAllBytes("/tmp/frame_" + Time.frameCount + ".jpg", jpg);
            await Task.Delay(1000); // 1 FPS
        }
    }

    void RenderPanorama()
    {
        // 在这里执行你的拼接逻辑:
        // - 设置 Projector
        // - 绘制鱼眼矫正后的网格
        // - 渲染球面全景等
        renderCam.Render();
    }

    async Task<byte[]> CaptureAsJPG(RenderTexture rt)
    {
        var request = AsyncGPUReadback.Request(rt);
        while (!request.done)
            await Task.Yield();

        if (request.hasError)
        {
            Debug.LogError("GPU readback failed");
            return null;
        }

        // 转为 Texture2D 并编码
        Texture2D tex = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false);
        tex.LoadRawTextureData(request.GetData<byte>());
        tex.Apply();

        byte[] jpg = tex.EncodeToJPG(90);
        Object.Destroy(tex);
        return jpg;
    }

    void OnDestroy()
    {
        if (renderTex != null) renderTex.Release();
    }
}

权限

sudo chmod +x YourGame.x86_64 && sudo ./YourGame.x86_64

unity下别忘了挂载脚本

二、egl验证

使用egl去验证无头模式

c 复制代码
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl32.h>
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>

// EGL 函数指针(动态加载)
PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = nullptr;
PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = nullptr;

bool initEGL(EGLDisplay& display, EGLContext& context) {
    // 获取函数指针
    eglQueryDevicesEXT = (PFNEGLQUERYDEVICESEXTPROC) eglGetProcAddress("eglQueryDevicesEXT");
    eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC) eglGetProcAddress("eglGetPlatformDisplayEXT");

    if (!eglQueryDevicesExtensions || !eglGetPlatformDisplayEXT) {
        std::cerr << "EGL EXT not supported\n";
        return false;
    }

    // 枚举 GPU 设备
    EGLDeviceEXT devices[4];
    EGLint numDevices;
    if (!eglQueryDevicesEXT(4, devices, &numDevices) || numDevices == 0) {
        std::cerr << "No EGL devices found\n";
        return false;
    }

    // 使用第一个 NVIDIA GPU
    display = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, devices[0], nullptr);
    if (display == EGL_NO_DISPLAY) {
        std::cerr << "Failed to get EGL display\n";
        return false;
    }

    if (!eglInitialize(display, nullptr, nullptr)) {
        std::cerr << "eglInitialize failed\n";
        return false;
    }

    // 选择配置
    EGLint configAttribs[] = {
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
        EGL_NONE
    };
    EGLConfig config;
    EGLint numConfigs;
    eglChooseConfig(display, configAttribs, &config, 1, &numConfigs);

    // 创建 context(无 surface)
    EGLint contextAttribs[] = {
        EGL_CONTEXT_CLIENT_VERSION, 3,
        EGL_NONE
    };
    context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
    if (context == EGL_NO_CONTEXT) {
        std::cerr << "Failed to create EGL context\n";
        return false;
    }

    if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context)) {
        std::cerr << "eglMakeCurrent failed\n";
        return false;
    }

    return true;
}

GLuint createFBO(int width, int height, GLuint& colorTex, GLuint& depthRb) {
    // 创建颜色纹理
    glGenTextures(1, &colorTex);
    glBindTexture(GL_TEXTURE_2D, colorTex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // 创建深度渲染缓冲
    glGenRenderbuffers(1, &depthRb);
    glBindRenderbuffer(GL_RENDERBUFFER, depthRb);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);

    // 创建 FBO
    GLuint fbo;
    glGenFramebuffers(1, &fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex, 0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRb);

    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        std::cerr << "FBO incomplete\n";
        return 0;
    }

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    return fbo;
}

void renderToTexture(int width, int height) {
    // 创建 FBO
    GLuint colorTex, depthRb, fbo = createFBO(width, height, colorTex, depthRb);

    // 绑定 FBO 并渲染
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glViewport(0, 0, width, height);
    glClearColor(0.2f, 0.3f, 0.8f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // TODO: 在这里执行你的拼接渲染逻辑(如 Projector、球面映射等)
    // 例如:绘制两个 warp 后的纹理到球面

    // 读回像素
    std::vector<unsigned char> pixels(width * height * 4);
    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());

    // 转为 OpenCV Mat(BGR) 效率不高,注意只是测试,不要使用这类代码
    //实际解码的时候就要直接倒过来,也不要RGBA
    cv::Mat img(height, width, CV_8UC4, pixels.data());
    cv::Mat bgr;
    cvtColor(img, bgr, cv::COLOR_RGBA2BGR);
    cv::flip(bgr, bgr, 0); // OpenGL 是 bottom-up

    // 保存或送入 FFmpeg
    cv::imwrite("rendered_panorama.jpg", bgr);
    std::cout << "Saved rendered image.\n";

    // 清理
    glDeleteFramebuffers(1, &fbo);
    glDeleteTextures(1, &colorTex);
    glDeleteRenderbuffers(1, &depthRb);
}

int main() {
    EGLDisplay display;
    EGLContext context;

    if (!initEGL(display, context)) {
        return -1;
    }

    std::cout << "EGL initialized. Rendering...\n";
    renderToTexture(1920, 960); // 输出 2:1 全景图

    eglDestroyContext(display, context);
    eglTerminate(display);
    return 0;
}

g++ -o headless_render headless_render.cpp \
    -lEGL -lGLESv2 -lopencv_core -lopencv_imgcodecs \
    -I/usr/include/libdrm

三、备用Xvfb

c 复制代码
>  安装 Xvfb sudo apt install xvfb
>  启动虚拟显示 Xvfb :99 -screen 0 1920x960x24 &
>  在该显示下运行 Unity DISPLAY=:99 ./YourGame.x86_64 -batchmode
相关推荐
郝学胜-神的一滴2 小时前
深入解析Mipmap层级判定原理:从理论到实践
c++·unity·godot·游戏程序·图形渲染·unreal engine
weixin_409383123 小时前
cocos shader闪光
游戏引擎·cocos2d
Howrun7773 小时前
虚幻引擎_用户小控件_准星
c++·游戏引擎·虚幻
孟无岐19 小时前
【Laya】Component 使用说明
typescript·游戏引擎·游戏程序·laya
weixin_4093831219 小时前
cocos shader三角流光
游戏引擎·cocos2d
Mars-xq21 小时前
godot 毛玻璃效果着色器shader
游戏引擎·godot·着色器
绀目澄清1 天前
unity3d AI Navigation 中文文档
游戏·unity
绀目澄清1 天前
Unity 的AI Navigation 系统详细总结
人工智能·unity·游戏引擎
绀目澄清1 天前
Unity3D AI Navigation 详解:从基础概念到实战应用
unity·游戏引擎