Pipeline: composing all baked textures
This commit is contained in:
parent
d9d07b33b1
commit
a1a961e7a7
3 changed files with 243 additions and 2 deletions
|
@ -280,10 +280,12 @@ def bw_submit(lowpoly_obj_names, resolution=4096, tree_name='robossembler', area
|
|||
node_y_pos -= 4000
|
||||
|
||||
bpy.ops.bake_wrangler.bake_pass(tree=tree_name, node=node_batch.name, sock=-1)
|
||||
logger.info('Baking first pass is finished!')
|
||||
|
||||
# double pass
|
||||
tree.nodes['Mesh Settings']['ray_dist'] = 0.01
|
||||
|
||||
bpy.ops.bake_wrangler.bake_pass(tree=tree_name, node=node_batch_double.name, sock=-1)
|
||||
logger.info('Baking double pass is finished!')
|
||||
|
||||
return True
|
||||
return textures_path
|
||||
|
|
237
cg/blender/texturing/composing.py
Normal file
237
cg/blender/texturing/composing.py
Normal file
|
@ -0,0 +1,237 @@
|
|||
# coding: utf-8
|
||||
# Copyright (C) 2023 Ilia Kurochkin <brothermechanic@gmail.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Basic image processing for asset pipeline.
|
||||
'''
|
||||
__version__ = '0.1'
|
||||
|
||||
import logging
|
||||
import os
|
||||
import bpy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
def compose_baked_textures(textures_path, resolution=4096):
|
||||
''' Mix, edit, and inpaint baked textures to production condition. '''
|
||||
|
||||
bake_path = os.path.join(textures_path, 'bake')
|
||||
bake_path_double = os.path.join(textures_path, 'bake_double')
|
||||
|
||||
baked_images = sorted(os.listdir(bake_path))
|
||||
|
||||
bpy.context.scene.render.resolution_x = resolution
|
||||
bpy.context.scene.render.resolution_y = resolution
|
||||
bpy.context.scene.render.resolution_percentage = 100
|
||||
bpy.context.scene.frame_start = 1
|
||||
bpy.context.scene.frame_end = 1
|
||||
bpy.context.scene.render.filepath = textures_path
|
||||
bpy.context.scene.render.image_settings.file_format = 'PNG'
|
||||
bpy.context.scene.render.image_settings.color_mode = 'RGB'
|
||||
bpy.context.scene.render.image_settings.compression = 100
|
||||
|
||||
bpy.context.scene.display_settings.display_device = 'sRGB'
|
||||
bpy.context.scene.view_settings.view_transform = 'Standard'
|
||||
bpy.context.scene.view_settings.look = 'None'
|
||||
bpy.context.scene.view_settings.exposure = 0
|
||||
bpy.context.scene.view_settings.gamma = 1
|
||||
bpy.context.scene.view_settings.use_curve_mapping = False
|
||||
|
||||
bpy.context.scene.render.use_compositing = True
|
||||
bpy.context.scene.render.dither_intensity = 2
|
||||
bpy.context.scene.use_nodes = True
|
||||
|
||||
tree = bpy.context.scene.node_tree
|
||||
|
||||
# remove all default nodes
|
||||
for node in tree.nodes:
|
||||
tree.nodes.remove(node)
|
||||
|
||||
node_y_pos = 0
|
||||
# render for eatch uv packs
|
||||
uv_images = [image for image in baked_images if 'UV' in image]
|
||||
for uv_image in uv_images:
|
||||
uv_obj = bpy.data.images.load(os.path.join(bake_path, uv_image))
|
||||
uv_node = tree.nodes.new(type='CompositorNodeImage')
|
||||
uv_node.image = bpy.data.images[uv_obj.name]
|
||||
uv_node.location = 0, (node_y_pos + 500)
|
||||
key_node = tree.nodes.new(type="CompositorNodeLumaMatte")
|
||||
key_node.location = 250, (node_y_pos + 500)
|
||||
key_node.limit_max = 0.01
|
||||
tree.links.new(uv_node.outputs['Image'], key_node.inputs['Image'])
|
||||
de_node = tree.nodes.new(type="CompositorNodeDilateErode")
|
||||
de_node.location = 500, (node_y_pos + 500)
|
||||
de_node.distance = -2
|
||||
tree.links.new(key_node.outputs['Matte'], de_node.inputs['Mask'])
|
||||
|
||||
slot_name = uv_image.split('_UV')[0]
|
||||
for image in baked_images:
|
||||
if '_UV' in image and image.startswith(slot_name):
|
||||
continue
|
||||
if '_AO' in image:
|
||||
image_obj = bpy.data.images.load(os.path.join(bake_path, image))
|
||||
image_node = tree.nodes.new(type='CompositorNodeImage')
|
||||
image_node.location = -500, (node_y_pos + 200)
|
||||
image_node.image = bpy.data.images[image_obj.name]
|
||||
|
||||
image_obj_double = bpy.data.images.load(os.path.join(bake_path_double, image))
|
||||
image_node_double = tree.nodes.new(type='CompositorNodeImage')
|
||||
image_node_double.location = -500, (node_y_pos - 200)
|
||||
image_node_double.image = bpy.data.images[image_obj_double.name]
|
||||
|
||||
lighten_node = tree.nodes.new(type="CompositorNodeMixRGB")
|
||||
lighten_node.location = 0, node_y_pos
|
||||
lighten_node.blend_type = 'LIGHTEN'
|
||||
|
||||
tree.links.new(image_node.outputs['Image'], lighten_node.inputs[1])
|
||||
tree.links.new(image_node_double.outputs['Image'], lighten_node.inputs[2])
|
||||
|
||||
aa_node = tree.nodes.new(type="CompositorNodeAntiAliasing")
|
||||
aa_node.location = 500, node_y_pos
|
||||
aa_node.threshold = 0
|
||||
tree.links.new(lighten_node.outputs['Image'], aa_node.inputs['Image'])
|
||||
|
||||
elif '_N' in image:
|
||||
image_obj = bpy.data.images.load(os.path.join(bake_path, image))
|
||||
image_node = tree.nodes.new(type='CompositorNodeImage')
|
||||
image_node.location = -1000, (node_y_pos + 200)
|
||||
image_node.image = bpy.data.images[image_obj.name]
|
||||
|
||||
image_obj_double = bpy.data.images.load(os.path.join(bake_path_double, image))
|
||||
image_node_double = tree.nodes.new(type='CompositorNodeImage')
|
||||
image_node_double.location = -1000, (node_y_pos - 200)
|
||||
image_node_double.image = bpy.data.images[image_obj_double.name]
|
||||
|
||||
channel_node = tree.nodes.new(type="CompositorNodeSeparateColor")
|
||||
channel_node.location = -500, (node_y_pos + 200)
|
||||
|
||||
tree.links.new(image_node.outputs['Image'], channel_node.inputs['Image'])
|
||||
|
||||
mix_node = tree.nodes.new(type="CompositorNodeMixRGB")
|
||||
mix_node.location = 0, node_y_pos
|
||||
mix_node.blend_type = 'MIX'
|
||||
|
||||
tree.links.new(channel_node.outputs['Blue'], mix_node.inputs[0])
|
||||
tree.links.new(image_node_double.outputs['Image'], mix_node.inputs[1])
|
||||
tree.links.new(image_node.outputs['Image'], mix_node.inputs[2])
|
||||
|
||||
aa_node = tree.nodes.new(type="CompositorNodeAntiAliasing")
|
||||
aa_node.location = 500, node_y_pos
|
||||
aa_node.threshold = 0
|
||||
tree.links.new(mix_node.outputs['Image'], aa_node.inputs['Image'])
|
||||
|
||||
elif '_R' in image:
|
||||
image_obj = bpy.data.images.load(os.path.join(bake_path, image))
|
||||
image_node = tree.nodes.new(type='CompositorNodeImage')
|
||||
image_node.location = -600, (node_y_pos + 100)
|
||||
image_node.image = bpy.data.images[image_obj.name]
|
||||
|
||||
greater_node = tree.nodes.new(type="CompositorNodeMath")
|
||||
greater_node.location = -300, (node_y_pos + 200)
|
||||
greater_node.operation = 'GREATER_THAN'
|
||||
greater_node.inputs[1].default_value = 0.01
|
||||
|
||||
tree.links.new(image_node.outputs['Image'], greater_node.inputs['Value'])
|
||||
|
||||
mix_node = tree.nodes.new(type="CompositorNodeMixRGB")
|
||||
mix_node.location = 0, node_y_pos
|
||||
mix_node.blend_type = 'MIX'
|
||||
mix_node.inputs[1].default_value = (1, 1, 1, 1)
|
||||
|
||||
tree.links.new(greater_node.outputs['Value'], mix_node.inputs[0])
|
||||
tree.links.new(image_node.outputs['Image'], mix_node.inputs[2])
|
||||
|
||||
aa_node = tree.nodes.new(type="CompositorNodeAntiAliasing")
|
||||
aa_node.location = 500, node_y_pos
|
||||
aa_node.threshold = 0
|
||||
tree.links.new(mix_node.outputs['Image'], aa_node.inputs['Image'])
|
||||
|
||||
else:
|
||||
image_obj = bpy.data.images.load(os.path.join(bake_path, image))
|
||||
image_node = tree.nodes.new(type='CompositorNodeImage')
|
||||
image_node.location = 0, node_y_pos
|
||||
image_node.image = bpy.data.images[image_obj.name]
|
||||
|
||||
aa_node = tree.nodes.new(type="CompositorNodeAntiAliasing")
|
||||
aa_node.location = 500, node_y_pos
|
||||
aa_node.threshold = 0
|
||||
tree.links.new(image_node.outputs['Image'], aa_node.inputs['Image'])
|
||||
|
||||
sa_node = tree.nodes.new(type="CompositorNodeSetAlpha")
|
||||
sa_node.location = 1000, node_y_pos
|
||||
sa_node.mode = 'REPLACE_ALPHA'
|
||||
tree.links.new(aa_node.outputs['Image'], sa_node.inputs['Image'])
|
||||
tree.links.new(de_node.outputs['Mask'], sa_node.inputs['Alpha'])
|
||||
|
||||
sa_node = tree.nodes.new(type="CompositorNodeSetAlpha")
|
||||
sa_node.location = 1000, node_y_pos
|
||||
sa_node.mode = 'REPLACE_ALPHA'
|
||||
tree.links.new(aa_node.outputs['Image'], sa_node.inputs['Image'])
|
||||
tree.links.new(de_node.outputs['Mask'], sa_node.inputs['Alpha'])
|
||||
|
||||
ip_node = tree.nodes.new(type="CompositorNodeInpaint")
|
||||
ip_node.location = 1500, node_y_pos
|
||||
ip_node.distance = 512
|
||||
tree.links.new(sa_node.outputs['Image'], ip_node.inputs['Image'])
|
||||
|
||||
sc_node = tree.nodes.new(type="CompositorNodeScale")
|
||||
sc_node.location = 2000, node_y_pos
|
||||
sc_node.space = 'RENDER_SIZE'
|
||||
tree.links.new(ip_node.outputs['Image'], sc_node.inputs['Image'])
|
||||
|
||||
if '_D' in image or '_N' in image:
|
||||
out_node = tree.nodes.new(type="CompositorNodeOutputFile")
|
||||
out_node.location = 3000, node_y_pos
|
||||
out_node.file_slots[0].path = 'T_{}{}'.format(
|
||||
slot_name,
|
||||
image.split(slot_name)[1].split('.')[0].lower())
|
||||
tree.links.new(sc_node.outputs['Image'], out_node.inputs['Image'])
|
||||
|
||||
else:
|
||||
gamma_node = tree.nodes.new(type="CompositorNodeGamma")
|
||||
gamma_node.location = 2500, node_y_pos
|
||||
gamma_node.inputs[1].default_value = 2.2
|
||||
|
||||
tree.links.new(sc_node.outputs['Image'], gamma_node.inputs['Image'])
|
||||
|
||||
out_node = tree.nodes.new(type="CompositorNodeOutputFile")
|
||||
out_node.location = 3000, node_y_pos
|
||||
out_node.file_slots[0].path = 'T_{}{}'.format(
|
||||
slot_name,
|
||||
image.split(slot_name)[1].split('.')[0].lower())
|
||||
tree.links.new(gamma_node.outputs['Image'], out_node.inputs['Image'])
|
||||
|
||||
node_y_pos -= 500
|
||||
|
||||
# render
|
||||
bpy.ops.render.render(use_viewport=True)
|
||||
# remove all nodes after rendering
|
||||
for node in tree.nodes:
|
||||
tree.nodes.remove(node)
|
||||
|
||||
# cleanup filenames
|
||||
composed_textures = [
|
||||
texture
|
||||
for texture in os.listdir(textures_path)
|
||||
if '000' in texture and texture.endswith('.png')
|
||||
]
|
||||
for texture in composed_textures:
|
||||
os.rename(
|
||||
os.path.join(textures_path, texture),
|
||||
os.path.join(textures_path, texture.split('000')[0] + '.png')
|
||||
)
|
||||
|
||||
logger.info('Composing %s textures is finished!', len(composed_textures))
|
||||
return textures_path
|
|
@ -23,6 +23,7 @@ from blender.processing.midpoly_setup import hightpoly_collections_to_midpoly
|
|||
from blender.processing.lowpoly_setup import parts_to_shells
|
||||
from blender.processing.uv_setup import uv_unwrap
|
||||
from blender.texturing.bake_submitter import bw_submit
|
||||
from blender.texturing.composing import compose_baked_textures
|
||||
from blender.export.dae import export_dae
|
||||
from blender.export.stl import export_stl
|
||||
import bpy
|
||||
|
@ -129,7 +130,8 @@ def cg_pipeline(**kwargs):
|
|||
|
||||
# bake textures
|
||||
bpy.ops.wm.open_mainfile(filepath=blend_path)
|
||||
bw_submit(lowpoly_obj_names)
|
||||
textures_path = bw_submit(lowpoly_obj_names)
|
||||
compose_baked_textures(textures_path)
|
||||
|
||||
# export object meshes and urdf
|
||||
to_urdf = collections.defaultdict(list)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue