Files
Alternativa3D/src/alternativa/engine3d/materials/VertexLightTextureMaterial.as
2012-03-29 14:21:07 +06:00

356 lines
17 KiB
ActionScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
* If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.
* You may add additional accurate notices of copyright ownership.
*
* It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/
* */
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.Transform3D;
import alternativa.engine3d.core.VertexAttributes;
import alternativa.engine3d.lights.DirectionalLight;
import alternativa.engine3d.lights.OmniLight;
import alternativa.engine3d.lights.SpotLight;
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 flash.display3D.Context3D;
import flash.display3D.Context3DBlendFactor;
import flash.display3D.Context3DProgramType;
import flash.display3D.VertexBuffer3D;
import flash.utils.Dictionary;
use namespace alternativa3d;
/**
* Texture material with dynamic vertex lightning. The material is able to draw skin
* with the number of bones in surface no more than 41. To reduce the number of bones in surface can break
* the skin for more surface with fewer bones. Use the method Skin.divide(). To be drawn with this material, geometry should have UV coordinates and vertex normals.
*
* @see alternativa.engine3d.objects.Skin#divide()
* @see alternativa.engine3d.core.VertexAttributes#TEXCOORDS
* @see alternativa.engine3d.core.VertexAttributes#NORMAL
*/
public class VertexLightTextureMaterial extends TextureMaterial {
private static var caches:Dictionary = new Dictionary(true);
private var cachedContext3D:Context3D;
private var programsCache:Dictionary;
private static const _passLightingProcedure:Procedure = new Procedure(["#v0=vLightColor","mov v0, i0"], "passLightingProcedure");
private static const _ambientLightProcedure:Procedure = new Procedure(["mov o0, i0"], "ambientLightProcedure");
private static const _mulLightingProcedure:Procedure = new Procedure(["#v0=vLightColor","mul o0, i0, v0"], "mulLightingProcedure");
private static const _directionalLightCode:Array = [
"dp3 t0.x,i0,c0",
"sat t0.x,t0.x",
"mul t0, c1, t0.xxxx",
"add o0, o0, t0"
];
private static const _omniLightCode:Array = [
"sub t0, c0, i1", // L = pos - lightPos
"dp3 t0.w, t0, t0", // lenSqr
"nrm t0.xyz,t0.xyz", // L = normalize(L)
"dp3 t0.x,t0,i0", // dot = dot(normal, L)
"sqt t0.w,t0.w", // len = sqt(lensqr)
"sub t0.w, t0.w, c2.z", // len = len - atenuationBegin
"div t0.y, t0.w, c2.y", // att = len/radius
"sub t0.w, c2.x, t0.y", // att = 1 - len/radius
"sat t0.xw,t0.xw", // t = sat(t)
"mul t0.xyz,c1.xyz,t0.xxx", // t = color*t
"mul t0.xyz, t0.xyz, t0.www",
"add o0.xyz, o0.xyz, t0.xyz"
];
private static const _spotLightCode:Array = [
"sub t0, c0, i1", // L = pos - lightPos
"dp3 t0.w, t0, t0", // lenSqr
"nrm t0.xyz,t0.xyz", // L = normalize(L)
"dp3 t1.x, t0.xyz, c3.xyz", //axisDirDot
"dp3 t0.x,t0,i0", // dot = dot(normal, L)
"sqt t0.w,t0.w", // len = sqt(lensqr)
"sub t0.w, t0.w, c2.y", // len = len - atenuationBegin
"div t0.y, t0.w, c2.x", // att = len/radius
"sub t0.w, c0.w, t0.y", // att = 1 - len/radius
"sub t0.y, t1.x, c2.w",
"div t0.y, t0.y, c2.z",
"sat t0.xyw,t0.xyw", // t = sat(t)
"mul t1.xyz,c1.xyz,t0.xxx", // t = color*t
"mul t1.xyz,t1.xyz,t0.yyy", //
"mul t1.xyz, t1.xyz, t0.www",
"add o0.xyz, o0.xyz, t1.xyz"
];
private static const _lightsProcedures:Dictionary = new Dictionary(true);
/**
* Creates a new VertexLightTextureMaterial instance.
*
* @param diffuse Diffuse map.
* @param alpha Transparency.
*/
public function VertexLightTextureMaterial(diffuse:TextureResource = null, opacityMap:TextureResource = null, alpha:Number = 1) {
super(diffuse, opacityMap, alpha);
}
/**
* @inheritDoc
*/
override public function clone():Material {
var res:VertexLightTextureMaterial = new VertexLightTextureMaterial(diffuseMap, opacityMap, alpha);
res.clonePropertiesFrom(this);
return res;
}
/**
* @param object
* @param materialKey
* @param opacityMap
* @param alphaTest - 0:disabled 1:alpha-test 2:contours
* @param lights
* @param directionalLight
* @param lightsLength
*/
private function getProgram(object:Object3D, programs:Dictionary, camera:Camera3D, materialKey:String, opacityMap:TextureResource, alphaTest:int, lights:Vector.<Light3D>, lightsLength:int):ShaderProgram {
var key:String = materialKey + (opacityMap != null ? "O" : "o") + alphaTest.toString();
var program:ShaderProgram = programs[key];
if (program == null) {
var vertexLinker:Linker = new Linker(Context3DProgramType.VERTEX);
vertexLinker.declareVariable("tTotalLight");
vertexLinker.declareVariable("aNormal", VariableType.ATTRIBUTE);
vertexLinker.declareVariable("cAmbientColor", VariableType.CONSTANT);
vertexLinker.addProcedure(_passUVProcedure);
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(_ambientLightProcedure);
vertexLinker.setInputParams(_ambientLightProcedure, "cAmbientColor");
vertexLinker.setOutputParams(_ambientLightProcedure, "tTotalLight");
if (lightsLength > 0) {
var normalVar:String = "aNormal";
if (object.deltaTransformProcedure != null) {
vertexLinker.declareVariable("tTransformedNormal");
vertexLinker.addProcedure(object.deltaTransformProcedure);
vertexLinker.setInputParams(object.deltaTransformProcedure, "aNormal");
vertexLinker.setOutputParams(object.deltaTransformProcedure, "tTransformedNormal");
normalVar = "tTransformedNormal";
}
for (var i:uint = 0; i < lightsLength; i++) {
var light:Light3D = lights[i];
var lightProcedure:Procedure = _lightsProcedures[light];
if (lightProcedure == null) {
lightProcedure = new Procedure();
if (light is DirectionalLight) {
lightProcedure.compileFromArray(_directionalLightCode);
lightProcedure.assignVariableName(VariableType.CONSTANT, 0, "c" + light.name + "Direction");
lightProcedure.name = "Directional" + i.toString();
} else if (light is OmniLight) {
lightProcedure.compileFromArray(_omniLightCode);
lightProcedure.assignVariableName(VariableType.CONSTANT, 0, "c" + light.name + "Position");
lightProcedure.assignVariableName(VariableType.CONSTANT, 2, "c" + light.name + "Radius");
lightProcedure.name = "Omni" + i.toString();
} else if (light is SpotLight) {
lightProcedure.compileFromArray(_spotLightCode);
lightProcedure.assignVariableName(VariableType.CONSTANT, 0, "c" + light.name + "Position");
lightProcedure.assignVariableName(VariableType.CONSTANT, 2, "c" + light.name + "Radius");
lightProcedure.assignVariableName(VariableType.CONSTANT, 3, "c" + light.name + "Axis");
lightProcedure.name = "Spot" + i.toString();
}
lightProcedure.assignVariableName(VariableType.CONSTANT, 1, "c" + light.name + "Color");
_lightsProcedures[light] = lightProcedure;
}
vertexLinker.addProcedure(lightProcedure);
vertexLinker.setInputParams(lightProcedure, normalVar, positionVar);
vertexLinker.setOutputParams(lightProcedure, "tTotalLight");
}
}
vertexLinker.addProcedure(_passLightingProcedure);
vertexLinker.setInputParams(_passLightingProcedure, "tTotalLight");
var fragmentLinker:Linker = new Linker(Context3DProgramType.FRAGMENT);
fragmentLinker.declareVariable("tColor");
var outputProcedure:Procedure = opacityMap != null ? getDiffuseOpacityProcedure : getDiffuseProcedure;
fragmentLinker.addProcedure(outputProcedure);
fragmentLinker.setOutputParams(outputProcedure, "tColor");
if (alphaTest > 0) {
outputProcedure = alphaTest == 1 ? thresholdOpaqueAlphaProcedure : thresholdTransparentAlphaProcedure;
fragmentLinker.addProcedure(outputProcedure, "tColor");
fragmentLinker.setOutputParams(outputProcedure, "tColor");
}
fragmentLinker.addProcedure(_mulLightingProcedure, "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, lights:Vector.<Light3D>, lightsLength:int):DrawUnit {
// Buffers
var object:Object3D = surface.object;
var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION);
var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]);
var normalsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.NORMAL);
// 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.setVertexConstantsFromVector(program.vertexShader.getVariableIndex("cAmbientColor"), camera.ambient, 1);
drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cThresholdAlpha"), alphaThreshold, 0, 0, alpha);
if (lightsLength > 0) {
drawUnit.setVertexBufferAt(program.vertexShader.getVariableIndex("aNormal"), normalsBuffer, geometry._attributesOffsets[VertexAttributes.NORMAL], VertexAttributes.FORMATS[VertexAttributes.NORMAL]);
var i:int;
var light:Light3D;
var transform:Transform3D;
var rScale:Number;
for (i = 0; i < lightsLength; i++) {
light = lights[i];
transform = light.lightToObjectTransform;
var len:Number = Math.sqrt(transform.c*transform.c + transform.g*transform.g + transform.k*transform.k);
if (light is DirectionalLight) {
drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("c" + light.name + "Direction"), -transform.c/len, -transform.g/len, -transform.k/len);
} else if (light is OmniLight) {
var omni:OmniLight = light as OmniLight;
rScale = Math.sqrt(transform.a*transform.a + transform.e*transform.e + transform.i*transform.i);
rScale += Math.sqrt(transform.b*transform.b + transform.f*transform.f + transform.j*transform.j);
rScale += len;
rScale /= 3;
drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("c" + light.name + "Position"), transform.d, transform.h, transform.l);
drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("c" + light.name + "Radius"), 1, omni.attenuationEnd*rScale - omni.attenuationBegin*rScale, omni.attenuationBegin*rScale);
} else if (light is SpotLight) {
var spot:SpotLight = light as SpotLight;
drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("c" + light.name + "Position"), transform.d, transform.h, transform.l);
drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("c" + light.name + "Axis"), -transform.c/len, -transform.g/len, -transform.k/len);
rScale = Math.sqrt(transform.a*transform.a + transform.e*transform.e + transform.i*transform.i);
rScale += Math.sqrt(transform.b*transform.b + transform.f*transform.f + transform.j*transform.j);
rScale += len;
rScale /= 3;
var falloff:Number = Math.cos(spot.falloff*0.5);
var hotspot:Number = Math.cos(spot.hotspot*0.5);
drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("c" + light.name + "Radius"), spot.attenuationEnd*rScale - spot.attenuationBegin*rScale, spot.attenuationBegin*rScale, hotspot == falloff ? 0.000001 : hotspot - falloff, falloff);
}
drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("c" + light.name + "Color"), light.red, light.green, light.blue);
}
}
// 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.<Light3D>, lightsLength:int, objectRenderPriority:int = -1):void {
if (diffuseMap == null || diffuseMap._texture == null || opacityMap != null && opacityMap._texture == null) return;
var object:Object3D = surface.object;
// Buffers
var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION);
var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]);
var normalsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.NORMAL);
if (positionBuffer == null || uvBuffer == null || normalsBuffer == null) return;
// Program
var light:Light3D;
var materialKey:String = "";
for (var i:int = 0; i < lightsLength; i++) {
light = lights[i];
materialKey += light.lightID;
}
// Refresh programs for this context.
if (camera.context3D != cachedContext3D) {
cachedContext3D = camera.context3D;
programsCache = caches[cachedContext3D];
if (programsCache == null) {
programsCache = new Dictionary();
caches[cachedContext3D] = programsCache;
}
}
var optionsPrograms:Dictionary = programsCache[object.transformProcedure];
if (optionsPrograms == null) {
optionsPrograms = new Dictionary(false);
programsCache[object.transformProcedure] = optionsPrograms;
}
var program:ShaderProgram;
var drawUnit:DrawUnit;
// Opaque passOpaque pass
if (opaquePass && alphaThreshold <= alpha) {
if (alphaThreshold > 0) {
// Alpha test
// use opacityMap if it is presented
program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 1, lights, lightsLength);
drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap, lights, lightsLength);
} else {
// do not use opacityMap at all
program = getProgram(object, optionsPrograms, camera, materialKey, null, 0, lights, lightsLength);
drawUnit = getDrawUnit(program, camera, surface, geometry, null, lights, lightsLength);
}
// 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, materialKey, opacityMap, 2, lights, lightsLength);
drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap, lights, lightsLength);
} else {
// There is no Alpha threshold or check z-buffer by previous pass
program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 0, lights, lightsLength);
drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap, lights, lightsLength);
}
// 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);
}
}
}
}