cell shading material alternativa3d 8

David Jones
@david3jones
avatar-davidejones

So I’ve taken my first outing into assembly with actionscript AGAL to try to create a cell shaded material with alternativa3d 8. I’ve only ever used the already setup alternativa3d materials but decided to try create something myself based on some examples online. After some time reading online and going through the alternativa3d source code I managed to create something that partially works but could certainly be improved. I used 2 sites for reference on this, the first being this toon shaded example at wonderfl http://wonderfl.net/c/rnQQ and the second being some written information relating to Minko here http://blogs.aerys.in/jeanmarc-leroux/2012/01/23/single-pass-cel-shading/ both are great examples. Although I seemed to get in a bit of an argument with the author in the latter link while asking if I could see his AGAL code. I don’t normally get into arguments online let alone post about it on my blog but he had such a uncompromising arrogant attitude that it seemed hard not to mention it. Apparently trying AGAL is a waste of my time, I will learn nothing new and sticking to it making naive programs is all I know what to do anyway…lol a bit of a contradiction considering its my first time using it. I even bowed out and said we agree to disagree but he couldn’t accept that either! The thing that really blew my mind is that I was agreeing with his opinion that its far better to use something else rather than writing AGAL and that I’m just doing it for fun but he really couldn’t seemed to grasp that. I’m just going to assume he was having a bad day to be acting like that. Anyway regardless of his attitude his information was great. Here is my final result I came up with. Unfortunately I couldn’t seem to get the black border as you would expect. Alternativa3d have mentioned they will be creating shaders such as a cellshader on their to do list. So hopefully that isn’t too far off. cellshading Using the material is pretty easy, just apply it to a mesh like any other oh and add a directional light to the scene.

var pu:BitmapTextureResource = new BitmapTextureResource(new skincol().bitmapData);
var mat:CellShadingMaterial = new CellShadingMaterial(pu);

and here is the CellShadingMaterial I created, note that it is using alternativa3d’s own libraries and the AGAL code looks slightly different to what you normally might see.

package alternativa.engine3d.materials
{
    	import alternativa.engine3d.alternativa3d;
    	import alternativa.engine3d.core.Camera3D;
    	import alternativa.engine3d.core.DrawUnit;
    	import alternativa.engine3d.core.DebugDrawUnit;
    	import alternativa.engine3d.core.Light3D;
    	import alternativa.engine3d.core.Object3D;
    	import alternativa.engine3d.core.Renderer;
    	import alternativa.engine3d.core.VertexAttributes;
    	import alternativa.engine3d.materials.compiler.Linker;
    	import alternativa.engine3d.materials.compiler.Procedure;
    	import alternativa.engine3d.materials.compiler.VariableType;
    	import alternativa.engine3d.objects.Surface;
    	import alternativa.engine3d.resources.Geometry;
    	import alternativa.engine3d.resources.TextureResource;
    
    	import avmplus.getQualifiedClassName;
    
    	import flash.display3D.Context3D;
    	import flash.display3D.Context3DBlendFactor;
    	import flash.display3D.Context3DProgramType;
    	import flash.display3D.VertexBuffer3D;
    	import flash.utils.Dictionary;
    	import flash.utils.getDefinitionByName;
    	
    	import alternativa.engine3d.lights.DirectionalLight;
    
    	use namespace alternativa3d;
    	
    	public class CellShadingMaterial extends Material
    	{		
    		private static var caches:Dictionary = new Dictionary(true);
    		private var cachedContext3D:Context3D;
    		private var programsCache:Dictionary;
    				
    		// Vertex Procedure
    		static alternativa3d const getCellVProcedure:Procedure = new Procedure([
    			"#a0=aPosition",
    			"#a1=aNormal",
    			"m44 o0 a0 c0",
    			"mov v1, a1"
    		], "getCellVProcedure");
    		
    		// Fragment procedure
    		static alternativa3d const getCellSProcedure:Procedure = new Procedure([
    			"#c0=cLightdirection",
    			"#s0=sDiffuse",
    			"mov t0 c0",
    			"nrm t2.xyz t0.xyz",
    			"dp3 t1.x, v1.xyz, t2.xyz",   
    			"tex t0, t1.xx, s0 <2d,nearest>",
    			"mov o0, t0"
    		], "getCellSProcedure");
    		
    		public var diffuseMap:TextureResource;
    		
    		public function CellShadingMaterial(diffuseMap:TextureResource)
    		{
    			this.diffuseMap = diffuseMap;
    		}
    		
    		override alternativa3d function fillResources(resources:Dictionary, resourceType:Class):void {
    			super.fillResources(resources, resourceType);
    			if (diffuseMap != null && A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(diffuseMap)) as Class, resourceType)) {
    				resources[diffuseMap] = true;
    			}
    		}
    		
    		private function getProgram(object:Object3D, programs:Vector., camera:Camera3D):ToonMaterialProgram {
    			var key:int = 0;
    			var program:ToonMaterialProgram = programs[key];
    			if (program == null) {
    				// Make program
    				// Vertex shader
    				var vertexLinker:Linker = new Linker(Context3DProgramType.VERTEX);
    
    				var positionVar:String = "aPosition";
    				var normalVar:String = "aNormal";
    				vertexLinker.declareVariable(positionVar, VariableType.ATTRIBUTE);
    				vertexLinker.declareVariable(normalVar, VariableType.ATTRIBUTE);
    				getCellVProcedure.assignVariableName(VariableType.CONSTANT, 0, "cProjMatrix", 4);
    				
    				if (object.transformProcedure != null) {
    					positionVar = appendPositionTransformProcedure(object.transformProcedure, vertexLinker);
    				}
    				vertexLinker.addProcedure(getCellVProcedure);
    				vertexLinker.setInputParams(getCellVProcedure, positionVar);
    				vertexLinker.setInputParams(getCellVProcedure, normalVar);
    
    
    				// Pixel shader
    				var fragmentLinker:Linker = new Linker(Context3DProgramType.FRAGMENT);
    				var outProcedure:Procedure = getCellSProcedure;
    				fragmentLinker.declareVariable("cLightdirection", VariableType.CONSTANT);
    				fragmentLinker.addProcedure(outProcedure);
    				fragmentLinker.setInputParams(outProcedure, "cLightdirection");
    				fragmentLinker.varyings = vertexLinker.varyings;
    
    				program = new ToonMaterialProgram(vertexLinker, fragmentLinker);
    
    				program.upload(camera.context3D);
    				programs[key] = program;
    			}
    			return program;
    		}
    		
    		private function getDrawUnit(program:ToonMaterialProgram, camera:Camera3D, surface:Surface, geometry:Geometry, light:Light3D):DrawUnit {
    			var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION);
    			var normalsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.NORMAL);
    
    			var object:Object3D = surface.object;
    
    			// Draw call
    			var drawUnit:DrawUnit = camera.renderer.createDrawUnit(object, program.program, geometry._indexBuffer, surface.indexBegin, surface.numTriangles, program);
    
    			// Streams
    			// a0, a1
    			drawUnit.setVertexBufferAt(program.aPosition, positionBuffer, geometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]);
    			drawUnit.setVertexBufferAt(program.aNormal, normalsBuffer, geometry._attributesOffsets[VertexAttributes.NORMAL], VertexAttributes.FORMATS[VertexAttributes.NORMAL]);
    			
    			//Constants
    			object.setTransformConstants(drawUnit, surface, program.vertexShader, camera);
    			drawUnit.setProjectionConstants(camera, program.cProjMatrix, object.localToCameraTransform); //cProjMatrix c0;
    			
    			//drawUnit.setFragmentConstantsFromVector(program.cLightdirection, new Vector.([light.x,light.y,light.z,0.0]), -1); //c0		
    			drawUnit.setFragmentConstantsFromNumbers(program.cLightdirection, light.x,light.y,light.z, 1);
    
    			// Textures
    			drawUnit.setTextureAt(program.sDiffuse, diffuseMap._texture);
    			return drawUnit;
    		}
    		
    		override alternativa3d function collectDraws(camera:Camera3D, surface:Surface, geometry:Geometry, lights:Vector., lightsLength:int, useShadow:Boolean, objectRenderPriority:int = -1):void {
    			var object:Object3D = surface.object;
    			var lightGroup:Vector. = new Vector.();
    			var light:Light3D;
    
    			// Buffers
    			var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION);
    			var normalsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.NORMAL);
    
    			// Check validity
    			if (positionBuffer == null || normalsBuffer == null || diffuseMap == null || diffuseMap._texture == null) return;
    
    			// Refresh program cache for this context
    			if (camera.context3D != cachedContext3D) {
    				cachedContext3D = camera.context3D;
    				programsCache = caches[cachedContext3D];
    				if (programsCache == null) {
    					programsCache = new Dictionary();
    					caches[cachedContext3D] = programsCache;
    				}
    			}
    			var optionsPrograms:Vector. = programsCache[object.transformProcedure];
    			if(optionsPrograms == null) {
    				optionsPrograms = new Vector.(5, true);
    				programsCache[object.transformProcedure] = optionsPrograms;
    			}
    			
    			
    			for (var i:int = 0; i < lights.length; i++) {
    				if(lights[i] is DirectionalLight) {
    					light = lights[i];
    				}
    			}
    
    			var program:ToonMaterialProgram;
    			var drawUnit:DrawUnit;
    			
    			program = getProgram(object, optionsPrograms, camera);
    			drawUnit = getDrawUnit(program, camera, surface, geometry, light);
    			
    			camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.OPAQUE);
    		}
    	}
}
    
    
import alternativa.engine3d.materials.ShaderProgram;
import alternativa.engine3d.materials.compiler.Linker;
    
import flash.display3D.Context3D;
    
class ToonMaterialProgram extends ShaderProgram {
    
    	public var aPosition:int = -1;
    	public var aNormal:int = -1;
    	public var cProjMatrix:int = -1;
    	
    	public var cLightdirection:int = -1;
    	public var sDiffuse:int = -1;
    
    	public function ToonMaterialProgram(vertex:Linker, fragment:Linker) {
    		super(vertex, fragment);
    	}
    
    	override public function upload(context3D:Context3D):void {
    		super.upload(context3D);
    
    		aPosition = vertexShader.findVariable("aPosition");
    		aNormal = vertexShader.findVariable("aNormal");
    		cProjMatrix = vertexShader.findVariable("cProjMatrix");
    		
    		cLightdirection = fragmentShader.findVariable("cLightdirection");
    		sDiffuse = fragmentShader.findVariable("sDiffuse");
    	}
    
}

You can download the demo files here

Downloads

Comments

  • avatar-62316e
    # 62316e
    Nice! Can you also add support for diffuse texture.
  • avatar-davidejones
    # davidejones
    I’ve created a few different cell shaded materials since this one, i may release them at somepoint.

Comments are currently closed