Overview of Shaders
As of version 1.7.0, Panda3D supports two shading languages: Cg and GLSL. Section assumes that you have a working knowledge of a shader language. If not, it would be wise to read about Cg or GLSL before trying to understand how they fit into Panda3D.
A shader program can consist of three shaders: a vertex shader, a geometry shader and a fragment shader (often referred to a pixel shader).
The vertex shader operates on every individual vertex. The processed vertices are then passed to the geometry shader (if it exists), which converts it into visible geometry (e.g. triangles, lines, points). The fragment shader is then executed on every pixel of the geometry and outputs the color of the pixel.
As geometry shaders are a relatively new feature to graphic cards, they are often omitted and therefore you will often only find a vertex and fragment shader.
Cg Shaders
Overview of Cg Shaders
A Cg shader must contain procedures named vshader()
and fshader()
; the vertex shader and fragment shader respectively. If a geometry shader is used, then it must also contain a procedure named gshader()
. Furthermore, additional procedures named vshader1()
, fshader1()
, vshader2()
, fshader2()
, and so forth may be used. These latter pairs of procedures represent fall-back code-paths to be used when the video card doesn't support the first pair. If none of the pairs is supported, then the shader is disabled and has no effect (i.e. rendering proceeds normally using the standard pipeline).
Single-File Cg Shaders
To write a Cg shader in a single file, you must create a shader program that looks much like the one shown below. This example preserves position but switches the red and green channels of everything it is applied to:
void vshader(float4 vtx_position : POSITION,
float4 vtx_color: COLOR,
out float4 l_position : POSITION,
out float4 l_color0 : COLOR0,
uniform float4x4 mat_modelproj)
{
l_position=mul(mat_modelproj, vtx_position);
l_color0=vtx_color;
}
void fshader(float4 l_color0 : COLOR0,
out float4 o_color : COLOR)
{
o_color=float4(l_color0[1],l_color0[0], l_color0[2],l_color0[3]);
}
Multi-File Cg Shaders
Cg shaders can be divided into several files as well; one for the vertex shader, another for the fragment shader, and a third for the geometry shader. The procedure names are still required to be vshader()
, fshader()
and gshader()
in their respective shader files.
GLSL Shaders
Overview of GLSL Shaders
To write a GLSL shader, you must write your vertex, pixel and geometry shaders separately.
GLSL Example
This example preserves position but switches the red and green channels of everything it is applied to.
This is the vertex shader:
void main() {
gl_Position = ftransform();
}
Since this does the default operation, it can be omitted.
This is the fragment shader:
void main() {
gl_FragColor = vec4(gl_Color.g, gl_Color.r, gl_Color.b, 1.0);
}
Using Shaders in Panda3D
Shaders in Panda3D use the Shader
class. When a shader is loaded, an object of this class is returned. This is then applied to objects of the NodePath
class.
Importing the Shader Class
Before a shader can be applied, the Panda3D Shader class must be imported.
from panda3d.core import Shader
Loading a Cg Shader
Loading a single-file Cg shader is done with the Shader.load()
procedure. The first parameter is the path to the shader file, and the second is the shader language, which in this case is Shader.SLCg
. The following is an example of using this procedure:
myShader = Shader.load("myshader.sha", Shader.SLCg)
Loading a multi-file Cg shader requires a different set of parameters for Shader.load()
; the first being the shader language, and the second, third and fourth being paths to the vertex, fragment and geometry shaders respectively. Here is an example:
myShader = Shader.load(Shader.SLCg, "myvertexshader.sha", "myfragmentshader.sha", "mygeometryshader.sha")
One last thing to note about Shader.load()
is that it can be used to load single-file Cg shaders without the second parameter (Shader.SLCg
). However, in order for this to work, the first line of the shader file must be the following:
//Cg
Shader.load()
can then be used like so:
myShader = Shader.load("myshader.sha")
Loading a GLSL Shader
In the following code sample, a GLSL shader is loaded:
myShader = Shader.load(Shader.SLGLSL, "myvertexshader.glsl", "myfragmentshader.glsl", "mygeometryshader.glsl")
Applying a Shader
Shaders can be applied to any NodePath
with the setShader()
procedure. Here is an example that applies a shader myShader
to a model myModel
:
myModel.setShader(myShader)
The call to setShader()
causes the NodePath
to be rendered with the shader passed to it as a parameter. Shaders propagate down the scene graph; the node and everything beneath it will use the shader.
The Shader can Fetch Data from the Panda3D Runtime
Each shader program contains a parameter list. Panda3D scans the parameter list and interprets each parameter name as a request to extract data from the panda runtime. For example, if the shader contains a parameter declaration float3 vtx_position : POSITION
, Panda3D will interpret that as a request for the vertex position, and it will satisfy the request. Panda3D will only allow parameter declarations that it recognizes and understands.
Panda3D will generate an error if the parameter qualifiers do not match what Panda3D is expecting. For example, if you declare the parameter float3 vtx_position
, then Panda3D will be happy. If, on the other hand, you were to declare uniform shader2d vtx_position
, then Panda3D would generate two separate errors: Panda3D knows that vtx_position is supposed to be a float-vector, not a texture, that it is supposed to be varying, not uniform.
Again, all parameter names must be recognized. There is a list of possible Cg shader inputs that shows all the valid parameter names and the data that Panda3D will supply.
Supplying data to the Shader Manually
Most of the data that the shader could want can be fetched from Panda3D at runtime by using the appropriate parameter names. However, it is sometimes necessary to supply some user-provided data to the shader. For this, you need setShaderInput()
. Here is an example:
myModel.setShaderInput("tint", Vec4(1.0, 0.5, 0.5, 1.0))
The method setShaderInput()
stores data that can be accessed by the shader. It is possible to store data of type Texture
, NodePath
, and Vec4
. The setShaderInput()
method also accepts separate floating point numbers, which it combines into a Vec4
.
The data that you store using setShaderInput()
isn't necessarily used by the shader. Instead, the values are stored in the node, but unless the shader explicitly asks for them, they will sit unused. So the setShaderInput("tint", Vec4(1.0, 0.5, 0.5, 1.0))
above simply stores the vector. It is up to the shader whether or not it is interested in a data item labeled "tint".
To fetch data that was supplied using setShaderInput()
, the shader must use the appropriate parameter name. See the list of possible Cg shader inputs, many of which refer to the data that was stored using setShaderInput()
.
Shader Inputs propagate down the scene graph, and accumulate as they go. For example, if you store setShaderInput("x", 1)
on a node, and setShaderInput("y", 2)
on its child, then the child will contain both values. If you store setShaderInput("z", 1)
on a node, and setShaderInput("z", 2)
on its child, then the latter will override the former. The method setShaderInput()
accepts a third parameter, priority, which defaults to zero. If you store setShaderInput("w", 1, 1000)
on a node, and setShaderInput("w", 2, 500)
on the child, then the child will contain ("w"==1), because the priority 1000 overrides the priority 500.
Shader Render Attributes
The functions nodePath.setShader()
and nodePath.setShaderInput()
are used to apply a shader to a node in the scene graph. Internally, these functions manipulate a render attribute of class ShaderAttrib
on the node.
In rare occasions, it is necessary to manipulate ShaderAttrib
objects explicitly. As an example, the code below shows how to create a ShaderAttrib
and apply it to a camera:
myShaderAttrib = ShaderAttrib.make()
myShaderAttrib = myShaderAttrib.setShader(Shader.load("myshader.sha"))
myShaderAttrib = myShaderAttrib.setShaderInput("tint", Vec4(1.0,0.5,0.5,1.0))
base.cam.node().setInitialState(render.getState().addAttrib(myShaderAttrib))
Be careful: attribs are immutable objects. So when you apply a function like setShader()
or setShaderInput()
to a ShaderAttrib
, you aren't modifying the attrib. Instead, these functions work by returning a new attrib (which contains the modified data).
Deferred Shader Compilation
When you create an object of class shader, it compiles the shader, checking for syntax errors. But it does not check whether or not your video card is powerful enough to handle the shader. Panda3D only does that later on, when you try to render something with the shader.
In the unusual event that your computer contains multiple video cards, the shader may be compiled more than once. It is possible that the compilation could succeed for one video card, and fail for the other.