2.8 颜色和混合 (Color and Blending) WebGL 核心概念:2.8 颜色和混合 (Color and Blending) 详解 在 WebGL 图形渲染管线中,颜色和混合是至关重要的环节。它们决定了最终呈现在屏幕上的像素色彩,以及不同图形元素之间如何相互作用。理解颜色和混合的概念,能够帮助我们创建更丰富、更具表现力的视觉效果,例如透明度、半透明效果、粒子效果、光照效果等等。 2.8.1 WebGL 中的颜色表示 在 WebGL 中,颜色通常使用 RGBA (Red, Green, Blue, Alpha) 模式表示。每个颜色分量(红、绿、蓝、透明度)都使用 0.0 到 1.0 范围内的浮点数表示,这被称为 归一化颜色值。
在 WebGL 图形渲染管线中,颜色和混合是至关重要的环节。它们决定了最终呈现在屏幕上的像素色彩,以及不同图形元素之间如何相互作用。理解颜色和混合的概念,能够帮助我们创建更丰富、更具表现力的视觉效果,例如透明度、半透明效果、粒子效果、光照效果等等。
在 WebGL 中,颜色通常使用 RGBA (Red, Green, Blue, Alpha) 模式表示。每个颜色分量(红、绿、蓝、透明度)都使用 0.0 到 1.0 范围内的浮点数表示,这被称为 归一化颜色值。
红色 (Red, R): 表示颜色中红色的强度,0.0 表示没有红色,1.0 表示最大红色强度。
绿色 (Green, G): 表示颜色中绿色的强度,0.0 表示没有绿色,1.0 表示最大绿色强度。
蓝色 (Blue, B): 表示颜色中蓝色的强度,0.0 表示没有蓝色,1.0 表示最大蓝色强度。
透明度 (Alpha, A): 表示颜色的不透明度,0.0 表示完全透明,1.0 表示完全不透明。介于 0.0 和 1.0 之间的值表示半透明。
例如:
vec4(1.0, 0.0, 0.0, 1.0) 表示纯红色,完全不透明。
vec4(0.0, 1.0, 0.0, 0.5) 表示半透明的绿色。
vec4(0.5, 0.5, 0.5, 1.0) 表示灰色,完全不透明。
vec4(1.0, 1.0, 1.0, 0.0) 表示完全透明的白色(实际上不可见)。
在 WebGL 程序中,颜色值通常以 vec4 类型(四维向量)存储和传递,尤其是在着色器程序中。
数据类型和精度:
WebGL 着色器语言 (GLSL ES) 中,颜色分量通常使用 float 类型,即单精度浮点数。在顶点着色器和片元着色器之间传递颜色数据时,可以使用 varying vec4 类型的变量。
颜色来源:
WebGL 中的颜色可以来自多个来源:
顶点属性 (Vertex Attributes): 可以为每个顶点指定颜色属性,例如使用 gl.vertexAttribPointer() 设置顶点颜色数据。这通常用于实现顶点颜色渐变等效果。
Uniform 变量 (Uniform Variables): 可以使用 gl.uniform4f() 或 gl.uniform4fv() 设置 uniform 变量,在着色器中作为常量颜色值使用。这适用于整个绘制调用颜色一致的情况。
纹理 (Textures): 纹理可以包含像素颜色信息,通过纹理采样可以在片元着色器中获取纹理颜色。
着色器计算 (Shader Calculation): 颜色也可以在顶点着色器或片元着色器中通过数学计算动态生成。例如,根据光照模型、材质属性等计算颜色。
什么是混合?
WebGL 混合是一种将 源颜色 (Source Color) 和 目标颜色 (Destination Color) 组合在一起的技术。
源颜色 (Source Color): 指即将被绘制的片元颜色,通常由片元着色器输出。
目标颜色 (Destination Color): 指帧缓冲区 (Framebuffer) 中已经存在的颜色,即当前像素位置的颜色。
混合的目标是将源颜色和目标颜色按照一定的规则进行混合,生成新的颜色,并将这个新颜色写入帧缓冲区,最终显示在屏幕上。
为什么需要混合?
混合的主要用途包括:
实现透明和半透明效果: 通过混合,可以使物体看起来是透明或半透明的,允许背景颜色透过前景物体显示出来。例如,模拟玻璃、水、烟雾等效果。
实现特效: 混合可以用于创建各种视觉特效,例如粒子效果、光晕效果、发光效果、火焰效果等。
抗锯齿 (Anti-aliasing): 在某些抗锯齿技术中,混合也扮演着重要的角色。
混合过程详解:
WebGL 的混合过程可以概括为以下步骤:
获取源颜色 (Source Color): 片元着色器计算并输出当前片元的颜色,作为源颜色。
获取目标颜色 (Destination Color): 从帧缓冲区读取当前片元位置已有的颜色,作为目标颜色。
计算混合因子 (Blend Factors): 根据设置的混合函数,计算出源混合因子 (Source Factor, S) 和目标混合因子 (Destination Factor, D)。
执行混合操作 (Blend Operation): 根据设置的混合方程,将源颜色、目标颜色以及混合因子进行数学运算,得到混合后的颜色。
写入帧缓冲区 (Framebuffer): 将混合后的颜色写入帧缓冲区,替换原来的目标颜色。
可以用 Mermaid 图表来表示混合过程:
关键配置:混合函数和混合方程
WebGL 提供了灵活的配置选项来控制混合过程,主要通过以下两个函数进行设置:
gl.blendFunc(sfactor, dfactor) (混合函数): 设置源混合因子 (sfactor) 和目标混合因子 (dfactor)。
gl.blendEquation(mode) (混合方程): 设置混合操作 (mode),即如何将源颜色、目标颜色以及混合因子组合起来。
2.8.2.1 混合函数 (gl.blendFunc)
gl.blendFunc(sfactor, dfactor) 函数接受两个参数:
sfactor (源混合因子): 指定如何计算源颜色的混合因子。
dfactor (目标混合因子): 指定如何计算目标颜色的混合因子。
WebGL 提供了多种预定义的混合因子常量,常用的包括:
| 常量名称 | 源因子 (S) 的计算方式 | 目标因子 (D) 的计算方式 | 描述 |
|---|---|---|---|
gl.ZERO |
(0, 0, 0, 0) |
(0, 0, 0, 0) |
零,颜色分量乘以 0 |
gl.ONE |
(1, 1, 1, 1) |
(1, 1, 1, 1) |
一,颜色分量乘以 1 |
gl.SRC_COLOR |
(Rs, Gs, Bs, As) (源颜色) |
(Rd, Gd, Bd, Ad) (目标颜色) |
源颜色 |
gl.ONE_MINUS_SRC_COLOR |
(1-Rs, 1-Gs, 1-Bs, 1-As) |
(1-Rd, 1-Gd, 1-Bd, 1-Ad) |
一减去源颜色 |
gl.DST_COLOR |
(Rd, Gd, Bd, Ad) (目标颜色) |
(Rd, Gd, Bd, Ad) (目标颜色) |
目标颜色 |
gl.ONE_MINUS_DST_COLOR |
(1-Rd, 1-Gd, 1-Bd, 1-Ad) |
(1-Rd, 1-Gd, 1-Bd, 1-Ad) |
一减去目标颜色 |
gl.SRC_ALPHA |
(As, As, As, As) (源颜色的 Alpha 值) |
(Ad, Ad, Ad, Ad) (目标颜色的 Alpha 值) |
源颜色的 Alpha 值 |
gl.ONE_MINUS_SRC_ALPHA |
(1-As, 1-As, 1-As, 1-As) |
(1-Ad, 1-Ad, 1-Ad, 1-Ad) |
一减去源颜色的 Alpha 值 |
gl.DST_ALPHA |
(Ad, Ad, Ad, Ad) (目标颜色的 Alpha 值) |
(Ad, Ad, Ad, Ad) (目标颜色的 Alpha 值) |
目标颜色的 Alpha 值 |
gl.ONE_MINUS_DST_ALPHA |
(1-Ad, 1-Ad, 1-Ad, 1-Ad) |
(1-Ad, 1-Ad, 1-Ad, 1-Ad) |
一减去目标颜色的 Alpha 值 |
gl.SRC_ALPHA_SATURATE |
(min(As, 1-Ad), min(As, 1-Ad), min(As, 1-Ad), 1) (源 Alpha 饱和) |
仅能作为源因子使用,目标因子通常为 gl.ONE 或 gl.ZERO |
源 Alpha 饱和因子,常用于粒子效果等,确保颜色不会过度饱和。 |
其中,(Rs, Gs, Bs, As) 代表源颜色 (Source Color) 的 RGBA 分量,(Rd, Gd, Bd, Ad) 代表目标颜色 (Destination Color) 的 RGBA 分量。
常用混合函数组合:
Alpha 混合 (Alpha Blending, 透明度混合): 这是最常用的混合模式,用于实现透明和半透明效果。
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
源因子: gl.SRC_ALPHA (源颜色的 Alpha 值)
目标因子: gl.ONE_MINUS_SRC_ALPHA (一减去源颜色的 Alpha 值)
混合公式: 最终颜色 = 源颜色 * 源Alpha + 目标颜色 * (1 - 源Alpha)
这种混合模式的效果是,源颜色的透明度越高,源颜色在最终颜色中的贡献就越小,目标颜色(背景颜色)的贡献就越大。
加法混合 (Additive Blending): 用于实现发光、光晕、火焰等效果。
gl.blendFunc(gl.ONE, gl.ONE);
源因子: gl.ONE (1)
目标因子: gl.ONE (1)
混合公式: 最终颜色 = 源颜色 * 1 + 目标颜色 * 1 = 源颜色 + 目标颜色
加法混合会将源颜色和目标颜色直接相加,颜色值会叠加,从而产生更亮的效果。
乘法混合 (Multiplicative Blending): 用于实现阴影、变暗等效果。
gl.blendFunc(gl.DST_COLOR, gl.ZERO); // 或者 gl.blendFunc(gl.ZERO, gl.SRC_COLOR);
源因子: gl.DST_COLOR (目标颜色)
目标因子: gl.ZERO (0)
混合公式: 最终颜色 = 源颜色 * 目标颜色 + 目标颜色 * 0 = 源颜色 * 目标颜色
乘法混合会将源颜色和目标颜色相乘,颜色值会变暗。
2.8.2.2 混合方程 (gl.blendEquation)
gl.blendEquation(mode) 函数用于设置混合操作,即如何将源颜色、目标颜色以及混合因子组合起来。
mode 参数可以是以下常量之一:
| 常量名称 | 混合方程 | 描述 |
|---|---|---|
gl.FUNC_ADD |
最终颜色 = 源颜色 * SrcFactor + 目标颜色 * DstFactor |
加法混合 (默认值) |
gl.FUNC_SUBTRACT |
最终颜色 = 源颜色 * SrcFactor - 目标颜色 * DstFactor |
减法混合 (源颜色减去目标颜色) |
gl.FUNC_REVERSE_SUBTRACT |
最终颜色 = 目标颜色 * DstFactor - 源颜色 * SrcFactor |
反向减法混合 (目标颜色减去源颜色) |
gl.MIN |
最终颜色 = min(源颜色, 目标颜色) |
取最小值,选择源颜色和目标颜色中较小的颜色分量 |
gl.MAX |
最终颜色 = max(源颜色, 目标颜色) |
取最大值,选择源颜色和目标颜色中较大的颜色分量 |
默认混合方程是 gl.FUNC_ADD (加法混合)。 在大多数情况下,我们使用加法混合就足够了,例如实现透明度混合、加法混合特效等。减法混合、反向减法混合、MIN 和 MAX 混合方程在一些特殊效果中可能会用到,但相对较少。
启用和禁用混合
混合默认是禁用的。要启用混合,需要调用 gl.enable(gl.BLEND)。 要禁用混合,调用 gl.disable(gl.BLEND)。
// 启用混合 gl.enable(gl.BLEND); // 设置混合函数 (例如 Alpha 混合) gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); // 设置混合方程 (默认是 gl.FUNC_ADD,通常不需要显式设置) // gl.blendEquation(gl.FUNC_ADD); // ... 执行绘制操作 ... // 禁用混合 (如果需要恢复默认行为) gl.disable(gl.BLEND);
混合顺序 (Draw Order) 的重要性
当启用混合时,绘制顺序非常重要,特别是对于 透明或半透明物体。
不透明物体: 不透明物体可以按照任意顺序绘制,因为它们会完全遮挡后面的物体。
透明/半透明物体: 透明或半透明物体必须 从后往前 绘制 (back-to-front rendering)。
为什么需要从后往前绘制透明物体?
考虑 Alpha 混合的公式: 最终颜色 = 源颜色 * 源Alpha + 目标颜色 * (1 - 源Alpha)
如果先绘制前面的透明物体,再绘制后面的透明物体,那么后面的物体的混合就会基于前面物体已经混合过的颜色,导致混合结果不正确。
只有从后往前绘制,才能保证每个透明物体的混合都基于正确的背景颜色(即之前绘制的物体或背景颜色)。
深度测试与混合:
通常情况下,我们会同时启用深度测试 (gl.enable(gl.DEPTH_TEST)) 和混合 (gl.enable(gl.BLEND))。
深度测试 用于解决不透明物体的遮挡关系,确保前面的不透明物体遮挡后面的不透明物体。
混合 用于处理透明和半透明物体的颜色混合。
对于透明物体,虽然需要从后往前绘制以保证正确的混合顺序,但仍然需要启用深度测试,以确保透明物体不会遮挡前面的不透明物体,并且透明物体自身的前后部分也能够正确遮挡。
总结混合流程:
下面通过代码示例演示如何在 WebGL 中使用颜色和混合。
示例 1: 绘制两个重叠的彩色矩形 (不混合)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>WebGL Color Example (No Blending)</title> </head> <body> <canvas id="webglCanvas" width="400" height="300"></canvas> <script> const canvas = document.getElementById('webglCanvas'); const gl = canvas.getContext('webgl'); if (!gl) { alert('WebGL not supported'); return; } // 顶点着色器 const vertexShaderSource = ` attribute vec4 a_position; attribute vec4 a_color; varying vec4 v_color; void main() { gl_Position = a_position; v_color = a_color; } `; // 片元着色器 const fragmentShaderSource = ` precision mediump float; varying vec4 v_color; void main() { gl_FragColor = v_color; } `; // 创建着色器程序 const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexShaderSource); gl.compileShader(vertexShader); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentShaderSource); gl.compileShader(fragmentShader); const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program); // 获取属性位置 const positionAttributeLocation = gl.getAttribLocation(program, 'a_position'); const colorAttributeLocation = gl.getAttribLocation(program, 'a_color'); // 矩形顶点数据和颜色数据 (两个矩形,颜色不同) const positions = new Float32Array([ // 第一个矩形 (红色) -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, // 第二个矩形 (绿色,稍微偏移) -0.2, -0.2, 0.8, -0.2, 0.8, 0.8, -0.2, 0.8, ]); const colors = new Float32Array([ // 第一个矩形 (红色) 1.0, 0.0, 0.0, 1.0, // Red 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, // 第二个矩形 (绿色) 0.0, 1.0, 0.0, 1.0, // Green 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, ]); // 创建顶点位置缓冲区 const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); // 创建顶点颜色缓冲区 const colorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); // 设置顶点属性指针 (位置) gl.enableVertexAttribArray(positionAttributeLocation); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0); // 设置顶点属性指针 (颜色) gl.enableVertexAttribArray(colorAttributeLocation); gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.vertexAttribPointer(colorAttributeLocation, 4, gl.FLOAT, false, 0, 0); // 清空画布 gl.clearColor(0.0, 0.0, 0.0, 1.0); // Black background gl.clear(gl.COLOR_BUFFER_BIT); // 绘制第一个矩形 (红色) gl.drawArrays(gl.TRIANGLE_FAN, 0, 4); // 绘制第二个矩形 (绿色) gl.drawArrays(gl.TRIANGLE_FAN, 4, 4); </script> </body> </html>
在这个示例中,我们绘制了两个矩形,一个红色,一个绿色,它们在屏幕上重叠。由于 没有启用混合,后绘制的绿色矩形会完全覆盖先绘制的红色矩形在重叠区域的颜色。
示例 2: 绘制两个重叠的半透明矩形 (Alpha 混合)
修改上面的代码,启用 Alpha 混合,并将矩形的 Alpha 值设置为 0.5 (半透明)。
// ... (之前的代码,顶点着色器、片元着色器、程序创建等不变) ... // 矩形顶点数据和颜色数据 (两个矩形,半透明颜色) const colors = new Float32Array([ // 第一个矩形 (半透明红色) 1.0, 0.0, 0.0, 0.5, // Semi-transparent Red 1.0, 0.0, 0.0, 0.5, 1.0, 0.0, 0.0, 0.5, 1.0, 0.0, 0.0, 0.5, // 第二个矩形 (半透明绿色) 0.0, 1.0, 0.0, 0.5, // Semi-transparent Green 0.0, 1.0, 0.0, 0.5, 0.0, 1.0, 0.0, 0.5, 0.0, 1.0, 0.0, 0.5, ]); // ... (创建缓冲区、设置顶点属性指针代码不变) ... // 清空画布 gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); // **启用混合** gl.enable(gl.BLEND); // **设置 Alpha 混合函数** gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); // 绘制第一个矩形 (半透明红色) gl.drawArrays(gl.TRIANGLE_FAN, 0, 4); // 绘制第二个矩形 (半透明绿色) gl.drawArrays(gl.TRIANGLE_FAN, 4, 4); // **禁用混合 (可选,如果后续绘制不需要混合)** // gl.disable(gl.BLEND);
在这个示例中,我们修改了颜色数据的 Alpha 值,并将 gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 添加到绘制代码中。 现在,重叠区域会显示红色和绿色的混合效果,呈现出一种黄绿色。这是因为使用了 Alpha 混合,半透明的绿色矩形与半透明的红色矩形进行了混合。
示例 3: 加法混合 (Additive Blending) 示例
将 gl.blendFunc 修改为加法混合模式:
// ... (之前的代码,顶点着色器、片元着色器、程序创建、顶点数据等不变) ... // ... (创建缓冲区、设置顶点属性指针代码不变) ... // 清空画布 gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); // 启用混合 gl.enable(gl.BLEND); // **设置加法混合函数** gl.blendFunc(gl.ONE, gl.ONE); // 加法混合 // 绘制第一个矩形 (红色) gl.drawArrays(gl.TRIANGLE_FAN, 0, 4); // 绘制第二个矩形 (绿色) gl.drawArrays(gl.TRIANGLE_FAN, 4, 4); // 禁用混合 (可选) // gl.disable(gl.BLEND);
在这个示例中,我们使用了 gl.blendFunc(gl.ONE, gl.ONE) 设置为加法混合。 重叠区域的颜色会变成亮黄色,因为红色和绿色相加,颜色值叠加,变得更亮。加法混合常用于创建发光效果。
颜色和混合是 WebGL 图形渲染中不可或缺的部分。理解颜色表示方法和混合原理,并灵活运用 gl.blendFunc() 和 gl.blendEquation() 函数,可以实现各种复杂的视觉效果。
颜色表示: WebGL 使用 RGBA 模式,颜色分量值范围为 0.0 到 1.0。
混合原理: 将源颜色和目标颜色按照混合函数和混合方程进行组合。
混合函数 (gl.blendFunc): 设置源混合因子和目标混合因子,决定源颜色和目标颜色在混合中的权重。
混合方程 (gl.blendEquation): 设置混合操作,决定如何组合源颜色、目标颜色和混合因子。
常用混合模式: Alpha 混合 (透明度)、加法混合 (发光)、乘法混合 (阴影) 等。
混合顺序: 透明物体需要从后往前绘制。
启用/禁用混合: 使用 gl.enable(gl.BLEND) 和 gl.disable(gl.BLEND) 控制混合的开关。
通过掌握颜色和混合技术,可以为 WebGL 应用增添更多视觉层次和表现力,创造出更生动、更吸引人的图形效果。 希望本章的详细讲解和代码示例能够帮助你深入理解 WebGL 中的颜色和混合概念,并在实际项目中灵活应用。