AI 协作日志
学生 Web 实验作品平台
学号:202412211402020 · 来源:ai-log.md
# 实验一 AI协作记录
> 请记录本人真实的AI协作过程。不要只粘贴AI生成结果,应体现自己的判断、测试和核验。
AI协作记录与任务思考题回答
## AI协作1:理解数据流
### 我的目标
刚接触WebGL,task1的代码照着写能跑,但每一步为什么要这么写完全懵。比如为什么要createBuffer还要bindBuffer?vertexAttribPointer那一长串参数都是啥?光栅化又是什么?想先建立一个整体认识。
### 提示词
"从JavaScript的Float32Array到屏幕上出现一个三角形,中间发生了哪些关键步骤?请结合WebGL2解释Buffer、vertexAttribPointer、Vertex Shader、光栅化和 Fragment Shader各自负责什么,不要直接修改我的代码。我是初学者,尽量通俗一点。"
### AI关键回答摘要
AI给了一个7步的流程:
1. JS里写Float32Array,相当于在草稿纸上列好顶点坐标,数据在CPU内存里
2. createBuffer+bufferData,相当于把草稿纸的数据抄到GPU的黑板上,上传到GPU内存
3. getAttribLocation,相当于问Shader"vPosition这个属性在你那的几号门",获取属性索引
4. vertexAttribPointer,相当于贴一张说明书,告诉GPU黑板上的数据怎么读——每2个数一组,每组是一个顶点的坐标
5. Vertex Shader,每个顶点走一遍这个加工站,算出最终位置
6. 光栅化,把三角形切成一个个像素,同时把顶点的颜色等属性匀到每个像素上,也就是插值
7. Fragment Shader,每个像素走一遍这个上色车间,算出最终颜色
### 我的判断
这7步框架听起来挺清楚的,那个"抄到黑板上"的比方也帮我理解了Buffer的作用。但有几个地方AI说了跟没说一样:
- vertexAttribPointer为什么非要先bindBuffer才能调?直接传Buffer进去不行吗?
- 光栅化的"插值"到底是怎么算的?AI只说了"匀一下",太模糊了
- 改了JS的数据,GPU黑板上的数据会自动更新吗?
### 验证方法
1. 对着task1.js的代码,把AI说的7步一行一行对应到代码里,看每一步分别是哪行
2. 把vertexAttribPointer的第二个参数从2改成3,刷新看效果
3. 修改vertices数组里的值,但注释掉bufferData那行,看画面变不变
### 验证结果
1. 7步都能在代码里找到对应,框架没问题
2. size改成3后,三角形形状歪了。说明size确实影响GPU怎么读数据——AI说的"每2个数一组"是对的
3. 改了vertices但不调用bufferData,画面一点没变。验证了"CPU和GPU是两块内存,改了一边另一边不知道"——AI那个"抄到黑板上"的比方是对的
### 修正后的结论
原以为vertexAttribPointer就是"把Buffer和属性绑在一起",绑完之后属性就记住了这个Buffer。
实际上不是——vertexAttribPointer是跟"调用的时候当前绑定的那个Buffer"关联的。也就是说,调用它的时候,哪个Buffer挂在ARRAY_BUFFER上,它就跟哪个Buffer建立关系。换了新Buffer之后,如果不重新调vertexAttribPointer,属性还指着老的Buffer。
还有"插值",AI只说了个概念,直到做任务4看到三角形里红绿蓝渐变的效果,才真正明白"插值"是什么意思——就是颜色慢慢过渡过去,不是突然变的。
---
## AI协作2:程序调试
### 现象
任务3卡了好久。左边的三角形好好的,右边的四边形就是不对——颜色跟三角形一样是橙色,形状也怪怪的,不是矩形。Console里也没报错,完全不知道从哪下手。
### 我的初步判断
颜色不对可能是uniform没改对?形状不对可能是顶点坐标写错了,或者draw mode不对?但不确定,一个个试太费时间了。
### 给AI的提示词
"我写WebGL2的多对象绘制,第一个三角形正常,第二个四边形显示有问题:颜色跟第一个一样,形状也不对。请不要直接帮我改代码,也不要重写,就告诉我最可能的几个原因,以及每个原因怎么验证。
### AI给出的关键建议
原因1:TRIANGLE_STRIP的顶点顺序错了
TRIANGLE_STRIP对顶点顺序非常挑剔,顺序不对的话三角形会翻转或者缺角。推荐顺序是左上、左下、右上、右下的带状排列。
验证:把4个顶点坐标console.log出来,对照推荐顺序一个一个看。
原因2:顶点数量不对
AI说可能数组里float的数量不对,或者drawArrays第三个参数写错了,写成了float数而不是顶点数。
验证:加一行 console.log(quad.length / 2),看输出是不是4。
原因3:没重新设置vertexAttribPointer
WebGL是状态机,vertexAttribPointer是跟当前绑定的Buffer关联的。如果绑定新Buffer后不重新设置vertexAttribPointer,顶点属性还在读旧Buffer的数据。
验证:检查四边形的bindBuffer之后,有没有重新调用vertexAttribPointer和enableVertexAttribArray。
颜色问题90%是因为uniform没重新设——WebGL是状态机,设一次就一直有效,画第二个图形的时候如果不改,就用第一个的颜色。
### 我实际进行的测试
第一步,先查颜色问题:
uniform4f是写了的,但是写在drawArrays的后面,就成了先画再改颜色,把uniform4f挪到drawArrays前面,再刷新,四边形变成紫色了
第二步,查顶点数量:
加了console.log(quad.length / 2),输出是4,没问题。
第三步,查形状问题:
把四个顶点坐标打印出来看:左上(0.2,0.5)、左下(0.2,-0.5)、右上(0.8,0.5)、右下(0.8,-0.5)。这个顺序是对的,是矩形
### 测试结果
颜色问题:我自己把uniform4f的位置写反了,写在drawArrays后面了。AI说的"状态机"方向是对的,但具体原因不是没写,而是顺序错了
顶点数量:没问题
形状:当前代码是对的
### 最终原因
1. 颜色相同:uniform4f放在了drawArrays之后,先绘制后修改,不生效
2. 形状:task2中确实因顶点顺序错误导致异常,修正后正常但task3中顺序正确
### 是否采纳AI建议及理由
部分采纳。
AI说的3个原因,在我当前的代码里只有1个半是对的——颜色问题方向对但具体原因不同,顶点顺序问题在task2踩过但task3已经修好了。
---
## AI协作3:顶点属性与颜色
### 我的目标
做到任务4,有两个顶点属性了,位置vPosition和颜色vColor,分别在两个Buffer里。
- GPU怎么知道哪个Buffer对应哪个属性?不会搞混吗?
- 为什么位置是2个float,颜色是4个float?这个数字是随便定的吗?
- GPU读Buffer的时候,是怎么知道"这个顶点读完了,该读下一个了"?
### 提示词
"WebGL2里,如果有两个顶点属性:vPosition每顶点2个float,vColor每顶点4个float,分别在两个Buffer里。GPU是怎么从每个Buffer里按顶点读取数据的?为什么一个读2个一个读4个?下一个顶点从哪里开始读?请从数据在内存里是怎么排列、GPU怎么按vertexAttribPointer的参数来读的角度解释,不要只列参数是什么意思。我是初学者,最好能举个例子。"
### AI关键回答摘要
AI用了"两个人各读各的书"来比方:
两个属性就像两个人,分别读两本不同的书,也就是两个Buffer
每本书的排版不一样:位置那本每行2个字(size=2),颜色那本每行4个字(size=4)
两个人各读各的,互不干扰
AI重点讲了步长(stride)的概念:
读完一个顶点后,要跳多少字节才到下一个顶点的开头,这就是步长
当stride参数传0的时候,代码里就是0,GPU会自动算步长 = size 乘以每个分量的字节数
例子:位置size=2,float是4字节,所以步长=8字节——读完第一个顶点的x和y共8字节,往后跳8字节就是第二个顶点的x和y
颜色size=4,步长=16字节
还有一种写法是把位置和颜色交错存在同一个Buffer里,比如x,y,r,g,b,a,x,y,r,g,b,a这样排列,这时候stride就不是0了,而是设成一个顶点总字节数,比如24字节,然后用offset来区分位置(从0开始)和颜色(从第8字节开始)。不过这次实验用的是分开两个Buffer的方式,比较简单。
### 对照实验
把颜色的size从4改成3,步长就从16字节变成12字节,就会读错位。
### 实际现象
把gl.vertexAttribPointer(vColor, 4, ...) 里的4改成3,一刷新——颜色全乱了不是变淡,是完全不对的颜色,渐变也没了,整个三角形变得花里胡哨的,但是改回4,立刻恢复正常的红绿蓝渐变
### 我的解释
Buffer里的颜色数据是这样排列的,每个顶点4个float,就是RGBA四个通道:
顶点0:红0、绿0、蓝0、透明度0
顶点1:红1、绿1、蓝1、透明度1
顶点2:红2、绿2、蓝2、透明度2
每个float占4字节,所以每个顶点占16字节。
size=4的时候,是正常情况:
顶点0:从第0字节开始读4个float,也就是16字节,正好是顶点0的RGBA
步长16字节,下一个顶点从第16字节开始
顶点1:从第16字节开始读4个float,正好是顶点1的RGBA
size=3的时候:
顶点0:从第0字节开始读3个float,就是12字节,读到RGB,透明度被跳过了
步长变成12字节,下一个顶点从第12字节开始
顶点1:从第12字节开始读3个float——这时候读到的是顶点0的透明度、顶点1的红、顶点1的绿
顶点2就更不对了,偏移得更多
所以数据整体错位了,越往后错得越离谱,颜色当然就完全乱了。
### 最终结论
AI的解释是对的。size参数不只是"读几个分量"这么简单,它还决定了步长,当stride=0时,步长错了就会导致所有后续顶点的数据都读错位置。
任务1 思考与记录
1. Float32Array 中连续两个数为什么可以表示一个二维顶点?
gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0) 中的第二个参数 size=2,告诉GPU每个顶点从Buffer中读取2个float。GPU按此规则遍历:第0-1个float是顶点0的(x,y),第2-3个是顶点1的(x,y),以此类推。二维顶点只需x、y两个分量,因此连续两个float恰好表示一个二维顶点。
2. Shader 中声明的是:
glsl
in vec4 vPosition;
而 JavaScript 中:
JavaScript
gl.vertexAttribPointer(
vPosition,
2,
...
);
为什么仍然可以正常绘制?
WebGL会自动补全缺失的顶点属性分量:缺失的x/y/z补0,w补1。因此传入的(x, y)会被自动补齐为 (x, y, 0.0, 1.0)。齐次坐标中w=1表示"点"(w=0表示向量),正好符合二维点的标准表示,因此可以正常绘制。
3. gl.drawArrays(gl.TRIANGLES, 0, 3) 中最后的3表示什么?
表示顶点数量,不是float的数量,也不是三角形的数量。在TRIANGLES模式下,每3个顶点组成一个三角形,3个顶点对应1个三角形。
容易混淆的是:Float32Array中有6个float(3个顶点×2个分量),但drawArrays的第三个参数永远按顶点计数,不按float计数。
4. 修改 JavaScript 中的顶点数据后,为什么要重新执行 gl.bufferData() 才能改变 GPU 中的数据?
CPU和GPU拥有独立的内存空间。JS中的Float32Array位于CPU内存,gl.bufferData() 的作用是将CPU数据复制到GPU的Buffer对象中。复制完成后,两边内存相互独立——修改CPU端的数组不会自动同步到GPU。因此必须再次调用 gl.bufferData() 重新上传,GPU端数据才会更新。
任务2 必须回答
对照实验: 将 gl.TRIANGLE_STRIP 依次修改为 gl.POINTS、gl.LINE_STRIP、gl.TRIANGLES,每次只修改图元类型,不改变顶点数据。
TRIANGLE_STRIP:完整填充的四边形(2个三角形拼接而成)
POINTS:4个独立的小点
LINE_STRIP:3条线段组成的折线,首尾不闭合(缺一条边)
TRIANGLES: 仅绘制1个三角形(前3个顶点),第4个顶点被忽略
1. 相同顶点数据为什么会得到不同图形?
顶点数据本身只是坐标序列,不具有形状意义。gl.drawArrays() 的第一个参数(图元模式)决定了GPU对顶点序列的组装规则:POINTS按点处理、LINES按线段处理、TRIANGLES按三角形处理。同一组顶点,组装规则不同,生成的图形就不同。
2. TRIANGLE_STRIP 中顶点顺序为什么重要?
TRIANGLE_STRIP的生成规则是:从第3个顶点开始,每新增一个顶点,就与它前面两个顶点组成一个新三角形,且相邻三角形共享一条边。顶点顺序错误会导致三角形翻转、出现空洞或形状扭曲。正确的带状顺序(如左上,左下,右上,右下)才能保证两个三角形正确拼接成规整的四边形。
3. 4个顶点通过 TRIANGLE_STRIP 实际组成了几个三角形?
2个。通用公式:n个顶点组成n-2个三角形。4个顶点即 4-2=2 个三角形(顶点0-1-2 和 顶点1-2-3),它们共享边1-2。
任务3
1. 为什么改变uniform后不需要重新编译Shader?
uniform是Shader程序的运行时参数,不是程序代码本身。编译Shader是将GLSL源码编译为GPU可执行程序,而uniform的值是程序运行时传入的数据——类似函数编译一次后,可以传入不同参数值多次调用,不需要重新编译。gl.uniform4f() 仅是修改参数值,不改变Shader程序结构。
2. 如果只设置一次 uColor,随后绘制两个对象,会发生什么?
两个对象颜色相同。因为WebGL是状态机:uniform值一旦设置就持续保持,后续所有绘制调用都使用该值,直到被显式修改。只设置一次uColor后绘制两个对象,第二个对象会沿用第一个对象设置的颜色。
3. 为什么绑定新的 Buffer 后需要再次正确设置顶点属性读取方式?
gl.vertexAttribPointer() 建立的是"当前绑定的ARRAY_BUFFER"与顶点属性之间的映射关系。也就是说,调用该函数时哪个Buffer绑定在ARRAY_BUFFER目标上,属性就与哪个Buffer关联。更换Buffer后若不重新调用 vertexAttribPointer(),顶点属性仍指向原来的Buffer,读取的数据就是错误的。
4. "当前 WebGL 状态会影响后续绘制"在本任务中体现在哪里?
uniform状态:uColor设置后持续生效,绘制第二个对象前必须重新设置
Buffer绑定状态:bindBuffer后ARRAY_BUFFER目标一直指向该Buffer,后续操作都针对它
顶点属性状态:enableVertexAttribArray后属性保持启用
当前Program状态:useProgram后所有操作基于该Shader程序
WebGL状态继承特:忘记修改uColor会导致两个图形同色
任务4
对照实验(AI协作点C): 将gl.vertexAttribPointer(vColor, 4, gl.FLOAT, false, 0, 0) 中的4临时改为3。
修改内容: 颜色属性的size由4改为3
预测: 缺少alpha通道,颜色可能变淡
实际结果: 颜色完全错乱,渐变效果消失,三角形呈现异常混合色
根因分析: 当stride=0时,GPU自动按 size × sizeof(float) 计算步长。size=3时步长为12字节,但Buffer中每顶点实际占16字节(RGBA四个float)。因此GPU读取第二个顶点时起始位置错位——将第一个顶点的alpha值当作了第二个顶点的红色分量。数据整体偏移,越往后错位越严重,导致颜色完全混乱。恢复size=4后正常。
1. 为什么位置 Buffer 每个顶点读取2个 float,而颜色 Buffer 每个顶点读取4个 float?
两个属性的数据结构不同:位置是二维坐标(x, y),含2个分量;颜色是RGBA四通道(红、绿、蓝、透明度),含4个分量。vertexAttribPointer 的size参数指定了该属性每顶点的分量数,GPU据此决定每个顶点读取多少数据。
2. 两个 Buffer 如何分别对应到 vPosition 和 vColor?
通过两步建立对应关系:
gl.getAttribLocation(program, 属性名) 获取Shader中该属性的索引位置(类似门牌号)
绑定对应Buffer后,调用 gl.vertexAttribPointer(属性索引, size, ...),将当前绑定的ARRAY_BUFFER与该属性索引关联
即:先绑定位置Buffer,用vPosition索引设置vertexAttribPointer,位置Buffer对应vPosition;再绑定颜色Buffer,用vColor索引设置,颜色Buffer对应vColor,两个属性各自独立。
3. 三角形内部为什么出现连续渐变色,而不是只有红、绿、蓝三个区域?
光栅化阶段会对顶点属性进行线性插值。三角形被离散为片元时,每个片元的颜色根据其在三角形中的重心位置,对三个顶点的颜色进行加权平均——距离哪个顶点越近,该顶点颜色的权重越大。因此靠近红色顶点处偏红,靠近绿色顶点处偏绿,中间区域平滑过渡,形成连续渐变。
4. 顶点颜色插值主要发生在哪个阶段?Fragment Shader 得到的是顶点原始颜色还是插值后的颜色?
插值发生在光栅化阶段,位于图元组装与片元着色器之间。Fragment Shader得到的是插值后的颜色,而非顶点原始颜色。
完整流程:Vertex Shader输出每个顶点的颜色,图元组装,光栅化(三角形离散为片元 + 属性插值),Fragment Shader(每个片元执行一次,输入已为插值后的颜色值)。插值由GPU硬件自动完成,无需Fragment Shader手动计算。
任务5
我修改了什么:将任务2的TRIANGLE_STRIP改为LINE_LOOPPOINTS,每次只改draw mode一个变量
预测结果:LINE_LOOP是闭合四边形边框(4条边)
实际结果:四条边完整闭合
原因解释:先按折线连接所有顶点,再将最后一个顶点与第一个顶点相连形成闭环。4顶点=4条线段
我修改了什么:将任务2的TRIANGLE_STRIP改为LINE_STRIP,每次只改draw mode一个变量
预测结果:LINE_STRIP是3条线段的折线(缺一边)
实际结果: 仅3条线段,首尾不连
原因解释:n顶点连n-1条线段。4顶点=3条线(0-1, 1-2, 2-3)
我修改了什么:将任务2的TRIANGLE_STRIP改为POINTS,每次只改draw mode一个变量
预测结果:POINTS是4个独立小点
实际结果:4个小点
原因解释:每个顶点独立绘制为一个点,不做连接
我修改了什么:将任务2的TRIANGLE_STRIP改为TRIANGLES,每次只改draw mode一个变量
预测结果:TRIANGLES仅1个三角形,第4顶点被忽略
实际结果:仅前3顶点组成1个三角形
原因解释:每3顶点一组。4顶点仅够1组,剩余1顶点被丢弃