Download one shader exemple – Moussa Dembélé –


model made by Jahan Zaib
contains 252 252 triangles)

Sorry, either Adobe flash is not installed or you do not have it enabled

So….. where to start ?

First of all we need the .mtl and .obj files.
You will find them and all sources in the sources section.

All the projecst are made with FlashDevelop.
The sources of the one shader project are in the  – one_shader – directory.
you will find in the project’s sources  a class named FerrariMesh.
This class has in charge the parsing of the files and to display the mesh .
The wavefront files are embeded (by the way this ferrari model is a really beautifull piece of work made by Jahan Zaib)

[Embed (source = "../lib/ferrari/Ferrari F430.obj",	mimeType = "application/octet-stream")]
private var _objData:Class;

[Embed (source = "../lib/ferrari/Ferrari F430.mtl",	mimeType = "application/octet-stream")]
private var _mtlData:Class;


The first thing to do is to create the shaderSet. (shaderSet are containers for shader programs and byteArray writers)
This is done in the setShaders() function :

private function setShaders():void {
	// definition and decalration of the shaders to use
	var shader:MPhongShader = new MPhongShader();

	// assign the writer wich will build the vertices byteArray buffer and the indices buffer
	var writer:MVerticesBytesArrayWriter = new MVerticesBytesArrayWriter();

	// ShaderSets's container
	_shaderStore = new ShaderStore();
	// a ShaderSet object contains a IMShader and a IMBytesArrayWriter
	var shaderSet:ShaderSet = new ShaderSet(ShaderType.DEFAULT_SHADER, shader, writer);

In the first line we instanciate a shader(MPhongShader).
For this exemple there is only one shader used and so only one writer.
we ‘ll talk about shaders later but for now what matter is that we instanciate the writer that will create the vertex and indices buffers,
and pass it alongside the shader to a ShaderSet instance (in line 142).
We also give an ID to this shaderSet : ShaderType.DEFAULT_SHADER. It’s just a String and we can pass any String we want.
It is used to get access to the shaderSet once stored in the shaderStore.
We have only one shader in this exemple so wait for the next exemples (two shaders and mutli-shaders) to see more precisely why and how to work with the ID.

This done ,we can now instanciate the parser :

_parser = new MObjParser(_context3D, 2, _shaderStore);

the first parameter is the Context3D (used for the buffers creation),
the second parameter is a Number and represent the size scale applied to the mesh for it’s creation, 2 meaning two times the size define in the .obj file,
and the last parameter is our shaderStore.

Then we call the parse methode of the parser (line 92) with two parameters : the .obj and .mtl files as String :

var bytes:ByteArray = new _objData();
var objFile:String = bytes.readUTFBytes(bytes.bytesAvailable);			
bytes = new _mtlData();
var mtlFile:String = bytes.readUTFBytes(bytes.bytesAvailable);
_mesh = _parser.parse(objFile, mtlFile);

In return we get the mesh (an instance of MObjMesh) and that’s all we need !
Nice isn’t it ?


Now let’s see how we could display this mesh.
First we need a world to put the Ferrari in.
You will find the class ‘World’ in the src directory.

After the creation of the Context3D

stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onCreate);

We call the build() function in wich we create the elements for the world

private function build():void {
	_meshes = new Vector.<IMMesh>();
	_modelViewProjection = new Matrix3D();


- setProjection() is where we define the PerspectiveMatrix3D.
- setMeshs() is where we instanciate the ferrari mesh :

private function setMeshs():void 
	var mesh:FerrariMesh = new FerrariMesh(_context);
	mesh.position = new Vector3D(0, 0, 100);

There is only one mesh in this project but we could have more, that’s the reason why i’m using the vector _meshes (line 130).

- setCamera() is where the camera is created and passed to the ferraiMesh (the mesh will need it for maxtrix computation).
- setLights() is where the lights are created and passed to the ferraiMesh (the mesh will need it for maxtrix computation).

Finally on the enterFrame event we have the render function.

public function render(e:Event ):void 
	if ( !_context ) return;
	_context.clear(0, 0, 0, 1);

	var camera:OrbitalCamera = _cameras[0] as OrbitalCamera;

	// move object
	var mesh:IMMesh = _meshes[0];
	if(!_mouseDown) t += 2.0;			
	var rx:Number = (t * 0.35) % 360 ;
	var ry:Number = (t * 0.3) %360;
	var rz:Number = (t * 0.5) % 360;		

	(mesh as FerrariMesh).setRotation(rx, ry, rz);

	mesh.cameraPosition = camera.position;
	mesh.modelViewProjection = _modelViewProjection;
	// draw current mesh


in the line 240 the model/view/projection matrix is updated.
We pass the new model/view/projection matrix  and the camera position to the mesh and then call the drawTriangle method of the ferrariMesh.
This method will take in charge all the drawing of the mesh.

Now let’s go back to the FerraiMesh to see what are these functions we’re calling :

at the end of the FerraiMesh constructor the initPrograms() function is called.

private function initPrograms():void {
	// create and upload programs
	var shaderSet:ShaderSet = _shaderStore.getShaderById(ShaderType.DEFAULT_SHADER);
	// set the program

as you know , before drawing triangles we have to declare what is the program which have to be used.
In this project we have only one shader and only one program so we can assign it only once.
this allow us to gain extra time during rendering events.
We have also to declare program constants.
These constants are constants only from the graphics card point of view during a drawTriangle loop.
In fact in the FerrariMesh point of view there are three kinds of « constants » :
1- the ones that won’t change at all. For this project this is the light beacause it is not moving.
It is defined in the setSaticConstants() method and called by at the end of the build method.

public function setSaticConstants():void {
	var color:Object;
	var pos:Vector.<Number>;
	//fc0, ambient light 
	_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, _ambiantLight);
	//fc1, light diffuse color
	color = ColorUtil.hexArgb(_lights[0].diffuseColor);
	_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, Vector.<Number>([Number(color.rouge)/255,Number(color.vert)/255,Number(color.bleu)/255,_lights[0].diffuseIntensity]));
	//fc2, light position	
	pos =  Vector.<Number>([_lights[0].position.x,_lights[0].position.y,_lights[0].position.z,1]);
	_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 2, pos);			
	//fc3, light specular color
	color = ColorUtil.hexArgb(_lights[0].specularColor);
	_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 3, Vector.<Number>([Number(color.rouge)/255,Number(color.vert)/255,Number(color.bleu)/255,_lights[0].specularIntensity]));

2- the ones  that change only once in a drawing loop.
Remember the mesh contains several polygon groups , each one having diferent settings. So some constants will change for each polygon group and some won’t.
For this project these are the matrix (for the scene, the object position, the object rotation and the camera position).
They are declared in the setFrameConstants() method.

private function setFrameConstants():void {
	var pos:Vector.<Number>;
	//vc0, scene
	_context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _modelViewProjection, true);
	//vc4, object position
	_context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 4, _modelMatrix, true); 
	//vc8, object rotation
	var rotMat:Matrix3D = _modelMatrix.clone();
	_context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 8, rotMat, true);	
	//fc4, camera position	
	pos =  Vector.<Number>([_cameraPosition.x, _cameraPosition.y, _cameraPosition.z, 1]);
	_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 4, pos);	

3- the ones  that change for each polygon group.
For this project these are the material ambiant color and the specular material color.
They are declared in the setDefaultConstant() method.

public function setDefaultConstant(mtlSet:MMtlSet):void {
	// fc5 , material ambiant color
	var color:Object = ColorUtil.hexArgb(mtlSet.ambiantColor);
	_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT,  5, Vector.<Number>([Number(color.rouge)/255,Number(color.vert)/255,Number(color.bleu)/255,1]));
	//fc6, specular material color
	color = ColorUtil.hexArgb(mtlSet.specularColor);
	_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 6, Vector.<Number>([Number(color.rouge)/255,Number(color.vert)/255,Number(color.bleu)/255,mtlSet.specularCoef]));

It is really important to separate theses declarations and try to reduce the more you can calls to Context3D for setting constants because it will cost you rendering time.

drawTriangles() method.
This is where we do the rendering jobs. This method will be called by each frame.
First we declare the constant that will change only once in the drawing loop. (line 190).
Then we get the shader we want to use (there’s only one in this project)  line 208.
Remember the mesh is made of Mobject that are made of MPolygonGroups that contains vertexBuffer and indexBuffer…
So we loop through objects (line 214).
For each object we loop through polygon groups (line 220). This is where we declare the constants depending on polygon groups (line 224).
And then loop through buffers (line 210), setting the activeVertexBuffer and calling the context3D.drawtriangle() function.

public function drawTriangles():void {
	var shaderSet:ShaderSet;
	var buffer3D:VertexBuffer3D;
	var index3D:IndexBuffer3D;
	var object3Dset:MObject3D;
	var mPolygonsGroup:MPolygoneGroup3D;
	var polygonBufferSet:MPolygonGroupBuffers;
	var color:Object;
	var specCoef:Number;

	// ----------------------
	// define the constantes wich are not dependant of groups
	// ----------------------			

	shaderSet = _shaderStore.getShaderById(ShaderType.DEFAULT_SHADER);

	// loop through the objects
	var oSize:int = _mesh.mObjects.length;
	for (var i:int = 0; i < oSize; i++ ) {
		object3Dset =  _mesh.mObjects[i];
		//trace("drawing object " +;

		// loop through the groups
		var pgSize:int = object3Dset.polygonsGroups.length;
		for (var j:int = 0; j < pgSize; j++ ) {
			mPolygonsGroup = object3Dset.polygonsGroups[j];
			//trace("drawing group " +;
			// define the constantes depending on groups

			// loop through the buffers
			var pbSize:int = mPolygonsGroup.polygonBufferSet.length;
			for (var k:int = 0; k < pbSize; k++ ) {
				polygonBufferSet = mPolygonsGroup.polygonBufferSet[k];
				buffer3D = polygonBufferSet.vertexBuffer;
				index3D = polygonBufferSet.indexBuffer;
				shaderSet.shader.setActiveVertextBuffer(_context3D, buffer3D)
				//trace("drawing triangles ");
				_context3D.drawTriangles(index3D, 0, -1);


And… that’s it :-)

Oups… I almost forget, it’s time to talk about shaders.
I’ve created an Interface called IMShader and several classes that implements this interface.
In these classes we defined the vertex and fragment programs,
assemble and upload them.
In addition there are two important methods :
- setActiveVertextBuffer
- releaseActiveVertextBuffer

setActiveVertextBuffer has to be called each time vertexBuffers have changed.
It’s in this mehtod that we define the vertexBuffer that is used.
For instance in the MPhongShader :

public function setActiveVertextBuffer(context3D:Context3D, vertexBuffer:VertexBuffer3D):void {
	// XYZ
	context3D.setVertexBufferAt(0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
	// colors
	context3D.setVertexBufferAt(1, vertexBuffer, 3, Context3DVertexBufferFormat.BYTES_4);
	// normales
	context3D.setVertexBufferAt(2, vertexBuffer, 4, Context3DVertexBufferFormat.FLOAT_3);

releaseActiveVertextBuffer has to be called each time needed.
in this project there is no need but it could have been the case if for a polygon group we used a second shader in which 4 vertex buffers were defined,
and then reused the MPhongShader.
We would have had an error due to the fact that in MPhongShader there is only 3 vertex buffers defined.
Agal wouldn’t have known what to do with the fourth vertex buffer defined previoulsy.

public function releaseActiveVertextBuffer(context3D:Context3D):void {
	// XYZ
	context3D.setVertexBufferAt(0, null);
	// colors
	context3D.setVertexBufferAt(1, null);
	// normales
	context3D.setVertexBufferAt(2, null);

There’s also another important method in this class :
public function get dataPerVertex():int
this method return the dataPerVertex specific to it (in MPhongShader it is 7)
This value will be used by the parser when creating VertexBuffers.

Note that a shader is specific to the way you want to render a polygon group and to your world,
if you use two light instead of one for instance.
I’ve implemented them using AGALMiniAssembler because I wanted to start from the base,
but you can use whatever you want (like AGAL Macro Assembler ) as long as you implement
the interface.

This time that’s it :-)


Any questions, any comments, don’t hesitate !

And now next step : textures..



Leave a Reply




You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>