Shader for wireframe mesh

Here’s an example that uses a shader to create a wire frame from the triangles in a mesh. One restriction is the triangle vertices have to be red, green, and blue. Tap the screen to switch between the colored meshes and it’s wire frame.

displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)

function setup() 
    img=image(WIDTH,HEIGHT)
    
    -- this mesh creates colored boxes
    m=mesh()
    m.vertices={vec2(10,100),vec2(WIDTH-10,100),vec2((WIDTH-20)/2,500)}
    m:addRect(200,450,200,200,45)
    m:addRect(800,400,100,200,32)
    m:addRect(800,600,50,200,68)
    m:addRect(600,600,200,100,13)
    m:addRect(300,650,200,100,70)
    m:setColors(255,255,255,255)
    m:color(1,255,0,0)
    m:color(2,0,255,0)
    m:color(3,0,0,255)
    for z=1,10 do
        m:color(z*3+1,255,0,0,255)
        m:color(z*3+2,0,0,255,255)
        m:color(z*3+3,0,255,0,255)
    end
    setContext(img)
    m:draw()    -- create img with 5 boxes
    setContext()
    
    -- this mesh uses img created from above for the wire frame
    m=mesh()
    m:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    m.texture=img
end

function draw() 
    background(0) 
    m:draw()
    fill(255)
    if m.shader==nil then
        text("Tap screen for wire frame",WIDTH/2,HEIGHT-50)
    else
        text("Tap screen for color fill",WIDTH/2,HEIGHT-50)
    end
end

function touched(t) -- flip between colored boxes or wire frame
    if t.state==BEGAN then
        if m.shader==nil then
            m.shader=shader(vShader,fShader)
        else
            m.shader=nil
        end
    end
end

-- shader to create wire frames

vShader = [[
    uniform mat4 modelViewProjection;
    attribute vec4 position; 
    attribute vec2 texCoord;
    varying highp vec2 vTexCoord;
    void main() 
    {   vTexCoord = texCoord;
        gl_Position = modelViewProjection * position;
    }    ]]

fShader = [[
    uniform lowp sampler2D texture;
    varying highp vec2 vTexCoord;
    void main() 
    {   lowp vec4 col = texture2D( texture, vTexCoord);
        if (col.r<.01 && col.g>0.0 && col.b>0.0 || 
                col.g<.01 && col.r>0.0 && col.b>0.0 || 
                col.b<.01 && col.r>0.0 && col.g>0.0)
        {   col.r=0.0; col.g=1.0; col.b=0.0; } 
        else discard;               
        gl_FragColor=col;
    }     ]]

@dave1707 Wow, that’s sweet! I wonder how to make a wireframe shader without the limitation of the colours being only red green and blue.

@Kolosso Here’s the above code to draw the wireframe without a shader or using the colors. This works for 2D meshes and when there isn’t a lot of vertices. But when there’s a lot of vertices or it’s a 3D mesh, this won’t work very well. For a 3D mesh, the x,y,z values would have to be converted to 2D values and then drawn. That would add even more calculations for each vertices. I’m not sure if a shader could do it this way, probably not.

displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)

function setup() 
    img=image(WIDTH,HEIGHT)
    
    -- this mesh creates colored boxes
    m=mesh()
    m.vertices={vec2(10,100),vec2(WIDTH-10,100),vec2((WIDTH-20)/2,500)}
    m:addRect(200,450,200,200,45)
    m:addRect(800,400,100,200,32)
    m:addRect(800,600,50,200,68)
    m:addRect(600,600,200,100,13)
    m:addRect(300,650,200,100,70)
    m:setColors(255,255,255,255)
    m:color(1,255,0,0)
    m:color(2,0,255,0)
    m:color(3,0,0,255)
    for z=1,10 do
        m:color(z*3+1,255,0,0,255)
        m:color(z*3+2,0,0,255,255)
        m:color(z*3+3,0,255,0,255)
    end
    setContext(img)
    m:draw()    -- create img with 5 boxes
    setContext()
    for a,b in pairs(m.vertices) do
        print(a,b)
    end
    print(m.size)
    
    -- this mesh uses img created from above for the wire frame
    m1=mesh()
    m1:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    m1.texture=img

end

function draw() 
    background(0) 
    fill(255)
    if show then
        stroke(255)
        strokeWidth(2)
        for z=1,m.size,3 do
            mv=m.vertices
            line(mv[z].x,mv[z].y,mv[z+1].x,mv[z+1].y)   
            line(mv[z].x,mv[z].y,mv[z+2].x,mv[z+2].y)   
            line(mv[z+1].x,mv[z+1].y,mv[z+2].x,mv[z+2].y)        
        end
        text("Tap screen for color fill",WIDTH/2,HEIGHT-50)
    else
        m:draw()
        text("Tap screen for wire frame",WIDTH/2,HEIGHT-50)
    end
end

function touched(t) -- flip between colored boxes or wire frame
    if t.state==BEGAN then
        show=not show
    end
end

Here’s a variation that allows fill by setting m.shader.fill. Thanks for this example (it taught me how to make my first shader).

-- Wireframe Shader

displayMode(OVERLAY)
supportedOrientations(LANDSCAPE_ANY)

function setup() 
    -- spiral boxes mesh
    m=mesh()
    for i = 1, 50 do
        m:addRect(WIDTH/2,HEIGHT/2,500-i*10,500-i*10,i/10)
    end
    m:setColors(255,255,255,255)
    for z=1,100 do
        m:color((z-1)*3+1,255,0,0,255)
        m:color((z-1)*3+2,0,0,255,255)
        m:color((z-1)*3+3,0,255,0,255)
    end
    m.shader=shader(vShader,fShader)
    m.shader.fill = color(0,220,0,10)
    m.shader.stroke = color(0,255,0,255)
    m.shader.strokeWidth = 0
end

function draw() 
    background(0) 
    m:draw()
    fill(255)
    resetMatrix()
    if m.shader.strokeWidth == 0 then
        text("Color",WIDTH/2,HEIGHT-50)
    else
        text("Wireframe",WIDTH/2,HEIGHT-50)
    end
end

function touched(t) -- flip between colored boxes or wire frame
    if t.state==BEGAN then
        if m.shader.strokeWidth == 0 then
            m.shader.strokeWidth = .5
        else
            m.shader.strokeWidth = 0
        end
    end
end

-- Shaders

vShader = [[
//
// This just passes variables to the fragment shader
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
    //Pass the mesh color to the fragment shader
    vColor = color;
    vTexCoord = texCoord;
    
    //Multiply the vertex position by our combined transform
    gl_Position = modelViewProjection * position;
}
]]

fShader = [[
//
// A basic wireframe shader (set all mesh vertices to red, green, or blue to make this work
//

precision highp float;

//Uniforms
uniform lowp vec4 stroke;
uniform lowp vec4 fill;
uniform highp float strokeWidth;

//The interpolated vertex color for this fragment
varying lowp vec4 vColor;

//Mesh edge detection
bool isEdge(float sample, float c1, float c2)
{
    if (sample < strokeWidth*.01 && c1 > 0. && c2 > 0.) return true;
    else return false;
}

void main()
{
    lowp vec4 col = vColor;
    
    //Check if the color is an edge color
    if (isEdge(col.r,col.g,col.b)||isEdge(col.g,col.b,col.r)||isEdge(col.b,col.r,col.g)) {
        col = stroke; //If so, set the output color to the stroke color
    } else if (col.a != 0.) {
        col = fill; //If the pixel is not transparent, set it to fill color
    }
    
    //Set the output color to the texture color
    gl_FragColor = col;
}

]]

It works strangely in 3d with alpha fill.

see also @yojimbo2000 wireframe shader in his .obj importer…

https://codea.io/talk/discussion/6955/3d-obj-model-importer-for-potential-future-inclusion-with-codea-now-with-wireframe-mode

@dave1707 “I’m not sure if a shader could do it this way, probably not.” - I recently figured out what the xyz-W value is for in the vertex shader, it essentially equates to perspective. I will look a bit more into this when I have a moment, but I think its entirely possible to use a vertex/fragment shader combo to realize wireframe drawing. I will do a quick google search for “glsl wireframe” and see if anything comes up. Yeah quite a bit came up, here’s one of the first entries:
http://rickyreusser.com/glsl-solid-wireframe/demo.html
https://github.com/rreusser/glsl-solid-wireframe

I also recomend checking out the vertexShaderArt.com help/youtube video tutorials. They are quite informative and useful, essentially teaches how to turn thousands of x=0,y=0,z=0 verticies into any kind of geometry using just one vertex shader.

@AxiomCrux Thanks for the info. I’ll have to look at them when I have more time.