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))