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

337 lines
9.5 KiB
JavaScript

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