WebGL 从零开始:绘制你的第一个3D点

WebGL程序的两大核心组件

WebGL程序就像一台精密的机器,需要两个关键部件协同工作才能正常运行:

JavaScript程序 - 负责控制和数据处理

着色器程序 - 负责图形渲染

这两个部分缺一不可,就像汽车需要发动机和方向盘一样。

从最简单的红点开始

我们的第一个目标很明确:在屏幕中心绘制一个红色的点,大小为10像素。

顶点着色器:告诉GPU在哪里画点

glsl 复制代码
void main(){
    // 告诉GPU在裁剪坐标系原点画点
    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
    // 设置点的大小为10像素
    gl_PointSize = 10.0;
}

这里用到了GLSL语言的几个重要概念:

  • gl_Position是内置变量,用来设置顶点位置
  • gl_PointSize专门控制点的大小
  • vec4是包含4个浮点数的向量容器

片元着色器:告诉GPU用什么颜色画

glsl 复制代码
void main(){
    // 设置像素颜色为红色
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); 
}

注意颜色值的表示方法:

  • WebGL中颜色分量范围是0-1,不是0-255
  • 红色表示为(1.0, 0.0, 0.0, 1.0)
  • 对应CSS中的rgb(255, 0, 0)

完整的HTML结构

html 复制代码
<body>
    <!-- 顶点着色器源码 -->
    <script type="shader-source" id="vertexShader">
     void main(){
        gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
        gl_PointSize = 10.0;
    }
    </script>
    
    <!-- 片元着色器源码 -->
    <script type="shader-source" id="fragmentShader">
     void main(){
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); 
    }
    </script>
    
    <canvas id="canvas"></canvas>
</body>

JavaScript核心代码实现

第一步:获取WebGL绘图环境

javascript 复制代码
// 获取canvas元素
var canvas = document.querySelector('#canvas');
// 获取WebGL上下文(兼容处理)
var gl = canvas.getContext('webgl') || canvas.getContext("experimental-webgl");

第二步:创建和编译着色器

javascript 复制代码
// 获取顶点着色器源码
var vertexShaderSource = document.querySelector('#vertexShader').innerHTML;
// 创建顶点着色器对象
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
// 将源码分配给着色器对象
gl.shaderSource(vertexShader, vertexShaderSource);
// 编译顶点着色器程序
gl.compileShader(vertexShader);

// 片元着色器创建过程类似
var fragmentShaderSource = document.querySelector('#fragmentShader').innerHTML;
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);

第三步:创建和链接着色器程序

javascript 复制代码
// 创建着色器程序
var program = gl.createProgram();
// 将顶点着色器挂载到程序上
gl.attachShader(program, vertexShader); 
// 将片元着色器挂载到程序上
gl.attachShader(program, fragmentShader);
// 链接着色器程序
gl.linkProgram(program);
// 启用着色器程序
gl.useProgram(program);

第四步:执行绘制操作

javascript 复制代码
// 设置清空画布颜色为黑色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 用设置的颜色清空画布
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制点图元
gl.drawArrays(gl.POINTS, 0, 1);

让点动起来:交互式绘制

静态点不够有趣,让我们实现点击画布就在点击位置绘制彩色点的功能。

改进的着色器程序

glsl 复制代码
// 顶点着色器 - 接收外部数据
precision mediump float;
// 接收点在canvas坐标系上的坐标
attribute vec2 a_Position;
// 接收canvas的宽高尺寸
attribute vec2 a_Screen_Size;

void main(){
    // 将屏幕坐标转换为裁剪坐标
    vec2 position = (a_Position / a_Screen_Size) * 2.0 - 1.0; 
    position = position * vec2(1.0, -1.0);
    gl_Position = vec4(position, 0, 1);
    gl_PointSize = 10.0;
}

// 片元着色器 - 接收颜色数据
precision mediump float;
// 接收JavaScript传过来的颜色值
uniform vec4 u_Color;

void main(){
    // 将颜色值转换为WebGL格式
    vec4 color = u_Color / vec4(255, 255, 255, 1);
    gl_FragColor = color; 
}

交互式JavaScript实现

javascript 复制代码
// 获取着色器变量位置
var a_Position = gl.getAttribLocation(program, 'a_Position');
var a_Screen_Size = gl.getAttribLocation(program, 'a_Screen_Size');
var u_Color = gl.getUniformLocation(program, 'u_Color');

// 传递canvas尺寸信息
gl.vertexAttrib2f(a_Screen_Size, canvas.width, canvas.height);

// 存储点击位置的数组
var points = [];

// 绑定点击事件
canvas.addEventListener('click', e => {
    var x = e.pageX;
    var y = e.pageY;
    var color = {r: Math.random()*255, g: Math.random()*255, b: Math.random()*255, a: 255};
    points.push({ x: x, y: y, color: color });
    
    // 清空画布并重新绘制所有点
    gl.clearColor(0, 0, 0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    
    for (let i = 0; i < points.length; i++) {
        // 传递颜色数据
        gl.uniform4f(u_Color, points[i].color.r, points[i].color.g, points[i].color.b, points[i].color.a);
        // 传递位置数据
        gl.vertexAttrib2f(a_Position, points[i].x, points[i].y);
        // 绘制点
        gl.drawArrays(gl.POINTS, 0, 1);
    }
});

核心知识点总结

GLSL语言基础

  • attribute变量:只能在顶点着色器中使用,接收顶点数据
  • uniform变量:可在顶点和片元着色器中使用,接收全局数据
  • varying变量:在顶点着色器和片元着色器间传递插值数据
  • 向量运算:vec2、vec3、vec4等容器类型的运算规则

WebGL API核心方法

  • createShader():创建着色器对象
  • shaderSource():提供着色器源码
  • compileShader():编译着色器
  • createProgram():创建着色器程序
  • attachShader():绑定着色器到程序
  • linkProgram():链接着色器程序
  • useProgram():启用着色器程序
  • drawArrays():执行绘制操作

坐标系转换

从canvas坐标系到裁剪坐标系的转换公式:

ini 复制代码
position = (canvas_position / canvas_size) * 2.0 - 1.0

这个转换将浏览器坐标映射到WebGL的标准化设备坐标系(NDC),其中x、y坐标范围都是[-1, 1]。

性能优化提示

直接使用gl.vertexAttrib2f逐个传递数据效率较低。后续章节我们会学习使用缓冲区(Buffer)来批量传递顶点数据,这能显著提升渲染性能。

现在你已经掌握了WebGL的基础绘制技能,准备好迎接更复杂的三角形绘制挑战了吗?

相关推荐
mCell5 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell6 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭6 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
少云清6 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
银烛木7 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_607076607 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声7 小时前
CSS3 图片模糊处理
前端·css·css3
IT、木易7 小时前
css3 backdrop-filter 在移动端 Safari 上导致渲染性能急剧下降的优化方案有哪些?
前端·css3·safari
0思必得07 小时前
[Web自动化] Selenium无头模式
前端·爬虫·selenium·自动化·web自动化
anOnion7 小时前
构建无障碍组件之Dialog Pattern
前端·html·交互设计