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