// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. import { Vec3, Vec4, Mat4 } from '../../core/value-types'; import BaseRenderer from '../core/base-renderer'; import enums from '../enums'; import { RecyclePool } from '../memop'; let _a16_view = new Float32Array(16); let _a16_view_inv = new Float32Array(16); let _a16_proj = new Float32Array(16); let _a16_viewProj = new Float32Array(16); let _a4_camPos = new Float32Array(4); let _a64_shadow_lightViewProj = new Float32Array(64); let _a16_shadow_lightViewProjs = []; let _a4_shadow_info = new Float32Array(4); let _camPos = new Vec4(0, 0, 0, 0); let _camFwd = new Vec3(0, 0, 0); let _v3_tmp1 = new Vec3(0, 0, 0); const CC_MAX_LIGHTS = 4; const CC_MAX_SHADOW_LIGHTS = 2; let _float16_pool = new RecyclePool(() => { return new Float32Array(16); }, 8); function sortView (a, b) { return (a._priority - b._priority); } export default class ForwardRenderer extends BaseRenderer { constructor(device, builtin) { super(device, builtin); this._time = new Float32Array(4); this._lights = []; this._shadowLights = []; this._numLights = 0; this._defines = { }; this._registerStage('shadowcast', this._shadowStage.bind(this)); this._registerStage('opaque', this._opaqueStage.bind(this)); this._registerStage('transparent', this._transparentStage.bind(this)); } reset () { _float16_pool.reset(); super.reset(); } render (scene, dt) { this.reset(); if (!CC_EDITOR) { if (dt) { this._time[0] += dt; this._time[1] = dt; this._time[2] ++; } this._device.setUniform('cc_time', this._time); } this._updateLights(scene); const canvas = this._device._gl.canvas; for (let i = 0; i < scene._cameras.length; ++i) { let view = this._requestView(); let width = canvas.width; let height = canvas.height; let camera = scene._cameras.data[i]; camera.extractView(view, width, height); } // render by cameras this._viewPools.sort(sortView); for (let i = 0; i < this._viewPools.length; ++i) { let view = this._viewPools.data[i]; this._render(view, scene); } } // direct render a single camera renderCamera (camera, scene) { this.reset(); this._updateLights(scene); const canvas = this._device._gl.canvas; let width = canvas.width; let height = canvas.height; let view = this._requestView(); camera.extractView(view, width, height); // render by cameras this._viewPools.sort(sortView); for (let i = 0; i < this._viewPools.length; ++i) { let view = this._viewPools.data[i]; this._render(view, scene); } } _updateLights (scene) { this._lights.length = 0; this._shadowLights.length = 0; let lights = scene._lights; for (let i = 0; i < lights.length; ++i) { let light = lights.data[i]; light.update(this._device); if (light.shadowType !== enums.SHADOW_NONE) { if (this._shadowLights.length < CC_MAX_SHADOW_LIGHTS) { this._shadowLights.unshift(light); } let view = this._requestView(); light.extractView(view, ['shadowcast']); this._lights.splice(0, 0, light); } else { this._lights.push(light); } } this._updateLightDefines(); this._numLights = lights._count; } _updateLightDefines () { let defines = this._defines; for (let i = 0; i < this._lights.length; ++i) { let light = this._lights[i]; let lightKey = `CC_LIGHT_${i}_TYPE`; let shadowKey = `CC_SHADOW_${i}_TYPE`; if (defines[lightKey] !== light._type){ defines[lightKey] = light._type; this._definesChanged = true; } if (defines[shadowKey] !== light._shadowType){ defines[shadowKey] = light._shadowType; this._definesChanged = true; } } let newCount = Math.min(CC_MAX_LIGHTS, this._lights.length); if (defines.CC_NUM_LIGHTS !== newCount) { defines.CC_NUM_LIGHTS = newCount; this._definesChanged = true; } newCount = Math.min(CC_MAX_LIGHTS, this._shadowLights.length); if (defines.CC_NUM_SHADOW_LIGHTS !== newCount) { defines.CC_NUM_SHADOW_LIGHTS = newCount; this._definesChanged = true; } } _submitLightsUniforms () { let device = this._device; if (this._lights.length > 0) { let positionAndRanges = _float16_pool.add(); let directions = _float16_pool.add(); let colors = _float16_pool.add(); let lightNum = Math.min(CC_MAX_LIGHTS, this._lights.length); for (let i = 0; i < lightNum; ++i) { let light = this._lights[i]; let index = i * 4; colors.set(light._colorUniform, index); directions.set(light._directionUniform, index); positionAndRanges.set(light._positionUniform, index); positionAndRanges[index+3] = light._range; if (light._type === enums.LIGHT_SPOT) { directions[index+3] = light._spotUniform[0]; colors[index+3] = light._spotUniform[1]; } else { directions[index+3] = 0; colors[index+3] = 0; } } device.setUniform('cc_lightDirection', directions); device.setUniform('cc_lightColor', colors); device.setUniform('cc_lightPositionAndRange', positionAndRanges); } } _submitShadowStageUniforms(view) { let light = view._shadowLight; let shadowInfo = _a4_shadow_info; shadowInfo[0] = light.shadowMinDepth; shadowInfo[1] = light.shadowMaxDepth; shadowInfo[2] = light.shadowDepthScale; shadowInfo[3] = light.shadowDarkness; this._device.setUniform('cc_shadow_map_lightViewProjMatrix', Mat4.toArray(_a16_viewProj, view._matViewProj)); this._device.setUniform('cc_shadow_map_info', shadowInfo); this._device.setUniform('cc_shadow_map_bias', light.shadowBias); this._defines.CC_SHADOW_TYPE = light._shadowType; } _submitOtherStagesUniforms() { let shadowInfo = _float16_pool.add(); for (let i = 0; i < this._shadowLights.length; ++i) { let light = this._shadowLights[i]; let view = _a16_shadow_lightViewProjs[i]; if (!view) { view = _a16_shadow_lightViewProjs[i] = new Float32Array(_a64_shadow_lightViewProj.buffer, i * 64, 16); } Mat4.toArray(view, light.viewProjMatrix); let index = i*4; shadowInfo[index] = light.shadowMinDepth; shadowInfo[index+1] = light.shadowMaxDepth; shadowInfo[index+2] = light._shadowResolution; shadowInfo[index+3] = light.shadowDarkness; } this._device.setUniform(`cc_shadow_lightViewProjMatrix`, _a64_shadow_lightViewProj); this._device.setUniform(`cc_shadow_info`, shadowInfo); // this._device.setUniform(`cc_frustumEdgeFalloff_${index}`, light.frustumEdgeFalloff); } _sortItems (items) { // sort items items.sort((a, b) => { // if (a.layer !== b.layer) { // return a.layer - b.layer; // } if (a.passes.length !== b.passes.length) { return a.passes.length - b.passes.length; } return a.sortKey - b.sortKey; }); } _shadowStage (view, items) { // update rendering this._submitShadowStageUniforms(view); // this._sortItems(items); // draw it for (let i = 0; i < items.length; ++i) { let item = items.data[i]; if (item.effect.getDefine('CC_CASTING_SHADOW')) { this._draw(item); } } } _drawItems (view, items) { let shadowLights = this._shadowLights; if (shadowLights.length === 0 && this._numLights === 0) { for (let i = 0; i < items.length; ++i) { let item = items.data[i]; this._draw(item); } } else { for (let i = 0; i < items.length; ++i) { let item = items.data[i]; for (let shadowIdx = 0; shadowIdx < shadowLights.length; ++shadowIdx) { this._device.setTexture('cc_shadow_map_'+shadowIdx, shadowLights[shadowIdx].shadowMap, this._allocTextureUnit()); } this._draw(item); } } } _opaqueStage (view, items) { view.getPosition(_camPos); // update uniforms this._device.setUniform('cc_matView', Mat4.toArray(_a16_view, view._matView)); this._device.setUniform('cc_matViewInv', Mat4.toArray(_a16_view_inv, view._matViewInv)); this._device.setUniform('cc_matProj', Mat4.toArray(_a16_proj, view._matProj)); this._device.setUniform('cc_matViewProj', Mat4.toArray(_a16_viewProj, view._matViewProj)); this._device.setUniform('cc_cameraPos', Vec4.toArray(_a4_camPos, _camPos)); // update rendering this._submitLightsUniforms(); this._submitOtherStagesUniforms(); this._drawItems(view, items); } _transparentStage (view, items) { view.getPosition(_camPos); view.getForward(_camFwd); // update uniforms this._device.setUniform('cc_matView', Mat4.toArray(_a16_view, view._matView)); this._device.setUniform('cc_matViewInv', Mat4.toArray(_a16_view_inv, view._matViewInv)); this._device.setUniform('cc_matProj', Mat4.toArray(_a16_proj, view._matProj)); this._device.setUniform('cc_matViewProj', Mat4.toArray(_a16_viewProj, view._matViewProj)); this._device.setUniform('cc_cameraPos', Vec4.toArray(_a4_camPos, _camPos)); this._submitLightsUniforms(); this._submitOtherStagesUniforms(); // calculate zdist for (let i = 0; i < items.length; ++i) { let item = items.data[i]; // TODO: we should use mesh center instead! item.node.getWorldPosition(_v3_tmp1); Vec3.sub(_v3_tmp1, _v3_tmp1, _camPos); item.sortKey = -Vec3.dot(_v3_tmp1, _camFwd); } this._sortItems(items); this._drawItems(view, items); } }