Blender渲染App最佳實(shí)踐
本篇主要是介紹如何將渲染軟件 Blender 創(chuàng)建成 BatchCompute 的 App,并通過此 App 提交 Blender 渲染作業(yè)。 Blender 是目前最流行的一款開源的跨平臺(tái)全能三維動(dòng)畫制作軟件,提供從建模、動(dòng)畫、材質(zhì)、渲染、到音頻處理、視頻剪輯等一系列動(dòng)畫短片制作解決方案。 具體介紹可以看這里:https://www.blender.org/features/。
1. 準(zhǔn)備工作
(1) 開通服務(wù)
開通批量計(jì)算服務(wù)(BatchCompute): http://bestwisewords.com/document_detail/127644.html
開通對(duì)象存儲(chǔ)服務(wù)(OSS): https://oss.console.aliyun.com
開通MNS服務(wù): https://mns.console.aliyun.com
開通容器服務(wù):https://cr.console.aliyun.com
如果已經(jīng)開通,請(qǐng)忽略此步驟。
(2) 地域的選擇
本篇例子所有阿里云服務(wù)都需要使用相同的地域。
本篇例子使用地域: 華南1(深圳)
(3) 準(zhǔn)備OSS Bucket
請(qǐng)到OSS控制臺(tái)創(chuàng)建一個(gè)Bucket。
本篇例子假設(shè)創(chuàng)建的 bucket 名稱為:blender-demo
, 地域在華南1(深圳)。
注意:使用批量計(jì)算時(shí),地域需要和 OSS bucket 的地域相同。 注意:實(shí)際操作時(shí),需要將例子中的bucket 名稱修改為您自己創(chuàng)建的真實(shí)的bucket名稱。
2. 制作 Blender Docker 鏡像
(1) 創(chuàng)建一個(gè)Dockerfile文件
文件名:Dockerfile
, 內(nèi)容如下:
FROM ubuntu:latest
MAINTAINER your-name<your-email>
# 更新源
RUN apt update
# 清除緩存
RUN apt autoclean
# 安裝
RUN apt install python python-pip curl pulseaudio blender -y
# 啟動(dòng)時(shí)運(yùn)行這個(gè)命令
CMD ["/bin/bash"]
(2) build
docker build -t ubuntu-blender ./
等待完成,然后使用下面的命令查看是否有 ubuntu-blender
docker images
(3) check
docker run -t ubuntu-blender blender -v
顯示:
Blender 2.79 (sub 0)
記住此版本信息,下面要用到。
3. Docker鏡像上傳
您需要將 ubuntu-blender 上傳到 BatchCompute 支持Registry。
BatchCompute支持2種Registry:阿里云的 CR(Container Registry)和阿里云的OSS。
選擇一種即可,推薦第一種: CR。
如何上傳,請(qǐng)參考以下文檔:
假設(shè)已經(jīng)上傳到CR(地域:華南1-深圳),名稱為:registry.cn-shenzhen.aliyuncs.com/batchcompute_test/blender:1.0
4. 創(chuàng)建 App
BatchCompute 提交作業(yè),需要配置很多參數(shù)。BatchCompute 提供的 App 模板機(jī)制,讓用戶很方便預(yù)設(shè)參數(shù)默認(rèn)值,提交作業(yè)時(shí),只需填寫少量參數(shù)即可。
下面我們來創(chuàng)建一個(gè) Blender 渲染 App。
(1) 開始創(chuàng)建 App
打開批量計(jì)算控制臺(tái):https://batchcompute.console.aliyun.com
填寫基本信息
Docker 鏡像名稱,填寫您已經(jīng)上傳到CR的鏡像名稱,如:registry.cn-shenzhen.aliyuncs.com/batchcompute_test/blender:1.0
運(yùn)行時(shí)參數(shù)
只需修改實(shí)例類型為8核16GB規(guī)格,其他的默認(rèn)即可。
(2) 命令行和參數(shù)配置
命令行填寫:
python -c "import os;import sys;sys.path.append('/home/scripts/'); from framer import parseFrames; frames=parseFrames('${frames}'); framestr=','.join(map(lambda x:str(x), frames)); s='blender -b /home/input/${scene_file_path} -o /home/output/result/${output_name_format} -F ${format} -f %s' % framestr; print('exec: %s' % s); os.system(s);"
${..} 都是變量,可以作為輸入和輸出參數(shù)。在使用此App提交作業(yè)的時(shí)候,傳入的參數(shù)將替換掉這些變量。
輸入?yún)?shù)
注意:實(shí)際操作時(shí),需要將例子中的bucket 名稱修改為您自己創(chuàng)建的真實(shí)的bucket名稱。
名稱 | 默認(rèn)值 | 允許覆蓋 | 本地目錄絕對(duì)路徑 | 備注 |
scripts_oss_folder |
| 否 |
| 輸入OSS目錄路徑,該路徑將掛載到虛擬機(jī)的 |
input_oss_folder | 是 |
| 輸入OSS目錄路徑,該路徑將掛載到虛擬機(jī)的 | |
scene_file_path | 是 | 渲染場(chǎng)景文件的路徑,相對(duì)于 | ||
frames | 是 | 支持連續(xù)幀:”1-10”, 支持多幀(逗號(hào)隔開,無空格):”1,3,5-10” | ||
format | PNG | 是 | 渲染輸出格式,支持:TGA,RAWTGA,JPEG,IRIS,IRIZ,AVIRAW,AVIJPEG,PNG,BMP | |
output_name_format |
| 是 | 輸出文件名,#會(huì)被替代為幀序號(hào),不足位補(bǔ)零。舉例: |
輸出參數(shù)
名稱 | 默認(rèn)值 | 允許覆蓋 | 本地目錄絕對(duì)路徑 | 備注 |
output_oss_folder | 是 |
| 輸出OSS目錄路徑, 如: |
環(huán)境變量
環(huán)境變量可以不用配置, 直接提交即可。
4. 提交渲染作業(yè)
在App列表中可以看到已經(jīng)創(chuàng)建好的 ubuntu-blender, 點(diǎn)擊”提交作業(yè)”。
(1) 準(zhǔn)備工作
在提交作業(yè)前,還有一些準(zhǔn)備工作。
手動(dòng)上傳分幀器
分幀器python代碼(見附錄),上傳到您的OSS目錄下,比如:oss://blender-demo/scripts/framer.py
手動(dòng)上傳blender場(chǎng)景文件
Blender 官網(wǎng)提供了好多 demo 文件: https://www.blender.org/download/demo-files/
本例子需要下載 2.79 版本(注意:要和鏡像中安裝的Blender版本相同。不同版本的可能渲染不出來)
素材下載后,解壓得到目錄: splash279/
將整個(gè)目錄上傳到 oss://blender-demo/input/
下面,即:oss://blender-demo/input/splash279/
。
(2) 開始提交作業(yè)
實(shí)例類型要選大一點(diǎn)的,比如: 8核16GB。
實(shí)例數(shù)量本例子填 2 個(gè)。
(3) 參數(shù)配置
注意:實(shí)際操作時(shí),需要將例子中的 bucket 名稱修改為您自己創(chuàng)建的真實(shí)的bucket名稱。
輸入:
參數(shù) | 值 | 說明 |
input_oss_folder |
| 場(chǎng)景文件所在OSS目錄 |
scene_file_path |
| 場(chǎng)景文件名 |
frames |
| 渲染1到4幀 |
format | PNG | 渲染輸出格式,默認(rèn)即可 |
output_name_format | ####.png | 渲染輸出文件名,默認(rèn)即可 |
scripts_oss_folder 設(shè)置了默認(rèn)值,且不允許覆蓋,可以不用填。
輸出:
參數(shù) | 值 | 說明 |
input_oss_folder |
| 輸出OSS目錄, 渲染結(jié)果圖片將保存到此目錄的 |
Loggin(日志目錄配置):
參數(shù) | 值 | 說明 |
StdoutPath |
| stdout日志輸出到此 |
StderrPath |
| stderr日志輸出到此 |
填好后點(diǎn)擊提交即可。
5. 查看作業(yè)狀態(tài)和結(jié)果
(1) 查看作業(yè)狀態(tài)
(2) 查看結(jié)果
oss://blender-demo/output/result/
(3) 渲染時(shí)長(zhǎng)和實(shí)例規(guī)格參考
實(shí)例規(guī)格 | 節(jié)點(diǎn)數(shù) | 渲染幀數(shù) | 時(shí)長(zhǎng) |
ecs.sn1ne.2xlarge(8核16GB) | 2 | 1-4 | 9-12分鐘 |
ecs.sn1ne.4xlarge (16核/32GB) | 2 | 1-4 | 4-6分鐘 |
6. 附錄
分幀器代碼(python):
framer.py:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import os
import math
import sys
import re
NOTHING_TO_DO = 'Nothing to do, exit'
def _calcRange(a,b, id, step):
start = min(id * step + a, b)
end = min((id+1) * step + a-1, b)
return (start, end)
def _parseContinuedFrames(render_frames, total_nodes, id=None, return_type='list'):
'''
解析連續(xù)幀, 如:1-10
'''
[a,b]=render_frames.split('-')
a=int(a)
b=int(b)
#print(a,b)
step = int(math.ceil((b-a+1)*1.0/total_nodes))
#print('step:', step)
mod = (b-a+1) % total_nodes
#print('mod:', mod)
if mod==0 or id < mod:
(start, end) = _calcRange(a,b, id, step)
#print('--->',start, end)
return (start, end) if return_type!='list' else range(start, end+1)
else:
a1 = step * mod + a
#print('less', a1, b, id)
(start, end) = _calcRange(a1 ,b, id-mod, step-1)
#print('--->',start, end)
return (start, end) if return_type!='list' else range(start, end+1)
def _parseIntermittentFrames(render_frames, total_nodes, id=None):
'''
解析不連續(xù)幀, 如:1,3,8-10,21
'''
a1=render_frames.split(',')
a2=[]
for n in a1:
a=n.split('-')
a2.append(range(int(a[0]),int(a[1])+1) if len(a)==2 else [int(a[0])])
a3=[]
for n in a2:
a3=a3+n
#print('a3',a3)
step = int(math.ceil(len(a3)*1.0/total_nodes))
#print('step',step)
mod = len(a3) % total_nodes
#print('mod:', mod)
if mod==0 or id < mod:
(start, end) = _calcRange(0, len(a3)-1, id, step)
#print(start, end)
a4= a3[start: end+1]
#print('--->', a4)
return a4
else:
#print('less', step * mod , len(a3)-1, id)
(start, end) = _calcRange( step * mod ,len(a3)-1, id-mod, step-1)
if start > len(a3)-1:
print(NOTHING_TO_DO)
sys.exit(0)
#print(start, end)
a4= a3[start: end+1]
#print('--->', a4)
return a4
def parseFrames(render_frames, return_type='list', id=None, total_nodes=None):
'''
@param render_frames {string}: 需要渲染的總幀數(shù)列表范圍,可以用"-"表示范圍,不連續(xù)的幀可以使用","隔開, 如: 1,3,5-10
@param return_type {string}: 取值范圍[list,range]。 list樣例: [1,2,3], range樣例: (1,3)。
注意: render_frames包含","時(shí)有效,強(qiáng)制為list。
@param id, 節(jié)點(diǎn)ID,從0開始。 正式環(huán)境不要填寫,將從環(huán)境變量 BATCH_COMPUTE_DAG_INSTANCE_ID 中取得。
@param total_nodes, 總共的節(jié)點(diǎn)個(gè)數(shù)。正式環(huán)境不要填寫,將從環(huán)境變量 BATCH_COMPUTE_DAG_INSTANCE_COUNT 中取得。
'''
if id==None:
id=os.environ['BATCH_COMPUTE_DAG_INSTANCE_ID']
if type(id)==str:
id = int(id)
if total_nodes==None:
total_nodes = os.environ['BATCH_COMPUTE_DAG_INSTANCE_COUNT']
if type(total_nodes)==str:
total_nodes = int(total_nodes)
if re.match(r'^(\d+)\-(\d+)$',render_frames):
# 1-2
# continued frames
return _parseContinuedFrames(render_frames, total_nodes, id, return_type)
else:
# intermittent frames
return _parseIntermittentFrames(render_frames, total_nodes, id)