mirror of
https://github.com/MapMakersAndProgrammers/Alternativa3D.git
synced 2025-10-26 09:59:10 -07:00
356 lines
17 KiB
ActionScript
356 lines
17 KiB
ActionScript
/**
|
||
* 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);
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|