LOD
Script de génération des tuiles à partir d’un modèles Highpoly.
Le script est à lancer dans Blender (4.2 minimum)
- Le modèle 3d doit être déjà découpé en tuiles.
- Les meshs doivent être dans une collection nommé « LOD »
- Toutes les images doivent être packed dans le fichier
- Une area de NODE EDITOR doit être ouverte pour accéder à son contexte lors du baking
- Les export en glb seront créé à l’emplacement du fichier .blend ou de la commande qui l’appelle.
Pour le moment, le script ignore les maps de normal, metallic et roughness déjà présentes.
import os
import subprocess
from pathlib import Path
from contextlib import redirect_stdout
from sys import argv,stderr
import argparse
import bpy
import bpy_types
import bmesh
#Export texture of highest quality
TexDim = 2048
#Configure render for baking
bpy.data.scenes['Scene'].render.bake.use_pass_direct = False
bpy.data.scenes['Scene'].render.bake.use_pass_indirect = False
bpy.data.scenes['Scene'].render.bake.use_pass_color = True
bpy.data.scenes['Scene'].render.engine = 'CYCLES'
bpy.data.scenes['Scene'].cycles.device = 'GPU'
bpy.data.scenes['Scene'].render.bake.margin = 2
#Only meshes in this collection will be rendered
collection = bpy.data.collections["LOD"]
objects = list(collection.objects)
print(objects)
def rebake(obj):
#Skip non-mesh objects
if type(obj.data) != bpy_types.Mesh:
return
#Skip empty meshes
if len(obj.data.polygons) == 0:
return
print("Bake: {}".format(obj.name_full))
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
mesh = obj.data
#Smoothed meshed are lighter
mesh.shade_smooth()
#Create new UV, but use existing unwrapping
uv = obj.data.uv_layers.new(name='UVMap_smart', do_init=True)
uv.active = True
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.remove_doubles(threshold=00)
#Smart UV project is optional bc mesh should be already unwrapped !
#If mesh is not already unwrap, you need first to transfer texture to the mesh
#bpy.ops.uv.smart_project()
#Reorganize UV to fit on a single image
print("Pack Islands")
#move overlap
bpy.ops.uv.select_all(action='DESELECT')
bpy.ops.uv.select_overlap()
bm = bmesh.from_edit_mesh(mesh)
uv_layer = bm.loops.layers.uv.verify()
# adjust uv coordinates
for face in bm.faces:
for loop in face.loops:
loop_uv = loop[uv_layer]
# use xy position of the vertex as a uv coordinate
if loop_uv.select:
loop_uv.uv = (loop_uv.uv.x+1,loop_uv.uv.y)
bpy.ops.uv.select_all(action='SELECT')
bpy.ops.uv.pack_islands(margin=0.0001)
bpy.ops.object.mode_set(mode="OBJECT")
#create empty img of quality dimension
bake_img_name="DiffuseTex_" + obj.name_full
bake_img = bpy.ops.image.new(name =bake_img_name,width = TexDim,height = TexDim)
#Add this img in all material used by the chunck
for mat_slot in obj.material_slots:
mat= mat_slot.material
nodes = mat.node_tree.nodes
if not 'bake_node' in nodes:
bake_node = nodes.new('ShaderNodeTexImage')
bake_node.name="bake_node"
bake_node = nodes['bake_node']
bake_img = bpy.data.images.get(bake_img_name)
bake_node.image= bake_img
#deselect all nodes
for node in nodes:
node.select = False
#There can be only one selected and active node for baking
bake_node.select = True
nodes.active = bake_node
#Bake the img
bpy.ops.object.bake(type='DIFFUSE',margin=32)
print("We are baking : {}".format(obj.name_full))
#optional : save a copy of this img
#bake_img.filepath_raw = os.path.join(path , bake_img_name + ".png")
bake_img.file_format = 'PNG'
#bake_img.save()
#Diffuse Saved
#TO DO : if normal map is present, bake and add NM
#TO DO : if rougness/metallic is present, bake and add. Metallic is kinda tricky.
#Delete all previously used material
obj.data.materials.clear()
#Delete useless UV
uv = obj.data.uv_layers[0]
obj.data.uv_layers.remove(uv)
#Create glorious new baked material with a Principled shader
mat = bpy.data.materials.new(name="processed_material_" + obj.name_full)
mat.use_nodes = True
mat.use_backface_culling = True
nodes = mat.node_tree.nodes
links = mat.node_tree.links
mat.node_tree.links.clear()
mat.node_tree.nodes.clear()
output = nodes.new(type='ShaderNodeOutputMaterial')
shader = nodes.new(type="ShaderNodeBsdfPrincipled")
links.new(shader.outputs[0], output.inputs[0])
imgDiffuse_node = nodes.new('ShaderNodeTexImage')
imgDiffuse_node.name="bake_node"
imgDiffuse_node.image= bake_img
links.new(imgDiffuse_node.outputs[0], shader.inputs[0])
#Shader default value : roughness =1
shader.inputs['Roughness'].default_value = 1
obj.data.materials.append(mat)
bpy.context.object.data.validate(verbose=True)
#Bake and add AO. AO depends only of geometry, so it can be done on the new material.
print("baking AO")
bake_img_AO_name ="AO_" + obj.name_full
AO_img = bpy.ops.image.new(name =bake_img_AO_name,width = TexDim,height = TexDim)
#create nodes in material
#create output node gtlf. YOU NEED TO HAVE AN AREA WITH NODE EDITOR FOR CONTEXT ACCESS
win = bpy.context.window
scr = bpy.context.window.screen
areas = [area for area in scr.areas if area.type == 'NODE_EDITOR']
areas[0].spaces.active.node_tree = mat.node_tree
regions = [region for region in areas[0].regions if region.type == 'WINDOW']
#CONTEXT ACCESS OF DOOM
with bpy.context.temp_override(window=win, area=areas[0], region=regions[0], screen=scr) :
bpy.ops.node.gltf_settings_node_operator()
gltf_node = nodes['Group']
#If you're wondering why : https://docs.blender.org/manual/en/latest/addons/import_export/scene_gltf2.html
bake_node_AO = nodes.new('ShaderNodeTexImage')
bake_node_AO.name="bake_AO_node"
bake_img_AO = bpy.data.images.get(bake_img_AO_name)
bake_node_AO.image= bake_img_AO
#deselect all nodes
for node in nodes:
node.select = False
#There can be only one selected and active node
bake_node_AO.select = True
nodes.active = bake_node_AO
#Bake AO
bpy.ops.object.bake(type='AO',margin=32)
#Make link
links.new(bake_node_AO.outputs[0], gltf_node.inputs[0])
#Baking is done
#export file
ofile = bpy.path.abspath( str(TexDim) + "\\" + obj.name_full + ".glb")
#I gave up paths because of windows vs. WSL vs. PowerShell relative path description. File is saved in current folder.
print(ofile)
bpy.ops.export_scene.gltf(filepath=ofile,export_all_vertex_colors=False,use_selection=True)
obj.select_set(False)
#Hide everybody
print("LOD rebake and export")
bpy.ops.object.select_all(action='DESELECT')
for obj in objects:
if obj.type=='MESH':
obj.hide_render = True
for obj in objects:
print("REBAKE : {}".format(obj.name_full))
obj.hide_render = False
print(" {} is up for rendering".format(obj.name_full))
rebake(obj)
obj.hide_render = True
print(" {} is back to the shadowrealm".format(obj.name_full))