GLSL 编程作业清单
本文件包含所有章节的编程作业,帮助你通过实际编程来掌握 GLSL。
📋 作业说明
- 难度等级:⭐ 简单 | ⭐⭐ 中等 | ⭐⭐⭐ 困难
- 预计时间:每个作业建议在 30-60 分钟内完成
- 提交方式:创建独立的 HTML 文件或使用你的项目环境运行代码
第一阶段:GLSL 语言基础
作业 01:环境搭建与第一个 Shader ⭐
任务:
- 按照教程搭建原生 Three.js 环境
- 创建一个平面(PlaneGeometry),使用
RawShaderMaterial - 实现一个简单的单色平面(比如蓝色)
- 添加一个
uTimeuniform,让颜色随时间变化(比如sin(uTime)控制亮度)
要求:
- 必须使用
RawShaderMaterial(不能用ShaderMaterial) - 必须手动声明所有变量(
precision、attribute、uniform) - 实现基本的渲染循环
扩展挑战:
- 让颜色在 RGB 三个通道上分别使用不同的时间函数(如
sin(uTime)、cos(uTime * 0.5))
作业 02:类型系统与向量操作 ⭐
任务:
- 创建一个平面,使用 UV 坐标绘制图案
- 使用
vec2、vec3、vec4进行颜色计算 - 实现以下效果(任选 2-3 个):
- 使用
vec2的xy分量作为 RGB 的前两个通道 - 使用
vec3构造颜色,然后用vec4添加透明度 - 使用
vec3(1.0)创建白色,然后通过 swizzling 创建其他颜色
- 使用
要求:
- 所有浮点数必须使用
.0后缀 - 不能混用
int和float - 展示至少 3 种不同的向量构造方式
代码示例框架:
// 片段着色器中
void main() {
vec2 uv = vUv;
// 任务 1:使用 vec2 的 xy 作为颜色
vec3 color1 = vec3(uv.xy, 0.5);
// 任务 2:使用 vec3 和 vec4
vec3 baseColor = vec3(1.0, 0.5, 0.0);
vec4 finalColor = vec4(baseColor, 1.0);
// 任务 3:使用 swizzling
vec3 white = vec3(1.0);
vec3 swapped = white.bgr; // 交换顺序
gl_FragColor = vec4(color1, 1.0);
}
作业 03:向量运算与几何函数 ⭐⭐
章节:03. 向量与矩阵操作
任务:
- 使用
distance()和step()绘制一个圆形 - 使用
length()和normalize()创建径向渐变 - 使用
dot()创建方向性光照效果(模拟简单的 Lambert 光照)
要求:
- 圆形中心在屏幕中央,半径可调
- 径向渐变从中心向外扩散
- 光照效果需要计算法线(可以使用
normalize(vec3(vUv - 0.5, 0.0))作为简单法线)
提示:
// 圆形
float d = distance(vUv, vec2(0.5));
float circle = step(d, 0.3); // 半径 0.3
// 径向渐变
float dist = length(vUv - vec2(0.5));
float gradient = 1.0 - dist * 2.0; // 从中心向外变暗
// 简单光照
vec3 normal = normalize(vec3(vUv - vec2(0.5), 0.1));
vec3 lightDir = normalize(vec3(0.5, 0.5, 1.0));
float light = dot(normal, lightDir);
作业 04:attribute、uniform、varying 实战 ⭐⭐
章节:04. 输入输出变量
任务:
- 创建一个立方体(BoxGeometry),在顶点着色器中修改顶点位置
- 使用
varying将自定义数据从顶点着色器传递到片段着色器 - 实现以下效果:
- 使用
uniform控制立方体的颜色 - 使用
attribute读取顶点的normal,通过varying传递到片段着色器 - 在片段着色器中使用法线信息创建颜色渐变
- 使用
要求:
- 必须显式声明所有
attribute、uniform、varying - 在顶点着色器中修改顶点位置(比如添加
sin波动) - 使用法线信息在片段着色器中创建视觉效果
扩展挑战:
- 添加一个
uniform vec3 uMouse,让颜色根据鼠标位置变化
作业 05:控制流与函数 ⭐
任务:
- 实现一个自定义函数,计算两个颜色的混合
- 使用
if-else创建一个分区域的颜色效果 - 重要:将
if-else改写为使用step()或mix()的无分支版本 - 使用
for循环创建重复图案(比如条纹)
要求:
- 自定义函数必须使用
in、out或inout修饰符 - 展示
if-else和数学函数两种实现方式 for循环次数必须是编译时常量
代码示例:
// 任务 1:自定义函数
vec3 mixColors(vec3 color1, vec3 color2, float t) {
return mix(color1, color2, t);
}
// 任务 2:if-else 版本
vec3 color;
if (vUv.x < 0.5) {
color = vec3(1.0, 0.0, 0.0); // 红色
} else {
color = vec3(0.0, 0.0, 1.0); // 蓝色
}
// 任务 3:无分支版本
vec3 color = mix(
vec3(1.0, 0.0, 0.0),
vec3(0.0, 0.0, 1.0),
step(0.5, vUv.x)
);
// 任务 4:for 循环创建条纹
float stripes = 0.0;
for (int i = 0; i < 5; i++) {
float stripe = step(0.5, fract(vUv.x * 10.0 + float(i) * 0.2));
stripes += stripe;
}
作业 06:内置函数综合应用 ⭐⭐
章节:06. 常用内置函数详解
任务:
- 使用
mix()创建颜色渐变 - 使用
smoothstep()创建平滑过渡的边缘 - 使用
fract()创建重复的图案 - 使用
sin()、cos()创建波浪效果 - 组合多个函数创建一个复杂的视觉效果
要求:
- 必须使用
mix、step、smoothstep、fract至少各一次 - 创建一个"综合展示"场景,展示所有效果
创意建议:
- 创建一个"函数画廊",屏幕分成多个区域,每个区域展示不同的函数效果
- 或者创建一个随时间变化的动画,展示不同函数的组合效果
第二阶段:图形学核心原理
作业 07:理解插值 ⭐⭐
任务:
- 创建一个三角形,三个顶点分别设置为红色、绿色、蓝色
- 观察中间区域的自动渐变效果
- 创建一个平面,在顶点着色器中为不同区域设置不同的
varying值 - 在片段着色器中使用这些插值后的值创建渐变效果
要求:
- 必须使用
varying变量传递数据 - 展示至少 2 种不同的插值效果(颜色、数值等)
- 理解为什么三角形中间的颜色是自动计算的
扩展挑战:
- 创建一个自定义的
varying float vElevation,在顶点着色器中根据位置计算高度,在片段着色器中使用高度值控制颜色
作业 08:实现完整的光照模型 ⭐⭐⭐
章节:08. 光照模型实战:从 Lambert 到 Phong
任务:
- 实现基础的 Lambert 漫反射光照
- 添加环境光
- 添加 Phong 镜面高光
- 可选:改进为 Blinn-Phong 模型
要求:
- 必须使用一个 3D 物体(如球体或立方体)
- 需要正确计算和传递法线、位置、光线方向、视线方向
- 所有向量必须归一化
- 实现可交互的光源位置(通过
uniform控制)
提示:
- 使用
SphereGeometry或BoxGeometry - 在顶点着色器中计算世界空间位置和法线
- 在片段着色器中计算所有光照分量
扩展挑战:
- 实现多光源支持
- 添加阴影效果(高级)
第三阶段:Shader 编程实战
作业 09:第一个动态 Shader ⭐⭐
任务: 参考教程中的"动态色彩波浪"效果,实现以下变体:
- 基础版本:实现教程中的波浪效果
- 改进版本:修改频率、振幅、方向
- 创意版本:添加你自己的创意元素
要求:
- 必须使用
uTimeuniform 实现动画 - 顶点着色器和片段着色器都要有动态效果
- 代码结构清晰,有注释
创意建议:
- 改变波浪的方向(X、Y、对角线)
- 添加多个频率的叠加
- 改变颜色计算方式
- 添加交互控制(鼠标位置影响效果)
作业 10:三大 WebGL 效果实战 ⭐⭐⭐
任务: 实现以下三个效果(每个效果单独完成):
效果 1:菲涅尔效果
- 创建一个 3D 物体(球体或复杂模型)
- 实现边缘发光效果
- 边缘颜色和中心颜色可配置
效果 2:纹理流动
- 加载一张纹理图片
- 实现纹理的滚动动画
- 可选:添加波浪扭曲效果
效果 3:噪声溶解
- 实现基础的噪声函数
- 创建溶解效果(物体逐渐消失)
- 添加边缘发光(溶解边缘)
要求:
- 每个效果都要有完整的代码和注释
- 效果参数可以通过
uniform调整 - 代码结构清晰,易于理解和修改
扩展挑战:
- 将三个效果组合在一起
- 添加 GUI 控制面板(如 dat.GUI)
作业 11:屏幕后处理效果 ⭐⭐⭐
任务:
使用 Three.js 的 EffectComposer 实现以下后处理效果:
- 灰度效果:将场景转换为黑白
- 晕影效果:边缘变暗
- 像素化效果:降低分辨率
- 色差效果:RGB 通道偏移
要求:
- 必须使用
EffectComposer和自定义ShaderPass - 每个效果都要可以单独开启/关闭
- 效果参数可调节
提示:
- 使用
RenderPass渲染场景 - 使用
ShaderPass实现后处理 - 通过
uniform控制效果强度
第四阶段:工程化与调试
作业 12:Shader 调试技巧 ⭐⭐
任务:
- 创建一个有问题的 Shader(故意引入错误)
- 使用可视化调试方法找出问题
- 修复问题并验证
常见错误类型:
- 变量未声明
- 类型不匹配
- 精度问题
- 除零错误
- 向量未归一化
要求:
- 展示至少 3 种不同的调试方法
- 记录调试过程和解决方案
- 总结调试经验
调试方法:
- 将中间值输出为颜色(如
gl_FragColor = vec4(vNormal, 1.0)) - 使用
step()创建调试网格 - 逐步简化代码定位问题
作业 13:Three.js Shader 工程化 ⭐⭐⭐
章节:13. Three.js 源码级解析:GLSL 的工程化艺术
任务:
- 研究 Three.js 的
ShaderChunk系统 - 使用
onBeforeCompile修改内置材质 - 创建一个自定义的 Shader 模块系统
要求:
- 理解
ShaderChunk的工作原理 - 成功修改至少一个内置材质(如
MeshStandardMaterial) - 创建可复用的 Shader 代码片段
提示:
material.onBeforeCompile = (shader) => {
// 修改顶点着色器
shader.vertexShader = shader.vertexShader.replace(
'#include <worldpos_vertex>',
'#include <worldpos_vertex>\n' + customCode
);
// 添加自定义 uniform
shader.uniforms.uCustom = { value: 1.0 };
};
第五阶段:底层深造
作业 14:纯原生 WebGL ⭐⭐⭐
任务: 不使用 Three.js,直接使用 WebGL API 绘制一个三角形。
要求:
- 手动创建 WebGL 上下文
- 手动编写和编译 Shader
- 手动创建和绑定 Buffer
- 手动设置
vertexAttribPointer - 手动调用
drawArrays
提示:
- 这是最底层的 WebGL 编程
- 需要理解 WebGL 的完整流程
- 代码会比较冗长,但能深刻理解底层原理
作业 15:综合项目 ⭐⭐⭐
章节:综合所有知识
任务: 创建一个完整的 Shader 项目,包含以下特性:
- 多个 Shader 效果:至少实现 3 种不同的效果
- 交互控制:鼠标或键盘控制参数
- 性能优化:注意 Draw Call 和 Shader 复杂度
- 代码质量:清晰的注释和结构
项目建议:
- Shader 画廊:展示多个 Shader 效果
- 交互式 Shader 编辑器:实时修改 Shader 代码
- 3D 场景:包含光照、后处理等完整效果
📝 作业提交建议
- 代码规范:使用清晰的变量命名,添加注释
- 文档说明:每个作业附上简短的说明文档
- 效果展示:提供截图或 GIF 动画
- 问题记录:记录遇到的问题和解决方案
🎯 学习路径建议
- 初学者:完成 ⭐ 和 ⭐⭐ 的作业
- 进阶学习者:完成所有 ⭐⭐ 和部分 ⭐⭐⭐ 的作业
- 专家级:完成所有作业,并尝试扩展挑战
祝你学习愉快!通过实际编程,GLSL 会变得不再神秘! 🚀