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;
}
}