☰
深入探索 The Book of Shaders 第四章:跨平台运行 Fragment Shader 的完整工具链与主流框架接入指南
2026/10/15 1:52:34 网站建设 项目流程

【免费下载链接】thebookofshaders

Step-by-step guide through the abstract and complex universe of Fragment Shaders.

项目地址:https://gitcode.com/gh_mirrors/th/thebookofshaders
点击查看免费下载

本章是《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 资源随可执行文件一同安装。

七、总结:一套约定,四种姿势

回顾本章,你可以提炼出一条清晰的实践主线:

  1. 浏览器预览:<canvas class="glslCanvas">

【免费下载链接】thebookofshaders

Step-by-step guide through the abstract and complex universe of Fragment Shaders.

项目地址:https://gitcode.com/gh_mirrors/th/thebookofshaders
点击查看免费下载
上一篇:探索高效笔记:Obsidian.nvim —— 你的 Neovim 笔记伴侣
下一篇:CANN/asc-devkit调试接口__trap函数

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询