polygonjs-engine
Version:
node-based webgl 3D engine https://polygonjs.com
264 lines (240 loc) • 8.26 kB
text/typescript
import {Constructor, PolyDictionary} from '../../../../../types/GlobalTypes';
import {WebGLRenderer} from 'three/src/renderers/WebGLRenderer';
import {Vector2} from 'three/src/math/Vector2';
import {Scene} from 'three/src/scenes/Scene';
import {NodeContext} from '../../../../poly/NodeContext';
import {SceneObjNode} from '../../Scene';
import {BaseThreejsCameraObjNodeType} from '../../_BaseCamera';
import {Poly} from '../../../../Poly';
import {
WebGlRendererRopNode,
WebGLRendererWithSampling,
DEFAULT_SHADOW_MAP_TYPE,
DEFAULT_OUTPUT_ENCODING,
DEFAULT_TONE_MAPPING,
} from '../../../rop/WebGlRenderer';
import {Css2DRendererRopNode} from '../../../rop/Css2DRenderer';
import {Css3DRendererRopNode} from '../../../rop/Css3DRenderer';
import {RopType} from '../../../../poly/registers/nodes/Rop';
import {ParamConfig} from '../../../utils/params/ParamsConfig';
export function CameraRenderParamConfig<TBase extends Constructor>(Base: TBase) {
return class Mixin extends Base {
render = ParamConfig.FOLDER();
setScene = ParamConfig.BOOLEAN(0);
/** @param override rendered scene */
scene = ParamConfig.OPERATOR_PATH('/scene1', {
visibleIf: {setScene: 1},
nodeSelection: {
context: NodeContext.OBJ,
types: [SceneObjNode.type()],
},
});
setRenderer = ParamConfig.BOOLEAN(0);
/** @param override renderer used */
renderer = ParamConfig.OPERATOR_PATH('./renderers1/webGlRenderer1', {
visibleIf: {setRenderer: 1},
nodeSelection: {
context: NodeContext.ROP,
types: [WebGlRendererRopNode.type()],
},
});
setCssRenderer = ParamConfig.BOOLEAN(0);
/** @param add a css renderer */
cssRenderer = ParamConfig.OPERATOR_PATH('./renderers1/css2DRenderer1', {
visibleIf: {setCssRenderer: 1},
nodeSelection: {
context: NodeContext.ROP,
types: [RopType.CSS2D, RopType.CSS3D],
},
});
};
}
export class RenderController {
private _renderers_by_canvas_id: PolyDictionary<WebGLRenderer> = {};
private _resolution_by_canvas_id: PolyDictionary<Vector2> = {};
private _resolved_scene: Scene | undefined;
private _resolved_renderer_rop: WebGlRendererRopNode | undefined;
private _resolved_cssRenderer_rop: Css2DRendererRopNode | Css3DRendererRopNode | undefined;
constructor(private node: BaseThreejsCameraObjNodeType) {}
//
//
// render methods
//
//
render(canvas: HTMLCanvasElement, size?: Vector2, aspect?: number) {
if (this.node.pv.doPostProcess) {
this.node.post_process_controller.render(canvas, size);
} else {
this.render_with_renderer(canvas);
}
if (this._resolved_cssRenderer_rop && this._resolved_scene && this.node.pv.setCssRenderer) {
const cssRenderer = this.cssRenderer(canvas);
if (cssRenderer) {
cssRenderer.render(this._resolved_scene, this.node.object);
}
}
}
render_with_renderer(canvas: HTMLCanvasElement) {
const renderer = this.renderer(canvas);
if (renderer) {
// renderer.autoClear = false;
if (this._resolved_scene) {
renderer.render(this._resolved_scene, this.node.object);
}
}
}
async update() {
this.update_scene();
this.update_renderer();
this.update_cssRenderer();
}
//
//
// SCENE
//
//
get resolved_scene() {
return this._resolved_scene;
}
private update_scene() {
if (this.node.pv.setScene) {
const param = this.node.p.scene;
if (param.isDirty()) {
param.find_target();
}
const node = param.found_node_with_context_and_type(NodeContext.OBJ, SceneObjNode.type());
if (node) {
// it's probably weird to cook the node here, but that works for now
if (node.isDirty()) {
node.cookController.cook_main_without_inputs();
}
this._resolved_scene = node.object;
}
} else {
this._resolved_scene = this.node.scene().threejsScene();
}
}
//
//
// RENDERER
//
//
private update_renderer() {
if (this.node.pv.setRenderer) {
const param = this.node.p.renderer;
if (param.isDirty()) {
param.find_target();
}
this._resolved_renderer_rop = param.found_node_with_context_and_type(NodeContext.ROP, RopType.WEBGL);
} else {
this._resolved_renderer_rop = undefined;
}
}
private update_cssRenderer() {
if (this.node.pv.setCssRenderer) {
const param = this.node.p.cssRenderer;
if (param.isDirty()) {
param.find_target();
}
this._resolved_cssRenderer_rop = param.found_node_with_context_and_type(NodeContext.ROP, [
RopType.CSS2D,
RopType.CSS3D,
]);
} else {
if (this._resolved_cssRenderer_rop) {
// TODO: not yet sure how to remove it so that it can be easily added again
// const renderer = this.cssRenderer()
// const dom
// this._resolved_cssRenderer_rop.remove_renderer_element(canvas);
}
this._resolved_cssRenderer_rop = undefined;
}
}
renderer(canvas: HTMLCanvasElement) {
return this._renderers_by_canvas_id[canvas.id];
}
cssRenderer(canvas: HTMLCanvasElement) {
if (this._resolved_cssRenderer_rop && this.node.pv.setCssRenderer) {
return this._resolved_cssRenderer_rop.renderer(canvas);
}
}
private _super_sampling_size = new Vector2();
createRenderer(canvas: HTMLCanvasElement, size: Vector2): WebGLRenderer | undefined {
const gl = Poly.renderersController.renderingContext(canvas);
if (!gl) {
console.error('failed to create webgl context');
return;
}
let renderer: WebGLRendererWithSampling | undefined;
if (this.node.pv.setRenderer) {
this.update_renderer();
if (this._resolved_renderer_rop) {
renderer = this._resolved_renderer_rop.create_renderer(canvas, gl);
}
}
if (!renderer) {
renderer = RenderController._create_default_renderer(canvas, gl);
}
// https://github.com/mrdoob/js/issues/15493
// This below is an attempt to fix env map not being loaded in firefox, but that doesn't work.
// Since the threejs example (https://threejs.org/examples/?q=exr#webgl_materials_envmaps_exr) also only works in chrome, not in firefox, I assume this is a firefox+linux bug
// console.log(renderer.extensions)
// renderer.extensions.get( 'EXT_color_buffer_float' );
// attempt to have particle systems work in firefox on mobile
// (current solution is to have the node SOP/particlesSystemGPU force webgl2 to be used)
// renderer.extensions.get( 'WEBGL_color_buffer_float' );
// renderer.extensions.get( 'WEBGL_draw_buffers' );
Poly.renderersController.registerRenderer(renderer);
this._renderers_by_canvas_id[canvas.id] = renderer;
this._super_sampling_size.copy(size);
if (renderer.sampling) {
this._super_sampling_size.multiplyScalar(renderer.sampling);
}
this.set_renderer_size(canvas, this._super_sampling_size);
// remove devicePixelRatio for now, as this seems to double the size
// of the canvas on high dpi screens
// or if this is used, make sure to have the canvas at 100% width and height
// renderer.setPixelRatio(window.devicePixelRatio);
return renderer;
}
private static _create_default_renderer(canvas: HTMLCanvasElement, gl: WebGLRenderingContext) {
const renderer = new WebGLRenderer({
canvas: canvas,
antialias: true,
alpha: true,
context: gl,
});
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = DEFAULT_SHADOW_MAP_TYPE;
renderer.physicallyCorrectLights = true;
// // TODO: find a way to have those accessible via params
renderer.toneMapping = DEFAULT_TONE_MAPPING;
renderer.toneMappingExposure = 1;
renderer.outputEncoding = DEFAULT_OUTPUT_ENCODING;
return renderer;
}
delete_renderer(canvas: HTMLCanvasElement) {
const renderer = this.renderer(canvas);
if (renderer) {
Poly.renderersController.deregisterRenderer(renderer);
}
}
canvas_resolution(canvas: HTMLCanvasElement) {
return this._resolution_by_canvas_id[canvas.id];
}
set_renderer_size(canvas: HTMLCanvasElement, size: Vector2) {
this._resolution_by_canvas_id[canvas.id] = this._resolution_by_canvas_id[canvas.id] || new Vector2();
this._resolution_by_canvas_id[canvas.id].copy(size);
const renderer = this.renderer(canvas);
if (renderer) {
const update_style = false;
renderer.setSize(size.x, size.y, update_style);
}
if (this._resolved_cssRenderer_rop) {
const cssRenderer = this.cssRenderer(canvas);
if (cssRenderer) {
cssRenderer.setSize(size.x, size.y);
}
}
}
}