How to render a wireframe

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Jaybill McCarthy
:warning: Old Version Published before Godot 3 was released.

Hi there. So I’m trying to render some 3d models in a style similar to this, i.e., just the wireframe:
what I want

The best I’ve been able to achieve with spatialMaterials is the following (different model, obviously)
whatiget

Am I going about this the right way? I just want the wireframe, and while the effect I’ve got is okay, it’s not exactly what I want. Any ideas?

I’m including my shader settings below. It’s basically an unshaded material and then a second material pass that’s unshaded, white, and uses ‘grow’ to make the outline.

    [gd_resource type="SpatialMaterial" load_steps=2 format=2]

[ext_resource path="res://materials/white.tres" type="Material" id=1]

[resource]

render_priority = 0
next_pass = ExtResource( 1 )
flags_transparent = true
flags_unshaded = true
flags_vertex_lighting = false
flags_no_depth_test = false
flags_use_point_size = false
flags_world_triplanar = false
flags_fixed_size = false
flags_albedo_tex_force_srgb = false
vertex_color_use_as_albedo = true
vertex_color_is_srgb = false
params_diffuse_mode = 0
params_specular_mode = 4
params_blend_mode = 0
params_cull_mode = 2
params_depth_draw_mode = 1
params_line_width = 1.0
params_point_size = 1.0
params_billboard_mode = 0
params_grow = false
params_use_alpha_scissor = false
albedo_color = Color( 0, 0, 0, 1 )
metallic = 0.0
metallic_specular = 0.5
metallic_texture_channel = 0
roughness = 0.0
roughness_texture_channel = 0
emission_enabled = false
normal_enabled = false
rim_enabled = false
clearcoat_enabled = false
anisotropy_enabled = false
ao_enabled = false
depth_enabled = false
subsurf_scatter_enabled = false
transmission_enabled = false
refraction_enabled = false
detail_enabled = false
uv1_scale = Vector3( 1, 1, 1 )
uv1_offset = Vector3( 0, 0, 0 )
uv1_triplanar = false
uv1_triplanar_sharpness = 1.0
uv2_scale = Vector3( 1, 1, 1 )
uv2_offset = Vector3( 0, 0, 0 )
uv2_triplanar = false
uv2_triplanar_sharpness = 1.0
proximity_fade_enable = false
distance_fade_enable = false
_sections_unfolded = [ "Albedo", "Ambient Occlusion", "Depth", "Emission", "Flags", "Metallic", "NormalMap", "Parameters", "Transmission", "Vertex Color" ]

And here’s the ‘white’ material that one references:

[gd_resource type="SpatialMaterial" format=2]

[resource]

render_priority = 0
flags_transparent = false
flags_unshaded = true
flags_vertex_lighting = false
flags_no_depth_test = false
flags_use_point_size = false
flags_world_triplanar = false
flags_fixed_size = false
flags_albedo_tex_force_srgb = false
vertex_color_use_as_albedo = false
vertex_color_is_srgb = false
params_diffuse_mode = 0
params_specular_mode = 4
params_blend_mode = 0
params_cull_mode = 1
params_depth_draw_mode = 0
params_line_width = 1.0
params_point_size = 1.0
params_billboard_mode = 0
params_grow = true
params_grow_amount = 0.3
params_use_alpha_scissor = false
albedo_color = Color( 1, 1, 1, 1 )
metallic = 0.0
metallic_specular = 0.5
metallic_texture_channel = 0
roughness = 0.0
roughness_texture_channel = 0
emission_enabled = false
normal_enabled = false
rim_enabled = false
clearcoat_enabled = false
anisotropy_enabled = false
ao_enabled = false
depth_enabled = false
subsurf_scatter_enabled = false
transmission_enabled = false
refraction_enabled = false
detail_enabled = false
uv1_scale = Vector3( 1, 1, 1 )
uv1_offset = Vector3( 0, 0, 0 )
uv1_triplanar = false
uv1_triplanar_sharpness = 1.0
uv2_scale = Vector3( 1, 1, 1 )a
uv2_offset = Vector3( 0, 0, 0 )
uv2_triplanar = false
uv2_triplanar_sharpness = 1.0
proximity_fade_enable = false
distance_fade_enable = false
_sections_unfolded = [ "Albedo", "Ambient Occlusion", "Clearcoat", "Depth", "Detail", "Emission", "Flags", "Parameters", "Refraction", "Roughness", "Vertex Color" ]

Hello, did you tried this addon? Wireframe Tool - Godot Asset Library

frankiezafe | 2019-02-04 09:49

:bust_in_silhouette: Reply From: Jaybill McCarthy

So after more research, I’ll answer my own question: I haven’t found a good way to do this with materials. The best thing I could come up with was to write a script to create the wireframe geometry by looping over the vertices in the model, but this is expensive.

What I ended up doing instead was actually making the actual model geometry a wireframe. This works fine for my purposes. If you’re using blender and want to do this, it’s very simple to achieve using the Wireframe Modifier

:bust_in_silhouette: Reply From: frankiezafe

I’ve written a bunch of scripts to generate wireframe geometries: frankie / Godot Wireframe · GitLab

Maybe it could help…

:bust_in_silhouette: Reply From: Mitten.O

I ran into this problem and created a solution that better fit my use case.

First, I found several articles describing how to make a wireframe shader using barymetric
coordinates. One, Another. Sadly this approach requires custom data for each vertex which is not obviously possible in godot. And the approach even required duplicating vertices to get the custom data to be vertex-specic (or a fancy algorithm to make sure all vertices of the same face have different barycentric coordinates).

But hey, there is built-in face-specific data for each vertex: UV coordinates. There are only two, but the diagonal X = Y is also unambiguous, so it’s enough to recognize edges in the shader.

I created this script for assigning UV coordinates in blender:

# This example assumes we have a mesh object selected
# You must also mark all edges as seams and UV unwrap
# the mesh before running this, to make every face independent.

import bpy
import bmesh

# Get the active mesh
me = bpy.context.active_object.data


def set_loop_uv( mesh, loop_idx, x, y ):
    uv_coords = mesh.uv_layers.active.data[loop_idx].uv
    uv_coords.x = x
    uv_coords.y = y

print( "Doing the thing!!" );

# edge => [polygon] that use that edge
polygons_by_edges = {}

# Populates polygons_by_edges
def add_polygon_by_edge( edge, polygon ):
    polygon_list = polygons_by_edges.get( edge ) or []
    polygon_list.append( polygon )
    polygons_by_edges[ edge ] = polygon_list

# Index faces by edges
for face in me.polygons:
    for edge in face.edge_keys:
        add_polygon_by_edge( edge, face )

# Get all polygons that use vertices v1 and v2
def get_polygons_by_vertices( v1, v2 ):
    polygons = polygons_by_edges.get( (v1, v2) ) or polygons_by_edges.get( (v2, v1) )
    return polygons

# Returns true if the edge (v1,v2) is part of a flat surface.
def are_part_of_flat( v1, v2 ):
    polygons = get_polygons_by_vertices( v1, v2 )
    if( len( polygons ) <= 0 ):
        print( "No polygons found!" );
        return False
    else:
        normal = polygons[ 0 ].normal
        for face in polygons:
            if( face.normal.cross( normal ).length > 0.1 ):
                return False
        return True

# Loops per face
flat_edges = 0
for face in me.polygons:
    # Place UV coordinates in opposite corners
    # So it's easy for the shader to know we are near
    # an edge by noticing that one component of the UV
    # is zero. (Or they are near equal, to catch the hypothenuse).
    x_0 = 0;
    y_0 = 1;
    x_1 = 0;
    y_1 = 0;
    x_2 = 1;
    y_2 = 0;
    x_3 = 1;
    y_3 = 1;
    
    # We only want wire edges at points close to turns. We don't
    # want them in the middle of flat areas. (I think).
    # So notice when an edge is flat and move it away from
    # the range where it causes the edge to be drawn.
    if( are_part_of_flat( face.vertices[ 0 ], face.vertices[ 1 ] ) ):
        x_0 = 0.5
        y_0 = 0.5
        x_1 = 0.5
        print("Flat edge Face: %i, Edge: %i, %i" % (face.index, face.vertices[ 0 ], face.vertices[ 1 ] ) )
        flat_edges = flat_edges + 1
    if( are_part_of_flat( face.vertices[ 1 ], face.vertices[ 2 ] ) ):
        y_1 = 0.5
        y_2 = 0.5
        x_2 = 0.5
        print("Flat edge Face: %i, Edge: %i, %i" % (face.index, face.vertices[ 0 ], face.vertices[ 1 ] ) )
        flat_edges = flat_edges + 1
    if( are_part_of_flat( face.vertices[ 0 ], face.vertices[ 2 ] ) ):
        y_0 = 0.5
        x_2 = 0.5
        print("Flat edge Face: %i, Edge: %i, %i" % (face.index, face.vertices[ 0 ], face.vertices[ 1 ] ) )
        flat_edges = flat_edges + 1
    
    if( len( face.vertices ) == 3 ):
        set_loop_uv( me, face.loop_indices[ 0 ], x_0, y_0 )
        set_loop_uv( me, face.loop_indices[ 1 ], x_1, y_1 )
        set_loop_uv( me, face.loop_indices[ 2 ], x_2, y_2 )
    elif( len( face.loop_indices ) == 4 ):
        print( "QUAD!" );
        set_loop_uv( me, face.loop_indices[ 0 ], x_0, y_0 )
        set_loop_uv( me, face.loop_indices[ 1 ], x_1, y_1 )
        set_loop_uv( me, face.loop_indices[ 2 ], x_2, y_2 )
        set_loop_uv( me, face.loop_indices[ 3 ], x_3, y_3 )
    else:
        print( "UNKNOWN!" );
    for vert_idx, loop_idx in zip(face.vertices, face.loop_indices):
        uv_coords = me.uv_layers.active.data[loop_idx].uv
        print("face idx: %i, vert idx: %i, uvs: %f, %f" % (face.index, vert_idx, uv_coords.x, uv_coords.y))

print( "Flat edges: %i" % ( flat_edges) )

Usage:

  1. Select the object
  2. Go to edit mode, select all
  3. UV > Mark seam (marking all edges as seams to make faces independent)
  4. UV > Unwrap
  5. Exit edit mode
  6. Paste script to a text window
  7. Execute run script (with the object selected)

You can look in the UV editor, all faces should have been positioned as simple triangles.

Export. I used gltf2.

Open in godot and use this shader:

shader_type spatial;
render_mode unshaded;

void fragment() {
	float distance_from_edge = min(
		min(
			min( UV.x, UV.y ),
			min( abs( 1.0 - UV.x ), abs( 1.0- UV.y ) ) ),
		abs( UV.x - UV.y ) );
	if( distance_from_edge < 0.02 )
		ALBEDO = vec3( 0.0, 0.0, 0.0 );
	else
		ALBEDO = vec3( 1.0, 1.0, 1.0 );
}