def execute(self, context): if bpy.context.scene.exportGlslOptions == "0": Scene = bpy.context.scene Materials = bpy.data.materials #self.filepath = bpy.path.relpath(self.filepath) self.filepath = os.path.dirname(self.filepath) for mat in Materials: Shader = gpu.export_shader(Scene,mat) frag = open(self.filepath + "\mat_" + mat.name + ".frag","w") vertex = open(self.filepath + "\mat_" + mat.name + ".vert" ,"w") frag.write(Shader["fragment"]) vertex.write(Shader["vertex"]) elif bpy.context.scene.exportGlslOptions == "1": Scene = bpy.context.scene ObjList = bpy.context.selected_objects for obj in ObjList: print() print(obj) for matSL in obj.material_slots: mat = matSL.material #print(mat) #self.filepath = bpy.path.relpath(self.filepath) path = os.path.dirname(self.filepath) print(path) Shader = gpu.export_shader(Scene,mat) frag = open(path + "\mat_" + mat.name + ".frag","w") vertex = open(path + "\mat_" + mat.name + ".vert" ,"w") frag.write(Shader["fragment"]) vertex.write(Shader["vertex"]) elif bpy.context.scene.exportGlslOptions == "2": Scene = bpy.context.scene mat = bpy.context.material #self.filepath = bpy.path.relpath(self.filepath) self.filepath = os.path.dirname(self.filepath) Shader = gpu.export_shader(Scene,mat) frag = open(self.filepath + "\mat_" + mat.name + ".frag","w") vertex = open(self.filepath + "\mat_" + mat.name + ".vert" ,"w") frag.write(Shader["fragment"]) vertex.write(Shader["vertex"]) return {'FINISHED'}
def get_dynamic_constants(mat, scn, paths): # Example: #paths = [ #'node_tree.nodes["slicep"].outputs[0].default_value', #'node_tree.nodes["slicen"].outputs[0].default_value', #] shader = gpu.export_shader(scn, mat) code = shader['fragment'].rsplit('}', 2)[1] sentinel = 0.406198 # random() while ("%f" % sentinel) in code: sentinel = random.random() # We're assuming "%f"%sentinel yelds exactly the same string # as the code generator (sprintf is used in both cases) # Possible optimization: use a different sentinel per path # and call export_shader and update() only once varnames = [] for p in paths: try: orig_obj = mat.path_resolve(p) except ValueError: varnames.append(None) continue obj, attr = ('.' + p).rsplit('.', 1) obj = eval('mat' + obj) print(obj, attr) is_vector = hasattr(orig_obj, '__getitem__') if is_vector: orig_val = orig_obj[0] orig_obj[0] = sentinel else: setattr(obj, attr, sentinel) scn.update() sh = gpu.export_shader(scn, mat) c = sh['fragment'].rsplit('}', 2)[1] # restore original if is_vector: orig_obj[0] = orig_val else: setattr(obj, attr, orig_obj) pos = c.find("%f" % sentinel) if pos != -1: varnames.append(c[:pos].rsplit(' ', 3)[1]) else: varnames.append(None) if any(varnames): scn.update() return varnames
def get_dynamic_constants(mat, scn, paths): # Example: #paths = [ #'node_tree.nodes["slicep"].outputs[0].default_value', #'node_tree.nodes["slicen"].outputs[0].default_value', #] shader = gpu.export_shader(scn, mat) code = shader['fragment'].rsplit('}', 2)[1] sentinel = 0.406198 # random() while ("%f"%sentinel) in code: sentinel = random.random() # We're assuming "%f"%sentinel yelds exactly the same string # as the code generator (sprintf is used in both cases) # Possible optimization: use a different sentinel per path # and call export_shader and update() only once varnames = [] for p in paths: try: orig_obj = mat.path_resolve(p) except ValueError: varnames.append(None) continue obj, attr = ('.'+p).rsplit('.', 1) obj = eval('mat'+obj) print(obj, attr) is_vector = hasattr(orig_obj, '__getitem__') if is_vector: orig_val = orig_obj[0] orig_obj[0] = sentinel else: setattr(obj, attr, sentinel) scn.update() sh = gpu.export_shader(scn, mat) c = sh['fragment'].rsplit('}', 2)[1] # restore original if is_vector: orig_obj[0] = orig_val else: setattr(obj, attr, orig_obj) pos = c.find("%f"%sentinel) if pos!=-1: varnames.append(c[:pos].rsplit(' ',3)[1]) else: varnames.append(None) if any(varnames): scn.update() return varnames
def invoke(all_data, target_data, material, context, fname, flags=None): dirname = os.path.dirname(fname) if 'paths' in all_data['scene']: dirname = os.path.join(dirname, all_data['scene']['paths']['materials']) if not os.path.exists(dirname): os.makedirs(dirname) sha = gpu.export_shader(context.scene, material) add_lamp_name_for_unf_type16(sha) add_environment_values_for_unf_type21_28(sha, material, context.scene.world) find_and_correct_spot_light_uniforms(material, sha) replace_names_for_shared_uniforms(sha) replace_common_uniforms(sha) replace_sampler_for_textures(material, sha) replace_attributes(material, sha) f = open(os.path.join(dirname, material.name + '.vert'), 'w') f.write(sha['vertex']) f.close() f = open(os.path.join(dirname, material.name + '.frag'), 'w') f.write(sha['fragment']) f.close() target_data['vert'] = material.name + '.vert' target_data['frag'] = material.name + '.frag' target_data['name'] = material.name uniforms = [] for unf in sha['uniforms']: uniform = {} for key, val in tuple(unf.items()): if key == 'lamp': val = val.name elif key == 'image': if unf['type'] != 'p3d_texture': saved_img = save_image( val, fname, all_data['scene']['paths']['images']) #val = saved_img val = os.path.split(saved_img)[1] else: val = '' #val = os.path.split(val.filepath)[1] elif key == 'texpixels': #val = '' fname = material.name + '-' + unf['varname'] + '.dat' f = open(os.path.join(dirname, fname), 'wb') f.write(val) f.close() #val = os.path.join(all_data['scene']['paths']['materials'], # fname).replace('\\','/') val = fname if not key in uniform.keys(): uniform[key] = val uniforms.append(uniform) target_data['uniforms'] = uniforms
def write_shader(self, scene, mat): """Writes a shader for the given material to two GLSL files.""" shader = gpu.export_shader(scene, mat) path = os.path.join(self.filepath, "mat_%s.%%s" % mat.name) with open(path % "frag", "w") as frag: frag.write(shader["fragment"]) with open(path % "vert", "w") as vertex: vertex.write(shader["vertex"]) self.export_count += 1
def set_shader_lib(fragment='', mat=None, scn=None): global SHADER_LIB if not SHADER_LIB or debug_lib: if not fragment: if mat and scn: import gpu fragment = gpu.export_shader(scn, mat)['fragment'] else: raise Exception("Wrong arguments") print('Converting shader lib') parts = fragment.rsplit('}', 2) SHADER_LIB = \ """ #ifdef GL_ES #if __VERSION__ < 130 #extension GL_OES_standard_derivatives : enable #extension GL_EXT_shader_texture_lod : enable #else #define texture2D texture #define texture2DLod textureLod #define textureCube texture #define textureCubeLod textureLod #define texture2DProj textureProj #define sample sample_ #endif precision highp float; precision highp int; #if __VERSION__ < 130 #ifdef GL_EXT_shader_texture_lod #define texture2DLod texture2DLodEXT #define textureCubeLod textureCubeLodEXT #else #define texture2DLod texture2D #define textureCubeLod textureCube #endif #endif #endif """ \ +defines+uniforms+(parts[0]+'}').replace('\r','')+'\n' SHADER_LIB = do_lib_replacements(SHADER_LIB).encode('ascii', 'ignore').decode() # This section below is necessary because something is interpreted as non ascii for some reason # despite the line above (which is also necessary, mysteriously...) splits = SHADER_LIB.split('BIT_OPERATIONS', 2) if len(splits) == 3: a, b, c = splits SHADER_LIB = a + 'BIT_OPERATIONS\n#endif' + c if debug_lib: open('/tmp/shader_lib.orig.glsl', 'w').write((parts[0] + '}').replace('\r', '') + '\n') open('/tmp/shader_lib.glsl', 'w').write(SHADER_LIB)
def export_materials(materials, shaders, programs, techniques): def export_material(material): return { 'values': { 'diffuse': list((material.diffuse_color * material.diffuse_intensity)[:]) + [material.alpha], 'specular': list((material.specular_color * material.specular_intensity)[:]) + [material.specular_alpha], 'emission': list((material.diffuse_color * material.emit)[:]) + [material.alpha], 'ambient': [material.ambient] * 4, 'shininess': material.specular_hardness, 'textures': [ts.texture.name for ts in material.texture_slots if ts and ts.texture.type == 'IMAGE'], 'uv_layers': [ts.uv_layer for ts in material.texture_slots if ts] } } exp_materials = {} for material in materials: exp_materials[material.name] = export_material(material) if not EXPORT_SHADERS: continue # Handle shaders shader_data = gpu.export_shader(bpy.context.scene, material) fs_bytes = shader_data['fragment'].encode() fs_uri = 'data:text/plain;base64,' + base64.b64encode(fs_bytes).decode('ascii') shaders[material.name+'FS'] = {'type': 35632, 'uri': fs_uri} vs_bytes = shader_data['vertex'].encode() vs_uri = 'data:text/plain;base64,' + base64.b64encode(vs_bytes).decode('ascii') shaders[material.name+'VS'] = {'type': 35633, 'uri': vs_uri} # Handle programs programs[material.name+'Program'] = { 'attributes' : [], 'fragmentShader' : material.name+'FS', 'vertexShader' : material.name+'VS', } # Handle techniques techniques['material.name'+'Technique'] = { 'program' : material.name+'Program', 'attributes' : {a['varname'] : a['varname'] for a in shader_data['attributes']}, 'uniforms' : {u['varname'] : u['varname'] for u in shader_data['uniforms']}, } return exp_materials
def __init__(self, blenderMaterial, useGL3=False): self.resourceFiles = [] self._blenderMaterialName = blenderMaterial.name self.shadersDirectoryName = "assets/shaders/" self._materialPropertyName = propertyName(blenderMaterial.name) try: shader = gpu.export_shader(bpy.context.scene, blenderMaterial) vertexShaderName = os.path.join( self.shadersDirectoryName, self._materialPropertyName.lower() + ".vert") self.resourceFiles.append(vertexShaderName) with open(vertexShaderName, "w") as f: f.write(preprocessVertexShaderForQt3D(shader["vertex"])) fragmentShaderName = os.path.join( self.shadersDirectoryName, self._materialPropertyName.lower() + ".frag") self.resourceFiles.append(fragmentShaderName) with open(fragmentShaderName, "w") as f: f.write(preprocessFragmentShaderForQt3D(shader["fragment"])) parameters = [ " Parameter { name: \"" + param["varname"] + "\"; value: " + parameterUniformValue(param) + " }" for param in shader["uniforms"] ] except: pass self._content = ( " readonly property Material " + self._materialPropertyName + ": PhongMaterial {\n" " ambient: " + blenderColorToQColor( [blenderMaterial.ambient * 0.2 for i in range(0, 3)]) + "\n" " diffuse: " + blenderColorToQColor(blenderMaterial.diffuse_color) + "\n" " specular: " + blenderColorToQColor(blenderMaterial.specular_color) + "\n" " }\n")
def embed_shaders(scene): for mat in bpy.data.materials: shader = gpu.export_shader(scene, mat) mat.gamesettings.fragment_shader = shader["fragment"] mat.gamesettings.vertex_shader = shader["vertex"] attributes = shader["attributes"] uniforms = [] for u in shader["uniforms"]: u2 = {} uniforms.append(u2) for k2 in u: u2[k2] = u[k2] if "lamp" in u2 and u2["lamp"] is not None: u2["lamp"] = u2["lamp"].name if "image" in u2 and u2["image"] is not None: u2["image"] = u2["image"].name mat.gamesettings.uniforms = json.dumps(uniforms) mat.gamesettings.attributes = json.dumps(attributes)
def embed_shaders(scene): for mat in bpy.data.materials: shader = gpu.export_shader(scene, mat); mat.gamesettings.fragment_shader = shader["fragment"] mat.gamesettings.vertex_shader = shader["vertex"] attributes = shader["attributes"] uniforms = []; for u in shader["uniforms"]: u2 = {}; uniforms.append(u2) for k2 in u: u2[k2] = u[k2] if "lamp" in u2 and u2["lamp"] is not None: u2["lamp"] = u2["lamp"].name if "image" in u2 and u2["image"] is not None: u2["image"] = u2["image"].name mat.gamesettings.uniforms = json.dumps(uniforms) mat.gamesettings.attributes = json.dumps(attributes)
def export_materials(settings, materials, shaders, programs, techniques): def export_material(material): all_textures = [ts for ts in material.texture_slots if ts and ts.texture.type == 'IMAGE'] diffuse_textures = [t.texture.name for t in all_textures if t.use_map_color_diffuse] emission_textures = [t.texture.name for t in all_textures if t.use_map_emit] specular_textures = [t.texture.name for t in all_textures if t.use_map_color_spec] diffuse_color = list((material.diffuse_color * material.diffuse_intensity)[:]) + [material.alpha] emission_color = list((material.diffuse_color * material.emit)[:]) + [material.alpha] specular_color = list((material.specular_color * material.specular_intensity)[:]) + [material.specular_alpha] technique = 'PHONG' if material.use_shadeless: technique = 'CONSTANT' elif material.specular_intensity == 0.0: technique = 'LAMBERT' elif material.specular_shader == 'BLINN': technique = 'BLINN' return { 'extensions': { 'KHR_materials_common': { 'technique': technique, 'values': { 'ambient': ([material.ambient]*3) + [1.0], 'diffuse': diffuse_textures[-1] if diffuse_textures else diffuse_color, 'doubleSided': not material.game_settings.use_backface_culling, 'emission': emission_textures[-1] if emission_textures else emission_color, 'specular': specular_textures[-1] if specular_textures else specular_color, 'shininess': material.specular_hardness, 'transparency': material.alpha, 'transparent': material.use_transparency, } } } } exp_materials = {} for material in materials: if settings['materials_export_shader'] == False: exp_materials[material.name] = export_material(material) else: # Handle shaders shader_data = gpu.export_shader(bpy.context.scene, material) if settings['asset_profile'] == 'DESKTOP': shader_converter.to_130(shader_data) else: shader_converter.to_web(shader_data) fs_bytes = shader_data['fragment'].encode() fs_uri = 'data:text/plain;base64,' + base64.b64encode(fs_bytes).decode('ascii') shaders[material.name+'FS'] = {'type': 35632, 'uri': fs_uri} vs_bytes = shader_data['vertex'].encode() vs_uri = 'data:text/plain;base64,' + base64.b64encode(vs_bytes).decode('ascii') shaders[material.name+'VS'] = {'type': 35633, 'uri': vs_uri} # Handle programs programs[material.name+'Program'] = { 'attributes' : [a['varname'] for a in shader_data['attributes']], 'fragmentShader' : material.name+'FS', 'vertexShader' : material.name+'VS', } # Handle parameters/values values = {} parameters = {} for attribute in shader_data['attributes']: name = attribute['varname'] semantic = gpu_luts.TYPE_TO_SEMANTIC[attribute['type']] _type = gpu_luts.DATATYPE_TO_GLTF_TYPE[attribute['datatype']] parameters[name] = {'semantic': semantic, 'type': _type} for uniform in shader_data['uniforms']: valname = gpu_luts.TYPE_TO_NAME.get(uniform['type'], uniform['varname']) rnaname = valname semantic = None node = None value = None if uniform['varname'] == 'bl_ModelViewMatrix': semantic = 'MODELVIEW' elif uniform['varname'] == 'bl_ProjectionMatrix': semantic = 'PROJECTION' elif uniform['varname'] == 'bl_NormalMatrix': semantic = 'MODELVIEWINVERSETRANSPOSE' else: if uniform['type'] in gpu_luts.LAMP_TYPES: node = uniform['lamp'].name valname = node + '_' + valname semantic = gpu_luts.TYPE_TO_SEMANTIC.get(uniform['type'], None) if not semantic: lamp_obj = bpy.data.objects[node] value = getattr(lamp_obj.data, rnaname) elif uniform['type'] in gpu_luts.MIST_TYPES: valname = 'mist_' + valname mist_settings = bpy.context.scene.world.mist_settings if valname == 'mist_color': value = bpy.context.scene.world.horizon_color else: value = getattr(mist_settings, rnaname) if valname == 'mist_falloff': value = 0.0 if value == 'QUADRATIC' else 1.0 if 'LINEAR' else 2.0 elif uniform['type'] in gpu_luts.WORLD_TYPES: world = bpy.context.scene.world value = getattr(world, rnaname) elif uniform['type'] in gpu_luts.MATERIAL_TYPES: value = gpu_luts.DATATYPE_TO_CONVERTER[uniform['datatype']](getattr(material, rnaname)) values[valname] = value elif uniform['type'] == gpu.GPU_DYNAMIC_SAMPLER_2DIMAGE: for ts in [ts for ts in material.texture_slots if ts and ts.texture.type == 'IMAGE']: if ts.texture.image.name == uniform['image'].name: value = ts.texture.name values[uniform['varname']] = value else: print('Unconverted uniform:', uniform) parameter = {} if semantic: parameter['semantic'] = semantic if node: parameter['node'] = node else: parameter['value'] = gpu_luts.DATATYPE_TO_CONVERTER[uniform['datatype']](value) if uniform['type'] == gpu.GPU_DYNAMIC_SAMPLER_2DIMAGE: parameter['type'] = 35678 #SAMPLER_2D else: parameter['type'] = gpu_luts.DATATYPE_TO_GLTF_TYPE[uniform['datatype']] parameters[valname] = parameter uniform['valname'] = valname # Handle techniques tech_name = material.name + 'Technique' techniques[tech_name] = { 'parameters' : parameters, 'program' : material.name+'Program', 'attributes' : {a['varname'] : a['varname'] for a in shader_data['attributes']}, 'uniforms' : {u['varname'] : u['valname'] for u in shader_data['uniforms']}, } exp_materials[material.name] = {'technique': tech_name, 'values': values} # exp_materials[material.name] = {} return exp_materials
def mat_to_json(mat, scn): global SHADER_LIB shader = gpu.export_shader(scn, mat) parts = shader['fragment'].rsplit('}', 2) if not SHADER_LIB: SHADER_LIB = "#extension GL_OES_standard_derivatives : enable\n"\ +"precision highp float;\n"\ +"precision highp int;\n"+(parts[0]+'}')\ .replace('gl_ModelViewMatrixInverse','mat4(1)')\ .replace('gl_ModelViewMatrix','mat4(1)')\ .replace('gl_ProjectionMatrixInverse','mat4(1)')\ .replace('gl_ProjectionMatrix','mat4(1)')\ .replace('gl_NormalMatrixInverse','mat3(1)')\ .replace('gl_NormalMatrix','mat3(1)')\ .replace('sampler2DShadow','sampler2D')\ .replace('shadow2DProj(shadowmap, co).x', 'step(co.z,texture2D(shadowmap, co.xy).x)')\ .replace('gl_LightSource[i].position','vec3(0,0,0)')\ .replace('gl_LightSource[i].diffuse','vec3(0,0,0)')\ .replace('gl_LightSource[i].specular','vec3(0,0,0)')\ .replace('gl_LightSource[i].halfVector','vec3(0,0,0)')\ .replace('float rad[4], fac;', 'float rad[4];float fac;')\ .replace('''/* These are needed for high quality bump mapping */ #version 130 #extension GL_ARB_texture_query_lod: enable #define BUMP_BICUBIC''','') #open('/tmp/shader_lib','w').write(SHADER_LIB) try: import shader_lib_filter, imp imp.reload(shader_lib_filter) print('Applying shader_lib_filter.py') SHADER_LIB = shader_lib_filter.shader_lib_filter(SHADER_LIB) except: pass shader['fragment'] = (parts[1] + '}').replace('sampler2DShadow', 'sampler2D') # Stuff for debugging shaders # TODO write only when they have changed if os.name != 'nt': SHM = "/run/shm/" open(SHM + mat.name + '.v', 'w').write(shader['vertex']) open(SHM + mat.name + '.f', 'w').write(shader['fragment']) try: shader['fragment'] = open(SHM + mat.name + '.f2').read() except: pass #from pprint import pprint #pprint(shader['attributes']) # --------------------------- dyn_consts = loads(mat.get('dyn_consts') or '[]') replacements = loads(mat.get('replacements') or '[]') # Checking hash of main() is not enough code_hash = hash(shader['fragment']) % 2147483648 #print(shader['fragment'].split('{')[0]) premain, main = shader['fragment'].split('{') if mat.get('code_hash') != code_hash: # Dynamic uniforms (for animation, per object variables, # particle layers or other custom stuff) dyn_consts = [] block = bpy.data.texts.get('custom_uniforms') if block: paths = [x for x in block.as_string().split('\n') if x] print(paths) dyn_consts = get_dynamic_constants(mat, scn, paths) else: print('no block') mat['dyn_consts'] = dumps(dyn_consts) # Get list of unknown varyings and save them # as replacement strings known = [ 'var' + a['varname'][3:] for a in shader['attributes'] if a['varname'] ] varyings = [ x[:-1].split() for x in premain.split('\n') if x.startswith('varying') and len(x) < 21 # this filters varposition/varnormal ] replacements = [] for v in varyings: if v[2] not in known: replacements.append( (' '.join(v), 'const {t} {n} = {t}(0.0)'.format(t=v[1], n=v[2]))) mat['replacements'] = dumps(replacements) if any(dyn_consts): # Separate constants from the rest lines = premain.split('\n') # This generates a dictionary with the var name # and comma-separated values in a string, like this: # {'cons123': '1.23, 4.56, 7.89', ...} consts = dict([(c[2], c[3].split('(')[1][:-2]) for c in [l.split(' ', 3) for l in lines] if c[0] == 'const']) premain = '\n'.join(l for l in lines if not l.startswith('cons')) # Convert them back to constants, except for dynamic ones lines = [] TYPES = [ 'float', 'vec2', 'vec3', 'vec4', '', '', '', '', 'mat3', '', '', '', '', '', '', 'mat4' ] types = [None] * len(dyn_consts) for k, v in consts.items(): t = TYPES[v.count(',')] #print(k, dyn_consts, k in dyn_consts) if k in dyn_consts: lines.append('uniform {0} {1};'.format(t, k)) types[dyn_consts.index(k)] = t else: lines.append('const {0} {1} = {0}({2});'.format(t, k, v)) shader['fragment'] = '\n'.join(lines) + '\n' + premain + '{' + main shader['uniforms'] += [{ 'type': -1, 'varname': c, 'gltype': types[i], 'index': i } for i, c in enumerate(dyn_consts)] mat['code_hash'] = code_hash for a, b in replacements: shader['fragment'] = shader['fragment'].replace(a, b) # Find number of required shape attributes (excluding basis) # And number of bones num_shapes = 0 num_bones = 0 num_partsegments = 0 for ob in scn.objects: if ob.type == 'MESH': # TODO: manage materials attached to object if mat in list(ob.data.materials): if ob.data.shape_keys: num_shapes = max(num_shapes, len(ob.data.shape_keys.key_blocks) - 1) if ob.particle_systems: num_partsegments = 1 # TODO check correct p systems and segments if ob.parent and ob.parent.type == 'ARMATURE' and not ob.parent_bone and not ob.get( 'apply_armature'): num_bones = max( num_bones, len([b for b in ob.parent.data.bones if b.use_deform])) if num_shapes: shader['attributes'].append({ 'type': 99, 'count': num_shapes, 'varname': '' }) if num_partsegments: shader['attributes'].append({ 'type': 77, 'count': num_partsegments, 'varname': '' }) if num_bones: shader['attributes'].append({ 'type': 88, 'count': num_bones, 'varname': '' }) last_lamp = "" for u in shader['uniforms']: if 'lamp' in u: u['lamp'] = last_lamp = u['lamp'].name if 'image' in u: # TODO: if the image is used in several textures, how can we know which? slots = list(mat.texture_slots) if mat.use_nodes: for node in mat.node_tree.nodes: if node.type == 'MATERIAL' and node.material: slots.extend(node.material.texture_slots) texture_slot = [ t for t in slots if t and t.texture and t.texture.type == 'IMAGE' and t.texture.image == u['image'] ] if not texture_slot: print("Warning: image %s not found in material %s." % (u['image'].name, mat.name)) u['filter'] = True u['wrap'] = 'R' else: u['filter'] = texture_slot[0].texture.use_interpolation u['wrap'] = 'R' if texture_slot[ 0].texture.extension == 'REPEAT' else 'C' u['size'] = 0 fpath = bpy.path.abspath(u['image'].filepath) if os.path.exists(fpath): u['size'] = os.path.getsize(fpath) u['filepath'] = u['image'].name + '.' + u['image'].filepath.rsplit( '.', 1)[1] u['image'] = u['image'].name tex_sizes[u['image']] = u['size'] if 'texpixels' in u: # Minimum shadow buffer is 128x128 if u['texsize'] > 16000: # This is a shadow buffer u['type'] = gpu.GPU_DYNAMIC_SAMPLER_2DSHADOW del u['texpixels'] # we don't need this # Assuming a lamp uniform is always sent before this one u['lamp'] = last_lamp # TODO: send lamp stuff else: # It's a ramp # encode as PNG data URI import struct, zlib def png_chunk(ty, data): return struct.pack('>I',len(data)) + ty + data +\ struct.pack('>I',zlib.crc32(ty + data)) u['filepath'] = 'data:image/png;base64,' + base64.b64encode( b'\x89PNG\r\n\x1a\n' + png_chunk(b'IHDR', struct.pack('>IIBBBBB', 256, 1, 8, 6, 0, 0, 0)) + png_chunk(b'IDAT', zlib.compress(b'\x00' + u['texpixels'][:1024])) + png_chunk(b'IEND', b'') #for some reason is 257px? ).decode() u['image'] = hex(hash(u['filepath']))[-10:] u['wrap'] = 'C' # clamp to edge u['type'] = gpu.GPU_DYNAMIC_SAMPLER_2DIMAGE u['size'] = 0 del u['texpixels'] # Engine builds its own vertex shader del shader['vertex'] shader['double_sided'] = not mat.game_settings.use_backface_culling shader['type'] = 'MATERIAL' shader['name'] = mat.name shader['scene'] = scn.name ret = dumps(shader).encode('utf8') mat['hash'] = hash(ret) % 2147483648 return ret
def mat_to_json(mat, scn): global SHADER_LIB shader = gpu.export_shader(scn, mat) parts = shader['fragment'].rsplit('}',2) if not SHADER_LIB: SHADER_LIB = "#extension GL_OES_standard_derivatives : enable\n"\ +"precision highp float;\n"\ +"precision highp int;\n"+(parts[0]+'}')\ .replace('gl_ModelViewMatrixInverse','mat4(1)')\ .replace('gl_ModelViewMatrix','mat4(1)')\ .replace('gl_ProjectionMatrixInverse','mat4(1)')\ .replace('gl_ProjectionMatrix','mat4(1)')\ .replace('gl_NormalMatrixInverse','mat3(1)')\ .replace('gl_NormalMatrix','mat3(1)')\ .replace('sampler2DShadow','sampler2D')\ .replace('shadow2DProj(shadowmap, co).x', 'step(co.z,texture2D(shadowmap, co.xy).x)')\ .replace('gl_LightSource[i].position','vec3(0,0,0)')\ .replace('gl_LightSource[i].diffuse','vec3(0,0,0)')\ .replace('gl_LightSource[i].specular','vec3(0,0,0)')\ .replace('gl_LightSource[i].halfVector','vec3(0,0,0)')\ .replace('float rad[4], fac;', 'float rad[4];float fac;')\ .replace('''/* These are needed for high quality bump mapping */ #version 130 #extension GL_ARB_texture_query_lod: enable #define BUMP_BICUBIC''','') #open('/tmp/shader_lib','w').write(SHADER_LIB) try: import shader_lib_filter, imp imp.reload(shader_lib_filter) print('Applying shader_lib_filter.py') SHADER_LIB = shader_lib_filter.shader_lib_filter(SHADER_LIB) except: pass shader['fragment'] = (parts[1]+'}').replace('sampler2DShadow','sampler2D') # Stuff for debugging shaders # TODO write only when they have changed if os.name != 'nt': SHM = "/run/shm/" open(SHM + mat.name+'.v','w').write(shader['vertex']) open(SHM + mat.name+'.f','w').write(shader['fragment']) try: shader['fragment']=open(SHM + mat.name+'.f2').read() except: pass #from pprint import pprint #pprint(shader['attributes']) # --------------------------- dyn_consts = loads(mat.get('dyn_consts') or '[]') replacements = loads(mat.get('replacements') or '[]') # Checking hash of main() is not enough code_hash = hash(shader['fragment']) % 2147483648 #print(shader['fragment'].split('{')[0]) premain, main = shader['fragment'].split('{') if mat.get('code_hash') != code_hash: # Dynamic uniforms (for animation, per object variables, # particle layers or other custom stuff) dyn_consts = [] block = bpy.data.texts.get('custom_uniforms') if block: paths = [x for x in block.as_string().split('\n') if x] print(paths) dyn_consts = get_dynamic_constants(mat, scn, paths) else: print('no block') mat['dyn_consts'] = dumps(dyn_consts) # Get list of unknown varyings and save them # as replacement strings known = ['var'+a['varname'][3:] for a in shader['attributes'] if a['varname']] varyings = [x[:-1].split() for x in premain.split('\n') if x.startswith('varying') and len(x) < 21 # this filters varposition/varnormal ] replacements = [] for v in varyings: if v[2] not in known: replacements.append((' '.join(v), 'const {t} {n} = {t}(0.0)'.format(t=v[1], n=v[2]))) mat['replacements'] = dumps(replacements) if any(dyn_consts): # Separate constants from the rest lines = premain.split('\n') # This generates a dictionary with the var name # and comma-separated values in a string, like this: # {'cons123': '1.23, 4.56, 7.89', ...} consts = dict([(c[2], c[3].split('(')[1][:-2]) for c in [l.split(' ', 3) for l in lines] if c[0]=='const']) premain = '\n'.join(l for l in lines if not l.startswith('cons')) # Convert them back to constants, except for dynamic ones lines = [] TYPES = ['float', 'vec2', 'vec3', 'vec4', '','','','','mat3','','','','','','','mat4'] types = [None]*len(dyn_consts) for k,v in consts.items(): t = TYPES[v.count(',')] #print(k, dyn_consts, k in dyn_consts) if k in dyn_consts: lines.append('uniform {0} {1};'.format(t, k)) types[dyn_consts.index(k)] = t else: lines.append('const {0} {1} = {0}({2});'.format(t, k, v)) shader['fragment'] = '\n'.join(lines) + '\n' + premain + '{' + main shader['uniforms'] += [ {'type': -1, 'varname': c, 'gltype': types[i], 'index': i} for i,c in enumerate(dyn_consts)] mat['code_hash'] = code_hash for a,b in replacements: shader['fragment'] = shader['fragment'].replace(a, b) # Find number of required shape attributes (excluding basis) # And number of bones num_shapes = 0 num_bones = 0 num_partsegments = 0 for ob in scn.objects: if ob.type == 'MESH': # TODO: manage materials attached to object if mat in list(ob.data.materials): if ob.data.shape_keys: num_shapes = max(num_shapes, len(ob.data.shape_keys.key_blocks) - 1) if ob.particle_systems: num_partsegments = 1 # TODO check correct p systems and segments if ob.parent and ob.parent.type == 'ARMATURE' and not ob.parent_bone and not ob.get('apply_armature'): num_bones = max(num_bones, len([b for b in ob.parent.data.bones if b.use_deform])) if num_shapes: shader['attributes'].append({'type':99, 'count': num_shapes, 'varname': ''}) if num_partsegments: shader['attributes'].append({'type':77, 'count': num_partsegments, 'varname': ''}) if num_bones: shader['attributes'].append({'type':88, 'count': num_bones, 'varname': ''}) last_lamp = "" for u in shader['uniforms']: if 'lamp' in u: u['lamp'] = last_lamp = u['lamp'].name if 'image' in u: # TODO: if the image is used in several textures, how can we know which? slots = list(mat.texture_slots) if mat.use_nodes: for node in mat.node_tree.nodes: if node.type=='MATERIAL' and node.material: slots.extend(node.material.texture_slots) texture_slot = [t for t in slots if t and t.texture and t.texture.type=='IMAGE' and t.texture.image==u['image']] if not texture_slot: print("Warning: image %s not found in material %s."%(u['image'].name, mat.name)) u['filter'] = True u['wrap'] = 'R' else: u['filter'] = texture_slot[0].texture.use_interpolation u['wrap'] = 'R' if texture_slot[0].texture.extension == 'REPEAT' else 'C' u['size'] = 0 fpath = bpy.path.abspath(u['image'].filepath) if os.path.exists(fpath): u['size'] = os.path.getsize(fpath) u['filepath'] = u['image'].name + '.' + u['image'].filepath.rsplit('.',1)[1] u['image'] = u['image'].name tex_sizes[u['image']] = u['size'] if 'texpixels' in u: # Minimum shadow buffer is 128x128 if u['texsize'] > 16000: # This is a shadow buffer u['type'] = gpu.GPU_DYNAMIC_SAMPLER_2DSHADOW del u['texpixels'] # we don't need this # Assuming a lamp uniform is always sent before this one u['lamp'] = last_lamp # TODO: send lamp stuff else: # It's a ramp # encode as PNG data URI import struct, zlib def png_chunk(ty, data): return struct.pack('>I',len(data)) + ty + data +\ struct.pack('>I',zlib.crc32(ty + data)) u['filepath'] = 'data:image/png;base64,' + base64.b64encode( b'\x89PNG\r\n\x1a\n'+png_chunk(b'IHDR', struct.pack('>IIBBBBB', 256, 1, 8, 6, 0, 0, 0))+ png_chunk(b'IDAT', zlib.compress( b'\x00'+u['texpixels'][:1024])) + png_chunk(b'IEND', b'') #for some reason is 257px? ).decode() u['image'] = hex(hash(u['filepath']))[-10:] u['wrap'] = 'C' # clamp to edge u['type'] = gpu.GPU_DYNAMIC_SAMPLER_2DIMAGE u['size'] = 0 del u['texpixels'] # Engine builds its own vertex shader del shader['vertex'] shader['double_sided'] = not mat.game_settings.use_backface_culling shader['type'] = 'MATERIAL' shader['name'] = mat.name shader['scene'] = scn.name ret = dumps(shader).encode('utf8') mat['hash'] = hash(ret) % 2147483648 return ret
def export_material(self, state, material): shader_data = gpu.export_shader(bpy.context.scene, material) if state['settings']['asset_profile'] == 'DESKTOP': shader_converter.to_130(shader_data) else: shader_converter.to_web(shader_data) if self.settings.embed_shaders is True: fs_bytes = shader_data['fragment'].encode() fs_uri = 'data:text/plain;base64,' + base64.b64encode( fs_bytes).decode('ascii') vs_bytes = shader_data['vertex'].encode() vs_uri = 'data:text/plain;base64,' + base64.b64encode( vs_bytes).decode('ascii') else: names = [ bpy.path.clean_name(name) + '.glsl' for name in (material.name + 'VS', material.name + 'FS') ] data = (shader_data['vertex'], shader_data['fragment']) for name, data in zip(names, data): filename = os.path.join(state['settings']['gltf_output_dir'], name) with open(filename, 'w') as fout: fout.write(data) vs_uri, fs_uri = names state['shaders'].append({'type': 35632, 'uri': fs_uri}) state['shaders'].append({'type': 35633, 'uri': vs_uri}) # Handle programs state['programs'].append({ 'attributes': [a['varname'] for a in shader_data['attributes']], 'fragmentShader': 'shader_{}_FS'.format(material.name), 'vertexShader': 'shader_{}_VS'.format(material.name), }) # Handle parameters/values values = {} parameters = {} for attribute in shader_data['attributes']: name = attribute['varname'] semantic = gpu_luts.TYPE_TO_SEMANTIC[attribute['type']] _type = gpu_luts.DATATYPE_TO_GLTF_TYPE[attribute['datatype']] parameters[name] = {'semantic': semantic, 'type': _type} for uniform in shader_data['uniforms']: valname = gpu_luts.TYPE_TO_NAME.get(uniform['type'], uniform['varname']) rnaname = valname semantic = None node = None value = None if uniform['varname'] == 'bl_ModelViewMatrix': semantic = 'MODELVIEW' elif uniform['varname'] == 'bl_ProjectionMatrix': semantic = 'PROJECTION' elif uniform['varname'] == 'bl_NormalMatrix': semantic = 'MODELVIEWINVERSETRANSPOSE' else: if uniform['type'] in gpu_luts.LAMP_TYPES: node = uniform['lamp'].name valname = node + '_' + valname semantic = gpu_luts.TYPE_TO_SEMANTIC.get( uniform['type'], None) if not semantic: lamp_obj = bpy.data.objects[node] value = getattr(lamp_obj.data, rnaname) elif uniform['type'] in gpu_luts.MIST_TYPES: valname = 'mist_' + valname mist_settings = bpy.context.scene.world.mist_settings if valname == 'mist_color': value = bpy.context.scene.world.horizon_color else: value = getattr(mist_settings, rnaname) if valname == 'mist_falloff': if value == 'QUADRATIC': value = 0.0 elif value == 'LINEAR': value = 1.0 else: value = 2.0 elif uniform['type'] in gpu_luts.WORLD_TYPES: world = bpy.context.scene.world value = getattr(world, rnaname) elif uniform['type'] in gpu_luts.MATERIAL_TYPES: converter = gpu_luts.DATATYPE_TO_CONVERTER[ uniform['datatype']] value = converter(getattr(material, rnaname)) values[valname] = value elif uniform['type'] == gpu.GPU_DYNAMIC_SAMPLER_2DIMAGE: texture_slots = [ slot for slot in material.texture_slots if slot and slot.texture.type == 'IMAGE' ] for slot in texture_slots: if slot.texture.image.name == uniform['image'].name: value = 'texture_' + slot.texture.name values[uniform['varname']] = value else: print('Unconverted uniform:', uniform) parameter = {} if semantic: parameter['semantic'] = semantic if node: parameter['node'] = 'node_' + node elif value: parameter['value'] = gpu_luts.DATATYPE_TO_CONVERTER[ uniform['datatype']](value) else: parameter['value'] = None if uniform['type'] == gpu.GPU_DYNAMIC_SAMPLER_2DIMAGE: parameter['type'] = 35678 # SAMPLER_2D else: parameter['type'] = gpu_luts.DATATYPE_TO_GLTF_TYPE[ uniform['datatype']] parameters[valname] = parameter uniform['valname'] = valname # Handle techniques tech_name = 'technique_' + material.name state['techniques'].append({ 'parameters': parameters, 'program': 'program_' + material.name, 'attributes': {a['varname']: a['varname'] for a in shader_data['attributes']}, 'uniforms': {u['varname']: u['valname'] for u in shader_data['uniforms']}, }) return {'technique': tech_name, 'values': values}
import bpy, gpu uniform_types = {} for k,v in gpu.__dict__.items(): if isinstance(v,int) and k.startswith('GPU_DYNAMIC_'): uniform_types[v] = k[12:] data_types = ['0','1i','1f','2f','3f','4f','m3','m4','4ub'] attr_types = {6: 'CD_MCOL', 5: 'CD_MTFACE', 14: 'CD_ORCO', 18: 'CD_TANGENT'} shader = gpu.export_shader(bpy.context.scene, bpy.context.object.material_slots[0].material) shader['uniforms'].sort(key=lambda e: uniform_types.get(e['type'],'zzz')) print('------') for uniform in shader['uniforms']: ucopy = uniform.copy() if 'texpixels' in ucopy: del ucopy['texpixels'] del ucopy['type'] del ucopy['datatype'] #del ucopy['varname'] utype_str = uniform_types.get(uniform['type'],'unknown '+str(uniform['type'])) print(utype_str, data_types[uniform['datatype']], ucopy)
import bpy, gpu uniform_types = {} for k, v in gpu.__dict__.items(): if isinstance(v, int) and k.startswith('GPU_DYNAMIC_'): uniform_types[v] = k[12:] data_types = ['0', '1i', '1f', '2f', '3f', '4f', 'm3', 'm4', '4ub'] attr_types = {6: 'CD_MCOL', 5: 'CD_MTFACE', 14: 'CD_ORCO', 18: 'CD_TANGENT'} shader = gpu.export_shader(bpy.context.scene, bpy.context.object.material_slots[0].material) shader['uniforms'].sort(key=lambda e: uniform_types.get(e['type'], 'zzz')) print('------') for uniform in shader['uniforms']: ucopy = uniform.copy() if 'texpixels' in ucopy: del ucopy['texpixels'] del ucopy['type'] del ucopy['datatype'] #del ucopy['varname'] utype_str = uniform_types.get(uniform['type'], 'unknown ' + str(uniform['type'])) print(utype_str, data_types[uniform['datatype']], ucopy)
def export_materials(settings, materials, shaders, programs, techniques): def export_material(material): return { 'values': { 'diffuse': list( (material.diffuse_color * material.diffuse_intensity)[:]) + [material.alpha], 'specular': list( (material.specular_color * material.specular_intensity)[:]) + [material.specular_alpha], 'emission': list((material.diffuse_color * material.emit)[:]) + [material.alpha], 'ambient': [material.ambient] * 4, 'shininess': material.specular_hardness, 'textures': [ ts.texture.name for ts in material.texture_slots if ts and ts.texture.type == 'IMAGE' ], 'uv_layers': [ts.uv_layer for ts in material.texture_slots if ts] } } exp_materials = {} for material in materials: if settings['materials_export_shader'] == False: exp_materials[material.name] = export_material(material) else: # Handle shaders shader_data = gpu.export_shader(bpy.context.scene, material) if settings['asset_profile'] == 'DESKTOP': shader_converter.to_130(shader_data) else: shader_converter.to_web(shader_data) fs_bytes = shader_data['fragment'].encode() fs_uri = 'data:text/plain;base64,' + base64.b64encode( fs_bytes).decode('ascii') shaders[material.name + 'FS'] = {'type': 35632, 'uri': fs_uri} vs_bytes = shader_data['vertex'].encode() vs_uri = 'data:text/plain;base64,' + base64.b64encode( vs_bytes).decode('ascii') shaders[material.name + 'VS'] = {'type': 35633, 'uri': vs_uri} # Handle programs programs[material.name + 'Program'] = { 'attributes': [a['varname'] for a in shader_data['attributes']], 'fragmentShader': material.name + 'FS', 'vertexShader': material.name + 'VS', } # Handle parameters/values values = {} parameters = {} for attribute in shader_data['attributes']: name = attribute['varname'] semantic = gpu_luts.TYPE_TO_SEMANTIC[attribute['type']] _type = gpu_luts.DATATYPE_TO_GLTF_TYPE[attribute['datatype']] parameters[name] = {'semantic': semantic, 'type': _type} for uniform in shader_data['uniforms']: valname = gpu_luts.TYPE_TO_NAME.get(uniform['type'], uniform['varname']) rnaname = valname semantic = None node = None value = None if uniform['type'] in gpu_luts.LAMP_TYPES: node = uniform['lamp'].name valname = node + '_' + valname semantic = gpu_luts.TYPE_TO_SEMANTIC.get( uniform['type'], None) if not semantic: lamp_obj = bpy.data.objects[node] value = getattr(lamp_obj.data, rnaname) elif uniform['type'] in gpu_luts.MIST_TYPES: valname = 'mist_' + valname settings = bpy.context.scene.world.mist_settings if valname == 'mist_color': value = bpy.context.scene.world.horizon_color else: value = getattr(settings, rnaname) if valname == 'mist_falloff': value = 0.0 if value == 'QUADRATIC' else 1.0 if 'LINEAR' else 2.0 elif uniform['type'] in gpu_luts.WORLD_TYPES: world = bpy.context.scene.world value = getattr(world, rnaname) elif uniform['type'] in gpu_luts.MATERIAL_TYPES: value = gpu_luts.DATATYPE_TO_CONVERTER[ uniform['datatype']](getattr(material, rnaname)) values[valname] = value else: print('Unconverted uniform:', uniform) parameter = {} if semantic: parameter['semantic'] = semantic parameter['node'] = node else: parameter['value'] = gpu_luts.DATATYPE_TO_CONVERTER[ uniform['datatype']](value) parameter['type'] = gpu_luts.DATATYPE_TO_GLTF_TYPE[ uniform['datatype']] parameters[valname] = parameter uniform['valname'] = valname # Handle techniques tech_name = material.name + 'Technique' techniques[tech_name] = { 'parameters': parameters, 'program': material.name + 'Program', 'attributes': { a['varname']: a['varname'] for a in shader_data['attributes'] }, 'uniforms': {u['varname']: u['valname'] for u in shader_data['uniforms']}, } exp_materials[material.name] = { 'technique': tech_name, 'values': values } # exp_materials[material.name] = {} return exp_materials
def export(scene, mat): shader = gpu.export_shader(scene, mat) us1 = [] us2 = [] ats = [] texvars = [] preload = [] bindtex = [] for x in mat.texture_slots: if x: print("huh", x.name, x.texture) print("DYNCO!!", gpu.GPU_DYNAMIC_LAMP_DYNCO) for u in shader["uniforms"]: if ((u["type"] != gpu.GPU_DYNAMIC_SAMPLER_2DBUFFER) and (u["type"] != gpu.GPU_DYNAMIC_SAMPLER_2DIMAGE) and (u["type"] != gpu.GPU_DYNAMIC_SAMPLER_2DSHADOW)): continue identifier = "sampler-" + str(u['texnumber']) + "-texture" gvar = identifier env = "usr" # TODO: move this to s_expr if u["type"] == gpu.GPU_DYNAMIC_SAMPLER_2DBUFFER: line = "(preload-buffer \"%s\" %d %d %s)" % (identifier, u['texsize'], env) elif u["type"] == gpu.GPU_DYNAMIC_SAMPLER_2DIMAGE: line = "(preload-image %d %d \"%s\" %s)" % ( u['image'].size[0], u['image'].size[1], "/home/nha/data/blender_glsl_material/test" + u['image'].filepath, env) elif u["type"] == gpu.GPU_DYNAMIC_SAMPLER_2DSHADOW: line = "(preload-shadow-buffer \"lamp-%s\" %d %s)" % ( io_material_glsl.format.s_expression.valid_id( u['lamp'].name), u['lamp'].data.shadow_buffer_size, env) texvars.append("(define %s #<unspecified>)" % (gvar)) bindtex.append("(sampler \"%s\" sampler-%d-bind-index %s %s)" % (u['varname'], u['texnumber'], gvar, env)) preload.append("(set! %s %s)" % (gvar, line)) seen = set() for uniform in shader["uniforms"]: defs, u = map_u(mat, uniform, seen, io_material_glsl.format.s_expression) us1 += defs us2 += [u] for attribute in shader["attributes"]: ats += [map_atr(mat, attribute, io_material_glsl.format.s_expression)] #us1.sort() #us2.sort() #print('\n'.join(us1)) # print('\n'.join(us2)) #print(ats) # TODO: fix this header stuff header = """ #version 130 #extension GL_ARB_texture_query_lod : enable #extension GL_EXT_gpu_shader4: enable #extension GL_ARB_draw_instanced: enable #define GPU_ATI #define CLIP_WORKAROUND #define BUMP_BICUBIC 1 """ file = "/home/nha/data/blender_glsl_material/render-osg/test/" + mat.name try: os.remove(file + ".vert") os.remove(file + ".frag") os.remove(file + ".scm") except: pass with open(file + ".vert.in", "w") as f: # f.write(header) f.write(shader["vertex"]) with open(file + ".frag.in", "w") as f: # f.write(header) f.write(shader["fragment"]) with open(file + ".scm", "w") as f: preload_txt = "(define (preload usr)\n (begin\n %s))\n\n" % ( '\n '.join(preload)) bind_txt = "(define (bind-samplers usr)\n (begin\n %s))\n\n" % ( '\n '.join(bindtex)) texvars_txt = '\n'.join(texvars) + '\n\n' f.write( io_material_glsl.format.s_expression.group_defines( io_material_glsl.dnalink.is_global_group, io_material_glsl.dnalink.get_group, us1)) f.write(texvars_txt) f.write(preload_txt) f.write(bind_txt) f.write( io_material_glsl.format.s_expression.group_uniforms( io_material_glsl.dnalink.all_groups, us2)) f.write(io_material_glsl.format.s_expression.group_attributes(ats))
def get_dynamic_constants(mat, scn, paths, code): # Example: #paths = [ #'node_tree.nodes["slicep"].outputs[0].default_value', #'node_tree.nodes["slicen"].outputs[0].default_value', #] restore = [] sentinels = [] lookup_data = [] varnames_types = [] for p in paths: # We're assuming "%f"%sentinel yelds exactly the same string # as the code generator (sprintf is used in both cases) sentinel = random() while ("%f" % sentinel) in code or sentinel in sentinels: sentinel = random() if p.startswith('nodes['): p = 'node_tree.' + p try: orig_obj = mat.path_resolve(p) except ValueError: lookup_data.append([0, 0, 0]) continue obj, attr = ('.' + p).rsplit('.', 1) obj = eval('mat' + obj) #print(obj, attr) value = orig_obj is_vector = hasattr(orig_obj, '__getitem__') orig_val = None if is_vector: value = list(orig_obj) orig_val = orig_obj[0] orig_obj[0] = sentinel vlen = len(orig_obj) else: vlen = 1 setattr(obj, attr, sentinel) lookup_data.append([sentinel, vlen, value]) restore.append([is_vector, obj, attr, orig_obj, orig_val]) scn.update() sh = gpu.export_shader(scn, mat) c = sh['fragment'].rsplit('}', 2)[1] for sentinel, vlen, value in lookup_data: if not sentinel: varnames_types.append([None, None, 0]) continue pos = c.find("%f" % sentinel) if pos != -1: varname = c[:pos].rsplit(' ', 3)[1] varnames_types.append([varname, vlen, value]) else: varnames_types.append([None, vlen, value]) for is_vector, obj, attr, orig_obj, orig_val in restore: # restore original if is_vector: orig_obj[0] = orig_val else: setattr(obj, attr, orig_obj) if any(varnames_types): scn.update() #pprint(varnames_types) return [varnames_types, sh]
def export_materials(settings, materials, shaders, programs, techniques): def export_material(material): return { 'values': { 'diffuse': list((material.diffuse_color * material.diffuse_intensity)[:]) + [material.alpha], 'specular': list((material.specular_color * material.specular_intensity)[:]) + [material.specular_alpha], 'emission': list((material.diffuse_color * material.emit)[:]) + [material.alpha], 'ambient': [material.ambient] * 4, 'shininess': material.specular_hardness, 'textures': [ts.texture.name for ts in material.texture_slots if ts and ts.texture.type == 'IMAGE'], 'uv_layers': [ts.uv_layer for ts in material.texture_slots if ts] } } exp_materials = {} for material in materials: if settings['materials_export_shader'] == False: exp_materials[material.name] = export_material(material) else: # Handle shaders shader_data = gpu.export_shader(bpy.context.scene, material) if settings['asset_profile'] == 'DESKTOP': shader_converter.to_130(shader_data) else: shader_converter.to_web(shader_data) fs_bytes = shader_data['fragment'].encode() fs_uri = 'data:text/plain;base64,' + base64.b64encode(fs_bytes).decode('ascii') shaders[material.name+'FS'] = {'type': 35632, 'uri': fs_uri} vs_bytes = shader_data['vertex'].encode() vs_uri = 'data:text/plain;base64,' + base64.b64encode(vs_bytes).decode('ascii') shaders[material.name+'VS'] = {'type': 35633, 'uri': vs_uri} # Handle programs programs[material.name+'Program'] = { 'attributes' : [a['varname'] for a in shader_data['attributes']], 'fragmentShader' : material.name+'FS', 'vertexShader' : material.name+'VS', } # Handle parameters/values values = {} parameters = {} for attribute in shader_data['attributes']: name = attribute['varname'] semantic = gpu_luts.TYPE_TO_SEMANTIC[attribute['type']] _type = gpu_luts.DATATYPE_TO_GLTF_TYPE[attribute['datatype']] parameters[name] = {'semantic': semantic, 'type': _type} for uniform in shader_data['uniforms']: valname = gpu_luts.TYPE_TO_NAME.get(uniform['type'], uniform['varname']) rnaname = valname semantic = None node = None value = None if uniform['type'] in gpu_luts.LAMP_TYPES: node = uniform['lamp'].name valname = node + '_' + valname semantic = gpu_luts.TYPE_TO_SEMANTIC.get(uniform['type'], None) if not semantic: lamp_obj = bpy.data.objects[node] value = getattr(lamp_obj.data, rnaname) elif uniform['type'] in gpu_luts.MIST_TYPES: valname = 'mist_' + valname settings = bpy.context.scene.world.mist_settings if valname == 'mist_color': value = bpy.context.scene.world.horizon_color else: value = getattr(settings, rnaname) if valname == 'mist_falloff': value = 0.0 if value == 'QUADRATIC' else 1.0 if 'LINEAR' else 2.0 elif uniform['type'] in gpu_luts.WORLD_TYPES: world = bpy.context.scene.world value = getattr(world, rnaname) elif uniform['type'] in gpu_luts.MATERIAL_TYPES: value = gpu_luts.DATATYPE_TO_CONVERTER[uniform['datatype']](getattr(material, rnaname)) values[valname] = value else: print('Unconverted uniform:', uniform) parameter = {} if semantic: parameter['semantic'] = semantic parameter['node'] = node else: parameter['value'] = gpu_luts.DATATYPE_TO_CONVERTER[uniform['datatype']](value) parameter['type'] = gpu_luts.DATATYPE_TO_GLTF_TYPE[uniform['datatype']] parameters[valname] = parameter uniform['valname'] = valname # Handle techniques tech_name = material.name + 'Technique' techniques[tech_name] = { 'parameters' : parameters, 'program' : material.name+'Program', 'attributes' : {a['varname'] : a['varname'] for a in shader_data['attributes']}, 'uniforms' : {u['varname'] : u['valname'] for u in shader_data['uniforms']}, } exp_materials[material.name] = {'technique': tech_name, 'values': values} # exp_materials[material.name] = {} return exp_materials
def mat_to_json_try(mat, scn): global SHADER_LIB global get_animation_data_strips if not get_animation_data_strips: from . import exporter get_animation_data_strips = exporter.get_animation_data_strips # NOTE: This export is replaced later shader = gpu.export_shader(scn, mat) set_shader_lib(shader['fragment']) parts = shader['fragment'].rsplit('}', 2) shader['fragment'] = ('\n' + parts[1] + '}') # Stuff for debugging shaders # TODO write only when they have changed # if os.name != 'nt': # SHM = "/run/shm/" # open(SHM + mat.name+'.v','w').write(shader['vertex']) # open(SHM + mat.name+'.f','w').write(shader['fragment']) # try: # shader['fragment']=open(SHM + mat.name+'.f2').read() # except: # pass #from pprint import pprint #pprint(shader['attributes']) # --------------------------- # # Checking hash of main() is not enough # code_hash = hash(shader['fragment']) % 2147483648 #print(shader['fragment'].split('{')[0]) print(mat.name) strips, drivers = get_animation_data_strips(mat.animation_data) if mat.node_tree and mat.node_tree.nodes: s, d = get_animation_data_strips(mat.node_tree.animation_data) strips += s drivers += d if 1: # Dynamic uniforms (for animation, per object variables, # particle layers or other custom stuff) dyn_consts = [] # Old custom_uniforms API block = bpy.data.texts.get('custom_uniforms') if block: paths = [x for x in block.as_string().split('\n') if x] #print(paths) else: paths = [] # Animated custom_uniforms to_unmute = [] for strip in strips: last_path = '' for f in bpy.data.actions[strip['action']].fcurves: if not f.mute: f.mute = True to_unmute.append(f) if f.data_path != last_path: last_path = f.data_path if last_path not in paths: paths.append(last_path) paths += drivers # drivers is a list of data_paths for now dyn_consts = [] dyn_ecounts = [] dyn_values = [] # NOTE: This _replaces_ the previously exported shader dyn_stuff, shader = get_dynamic_constants(mat, scn, paths, shader['fragment']) for a, b, c in dyn_stuff: dyn_consts.append(a) dyn_ecounts.append(b) dyn_values.append(c) # We're repeating what we did at the beginning shader['fragment'] = ('\n' + shader['fragment'].rsplit('}', 2)[1] + '}') premain, main = shader['fragment'].split('{') # Restore previously muted fcurves for f in to_unmute: f.mute = False # Get list of unknown varyings and save them # as replacement strings known = [ 'var' + a['varname'][3:] for a in shader['attributes'] if a['varname'] ] varyings = [ x[:-1].split() for x in premain.split('\n') if x.startswith('varying') and len(x) < 21 # this filters varposition/varnormal ] replacements = [] for v in varyings: if v[2] not in known: replacements.append( (' '.join(v), 'const {t} {n} = {t}(0.0)'.format(t=v[1], n=v[2]))) shader['fragment'] = shader['fragment'].replace('sampler2DShadow','sampler2D')\ .replace('\nin ', '\nvarying ') shader['fragment'] = re.sub(r'[^\x00-\x7f]', r'', shader['fragment']) if any(dyn_consts): # Separate constants from the rest #print(mat.name,'===>',premain) lines = premain.split('\n') # This generates a dictionary with the var name # and comma-separated values in a string, like this: # {'cons123': '1.23, 4.56, 7.89', ...} consts = dict([(c[2], c[3].split('(')[1][:-2]) for c in [l.split(' ', 3) for l in lines] if c[0] == 'const']) premain = '\n'.join(l for l in lines if not l.startswith('cons')) # Convert them back to constants, except for dynamic ones lines = [] TYPES = [ 'float', 'vec2', 'vec3', 'vec4', '', '', '', '', 'mat3', '', '', '', '', '', '', 'mat4' ] types = [None] * len(dyn_consts) for k, v in consts.items(): t = TYPES[v.count(',')] #print(k, dyn_consts, k in dyn_consts) if k in dyn_consts: lines.append('uniform {0} {1};'.format(t, k)) # GL type may differ from value dyn_ecounts[dyn_consts.index(k)] = v.count(',') + 1 else: lines.append('const {0} {1} = {0}({2});'.format(t, k, v)) shader['fragment'] = '\n'.join(lines) + '\n' + premain + '{' + main shader['uniforms'] += [ # TODO: index is never used as it's always in order, # remove it after ensuring in legacy engine { 'type': -1, 'varname': c or '', 'count': dyn_ecounts[i], 'index': i, 'path': paths[i], 'value': dyn_values[i] } for i, c in enumerate(dyn_consts) ] # mat['code_hash'] = code_hash for a, b in replacements: shader['fragment'] = shader['fragment'].replace(a, b) if mat.game_settings.alpha_blend == 'CLIP': shader['fragment'] = re.sub( r'gl_FragColor = ([^;]*)', r'if(\1.a < 0.5)discard; gl_FragColor = \1', shader['fragment']) # Find number of required shape attributes (excluding basis) # And number of bones num_shapes = 0 num_bones = 0 num_partsegments = 0 weights6 = False for ob in scn.objects: if ob.type == 'MESH': # TODO: manage materials attached to object if mat in list(ob.data.materials): if ob.data.shape_keys: num_shapes = max(num_shapes, len(ob.data.shape_keys.key_blocks) - 1) if ob.particle_systems: num_partsegments = 1 # TODO check correct p systems and segments if ob.parent and ob.parent.type == 'ARMATURE' and ob.parent_type != 'BONE' and not ob.get( 'apply_armature'): #print("Material", mat.name, "has armature", ob.parent.name, "because of mesh", ob.name) num_bones = max( num_bones, len([b for b in ob.parent.data.bones if b.use_deform])) if ob.get('weights6'): weights6 = True if num_shapes: shader['attributes'].append({ 'type': 99, 'count': num_shapes, 'varname': '' }) if num_partsegments: shader['attributes'].append({ 'type': 77, 'count': num_partsegments, 'varname': '' }) if num_bones: t = 86 if weights6 else 88 shader['attributes'].append({ 'type': t, 'count': num_bones, 'varname': '' }) last_lamp = "" param_mats = {} for u in shader['uniforms']: if 'lamp' in u: u['lamp'] = last_lamp = u['lamp'].name if 'image' in u: # TODO: if the image is used in several textures, how can we know which? slots = list(mat.texture_slots) if mat.use_nodes: for node in mat.node_tree.nodes: if node.type == 'MATERIAL' and node.material: slots.extend(node.material.texture_slots) texture_slot = [ t for t in slots if t and t.texture and t.texture.type == 'IMAGE' and t.texture.image == u['image'] ] if not texture_slot: print("Warning: image %s not found in material %s." % (u['image'].name, mat.name)) u['filter'] = True u['wrap'] = 'R' else: u['filter'] = texture_slot[0].texture.use_interpolation u['wrap'] = 'R' if texture_slot[ 0].texture.extension == 'REPEAT' else 'C' u['size'] = 0 fpath = bpy.path.abspath(u['image'].filepath) if os.path.isfile(fpath): u['size'] = os.path.getsize(fpath) # u['filepath'] is only used in old versions of the engine u['filepath'] = u['image'].name + '.' + u['image'].get( 'exported_extension', fpath.split('.').pop()) u['image'] = u['image'].name tex_sizes[u['image']] = u['size'] if 'texpixels' in u: # Minimum shadow buffer is 128x128 if u['texsize'] > 16000: # This is a shadow buffer u['type'] = gpu.GPU_DYNAMIC_SAMPLER_2DSHADOW del u['texpixels'] # we don't need this # Assuming a lamp uniform is always sent before this one u['lamp'] = last_lamp # TODO: send lamp stuff else: # It's a ramp # encode as PNG data URI # TODO: Store this in the JSON texture list when the old engine # is no longer used def png_chunk(ty, data): return struct.pack('>I',len(data)) + ty + data +\ struct.pack('>I',zlib.crc32(ty + data)) u['filepath'] = 'data:image/png;base64,' + base64.b64encode( b'\x89PNG\r\n\x1a\n' + png_chunk(b'IHDR', struct.pack('>IIBBBBB', 256, 1, 8, 6, 0, 0, 0)) + png_chunk(b'IDAT', zlib.compress(b'\x00' + u['texpixels'][:1024])) + png_chunk(b'IEND', b'') #for some reason is 257px? ).decode() u['image'] = hex(hash(u['filepath']))[-15:] u['wrap'] = 'C' # clamp to edge u['type'] = gpu.GPU_DYNAMIC_SAMPLER_2DIMAGE u['size'] = 0 del u['texpixels'] if 'material' in u: param_mats[u['material'].name] = u['material'] u['material'] = u['material'].name # Detect unused varyings (attributes) for line in shader['fragment'].split('\n'): # len<22 filters varposition/varnormal if len(line) < 22 and line.startswith('varying'): _, gltype, name = line[:-1].split(' ') used = False for attr in shader['attributes']: if attr['varname'][3:] == name[3:]: used = True break if not used: shader['attributes'].append( dict( type=-1, # UNUSED varname=name, gltype=gltype, )) # Engine builds its own vertex shader del shader['vertex'] shader['double_sided'] = not mat.game_settings.use_backface_culling shader['type'] = 'MATERIAL' shader['material_type'] = 'BLENDER_INTERNAL' shader['name'] = mat.name shader['scene'] = scn.name shader['params'] = [{ 'name': m.name, 'diffuse_color': list(m.diffuse_color), 'diffuse_intensity': m.diffuse_intensity, 'specular_color': list(m.specular_color), 'specular_intensity': m.specular_intensity, 'specular_hardness': m.specular_hardness, 'emit': m.emit, 'alpha': m.alpha, } for m in param_mats.values()] shader['animation_strips'] = strips shader['fragment_hash'] = hash(shader['fragment']) ret = loads(dumps(shader)) # mat['hash'] = hash(ret) % 2147483648 return ret
def export_materials(settings, materials, shaders, programs, techniques): def export_material(material): all_textures = [ ts for ts in material.texture_slots if ts and ts.texture.type == 'IMAGE' ] diffuse_textures = [ 'texture_' + t.texture.name for t in all_textures if t.use_map_color_diffuse ] emission_textures = [ 'texture_' + t.texture.name for t in all_textures if t.use_map_emit ] specular_textures = [ 'texture_' + t.texture.name for t in all_textures if t.use_map_color_spec ] diffuse_color = list( (material.diffuse_color * material.diffuse_intensity)[:]) + [material.alpha] emission_color = list( (material.diffuse_color * material.emit)[:]) + [material.alpha] specular_color = list( (material.specular_color * material.specular_intensity)[:]) + [material.specular_alpha] technique = 'PHONG' if material.use_shadeless: technique = 'CONSTANT' emission_textures = diffuse_textures emission_color = diffuse_color elif material.specular_intensity == 0.0: technique = 'LAMBERT' elif material.specular_shader == 'BLINN': technique = 'BLINN' return { 'extensions': { 'KHR_materials_common': { 'technique': technique, 'values': { 'ambient': ([material.ambient] * 3) + [1.0], 'diffuse': diffuse_textures[-1] if diffuse_textures else diffuse_color, 'doubleSided': not material.game_settings.use_backface_culling, 'emission': emission_textures[-1] if emission_textures else emission_color, 'specular': specular_textures[-1] if specular_textures else specular_color, 'shininess': material.specular_hardness, 'transparency': material.alpha, 'transparent': material.use_transparency, } } }, 'name': material.name, } exp_materials = {} for material in materials: if settings['shaders_data_storage'] == 'NONE': exp_materials['material_' + material.name] = export_material(material) else: # Handle shaders shader_data = gpu.export_shader(bpy.context.scene, material) if settings['asset_profile'] == 'DESKTOP': shader_converter.to_130(shader_data) else: shader_converter.to_web(shader_data) fs_name = 'shader_{}_FS'.format(material.name) vs_name = 'shader_{}_VS'.format(material.name) storage_setting = settings['shaders_data_storage'] if storage_setting == 'EMBED': fs_bytes = shader_data['fragment'].encode() fs_uri = 'data:text/plain;base64,' + base64.b64encode( fs_bytes).decode('ascii') vs_bytes = shader_data['vertex'].encode() vs_uri = 'data:text/plain;base64,' + base64.b64encode( vs_bytes).decode('ascii') elif storage_setting == 'EXTERNAL': names = [ bpy.path.clean_name(name) + '.glsl' for name in (material.name + 'VS', material.name + 'FS') ] data = (shader_data['vertex'], shader_data['fragment']) for name, data in zip(names, data): filename = os.path.join(settings['gltf_output_dir'], name) with open(filename, 'w') as fout: fout.write(data) vs_uri, fs_uri = names else: print( 'Encountered unknown option ({}) for shaders_data_storage setting' .format(storage_setting)) shaders[fs_name] = {'type': 35632, 'uri': fs_uri} shaders[vs_name] = {'type': 35633, 'uri': vs_uri} # Handle programs programs['program_' + material.name] = { 'attributes': [a['varname'] for a in shader_data['attributes']], 'fragmentShader': 'shader_{}_FS'.format(material.name), 'vertexShader': 'shader_{}_VS'.format(material.name), } # Handle parameters/values values = {} parameters = {} for attribute in shader_data['attributes']: name = attribute['varname'] semantic = gpu_luts.TYPE_TO_SEMANTIC[attribute['type']] _type = gpu_luts.DATATYPE_TO_GLTF_TYPE[attribute['datatype']] parameters[name] = {'semantic': semantic, 'type': _type} for uniform in shader_data['uniforms']: valname = gpu_luts.TYPE_TO_NAME.get(uniform['type'], uniform['varname']) rnaname = valname semantic = None node = None value = None if uniform['varname'] == 'bl_ModelViewMatrix': semantic = 'MODELVIEW' elif uniform['varname'] == 'bl_ProjectionMatrix': semantic = 'PROJECTION' elif uniform['varname'] == 'bl_NormalMatrix': semantic = 'MODELVIEWINVERSETRANSPOSE' else: if uniform['type'] in gpu_luts.LAMP_TYPES: node = uniform['lamp'].name valname = node + '_' + valname semantic = gpu_luts.TYPE_TO_SEMANTIC.get( uniform['type'], None) if not semantic: lamp_obj = bpy.data.objects[node] value = getattr(lamp_obj.data, rnaname) elif uniform['type'] in gpu_luts.MIST_TYPES: valname = 'mist_' + valname mist_settings = bpy.context.scene.world.mist_settings if valname == 'mist_color': value = bpy.context.scene.world.horizon_color else: value = getattr(mist_settings, rnaname) if valname == 'mist_falloff': value = 0.0 if value == 'QUADRATIC' else 1.0 if 'LINEAR' else 2.0 elif uniform['type'] in gpu_luts.WORLD_TYPES: world = bpy.context.scene.world value = getattr(world, rnaname) elif uniform['type'] in gpu_luts.MATERIAL_TYPES: value = gpu_luts.DATATYPE_TO_CONVERTER[ uniform['datatype']](getattr(material, rnaname)) values[valname] = value elif uniform['type'] == gpu.GPU_DYNAMIC_SAMPLER_2DIMAGE: for ts in [ ts for ts in material.texture_slots if ts and ts.texture.type == 'IMAGE' ]: if ts.texture.image.name == uniform['image'].name: value = 'texture_' + ts.texture.name values[uniform['varname']] = value else: print('Unconverted uniform:', uniform) parameter = {} if semantic: parameter['semantic'] = semantic if node: parameter['node'] = 'node_' + node else: parameter['value'] = gpu_luts.DATATYPE_TO_CONVERTER[ uniform['datatype']](value) if uniform['type'] == gpu.GPU_DYNAMIC_SAMPLER_2DIMAGE: parameter['type'] = 35678 #SAMPLER_2D else: parameter['type'] = gpu_luts.DATATYPE_TO_GLTF_TYPE[ uniform['datatype']] parameters[valname] = parameter uniform['valname'] = valname # Handle techniques tech_name = 'technique_' + material.name techniques[tech_name] = { 'parameters': parameters, 'program': 'program_' + material.name, 'attributes': { a['varname']: a['varname'] for a in shader_data['attributes'] }, 'uniforms': {u['varname']: u['valname'] for u in shader_data['uniforms']}, } exp_materials['material_' + material.name] = { 'technique': tech_name, 'values': values } # exp_materials[material.name] = {} return exp_materials
def get_shader_lib(mat_list): global SHADER_LIB if True: ## For GPUs unsupported by PBR branch, put an exported scene json in ## /tmp/myou-shader-lib.json and uncomment this code # try: # for ob in json.load(open('/tmp/myou-shader-lib.json')): # if ob['type'] == 'SHADER_LIB': # SHADER_LIB = ob['code'] # return SHADER_LIB # except: # pass import bpy, gpu mat = bpy.data.materials.new('delete_me') fragment = gpu.export_shader(bpy.context.scene, mat)['fragment'] bpy.data.materials.remove(mat) print('Converting shader lib') parts = fragment.rsplit('}', 2) SHADER_LIB = \ """ #ifdef GL_ES #if __VERSION__ < 130 #extension GL_OES_standard_derivatives : enable #extension GL_EXT_shader_texture_lod : enable #else #define texture2D texture #define texture2DLod textureLod #define textureCube texture #define textureCubeLod textureLod #define texture2DProj textureProj #define sample sample_ #endif precision highp float; precision highp int; #if __VERSION__ < 130 #ifdef GL_EXT_shader_texture_lod #define texture2DLod texture2DLodEXT #define textureCubeLod textureCubeLodEXT #else #define texture2DLod texture2D #define textureCubeLod textureCube #endif #endif #endif """ \ +defines+uniforms+(parts[0]+'}').replace('\r','')+'\n'+""" #ifdef SS_REFR uniform vec2 unfrefract_size_px, unfrefract_px_size; void screen_space_refraction(vec4 color, vec3 normal, float roughness, float ior, out vec4 out_col){ vec2 limit = (unfrefract_size_px-.5) * unfrefract_px_size; vec2 uv = gl_FragCoord.xy * unfrefract_px_size; uv += normal.xy*(ior-1.); out_col = texture2D(unfrefract, min(uv, limit)); out_col.r = srgb_to_linearrgb(out_col.r); out_col.g = srgb_to_linearrgb(out_col.g); out_col.b = srgb_to_linearrgb(out_col.b); // out_col = vec4(uv, texture2D(unfrefract, uv).g, 1.0); } #endif """ functions = get_patched_functions(SHADER_LIB) # remove unused functions # we're adding '' and 'material_preview_matcap' to ensure we add # the preamble and uniforms/globals, respectively used = { '', 'material_preview_matcap', 'mul', 'color_to_blender_normal_new_shading', 'distance_based_roughness' } call = re.compile(r"\b\w+\s*\(", flags=re.DOTALL) def add_used(code): for name in call.findall(code): name = name[:-1] if name in functions and name not in used: used.add(name) add_used(functions[name]) for m in mat_list: add_used(m['fragment']) print('GLSL functions: total', len(functions), 'used', len(used)) SHADER_LIB = ''.join(f for k, f in functions.items() if k in used).encode('ascii', 'ignore').decode() # This section below is necessary because something is interpreted as non ascii for some reason # despite the line above (which is also necessary, mysteriously...) splits = SHADER_LIB.split('BIT_OPERATIONS', 2) if len(splits) == 3: a, b, c = splits SHADER_LIB = a + 'BIT_OPERATIONS\n#endif' + c if debug_lib: open('/tmp/shader_lib.orig.glsl', 'w').write((parts[0] + '}').replace('\r', '') + '\n') open('/tmp/shader_lib.glsl', 'w').write(SHADER_LIB) return SHADER_LIB