TintMaterial using alternativa3d 8

David Jones
@david3jones
avatar-davidejones

If you’ve ever seen or played the maxracer demo by alternativa3d you are able to select a color for your car. In a recent personal project I wanted to do the same, I wanted to either tint the entire diffuse color or I wanted to tint only white areas specified in a second image map. Well to get the effect I wanted I ended up creating my own tint material. tintmaterial I based the new tintmaterial off the already existing texturematerial. So it is able to handle the opacity map as well. The usage of the tintmaterial is pretty simple and just like the others, just pass in the diffuse image and opacity image if you have one and that’s it. If you are using the opacity map for the tint regions you set the fourth parameter to true. This won’t just overwrite the white areas with your color it will also blend it slightly. If you look in the demo the white box has some slight shading to it notice that the tint will also keep this in there it won’t just solid fill over it.

//tint material that also uses transparency
var mat:TintMaterial = new TintMaterial(diffuse,transparent,0x7f3d3d);
mat.alphaThreshold = 0.5;
    
//tint material no transparency
var mat:TintMaterial = new TintMaterial(diffuse,null,0x7f3d3d);
    
//tint material uses transparency map for tinting regions
var mat:TintMaterial = new TintMaterial(diffuse,transparent,0x7f3d3d,true);
    
//apply material to surface
surface.material = mat;

and here is the full tintmaterial code I created

package alternativa.engine3d.materials {
    	import alternativa.engine3d.alternativa3d;
    	import alternativa.engine3d.core.Camera3D;
    	import alternativa.engine3d.core.DrawUnit;
    	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;
    
    	use namespace alternativa3d;
    
    	/**
    	 * The materiall fills surface with bitmap image in light-independent manner. Can draw a Skin with no more than 41 Joints per surface. See Skin.divide() for more details.
    	 * 
    	 * To be drawn with this material, geometry shoud have UV coordinates.
    	 * @see alternativa.engine3d.objects.Skin#divide()
    	 * @see alternativa.engine3d.core.VertexAttributes#TEXCOORDS
    	 */
    	public class TintMaterial extends Material {
    
    		/**
    		 * @private
    		 */
    		alternativa3d override function get canDrawInShadowMap():Boolean {
    			return opaquePass && alphaThreshold == 0;
    		}
    
    		private static var caches:Dictionary = new Dictionary(true);
    		private var cachedContext3D:Context3D;
    		private var programsCache:Dictionary;
    
    		/**
    		 * @private
    		 * Procedure for tinted diffuse map with alpha channel
    		 */
    		static alternativa3d const getDiffuseTintProcedure:Procedure = new Procedure([
    			"#v0=vUV",
    			"#s0=sDiffuse",
    			"#c0=cThresholdAlpha",
    			"#c1=cColor",
    			"tex t0, v0, s0 <2d, linear,repeat, miplinear>",
    			"mul t0.w, t0.w, c0.w",
    			"mul t1, t0, c1",
    			"mov o0, t1"
    		], "getDiffuseTintProcedure");
    
    		/**
    		 * @private
    		 * Procedure for tinted diffuse with opacity map.
    		 */
    		static alternativa3d const getDiffuseOpacityTintProcedure:Procedure = new Procedure([
    			"#v0=vUV",
    			"#s0=sDiffuse",
    			"#s1=sOpacity",
    			"#c0=cThresholdAlpha",
    			"#c1=cColor",
    			"tex t0, v0, s0 <2d, linear,repeat, miplinear>",
    			"tex t1, v0, s1 <2d, linear,repeat, miplinear>",
    			"mul t0.w, t0.w, t0.w", //texture
    			"mul t1, t0, c1",		//color texture
    			"mul t1.w, t0.x, c0.w", //apply opacity
    			"mov o0, t1"			//move to output
    		], "getDiffuseOpacityProcedure");
    		
    		
    		/**
    		 * @private
    		 * Procedure for diffuse with tints based on opacity map.
    		 */
    		 
    		static alternativa3d const getDiffuseTintMapProcedure:Procedure = new Procedure([
    			"#v0=vUV",
    			"#s0=sDiffuse",
    			"#s1=sOpacity",
    			"#c0=cThresholdAlpha",
    			"#c1=cColor",
    			"#c2=cData",
    			"tex t0, v0, s0 <2d, linear,repeat, miplinear>",
    			"tex t1, v0, s1 <2d, linear,repeat, miplinear>",
    						
    			"add t2.x, t1.x, t1.y, t1.z", 	//ft2.x = ft1.x+ft1.y+ft1.z (3 for white less for anything else)
    			"sge t3.x, t2.x, c2.z",			  	//sge ft3.x, ft2.x, 3 <- 1 if white 
    			"slt t3.y, t2.x, c2.y",				//slt ft3.y, ft2.x, 3 <- 0 if black
    			//ft1 * ft3.y + tintColor * ft3.x
    			
    			"sub t3.y, t3.y, t1.x",  //subtract diamonds
    			
    			"mul t4, t0, t3.y", //ft1 * ft3.y
    			"mul t5, c1, t3.x", // tintColor * ft3.x
    			"mul t5, t5, t0.x", // blend tint with white
    			"add t6, t4, t5",	// +
    			
    			
    			//just to use variables
    			//"add t2, t1, c1",
    			"mul t1.w, t1.x, c0.w",
    			
    			"mov o0, t6"			//move to output
    		], "getDiffuseTintMapProcedure");
    		 
    		
    		/**
    		 * @private
    		 * Alpha-test check procedure.
    		 */
    		static alternativa3d const thresholdOpaqueAlphaProcedure:Procedure = new Procedure([
    			"#c0=cThresholdAlpha",
    			"sub t0.w, i0.w, c0.x",
    			"kil t0.w",
    			"mov o0, i0"
    		], "thresholdOpaqueAlphaProcedure");
    
    		/**
    		 * @private
    		 * Alpha-test check procedure.
    		 */
    		static alternativa3d const thresholdTransparentAlphaProcedure:Procedure = new Procedure([
    			"#c0=cThresholdAlpha",
    			"slt t0.w, i0.w, c0.x",
    			"mul i0.w, t0.w, i0.w",
    			"mov o0, i0"
    		], "thresholdTransparentAlphaProcedure");
    
    		/**
    		 * @private
    		 * Pass UV to the fragment shader procedure
    		 */
    		static alternativa3d const _passUVProcedure:Procedure = new Procedure(["#v0=vUV", "#a0=aUV", "mov v0, a0"], "passUVProcedure");
    
    		/**
    		 * Diffuse map.
    		 */
    		public var diffuseMap:TextureResource;
    		
    		/**
    		 *  Opacity map.
    		 */
    		public var opacityMap:TextureResource;
    		
    		/**
    		 *  If true, perform transparent pass. Parts of surface, cumulative alpha value of which is below than  alphaThreshold draw within transparent pass.
    		 * @see #alphaThreshold
    		 */
    		public var transparentPass:Boolean = true;
    		
    		/**
    		 * If true, perform opaque pass. Parts of surface, cumulative alpha value of which is greater or equal than  alphaThreshold draw within opaque pass.
    		 * @see #alphaThreshold
    		 */
    		public var opaquePass:Boolean = true;
    		
    		/**
    		 * alphaThreshold defines starts from which value of alpha a fragment of surface will get into transparent pass.
    		 * @see #transparentPass
    		 * @see #opaquePass
    		 */
    		public var alphaThreshold:Number = 0;
    		
    		/**
    		 *  Transparency.
    		 */
    		public var alpha:Number = 1;
    		
    		
    		public var color:Number = 0xFF0000;
    		private var red:Number;
    		private var green:Number;
    		private var blue:Number;
    		private var tintOpacityOnly:Boolean=false;
    		
    		/**
    		 * Creates a new TextureMaterial instance.
    		 *
    		 * @param diffuseMap Diffuse map.
    		 * @param alpha Transparency.
    		 */
    		public function TintMaterial(diffuseMap:TextureResource = null, opacityMap:TextureResource = null, color:uint=0xFF0000, tintOpacityOnly:Boolean=false,alpha:Number = 1) {
    			this.diffuseMap = diffuseMap;
    			this.opacityMap = opacityMap;
    			this.color = color;
    			this.alpha = alpha;
    			this.tintOpacityOnly = tintOpacityOnly;
    			
    			red = ((color >> 16) & 0xFF)/0xFF;
    			green = ((color >> 8) & 0xFF)/0xFF;
    			blue = (color & 0xff)/0xFF;
    		}
    
    		/**
    		 * @private
    		 */
    		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;
    			}
    			if (opacityMap != null && A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(opacityMap)) as Class, resourceType)) {
    				resources[opacityMap] = true;
    			}
    		}
    
    		/**
    		 * @param object
    		 * @param programs
    		 * @param camera
    		 * @param opacityMap
    		 * @param alphaTest 0 - disabled, 1 - opaque, 2 - contours
    		 * @return
    		 */
    		private function getProgram(object:Object3D, programs:Vector., camera:Camera3D, opacityMap:TextureResource, alphaTest:int):ShaderProgram {
    			var key:int = (opacityMap != null ? 3 : 0) + alphaTest;
    			var program:ShaderProgram = programs[key];
    			if (program == null) {
    				// Make program
    				// Vertex shader
    				var vertexLinker:Linker = new Linker(Context3DProgramType.VERTEX);
    				
    				var positionVar:String = "aPosition";
    				vertexLinker.declareVariable(positionVar, VariableType.ATTRIBUTE);
    				if (object.transformProcedure != null) {
    					positionVar = appendPositionTransformProcedure(object.transformProcedure, vertexLinker);
    				}
    				vertexLinker.addProcedure(_projectProcedure);
    				vertexLinker.setInputParams(_projectProcedure, positionVar);
    				vertexLinker.addProcedure(_passUVProcedure);
    
    				// Pixel shader
    				var fragmentLinker:Linker = new Linker(Context3DProgramType.FRAGMENT);
    				var outProcedure:Procedure = (opacityMap != null ? getDiffuseOpacityTintProcedure : getDiffuseTintProcedure);
    				
    				if(tintOpacityOnly) {
    					outProcedure = getDiffuseTintMapProcedure
    				}
    				
    				fragmentLinker.addProcedure(outProcedure);
    				if (alphaTest > 0) {
    					fragmentLinker.declareVariable("tColor");
    					fragmentLinker.setOutputParams(outProcedure, "tColor");
    					if (alphaTest == 1) {
    						fragmentLinker.addProcedure(thresholdOpaqueAlphaProcedure, "tColor");
    					} else {
    						fragmentLinker.addProcedure(thresholdTransparentAlphaProcedure, "tColor");
    					}
    				}
    				fragmentLinker.varyings = vertexLinker.varyings;
    				
    				program = new ShaderProgram(vertexLinker, fragmentLinker);
    
    				program.upload(camera.context3D);
    				programs[key] = program;
    			}
    			return program;
    		}
    		
    		private function getDrawUnit(program:ShaderProgram, camera:Camera3D, surface:Surface, geometry:Geometry, opacityMap:TextureResource):DrawUnit {
    			var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION);
    			var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]);
    
    			var object:Object3D = surface.object;
    
    			// Draw call
    			var drawUnit:DrawUnit = camera.renderer.createDrawUnit(object, program.program, geometry._indexBuffer, surface.indexBegin, surface.numTriangles, program);
    
    			// Streams
    			drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aPosition"), positionBuffer, geometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]);
    			drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aUV"), uvBuffer, geometry._attributesOffsets[VertexAttributes.TEXCOORDS[0]], VertexAttributes.FORMATS[VertexAttributes.TEXCOORDS[0]]);
    			//Constants
    			object.setTransformConstants(drawUnit, surface, program.vertexShader, camera);
    			drawUnit.setProjectionConstants(camera, program.vertexShader.getVariableIndex("cProjMatrix"), object.localToCameraTransform);
    			drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cThresholdAlpha"), alphaThreshold, 0, 0, alpha);
    			drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cColor"), red, green, blue, alpha);
    			
    			if(tintOpacityOnly) {
    				drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cData"), -1, 255, 1, 0);
    			}
    			
    			
    			// Textures
    			drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sDiffuse"), diffuseMap._texture);
    			if (opacityMap != null) {
    				drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sOpacity"), opacityMap._texture);
    			}
    			return drawUnit;
    		}
    
    		/**
    		 * @private
    		 */
    		override alternativa3d function collectDraws(camera:Camera3D, surface:Surface, geometry:Geometry, lights:Vector., lightsLength:int, objectRenderPriority:int = -1):void {
    			var object:Object3D = surface.object;
    			
    			// Buffers
    			var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION);
    			var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]);
    			
    			// Check validity
    			if (positionBuffer == null || uvBuffer == null || diffuseMap == null || diffuseMap._texture == null || opacityMap != null && opacityMap._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.(6, true);
    				programsCache[object.transformProcedure] = optionsPrograms;
    			}
    
    			var program:ShaderProgram;
    			var drawUnit:DrawUnit;
    			// Opaque pass
    			if (opaquePass && alphaThreshold <= alpha) {
    				if (alphaThreshold > 0) {
    					// Alpha test
    					// use opacityMap if it is presented
    					program = getProgram(object, optionsPrograms, camera, opacityMap, 1);
    					drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap);
    				} else {
    					// do not use opacityMap at all
    					program = getProgram(object, optionsPrograms, camera, null, 0);
    					drawUnit = getDrawUnit(program, camera, surface, geometry, null);
    				}
    				// Use z-buffer within DrawCall, draws without blending
    				camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.OPAQUE);
    			}
    			// Transparent pass
    			if (transparentPass && alphaThreshold > 0 && alpha > 0) {
    				// use opacityMap if it is presented
    				if (alphaThreshold <= alpha && !opaquePass) {
    					// Alpha threshold
    					program = getProgram(object, optionsPrograms, camera, opacityMap, 2);
    					drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap);
    				} else {
    					// There is no Alpha threshold or check z-buffer by previous pass
    					program = getProgram(object, optionsPrograms, camera, opacityMap, 0);
    					drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap);
    				}
    				// Do not use z-buffer, draws with blending
    				drawUnit.blendSource = Context3DBlendFactor.SOURCE_ALPHA;
    				drawUnit.blendDestination = Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA;
    				camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.TRANSPARENT_SORT);
    			}
    		}
    
    		
    
    	}
}
    

You can download the demo files here

Downloads

Comments

    Comments are currently closed