使用AE插件Pixels World 3载入一个不含材质的obj模型

Pixels World 3是中梓星音大佬(https://space.bilibili.com/564908)开发的插件,通过敲代码实现一些有趣的效果,给程序员迈向AE动态设计开辟了一条新的道路,希望大家多多支持
最近星音大大关于PW3的演示视频很有意思,展示了一些PW3的新功能,大家可以看看
https://www.bilibili.com/video/BV1Sh411R7zK
下面进入正题
警告——
在操作步骤中可能包含以下内容:



关于obj
在敲代码之前,我们先来了解一下obj文件
Obj文件可以使用记事本方式打开
在C4D中创建的正方体导出的obj文件打开后内容如下:

其中以v、vn、vt、f开头的行是我们主要要关注的部分。
v表示的是一个顶点位置信息,v后面的三个参数依次为顶点的x、y、z坐标位置。
vn表示的是一个顶点法向量,其后三个参数依次为顶点法向量的x、y、z坐标。
vt表示的是一个纹理坐标,其意义在于绘制模型的三角面片时,每个顶点取像素点时对应的纹理图片上的坐标。
f表示的是一个面片(一般为三角形面),格式一般为f v/vt/vn v/vt/vn v/vt/vn(f 顶点索引 / 纹理坐标索引 / 顶点法向量索引)。有的f后面跟有四组参数,这是四边形面的面片表示。
以上参考了:
https://blog.csdn.net/xyh930929/article/details/82260581
https://www.cnblogs.com/Anzhongliu/p/6092048.html

关于代码
因为我还不知道怎么导入纹理材质,所以只能想办法把不带材质的obj模型载入PW3。
另外我因为没有专门学习过Lua语言,所写代码的依据都来自于星音大大制作的PW3网上文档(https://milai.tech/products/PixelsWorld/docs_CN/)以及网络上零零散散的相关博客,所以代码没能写的太好,执行效率也很低下,权当做抛砖引玉吧。
在忽略了模型的材质之后,我们只需要关注的是obj文件里面的以v(顶点)和f(面片)开头的行(而在PW3的网上文档里缺少(或者是我没看懂)关于顶点法向量的描述,因此以vn开头的行也被我忽略了),而以f开头的行中也只需要关注表示顶点索引的参数(它们将表示面片的绘制顺序,在PW3的网上文档里关于poly函数的解释里有关于面片绘制顺序的说明)。
简言之,就是要取以v开头的行的后三个参数,和以f开头的行的三组(或者四组)参数中的每组第一个参数。
代码如下:
--输入的obj文件路径
path = utf8ToLocal(projectFolder)..[[cube.obj]]
-- 分割字符串
-- 参考:https://blog.csdn.net/forestsenlin/article/details/50590577
function splitStr( str,reps )
local splitList = {};
string.gsub(str,'[^'..reps..']+',function(w)
table.insert(splitList,w)
end)
return splitList
end
vertexArray = {}
faceArray = {}
-- 读入文件
local function readOBJ(fileName)
local f = assert(io.open(fileName,'r'))
local line = f:read('*line')
while line do
-- println('line content:'..line)
if string.sub(line,1,2)=="v "then -- 顶点 vertex
local tmp = splitStr(line,' ')
local p = {-tonumber(tmp[2]),-tonumber(tmp[3]),-tonumber(tmp[4])} -- x,y,z坐标要反向,然而道理我也不明白
table.insert(vertexArray,p)
end
if string.sub(line,1,2)=="f "then -- 面 face
local tmp = splitStr(line,' ')
local f1 = splitStr(tmp[2],'/')[1]
local f2 = splitStr(tmp[3],'/')[1]
local f3 = splitStr(tmp[4],'/')[1]
local f = {}
if #tmp==5 then -- 四边面的情况
local f4 = splitStr(tmp[5],'/')[1]
f = {tonumber(f1),tonumber(f2),tonumber(f3),tonumber(f4)}
elseif #tmp==4 then -- 三边面的情况
f = {tonumber(f1),tonumber(f2),tonumber(f3),tonumber(f1)}
end
table.insert(faceArray,f)
end
line = f:read('*line')
end
f:close()
end
readOBJ(path)
根据PW3网上文档中poly函数的描述,其绘制的obj格式应当如下:


为了满足这样的格式,对于含有任意数量顶点和任意数量面片绘制顺序的obj模型文件,要一行行的将每个顶点位置和每个面片绘制顺序手敲下来显得非常不现实。
我想到的解决办法是使用Lua语言创建一个具有这种格式的txt文件,然后再读入这个文件执行即可。(这个文件是不会自己删掉的,但是内容会被覆盖,有兴趣的可以打开看看)
创建文件的代码如下:
--中间文件(生成的obj代码块)存储路径
pathOBJ = utf8ToLocal(projectFolder)..[[objCode.txt]]
-- 写入
local function writeFile(fileName,content)
local f = assert(io.open(fileName,'w')) -- 如果文件不存在则创建,文件若存在则被覆盖
f:write(content)
f:close()
end
-- 追加
local function appendFile(fileName,content)
local f = assert(io.open(fileName,'a'))
f:write(content)
f:close()
end
writeFile(pathOBJ,[[
version3()
dim3()
-- normal()
anime()
getLight()
mainColor = {0.2,0.4,0.6}
obj = {
point = {]])
-- 输出顶点信息
contentForVertex = ""
for i=1,#vertexArray do
contentForVertex = contentForVertex.."{p=vertexArray["..i.."]},\n"
end
appendFile(pathOBJ,contentForVertex)
appendFile(pathOBJ,[[},
prim = {]])
-- 输出面信息
contentForFace = ""
for i=1,#faceArray do
contentForFace = contentForFace..[[{type="triangler",]].."pref=faceArray["..i.."],color=mainColor},\n"
end
appendFile(pathOBJ,contentForFace)
appendFile(pathOBJ,[[},
-- my_tex = INPUT
}
poly(obj)
]])
对于cube.obj,生成的中间文件如下:

最后只需要将这个文件(objCode.txt)读入并执行就好了:
runFile(localToUtf8(pathOBJ))
效果展示
我导入的一些obj模型:

(上面这个模型有1w+个顶点,已经很卡了,摄像机动不了了)
(这个Miku的模型我忘记是哪里来的了,如果有侵权的嫌疑,我会删除的)

(上面这个模型有接近3000个顶点,可以调整摄像机角度来看看,基本不卡)

(上面这个模型只有8个顶点,因此就非常流畅了,喜欢麻烦的读者都建议这样来创建cube,而不要使用PW3的cube函数)
在将完整的代码罗列出来之前,这里有一些要提醒读者(程序员)的事情——
代码文件的后缀为code,是我瞎写的,其实就是一个txt文件,你可以用记事本或者别的文本文件编辑软件打开;
默认所有的文件路径都是在AE工程文件即.aep文件的同级目录,也就是说请将要导入的obj文件以及这个代码文件都放在AE工程文件的旁边。如果想要修改路径还请参考PW3的网上文档(https://milai.tech/products/PixelsWorld/docs_CN/);
顶点面片多了就很卡很卡很卡,选择导入obj模型时需谨慎;
有的obj导入了但是看不见,可能是太小了,这边建议摄像机推近一点,如果因为很卡导致摄像机推不近,建议用三维软件载入obj模型然后导出的时候放大点(如果你使用的是C4D,建议导出的大小和C4D自带的人偶大小相当)(所以为什么不直接用三维软件呢???);
因为代码里面是开了anime渲染模式的,所以需要在合成里创建灯光,才能看得见你的模型,详情参见PW3网上文档关于anime渲染的描述;
我用C4D导出obj的时候导出设置里面会默认勾选反转Z轴,但是这样导出的obj在导入PW3之后是左右翻转了的,所以建议导出的时候取消勾选反转Z轴。我还不知道其他的三维软件导出obj的时候会出现什么问题;
建议在使用前清理缓存,更换obj模型导入后可能短时间无法刷新,请耐性等待,如果还是不成,建议重置插件,如果还是不行,建议重启AE,如果还是不行,建议……
如果不清楚怎么修改代码中的一些参数,建议参阅PW3网上文档(https://milai.tech/products/PixelsWorld/docs_CN/);
在PW3插件编辑面板的代码栏里,请写上(内容用黄色标出来了,其中test.code就是代码文件的名字了):

本来是抱着一种尝试和娱乐的心态来敲代码的,因此也许一点都不专业,不要太较真啊。当然有什么好玩的主意欢迎留言。
完整代码
version3()
-- background(0.8)
move(width/2,height/2)
--输入的obj文件路径
path = utf8ToLocal(projectFolder)..[[cube.obj]]
--中间文件(生成的obj代码块)存储路径
pathOBJ = utf8ToLocal(projectFolder)..[[objCode.txt]]
-- 分割字符串
-- 参考:https://blog.csdn.net/forestsenlin/article/details/50590577
function splitStr( str,reps )
local splitList = {};
string.gsub(str,'[^'..reps..']+',function(w)
table.insert(splitList,w)
end)
return splitList
end
vertexArray = {}
faceArray = {}
-- 读入文件
local function readOBJ(fileName)
local f = assert(io.open(fileName,'r'))
local line = f:read('*line')
while line do
-- println('line content:'..line)
if string.sub(line,1,2)=="v "then -- 顶点 vertex
local tmp = splitStr(line,' ')
local p = {-tonumber(tmp[2]),-tonumber(tmp[3]),-tonumber(tmp[4])} -- x,y,z坐标要反向,然而道理我也不明白
table.insert(vertexArray,p)
end
if string.sub(line,1,2)=="f "then -- 面 face
local tmp = splitStr(line,' ')
local f1 = splitStr(tmp[2],'/')[1]
local f2 = splitStr(tmp[3],'/')[1]
local f3 = splitStr(tmp[4],'/')[1]
local f = {}
if #tmp==5 then -- 四边面的情况
local f4 = splitStr(tmp[5],'/')[1]
f = {tonumber(f1),tonumber(f2),tonumber(f3),tonumber(f4)}
elseif #tmp==4 then -- 三边面的情况
f = {tonumber(f1),tonumber(f2),tonumber(f3),tonumber(f1)}
end
table.insert(faceArray,f)
end
line = f:read('*line')
end
f:close()
end
readOBJ(path)
-- 写入
local function writeFile(fileName,content)
local f = assert(io.open(fileName,'w')) -- 如果文件不存在则创建,文件若存在则被覆盖
f:write(content)
f:close()
end
-- 追加
local function appendFile(fileName,content)
local f = assert(io.open(fileName,'a'))
f:write(content)
f:close()
end
writeFile(pathOBJ,[[
version3()
dim3()
-- normal()
anime()
getLight()
mainColor = {0.2,0.4,0.6}
obj = {
point = {]])
-- 输出顶点信息
contentForVertex = ""
for i=1,#vertexArray do
contentForVertex = contentForVertex.."{p=vertexArray["..i.."]},\n"
end
appendFile(pathOBJ,contentForVertex)
appendFile(pathOBJ,[[},
prim = {]])
-- 输出面信息
contentForFace = ""
for i=1,#faceArray do
contentForFace = contentForFace..[[{type="triangler",]].."pref=faceArray["..i.."],color=mainColor},\n"
end
appendFile(pathOBJ,contentForFace)
appendFile(pathOBJ,[[},
-- my_tex = INPUT
}
poly(obj)
]])
runFile(localToUtf8(pathOBJ))
临时写的,没有仔细打草稿,可能会有些疏漏,欢迎吐槽~~
另外不知道应该投到哪个分区好 :(
上传了代码文件和obj模型(不含Miku的那个)
链接:https://pan.baidu.com/s/1nit4zYfB5JR2B10WY2Y7ig
提取码:5glb

完