一. 太阳光晕概述:
当我们把镜头对准某一发光体的时候,会有光晕现象产生,这种光晕现象也称作“镜头眩光”。 在晴天的场景中实现这样一个光晕特效,往往会使得场景更为真实逼真。
二. 光晕效果显示的基本原理:
光晕特效实现的原理其实挺简单,它可以由一个主光晕加一系列镜头光环组成。主光晕以光源为中心,镜头光环则散列在由光源及镜头中心决定的直线上。
为实现逼真的光晕效果首先需要准备一组大小形状不同的光晕素材,以Away3D自带的实例为例:
在每帧绘制时,如果当前太阳的位置在屏幕中,则在太阳和屏幕中心点所在直线均匀放置若干个光晕素材的公告板即可
实现较为真实的太阳光晕效果。
:
定义光晕对象:
class FlareObject
{
private var flareSize:Number = 144;
public var sprite:Bitmap;
public var size:Number;
public var position:Number;
public var opacity:Number;
/**
* Constructor
*/
public function FlareObject(sprite:Bitmap, size:Number, position:Number, opacity:Number)
{
this.sprite = new Bitmap(new BitmapData(sprite.bitmapData.width, sprite.bitmapData.height, true, 0xFFFFFFFF));
this.sprite.bitmapData.copyChannel(sprite.bitmapData, sprite.bitmapData.rect, new Point(), BitmapDataChannel.RED, BitmapDataChannel.ALPHA);
this.sprite.alpha = opacity/100;
this.sprite.smoothing = true;
this.sprite.scaleX = this.sprite.scaleY = size*flareSize/sprite.width;
this.size = size;
this.position = position;
this.opacity = opacity;
}
}
该类中包含sprite光晕图片,通过设置其位置大小和alpha值来表现一个光晕效果.
初始化时创建一组光晕对象
private function initLensFlare():void
{
flares.push(new FlareObject(new Flare10(), 3.2, -0.01, 147.9));
flares.push(new FlareObject(new Flare11(), 6, 0, 30.6));
flares.push(new FlareObject(new Flare7(), 2, 0, 25.5));
flares.push(new FlareObject(new Flare7(), 4, 0, 17.85));
flares.push(new FlareObject(new Flare12(), 0.4, 0.32, 22.95));
flares.push(new FlareObject(new Flare6(), 1, 0.68, 20.4));
flares.push(new FlareObject(new Flare2(), 1.25, 1.1, 48.45));
flares.push(new FlareObject(new Flare3(), 1.75, 1.37, 7.65));
flares.push(new FlareObject(new Flare4(), 2.75, 1.85, 12.75));
flares.push(new FlareObject(new Flare8(), 0.5, 2.21, 33.15));
flares.push(new FlareObject(new Flare6(), 4, 2.5, 10.4));
flares.push(new FlareObject(new Flare7(), 10, 2.66, 50));
}
每帧根据太阳位置计算是否显示光晕 以及显示光晕的位置等信息
private function updateFlares():void
{
var flareVisibleOld:Boolean = flareVisible;
var sunScreenPosition:Vector3D = view.project(sun.scenePosition);
var xOffset:Number = sunScreenPosition.x - stage.stageWidth/2;
var yOffset:Number = sunScreenPosition.y - stage.stageHeight/2;
var earthScreenPosition:Vector3D = view.project(earth.scenePosition);
var earthRadius:Number = 190*stage.stageHeight/earthScreenPosition.z;
var flareObject:FlareObject;
flareVisible = (sunScreenPosition.x > 0 && sunScreenPosition.x < stage.stageWidth && sunScreenPosition.y > 0 && sunScreenPosition.y < stage.stageHeight && sunScreenPosition.z > 0 && Math.sqrt(xOffset*xOffset + yOffset*yOffset) > earthRadius)? true : false;
//update flare visibility
if (flareVisible != flareVisibleOld) {
for each (flareObject in flares) {
if (flareVisible)
addChild(flareObject.sprite);
else
removeChild(flareObject.sprite);
}
}
//update flare position
if (flareVisible) {
var flareDirection:Point = new Point(xOffset, yOffset);
for each (flareObject in flares) {
flareObject.sprite.x = sunScreenPosition.x - flareDirection.x*flareObject.position - flareObject.sprite.width/2;
flareObject.sprite.y = sunScreenPosition.y - flareDirection.y*flareObject.position - flareObject.sprite.height/2;
}
}
}
:
五. 上帝之光效果:
当我们把透过树叶等缝隙观察发光源时会出现一道道明显的光线,被称为上帝之光效果。如下图:
五. 上帝之光实现原理:
我们对上帝之光的绘制采用后处理的方式,首先在每帧绘制时先绘制到纹理(rtt),定位到发光源(如太阳)在屏幕空间的位置,然后对每个顶点到发光源位置的线段上进行n次采样并加以权重值求和作为该点的最终颜色信息,最终再将此纹理会知道后台缓冲区。
五. 上帝之光AS3滤镜方式实现代码:
package away3d.filters.tasks
{
import flash.display3D.Context3DProgramType;
import flash.display3D.textures.Texture;
import away3d.cameras.Camera3D;
import away3d.core.managers.Stage3DProxy;
import away3d.filters.tasks.Filter3DTaskBase;
/**
* GodRays Effect
* @author vancopper
*
*/
public class Filter3DGodRayTask extends Filter3DTaskBase
{
private var _numSteps:int = 30;
private var _lightPos:Vector.<Number> = Vector.<Number>([.5, .5, 1, 1]);
private var _values1:Vector.<Number> = Vector.<Number>([1, 1, 1, 1]);//numsamples, density, numsamples*density, 1 / numsamples * density
private var _values2:Vector.<Number> = Vector.<Number>([1, 1, 1, 1]);//weight, decay, exposure
private var _lightX:Number = 0;
private var _lightY:Number = 0;
private var _weight:Number = .5;
private var _decay:Number = .87;
private var _exposure:Number = .35;
private var _density:Number = 2.0;
public function Filter3DGodRayTask()
{
// super(requireDepthRender);
}
override protected function getFragmentCode():String
{
var code:String = "";
// Calculate vector from pixel to light source in screen space.
code += "sub ft0.xy, v0.xy, fc0.xy \n";
// Divide by number of samples and scale by control factor.
code += "mul ft0.xy, ft0.xy, fc1.ww \n";
// Store initial sample.
code += "tex ft1, v0, fs0 <2d, clamp, linear, mipnone> \n";
// Set up illumination decay factor.
code += "mov ft2.x, fc0.w \n";
// Store the texcoords
code += "mov ft4.xy, v0.xy \n";
for (var i:int = 0; i < _numSteps; i++)
{
// Step sample location along ray.
code += "sub ft4.xy, ft4.xy, ft0.xy \n";
// Retrieve sample at new location.
code += "tex ft3, ft4.xy, fs0 <2d, clamp, linear, mipnone> \n";
// Apply sample attenuation scale/decay factors.
code += "mul ft2.y, ft2.x, fc2.x \n";
code += "mul ft3.xyz, ft3.xyz, ft2.yyy \n";
// Accumulate combined color.
code += "add ft1.xyz, ft1.xyz, ft3.xyz \n";
// Update exponential decay factor.
code += "mul ft2.x, ft2.x, fc2.y \n";
}
// Output final color with a further scale control factor.
code += "mul ft1.xyz, ft1.xyz, fc2.zzz \n";
code += "mov oc, ft1";
return code;
}
override public function activate(stage3DProxy:Stage3DProxy, camera:Camera3D, depthTexture:Texture):void
{
// light position
_lightPos[0] = this._lightX / stage3DProxy.width;
_lightPos[1] = this._lightY / stage3DProxy.height;
// numsamples, density, numsamples * density, 1 / numsamples * density
_values1[0] = _numSteps;
_values1[1] = this._density;
_values1[2] = _numSteps * _values1[1];
_values1[3] = 1 / _values1[2];
// weight, decay, exposure
_values2[0] = this._weight;
_values2[1] = this._decay;
_values2[2] = this._exposure;
stage3DProxy.context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, _lightPos, 1 );
stage3DProxy.context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, _values1, 1 );
stage3DProxy.context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 2, _values2, 1 );
}
public function set lightX(value:Number):void { this._lightX = value; }
public function get lightX():Number { return this._lightX; }
public function set lightY(value:Number):void { this._lightY = value; }
public function get lightY():Number { return this._lightY; }
public function set decay(value:Number):void { this._decay = value; }
public function get decay():Number { return this._decay; }
public function set exposure(value:Number):void { this._exposure = value; }
public function get exposure():Number { return this._exposure; }
public function set weight(value:Number):void { this._weight = value; }
public function get weight():Number { return this._weight; }
public function set density(value:Number):void { this._density = value; }
public function get density():Number { return this._density; }
}
}
package away3d.filters
{
import away3d.filters.Filter3DBase;
import away3d.filters.tasks.Filter3DGodRayTask;
/**
* GodRays Effect
* @author vancopper
*
*/
public class GodRayFilter3D extends Filter3DBase
{
private var _godRaysTask:Filter3DGodRayTask;
public function GodRayFilter3D()
{
super();
_godRaysTask = new Filter3DGodRayTask();
addTask(_godRaysTask);
}
public function set lightX(value:Number):void { _godRaysTask.lightX = value; }
public function get lightX():Number { return _godRaysTask.lightX; }
public function set lightY(value:Number):void { _godRaysTask.lightY = value; }
public function get lightY():Number { return _godRaysTask.lightY; }
public function set decay(value:Number):void { _godRaysTask.decay = value; }
public function get decay():Number { return _godRaysTask.decay; }
public function set exposure(value:Number):void { _godRaysTask.exposure = value; }
public function get exposure():Number { return _godRaysTask.exposure; }
public function set weight(value:Number):void { _godRaysTask.weight = value; }
public function get weight():Number { return _godRaysTask.weight; }
public function set density(value:Number):void { _godRaysTask.density = value; }
public function get density():Number { return _godRaysTask.density; }
}
}
只需要在必要的时候开启此滤镜即可实现上帝之光效果