2022-06-25 00:23:03 +08:00

486 lines
12 KiB
JavaScript

// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
import { Mat4, Mat3, Vec3, toRadian } from '../../core/value-types';
import gfx from '../gfx';
import enums from '../enums';
const _forward = cc.v3(0, 0, -1);
let _m4_tmp = cc.mat4();
let _m3_tmp = Mat3.create();
let _transformedLightDirection = cc.v3(0, 0, 0);
// compute light viewProjMat for shadow.
function _computeSpotLightViewProjMatrix(light, outView, outProj) {
// view matrix
light._node.getWorldRT(outView);
Mat4.invert(outView, outView);
// proj matrix
Mat4.perspective(outProj, light._spotAngle * light._spotAngleScale, 1, light._shadowMinDepth, light._shadowMaxDepth);
}
function _computeDirectionalLightViewProjMatrix(light, outView, outProj) {
// view matrix
light._node.getWorldRT(outView);
Mat4.invert(outView, outView);
// TODO: should compute directional light frustum based on rendered meshes in scene.
// proj matrix
let halfSize = light._shadowFrustumSize / 2;
Mat4.ortho(outProj, -halfSize, halfSize, -halfSize, halfSize, light._shadowMinDepth, light._shadowMaxDepth);
}
function _computePointLightViewProjMatrix(light, outView, outProj) {
// view matrix
light._node.getWorldRT(outView);
Mat4.invert(outView, outView);
// The transformation from Cartesian to polar coordinates is not a linear function,
// so it cannot be achieved by means of a fixed matrix multiplication.
// Here we just use a nearly 180 degree perspective matrix instead.
Mat4.perspective(outProj, toRadian(179), 1, light._shadowMinDepth, light._shadowMaxDepth);
}
/**
* A representation of a light source.
* Could be a point light, a spot light or a directional light.
*/
export default class Light {
/**
* Setup a default directional light with no shadows
*/
constructor() {
this._poolID = -1;
this._node = null;
this._type = enums.LIGHT_DIRECTIONAL;
this._color = new Vec3(1, 1, 1);
this._intensity = 1;
// used for spot and point light
this._range = 1;
// used for spot light, default to 60 degrees
this._spotAngle = toRadian(60);
this._spotExp = 1;
// cached for uniform
this._directionUniform = new Float32Array(3);
this._positionUniform = new Float32Array(3);
this._colorUniform = new Float32Array([this._color.x * this._intensity, this._color.y * this._intensity, this._color.z * this._intensity]);
this._spotUniform = new Float32Array([Math.cos(this._spotAngle * 0.5), this._spotExp]);
// shadow params
this._shadowType = enums.SHADOW_NONE;
this._shadowFrameBuffer = null;
this._shadowMap = null;
this._shadowMapDirty = false;
this._shadowDepthBuffer = null;
this._shadowResolution = 1024;
this._shadowBias = 0.0005;
this._shadowDarkness = 1;
this._shadowMinDepth = 1;
this._shadowMaxDepth = 1000;
this._frustumEdgeFalloff = 0; // used by directional and spot light.
this._viewProjMatrix = cc.mat4();
this._spotAngleScale = 1; // used for spot light.
this._shadowFrustumSize = 50; // used for directional light.
}
/**
* Get the hosting node of this camera
* @returns {Node} the hosting node
*/
getNode() {
return this._node;
}
/**
* Set the hosting node of this camera
* @param {Node} node the hosting node
*/
setNode(node) {
this._node = node;
}
/**
* set the color of the light source
* @param {number} r red channel of the light color
* @param {number} g green channel of the light color
* @param {number} b blue channel of the light color
*/
setColor(r, g, b) {
Vec3.set(this._color, r, g, b);
this._colorUniform[0] = r * this._intensity;
this._colorUniform[1] = g * this._intensity;
this._colorUniform[2] = b * this._intensity;
}
/**
* get the color of the light source
* @returns {Vec3} the light color
*/
get color() {
return this._color;
}
/**
* set the intensity of the light source
* @param {number} val the light intensity
*/
setIntensity(val) {
this._intensity = val;
this._colorUniform[0] = val * this._color.x;
this._colorUniform[1] = val * this._color.y;
this._colorUniform[2] = val * this._color.z;
}
/**
* get the intensity of the light source
* @returns {number} the light intensity
*/
get intensity() {
return this._intensity;
}
/**
* set the type of the light source
* @param {number} type light source type
*/
setType(type) {
this._type = type;
}
/**
* get the type of the light source
* @returns {number} light source type
*/
get type() {
return this._type;
}
/**
* set the spot light angle
* @param {number} val spot light angle
*/
setSpotAngle(val) {
this._spotAngle = val;
this._spotUniform[0] = Math.cos(this._spotAngle * 0.5);
}
/**
* get the spot light angle
* @returns {number} spot light angle
*/
get spotAngle() {
return this._spotAngle;
}
/**
* set the spot light exponential
* @param {number} val spot light exponential
*/
setSpotExp(val) {
this._spotExp = val;
this._spotUniform[1] = val;
}
/**
* get the spot light exponential
* @returns {number} spot light exponential
*/
get spotExp() {
return this._spotExp;
}
/**
* set the range of the light source
* @param {number} val light source range
*/
setRange(val) {
this._range = val;
}
/**
* get the range of the light source
* @returns {number} range of the light source
*/
get range() {
return this._range;
}
/**
* set the shadow type of the light source
* @param {number} type light source shadow type
*/
setShadowType(type) {
if (this._shadowType === enums.SHADOW_NONE && type !== enums.SHADOW_NONE) {
this._shadowMapDirty = true;
}
this._shadowType = type;
}
/**
* get the shadow type of the light source
* @returns {number} light source shadow type
*/
get shadowType() {
return this._shadowType;
}
/**
* get the shadowmap of the light source
* @returns {Texture2D} light source shadowmap
*/
get shadowMap() {
return this._shadowMap;
}
/**
* get the view-projection matrix of the light source
* @returns {Mat4} light source view-projection matrix
*/
get viewProjMatrix() {
return this._viewProjMatrix;
}
/**
* set the shadow resolution of the light source
* @param {number} val light source shadow resolution
*/
setShadowResolution(val) {
if (this._shadowResolution !== val) {
this._shadowMapDirty = true;
}
this._shadowResolution = val;
}
/**
* get the shadow resolution of the light source
* @returns {number} light source shadow resolution
*/
get shadowResolution() {
return this._shadowResolution;
}
/**
* set the shadow bias of the light source
* @param {number} val light source shadow bias
*/
setShadowBias(val) {
this._shadowBias = val;
}
/**
* get the shadow bias of the light source
* @returns {number} light source shadow bias
*/
get shadowBias() {
return this._shadowBias;
}
/**
* set the shadow darkness of the light source
* @param {number} val light source shadow darkness
*/
setShadowDarkness(val) {
this._shadowDarkness = val;
}
/**
* get the shadow darkness of the light source
* @returns {number} light source shadow darkness
*/
get shadowDarkness() {
return this._shadowDarkness;
}
/**
* set the shadow min depth of the light source
* @param {number} val light source shadow min depth
*/
setShadowMinDepth(val) {
this._shadowMinDepth = val;
}
/**
* get the shadow min depth of the light source
* @returns {number} light source shadow min depth
*/
get shadowMinDepth() {
if (this._type === enums.LIGHT_DIRECTIONAL) {
return 1.0;
}
return this._shadowMinDepth;
}
/**
* set the shadow max depth of the light source
* @param {number} val light source shadow max depth
*/
setShadowMaxDepth(val) {
this._shadowMaxDepth = val;
}
/**
* get the shadow max depth of the light source
* @returns {number} light source shadow max depth
*/
get shadowMaxDepth() {
if (this._type === enums.LIGHT_DIRECTIONAL) {
return 1.0;
}
return this._shadowMaxDepth;
}
/**
* set the frustum edge falloff of the light source
* @param {number} val light source frustum edge falloff
*/
setFrustumEdgeFalloff(val) {
this._frustumEdgeFalloff = val;
}
/**
* get the frustum edge falloff of the light source
* @returns {number} light source frustum edge falloff
*/
get frustumEdgeFalloff() {
return this._frustumEdgeFalloff;
}
/**
* set the shadow frustum size of the light source
* @param {number} val light source shadow frustum size
*/
setShadowFrustumSize(val) {
this._shadowFrustumSize = val;
}
/**
* get the shadow frustum size of the light source
* @returns {number} light source shadow frustum size
*/
get shadowFrustumSize() {
return this._shadowFrustumSize;
}
/**
* extract a view of this light source
* @param {View} out the receiving view
* @param {string[]} stages the stages using the view
*/
extractView(out, stages) {
// TODO: view should not handle light.
out._shadowLight = this;
// priority. TODO: use varying value for shadow view?
out._priority = -1;
// rect
out._rect.x = 0;
out._rect.y = 0;
out._rect.w = this._shadowResolution;
out._rect.h = this._shadowResolution;
// clear opts
Vec3.set(out._color, 1, 1, 1);
out._depth = 1;
out._stencil = 1;
out._clearFlags = enums.CLEAR_COLOR | enums.CLEAR_DEPTH;
// stages & framebuffer
out._stages = stages;
out._framebuffer = this._shadowFrameBuffer;
// view projection matrix
switch(this._type) {
case enums.LIGHT_SPOT:
_computeSpotLightViewProjMatrix(this, out._matView, out._matProj);
break;
case enums.LIGHT_DIRECTIONAL:
_computeDirectionalLightViewProjMatrix(this, out._matView, out._matProj);
break;
case enums.LIGHT_POINT:
_computePointLightViewProjMatrix(this, out._matView, out._matProj);
break;
case enums.LIGHT_AMBIENT:
break;
default:
console.warn('shadow of this light type is not supported');
}
// view-projection
Mat4.mul(out._matViewProj, out._matProj, out._matView);
this._viewProjMatrix = out._matViewProj;
Mat4.invert(out._matInvViewProj, out._matViewProj);
// update view's frustum
// out._frustum.update(out._matViewProj, out._matInvViewProj);
out._cullingMask = 0xffffffff;
}
_updateLightPositionAndDirection() {
this._node.getWorldMatrix(_m4_tmp);
Mat3.fromMat4(_m3_tmp, _m4_tmp);
Vec3.transformMat3(_transformedLightDirection, _forward, _m3_tmp);
Vec3.toArray(this._directionUniform, _transformedLightDirection);
let pos = this._positionUniform;
let m = _m4_tmp.m;
pos[0] = m[12];
pos[1] = m[13];
pos[2] = m[14];
}
_generateShadowMap(device) {
this._shadowMap = new gfx.Texture2D(device, {
width: this._shadowResolution,
height: this._shadowResolution,
format: gfx.TEXTURE_FMT_RGBA8,
wrapS: gfx.WRAP_CLAMP,
wrapT: gfx.WRAP_CLAMP,
});
this._shadowDepthBuffer = new gfx.RenderBuffer(device,
gfx.RB_FMT_D16,
this._shadowResolution,
this._shadowResolution
);
this._shadowFrameBuffer = new gfx.FrameBuffer(device, this._shadowResolution, this._shadowResolution, {
colors: [this._shadowMap],
depth: this._shadowDepthBuffer,
});
}
_destroyShadowMap() {
if (this._shadowMap) {
this._shadowMap.destroy();
this._shadowDepthBuffer.destroy();
this._shadowFrameBuffer.destroy();
this._shadowMap = null;
this._shadowDepthBuffer = null;
this._shadowFrameBuffer = null;
}
}
/**
* update the light source
* @param {Device} device the rendering device
*/
update(device) {
this._updateLightPositionAndDirection();
if (this._shadowType === enums.SHADOW_NONE) {
this._destroyShadowMap();
} else if (this._shadowMapDirty) {
this._destroyShadowMap();
this._generateShadowMap(device);
this._shadowMapDirty = false;
}
}
}