mirror of
https://github.com/visgl/luma.gl.git
synced 2026-01-18 14:03:42 +00:00
269 lines
8.5 KiB
Markdown
269 lines
8.5 KiB
Markdown
# How Rendering Works
|
|
|
|
:::info
|
|
Applications will typically used the `Model` class in `@luma.gl/engine` module to issue draw calls.
|
|
While the `Model` class handles some of the necessary setup, it is still useful to understand how
|
|
rendering is done with the underlying `Renderpipeline`
|
|
:::
|
|
|
|
A major feature of any GPU API is the ability to issue GPU draw calls. luma.gl has been designed to offer developers full control over draw calls as outlined below.
|
|
|
|
## Tutorials
|
|
|
|
The luma.gl documentation includes a series of tutorials that show how to render with the luma.gl API.
|
|
|
|
## Drawing
|
|
|
|
### Creating a RenderPipeline
|
|
|
|
```typescript
|
|
const pipeline = device.createRenderPipeline({
|
|
id: 'my-pipeline',
|
|
vs: vertexShaderSourceString,
|
|
fs: fragmentShaderSourceString
|
|
});
|
|
```
|
|
|
|
Set or update uniforms, in this case world and projection matrices
|
|
|
|
```typescript
|
|
pipeline.setUniforms({
|
|
uMVMatrix: view,
|
|
uPMatrix: projection
|
|
});
|
|
```
|
|
|
|
Create a `VertexArray` to store buffer values for the vertices of a triangle and drawing
|
|
|
|
```typescript
|
|
const pipeline = device.createRenderPipeline({vs, fs});
|
|
|
|
pipeline.draw({vertexArray, ...});
|
|
```
|
|
|
|
Creating a pipeline for transform feedback, specifying which varyings to use
|
|
|
|
```typescript
|
|
const pipeline = device.createRenderPipeline({vs, fs, varyings: ['gl_Position']});
|
|
```
|
|
|
|
|
|
```
|
|
|
|
Set or update uniforms, in this case world and projection matrices
|
|
|
|
```typescript
|
|
pipeline.setUniforms({
|
|
uMVMatrix: view,
|
|
uPMatrix: projection
|
|
});
|
|
```
|
|
|
|
Create a `VertexArray` to store buffer values for the vertices of a triangle and drawing
|
|
|
|
```typescript
|
|
const pipeline = device.createRenderPipeline({vs, fs});
|
|
|
|
const vertexArray = new VertexArray(gl, {pipeline});
|
|
|
|
vertexArray.setAttributes({
|
|
aVertexPosition: new Buffer(gl, {data: new Float32Array([0, 1, 0, -1, -1, 0, 1, -1, 0])})
|
|
});
|
|
|
|
pipeline.draw({vertexArray, ...});
|
|
```
|
|
|
|
Creating a pipeline for transform feedback, specifying which varyings to use
|
|
|
|
```typescript
|
|
const pipeline = device.createRenderPipeline({vs, fs, varyings: ['gl_Position']});
|
|
```
|
|
|
|
## Rendering into a canvas
|
|
|
|
To render to the screen requires rendering into a canvas, a special `Framebuffer` should be obtained from a
|
|
`CanvasContext` using `canvasContext.getDefaultFramebuffer()`.
|
|
A device context `Framebuffer` and has a (single) special color attachment that is connected to the
|
|
current swap chain buffer, and also a depth buffer, and is automatically resized to match the size of the canvas
|
|
associated.
|
|
|
|
To draw to the screen in luma.gl, simply create a `RenderPass` by calling
|
|
`device.beginRenderPass()` and start rendering. When done rendering, call
|
|
`renderPass.end()`
|
|
|
|
```typescript
|
|
// A renderpass without parameters uses the default framebuffer of the device's default CanvasContext
|
|
const renderPass = device.beginRenderPass();
|
|
model.draw();
|
|
renderPass.end();
|
|
device.submit();
|
|
```
|
|
|
|
For more detail. `device.canvasContext.getDefaultFramebuffer()` returns a special framebuffer that lets you render to screen (into the device's swap chain textures). This framebuffer is used by default when a `device.beginRenderPass()` is called without providing a `framebuffer`:
|
|
|
|
```typescript
|
|
const renderPass = device.beginRenderPass({framebuffer: device.canvasContext.getDefaultFramebuffer()});
|
|
...
|
|
```
|
|
|
|
## Clearing the screen
|
|
|
|
`Framebuffer` attachments are cleared by default when a RenderPass starts. Control is provided via the `RenderPassProps.clearColor` parameter, setting this will clear the attachments to the corresponding color. The default clear color is a fully transparent black `[0, 0, 0, 0]`.
|
|
|
|
```typescript
|
|
const renderPass = device.beginRenderPass({clearColor: [0, 0, 0, 1]});
|
|
model.draw();
|
|
renderPass.end();
|
|
device.submit();
|
|
```
|
|
|
|
Depth and stencil buffers are also cleared to default values:
|
|
|
|
```typescript
|
|
const renderPass = device.beginRenderPass({
|
|
clearColor: [0, 0, 0, 1],
|
|
depthClearValue: 1,
|
|
stencilClearValue: 0
|
|
});
|
|
renderPass.end();
|
|
device.submit();
|
|
```
|
|
|
|
Clearing can be disabled by setting any of the clear properties to the string constant `'load'`. Instead of clearing before rendering, this loads the previous contents of the framebuffer. Clearing should generally be expected to be more performant.
|
|
|
|
## Offscreen rendering
|
|
|
|
While is possible to render into an `OffscreenCanvas`, offscreen rendering usually refers to
|
|
rendering into one or more application created `Texture`s.
|
|
|
|
To help organize and resize these textures, luma.gl provides a `Framebuffer` class.
|
|
A `Framebuffer` is a simple container object that holds textures that will be used as render targets for a `RenderPass`, containing
|
|
- one or more color attachments
|
|
- optionally, a depth, stencil or depth-stencil attachment
|
|
|
|
`Framebuffer` also provides a `resize` method makes it easy to efficiently resize all the attachments of a `Framebuffer` with a single method call.
|
|
|
|
`device.createFramebuffer` constructor enables the creation of a framebuffer with all attachments in a single step.
|
|
|
|
When no attachments are provided during `Framebuffer` object creation, new resources are created and used as default attachments for enabled targets (color and depth).
|
|
|
|
For color, new `Texture2D` object is created with no mipmaps and following filtering parameters are set.
|
|
|
|
| Texture parameter | Value |
|
|
| ----------------- | --------------- |
|
|
| `minFilter` | `linear` |
|
|
| `magFilter` | `linear` |
|
|
| `addressModeU` | `clamp-to-edge` |
|
|
| `addressModeV` | `clamp-to-edge` |
|
|
|
|
|
|
An application can render into an (HTML or offscreen) canvas by obtaining a
|
|
`Framebuffer` object from a `CanvasContext` using `canvasContext.getDefaultFramebuffer()`.
|
|
|
|
Alternatively an application can create custom framebuffers for rendering directly into textures.
|
|
|
|
The application uses a `Framebuffer` by providing it as a parameter to `device.beginRenderPass()`.
|
|
All operations on that `RenderPass` instance will render into that framebuffer.
|
|
|
|
A `Framebuffer` is shallowly immutable (the list of attachments cannot be changed after creation),
|
|
however a Framebuffer can be "resized".
|
|
|
|
## Framebuffer Attachments
|
|
|
|
A `Framebuffer` holds:
|
|
|
|
- an array of "color attachments" (often just one) that store data (one or more color `Texture`s)
|
|
- an optional depth, stencil or combined depth-stencil `Texture`).
|
|
|
|
All attachments must be in the form of `Texture`s.
|
|
|
|
## Resizing Framebuffers
|
|
|
|
Resizing a framebuffer effectively destroys all current textures and creates new
|
|
textures with otherwise similar properties. All data stored in the previous textures are lost.
|
|
This data loss is usually a non-issue as resizes are usually performed between render passes,
|
|
(typically to match the size of an off screen render buffer with the new size of the output canvas).
|
|
|
|
A default Framebuffer should not be manually resized.
|
|
|
|
```typescript
|
|
const framebuffer = device.createFramebuffer({
|
|
width: window.innerWidth,
|
|
height: window.innerHeight,
|
|
color: 'true',
|
|
depthStencil: true
|
|
});
|
|
```
|
|
|
|
Attaching textures and renderbuffers
|
|
|
|
```typescript
|
|
device.createFramebuffer({
|
|
depthStencil: device.createRenderbuffer({...}),
|
|
color0: device.createTexture({...})
|
|
});
|
|
framebuffer.checkStatus(); // optional
|
|
```
|
|
|
|
Resizing a framebuffer to the size of a window. Resizes (and possibly clears) all attachments.
|
|
|
|
```typescript
|
|
framebuffer.resize(window.innerWidth, window.innerHeight);
|
|
```
|
|
|
|
Specifying a framebuffer for rendering in each render calls
|
|
|
|
```typescript
|
|
const offScreenBuffer = device.createFramebuffer(...);
|
|
const offScreenRenderPass = device.beginRenderPass({framebuffer: offScreenFramebuffer});
|
|
model1.draw({
|
|
framebuffer: offScreenBuffer,
|
|
parameters: {}
|
|
});
|
|
model2.draw({
|
|
framebuffer: null, // the default drawing buffer
|
|
parameters: {}
|
|
});
|
|
```
|
|
|
|
|
|
Clearing a framebuffer
|
|
|
|
```typescript
|
|
framebuffer.clear();
|
|
framebuffer.clear({color: [0, 0, 0, 0], depth: 1, stencil: 0});
|
|
```
|
|
|
|
Binding a framebuffer for multiple render calls
|
|
|
|
```typescript
|
|
const framebuffer1 = device.createFramebuffer({...});
|
|
const framebuffer2 = device.createFramebuffer({...});
|
|
|
|
const renderPass1 = device.beginRenderPass({framebuffer: framebuffer1});
|
|
program.draw(renderPass1);
|
|
renderPass1.endPass();
|
|
|
|
const renderPass2 = device.beginRenderPass({framebuffer: framebuffer1});
|
|
program.draw(renderPass2);
|
|
renderPass2.endPass();
|
|
```
|
|
|
|
### Using Multiple Render Targets
|
|
|
|
The colorAttachments can be referenced in the shaders
|
|
|
|
Writing to multiple framebuffer attachments in GLSL fragment shader
|
|
|
|
```
|
|
#extension GL_EXT_draw_buffers : require
|
|
precision highp float;
|
|
void main(void) {
|
|
gl_FragData[0] = vec4(0.25);
|
|
gl_FragData[1] = vec4(0.5);
|
|
gl_FragData[2] = vec4(0.75);
|
|
gl_FragData[3] = vec4(1.0);
|
|
}
|
|
```
|
|
|