【免费下载链接】thebookofshaders
Step-by-step guide through the abstract and complex universe of Fragment Shaders.
本章是《The Book of Shaders》实践之路的关键一站:从浏览器里的一个<canvas>标签,到命令行中的一行脚本,再到 Processing、Three.js、openFrameworks、SFML 等主流图形框架,系统梳理「编写 → 显示 → 分享 → 策展」一套完整的 Fragment Shader 运行工具链。读完本文,你将掌握 glslCanvas、glslViewer、glslEditor、glslGallery 四大工具的用法与底层接入方式,并能把本书通篇使用的统一 uniform 约定(u_resolution、u_mouse、u_time)无缝迁移到你熟悉的任何平台。
一、工具生态总览:一次编写,处处运行
作为本书构建与作者艺术实践的一部分,作者围绕 Fragment Shader 打造了一整套工具生态,用于创建、显示、分享与策展shader。这些工具的核心承诺是:无需修改你的 shader 代码,即可在 Linux、macOS、Windows、树莓派(Raspberry Pi)以及各类浏览器中保持一致的运行效果。
整套生态按工作流被划分为四个环节,构成了本章的主干:
| 环节 | 工具 | 核心用途 |
|---|---|---|
| 显示(Display) | glslCanvas | 在浏览器中渲染独立 shader,一行<canvas>标签即可驱动 |
| 运行(Run) | glslViewer | 在命令行 / bash 脚本 / unix 管道中直接编译运行 shader |
| 创建(Create) | glslEditor | 在线编辑器,内嵌实时预览与多种交互组件 |
| 分享与策展(Share & Curate) | glslEditor 导出、glslGallery | 生成唯一分享链接、构建可嵌入任意站点的作品画廊 |
支撑这套生态的关键,是贯穿全书每一章 shader 示例的uniform 命名约定:u_resolution(画布分辨率,vec2)、u_time(运行时长,float)、u_mouse(鼠标位置,vec2/vec3)。以书中早期的示例 03/space.frag 为例,其片段着色器头部正是这一约定的标准形态:
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; uniform vec2 u_mouse; uniform float u_time; void main() { vec2 st = gl_FragCoord.xy/u_resolution; gl_FragColor = vec4(st.x,st.y,0.0,1.0); }这套约定在第四章介绍的每一个框架接入方案中都会被原样复用——只要统一了这三个 uniform,同一份.frag代码就能在浏览器、命令行和桌面框架间零成本移植。
二、在浏览器中运行:glslCanvas 的极简接入
本书所有「活体示例」(live examples)都通过glslCanvas渲染。它的设计目标是把「运行一个独立 shader」这件事简化到极致:你只需要在 HTML 中放置一个带特定 class 的canvas元素,glslCanvas 会自动读取 shader 源码、编译并逐帧渲染。
最小可用写法如下(出自本章文档):
<canvas class="glslCanvas">glslViewer yourShader.frag yourInputImage.png —w 500 -h 500 -E screenshot,yourOutputImage.png参数含义拆解:
yourShader.frag:片段着色器源码文件;yourInputImage.png:可选输入纹理;-w 500 -h 500:输出窗口 / 渲染分辨率;-E screenshot,yourOutputImage.png:导出(export)模式,将渲染结果截图保存为指定文件。
此外,glslViewer 也是在树莓派上编译 shader的推荐途径——这正是 openFrame.io 用它来展示 shader 艺术作品的原因,适合面向嵌入式场景与数字艺术装置的工作流。
离线编辑:SublimeText 插件
如果你更愿意在本地用 SublimeText 离线编写 shader,可以安装官方的 glslViewer 插件(Package Control 中的 glslViewer package)。装上之后,SublimeText 编辑器就能直接驱动 glslViewer 进行实时预览,实现「保存即刷新」的本地迭代体验,与书中内嵌编辑器的工作方式互补。
四、创建与内联预览:glslEditor 在线编辑器
为了让「编写 shader」这种偏抽象的体验更直观,作者制作了在线编辑器glslEditor。它内嵌于本书每一个 live example 中,提供一系列实用组件:实时画布预览、参数调节控件、语法提示等。
在本仓库的 src/main.js 中可以看到它的嵌入方式——站点通过new GlslEditor(ccList[i], {...})初始化,并附带了一组关键配置:
var editor = new GlslEditor(ccList[i], { canvas_size: 250, canvas_follow: true, canvas_float: 'right', tooltips: true, exportIcon: true }); editor.open(srcFile);其中canvas_follow: true表示预览画布跟随光标实时更新,exportIcon: true开启导出按钮——这正是下一节「分享」环节的入口。你也可以把 glslEditor 作为独立的 Web 应用使用,在浏览器中直接打开 editor 站点编写并运行 shader。
五、分享与策展:导出链接与 glslGallery 画廊
glslEditor 的嵌入版和独立版都带有导出按钮:点击后会为你的 shader 生成一个唯一 URL,方便你把作品链接发给任何人查看。同时它还支持直接导出到 openFrame.io——一个面向 shader 艺术作品的在线平台。
分享代码只是分享 shader 作品的开始。作者还制作了glslGallery:一个将多个 shader 编排成「画廊(gallery)」的工具,最终产物是一个可嵌入任何网站的组件。这使得策展(curate)成为可能——把散落的单个作品组织成有主题、可浏览的展览页面。
六、在你的常用框架中运行 shader
如果你已经在 Processing、Three.js、openFrameworks 或 SFML 中积累了编程经验,会希望在熟悉的平台上尝试 shader。本章文档以「同一套 uniform」为前提,给出了三个主流框架的完整接入示例(完整源码存放在仓库 04 目录下)。除此之外,仓库中还额外提供了SFML的完整工程,本节一并展开。
6.1 Three.js:在 WebGL 场景中挂载 ShaderMaterial
Three.js 是目前 WebGL 领域最知名的 JavaScript 3D 图形库之一。在 Three.js 中运行本书 shader 的核心思路是:把顶点着色器和片段着色器分别写入两个<script>标签,再通过THREE.ShaderMaterial与一个铺满屏幕的平面网格(PlaneBufferGeometry)组合渲染。
完整 HTML/JS 示例(与仓库 04/three_js/index.html 一致):
<body> <div id="container"></div> <script src="js/three.min.js"></script> <script id="vertexShader" type="x-shader/x-vertex"> void main() { gl_Position = vec4( position, 1.0 ); } </script> <script id="fragmentShader" type="x-shader/x-fragment"> uniform vec2 u_resolution; uniform float u_time; void main() { vec2 st = gl_FragCoord.xy/u_resolution.xy; gl_FragColor=vec4(st.x,st.y,0.0,1.0); } </script> <script> var container; var camera, scene, renderer, clock; var uniforms; init(); animate(); function init() { container = document.getElementById( 'container' ); camera = new THREE.Camera(); camera.position.z = 1; scene = new THREE.Scene(); clock = new THREE.Clock(); var geometry = new THREE.PlaneBufferGeometry( 2, 2 ); uniforms = { u_time: { type: "f", value: 1.0 }, u_resolution: { type: "v2", value: new THREE.Vector2() }, u_mouse: { type: "v2", value: new THREE.Vector2() } }; var material = new THREE.ShaderMaterial( { uniforms: uniforms, vertexShader: document.getElementById( 'vertexShader' ).textContent, fragmentShader: document.getElementById( 'fragmentShader' ).textContent } ); var mesh = new THREE.Mesh( geometry, material ); scene.add( mesh ); renderer = new THREE.WebGLRenderer(); renderer.setPixelRatio( window.devicePixelRatio ); container.appendChild( renderer.domElement ); onWindowResize(); window.addEventListener( 'resize', onWindowResize, false ); document.onmousemove = function(e){ uniforms.u_mouse.value.x = e.pageX uniforms.u_mouse.value.y = e.pageY } } function onWindowResize( event ) { renderer.setSize( window.innerWidth, window.innerHeight ); uniforms.u_resolution.value.x = renderer.domElement.width; uniforms.u_resolution.value.y = renderer.domElement.height; } function animate() { requestAnimationFrame( animate ); render(); } function render() { uniforms.u_time.value += clock.getDelta(); renderer.render( scene, camera ); } </script> </body>几个关键点:
id="fragmentShader"的 script 标签是核心替换位:本书后续章节的所有 shader 都可以直接复制粘贴进这个标签,其余脚手架代码无需改动;- 顶点着色器保持最简:
gl_Position = vec4( position, 1.0 );,将一个 2×2 的平面铺满视口; THREE.ShaderMaterial通过uniforms对象向 GLSL 传递参数,类型声明格式为"f"(float)、"v2"(vec2);- 时间推进依靠
THREE.Clock的getDelta()逐帧累加,分辨率与鼠标坐标则在窗口尺寸变化时同步更新(仓库版本 04/three_js/index.html 中额外维护了鼠标坐标并随 resize 一起刷新)。
6.2 Processing:P2D 渲染器与热重载
Processing 由 Ben Fry 与 Casey Reas 于 2001 年发起,是一个极简而强大的创意编程环境;Andres Colubri 对其中 OpenGL 与视频模块的重要更新,让 GLSL shader 在 Processing 中的使用变得空前简单。
Processing 会在 sketch 的data文件夹中查找名为shader.frag的文件。记得把示例 shader 复制进该目录并保持该文件名。
基础接入代码(与仓库 04/processing/processing.pde 一致):
PShader shader; void setup() { size(640, 360, P2D); noStroke(); shader = loadShader("shader.frag"); } void draw() { shader.set("u_resolution", float(width), float(height)); shader.set("u_mouse", float(mouseX), float(mouseY)); shader.set("u_time", millis() / 1000.0); shader(shader); rect(0,0,width,height); }要点说明:
- 渲染器必须指定为
P2D(OpenGL 2D 渲染器),shader 才能生效; shader.set(...)负责把 CPU 侧的参数注入到 GLSL uniform;shader(shader)将当前绘制管线的片段着色阶段替换为自定义 shader,随后rect(0,0,width,height)绘制一个覆盖全屏的矩形作为「广告牌(billboard)」来触发逐像素执行;- 仓库版本还附带了热重载逻辑:
keyPressed()中每次按键都重新执行loadShader("shader.frag"),实现「改完即按、即按即刷新」的快速迭代(见 04/processing/processing.pde)。
对应配套的 shader 文件(仓库 04/processing/data/shader.frag):
#ifdef GL_ES precision mediump float; #endif #define PROCESSING_COLOR_SHADER uniform vec2 u_resolution; uniform vec3 u_mouse; uniform float u_time; void main() { vec2 st = gl_FragCoord.st/u_resolution; gl_FragColor = vec4(st.x,st.y,0.0,1.0); }兼容性提示:为了让 shader 在 Processing2.1 之前的版本上正常工作,必须在 shader 文件开头添加一行#define PROCESSING_COLOR_SHADER——它告诉 Processing 使用内置的颜色空间约定,避免输出颜色被错误处理。若使用 2.1 及以后版本,这一行可以省略。
6.3 openFrameworks:C++ 管线与 ofxShader 增强
openFrameworks 是一个封装了 OpenGL 及其他开源 C++ 库的框架,与 Processing 的思维方式非常接近,但需要直接面对 C++ 编译器。与 Processing 类似,openFrameworks 也会在data文件夹中查找 shader 文件,因此记得把要用的.frag文件复制进去,并在加载时使用对应文件名。
本章文档给出的最简版本:
void ofApp::draw(){ ofShader shader; shader.load("","shader.frag"); shader.begin(); shader.setUniform1f("u_time", ofGetElapsedTimef()); shader.setUniform2f("u_resolution", ofGetWidth(), ofGetHeight()); ofRect(0,0,ofGetWidth(), ofGetHeight()); shader.end(); }而仓库中的完整实现 04/openFrameworks/src/ofApp.cpp 则更完整:setup()中一次性加载 shader,draw()里补齐了u_mouse(setUniform2f),并同样加入了按键热重载逻辑(keyPressed中重新shader.load("","shader.frag"))。对应的 shader 文件位于 04/openFrameworks/bin/data/shader.frag。
如果你希望在 openFrameworks 中以更简洁的方式使用 glslViewer / glslCanvas 规范中的全部 uniform,作者推荐ofxShader插件。它在底层补齐了统一 uniform 的注入工作,并额外支持:
- 多缓冲(multiple buffers)渲染;
- 材质 shader(material shaders);
- 热重载(hot-reload);
- 面向树莓派 OpenGL ES 的自动转换。
使用 ofxShader 后,代码可以精简为:
//-------------------------------------------------------------- void ofApp::setup(){ ofDisableArbTex(); sandbox.allocate(ofGetWidth(), ofGetHeight()); sandbox.load("grayscott.frag"); } //-------------------------------------------------------------- void ofApp::draw(){ sandbox.render(); sandbox.draw(0, 0); }sandbox.allocate()分配离屏缓冲区,load()载入 shader,render()执行渲染,draw(0,0)把结果绘制到窗口——整个「管线替换 + uniform 注入」的过程被封装掉了。
6.4 补充:SFML 的完整接入工程
虽然本章文档正文未展开 SFML 的代码,但仓库 04/sfml 目录下提供了完整的 SFML 接入工程,正好补齐了「框架列表」中提到的第四种选择。其核心逻辑位于 04/sfml/src/main.cpp:
auto shader = sf::Shader{}; if (!shader.loadFromFile("data/shader.frag", sf::Shader::Fragment)) { std::cerr << "Couldn't load fragment shader\n"; return -1; } ... shader.setUniform("u_resolution", sf::Glsl::Vec2{ window.getSize() }); shader.setUniform("u_mouse", sf::Glsl::Vec2{ mouse_position }); shader.setUniform("u_time", clock.getElapsedTime().asSeconds()); window.clear(); window.draw(shape, &shader); window.display();要点:
sf::Shader::loadFromFile仅加载片段着色器(sf::Shader::Fragment),顶点阶段使用 SFML 内置的默认管线;- uniform 通过
setUniform注入,坐标类参数统一用sf::Glsl::Vec2包装; - 一个铺满窗口的
RectangleShape配合window.draw(shape, &shader)即完成全屏渲染; - 工程由 04/sfml/CMakeLists.txt 构建,要求 CMake 3.15+、C++17 与 SFML 2.5+,并通过
install(DIRECTORY data ...)将 shader 资源随可执行文件一同安装。
七、总结:一套约定,四种姿势
回顾本章,你可以提炼出一条清晰的实践主线:
- 浏览器预览:
<canvas class="glslCanvas">赞
【免费下载链接】thebookofshaders
Step-by-step guide through the abstract and complex universe of Fragment Shaders.
相关推荐
The Book of Shaders 第 04 章实战:跨平台运行 Fragment Shader 的工具链与主流框架接入指南
The Book of Shaders 第 04 章实战:跨平台运行 Fragment Shader 的工具链与主流框架接入指南 本章(仓库目录 04/ )是《
Ant Design Pagination「更多分页」深度解析:省略号折叠与前后跳转按钮的源码实现
Ant Design Pagination「更多分页」深度解析:省略号折叠与前后跳转按钮的源码实现 导读 本文围绕 Ant Design 官方演示 更多分页说明
运行你的 Shader:《The Book of Shaders》第四章跨平台执行指南
运行你的 Shader:《The Book of Shaders》第四章跨平台执行指南 本指南围绕《The Book of Shaders》第四章"Runnin