UP | HOME

ArtTools

Table of Contents

记录一些美术工具的使用方法。
<!– more –>

photoshop

基本操作

工作区

   
鼠标滚轮 上下移动工作区
Ctrl+鼠标滚轮 左右移动工作区
窗口/工作区/复位基本功能 复位工作区

参考线使用

显示/隐藏 参考线

菜单 视图/显示/参考线 快捷键 Ctrl+;

查看参考线坐标信息

打开标尺线(Ctrl+r),查看参考线和标尺交点的值。

鼠标处位置、颜色信息

菜单 窗口/信息 打开,信息面板。

颜色设置

菜单 编辑/颜色设置 打开,颜色设置对话框。

Usage

将 alpha 固化到 rgb 通道

  1. 复制当前图层得到新的图层
  2. 将 alpha 通道转化为选区,反向选择
  3. Ctrl+X 裁切选中区域,隐藏 alpha 通道,就可以将 alpha 固化到 rgb 通道

存储为 TGA 格式

Script

Base

object to string
var args = dialog();
alert(args.toSource());
异常处理
try
{
    // ......
}
catch(e)
{
    alert(e);
}
GUI
hello world
#target photoshop

function main()
{
    try
    {
        var args = dialog();
        alert(args.toSource());
    }
    catch(e)
    {
        alert(e)
    }
}
main();

function dialog()
{
    var win = new Window("dialog", "HelloWorld")

    // informational text
    win.add("statictext", undefined, "test label")

    // test edit text
    var textA = win.add("edittext", undefined, "")
    textA.characters = 5;
    textA.active = true;

    var textB = win.add("edittext", undefined, "")
    textB.characters = 10;
    textB.active = true;

    // test btn
    win.add("button", undefined, "OK")
    if(win.show()==1)
    {
        return { "textA":textA.text, "textB":textB.text }
    }
}
modal dialog
#target photoshop

var GlobalVar = {
    "appVersion" : 0,
    "rLabel" : null,
    "gLabel" : null,
    "bLabel" : null,
};

function main()
{
    try
    {
        showWindow ();
    }
    catch(e)
    {
        alert(e)
    }
}
main();

function showWindow()
{
    var res = "dialog { alignChildren: 'fill', \
        text : 'Dialog',\
        info: Panel { orientation: 'column', alignChildren:'right', \
            text: 'Color Helper', \
            name: Group { orientation: 'row', \
                r: StaticText { text:'R:0' }, \
                g: StaticText { text:'G:0' }, \
                b: StaticText { text:'B:0' }, \
            } \
        }, \
        workInfo: Panel { orientation: 'column', \
            text: 'Work Info', \
            name: Group { orientation: 'row', \
                s: StaticText { text:'Company name:' }, \
                e: EditText { characters: 30 }, \
                checkBox1: Checkbox { text: 'CheckBox 1', value: true }, \
                radio1: RadioButton { text: 'RadioButton', value: true }\
            } \
        }, \
        buttons: Group { orientation: 'row', alignment: 'right', \
            cancelBtn: Button { text:'Cancel', properties:{name:'cancel'} } \
        }, \
    }";

    var win = new Window( res );
    win.center();
    GlobalVar.rLabel = win.info.name.r;
    GlobalVar.gLabel = win.info.name.g;
    GlobalVar.bLabel = win.info.name.b;

    win.buttons.cancelBtn.onClick = function()
    {
        return win.close();
    };
    win.workInfo.name.radio1.onClick = function()
    {

    };

    win.show();
    return win;
}
non modal dialog

dialog 为模态对话框
palette 为非模态对话框

palette 显示后会立即返回,因此,需要 while 循环,保持对话框不被关掉。palette 对话框中,EditText 控件不可用,需要弹出 dialog 来展示 EditText 控件。

#target photoshop

var GlobalVar = {
    "appVersion" : 0,
    "rLabel" : null,
    "gLabel" : null,
    "bLabel" : null,
    "aLabel" : null,
    "posX" : 0,
    "posY" : 0,
};

function main()
{
    try
    {
        showWindow ();
    }
    catch(e)
    {
        alert(e)
    }
}
main();

function showInputPosWindow()
{
    var res = "dialog { alignChildren: 'fill', \
        text : 'Dialog',\
        info: Panel { orientation: 'column', alignChildren:'right', \
            text: 'Input Pos', \
            pos: Group { orientation: 'row', \
                xText: StaticText { text:'X: ' }, x: EditText { text:'1', characters: 8},\
                yText: StaticText { text:'Y: ' }, y: EditText { text:'1', characters: 8},\
            }, \
        }, \
        buttons: Group { orientation: 'row', alignment: 'right', \
            okBtn: Button { text:'OK', properties:{name:'ok'} }, \
            cancelBtn: Button { text:'Cancel', properties:{name:'cancel'} } \
        }, \
    }";

    var posX = GlobalVar.posX;
    var posY = GlobalVar.posY;
    var win = new Window( res );
    win.center();
    win.buttons.okBtn.onClick = function()
    {
        GlobalVar.posX = posX;
        GlobalVar.posY = posY;
        win.close();
    };

    win.info.pos.x.text = posX;
    win.info.pos.x.text = posY;
    win.info.pos.x.onChange = function()
    {
        posX = Number(win.info.pos.x.text)
        if(isNaN (GlobalVar.posX))
        {
            posX = 0;
            win.info.pos.x.text = '0';
        }
    };
    win.info.pos.y.onChange = function()
    {
        posY = Number(win.info.pos.y.text)
        if(isNaN (posY))
        {
            posY = 0;
            win.info.pos.y.text = '0';
        }
    };
    win.show();
}

function showWindow()
{
    var res = "palette { alignChildren: 'fill', \
        text : 'Dialog',\
        info: Panel { orientation: 'column', alignChildren:'right', \
            text: 'Color Helper', \
            pos: Group { orientation: 'row', \
                x: StaticText { text:'X:      ', preferredSize:[60,18]}, \
                y: StaticText { text:'Y:      ', preferredSize:[60,18]}, \
            }, \
            rgbValue : Group { orientation: 'row', \
                r: StaticText { text:'R:     0', preferredSize:[60,18]}, \
                g: StaticText { text:'G:     0', preferredSize:[60,18]}, \
                b: StaticText { text:'B:     0', preferredSize:[60,18]}, \
                a: StaticText { text:'A:     0', preferredSize:[60,18]}, \
            } \
        }, \
        buttons: Group { orientation: 'row', alignment: 'right', \
            inputPosBtn: Button { text:'Input Pos', properties:{name:'ok'} }, \
            cancelBtn: Button { text:'Cancel', properties:{name:'cancel'} } \
        }, \
    }";

    var win = new Window( res );
    win.center();
    GlobalVar.rLabel = win.info.rgbValue.r;
    GlobalVar.gLabel = win.info.rgbValue.g;
    GlobalVar.bLabel = win.info.rgbValue.b;
    GlobalVar.aLabel = win.info.rgbValue.a;

    var isClosed = false;
    win.buttons.inputPosBtn.onClick = function()
    {
        showInputPosWindow();
        RefreshRGB();
    };
    win.buttons.cancelBtn.onClick = function()
    {
        isClosed = true;
    };

    function RefreshPosUI()
    {
        win.info.pos.x.text = "X: " + GlobalVar.posX;
        win.info.pos.y.text = "Y: " + GlobalVar.posY;
    }

    function RefreshRGB()
    {
        RefreshPosUI();

        var pointSample = app.activeDocument.colorSamplers.add([GlobalVar.posX, GlobalVar.posY]);
        GlobalVar.rLabel.text = "R:" + pointSample.color.rgb.red;
        GlobalVar.gLabel.text = "G:" + pointSample.color.rgb.green;
        GlobalVar.bLabel.text = "B:" + pointSample.color.rgb.blue;
        var alpha = 0;
        if(activeDocument!=null && activeDocument.activeLayer!=null)
        {
            alpha = activeDocument.activeLayer.opacity;
        }
        GlobalVar.aLabel.text = "A:" + alpha;
        pointSample.remove();
    }

    RefreshRGB();
    win.show();

    while(!isClosed)
    {
        app.refresh();
    }
}

UseCase

File & Directory
选择目录
var outFolder = Folder(oldPath).selectDlg("Select Export Path");
if (!outFolder.exists) {
    outFolder.create();
}
遍历 layer groups,layers
遍历 layer groups,处理 layer group 内的每个 layer
for(var a=0; a<doc.layerSets.length; a++)
{
    var lname = doc.layerSets[a].name;
    if (lname.substr(-4) == ".png")
    {
        saveLayer(doc.layers.getByName(lname), lname, oldPath, true);
    }
    else
    {
        // recursive
        scanLayerSets(doc.layerSets[a]);
    }
}
遍历不属于 layer groups 内的 layers
for(var j=0; j<doc.artLayers.length; j++)
{
    var name = doc.artLayers[j].name;
    if (name.substr(-4) == ".png")
    {
        saveLayer(el.layers.getByName(name), name, oldPath, false);
    }
}
遍历所有 layers
for (i = 0; i < doc.layers.length; i++)
{
    if (doc.layers[i].visible)
    {
        foundVisible = true
        break
    }
}
录制 Action 为 Script
ScriptingListener 插件安装
  1. 关闭 PS
  2. 下载 ScriptingListener 插件,并解压 https://download.adobe.com/pub/adobe/photoshop/win/13.x/Win_Scripting_Plug-In.zip
  3. 将 Scripting Utilites 目录下的 ScriptListener.8li 文件 Copy 到下面路径
    macOS: Applications\Adobe Photoshop [Photoshop_version]\Plug-ins\
    Windows: Program Files\Adobe\Adobe Photoshop [Photoshop_version]\Plug-ins\
  4. 重启 PS

插件下载地址 https://helpx.adobe.com/photoshop/kb/downloadable-plugins-and-content.html#ScriptingListenerplugin
官网说明文档 https://github.com/Adobe-CEP/CEP-Resources/blob/master/Documentation/Product%20specific%20Documentation/Photoshop%20Scripting/photoshop-scripting-guide-2020.pdf

ScriptingListener 插件使用

安装 ScriptingListener 插件后,在 PS 中的操作会被录制到桌面的日志文件中。文件名为 ScriptingListenerJS.log 和 ScriptingListenerVB.log

// =======================================================
// 对图片进行顺时针旋转 90
var idRtte = charIDToTypeID( "Rtte" );
var desc315 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref7 = new ActionReference();
var idDcmn = charIDToTypeID( "Dcmn" );
var idOrdn = charIDToTypeID( "Ordn" );
var idFrst = charIDToTypeID( "Frst" );
ref7.putEnumerated( idDcmn, idOrdn, idFrst );
desc315.putReference( idnull, ref7 );
var idAngl = charIDToTypeID( "Angl" );
var idAng = charIDToTypeID( "#Ang" );
desc315.putUnitDouble( idAngl, idAng, 90.000000 );
executeAction( idRtte, desc315, DialogModes.NO );

// =======================================================
// 选中 ps-test1 layer
var idslct = charIDToTypeID( "slct" );
var desc325 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref8 = new ActionReference();
var idLyr = charIDToTypeID( "Lyr " );
ref8.putName( idLyr, "ps-test1" );
desc325.putReference( idnull, ref8 );
var idMkVs = charIDToTypeID( "MkVs" );
desc325.putBoolean( idMkVs, false );
var idLyrI = charIDToTypeID( "LyrI" );
var list8 = new ActionList();
list8.putInteger( 3 );
desc325.putList( idLyrI, list8 );
executeAction( idslct, desc325, DialogModes.NO );


// 将当前激活的layer duplicate 到新的文档中,并激活新的文档
// =======================================================
var idMk = charIDToTypeID( "Mk  " );
var desc253 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref4 = new ActionReference();
var idDcmn = charIDToTypeID( "Dcmn" );
ref4.putClass( idDcmn );
desc253.putReference( idnull, ref4 );
var idNm = charIDToTypeID( "Nm  " );
desc253.putString( idNm, """new-ps-test1""" );
var idUsng = charIDToTypeID( "Usng" );
var ref5 = new ActionReference();
var idLyr = charIDToTypeID( "Lyr " );
var idOrdn = charIDToTypeID( "Ordn" );
var idTrgt = charIDToTypeID( "Trgt" );
ref5.putEnumerated( idLyr, idOrdn, idTrgt );
desc253.putReference( idUsng, ref5 );
var idVrsn = charIDToTypeID( "Vrsn" );
desc253.putInteger( idVrsn, 5 );
executeAction( idMk, desc253, DialogModes.NO );
导出 Layers 为 PNG
#target photoshop

function main(){
    if(!documents.length) return;
    var doc = activeDocument;
    var oldPath = activeDocument.path;

    var outFolder = Folder(oldPath).selectDlg("Select Export Path");
    if(outFolder==null)
    {
        alert("Cancle Export Operation!");
        return;
    }

    for (i = 0; i < doc.layers.length; i++)
    {
        if (doc.layers[i].visible)
        {
            var name = doc.layers[i].name;
            saveLayer(doc.layers.getByName(name), name, oldPath, false);
        }
    }

    function saveLayer(layer, lname, path, shouldMerge) {
        activeDocument.activeLayer = layer;
        duplayer();

        activeDocument.trim(TrimType.TRANSPARENT, true, true, true, true);
        var saveFile= File(path +"/out/"+lname+".png");
        SavePNG(saveFile);
        app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
    }
};

function duplayer()
{
    var idMk = charIDToTypeID( "Mk  " );
    var desc253 = new ActionDescriptor();
    var idnull = charIDToTypeID( "null" );
    var ref4 = new ActionReference();
    var idDcmn = charIDToTypeID( "Dcmn" );
    ref4.putClass( idDcmn );
    desc253.putReference( idnull, ref4 );
    var idNm = charIDToTypeID( "Nm  " );
    desc253.putString( idNm, activeDocument.activeLayer.name);
    var idUsng = charIDToTypeID( "Usng" );
    var ref5 = new ActionReference();
    var idLyr = charIDToTypeID( "Lyr " );
    var idOrdn = charIDToTypeID( "Ordn" );
    var idTrgt = charIDToTypeID( "Trgt" );
    ref5.putEnumerated( idLyr, idOrdn, idTrgt );
    desc253.putReference( idUsng, ref5 );
    var idVrsn = charIDToTypeID( "Vrsn" );
    desc253.putInteger( idVrsn, 5 );
    executeAction( idMk, desc253, DialogModes.NO );
};

main();

function SavePNG(saveFile){
    var pngOpts = new ExportOptionsSaveForWeb;
    pngOpts.format = SaveDocumentType.PNG
    pngOpts.PNG8 = false;
    pngOpts.transparency = true;
    pngOpts.interlaced = false;
    pngOpts.quality = 100;
    activeDocument.exportDocument(new File(saveFile),ExportType.SAVEFORWEB,pngOpts);
}

GIMP

Base

显示鼠标信息面板

Windows/Dockable Dialogs/Pointer

添加 Alpha 通道

右键点击图层,选择添加 Alpha 通道

020120_gimp_add_alpha.png

填充 Alpha 通道

  1. 选择要填充的区域
  2. 右键点击图层,选择 Add Layer Mask, 弹框中选择 Layer's alpha channel
  3. 使用 Bucket Fill Tool 进行填充
  4. 右键点击图层,选择 Apply Layer Mask

Usage

为图片中物体添加描边

径向模糊

Filters"->"Blur"->"Zoom Motion blur"

3ds max

基本操作

快捷键 功能
按下鼠标滚轮拖动 拖动视图
Alt+按下鼠标滚轮拖动 旋转视图
Alt+w 最大化、最小化视图切换
z 重置视角, 也可以点击右上角方向指示图标,再点击图标左上位置的小图标
g 显示、隐藏场景中网格线
h 按名称选择物体
l t f u p 左-顶-前-正交-透视视图切换
F3 线框模式显示切换
F4 EdgedFaces 模式显示切换
按下 Shift 移动选中物体 复制选中物体
Shift +g 隐藏几何体,只显示骨骼
显示 Scene Explorer

方法 1:Main toolbar > Toggle Scene Explorer
方法 2:Menu bar > Tool Menu > Scene Explorer

查看模型使用的材质球

菜单 Rendering/Material Explorer

查看骨骼层级结构

方法 1:Main toolbar > Schematic View
方法 2:Menu bar > Graph Editors menu > New Schematic View

查看修改顶点颜色

ModifierList / VertexPaint

按照顶点颜色显示模型
  • Step1 Display / Display Properties/ Vertex Colors
  • Step2 Display / Display Color/ Shaded - Object Color
  • Step3 场景显示模式切换为 Shaded
如何查看骨骼的绑定权重?
查看权重分布
  • Step0 选中蒙皮的模型
  • Step1 选中 Modify 选项卡
  • Step2 在 Modify Stack 中展开 Skin,选中 Envelope
  • Step3 在 Parameters 中的 Bones 列表中选择需要查看的骨骼

建模

Q&A

如何创建一个地球模型,并赋予贴图?
TODO 一个角色如何使用多个材质球?
如何查看模型的 UV 展开?
  • 选中模型
  • 选中修改选项卡 (Modify)
  • 在 Modify List 中选择 Unwrap UVW
  • 在 EditUVs 选项组中,点击 Open UV Editor

动画

基础知识

关键帧类型 颜色 功能
Planted Key (踩踏/固定关键帧) 橙色 站军姿 等动作,为保持一动不动时使用
Sliding Key(滑动关键点) 黄色 走路、跑步等运动中任何一直接触地面的部位都用滑动关键帧
Free Key(自由关键帧) 黑色 运动中不会一直接触地面的部位都用自由关键帧

骨骼绑定步骤

分析模型

性别 女
个性 豪放
职业 坦克(肉盾) 保护队友 控制
形体 人形 两足(无残缺)

分析模型需要活动的部位和道具
CS 人形骨骼自带




躯干

Bones 骨骼

盾牌
武器
头发
飘带(衣服的裙摆)

骨骼配置
  1. 确定骨骼重心 与模型重心吻合
  2. 配置腰或者腿骨骼 让骨骼关节和模型关节吻合
  3. 根据模型布线来确定躯干
  4. 确定肩 骨骼肩膀末端(靠近胸的一端)的旋转点要在模型锁骨位置,前端与模型大臂相吻合
  5. 配置手臂,手,手指
  6. 配置头

MaxScript

基础语法

if
-- if
if a<b do
(
	-- ...
)

-- if then else
if a<b then
(
	-- ...
)
else
(
	-- ...
)
for 循环
方式一
for i= 1 to tagInfoArr.count do
(
    local tagID = GetIDByName tagInfoArr[i].name
    if tagID==-1 do
    (
        tagID = FrameTagManager.CreateNewTag tagInfoArr[i].name 0
    )
    tagInfoArr[i].id = tagID
)
方式二
for i in 1 to tagInfoArr.count by 2 do
(
    local startTime = FrameTagManager.GetTimeByID tagInfoArr[i].id
    local endTime = FrameTagManager.GetTimeByID tagInfoArr[i+1].id
    local dur = endTime - startTime
    format "i=% dur=%\n" i dur
    if dur>0 do
    (
        configStr = "\n"
        continue
    )
)
string
-- 字符串转 float
"12.1" as float
-- 字符串替换
substituteString srcStr oldStr newStr
local csvFileName = substituteString maxFileName ".max" ".csv"
-- 字符串查找
local tmpIdx = findString tagName endTagNameSuffix
return tmpIdx!=undefined

http://docs.autodesk.com/3DSMAX/16/ENU/MAXScript-Help/index.html?url=files/GUID-A6A60FC7-6206-4FFC-80E2-0EF8544BE2C4.htm,topicNumber=d30e158996

数组
-- 定义数组
a = #(1,2,3,4)
-- 数组连接
join a #(5,6,7,8)
-- 打印数组元素个数
print a.count
-- 数组排序
fn CompareFunc lhs rhs =
(
    case of
    (
        (lhs.startTime < rhs.startTime):-1
        (lhs.startTime > rhs.startTime):1
        default:0
    )
)
qsort tagInfoArr CompareFunc
结构体
-- 定义结构体 person
Struct person (name, height, age, sex)

-- 定义结构体实例
Bill = person name:"Bill" height:72 age:34 sex:#male
Joe = person name:"Joseph" sex:#male

-- 封装方法
struct myGlobals
(
	myVar1 = undefined,
	myVar2 = undefined,
	myVar3 = undefined,
	mytext = undefined,

	fn run filename =
	(
		mytext = "I'm yours, master!"
		filein filename
	)
)
global myInstance = myGlobals()
myInstance.run "slave.ms"

各种功能

读写配置文件
fn SetBoolINISetting key value=
(
-- 		print ("--" + value)
    setINISetting hdxINIFile hdxINISection key value
),

fn GetBoolINISetting key=
(
    local value = getINISetting hdxINIFile hdxINISection key
-- 		print ("--" + value)
    return value=="true"
),
遍历

Tips

print 比较诡异
print "hello ->" + "world"
-- output: 输出两次
-- "hello ->"
-- "hello ->world"

print("hello ->" + "world")
-- output: 输出两次
-- "hello ->world"
-- "hello ->world"
maxscript 变量不分大小写
-- 下面会重名
local tmpIdx = 1
local TmpIdx = 2
删除老的 macroscript

删除#userScripts 目录下的脚本即可。
#userScripts 的值为 "C:\Users\<UserName>\AppData\Local\Autodesk\3dsMax\<ReleaseNumber> - 64bit\ENU\scripts"
https://knowledge.autodesk.com/search-result/caas/CloudHelp/cloudhelp/2015/ENU/MAXScript-Help/files/GUID-F7577416-051E-478C-BB5D-81243BAAC8EC-htm.html

maxscript –Type error: Call needs function or class, got:undefined

安装 maxscript 后,重启 3ds max,执行安装的脚本,报错:maxscript –Type error: Call needs function or class, got:undefined
解决方案:
将脚本放到 C:\Program Files\Autodesk\3ds Max 2014\stdplugs\stdscripts 目录下安装就可以了

– No ""eof"" function for undefined <<

打开文件时报如上错误。
解决方案:
查看被打开的文件是否被其他程序打开了,将其他程序关闭再执行。

Runtime error: Cannot directly set animation context time - use "at time…".
-- 不能直接设置 currentTime
currentTime = 10
-- 需要使用 sliderTime
sliderTime = 10

ERROR

Tools