Skip to content

Nismit/chottogl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

86 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

chottoGL

A minimal but powerful WebGL2 framework designed for creative coding, visualizations, and rapid WebGL prototyping.

Features

  • Lightweight and optimized WebGL2 wrapper for modern graphics programming
  • Simplified shader creation with automatic uniform parsing and type detection
  • Transform Feedback support for GPU-based simulations and particle systems
  • Framebuffer abstraction with mipmap support for post-processing effects
  • Multiple Render Targets (MRT) for deferred rendering and G-Buffers
  • Uniform Buffer Objects (UBO) for efficient uniform management
  • Particle system capabilities with ping-pong buffer optimization
  • Audio synthesis with GPU acceleration
  • Automatic texture unit management

Installation

Include the module in your project:

<script type="module">
  import { chottoGL } from './esChottoGL.js';
  // Your code here
</script>

Quick Start

import { chottoGL } from './esChottoGL.js';

// Initialize canvas
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

// Initialize chottoGL
const chotto = chottoGL(canvas);

// Create a shader with custom fragment shader
// Boilerplate (#version, precision, vTexCoord, fragColor) is auto-inserted
const shader = chotto.createShader({
  fragment: `
    uniform float uTime;

    void main() {
      vec2 uv = vTexCoord * 2.0 - 1.0;
      float radius = length(uv);
      float angle = atan(uv.y, uv.x);

      float r = 0.5 + 0.5 * sin(angle * 3.0 + uTime);
      float g = 0.5 + 0.5 * sin(radius * 5.0 - uTime * 0.5);
      float b = 0.5 + 0.5 * sin(radius * 10.0 + angle + uTime * 2.0);

      fragColor = vec4(r, g, b, 1.0);
    }`,
});

// Animation loop
let time = 0;
function render() {
  time += 0.01;
  chotto.clear(0.1, 0.1, 0.1, 1.0);
  shader.use().setUniform('uTime', time).draw();
  requestAnimationFrame(render);
}

render();

Core API

Initialization

const chotto = chottoGL(canvas, options);
  • canvas: HTMLCanvasElement to render to
  • options: (Optional) WebGL context options

Shader Creation

const shader = chotto.createShader({
  vertex: '...', // Optional, uses default if omitted
  fragment: '...', // Custom fragment shader
  transformFeedbackVaryings: [...], // Optional for transform feedback
  transformFeedbackMode: chotto.gl.SEPARATE_ATTRIBS, // Optional mode
  raw: false // Set to true to disable auto-insertion of boilerplate
});

Shader Boilerplate Auto-Insertion:

Fragment shaders automatically get the following boilerplate prepended:

#version 300 es
precision highp float;
in vec2 vTexCoord;
out vec4 fragColor;

This means you can write minimal shaders like:

const shader = chotto.createShader({
  fragment: `
    uniform float uTime;
    void main() {
      fragColor = vec4(vTexCoord, sin(uTime), 1.0);
    }`
});

To disable auto-insertion:

  • Use raw: true option
  • Or start your shader with #version for full control

Shader Usage

shader.use()
  .setUniform('uniformName', value)  // Supports float, int, bool, vectors, matrices
  .setUniform({ uniform1: value1, uniform2: value2 })  // Batch setting
  .setTexture('textureName', textureObject)  // Automatic texture unit management
  .draw();                                   // Draw fullscreen quad

// Custom geometry (particles, meshes, etc.)
shader.draw(vao, gl.POINTS, count);          // Draw with custom VAO, mode, and count

Uniform Type Support:

  • Scalars: float, int, bool
  • Vectors: vec2, vec3, vec4, ivec2, ivec3, ivec4
  • Matrices: mat2, mat3, mat4
  • Samplers: sampler2D, sampler3D, samplerCube, sampler2DArray
  • Arrays: All types support array notation
  • Flexible input: Accepts scalars, arrays, or typed arrays
  • Boolean conversion: true1, false0

Framebuffer Creation

const fbo = chotto.createFramebuffer(width, height, data, options);

// Options include mipmap support
const fboWithMipmap = chotto.createFramebuffer(width, height, null, {
  mipmap: true,
  minFilter: chotto.gl.LINEAR_MIPMAP_LINEAR
});

// Using the framebuffer
fbo.bind().clear(0, 0, 0, 1);
// Draw operations...
fbo.unbind();

// Update texture data
fbo.updateTexture(xOffset, yOffset, width, height, data, updateMipmap);

// Resize framebuffer
fbo.resize(newWidth, newHeight);

// Generate mipmaps
fbo.updateMipmap();

Multiple Render Targets (MRT)

For deferred rendering, G-Buffers, or any effect requiring multiple outputs:

// Numeric: N targets with same format
const gbuffer = chotto.createFramebuffer(width, height, null, {
  targets: 3,
  depth: true
});

// Array: per-target format configuration
const gbuffer = chotto.createFramebuffer(width, height, null, {
  targets: [
    { internalFormat: gl.RGBA, format: gl.RGBA, type: gl.UNSIGNED_BYTE },
    { internalFormat: gl.RGBA16F, format: gl.RGBA, type: gl.FLOAT },
    { internalFormat: gl.RGBA16F, format: gl.RGBA, type: gl.FLOAT }
  ]
});

// Access textures
gbuffer.textures[0]  // First target
gbuffer.textures[1]  // Second target
gbuffer.texture      // Alias for textures[0]
gbuffer.targetCount  // Number of targets

Fragment shader for MRT (start with #version 300 es to skip auto-insertion):

#version 300 es
precision highp float;
in vec2 vTexCoord;

layout(location = 0) out vec4 gAlbedo;
layout(location = 1) out vec4 gNormal;
layout(location = 2) out vec4 gPosition;

void main() {
  gAlbedo = vec4(1.0, 0.0, 0.0, 1.0);
  gNormal = vec4(0.0, 1.0, 0.0, 1.0);
  gPosition = vec4(vTexCoord, 0.0, 1.0);
}

Important: Always call fbo.unbind() before sampling from MRT textures to avoid feedback loops.

Transform Feedback

// Particle buffer with ping-pong for GPU particle simulation
const particles = chotto.createParticleBuffer([positions, velocities]);

// Process in update loop (auto-swaps buffers)
particles.process(updateShader, () => {
  updateShader.setUniform('uTime', time);
});

// Render particles
particles.draw(renderShader, { uSize: 5.0 });

// Audio buffer for GPU audio synthesis
const audio = chotto.createAudioBuffer(2048, 2); // size, channels

audio.process(audioShader, () => {
  audioShader.setUniform('u_sampleRate', 44100);
});

// Read back audio data
audio.readBack(outputBuffer);

Usage Examples

Basic Shader Example

Create a simple animated shader:

import { chottoGL } from './esChottoGL.js';

const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

const chotto = chottoGL(canvas);

const shader = chotto.createShader({
  fragment: `
    uniform float uTime;

    void main() {
      vec2 uv = vTexCoord * 2.0 - 1.0;
      float radius = length(uv);
      float angle = atan(uv.y, uv.x);

      float r = 0.5 + 0.5 * sin(angle * 3.0 + uTime);
      float g = 0.5 + 0.5 * sin(radius * 5.0 - uTime * 0.5);
      float b = 0.5 + 0.5 * sin(radius * 10.0 + angle + uTime * 2.0);

      fragColor = vec4(r, g, b, 1.0);
    }`,
});

let time = 0;
function render() {
  time += 0.01;
  chotto.clear(0.1, 0.1, 0.1, 1.0);
  shader.use().setUniform('uTime', time).draw();
  requestAnimationFrame(render);
}

render();

Post-Processing with Framebuffers

Create a scene and apply post-processing effects:

import { chottoGL } from './esChottoGL.js';

const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

const chotto = chottoGL(canvas);

// Scene rendering shader
const sceneShader = chotto.createShader({
  fragment: `
    uniform float uTime;
    uniform vec2 resolution;

    void main() {
      vec2 uv = vTexCoord * 2.0 - 1.0;
      uv.x *= resolution.x / resolution.y;

      vec3 color = vec3(0.5 + 0.5 * sin(uv.x * 10.0 + uTime),
                        0.5 + 0.5 * sin(uv.y * 10.0 + uTime * 0.7),
                        0.5 + 0.5 * sin((uv.x + uv.y) * 5.0 + uTime * 1.3));

      fragColor = vec4(color, 1.0);
    }`
});

// Post-processing shader
const postShader = chotto.createShader({
  fragment: `
    uniform sampler2D uTexture;
    uniform float uTime;

    void main() {
      vec2 uv = vTexCoord;
      float distortion = sin(uv.y * 40.0 + uTime * 2.0) * 0.003;
      vec2 distortedUV = vec2(uv.x + distortion, uv.y);

      vec3 color = texture(uTexture, distortedUV).rgb;

      float vignette = 1.0 - smoothstep(0.5, 0.8, length(uv - 0.5));
      color *= vignette;

      fragColor = vec4(color, 1.0);
    }`
});

// Create framebuffer for scene rendering
const fbo = chotto.createFramebuffer(canvas.width, canvas.height);

let time = 0;
function render() {
  time += 0.01;
  
  // Render scene to framebuffer
  fbo.bind().clear(0.0, 0.0, 0.0, 1.0);
  sceneShader.use()
    .setUniform('uTime', time)
    .setUniform('resolution', [canvas.width, canvas.height])
    .draw();
  
  // Render framebuffer to screen with post-processing
  fbo.unbind();
  chotto.clear(0.0, 0.0, 0.0, 1.0);
  postShader.use()
    .setUniform('uTime', time)
    .setTexture('uTexture', fbo.texture)
    .draw();
  
  requestAnimationFrame(render);
}

render();

Particle System with Transform Feedback

Create a GPU-based particle system using Transform Feedback:

import { chottoGL } from './esChottoGL.js';

const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

const chotto = chottoGL(canvas);
const PARTICLE_COUNT = 10000;

// Generate initial particle data
function generateParticles() {
  const positions = new Float32Array(PARTICLE_COUNT * 4);
  const velocities = new Float32Array(PARTICLE_COUNT * 4);
  
  for (let i = 0; i < PARTICLE_COUNT; i++) {
    const i4 = i * 4;
    
    // Position: random circular distribution
    const angle = Math.random() * Math.PI * 2;
    const radius = 0.1 + Math.random() * 0.3;
    positions[i4 + 0] = Math.cos(angle) * radius;
    positions[i4 + 1] = Math.sin(angle) * radius;
    positions[i4 + 2] = 0.0;
    positions[i4 + 3] = Math.random(); // Lifetime
    
    // Velocity: slightly toward center
    const vAngle = angle + Math.PI + (Math.random() - 0.5) * 1.0;
    const vMag = 0.001 + Math.random() * 0.002;
    velocities[i4 + 0] = Math.cos(vAngle) * vMag;
    velocities[i4 + 1] = Math.sin(vAngle) * vMag;
    velocities[i4 + 2] = 0.0;
    velocities[i4 + 3] = 0.003 + Math.random() * 0.008; // Decay rate
  }
  
  return { positions, velocities };
}

const particles = generateParticles();

// Update shader for particle simulation
const updateShader = chotto.createShader({
  vertex: `#version 300 es
    precision highp float;
    
    // Input attributes (current state)
    in vec4 aPosition; // (x, y, z, lifetime)
    in vec4 aVelocity; // (vx, vy, vz, decay)
    
    // Output varying (captured by Transform Feedback)
    out vec4 vPosition;
    out vec4 vVelocity;
    
    // Simulation parameters
    uniform float uTime;
    
    // Simulation logic...
    void main() {
      // Current position and velocity
      vec3 position = aPosition.xyz;
      float life = aPosition.w;
      vec3 velocity = aVelocity.xyz;
      float decay = aVelocity.w;
      
      // Update position
      position += velocity;
      
      // Update lifetime
      life -= decay;
      
      // Reset particle if lifetime expired
      if (life <= 0.0) {
        float angle = fract(sin(float(gl_VertexID) * 78.233) * 43758.5453 + uTime) * 6.283;
        float radius = 0.1 + fract(cos(float(gl_VertexID) * 10.873) * 13758.5453) * 0.3;
        
        position.x = cos(angle) * radius;
        position.y = sin(angle) * radius;
        position.z = 0.0;
        
        // Reset lifetime
        life = 0.8 + fract(sin(float(gl_VertexID) * 32.373) * 13758.5453) * 0.4;
      }
      
      // Output for Transform Feedback
      vPosition = vec4(position, life);
      vVelocity = vec4(velocity, decay);
      
      // Required but not used
      gl_Position = vec4(position, 1.0);
    }
  `,
  fragment: `#version 300 es
    precision highp float;
    out vec4 fragColor;
    void main() { fragColor = vec4(0.0); }
  `,
  transformFeedbackVaryings: ['vPosition', 'vVelocity'],
  transformFeedbackMode: chotto.gl.SEPARATE_ATTRIBS
});

// Render shader for particle visualization
const renderShader = chotto.createShader({
  vertex: `#version 300 es
    precision highp float;
    
    in vec4 aPosition;
    out float vLife;
    
    uniform mat4 uProjection;
    uniform float uParticleSize;
    
    void main() {
      vLife = aPosition.w; // Pass lifetime to fragment shader
      gl_Position = uProjection * vec4(aPosition.xyz, 1.0);
      gl_PointSize = uParticleSize * vLife;
    }
  `,
  fragment: `#version 300 es
    precision highp float;
    
    in float vLife;
    out vec4 fragColor;
    
    uniform vec3 uParticleColor;
    
    void main() {
      // Create circular particles
      vec2 coord = gl_PointCoord - vec2(0.5);
      float d = length(coord);
      float alpha = smoothstep(0.5, 0.35, d);
      if (alpha < 0.01) discard;
      
      vec3 color = uParticleColor;
      fragColor = vec4(color, 1.0);
    }
  `
});

// Create particle buffer for ping-pong simulation
const tfPair = chotto.createParticleBuffer([particles.positions, particles.velocities]);

// Create projection matrix
function createProjectionMatrix() {
  const aspect = canvas.width / canvas.height;
  const scaleX = 1.0 / aspect;
  const scaleY = 1.0;
  
  return new Float32Array([
    scaleX, 0, 0, 0,
    0, scaleY, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1
  ]);
}

// Animation settings
let time = 0;
const particleSize = 5.0;
const particleColor = { r: 1.0, g: 1.0, b: 1.0 };

// Render loop
function render(timestamp) {
  time = timestamp * 0.001;
  
  chotto.clear(0.0, 0.0, 0.0, 1.0);
  
  // Update particles with transform feedback
  tfPair.process(updateShader, () => {
    updateShader.setUniform('uTime', time);
    // Set other uniforms as needed
  });
  
  // Render updated particles
  tfPair.draw(renderShader, {
    uProjection: createProjectionMatrix(),
    uParticleSize: particleSize,
    uParticleColor: [particleColor.r, particleColor.g, particleColor.b]
  });
  
  requestAnimationFrame(render);
}

requestAnimationFrame(render);

GPU-Accelerated Audio Synthesis

Generate audio in real-time using WebGL:

import { chottoGL } from './esChottoGL.js';

const glCanvas = document.getElementById('glCanvas');
const chotto = chottoGL(glCanvas);

// Constants
const SAMPLE_RATE = 44100;
const BUFFER_SIZE = 2048;

// Create audio shader
const soundShader = chotto.createShader({
  vertex: `#version 300 es
    // Uniforms
    uniform float u_sampleRate;
    uniform float u_blockOffset;
    uniform float u_bpm;
    
    // Outputs (transform feedback varyings)
    out vec2 o_sound;
    
    // Utility functions
    float sine(float phase) {
      return sin(phase * 6.28318530718);
    }
    
    float saw(float phase) {
      return 2.0 * fract(phase) - 1.0;
    }
    
    float square(float phase) {
      return fract(phase) < 0.5 ? 1.0 : -1.0;
    }
    
    // Sound generating functions
    float kick(float time) {
      float amp = exp(-5.0 * time);
      float phase = 50.0 * time - 10.0 * exp(-70.0 * time);
      return amp * sine(phase);
    }
    
    float hihat(float time) {
      return exp(-30.0 * time) * (2.0 * fract(747.0 * time) - 1.0);
    }
    
    // Main sound generator
    vec2 mainSound(float time) {
      float beat = time / 60.0 * u_bpm;
      float kickTime = mod(beat, 1.0) / (u_bpm / 60.0);
      float hihatTime = mod(beat, 0.25) / (u_bpm / 60.0);
      
      vec2 out = vec2(0.0);
      out += vec2(kick(kickTime));
      out += vec2(0.7, 1.3) * vec2(hihat(hihatTime) * 0.5);
      
      return out;
    }
    
    void main() {
      float time = u_blockOffset + float(gl_VertexID) / u_sampleRate;
      o_sound = mainSound(time);
    }
  `,
  fragment: `#version 300 es
    precision highp float;
    out vec4 fragColor;
    void main() { fragColor = vec4(1.0); }
  `,
  transformFeedbackVaryings: ['o_sound'],
  transformFeedbackMode: chotto.gl.SEPARATE_ATTRIBS
});

// Create audio buffer for GPU synthesis
const audioTF = chotto.createAudioBuffer(BUFFER_SIZE, 2);

// Setup Web Audio API
let audioContext, gainNode, analyser;
const audioBuffer = new Float32Array(BUFFER_SIZE * 2);

async function setupAudio() {
  audioContext = new (window.AudioContext || window.webkitAudioContext)();
  gainNode = audioContext.createGain();
  analyser = audioContext.createAnalyser();
  
  analyser.connect(gainNode);
  gainNode.connect(audioContext.destination);
  
  // Create audio worklet
  await audioContext.audioWorklet.addModule('path-to-audio-processor.js');
  const workletNode = new AudioWorkletNode(audioContext, 'glsl-sound-processor');
  
  workletNode.port.onmessage = (event) => {
    if (event.data.type === 'needBuffer') {
      generateAudioBuffer(currentBlockOffset);
      currentBlockOffset += BUFFER_SIZE / SAMPLE_RATE;
      
      workletNode.port.postMessage({
        type: 'buffer',
        buffer: audioBuffer.slice()
      }, [audioBuffer.slice().buffer]);
    }
  };
  
  workletNode.connect(analyser);
}

// Generate audio data using WebGL
function generateAudioBuffer(blockOffset) {
  audioTF.process(soundShader, () => {
    soundShader.use()
      .setUniform('u_sampleRate', SAMPLE_RATE)
      .setUniform('u_blockOffset', blockOffset)
      .setUniform('u_bpm', 120);
  });
  
  audioTF.readBack(audioBuffer);
}

// UI and startup code...

Advanced Features

Uniform Buffer Objects (UBO)

UBOs provide efficient uniform management for complex shaders with many uniforms:

const ubo = chotto.createUniformBuffer('LightBlock', 0, {
  'lightPosition': { type: 'vec3', value: [0, 5, 10] },
  'lightColor': { type: 'vec3', value: [1, 0.9, 0.8] },
  'lightIntensity': { type: 'float', value: 1.0 }
});

// Link to shader
ubo.linkToShader(shader.program);

// Update values (more efficient than individual setUniform calls)
ubo.update({
  'lightPosition': { value: [10, 5, 3] },
  'lightIntensity': { value: 0.8 }
});

Supported UBO types: float, vec2, vec3, vec4

Automatic memory layout: Handles std140 alignment and padding automatically

Texture Loading

const texture = chotto.loadTexture('path/to/image.jpg', {
  minFilter: chotto.gl.LINEAR_MIPMAP_LINEAR,
  magFilter: chotto.gl.LINEAR,
  wrapS: chotto.gl.REPEAT,
  wrapT: chotto.gl.REPEAT,
  generateMipmap: true  // Default is true
});

// Disable mipmap generation
const textureNoMipmap = chotto.loadTexture('image.jpg', {
  generateMipmap: false,
  minFilter: chotto.gl.LINEAR
});

Texture options:

  • internalFormat: Default chotto.gl.RGBA
  • format: Default chotto.gl.RGBA
  • type: Default chotto.gl.UNSIGNED_BYTE
  • minFilter: Default chotto.gl.LINEAR
  • magFilter: Default chotto.gl.LINEAR
  • wrapS: Default chotto.gl.CLAMP_TO_EDGE
  • wrapT: Default chotto.gl.CLAMP_TO_EDGE
  • generateMipmap: Default true

Browser Compatibility

chottoGL requires browsers with WebGL2 support, which includes:

  • Chrome 56+
  • Firefox 51+
  • Safari 14.1+
  • Edge 79+

License

MIT License

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors