diff --git a/ISSUES.md b/ISSUES.md index c746975..575b90e 100644 --- a/ISSUES.md +++ b/ISSUES.md @@ -3,9 +3,9 @@ ## P1 * [x] Take new screenshots of settings dialog (MUST BE COMPLETE BEFORE RELEASE) -* [*] Create a slide for performance, and how vertex colors can be faster and look better than baked AO -* [*] Add emphasis about how vertex color AO is much faster when dealing with lots of objects than AO -* [X] Download some models and bake AO on them for the gallery (At least one or two should be done by release.) +* [x] Create a slide for performance, and how vertex colors can be faster and look better than baked AO +* [x] Add emphasis about how vertex color AO is much faster when dealing with lots of objects than AO +* [x] Download some models and bake AO on them for the gallery (At least one or two should be done by release.) ## After release * [ ] Backport documentation from Blender Market to GitHub (can be done after release) diff --git a/README.md b/README.md index a68f542..9a0b440 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,10 @@ For a quick preview of vertex colors, you can also enter **Vertex Paint** mode ( # Changelog +## v0.1.6 + +* Added support for face normals; hard-surface ambient occlusion will be much improved. (Thanks to Joseph for reporting this issue!) + ## v0.1.5 * Added "Ignore Small Objects" feature to speed up bakes that would otherwise have many small objects contributing diff --git a/__init__.py b/__init__.py index b6be615..010f890 100644 --- a/__init__.py +++ b/__init__.py @@ -18,7 +18,7 @@ "name": "Vertex Oven", "description": "Bake ambient occlusion straight to vertex colors", "author": "Forest Katsch", - "version": (0, 1, 5), + "version": (0, 1, 6), "blender": (2, 80, 0), "location": "3D View > Object > Vertex Oven", "warning": "Warning: this addon is still young, and problems may occur. If you're concerned about this addon, make sure you've backed up your Blender file first.", @@ -92,6 +92,14 @@ def get_valid_keys(self): "ignore_small_objects", "small_object_size", ] + +class BakeVertexPoint: + + def __init__(self, position, normal, vertex_index, loop_index): + self.position = position + self.normal = normal + self.vertex_index = vertex_index + self.loop_index = loop_index # This never worked right. #class ProgressWidget(object): @@ -181,15 +189,15 @@ def __init__(self, options, context): # The objects that contribute to ambient occlusion on the receiving objects self.bake_cast_objects = [] - # The vertex we're on. This goes up until it reaches `len(mesh.vertices)`. - self.last_vertex_index = 0 + # The point we're on. This goes up until it reaches `len(self.points_to_bake)`. + self.last_point_index = 0 # self.ao_data is a dictionary of {vertex_index: ambient occlusion} - self.ao_data = {} + self.ao_data = [] # Returns a value within the range 0..100 def get_progress_percentage(self): - return (self.last_vertex_index / len(self.active_mesh.vertices)) * 100 + return (self.last_point_index / len(self.points_to_bake)) * 100 @classmethod def random_vector(cls): @@ -277,18 +285,22 @@ def jitter_vertex(self, vertex, sample): offset = (directions[0] * (np.random.uniform() * fraction)) + (directions[1] * (np.random.uniform() * fraction)) return vertex.co + offset + + # Returns a hashable string type that uniquely identifies `vertex_index` and `loop_index`. + def get_vertex_loop_id(self, vertex_index, loop_index): + return str(vertex_index) + ":" + str(loop_index) - def calculate_vertex_ao(self, vertex): + def calculate_vertex_ao(self, position, normal): """ Returns a value, 0-1, of how occluded this `vertex` is. Samples are taken for each object; the count is determined by `self.options.sample_count`. """ obj = self.active_object - normal = obj.matrix_world.to_3x3() @ vertex.normal - position = (obj.matrix_world @ vertex.co) + normal = obj.matrix_world.to_3x3() @ normal + position = (obj.matrix_world @ position) - offset = (normal * 0.0001) + offset = (normal * 0.00001) occlusion = 0 @@ -315,7 +327,7 @@ def calculate_vertex_ao(self, vertex): sample_position_object = sample_position - offset sample_distance = self.distance_to_object(sample_position_object, direction, obj_cache[1], obj_cache[2], obj_cache[3]) - + if sample_distance >= 0: distance = min(sample_distance, distance) @@ -407,12 +419,14 @@ def get_vertex_group(self): """Returns Blender's `VertexGroup` object.""" obj = self.active_object name = self.options.group_name + + print(obj.vertex_groups) if not obj.vertex_groups or name not in obj.vertex_groups: group = obj.vertex_groups.new() group.name = name - obj.vertex_groups.active = group + #obj.vertex_groups.active = group group = obj.vertex_groups[name] @@ -424,28 +438,25 @@ def apply_vertex_colors(self): mesh = self.active_mesh layer = self.get_vertex_color_layer() - for polygon in mesh.polygons: - for i, index in enumerate(polygon.vertices): - vertex = mesh.vertices[index] - brightness = self.ao_data[vertex.index] + for index, point in enumerate(self.points_to_bake): + brightness = self.ao_data[index] - if self.options.color_invert: - brightness = 1 - brightness + if self.options.color_invert: + brightness = 1 - brightness - loop_index = polygon.loop_indices[i] - layer.data[loop_index].color = (brightness, brightness, brightness, 1.0) + layer.data[point.loop_index].color = (brightness, brightness, brightness, 1.0) def apply_vertex_groups(self): """Apply `self.ao_data` to the vertex group.""" group = self.get_vertex_group() - - for vertex_index in self.ao_data: - weight = self.ao_data[vertex_index] - + + for index, point in enumerate(self.points_to_bake): + weight = self.ao_data[index] + if self.options.weight_invert: weight = 1 - weight - group.add([vertex_index], weight, "REPLACE") + group.add([point.vertex_index], weight, "REPLACE") def start(self): print("Baking vertex AO...") @@ -512,6 +523,23 @@ def start_object(self, obj): # Make sure to set our seed here, too. np.random.seed(self.options.seed) + + self.points_to_bake = [] + + mesh = self.active_mesh + mesh.calc_tangents() + mesh.calc_normals_split() + + print("Finding all points to be baked...") + for poly in mesh.polygons: + + for poly_vertex_index in range(len(poly.vertices)): + vertex_index = poly.vertices[poly_vertex_index] + loop_index = poly.loop_indices[poly_vertex_index] + + self.points_to_bake.append(BakeVertexPoint(mesh.vertices[vertex_index].co, mesh.loops[loop_index].normal, vertex_index, loop_index)) + + self.last_point_index = 0 return False @@ -535,23 +563,22 @@ def bake(self, vertices=-1): mesh = self.active_mesh i = 0 - - for vertex in mesh.vertices[self.last_vertex_index:]: - ao_vertex = self.calculate_vertex_ao(vertex) - - self.ao_data[vertex.index] = ao_vertex + while self.last_point_index < len(self.points_to_bake): - self.last_vertex_index += 1 + point = self.points_to_bake[self.last_point_index] - i += 1 + self.ao_data.append(self.calculate_vertex_ao(point.position, point.normal)) - if vertices > 0 and i > vertices: + self.last_point_index += 1 + i += 1 + + if i > vertices: return False - + self.finish_object() - self.last_vertex_index = 0 + self.last_point_index = 0 return self.start_next_object() @@ -569,7 +596,7 @@ def finish_object(self): self.apply_vertex_groups() - self.ao_data = {} + self.ao_data = [] print("Bake completed on '{}'".format(self.active_object.name))