WebGL2中如何实现雨效与环形点群绘制对象共享同一顶点着色器?
两种可落地的合并实现方案
方案1:单着色器分支路由(一次绘制调用完成)
核心逻辑是利用gl_VertexID做顶点区间分流,把两类顶点的生成逻辑合并到同一个顶点着色器内,全程不需要外部顶点缓冲区传入数据。
- 对原两个着色器的uniform做去重重命名,避免同名变量冲突:将原雨效的
numVerts改为rainCount,原环形的numVerts改为circleCount,合并后所有uniform为:rainCount、circleCount、time、resolution - 着色器内通过顶点ID判断当前属于哪类对象,执行对应位置、点大小的计算逻辑
合并后的完整顶点着色器代码如下:
#version 300 es uniform int rainCount; uniform int circleCount; uniform float time; uniform vec2 resolution; #define PI radians(180.0) float hash(float p) { vec2 p2 = fract(vec2(p * 5.3983, p * 5.4427)); p2 += dot(p2.yx, p2.xy + vec2(21.5351, 14.3137)); return fract(p2.x * p2.y * 95.4337); } void main() { int vid = gl_VertexID; if (vid < rainCount) { // 雨效生成逻辑 float u = float(vid) / float(rainCount); float off = floor(time + u) / 1000.0; float x = hash(u + off) * 2.0 - 1.0; float y = fract(time + u) * -2.0 + 1.0; gl_Position = vec4(x, y, 0, 1); gl_PointSize = 2.0; } else { // 环形点生成逻辑 int circleVid = vid - rainCount; float u = float(circleVid) / float(circleCount); float angle = u * PI * 2.0; float radius = 0.8; vec2 pos = vec2(cos(angle), sin(angle)) * radius; float aspect = resolution.y / resolution.x; vec2 scale = vec2(aspect, 1); gl_Position = vec4(pos * scale, 0, 1); gl_PointSize = 5.0; } }
JS端调用时只需发起1次绘制调用,总顶点数为rainCount + circleCount,不需要绑定任何顶点缓冲区:
// 单次绘制总顶点数为两类顶点之和 gl.drawArrays(gl.POINTS, 0, rainCount + circleCount);
方案2:同上下文独立程序连续绘制(零着色器改动)
如果不想修改原有两个独立着色器的代码,可直接在同一个WebGL2上下文内连续发起两次绘制,共享同一个画布帧缓冲,两类绘制的着色器逻辑完全隔离,不会出现gl_Position、gl_PointSize的逻辑冲突:
- 分别为雨效、环形图案编译两个独立的着色器程序,保留原有代码完全不变
- 每帧渲染流程:
- 激活雨效对应的着色器程序,传入
numVerts、time两个uniform值,执行gl.drawArrays(gl.POINTS, 0, 雨效顶点数) - 激活环形图案对应的着色器程序,传入
numVerts、resolution两个uniform值,执行gl.drawArrays(gl.POINTS, 0, 环形顶点数)
- 激活雨效对应的着色器程序,传入
- 该方案完全兼容原有“顶点全靠着色器内部gl_VertexID生成、无外部缓冲区传入”的模式,不需要调整原有生成逻辑。
- 可通过调整两次
drawArrays的调用顺序控制图层上下层级,比如将雨效绘制放在环形绘制之后,即可实现雨效在环形图案上方叠加的效果。
两种方案的选择依据:
- 追求低状态切换开销、单次绘制效率优先,选方案1
- 要最大程度保留原有独立着色器的可维护性、零改动成本优先,选方案2
内容的提问来源于stack exchange,提问作者AngryWeeb
相关产品推荐
相关产品推荐

