# Built-in node
For the convenience of developers and to support built-in pipelines, the current small game framework has provided many built-in nodes.
# Core function node
The first is the basic core functional nodes:
class RGCameraNode extends RGNode<{},'Camera', {camera: BaseCamera}>
: Pass in acamera
and output aCamera
type data, generally as the beginning of a link.class RGCullNode extends RGNode<{camera:'Camera'},'MeshList', {lightMode: string,initSize?: number}>
: To remove the node, aCamera
type input is required, and alightMode
is provided As an initialization parameter, finally output a list of render bodies that meet the requirements of thelightMode
in the camera's frustum.class RGGenRenderTargetNode extends RGNode<{},'RenderTarget', {createRenderTarget(): RenderTexture | Screen;}>
: Render target node, used to create the output of a render targetRenderTarget
, through thecreateRenderTarget
method, It can be really created when it is used, saving memory.class RGClearNode extends RGNode<{camera:'Camera', renderTarget:'RenderTarget'},'None', {}>
: Clear the screen node, enter aCamera
and aRenderTarget
, use the former to target the canvas Clear the screen.class RGGenViewNode extends RGNode<{},'Camera', {viewObject: {view: Kanata.View}}>
: The essence of the screen clearing operation has nothing to do with the camera. Only oneView
is needed to provide the information of the screen clearing. Developers need to clean up the entire canvas at the beginning to use this solution.
With the above nodes and with the life cycle, we can achieve a simple screen clearing:
class MyRenderGraph extends engine.RenderGraph {
public onCamerasChange(cameras: engine.BaseCamera[]) {
const camera = cameras[0];
const cameraNode = this.createNode<engine.RGCameraNode>(`camera`, RGGenViewNode, {camera});
const rtNode = this.createNode<engine.RGGenRenderTargetNode>('rt-screen', RGGenRenderTargetNode, {createRenderTarget: () => camera.renderTarget});
const clearNode = this.createNode<engine.RGClearNode>('clear', RGClearNode, {});
this.connect(cameraNode, clearNode,'camera');
this.connect(rtNode, clearNode,'renderTarget');
}
}
# Render node
The rendering node is a special core function node. Because the developer has a strong customization of rendering, it is abstracted as a node that can inherit customization:
interface IRGRenderNodeOptions<TInputs> {
createUniformBlocks(): Kanata.UniformBlock[];
lightMode: string;
inputTypes?: TInputs;
uniformsMap?: {
[uniformName: string]: {
inputKey: keyof TInputs,
/**
* If the uniform is `RenderTexture`, which buffer to use.
*/
name:'depth' |'stencil'|'color',
}
};
}
class RGRenderNode<TInputs extends {
[key: string]: keyof IRGData;
} = {}> extends RGNode<
TInputs & {camera:'Camera', renderTarget:'RenderTarget', meshList:'MeshList'},
'RenderTarget',
IRGRenderNodeOptions<TInputs>
>
One of the most important is its initialization parameters. createUniformBlocks
allows developers to define a set of global Uniforms themselves (but due to limitations, only one is currently supported!), this and the previous chapter Rendering System Speaking of no contradiction, it can be considered that this is the highest priority. Then there is the lightMode
parameter, see Effects and Materials for details of this sum.
The second is the input parameters. The render node needs at least the camera camera
(usually from the RGCameraNode
node), the rendering target renderTarget
(usually from the RGGenRenderTargetNode
node) and the rendering list meshList
(usually from the RGCullNode
Node) in order to render, and the inputTypes
and uniformsMap
in the initialization parameters provide a cutout so that developers can add their own inputs and bind these inputs to the global Uniform in a convenient way stand up.
In addition to these, if the developer needs more in-depth customization, the render node also provides a life cycle:
public onRender(context: RenderSystem, options: IRGRenderNodeOptions<TInputs>);
In most cases, developers do not need to customize this life cycle. If you need to use this life cycle, you need to refer to Deep Pipeline.
However, it should be noted that for shadow drawing, a node RGLightNode
is specially provided, and its parameters are exactly the same as RGRenderNode
, but for drawing shadows, its output is generally used as the additional input of the rendering node when in use:
const fwRenderNode = this.createNode<RGRenderNode<{shadowMap:'RenderTarget'}>>('fb-render', RGRenderNode, {
lightMode:'ForwardBase',
createUniformBlocks: () => [this._fbUniforms!],
// Add input for shadow map
inputTypes: {'shadowMap':'RenderTarget'},
// The mapping of the input of the shadow map to the global Uniform
uniformsMap: {
u_shadowMapTex: {
inputKey:'shadowMap',
name:'color'
}
}
});
if (camera.shadowMode !== Kanata.EShadowMode.None) {
const scRenderNode = this.createNode<engine.RGLightNode>('shadow-caster', RGLightNode, {
lightMode:'ShadowCaster',
createUniformBlocks: () => [this._scUniforms!]
});
this.connect(scRenderNode, fwRenderNode,'shadowMap');
}
# Sky box node
The sky box node class RGSkyBoxNode extends RGNode<{},'MeshList', {}>
is very simple, and it is often aimed at the drawSkybox
parameter on the camera
. Generally, when using it, if the camera is used as the source to build a RenderGraph Time:
if (camera.drawSkybox) {
const skyboxNode = this.createNode('skybox', engine.RGSkyboxNode, {});
this.connect(skyboxNode, renderNode,'meshList');
}
That is, it is directly connected to the render node and can be used as the input of the render list of the render node.
# UI Node
# Light node
Shadows and multiple light sources are very common lighting effects. In the framework of the mini game, shadows are realized in the form of ShadowMap, and the drawing of multiple light sources is completed through multiple passes.
# Shadow node
To connect the shadow node in the pipeline, you need to go through three steps:
- Build the RenderTexture render target node
RGGenRenderTargetNode
of the shadow map - Build the render node
RGLightNode
of the shadow map - Pass the shadow map as a parameter to the render node of the scene
The complete pipeline code is as follows:
if (camera.shadowMode !== Kanata.EShadowMode.None) {
// Construct the RenderTexture of the shadow map
const scRTNode = this.createNode<RGGenRenderTargetNode>('sc-render-target', RGGenRenderTargetNode, {createRenderTarget: () => this._createRT(camera,'ShadowCaster')});
// Build the shaded render node
const scRenderNode = this.createNode<RGLightNode>('shadow-caster', RGLightNode, {
lightMode:'ShadowCaster',
createUniformBlocks: () => [this._scUniforms!]
});
// Connect the camera node, the rendering list and the shadow node, and connect the shadow node to the scene rendering node
this.connect(cameraNode, scRenderNode,'camera');
this.connect(scRTNode, scRenderNode,'renderTarget');
// fwCullNode outputs the rendering list of scene objects
this.connect(fwCullNode, scRenderNode,'meshList');
// Pass the shadow map as a parameter to the render node of the scene
this.connect(scRenderNode, fwRenderNode,'shadowMap');
}
In this way, we can start rendering the shadow
# Multi-light source node
To connect a node for multi-light source rendering in the pipeline, four steps are required:
- Construct a light source clipping node
RGCullLightFANode
, and light sources that have no effect on objects within the visual threshold will be eliminated