最近项目的开发中,遇到不少用户上传的视频、图片资源。 这些资源需要下载到端侧, 然后渲染出来。 由于Unity不能离线处理处理这些资源, 所以很多图片资源在显存直接展开成RGBA(png)或者RGB(jpg), 由于没有压缩,所以带来很大的显存开销。于是就想把unity中部署docker中, 在一些资源部署到server上去之前, 把图片压缩成ETC、ASTC、PVRTC这些支持GPU格式的纹理,从而达到降内存的目的。 此外在docker中除了部署unity的环境, 还可以部署python环境,python丰富的package可以处理音频、视频,从而形成一个完整的在线资源处理服务。

导出发布版Linux Unity

需要对应下载unity对应版本的linux standanle component, 安装之后, 你的unity就在导出的时候就有linux的选项了。

Target Platform 选择linux, Server Build一定要勾上, 这样在无图形界面才能运行的起来。

导出一个volume的文件夹, 然后docker run的时候以外挂盘的形式运行。

这里docker 镜像与外部的交互方式还是 http的方式, 因此需要在镜像里部署一个webserver, 以接收来自外部的请求, webserver接收到 get/post的数据之后进行解析, 然后发起unity, unity读取外部传递的参数, 类似下面的代码:

string[] args = Environment.GetCommandLineArgs();
Debug.Log("args len: " + args.Length);
for (int i = 0; i < args.Length; i++)
{
    Debug.Log("arg " + i + ": " + args[i]);
    if (args[i].StartsWith("--path"))
    {
        path = args[i].Replace("--path=", string.Empty).Trim();
        break;
    }
    if (args[i].StartsWith("--args"))
    {
        param = args[i].Replace("--args=", string.Empty).Trim();
    }
}

外部传递参数可以是路径的方式加参数的方式, 如在python里可以这样写:

def run(self, process_path):
    print("launch unity: " + self.launch_string)
    if os.path.exists(self.launch_string):
        print("process path: ", process_path)
        path = self.launch_string + ' --path=' + process_path
        os.system(path)
    else:
        print("not find unity " + self.launch_string)

在 shell 里直接:

<unity-launch-path> --pth=***.jpg

这样调用之后, unity测就会自动读取到传过来的参数了, 然后根据参数就可以分发不同的逻辑了。 由于这样的unity是导出的, 所以python里的启动unity速度很快, unity官方的 ml-agent 进行ai 训练就是使用的这样的方式。 然而这样的方式缺点也是很明显, 由于发布版本unity没有了 UnityEditor, 所以像AssetBundle、 TextureImporter这些东西都不能使用了。

Linux Unity Editor

Linux版本的unity激活 真是一个麻烦事, 因为都是命令行的,非图形界面不能像在Unityhub中来界面上直接激活。Unity提供了一种命令行的激活方式, 不过只针对是pro/plus版本的unity。 搜遍网络, 找到一位博主激活的unity版本docker镜像, 不过这里内置的unity是2017版本的, 其他版本的只好咨询博主了。

直接在docker里集成一个unity, 这里会导致镜像的容量会非常大, 此外此种方式unity的启动速度也会非常的慢, 因为unity在启动的时候, 会首先去server上验证一下license, 网上的很多使用此种方式来打包之类的ci。 作者在docker启动的时候外挂一个unity工程的虚拟盘来处理在线资源。

# 编译镜像
docker build -t hola.unity.v0.1 .

# 进入到本地目录, 把 Unity-volume 做 虚拟盘 映射到 镜像中
docker run -it --name Hola-Unity-Container --mount type=bind,source="$(pwd)"/unity-volume,target=/unity-volume hola.unity.v0.1:latest

# 进入到镜像, 以 batchmode 的方式处理资源
# DockerMain 是一个 Editor 类, Process 是无参静态方法
/opt/Unity/Editor/Unity -quit -batchmode -nographics -silent-crashes -logFile /dev/stdout -projectPath "/unity-volume" -executeMethod "DockerMain.Process"

渲染

unity 如果server build(headless)的版本, 是不支持渲染, 只支持一些运算相关的操作, 否则导出之后, 日志输出会提示Shader相关的报错, 即使内置最简单的FallBack “Diffuse”也不行。pyrender 支持以EGL和 mesa的两种方式运行, 其中Egl是默认的方式, 默认EGl使用的opengl的图像接口使用GPU加速运算的。 如果集群上跑渲染以headless方式, 没有GPU的话, 还是OSMesa 的方式进行, 其能保证在cpu上渲染出来相同的效果。

安装步骤是通过pip命令:

pip install pyrender

安装osmesa

sudo apt update
sudo wget https://github.com/mmatl/travis_debs/raw/master/xenial/mesa_18.3.3-0.deb
sudo dpkg -i ./mesa_18.3.3-0.deb || true
sudo apt install -f

也可以通过源码的方式安装, 下载之后,先行解压

tar xfv mesa-18.3.3.tar.gz
cd mesa-18.3.3

然后配置项, PREFIX是安装路径, 默认是/usr/local

./configure --prefix=PREFIX                                   \
            --enable-opengl --disable-gles1 --disable-gles2   \
            --disable-va --disable-xvmc --disable-vdpau       \
            --enable-shared-glapi                             \
            --disable-texture-float                           \
            --enable-gallium-llvm --enable-llvm-shared-libs   \
            --with-gallium-drivers=swrast,swr                 \
            --disable-dri --with-dri-drivers=                 \
            --disable-egl --with-egl-platforms= --disable-gbm \
            --disable-glx                                     \
            --disable-osmesa --enable-gallium-osmesa          \
            ac_cv_path_LLVM_CONFIG=llvm-config-6.0

最后通过make命令进行编译和安装

make -j8
make install

最后使用api之前先配置相关的环境变量:

MESA_HOME=/path/to/your/mesa/installation
export LIBRARY_PATH=$LIBRARY_PATH:$MESA_HOME/lib
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$MESA_HOME/lib
export C_INCLUDE_PATH=$C_INCLUDE_PATH:$MESA_HOME/include/
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:$MESA_HOME/include/

安装PyOpenGL:

git clone https://github.com/mmatl/pyopengl.git
pip install ./pyopengl

注意, 笔者测试法线这里一定要源码安装pyopengl, 如果之前通过pip从server发布版本的方式方式,会报错。

如pyrender 会依赖于pyopengl. 一定先卸载了。

pip intall pyrender

写在最后

如果在 ubuntu 的系统中, 不能直接通过 xvfb 的方式 启动unity, 必须通过 -nographics 方式:

/opt/Unity/Editor/Unity -batchmode -nographics

在镜像部署到目标机上时, 目标机不一定有当前主机上的环境, 比如说 apt-get 访问不到,因此这里需要将本地build完整的镜像导出, 然后放到目标机上:

docker save [OPTIONS] IMAGE [IMAGE...]

-o :输出到的文件。 如:

docker save -o my_ubuntu_v3.tar runoob/ubuntu:v3

再新的dockerfile里可以 在 使用From 加载进来。

清理docker 残留等:

docker system prune -a

参考

Unity 使用命令行激活

在linux下用 docker 的方式编译 Unity 并和CI进行集成

Github上RPGGameProject使用序列号激活Linux unity的例子

Running Unity 2020.1 in Docker

Unity Linux Component Download