协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
of-shader-programming【开发与工程】Implements GLSL shader programming in OpenFrameworks including vertex/fragment shaders, uniform management, VBO rendering, post-processing effects, and shader compilation debugging for GPU-accelerated graphics.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
name: of-shader-programming description: Implements GLSL shader programming in OpenFrameworks including vertex/fragment shaders, uniform management, VBO rendering, post-processing effects, and shader compilation debugging for GPU-accelerated graphics. license: MIT compatibility: opencode metadata: version: "1.0.0" domain: coding triggers: of shader, glsl programming, vertex shader, fragment shader, ofShader, uniform management, post-processing, gpu rendering uniform management archetypes:
Implements GPU-accelerated graphics using GLSL shaders within OpenFrameworks. Covers vertex and fragment shader development, uniform management, VBO rendering, post-processing pipelines, and shader compilation debugging for real-time visual applications.
ofShader::load() specifying vertex .vert and fragment .frag pathssetUniform1f(), setUniformMatrix4f(), setUniformTexture()ofVbo) for batched geometry rendering — never per-vertex CPU-to-GPU uploadsofLogError() if failedglm::perspective() or glm::ortho() before drawing with shadersUse this skill when:
Avoid this skill for:
ofDrawCircle(), ofDrawRectangle() instead (no shader overhead)Create Shader Files — Write separate .vert (vertex) and .frag (fragment) GLSL files in your project's bin/data/shaders/ directory. The vertex shader transforms geometry; the fragment shader determines pixel color. Keep vertex shaders minimal — move computation to fragment shaders when possible for better GPU utilization.
Checkpoint: Both .vert and .frag files must be present at the paths specified in ofShader::load() or OF will silently skip rendering.
Load and Validate Shaders — In setup(), load both shader stages and check for compilation errors immediately. Store shaders as class members with explicit error checking.
// ofApp.h
#pragma once
#include "ofMain.h"
class ofApp : public ofBaseApp {
public:
void setup() override;
void draw() override;
ofShader colorShader;
glm::mat4 projectionMatrix;
};
// ofApp.cpp — load with explicit error checking
void ofApp::setup() {
if (!colorShader.load("shaders/colorShader/vertex.glsl", "shaders/colorShader/fragment.glsl")) {
ofLogError("ofApp") << "Failed to load shader!\n"
<< colorShader.getVertexShaderInfo()
<< colorShader.getFragmentShaderInfo();
}
// Set up projection for 3D rendering
ofSetBackgroundAuto(true);
projectionMatrix = glm::perspective(glm::radians(60.0f),
(float)ofGetWidth() / (float)ofGetHeight(),
0.1f, 100.0f);
}
Checkpoint: Always verify shader.load() returns true — silent failures produce black screens that are hard to debug.
Bind Uniforms and Draw — In draw(), bind the shader, set all required uniforms (time, resolution, textures), upload the projection matrix, render geometry, then unbind. Never modify uniforms between draw calls for different objects without restoring previous values.
void ofApp::draw() {
// Bind shader and upload uniform data
colorShader.begin();
// Time and resolution — updated every frame
colorShader.setUniform1f("u_time", ofGetElapsedTimef());
colorShader.setUniform2f("u_resolution", (float)ofGetWidth(), (float)ofGetHeight());
// Projection and modelview matrices
colorShader.setUniformMatrix4f("u_projectionMatrix", projectionMatrix);
// Render geometry here — the shader determines appearance
ofDrawRectangle(0, 0, ofGetWidth(), ofGetHeight());
colorShader.end();
}
Checkpoint: Every uniform declared in the GLSL code MUST be bound in C++ before draw() — missing uniforms cause silent rendering failures.
Implement VBO-Based Rendering for Performance — For batched geometry, use ofVbo with vertex buffers. Define vertices, normals, and texture coordinates as arrays, upload to GPU once, then render with a single draw call. This avoids per-frame CPU-to-GPU bandwidth bottlenecks.
Checkpoint: VBO data must be updated with .setVertexData() each frame if geometry changes — or use .setData() for static geometry loaded once.
Build Post-Processing Pipelines — Render the scene to an ofFbo (framebuffer object), bind the FBO's texture as a uniform in a post-processing shader, and draw a full-screen quad. Chain multiple effects by using each FBO as input to the next stage.
// ofApp.h
#pragma once
#include "ofMain.h"
class ofApp : public ofBaseApp {
public:
void setup() override;
void draw() override;
ofFbo sceneFbo; // Renders the main scene
ofFbo blurFbo; // Intermediate blur stage
ofShader sceneShader; // Scene rendering shader
ofShader blurShader; // Post-processing blur
ofShader finalShader; // Final composite + bloom
void drawScene(); // Render scene to FBO
void applyBlur(); // First blur pass (horizontal)
void applyBlurVertical();// Second blur pass (vertical)
void renderFinal(); // Composite with bloom
};
void ofApp::setup() {
// Allocate FBOs at window size
sceneFbo.allocate(ofGetWidth(), ofGetHeight(), GL_RGBA);
blurFbo.allocate(ofGetWidth() / 2, ofGetHeight() / 2, GL_RGBA);
if (!sceneShader.load("shaders/scene.vert", "shaders/scene.frag")) {
ofLogError("ofApp") << "Scene shader failed: "
<< sceneShader.getFragmentShaderInfo();
}
// ... load blur and final shaders
}
void ofApp::draw() {
drawScene(); // 1. Render scene into FBO
applyBlur(); // 2. Horizontal blur pass
applyBlurVertical();// 3. Vertical blur pass (Gaussian)
renderFinal(); // 4. Composite: scene + blurred bloom to screen
}
Checkpoint: FBOs must be allocated before first use — calling begin() on an unallocated FBO crashes silently on macOS.
Standard vertex shader that transforms vertices by model-view-projection matrix and passes UV coordinates to the fragment shader.
// shaders/common/vertex.glsl
#version 330 core
in vec2 position;
in vec2 uv;
uniform mat4 u_projectionMatrix;
out vec2 v_uv;
void main() {
// Transform 2D position by projection matrix
gl_Position = u_projectionMatrix * vec4(position, 0.0, 1.0);
// Pass UV to fragment shader
v_uv = uv;
}
Fragment shader that creates a smooth color gradient driven by time — a common starting point for generative shaders.
// shaders/effects/colorWave/fragment.glsl
#version 330 core
in vec2 v_uv;
uniform float u_time;
uniform vec2 u_resolution;
out vec4 fragColor;
vec3 hsl2rgb(float h, float s, float l) {
// HSL to RGB conversion — compact implementation
vec3 rgb = clamp(abs(mod(h * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
return l + s * (rgb - 0.5) * (1.0 - abs(2.0 * l - 1.0));
}
void main() {
vec2 uv = v_uv;
// Create a flowing color wave based on time and position
float hue = fract(u_time * 0.1 + uv.x * 0.5 + uv.y * 0.3);
vec3 color = hsl2rgb(hue, 0.8, 0.5);
// Soft edge falloff from center
float dist = length(uv - 0.5) * 2.0;
color *= smoothstep(1.0, 0.0, dist);
fragColor = vec4(color, 1.0);
}
// ❌ BAD — Single pass blur with large kernel in fragment shader
// This is O(n²) per pixel and runs on CPU-simulated loops
#version 330 core
uniform sampler2D u_texture;
uniform float u_radius; // Can't actually use this for real radius!
void main() {
vec4 sum = vec4(0.0);
for (int i = -10; i <= 10; ++i) { // Fixed large loop — terrible performance
for (int j = -10; j <= 10; ++j) {
sum += texture(u_texture, gl_FragCoord.xy + vec2(i, j));
}
}
gl_FragColor = sum / 441.0;
}
// ✅ GOOD — Two-pass separable Gaussian blur (horizontal then vertical)
// Each pass samples only 9 pixels instead of 100+ per pixel
// shaders/post/horizontalBlur/fragment.glsl
#version 330 core
uniform sampler2D u_texture;
uniform vec2 u_direction; // (1.0, 0.0) for horizontal
uniform float u_blurAmount; // Controls spread
out vec4 fragColor;
void main() {
vec2 texCoord = gl_TexCoord[0].xy;
vec4 result = vec4(0.0);
float weights[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
// Sample center + 4 pixels in the blur direction
result += texture(u_texture, texCoord) * weights[0];
for (int i = 1; i <= 4; ++i) {
float offset = u_blurAmount * float(i);
result += texture(u_texture, texCoord + u_direction * offset) * weights[i];
result += texture(u_texture, texCoord - u_direction * offset) * weights[i];
}
fragColor = result;
}
#include "ofMain.h"
class MeshRenderer {
public:
void setup() {
// Define a simple quad with UVs — vertices in clip space (-1 to +1)
std::vector<glm::vec2> vertices = {
{-1.0f, -1.0f}, // bottom-left
{ 1.0f, -1.0f}, // bottom-right
{ 1.0f, 1.0f}, // top-right
{-1.0f, 1.0f} // top-left
};
std::vector<glm::vec2> uvs = {
{0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f}
};
std::vector<GLuint> indices = {0, 1, 2, 0, 2, 3}; // Two triangles
vbo.setVertexData(vertices.data(), vertices.size(), GL_STATIC_DRAW);
vbo.setTextureData(uvs.data(), uvs.size(), GL_STATIC_DRAW);
vbo.setIndexData(indices.data(), indices.size(), GL_STATIC_DRAW);
}
void draw(ofShader& shader) {
shader.begin();
// Enable vertex attribute locations (match GLSL 'in' variables)
shader.enableAttribute("position");
shader.enableAttribute("uv");
vbo.drawElements(GL_TRIANGLES, 6); // Draw the quad
shader.disableAttribute("position");
shader.disableAttribute("uv");
shader.end();
}
private:
ofVbo vbo;
};
Shader failures produce black screens. Always check both compilation and linking errors with detailed logging.
void ofApp::setup() {
// Attempt to load shader
bool loaded = myShader.load("shaders/myShader/vertex.glsl",
"shaders/myShader/fragment.glsl");
if (!loaded) {
ofLogError("ofApp::setup") << "===== SHADER LOAD FAILED =====";
// Check vertex shader compilation logs
auto vInfo = myShader.getVertexShaderInfo();
if (!vInfo.empty()) {
ofLogError("ofApp::setup") << "VERTEX SHADER LOG:\n" << vInfo;
}
// Check fragment shader compilation logs
auto fInfo = myShader.getFragmentShaderInfo();
if (!fInfo.empty()) {
ofLogError("ofApp::setup") << "FRAGMENT SHADER LOG:\n" << fInfo;
}
// Check link program log
auto lInfo = myShader.getInfoLog();
if (!lInfo.empty()) {
ofLogError("ofApp::setup") << "LINKER LOG:\n" << lInfo;
}
ofExit(1); // Fatal error — don't continue with broken shader
}
ofLogNotice("ofApp::setup") << "Shader loaded successfully";
}
shader.load() return value and inspect getVertexShaderInfo()/getFragmentShaderInfo() on failure#version 330 core or higher for GLSL compatibility with modern OF OpenGL contextsgl_FragCoord for position-independent rendering — use UV coordinates passed from vertex shader insteadofShader::begin() without matching end() — causes state corruption for subsequent OF drawing callsglVertex, glColor) with shaders — mixed rendering causes undefined behaviorWhen implementing or reviewing OpenFrameworks shader code, produce:
in/out variables.vert and .frag files with version directive, attribute declarations, and output typesin variable names| Skill | Purpose |
|---|---|
openframeworks | Core OF application development, lifecycle, and addon patterns |
performance-optimization | Profiling shader performance, reducing fill rate bottlenecks, optimizing draw calls |
Authoritative documentation and language references for GLSL shader development in OpenFrameworks.