var spotLight = scene.addChild(new Laya.SpotLight()) as Laya.SpotLight;
spotLight.color = new Laya.Vector3(1, 1, 0);
spotLight.transform.position = new Laya.Vector3(0.0, 1.2, 0.0);
var mat = spotLight.transform.worldMatrix;
mat.setForward(new Laya.Vector3(0.15, -1.0, 0.0));
spotLight.transform.worldMatrix = mat;
spotLight.range = 6.0;
spotLight.spotAngle = 32;
setForward 平行光的方向,分别代表x、y、z轴上的方向,负数为负轴,正数为正轴,值的范围为-1—0—1,超过范围后为-1或1,初学者们可以在这个范围内设值观察方向的变化。
(动图4-8)
如动图4-8,设置聚光灯旋转看到的效果
五、AreaLight
AreaLight(区域光)可以通过空间中的两个形状之一定义区域光:矩形或圆盘。区域光从该形状的一侧发射光。发射的光在该形状的表面区域的所有方向上均匀传播。区域光提供的照明强度以与光源距离的平方反比确定的速率减小(见平方反比定律)。由于此照明计算非常占用处理器,因此区域灯光在运行时不可用,只能烘焙到光照贴图中。
由于区域光同时从几个不同方向照亮对象,因此阴影趋向于比其他光源类型更柔和、细腻。您可以使用这种光源来创建逼真的路灯或靠近玩家的一排灯光。小的区域光可以模拟较小的光源(例如室内光照),但效果比点光源更逼真。
如图5-1所示,在一个场景中,通过调整区域光的尺寸和扩散,可以调整场景的光照效果
(动图5-1)
5.1 创建区域光
(动图5-2)
如动图5-2所示,在Scene3D或者任意节点下,鼠标右键选择Light,点击AreaLight,即可在场景中创建一个区域光。
5.2 组件属性
5.2.1 基础属性
如图5-3所示,当创建一个AreaLight后,在Inspector属性面板中会有如下几个属性
(图5-3)
Color
:区域光的颜色
Intensity
:区域光的强度
Shape
:区域光的形状
Rect
:矩形
Elliptic
:圆形
Spread
:扩散度
5.2.2 烘焙属性
Max Bounces
:烘焙的最大反弹数
六、如何为灯光添加阴影
投影是灯光照射模型时产生的即时阴影,可随着灯光角度、灯光强度、模型位置等变化而变化。投影是3D世界最重要的因素之一,能产生更加强烈的立体感。
即时阴影非常损耗性能,不能用得太多,特别是游戏场景,模型量较大,一般我们不使用实时投影,而使用静态的光照贴图。
6.1 灯光的阴影属性
要让场景中产生投影,我们需了解灯光的以下属性,每种光源都具备这些属性
6.1.1 阴影模式
(图6-1)
ShadowMode
:阴影模式,共分为四种模式:
None
:不产生阴影
Hard
:硬阴影,对性能要求较低
(图6-1-1)
SoftLow
:低强度软阴影,对性能要求一般
(图6-1-2)
SoftHigh
:高强度软阴影,对性能要求较高
(图6-1-3)效果最好
硬阴影和软阴影的区别:硬阴影(hard shadow)是指由理想电光源(即聚光性能好的单一光源)产生的阴暗均一、边界分明的阴影,它只包含物体的本影(umbra)。软阴影(soft shadow)则是由线、面或体光源等产生的边界柔和、有一定明暗过度的阴影,它包括物体的本影和半影(penumbra)。当物体完全处于阴影中时,即在本影域内,而当物体受到部分光照时则处于半影域内。
6.1.2 阴影属性
当我们选择一种阴影模式后,可以看到设置阴影的属性
(图6-2)
shadowStrength
:阴影强度,该值越大,阴影越明显。
(动图6-2-1)
shadowDistance
:灯光产生阴影的范围,范围是指摄像机到模型的距离,超出这个范围的模型将不会产生阴影与接受阴影,开发者可以根据场景大小进行设置。
(图6-2-2)
图6-2-2中,shadowDistance
距离为8时的效果
shadowDepthBias
:阴影贴图基于深度的偏移,将深度做一个偏移可以有效解决阴影痤疮(”shadow acne”)。但需要提醒的是:在 shadowDepthBias
过大时会导致阴影脱离物体,即”Peter Panning”现象的出现。
shadowNormalBias
:阴影贴图基于法线的偏移,将阴影Caster的表面沿着法线方向的反方向偏移,以防止自身阴影(“shadow acne”)伪影的出现。 较大的值可以更好地防止阴影痤疮(”shadow acne”),但要以阴影形状小于实际对象为代价。
shadowNearPlane
:阴影视锥的近裁面,可以对阴影视锥的近裁面进行设置。
6.1.3 阴影的级联模式
(图6-3)
shadowCascadesMode
:阴影的级联模式,数量越大,产生阴影贴图时,将视锥体划分的子视锥体越多,对应的阴影贴图也越多,阴影的质量会越好。
TwoCascades
:二级级联阴影分割比例。
(图6-3-1)
图6-3-1中,TwoCascades
的效果
FourCascades
:四级级联阴影分割比例,X、Y、Z依次为其分割比例,Z必须大于Y,Y必须大于X。
(图6-3-2)
图6-3-2中,FourCascades
的效果
6.2 模型的投影属性
除了光源的阴影设置,需要在模型上设置投影属性:如图6-4所示
(图6-4)
receiveShadow
:是否接受阴影,当模型此属性为true时,计算出的阴影会在此模型上显示出来。在游戏中,我们可以把场景的地面,及场景中可走动区域中的模型 castShadow
属性设置为true。
castShadow
:是否产生阴影,当模型此属性为true时,灯光根据产生阴影的模型位置、模型网格形状大小、与灯光的角度等进行阴影计算,然后在接受阴影的模型上产生阴影。比如场景中的角色、NPC等活动游戏元素可以开启此属性。
如动图6-5所示,展现了平行光实时阴影的效果
(动图6-5)
如动图6-6所示,展现了聚光灯实时阴影的效果
(动图6-6)
6.3 使用代码设置
灯光方面的设置:
directionLight.shadowMode = ShadowMode.SoftLow;
directionLight.shadowDistance = 3;
directionLight.shadowResolution = 1024;
directionLight.shadowCascadesMode = ShadowCascadesMode.NoCascades;
directionLight.shadowNormalBias = 4;
开启地面接收阴影和模型产生阴影:
var grid: Sprite3D = <Sprite3D>scene.addChild(Loader.getRes("res/threeDimen/staticModel/grid/plane.lh"));
(<MeshSprite3D>grid.getChildAt(0)).meshRenderer.receiveShadow = true;
var sphereSprite: MeshSprite3D = this.addPBRSphere(PrimitiveMesh.createSphere(0.1), new Vector3(0, 0.2, 0.5), scene);
sphereSprite.meshRenderer.castShadow = true;
七、多光源渲染
如图7-1所示,在IDE的项目设置中,可以对支持多光源做设置
(图7-1)
Enable Multi Light:是否支持多光源
Max Light Count:最大支持的光源数量,目前最大是50
Light Cluster Count: X、Y、Z轴的光照集群数量
X、Y、Z轴的光照集群数量,Z值会影响Cluster接受区域光(点光、聚光)影响的数量,Math.floor(2048 / lightClusterCount.z - 1) * 4 为每个Cluster的最大平均接受区域光数量,如果每个Cluster所接受光源影响的平均数量大于该值,则较远的Cluster会忽略其中多余的光照影响。
(动图7-2)
动图7-2,为多光源的示例,下面为创建多光源的代码
export class MultiLight extends BaseScript {
constructor() {
super();
onAwake(): void {
var moveScript: LightMoveScript = this.camera.addComponent(LightMoveScript);
var moverLights: LightSprite[] = moveScript.lights;
var offsets: Vector3[] = moveScript.offsets;
var moveRanges: Vector3[] = moveScript.moveRanges;
moverLights.length = 15;
for (var i: number = 0; i < 15; i++) {
var pointLight: PointLight = (<PointLight>this.scene.addChild(new PointLight()));
pointLight.range = 2.0 + Math.random() * 8.0;
pointLight.color.setValue(Math.random(), Math.random(), Math.random(), 1);
pointLight.intensity = 6.0 + Math.random() * 8;
moverLights[i] = pointLight;
offsets[i] = new Vector3((Math.random() - 0.5) * 10, pointLight.range * 0.75, (Math.random() - 0.5) * 10);
moveRanges[i] = new Vector3((Math.random() - 0.5) * 40, 0, (Math.random() - 0.5) * 40);
var spotLight: SpotLight = (<SpotLight>this.scene.addChild(new SpotLight()));
spotLight.transform.localPosition = new Vector3(0.0, 9.0, -35.0);
spotLight.transform.localRotationEuler = new Vector3(-15.0, 180.0, 0.0);
spotLight.color.setValue(Math.random(), Math.random(), Math.random(), 1);
spotLight.range = 50;
spotLight.intensity = 15;
spotLight.spotAngle = 60;
class LightMoveScript extends Laya.Script {
forward: Vector3 = new Vector3();
lights: LightSprite[] = [];
offsets: Vector3[] = [];
moveRanges: Vector3[] = [];
onUpdate(): void {
var seed: number = Laya.timer.currTimer * 0.002;
for (var i: number = 0, n: number = this.lights.length; i < n; i++) {
var transform: Transform3D = this.lights[i].transform;
var pos: Vector3 = transform.localPosition;
var off: Vector3 = this.offsets[i];
var ran: Vector3 = this.moveRanges[i];
pos.x = off.x + Math.sin(seed) * ran.x;
pos.y = off.y + Math.sin(seed) * ran.y;
pos.z = off.z + Math.sin(seed) * ran.z;
transform.localPosition = pos;
2023-11-29 17:33:41