As easy as three.js. You can regard this project as a better three.js, maybe with better design and better code quality. Many code referenced from three.js :)
Instead of making a specific renderer for specific usage, or a general renderer that hard to extent features, artgl is a framework for general usage. You can easily customize it, extent it to meet you requirements.
Build your post-process pipeline use rendergraph api, we will handle everything about render procedure. Auto support FBO reuse. Make multi pass rendering, add custom optimizer, tweaking effects, debug performance, more delightful than before.
We also use graph as the shader fragment linker. Its a revolutionary improvement of composability in shader source and shader computation abstraction. No more confusing #define #include. Make shader effect development productive and provide a sound abstraction in artgl shading model. You can write shader effect in component style, and compose them freely, publish them, organize your code better than before.
When WebGL2 supports, it would be used, or we will auto downgrade to 1 (try our best). When VAO or UBO support, we auto support and auto downgrade gracefully.
Performance matters.
....
source: https://github.com/mikialex/artgl
This repo also contains sub projects: example/ and viewer/
Artgl is composed by several modules, and still in progress of modularization. Some modules is supposed to be used in other project.
https://mikialex.github.io/2019/08/11/fbo-reuse-in-rendergraph-artgl/
https://mikialex.github.io/2019/08/03/uniform-upload-design/
https://mikialex.github.io/2019/07/16/graph-based-shadersource-management/
https://mikialex.github.io/2019/03/12/artgl-about/
https://mikialex.github.io/2019/04/30/wasm-scene/
Some old post maybe not meet the current design, just for reference;
Its a typical shader effect component:
@ShadingComponent()
export class PhongShading<T> extends BaseEffectShading<PhongShading<T>> {
constructor(lights: Array<Light<T>>) {
super();
this.lights = lights
}
decorate(graph: ShaderGraph): void {
graph.setFragmentRoot(
collectLightNodes<T>(this.lights,
(light) => {
return phongShading.make()
.input("lightDir", light.produceLightFragDir(graph))
.input("lightIntensity", light.produceLightIntensity(graph))
.input("surfaceNormal", graph.getVary(NormalFragVary))
.input("eyeDir", graph.getEyeDir())
.input("shininess", this.getPropertyUniform("shininess"))
})
)
}
foreachProvider(visitor: (p: ShaderUniformProvider) => any) {
visitor(this);
this.lights.forEach(light => {
visitor(light);
})
}
lights: Array<Light<T>>
@Uniform("shininess")
shininess: number = 15;
}
And you can use the things above in shading api:
Decorate shading with any other shading decorator. Provide uniform block with extendable shading provider.
...
const pointLight = new PointLight();
pointLight.position = new Vector3(-1, 3, 3);
pointLight.color = new Vector3(0.9, 0.8, 0.5);
pointLight.radius = 10;
const ambient = new AmbientLight();
ambient.color = new Vector3(0.3, 0.3, 0.4);
const dirLight = new DirectionalLight();
dirLight.color = new Vector3(0.3, 0.6, 0.8);
dirLight.direction = new Vector3(1, 1, -1).normalize();
const exposureController = new ExposureController();
const phong = new PhongShading<DirectionalLight | PointLight>([dirLight, pointLight]);
let shading = new Shading()
.decorate(phong)
.decorate(ambient)
.decorate(exposureController)
const planeMesh = new Mesh().g(planeGeo).s(shading)
root.addChild(planeMesh);
...
const depthPass = pass("depthPass").use(scene.renderScene)
.overrideShading(this.depthShader)
const scenePass = pass("scenePass")
.use(scene.render)
const depthResult = target("depthResult").needDepth().from(depthPass)
const sceneResult = target("sceneResult").needDepth().from(scenePass)
const createTAA = () => {
const taaPass = pass("taa").useQuad()
.overrideShading(this.taaShader)
.disableColorClear()
.beforeExecute(() => {
this.engine.unJit();
const VP: Matrix4 = this.engine.globalUniforms.VPMatrix.value
this.taaShading.VPMatrixInverse = this.taaShading.VPMatrixInverse.getInverse(VP, true);
this.taaShading.sampleCount = this.sampleCount;
})
.input("sceneResult", sceneResult)
.input("depthResult", depthResult)
.input("TAAHistoryOld", this.taaHistory.ping())
return taaPass
}
const AAedScene = when(this._enableTAA, this.taaHistory.pong().from(createTAA()), sceneResult)
const createTSSAO = () => {
const tssaoPass = pass("tssao").useQuad()
.overrideShading(this.tssaoShader)
.disableColorClear()
.beforeExecute(() => {
const VP: Matrix4 = this.engine.globalUniforms.VPMatrix.value
this.tssaoShading.VPMatrixInverse = this.tssaoShading.VPMatrixInverse.getInverse(VP, true);
this.tssaoShading.sampleCount = this.sampleCount;
})
.input("depthResult", depthResult)
.input("AOAcc", this.tssaoHistory.ping())
const tssaoCompose = pass("composeAll").useQuad()
.overrideShading(this.composeShader)
.input("basic", AAedScene)
.input("tssao", this.tssaoHistory.pong().from(tssaoPass))
.beforeExecute(() => {
this.composeShading.sampleCount = this.sampleCount;
})
.disableColorClear()
return tssaoCompose;
}
this.graph.setScreenRoot(
screen().from(
when(
this._enableTSSAO,
createTSSAO(),
pass("copy").useQuad().overrideShading(copier)
.input("copySource", AAedScene))
)
)