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
相关推荐
天人合一peng8 小时前
unity 生成标记根据背景色标记变色
unity·游戏引擎
天人合一peng12 小时前
unity 生成标记根据背景色变色为明显的颜色
unity·游戏引擎
魔士于安12 小时前
Unity 超市总动员 超市收银台 超市货架 超市购物手推车 超市常见商品
游戏·unity·游戏引擎·贴图·模型
CandyU212 小时前
Unity —— 数据持久化
unity·游戏引擎
zh路西法12 小时前
【Unity实现Oneshot胶卷显形】游戏窗口化与Win32API的使用
游戏·unity·游戏引擎
迪捷软件13 小时前
显控系统虚拟仿真的工程化路径
游戏引擎·cocos2d
凡情17 小时前
android隐私合规检测
android·unity
小贺儿开发17 小时前
Unity3D 本地 Stable Diffusion 文生图效果演示
人工智能·unity·stable diffusion·文生图·ai绘画·本地化
Swift社区18 小时前
传统游戏引擎 vs 鸿蒙 System 架构
架构·游戏引擎·harmonyos
mxwin1 天前
Unity Shader 半透明物体为什么不能写入深度缓冲?
unity·游戏引擎·shader