mirror of
https://github.com/smallmain/cocos-enhance-kit.git
synced 2025-01-14 06:51:08 +00:00
486 lines
12 KiB
JavaScript
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;
|
|
}
|
|
|
|
}
|
|
}
|