(Nearly) all the shaders

Here’s a bit of code that shows a very basic use case of vertex, tessellation, geometry and fragment shaders all at once. For easier downloading, it can be found (alongside some other snippets I wrote) at github.com/TheCheapestPixels/pa … rs/shaders

shader.py

#!/usr/bin/env python

import sys
import random

from direct.showbase.ShowBase import ShowBase
from panda3d.core import NodePath
from panda3d.core import Geom, GeomNode, GeomPatches, \
    GeomVertexFormat, GeomVertexData, GeomVertexArrayFormat, \
    GeomVertexWriter, GeomVertexReader, \
    InternalName
from panda3d.core import Shader
from direct.task import Task
from panda3d.core import PStatClient

class Base(ShowBase):
    def __init__(self):
        # The basics
        ShowBase.__init__(self)
        base.disableMouse()
        base.setBackgroundColor(0.1, 0.1, 0.1)
        base.setFrameRateMeter(True)
        PStatClient.connect()
        self.accept("escape", sys.exit)
        # Camera
        self.camera_orbit = base.render.attach_new_node("Camera orbit")
        self.camera_pitch = self.camera_orbit.attach_new_node("Camera pitch")
        base.camera.reparent_to(self.camera_pitch)
        base.camera.set_pos(0, -10, 0)
        # Camera control
        self.camera_movement = (0, 0)
        self.accept("arrow_up",       self.change_camera_movement, [ 0, -1])
        self.accept("arrow_up-up",    self.change_camera_movement, [ 0,  1])
        self.accept("arrow_down",     self.change_camera_movement, [ 0,  1])
        self.accept("arrow_down-up",  self.change_camera_movement, [ 0, -1])
        self.accept("arrow_left",     self.change_camera_movement, [-1,  0])
        self.accept("arrow_left-up",  self.change_camera_movement, [ 1,  0])
        self.accept("arrow_right",    self.change_camera_movement, [ 1,  0])
        self.accept("arrow_right-up", self.change_camera_movement, [-1,  0])
        base.taskMgr.add(self.move_camera, "Move camera")
        # Object
        self.model = self.create_model()
        self.model.reparent_to(base.render)
        taskMgr.add(self.refresh_shader_vars, "Refresh shaders variables", sort = 49)
        self.accept("r", self.reload_shader)

    def create_model(self):
        # Set up the vertex arrays
        vformatArray = GeomVertexArrayFormat()
        # Panda3D implicitly generates a bounding volume from a
        # column named "vertex", so you either
        # * have a column of that name, or
        # * add a bounding volume yourself.
        vformatArray.addColumn(InternalName.make("vertex"), 3, Geom.NTFloat32, Geom.CPoint)
        vformatArray.addColumn(InternalName.make("color"), 4, Geom.NTFloat32, Geom.CColor)

        vformat = GeomVertexFormat()
        vformat.addArray(vformatArray)
        vformat = GeomVertexFormat.registerFormat(vformat)

        vdata = GeomVertexData("Data", vformat, Geom.UHStatic)
        vertex = GeomVertexWriter(vdata, 'vertex')
        color = GeomVertexWriter(vdata, 'color')

        geom = Geom(vdata)

        # Vertex data
        vertex.addData3f(1.5, 0, -1)
        color.addData4f(1, 0, 0, 1)
        vertex.addData3f(-1.5, 0, -1)
        color.addData4f(0, 1, 0, 1)
        vertex.addData3f(0, 0, 1)
        color.addData4f(0, 0, 1, 1)

        # Primitive
        tri = GeomPatches(3, Geom.UHStatic)
        tri.add_vertex(2)
        tri.add_vertex(1)
        tri.add_vertex(0)
        tri.close_primitive()
        geom.addPrimitive(tri)

        # Create the actual node
        node = GeomNode('geom_node')
        node.addGeom(geom)
        np = NodePath(node)

        # Shader, initial shader vars, number of instances
        np.set_shader(Shader.load(Shader.SL_GLSL,
                                  vertex = "shader.vert",
                                  tess_control = "shader.tesc",
                                  tess_evaluation = "shader.tese",
                                  geometry = "shader.geom",
                                  fragment = "shader.frag"))
        np.set_shader_input("time", 0.0)
        np.set_shader_input("tess_level", 32.0)
        np.set_instance_count(2)
        return np

    def change_camera_movement(self, turn, pitch):
        self.camera_movement = (self.camera_movement[0] + turn,
                                self.camera_movement[1] + pitch)

    def move_camera(self, task):
        self.camera_orbit.set_h(self.camera_orbit, self.camera_movement[0] * globalClock.get_dt() * 360.0 / 3.0)
        new_pitch = self.camera_pitch.get_p() + self.camera_movement[1] * globalClock.get_dt() * 360.0 / 3.0
        self.camera_pitch.set_p(min(max(new_pitch, -89), 89))
        return Task.cont

    def refresh_shader_vars(self, task):
        self.model.set_shader_input("time", task.time)
        return Task.cont

    def reload_shader(self):
        self.model.set_shader(Shader.load(Shader.SL_GLSL,
                                          vertex = "shader.vert",
                                          tess_control = "shader.tesc",
                                          tess_evaluation = "shader.tese",
                                          geometry = "shader.geom",
                                          fragment = "shader.frag"))

if __name__ == '__main__':
    app = Base()
    app.run()

shader.vert

#version 140

// Displace the patch vertices based on the patches instance. One goes left, one
// goes right.

uniform mat4 p3d_ModelViewProjectionMatrix;
in vec4 vertex;
in vec4 color;

out vec4 vert_color;
flat out int vert_instance;

void main()  {
  vec4 vert_pos = vertex;
  vert_pos.x += (float(gl_InstanceID) - 0.5) * 3.0;
  gl_Position = vert_pos;
  vert_color = color;
  vert_instance = gl_InstanceID;
}

shader.tesc

#version 430

// Set the tessellation level.

uniform float tess_level;
in vec4 vert_color[];
flat in int vert_instance[];

layout(vertices = 3) out;
out vec4 tesc_color[];
flat out int tesc_instance[];

void main() {
  if (gl_InvocationID == 0) {
    gl_TessLevelInner[0] = tess_level;
    gl_TessLevelInner[1] = tess_level;
    gl_TessLevelOuter[0] = tess_level;
    gl_TessLevelOuter[1] = tess_level;
    gl_TessLevelOuter[2] = tess_level;
    gl_TessLevelOuter[3] = tess_level;
  }
  gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
  tesc_color[gl_InvocationID] = vert_color[gl_InvocationID];
  tesc_instance[gl_InvocationID] = vert_instance[gl_InvocationID];
}

shader.tese

#version 430

// Calculate the position and other attributes of the vertices of the now
// tessellated patches.

uniform mat4 p3d_ModelViewProjectionMatrix;
uniform float time;
layout(triangles, equal_spacing, ccw) in;
in vec4 tesc_color[];
flat in int tesc_instance[];

out vec4 tese_color;
flat out int tese_instance;

void main() {
  gl_Position = p3d_ModelViewProjectionMatrix *
                (gl_TessCoord.x * gl_in[0].gl_Position +
                 gl_TessCoord.y * gl_in[1].gl_Position +
                 gl_TessCoord.z * gl_in[2].gl_Position +
                 vec4(0, -0.3, 0, 0) *
                 sin(time * 5.0 + gl_TessCoord.x * 3.1415 * 2.0 * 3.0));
  tese_color = gl_TessCoord.x * tesc_color[0] +
               gl_TessCoord.y * tesc_color[1] +
               gl_TessCoord.z * tesc_color[2];
  tese_instance = tesc_instance[0];
}

shader.geom

#version 150

// Add backsides to the triangles.

layout(triangles) in;
in vec4[3] tese_color;
flat in int[3] tese_instance;

layout(triangle_strip, max_vertices = 6) out;
out vec4 geom_color;
flat out int geom_instance;

void main(void) {
  for (int i = 0; i < gl_in.length(); ++i) {
    gl_Position = gl_in[i].gl_Position;
    geom_color = tese_color[i];
    geom_instance = tese_instance[0];
    EmitVertex();
  }
  EndPrimitive();
  for (int i = 0; i < gl_in.length(); ++i) {
    gl_Position = gl_in[2-i].gl_Position;
    geom_color = tese_color[2-i];
    geom_instance = tese_instance[0];
    EmitVertex();
  }
  EndPrimitive();
}

shader.frag

#version 130

// Invert the colors of the non-first instance.

in vec4 geom_color;
flat in int geom_instance;

out vec4 frag_color;

void main () {
  if (geom_instance == 0) {
    frag_color = geom_color;
  } else {
    frag_color = vec4(1, 1, 1, 2) - geom_color;
  }
}

That’s very handy to have!

Code in repo now uses a struct for its inouts, shows the #pragma include (and, pointlessly, #pragma once), uses explicit locations for inouts, can create n triangles, and is in general just a bit cleaner.