Godot 4.5 这几天更新了,补全了模板缓冲,
在渲染Depth Buffer时,一般使用D32S8的类似格式。在Godot中,原本Depth Buffer就只渲染了Depth,因此S8的通道没有被使用。Stencil即特定材质可以向这个通道里写入内容,本文先称为Stencil通道,在后续过程中被其他材质读取使用。
但是直接使用 Stencil 中的 Outline 创建的几何体边框在
https://www.reddit.com/r/godot/comments/1nniemv/finally_clean_1pixel_outlines_in_godot_using/
The method still needs some work. I haven’t found a way to read the stencil buffer directly in a shader, so I created two post-processing effects: one that renders the stencil buffer in green and another in red to a SubViewport. Then I use that SubViewport texture for the edge detection.
Could you achieve this using a customized version of the default stencil outline (which uses BaseMaterial3D Grow), but change the grow effect to always be 1 pixel in screen space? Surely there's a way to compute this in a custom shader, without needing separate viewports.
I wish. The current grow technique moves the faces along their normals, leaving gaps in low poly geometry. I could simplify my shader a bunch if the stencil buffer as a whole could be passed to a shader, to my knowledge this is not possible as of version 4.5 but I would love to be proven wrong.
Edit for clarification: by simplify I mean remove the need for a subviewport
回顾 Unity 中的 Stencil Outline
方案一:生成平滑法线
I extended the SurfaceTool to bake smooth normals into the COLOR of my low poly models and used that to do the classic "pixel perfect" outline.
ColorOutline.gdshader
shader_type spatial;
render_mode unshaded, cull_front, depth_draw_always;
uniform float outline_width = 3.0;
uniform float distance_fade_max = 75.0;
uniform float distance_fade_min = 125.0;
void fragment() {
float distance_modifier = clamp(smoothstep(distance_fade_min, distance_fade_max, length(VERTEX)), 0.0, 1.0);
ALBEDO = vec3(0.0);
ALPHA = 0.8 * distance_modifier;
}
void vertex() {
vec4 clip_position = PROJECTION_MATRIX * (MODELVIEW_MATRIX * vec4(VERTEX, 1.0));
vec3 packed_normal = (COLOR.rgb * 2.0) - vec3(1.0);
vec3 clip_normal = mat3(PROJECTION_MATRIX) * (mat3(MODELVIEW_MATRIX) * packed_normal);
vec2 offset = normalize(clip_normal.xy) / VIEWPORT_SIZE * clip_position.w * outline_width * 2.0;
clip_position.xy += offset;
POSITION = clip_position;
}
MeshOutlinePostImportColor.gd
@tool
extends EditorScenePostImport
func _post_import(scene: Node) -> Object:
# Loop through any child mesh instances
# If the mesh has a path ending in tres, we use it so baked some smooth normals to its tangent
for child: Node in scene.get_children():
if child is MeshInstance3D:
if child.mesh.resource_path.ends_with('.tres'):
_pack_smooth_normals(child.mesh)
return scene
func _pack_smooth_normals(mesh: ArrayMesh) -> void:
var new_mesh := ImporterMesh.new()
for i: int in range(mesh.get_surface_count()):
var st := SurfaceTool.new()
st.create_from(mesh, i)
st.generate_normals_to_color(false)
new_mesh.add_surface(st.get_primitive_type(), st.commit_to_arrays(), [], {}, mesh.surface_get_material(i), mesh.surface_get_name(i))
var new_array_mesh := new_mesh.get_mesh()
ResourceSaver.save(new_array_mesh, mesh.resource_path)
surface_tool.cpp
void SurfaceTool::generate_normals_to_color(bool p_flip) {
ERR_FAIL_COND(primitive != Mesh::PRIMITIVE_TRIANGLES);
bool was_indexed = index_array.size();
deindex();
ERR_FAIL_COND((vertex_array.size() % 3) != 0);
HashMap<SmoothGroupVertex, Vector3, SmoothGroupVertexHasher> smooth_hash;
for (uint32_t vi = 0; vi < vertex_array.size(); vi += 3) {
Vertex *v = &vertex_array[vi];
Vector3 normal;
if (!p_flip) {
normal = Plane(v[0].vertex, v[1].vertex, v[2].vertex).normal;
} else {
normal = Plane(v[2].vertex, v[1].vertex, v[0].vertex).normal;
}
for (int i = 0; i < 3; i++) {
// Add face normal to smooth vertex influence if vertex is member of a smoothing group
if (v[i].smooth_group != UINT32_MAX) {
Vector3 *lv = smooth_hash.getptr(v[i]);
if (!lv) {
smooth_hash.insert(v[i], normal);
} else {
(*lv) += normal;
}
} else {
normal += Vector3(1.0, 1.0, 1.0);
normal /= 2.0;
v[i].color = Color(normal.x, normal.y, normal.z);
}
}
}
for (Vertex &vertex : vertex_array) {
if (vertex.smooth_group != UINT32_MAX) {
Vector3 *lv = smooth_hash.getptr(vertex);
if (!lv) {
vertex.color = Color();
} else {
Vector3 smooth_normal = lv->normalized();
Vector3 packed_normal = smooth_normal + Vector3(1.0, 1.0, 1.0);
packed_normal /= 2.0;
vertex.color = Color(packed_normal.x, packed_normal.y, packed_normal.z);
}
}
}
format |= Mesh::ARRAY_FORMAT_COLOR;
if (was_indexed) {
index();
}
}
// And in bind_methods add
ClassDB::bind_method(D_METHOD("generate_normals_to_color", "flip"), &SurfaceTool::generate_normals_to_color, DEFVAL(false));
// And add the function to the surface_tool.h
void generate_normals_to_color(bool p_flip = false);
方案二:使用 CompositorEffect
https://github.com/dmlary/godot-stencil-based-outline-compositor-effect
https://github.com/pink-arcana/godot-distance-field-outlines
https://godotshaders.com/shader/fake-stencil-silhouette-outline-object-based-but-without-depth-test/