Modern WebGL state wrapper for PEX. With pex-context
you allocate GPU resources (textures, buffers), setup state pipelines and passes and combine them together into commands.
const createContext = require('pex-context')
const createCube = require('primitive-cube')
const mat4 = require('pex-math/mat4')
const ctx = createContext({ width: 640, height: 480 })
const cube = createCube()
const clearCmd = {
pass: ctx.pass({
clearColor: [0, 0, 0, 1],
clearDepth: 1
})
}
const drawCmd = {
pass: ctx.pass({
clearColor: [0.2, 0.2, 0.2, 1],
clearDepth: 1
}),
pipeline: ctx.pipeline({
depthTest: true,
vert: `
attribute vec3 aPosition;
attribute vec3 aNormal;
uniform mat4 uProjectionMatrix;
uniform mat4 uViewMatrix;
varying vec3 vNormal;
void main () {
gl_Position = uProjectionMatrix * uViewMatrix * vec4(aPosition, 1.0);
vNormal = aNormal;
}
`,
frag: `
precision mediump float;
varying vec3 vNormal;
void main () {
gl_FragColor.rgb = vNormal;
gl_FragColor.a = 1.0;
}
`
}),
attributes: {
aPosition: ctx.vertexBuffer(cube.positions),
aNormal: ctx.vertexBuffer(cube.normals)
},
indices: ctx.indexBuffer(cube.cells),
uniforms: {
uProjectionMatrix: mat4.perspective(mat4.create(), Math.PI / 4, 640 / 480, 0.1, 100),
uViewMatrix: mat4.lookAt(mat4.create(), [2, 2, 5], [0, 0, 0], [0, 1, 0])
}
}
ctx.frame(() => {
ctx.submit(clearCmd)
ctx.submit(drawCmd)
})
Creating gl context wrapper.
var createContext = require('pex-context')
// full window canvas
var ctx = createContext()
// creates gl context from existing canvas and keeps it's size
var ctx = createContext({ gl: gl })
// creates gl context from existing canvas and keeps it's size
var ctx = createContext({ canvas: canvas })
// creates new canvas with given width and height
var ctx = createContext({ width: Number, height: Number })
Commands are plain javascript objects with GPU resources needed to complete a draw call
var cmd = {
pass: ctx.pass(..),
pipeline: ctx.pipeline(..),
attributes: [..]
}
ctx.submit({
pass: Pass
pipeline: Pipeline,
attributes: {
name: VertexBuffer,
// or
name: { buffer: VertexBuffer, offset: Number, stride: Number }
},
indices: IndexBuffer,
// or
indices: { buffer: IndexBuffer, offset: Number },
// or
count: Number,
instances: Number,
uniforms: {
name: Number,
name: Array,
name: Texture2D
}
})
Submit partially updated command without modifying the original one
// E.g. draw mesh with custom color
ctx.submit(cmd, {
uniforms: {
uColor: [1, 0, 0, 0]
}
})
Submit a batch of commands differences in opts.
// E.g. draw same mesh twice with different material and position
ctx.submit(cmd, [
{ pipeline: material1, uniforms: { uModelMatrix: position1 },
{ pipeline: material2, uniforms: { uModelMatrix: position2 }
])
cb
: Function - Request Animation Frame callackSubmit command while preserving state from another command.
This approach allows to simulate state stack with automatic cleanup at the end of callback.
// E.g. render to texture
ctx.submit(renderToFboCmd, () => {
ctx.submit(drawMeshCmd)
})
Textures represent pixel data uploaded to the GPU.
var tex = ctx.texture2D({
data: [255, 255, 255, 255, 0, 0, 0, 255],
width: 2,
height: 1,
pixelFormat: ctx.PixelFormat.RGB8,
encoding: ctx.Encoding.Linear,
wrap: ctx.Wrap.Repeat
})
property | info | type | default |
---|---|---|---|
data |
pixel data | Array, Uint8Array, Float32Array, HTMLCanvas, HTMLImage, HTMLVideo | null |
width |
texture width | Number/Int | 0 |
height |
texture height | Number/Int | 0 |
pixelFormat |
pixel data format | ctx.PixelFormat | ctx.PixelFormat.RGB8 |
encoding |
pixel data encoding | ctx.Encoding | ctx.Encoding.Linear |
wrapS |
wrapS mode | ctx.Wrap | ctx.Wrap.ClampToEdge |
wrapT |
wrapT mode | ctx.Wrap | ctx.Wrap.ClampToEdge |
wrap |
combines wrapS and wrapT | ctx.Wrap | ctx.Wrap.ClampToEdge |
min |
min filtering mode | ctx.Filter | ctx.Filter.Nearest |
mag |
mag filtering mode | ctx.Filter | ctx.Filter.Nearest |
aniso |
aniso level 1 | Number/Int | 0 |
mipmap |
generate mipmaps on update 2 | Boolean | false |
flipY |
flip pixel data on upload | Boolean | false |
name |
texture name for debugging | String | '' |
target |
texture target 3 | gl enum | gl.TEXTURE_2D or gl.TEXTURE_CUBE |
1 requries EXT_texture_filter_anisotropic
2 requires min
to be set to ctx.Filter.LinearMipmapLinear
or similar
3 read only
opts
: Object - see ctx.texture2D(opts)
var tex = ctx.textureCube([
{ data: new Uint8Array([..]), width: 64, height: 64 },
{ data: new Uint8Array([..]), width: 64, height: 64 },
{ data: new Uint8Array([..]), width: 64, height: 64 },
{ data: new Uint8Array([..]), width: 64, height: 64 },
{ data: new Uint8Array([..]), width: 64, height: 64 },
{ data: new Uint8Array([..]), width: 64, height: 64 }
])
var buf = ctx.vertexBuffer({ data: Array }) // aka Attribute Buffer
var buf = ctx.indexBuffer({ data: Array }) // aka Index Buffer
property | info | type | default |
---|---|---|---|
data |
pixel data | Array, Uint8Array, Float32Array | null |
type |
data type | ctx.DataType | ctx.DataType.Float32 |
usage |
buffer usage | ctx.Usage | ctx.Usage.StaticDraw |
var pipeline = ctx.pipeline({
vert: String,
frag: String,
depthWrite: Boolean, // true
depthTest: Boolean, // false
depthFunc: DepthFunc, // LessEqual
blend: Boolean, // false
blendSrcRGBFactor: BlendFactor,
blendSrcAlphaFactor: BlendFactor,
blendDstRGBFactor: BlendFactor,
blendDstAlphaFactor: BlendFactor,
cullFace: Boolean,
cullFaceMode: Face,
primitive: Primitive
})
var pass = ctx.pass({
color: [Texture2D, ...]
color: [{ texture: Texture2D | TextureCube, target: CubemapFace }, ...]
depth: Texture2D
clearColor: Array,
clearDepth: Number
})
// WebGL 2
// var tex2DArray = ctx.texture2DArray()
// var tex3D = ctx.texture2D()
// var ubo = ctx.uniformBuffer()
ctx.update(res, { data: Array })
const BlendFactor = {
One: gl.ONE,
Zero: gl.ZERO,
SrcAlpha: gl.SRC_ALPHA,
OneMinusSrcAlpha: gl.ONE_MINUS_SRC_ALPHA
}
const CubemapFace = {
PositiveX: gl.TEXTURE_CUBE_MAP_POSITIVE_X,
NegativeX: gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
PositiveY: gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
NegativeY: gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
PositiveZ: gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
NegativeZ: gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
}
const DataType = {
Float32: gl.FLOAT,
Uint8: gl.UNSIGNED_BYTE,
Uint16: gl.UNSIGNED_SHORT,
Uint32: gl.UNSIGNED_INT
}
const DepthFunc = {
Never: gl.NEVER,
Less: gl.LESS,
Equal: gl.EQUAL,
LessEqual: gl.LEQUAL,
Greater: gl.GREATER,
NotEqual: gl.NOTEQUAL,
GreaterEqual: gl.GEQUAL,
Always: gl.ALWAYS
}
const Face = {
Front: gl.FRONT,
Back: gl.BACK,
FrontAndBack: gl.FRONT_AND_BACK
}
const PixelFormat = {
RGBA8: 'rgba8', // gl.RGBA + gl.UNSIGNED_BYTE
RGBA32F: 'rgba32f', // gl.RGBA + gl.FLOAT
RGBA16F: 'rgba16f', // gl.RGBA + gl.HALF_FLOAT
R32F: 'r32f', // gl.ALPHA + gl.FLOAT
R16F: 'r16f', // gl.ALPHA + gl.HALF_FLOAT
Depth: 'depth' // gl.DEPTH_COMPONENT
}
const Primitive = {
Points: gl.POINTS,
Lines: gl.LINES,
LineStrip: gl.LINE_STRIP,
Triangles: gl.TRIANGLES,
TriangleStrip: gl.TRIANGLE_STRIP
}
const Wrap = {
ClampToEdge: gl.CLAMP_TO_EDGE,
Repeat: gl.REPEAT
}
To run e.g. shadow mapping example
cd examples
budo shadows.js --open --live -- -t glslify