back to home

Tutorial 4: Shader Basics

Shader Example

A shader in the field of computer graphics is a set of software instructions, which is used primarily to calculate rendering effects on graphics hardware with a high degree of flexibility.

Creating a Gouraud Shader

We will define a Vertex and a Fragment shader to create a smooth transition of light on a sphere. Most of the work will happen in the Vertex Shader (gouraud.vert, you can find the content of the file down below). Our main application written in C++ will only create the sphere and load the shaders for us while the real magic happens in the shader files themselves. They are written in the OpenGL Shading Language.
Since the syntax is very similar to C we won't have any problems reading or writing the code!

Diffuse and Specular Light

Shader Basics

Diffuse light is light that is reflected from the surface of the object in all directions equally. The position of the viewer (camera/eye) doesn't matter here. To calculate diffuse light we create the dot product between light_vert and normal_camera. To avoid negative values we use the function max().

Specular light is light that is reflected from the surface of the object along the reflection vector light_refl. This also means that it looks different depending on the position of the viewer (camera/eye). The closer the viewer is to the reflection vector the stronger the light appears. The dot product between light_refl and vertex_position_camera calculates the specular light.

gouraud.vert

	const vec4 light_position_world = vec4(4.0, 4.0, 4.0, 1.0);

	varying float specular_intensity;
	varying float diffuse_intensity;

	void main(void) {
		vec4 vertex_position_camera = gl_ModelViewMatrix * gl_Vertex;
		vec3 normal_camera = normalize(gl_NormalMatrix * gl_Normal);
		vec4 light_position_camera = gl_ModelViewMatrix * light_position_world;

		vec3 light_vert = normalize(vec3(light_position_camera - vertex_position_camera));
		vec3 light_refl = normalize(reflect(light_vert, normal_camera));

		// diffuse light
		diffuse_intensity = max(dot(light_vert, normal_camera), 0.0);

		// specular light
		specular_intensity = max(dot(light_refl, normalize(vec3(vertex_position_camera))), 0.0);
		specular_intensity = pow(specular_intensity, 6.0);

		gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
	}

gouraud.frag

	const vec4 diffuse_color = vec4(1.0, 0.0, 0.0, 1.0);
	const vec4 specular_color = vec4(1.0, 1.0, 1.0, 1.0);

	varying float specular_intensity;
	varying float diffuse_intensity;

	void main(void) {
		gl_FragColor =
			diffuse_color * diffuse_intensity +
			specular_color * specular_intensity;
	}

Load shader into OSG

This simple application will create a sphere and load our shader onto it

	#include <osg/ShapeDrawable>
	#include <osg/Geometry>
	#include <osg/StateSet>
	#include <osgViewer/Viewer>
	#include <osgGA/TrackballManipulator>
	#include <iostream>

	using namespace osg;

	Node *startup() {
		Group *scene = new Group();

		Geode *sphere = new Geode();
		sphere->addDrawable(new ShapeDrawable(new Sphere(Vec3(), 1)));

		StateSet *sphereStateSet = sphere->getOrCreateStateSet();
		sphereStateSet->ref();

		Program *programObject = new Program();
		Shader *vertexObject = new Shader(Shader::VERTEX);
		Shader *fragmentObject = new Shader(Shader::FRAGMENT);
		programObject->addShader(fragmentObject);
		programObject->addShader(vertexObject);

		vertexObject->loadShaderSourceFromFile("gouraud.vert");
		fragmentObject->loadShaderSourceFromFile("gouraud.frag");

		sphereStateSet->setAttributeAndModes(programObject, StateAttribute::ON);

		scene->addChild(sphere);
		return scene;
	}

	int main() {
		Node *scene = startup();
		if (!scene) return 1;
		osgViewer::Viewer viewer;
		viewer.setSceneData(scene);
		viewer.setCameraManipulator(new osgGA::TrackballManipulator());
		viewer.realize();

		while (!viewer.done()) {
			viewer.frame();
		}
	}