// 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; } } }