WebGL+Three.js入门与实战——绘制水平移动的点、通过鼠标控制绘制(点击绘制、移动绘制、模拟画笔)

个人简介

👀个人主页: 前端杂货铺

🙋‍♂️学习方向: 主攻前端方向,正逐渐往全干发展

📃个人状态: 研发工程师,现效力于中国工业软件事业

🚀人生格言: 积跬步至千里,积小流成江海

🥇推荐学习:🍍前端面试宝典 🍉Vue2 🍋Vue3 🍓Vue2/3项目实战 🥝Node.js🍒Three.js🍖数据结构与算法体系教程

🌕个人推广:每篇文章最下方都有加入方式,旨在交流学习&资源分享,快加入进来吧

文章目录

前言

大家好,这里是前端杂货铺

上一篇文章,我们学习了如何给画布换颜色、如何绘制一个点并且了解了三维坐标系...

接下来,继续我们 WebGL 内容的学习!

鼠标绘制的三种效果


一、绘制一个水平移动的点(attribute)

我们在着色器源程序中声明 attribute 变量,js 可以给这个变量传值,再绘制出来,从而就可以实现动态修改点的位置。

以下是 声明 attribute 变量 ,需要注意的是,attribute 只能在顶点着色器中使用,不能在片元着色器中使用。

javascript 复制代码
// attribute: 存储限定符; vec4: 类型; aPosition: 变量名;
attribute vec4 aPosition;

以下是 获取 attribute 变量 ,需要注意的是 获取 attribute 变量需要在 initShader 函数之后 ,因为会用到 program 这个程序对象。获取之后返回变量的存储地址

javascript 复制代码
// program: 程序对象; 'aPosition': 指定想要获取存储地址的 attribute 变量的名称
const aPositon = gl.getAttribLocation(program, 'aPosition');

以下是 给 attribute 变量赋值,可以传递 1/2/3 个分量的值,没有传递的值为 0.0。

javascript 复制代码
// 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 变量绘制一个水平移动的点

javascript 复制代码
<!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.clickXevent.clickY 来获取
  • 画布边距通过 event.target.getBoundingClientRect() 来获取,其 .left 和 .right 等同于 ctx.offsetWidth 和 ctx.offsetHeight
  • 画布的坐标通过 屏幕坐标 - 画布边距 获取
  • 转为 ndc 坐标:设画布的长和宽均为 400px,那么原点为 0,最左为 -200px,最右为 200px。想要转为原点为 0,最左为 -1,最右为 1,就可以均除以 200。最上和最下同理。

示例:我们点击画布的左上角的位置,查看控制台输出的坐标,如下

javascript 复制代码
<!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

javascript 复制代码
const points = [];

把以下内容替换掉,从而实现画笔效果:

javascript 复制代码
    // 给 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);
    }

画笔


总结

本文,我们首先实现了绘制水平移动的点,之后通过坐标的转移,认识了 ndc 坐标的求解,从而实现了鼠标控制绘制,(包括点击绘制、移动绘制和模拟画布)的效果。

更多 WebGL 和 Three.js 内容正在更新中...

好啦,本篇文章到这里就要和大家说再见啦,祝你这篇文章阅读愉快,你下篇文章的阅读愉快留着我下篇文章再祝!


参考资料:

  1. 百度百科 · WebGL
  2. WebGL + Three.js入门与实战【作者:yancy_慕课网】


相关推荐
真的很上进2 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
m0_748248023 小时前
WebAssembly与WebGL结合:高性能图形处理
webgl·wasm
噢,我明白了6 小时前
同源策略:为什么XMLHttpRequest不能跨域请求资源?
javascript·跨域
sanguine__6 小时前
APIs-day2
javascript·css·css3
关你西红柿子6 小时前
小程序app封装公用顶部筛选区uv-drop-down
前端·javascript·vue.js·小程序·uv
济南小草根6 小时前
把一个Vue项目的页面打包后再另一个项目中使用
前端·javascript·vue.js
小木_.7 小时前
【python 逆向分析某有道翻译】分析有道翻译公开的密文内容,webpack类型,全程扣代码,最后实现接口调用翻译,仅供学习参考
javascript·python·学习·webpack·分享·逆向分析
Aphasia3117 小时前
一次搞懂 JS 对象转换,从此告别类型错误!
javascript·面试
m0_748256567 小时前
Vue - axios的使用
前端·javascript·vue.js
m0_748256347 小时前
QWebChannel实现与JS的交互
java·javascript·交互