个人简介
👀个人主页: 前端杂货铺
🙋♂️学习方向: 主攻前端方向,正逐渐往全干发展
📃个人状态: 研发工程师,现效力于中国工业软件事业
🚀人生格言: 积跬步至千里,积小流成江海
🥇推荐学习:🍍前端面试宝典 🍉Vue2 🍋Vue3 🍓Vue2/3项目实战 🥝Node.js🍒Three.js🍖数据结构与算法体系教程🌕个人推广:每篇文章最下方都有加入方式,旨在交流学习&资源分享,快加入进来吧
内容 | 参考链接 |
---|---|
WebGL+Three.js入门与实战(一) | 给画布换颜色、绘制一个点、三维坐标系 |
WebGL+Three.js入门与实战(二) | 绘制水平移动的点、通过鼠标控制绘制 |
文章目录
- 前言
- 一、绘制一个水平移动的点(attribute)
- 二、通过鼠标控制绘制
- 1、鼠标点击绘制点
- 2、鼠标移动绘制点
- 3、模拟画笔
- 三、绘制不同颜色的点(uniform)
- 总结
前言
大家好,这里是前端杂货铺。
上一篇文章,我们学习了如何给画布换颜色、如何绘制一个点并且了解了三维坐标系…
接下来,继续我们 WebGL 内容的学习!
鼠标绘制的三种效果
绘制不同的颜色
一、绘制一个水平移动的点(attribute)
我们在着色器源程序中声明 attribute 变量,js 可以给这个变量传值,再绘制出来,从而就可以实现动态修改点的位置。
以下是 声明 attribute 变量,需要注意的是,attribute 只能在顶点着色器中使用,不能在片元着色器中使用。
// attribute: 存储限定符; vec4: 类型; aPosition: 变量名;attribute vec4 aPosition;
以下是 获取 attribute 变量,需要注意的是 获取 attribute 变量需要在 initShader 函数之后,因为会用到 program 这个程序对象。获取之后返回变量的存储地址。
// program: 程序对象; 'aPosition': 指定想要获取存储地址的 attribute 变量的名称const aPositon = gl.getAttribLocation(program, 'aPosition');
以下是 给 attribute 变量赋值,可以传递 1/2/3 个分量的值,没有传递的值为 0.0。
// location: 指定 attribute 变量的存储位置; v0, v1, v2, v3: 传入的分量值; gl.vertexAttrib1f(location, v0);gl.vertexAttrib2f(location, v0, v1);gl.vertexAttrib3f(location, v0, v1, v2);gl.vertexAttrib4f(location, v0, v1, v2, v3);
绘制流程如下:
基于以上知识点,我们使用 attribute 变量绘制一个水平移动的点
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./index.js"></script></head><body> <canvas id="canvas" width="400" height="400" style="background: gray;"> 此浏览器不支持canvas </canvas> <script> const ctx = document.getElementById('canvas'); const gl = ctx.getContext('webgl'); // 着色器 // 创建着色器源码 // 顶点着色器 const VERTEX_SHADER_SOURCE = ` // 存储限定符 类型 变量名 分号 (注: attribute 只传递顶点数据) attribute vec4 aPosition; void main() { gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0) gl_PointSize = 30.0; } `; // 片元着色器 const FRAGMENT_SHADER_SOURCE = ` void main() { // r g b a gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } ` const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE); // 获取 attribute 变量,返回变量的存储地址 const aPosition = gl.getAttribLocation(program, 'aPosition'); let x = 0; setInterval(() => { x += 0.1; if (x > 1.0) { x = 0; } // 给 attribute 变量赋值 gl.vertexAttrib1f(aPosition, x); // 执行绘制 gl.drawArrays(gl.POINTS, 0, 1); }, 200) </script></body></html>
attribute绘制一个水平移动的点
二、通过鼠标控制绘制
1、鼠标点击绘制点
接下来,我们通过鼠标来控制在画布上绘制点。
- 屏幕坐标通过
event.clickX
和event.clickY
来获取 - 画布边距通过
event.target.getBoundingClientRect()
来获取,其 .left 和 .right 等同于 ctx.offsetWidth 和 ctx.offsetHeight - 画布的坐标通过
屏幕坐标 - 画布边距
获取 - 转为 ndc 坐标:设画布的长和宽均为 400px,那么原点为 0,最左为 -200px,最右为 200px。想要转为原点为 0,最左为 -1,最右为 1,就可以均除以 200。最上和最下同理。
示例:我们点击画布的左上角的位置,查看控制台输出的坐标,如下
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./index.js"></script> <style> * { margin: 0; padding: 0; } canvas { margin: 50px auto 0; display: block; background: yellow; } </style></head><body> <canvas id="canvas" width="400" height="400"> 此浏览器不支持canvas </canvas> <script> const ctx = document.getElementById('canvas'); const gl = ctx.getContext('webgl'); // 着色器 // 创建着色器源码 // 顶点着色器 const VERTEX_SHADER_SOURCE = ` // 存储限定符 类型 变量名 分号 (注: attribute 只传递顶点数据) attribute vec4 aPosition; void main() { gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0) gl_PointSize = 10.0; // 点的大小 } `; // 片元着色器 const FRAGMENT_SHADER_SOURCE = ` void main() { // r g b a 绘制颜色 gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } ` const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE); // 获取 attribute 变量,返回变量的存储地址 const aPosition = gl.getAttribLocation(program, 'aPosition'); ctx.onclick = function (event) { // 坐标 const x = event.clientX; const y = event.clientY; console.log("鼠标点击的屏幕坐标:", x, y); // 获取边距 (上边距和左边距) domPosition.left 等同于 ctx.offsetLeft 的值 const domPosition = event.target.getBoundingClientRect(); console.log("画布边距:", domPosition.left, domPosition.top); // 点击的点位于画布上方 、 左侧的距离 (domPosition.left: 568(基于我显示屏的长度), domPosition.top: 50) const domx = x - domPosition.left; const domy = y - domPosition.top; console.log("画布的坐标:", domx, domy); // 固定值,画布长和宽的一半,均为200 const halfWidth = ctx.offsetWidth / 2; const halfHeight = ctx.offsetHeight / 2; console.log("画布长和宽的一半:", halfWidth, halfHeight); // 转为 ndc坐标 (-1, 1) const clickX = (domx - halfWidth) / halfWidth; const clickY = (halfHeight - domy) / halfHeight; console.log("转为ndc的坐标:", clickX, clickY); // 给 attribute 变量赋值 gl.vertexAttrib2f(aPosition, clickX, clickY); // 执行绘制 gl.drawArrays(gl.POINTS, 0, 1); } </script></body></html>
通过鼠标点击绘制点
2、鼠标移动绘制点
改为 ctx.onmousemove 实现鼠标移动:
绘制点跟随鼠标
3、模拟画笔
改为 ctx.onmousemove 的基础上,在 ctx.onmousemove 之前定义一个存储点的数组 points
const points = [];
把以下内容替换掉,从而实现画笔效果:
// 给 attribute 变量赋值 // gl.vertexAttrib2f(aPosition, clickX, clickY); // 执行绘制 // gl.drawArrays(gl.POINTS, 0, 1); for (let i = 0; i < points.length; i++) { gl.vertexAttrib2f(aPosition, points[i].clickX, points[i].clickY); gl.drawArrays(gl.POINTS, 0, 1); }
画笔
三、绘制不同颜色的点(uniform)
通过点击不同位置来控制绘制不同颜色的点,我们可以通过使用 uniform 变量来实现。
- 我们在片元着色器源码中声明 uColor,在 main 函数中进行相应设置。(没有的分量补全即可)
- 我们需要设置片元着色器的精度,它决定了 GPU 在计算 float 时使用的精度(highp、mediump、lowp,分别代表 高、中、低 精度)
- 通过
getUniformLocation
可以获取 uniform 变量的内存地址 - 通过鼠标点击的位置,控制绘制的点的颜色
gl.uniform2f(uColor, points[i].clickX, points[i].clickY)
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./index.js"></script></head><body> <canvas id="canvas" width="400" height="400" style="background: yellow;"> 此浏览器不支持canvas </canvas> <script> const ctx = document.getElementById('canvas'); const gl = ctx.getContext('webgl'); // 着色器 // 创建着色器源码 // 顶点着色器 const VERTEX_SHADER_SOURCE = ` // 存储限定符 类型 变量名 分号 (注: attribute 只传递顶点数据) attribute vec4 aPosition; void main() { gl_Position = aPosition; // vec4(0.0, 0.0, 0.0, 1.0) gl_PointSize = 10.0; } `; // 片元着色器 const FRAGMENT_SHADER_SOURCE = ` // 设置精度为中精度,高精度为 highp,低精度为lowp precision mediump float; uniform vec2 uColor; void main() { // r g b a gl_FragColor = vec4(uColor.r, uColor.g, 0.0, 1.0); } ` const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE); // 获取 attribute 变量,返回变量的存储地址 const aPosition = gl.getAttribLocation(program, 'aPosition'); // 获取 uniform 变量,返回变量的存储地址(包含顶点和片元着色器的程序对象,uniform变量名称) const uColor = gl.getUniformLocation(program, 'uColor'); // 指定 uniform 的存储地址,四个分量 gl.uniform4f(uColor, 1.0, 0.0, 0.0, 1.0); let points = []; ctx.onclick = function (event) { // 坐标 const x = event.clientX; const y = event.clientY; // 获取边距 (上边距和左边距) domPosition.left 等同于 ctx.offsetLeft 的值 const domPosition = event.target.getBoundingClientRect(); // 点击的点位于画布上方、 左侧的距离 (domPosition.left: 568(基于我显示屏的长度), domPosition.top: 50) const domx = x - domPosition.left; const domy = y - domPosition.top; // 固定值,画布长和宽的一半,均为200 const halfWidth = ctx.offsetWidth / 2; const halfHeight = ctx.offsetHeight / 2; // 转为 ndc坐标 (-1, 1) const clickX = (domx - halfWidth) / halfWidth; const clickY = (halfHeight - domy) / halfHeight; points.push({ clickX, clickY }); for (let i = 0; i < points.length; i++) { gl.vertexAttrib2f(aPosition, points[i].clickX, points[i].clickY); gl.uniform2f(uColor, points[i].clickX, points[i].clickY); gl.drawArrays(gl.POINTS, 0, 1); } } </script></body></html>
绘制不同的颜色
总结
本文,我们首先实现了绘制水平移动的点,之后通过坐标的转移,认识了 ndc 坐标的求解,从而实现了鼠标控制绘制,(包括点击绘制、移动绘制和模拟画布)的效果。此外,我们通过 uniform 实现了绘制不同颜色的点。
更多 WebGL 和 Three.js 内容正在更新中…
好啦,本篇文章到这里就要和大家说再见啦,祝你这篇文章阅读愉快,你下篇文章的阅读愉快留着我下篇文章再祝!
参考资料:
- 百度百科 · WebGL
- WebGL + Three.js入门与实战【作者:yancy_慕课网】