チュートリアル - ビットマップ ペイント ツールの開発 - UV 座標のアンラップ

チュートリアル > ビットマップ ペイント ツールの開発 - UV 座標のアンラップ

このビットマップ ペイント ツール開発の手順では、ペイント ツール内にあるオブジェクトの UV 座標のグラフィック表示を生成するオプションを追加し、それを拡散反射光マップとしてシーン オブジェクトに自動的に割り当てます。

全体の流れ:

作業対象のメッシュ オブジェクトを格納するためのローカル変数を追加します。

メッシュ オブジェクトだけを選択するための新しいフィルタ関数を追加します。

作業対象オブジェクトを選択するための新しい pickbutton を UI に追加します。 この pickbutton でフィルタ関数を使用します。

テクスチャ座標を取得するためのオプションを[編集](Edit)メニューに追加します。

選択したメッシュから UV 座標を読み込み、テクスチャ空間の頂点位置に基づいてストロークを描画する、新しい関数を追加します。

ペイントの既定の一時ファイル名を定義し、オプションでそれを各ストロークの後に保存します。

ビットマップの一時ディスク コピーを、選択したオブジェクトの拡散反射光チャネルに割り当てます。

ユーザ インタフェースに、自動保存の有効と無効を切り替えるための新しいチェックボックスを追加します。有効にすると、ビットマップはツールでペイントが行われるのに並行してリアルタイムで更新されます。

スクリプト:

macroScript MicroPaint category: "HowTo"
(
global MicroPaint_CanvasRollout
try (destroyDialog MicroPaint_CanvasRollout) catch()
local isErasing = isDrawing = false
local bitmapX = bitmapY = 512
local bitmapx_1 = bitmapx-1
local bitmapy_1 = bitmapy-1
local temp_bitmap_filename = (getDir #preview +"/microPaint_temp.bmp")
local theCanvasBitmap = bitmap bitmapX bitmapY color:white filename:temp_bitmap_filename
local theBackgroundBitmap = bitmap bitmapX bitmapY color:white
local currentPos = lastPos = [0,0]
 
local theChannel = 1
local theObj = undefined
 
rcMenu CanvasMenu
(
  subMenu "File"
  (
    menuItem new_menu "New"
    menuItem open_menu "Open..."
    menuItem save_as "Save As..."
    separator file_menu_1
    menuItem quit_tool "Quit"  
  )
  subMenu "Edit"
  (
    menuItem commit_menu "Commit Changes"
    separator edit_menu_1
    menuItem uv_menu "Get UV Coordinates..."
  )
  on commit_menu picked do copy theCanvasBitmap theBackgroundBitmap
  on uv_menu picked do MicroPaint_CanvasRollout.unwrapTexture()
 
  subMenu "Help"
  (
    menuItem about_tool "About MicroPaint..." 
  )
 
  on new_menu picked do
  (
    theBackgroundBitmap = theCanvasBitmap = bitmap bitmapX bitmapY color:MicroPaint_CanvasRollout.paperColor.color filename:temp_bitmap_filename
    MicroPaint_CanvasRollout.theCanvas.bitmap = theCanvasBitmap
  )
 
  on open_menu picked do
  (
    theOpenBitmap= selectBitmap()
    if theOpenBitmap != undefined do
    (
      copy theOpenBitmap theCanvasBitmap
      copy theOpenBitmap theBackgroundBitmap
      close theOpenBitmap
      MicroPaint_CanvasRollout.theCanvas.bitmap = theCanvasBitmap
    )
  )
 
  on save_as picked do
  (
    theSaveName = getSaveFileName types:"BMP (*.bmp)|*.bmp|Targa (*.tga)|*.tga|JPEG (*.jpg)|*.jpg"
    if theSaveName != undefined do
    (
      theCanvasBitmap.filename = theSaveName
      save theCanvasBitmap
      theCanvasBitmap.filename = temp_bitmap_filename
    )
  )
  on about_tool picked do messagebox "MicroPaint\nMAXScript Tutorial" title:"About..."
  on quit_tool picked do destroyDialog MicroPaint_CanvasRollout
)
 
fn mesh_filter obj = superclassof obj == GeometryClass and classof obj != TargetObject
 
rollout MicroPaint_CanvasRollout "MicroPaint"
(
  bitmap theCanvas pos:[0,0] width:bitmapX height:bitmapY bitmap:theCanvasBitmap
  colorpicker inkColor height:16 modal:false color:black across:6
  colorpicker paperColor height:16 modal:false color:white
  checkbutton autoSave "AutoSave" width:70
  checkbutton airBrush "AirBrush" width:70
  spinner AirBrushSpeed "Speed" range:[0.1,50,10] fieldwidth:30
  spinner BrushSize "Size" range:[1,50,1] type:#integer fieldwidth:40
  listbox BrushShape items:#("Circle","Box","Circle Smooth") pos:[bitmapX+5,0] width:90
 
 
  --NEW PICK BUTTON AND HANDLER:
  pickbutton pickMesh "Pick Mesh" width:90 height:30 pos:[bitmapX+5,140] filter:mesh_filter autodisplay:true
 
  on pickMesh picked obj do
  (
    if obj != undefined do
    (
      theObj = Obj
      try
      (
        copy theObj.material.diffusemap.bitmap theCanvasBitmap
        copy theObj.material.diffusemap.bitmap theBackgroundBitmap
        theCanvas.bitmap = theCanvasBitmap
      )catch() 
    )
  )
  --END NEW PICK BUTTON AND HANDLER
 
  fn paintBrush pos =
  (
    if isErasing then
      thePaintColor = (getPixels theBackgroundBitmap pos 1)[1]
    else
      thePaintColor = inkColor.color
    if thePaintColor == undefined then thePaintColor = white
 
    case BrushShape.selection of
    (
      1: (
        if distance pos currentPos <= BrushSize.value/2 do
          setPixels theCanvasBitmap pos #(thePaintColor)
        )
      2: setPixels theCanvasBitmap pos #(thePaintColor)
      3: (
        theFactor = (distance pos currentPos) / (BrushSize.value/2.0)
        if theFactor <= 1.0 do
        (
          theFactor = sin ( 90.0 * theFactor)
          thePixels = getPixels theCanvasBitmap pos 1
          if thePixels[1] != undefined do
          (
            thePixels[1] = (thePixels[1] * theFactor) + (thePaintColor * (1.0 - theFactor))
            setPixels theCanvasBitmap pos thePixels
          )
        )
      )--end case 3
    )--end case
  )--end fn
 
  fn drawStroke lastPos pos drawIt: =
  (
    currentPos = lastPos
    deltaX = pos.x - lastPos.x
    deltaY = pos.y - lastPos.y
    maxSteps = amax #(abs(deltaX),abs(deltaY))
    deltaStepX = deltaX / maxSteps
    deltaStepY = deltaY / maxSteps
    for i = 0 to maxSteps do
    (
      if airBrush.checked then
        for b = 1 to (BrushSize.value / AirBrushSpeed.value) do
          paintBrush (currentPos + (random [-BrushSize.value/2,-BrushSize.value/2] [BrushSize.value/2,BrushSize.value/2] ))
      else
        for b = -BrushSize.value/2 to BrushSize.value/2 do
          for c = -BrushSize.value/2 to BrushSize.value/2 do
            paintBrush (currentPos + [c,b])
      currentPos += [deltaStepX, deltaStepY]
    )
    if drawIt== true or drawIt == unsupplied do theCanvas.bitmap = theCanvasBitmap
  )
 
 
  --NEW UNWRAP TEXTURE FUNCTION STARTS HERE
  fn unwrapTexture =
  (
    if theObj != undefined then
    (
      theMesh = snapshotAsMesh theObj
      if meshop.getMapSupport theMesh theChannel do
      (
        faceCount = meshop.getNumMapFaces theMesh theChannel
        for f = 1 to faceCount do
        (
          theFace = meshop.getMapFace theMesh theChannel f
          vert1= meshop.getMapVert theMesh theChannel theFace.x
          vert2= meshop.getMapVert theMesh theChannel theFace.y
          vert3= meshop.getMapVert theMesh theChannel theFace.z
          drawStroke [vert1.x * bitmapx_1, bitmapy_1 - vert1.y * bitmapy_1] [vert2.x * bitmapx_1, bitmapy_1 - vert2.y * bitmapy_1] drawIt:false
          drawStroke [vert1.x * bitmapx_1, bitmapy_1 - vert1.y * bitmapy_1] [vert3.x * bitmapx_1, bitmapy_1 - vert3.y * bitmapy_1] drawIt:false
          drawStroke [vert3.x * bitmapx_1, bitmapy_1 - vert3.y * bitmapy_1] [vert2.x * bitmapx, bitmapy_1 - vert2.y * bitmapy_1] drawIt:false
        )
      ) 
      theCanvas.bitmap = theCanvasBitmap
      save theCanvasBitmap
      if theObj.material == undefined do theObj.material = Standard()
      if theObj.material.diffusemap == undefined do
        theObj.material.diffusemap = bitmapTexture filename:temp_bitmap_filename
      showTextureMap theObj.material true
      autoSave.checked = true
    )
  )
  --NEW UNWRAP TEXTURE FUNCTION ENDS HERE
 
  on autoSave changed state do if state do save theCanvasBitmap
 
  on MicroPaint_CanvasRollout lbuttondown pos do
  (
    lastPos = pos
    isDrawing = true
    isErasing = false
    drawStroke lastPos pos
  )
  on MicroPaint_CanvasRollout rbuttondown pos do
  (
    lastPos = pos
    isErasing = isDrawing = true
    drawStroke lastPos pos
  )
  on MicroPaint_CanvasRollout lbuttonup pos do
  (
    isErasing = isDrawing = false
    if autoSave.checked do save theCanvasBitmap
  )
  on MicroPaint_CanvasRollout rbuttonup pos do
  (
    isErasing = isDrawing = false
    if autoSave.checked do save theCanvasBitmap
  )
 
  on MicroPaint_CanvasRollout mousemove pos do
  (
    if isDrawing do drawStroke lastPos pos
    lastPos = pos
  )
)
createDialog MicroPaint_CanvasRollout (bitmapx+100) (bitmapy+30) menu:CanvasMenu
MicroPaint_CanvasRollout.theCanvas.bitmap = theBackgroundBitmap
)

ステップごとの解説

--Code in italic has no changes since the previous version. macroScript MicroPaint category:"HowTo"
(
global MicroPaint_CanvasRollout
try(destroyDialog MicroPaint_CanvasRollout)catch()
local isErasing = isDrawing = false
local bitmapX = bitmapY = 512
 
local bitmapx_1 = bitmapx-1
local bitmapy_1 = bitmapy-1

ビットマップ内の UV 座標の位置を計算するには、ピクセルをビットマップのサイズ - 1 としてカウントする必要があります (たとえば 1 ~ 512 ではなく、0 ~ 511)。簡単にするために、これらの正しい値を格納する変数を 2 つ定義します。

local temp_bitmap_filename = (getDir #preview +"/microPaint_temp.tga")
local theCanvasBitmap = bitmap bitmapX bitmapY color:white filename:temp_bitmap_filename

現在のペイントをディスク上の一時ファイルに保存できるようにするには、ファイル名を定義してビットマップに割り当てる必要があります。ファイル名はローカル変数に格納され、スクリプトの他の箇所でそのファイルにアクセスする場合にはそれが使用されます。BMP よりは TGA で保存した方が速くできるようですが、保存はどの形式で行ってもかまいません。

local theBackgroundBitmap = bitmap bitmapX bitmapY color:white
local currentPos = lastPos = [0,0] localtheChannel = 1

読み込み対象のマップ チャネルです。この値をコントロールするための、ユーザ インタフェース スピナーを追加することができます。この時点ではチャンネル 1 のみを扱います。

local theObj = undefined

この変数は、UV 座標にアクセスするメッシュ オブジェクトを格納します。

rcMenu CanvasMenu
(
subMenu "File"
(
menuItem new_menu "New"
menuItem open_menu "Open..."
menuItem save_as "Save As..."
separator file_menu_1
menuItem quit_tool "Quit"  
)
subMenu "Edit"
(
menuItem commit_menu "Commit Changes"
 
separator edit_menu_1
menuItem uv_menu "Get UV Coordinates..."
)

シーン オブジェクトからテクスチャ座標を取得するために使用する、新しいメニュー項目を追加します。

on commit_menu picked do copy theCanvasBitmap theBackgroundBitmap on uv_menu picked do MicroPaint_CanvasRollout.unwrapTexture()

ユーザがこのメニュー項目を選択した場合、新しい関数を呼び出します。新しい関数はロールアウト内に定義します(下記参照)。

subMenu "Help"
(
menuItem about_tool "About MicroPaint..." 
)

on new_menu picked do
(
theBackgroundBitmap = theCanvasBitmap = bitmap bitmapX bitmapY color:MicroPaint_CanvasRollout.paperColor.color filename:temp_bitmap_filename
MicroPaint_CanvasRollout.theCanvas.bitmap = theCanvasBitmap
)

on open_menu picked do
(
theOpenBitmap= selectBitmap()
if theOpenBitmap != undefined do
(
copy theOpenBitmap theCanvasBitmap
copy theOpenBitmap theBackgroundBitmap
close theOpenBitmap
MicroPaint_CanvasRollout.theCanvas.bitmap = theCanvasBitmap
)
)

on save_as picked do
(
theSaveName = getSaveFileName types:"BMP (*.bmp)|*.bmp|Targa (*.tga)|*.tga|JPEG (*.jpg)|*.jpg"
if theSaveName != undefined do
(
theCanvasBitmap.filename = theSaveName
save theCanvasBitmap theCanvasBitmap.filename = temp_bitmap_filename

新しいファイル名で保存した後、ファイル名を元の一時的な場所に戻します。こうしないと、自動保存により新しいファイルに書き出しが行われてしまいます。

)
)
on about_tool picked do messagebox "MicroPaint\nMAXScript Tutorial" title:"About..."
on quit_tool picked do destroyDialog MicroPaint_CanvasRollout
)
 
fn mesh_filter obj = superclassof obj == GeometryClass and classof obj != TargetObject

この関数により、ジオメトリ オブジェクトだけが選択できるようになります。 ただし、ターゲット オブジェクトは選択できません。この関数は新しいユーザ インタフェース コントロールである pickbutton でフィルタ関数として使用されます。

rollout MicroPaint_CanvasRollout "MicroPaint"
(
bitmap theCanvas pos:[0,0] width:bitmapX height:bitmapY bitmap:theCanvasBitmap
 
colorpicker inkColor height:16 modal:false color:black across:6

新しい自動保存オプションが収まるように、across パラメータを 6 に増やします。

colorpicker paperColor height:16 modal:false color:white
 
checkbutton autoSave "AutoSave" width:70

この新しいチェックボタンでは、ペイントが各ストロークの後で常にディスクに保存されるかどうかをコントロールします。

checkbutton airBrush "AirBrush" width:70
spinner AirBrushSpeed "Speed" range:[0.1,50,10] fieldwidth:30 spinner BrushSize "Size" range:[1,50,1] type:#integer fieldwidth:40

既定のブラシ サイズは 1 になりました。10 の厚さでアンラップしたテクスチャの描画を避けるためです。10 の厚さではテクスチャ アンラップの描画にとても時間がかかる可能性があります。

listbox BrushShape items:#("Circle", "Box", "Circle Smooth") pos:[bitmapX+5,0] width:90
 
pickbutton pickMesh "Pick Mesh" width:90 height:30 pos:[bitmapX+5,140] filter:mesh_filter autodisplay:true

この pickbutton は、テクスチャ座標をアンラップするシーン オブジェクトを選択するときに使用するものです。3ds Max 7 で追加された新しいオプションのキーワード、autoDisplay を使用します。true に設定されると、選択されたオブジェクトの名前が自動的にボタンに表示されます。3ds Max 6 以前のリリースでは、このオプションは無視されますが、それでもスクリプトは正しく機能します。

Pickbutton

on pickMesh picked obj do
(

これは、pickbutton のイベント ハンドラです。ユーザがオブジェクトを選択した場合、obj 変数のハンドラに渡されます。

if obj != undefined do (

obj 変数に有効な値が入っている場合(ユーザが選択をキャンセルしなかった場合)、

theObj = Obj

まず選択オブジェクトを新しい変数に割り当てます。この変数はペイント セッションの間、選択を保存します。

try
(
copy theObj.material.diffusemap.bitmap theCanvasBitmap
copy theObj.material.diffusemap.bitmap theBackgroundBitmap
theCanvas.bitmap = theCanvasBitmap

次に、オブジェクトの diffusemap ビットマップを読み込み、それをペイント ツールのキャンバスおよびバックグラウンドのビットマップにコピーして UI に割り当てます。

ビットマップがない場合、エラーはトラップされ何も起こりません。

)catch() 
)
)
 
fn paintBrush pos =
(
if isErasing then
thePaintColor = (getPixels theBackgroundBitmap pos 1)[1]
else
thePaintColor = inkColor.color
if thePaintColor == undefined then thePaintColor = white

case BrushShape.selection of
(
1: (
if distance pos currentPos <= BrushSize.value/2 do
setPixels theCanvasBitmap pos #(thePaintColor)
)
2: setPixels theCanvasBitmap pos #(thePaintColor)
3: (
theFactor = (distance pos currentPos) / (BrushSize.value/2.0)
if theFactor <= 1.0 do
(
theFactor = sin ( 90.0 * theFactor)
thePixels = getPixels theCanvasBitmap pos 1
if thePixels[1] != undefined do
(
thePixels[1] = (thePixels[1] * theFactor) + (thePaintColor * (1.0 - theFactor))
setPixels theCanvasBitmap pos thePixels
)
)
)--end case 3
)--end case
)--end fn
  fn drawStroke lastPos pos drawIt: =
(

drawStroke 関数に新しいオプションのキーワードを追加します。このキーワードが false に設定された場合、ストロークは描画されますが、ロールアウト内のビットマップは更新されません。このキーワードが true の場合、またはこのキーワードを省略した場合は、ビットマップはストロークの後に更新されます。これは、テクスチャ面ごとにビューを更新せずに、アンラップされたテクスチャをより速く描画できるようにするためです。

currentPos = lastPos
deltaX = pos.x - lastPos.x
deltaY = pos.y - lastPos.y
maxSteps = amax #(abs(deltaX),abs(deltaY))
deltaStepX = deltaX / maxSteps
deltaStepY = deltaY / maxSteps
for i = 0 to maxSteps do
(
if airBrush.checked then
for b = 1 to (BrushSize.value / AirBrushSpeed.value) do
paintBrush (currentPos + (random [-BrushSize.value/2,-BrushSize.value/2] [BrushSize.value/2,BrushSize.value/2] ))
else
for b = -BrushSize.value/2 to BrushSize.value/2 do
for c = -BrushSize.value/2 to BrushSize.value/2 do
paintBrush (currentPos + [c,b])
currentPos += [deltaStepX, deltaStepY]
) if drawIt== true or drawIt == unsupplied do theCanvas.bitmap = theCanvasBitmap

新しいスイッチが true に設定された場合、または何も設定されない場合、前と同じように表示を更新します。それ以外の場合、更新はスキップされます。

)

fn unwrapTexture =
(

オブジェクトの選択、テクスチャ座標のアンラップ、およびマテリアル/テクスチャ割り当てを実行する新しい関数です。

if theObj != undefined then
(

ユーザが有効なオブジェクトをクリックした場合、

theMesh =snapshotAsMeshtheObj

スタックの最上位からメッシュを抽出します。

if meshop.getMapSupport theMesh theChannel do
(

選択オブジェクトが指定されたチャネルをサポートする場合、

faceCount = meshop.getNumMapFaces theMesh theChannel

そのチャネル内のテクスチャ面の数を読み込み、

for f = 1 to faceCount do
(

メッシュ内の面のそれぞれについて、次のコードを繰り返します。

theFace = meshop.getMapFace theMesh theChannel f

これは、マップ面の定義です。Point3 値を返します。x、y、z はその面が使用するテクスチャ頂点を指すインデックスです。

vert1= meshop.getMapVert theMesh theChannel theFace.x
vert2= meshop.getMapVert theMesh theChannel theFace.y
vert3= meshop.getMapVert theMesh theChannel theFace.z

このデータを使用して、面の 3 つの頂点を読み込みます。それぞれが、実際の U、V、W の値を含む Point3 値です。ただし、描画するのは UV だけで、W は破棄されます。

drawStroke [vert1.x * bitmapx_1, bitmapy_1- vert1.y * bitmapy_1] [vert2.x * bitmapx_1, bitmapy_1- vert2.y * bitmapy_1] drawIt:false

最初と 2 つ目のテクスチャ頂点の座標をビットマップのサイズで乗じたものを使用して drawStroke 関数を呼び出します。drawIt フラグは、イメージ生成を加速するために false に設定されます。フラグを true に設定すると、関数がイメージに対してインタラクティブに描画されますが、かなり時間がかかる場合があります。

座標の計算は、UV 空間の左下隅が 0,0 で、右上隅が 1,1 という座標であるという事実に基づいて行われます。

もう一方の手のビットマップの左上隅は 0,0 で、右下隅は bitmapx-1,bitmapy-1 です (この例では 511,511)。

UV 座標をピクセル座標に変換するには、ビットマップの幅および高さを U および V の値でそれぞれ乗算し、高さから引くことで垂直方向の高さを逆にします。

drawStroke [vert1.x * bitmapx_1, bitmapy_1- vert1.y * bitmapy_1] [vert3.x * bitmapx_1, bitmapy_1- vert3.y * bitmapy_1] drawIt:false
drawStroke [vert3.x * bitmapx_1, bitmapy_1- vert3.y * bitmapy_1] [vert2.x * bitmapx_1, bitmapy_1- vert2.y * bitmapy_1] drawIt:false

残りの 2 つの頂点についても drawStroke 関数を呼び出します。こうして面のすべてのエッジを描画します。

)
) 
theCanvas.bitmap = theCanvasBitmap

ここでロールアウトのビットマップを更新し、結果を表示します。

save theCanvasBitmap

また、ビットマップを一時的な場所に保存します。

if theObj.material == undefined do theObj.material = Standard()

選択したオブジェクトにマテリアルがない場合、新しいマテリアルを割り当てます。

try
(if theObj.material.diffusemap == undefined do
theObj.material.diffusemap = bitmapTexture filename:temp_bitmap_filename

選択したオブジェクトの diffusemap がまだ定義されていない場合は、ペイントの一時コピーを指す新しいテクスチャ マップを割り当てます。オブジェクトに標準的なマテリアルが割り当てられていない場合、diffusemap チャネルにアクセスを試みるとエラーが発生します。これは try()catch() コンテキストで防止します。

showTextureMap theObj.material true

マテリアルのオブジェクト レベルで[ビューポート内でマップを表示](Show Map In Viewport)を押します。テクスチャの割り当てに成功した場合、ビットマップがビューポートに表示されます。

autoSave.checked = true

すべて問題なく進んだ場合、新しい自動保存オプションをオンにして、選択したオブジェクトが描画に並行してビットマップで強制的に更新されるようにします。

)catch()
)
)

on autoSave changed state do if state do save theCanvasBitmap

ユーザが新しいチェックボタンを選択した場合、ビットマップはただちに一時的な保存場所に保存されます。

on MicroPaint_CanvasRollout lbuttondown pos do
(
lastPos = pos
isDrawing = true
isErasing = false
drawStroke lastPos pos
)
on MicroPaint_CanvasRollout rbuttondown pos do
(
lastPos = pos
isErasing = isDrawing = true
drawStroke lastPos pos
)
on MicroPaint_CanvasRollout lbuttonup pos do
(
isErasing = isDrawing = false if autoSave.checked do save theCanvasBitmap 

新しいチェックボタンが押されている場合、ビットマップは各ストロークの後に一時的な保存場所に保存されます。

) 
on MicroPaint_CanvasRollout rbuttonup pos do
(
isErasing = isDrawing = false
 
if autoSave.checked do save theCanvasBitmap

新しいチェックボタンが押されている場合、ビットマップは各ストロークの消去後に一時的な保存場所に保存されます。

 ) 
 
on MicroPaint_CanvasRollout mousemove pos do
(
if isDrawing do drawStroke lastPos pos
lastPos = pos
)
)
createDialog MicroPaint_CanvasRollout (bitmapx+100) (bitmapy+30) menu:CanvasMenu
MicroPaint_CanvasRollout.theCanvas.bitmap = theBackgroundBitmap
) 

結果:

この例では、ティーポットに[UVW アンラップ](Unwrap UVW)モディファイヤが適用され、新しいテクスチャ座標を作成するためにフラッテン関数が使用されています。

その後、MicroPaint でオブジェクトが選択され、サイズ 5 の円形のスムーズ ブラシと、オンになって[Airbrush Speed]が 1.0 に設定されたエアブラシを使用してテクスチャ座標が描画されています。

ティーポットは、ビューポート内に次のように表示されます。

ここで、利用可能なブラシをどれでも使用して UV 座標の上にペイントすることができます。[AutoSave]ボタンが選択されていれば、ストロークはすべてビューポート内で更新されます。

前のチュートリアル:

チュートリアル - ビットマップ ペイント ツールの開発 - 変更の消去

次のチュートリアル:

チュートリアル - ビットマップ ペイント ツールの開発 - 3D ペイント

関連事項