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

news2024/12/24 18:18:09

个人简介

👀个人主页: 前端杂货铺
🙋‍♂️学习方向: 主攻前端方向,正逐渐往全干发展
📃个人状态: 研发工程师,现效力于中国工业软件事业
🚀人生格言: 积跬步至千里,积小流成江海
🥇推荐学习:🍍前端面试宝典 🍉Vue2 🍋Vue3 🍓Vue2/3项目实战 🥝Node.js🍒Three.js🍖数据结构与算法体系教程

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

文章目录

    • 前言
    • 一、绘制一个水平移动的点(attribute)
    • 二、通过鼠标控制绘制
      • 1、鼠标点击绘制点
      • 2、鼠标移动绘制点
      • 3、模拟画笔
    • 总结

前言

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

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

接下来,继续我们 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.clickXevent.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);
    }

画笔


总结

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

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

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


参考资料:

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

在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1430553.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MATLAB矩阵的操作(第二部分)

师从清风 矩阵的创建方法 在MATLAB中&#xff0c;矩阵的创建方法主要有三种&#xff0c;分别是&#xff1a;直接输入法、函数创建法和导入本地文件中的数据。 直接输入法 输入矩阵时要以中括号“[ ]”作为标识符号&#xff0c;矩阵的所有元素必须都在中括号内。 矩阵的同行元…

IEC104 S帧超时判定客户与服务端不匹配造成的异常链接问题分析

2、通过ss命令发现确有链接端口变化&#xff0c;与设备约一天一次的重连&#xff0c;通过抓包&#xff08;tcpdump -vvv -nn port 1001 -w 0926.cap&#xff09;分析得以下现象 2.1、异常情况时未对设备的I帧均匀的回S帧进行确认&#xff0c;正常情况时均匀的回S帧进行确认 2.…

数据在内存中的存储(上)

1. 整数在内存中的存储 整数的2进制表示方法有三种&#xff1a;即原码、反码和补码 三种表示方法均有符号位和数值位两部分&#xff0c;符号位都是用0表示“正”&#xff0c;用1表示“负”&#xff0c;而数值位最 高位的一位是被当做符号位&#xff0c;剩余的都是数值位。 正…

深度学习水论文怎么缝模块?搭积木永不过时!(附80个即插即用模型)

深度学习如何创新&#xff1f;如何水模型&#xff1f;总结来说就八个字&#xff1a;排列组合&#xff0c;会讲故事。说直白点&#xff0c;就是缝模块。 先看看别人怎么做&#xff0c;然后根据自己的实际情况将这些模块来一波随机组合&#xff0c;这样效率会高很多。我这边已经…

冰冻天气恰逢春运,“观冰精灵”化身电力供应守护者

据中国路网&#xff0c;截至2月1日14时&#xff0c;受降雪及路面结冰影响&#xff0c;河北、山西、内蒙古、黑龙江、江苏、安徽、河南、山东、西藏、陕西、宁夏、甘肃、新疆共封闭路段66个&#xff08;涉及44条高速公路、5条普通国道、5条普通省道&#xff09;&#xff0c;关闭…

Pandas.Series.clip() 修剪数值范围 详解 含代码 含测试数据集 随Pandas版本持续更新

关于Pandas版本&#xff1a; 本文基于 pandas2.2.0 编写。 关于本文内容更新&#xff1a; 随着pandas的stable版本更迭&#xff0c;本文持续更新&#xff0c;不断完善补充。 传送门&#xff1a; Pandas API参考目录 传送门&#xff1a; Pandas 版本更新及新特性 传送门&…

前端JavaScript篇之map和Object的区别、map和weakMap的区别

目录 map和Object的区别map和weakMap的区别 map和Object的区别 Object是JavaScript的内置对象&#xff0c;用于存储键值对。Object的键必须是字符串或符号&#xff0c;值可以是任意类型。Map是ES6引入的新数据结构&#xff0c;用于存储键值对。Map的键可以是任意类型&#xff…

高中数学两面角习题练习1

用到的定理 2 第1问证明&#xff1a; 第2问用到的知识和例子&#xff1a; 二面角锐角钝角判断的快速技巧 https://www.bilibili.com/video/BV13P41157K1/?spm_id_from333.788.recommend_more_video.0&vd_source91b03ee59c462b7b3cfbd57346cf1001 叉乘的几何意义及应用 …

day39 不同路径 不同路径Ⅱ

题目1&#xff1a;62 不同路径 题目链接&#xff1a;62 不同路径 题意 机器人位于m*n网格的左上角每次只能向下或向右移动一步&#xff0c;到达网格的右下角,返回不同路径的数量 动态规划 动规五部曲 1&#xff09;dp数组及下标i的含义 dp[i][j]&#xff1a;到达(i,j)有…

2024年 复习 HTML5+CSS3+移动web 笔记 之CSS遍 第5天

第 五 天 整个网站例 5.1 准备工作 项目目录与版心 base.css 5.2 网页制作思路 5.3 header 区域-整体布局 5.4 header区域-logo 5.5 header区域-导航 index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8">&l…

代码随想录算法训练营29期Day41|LeetCode 343,96

文档讲解&#xff1a;整数拆分 不同的二叉搜索树 343.整数拆分 题目链接&#xff1a;https://leetcode.cn/problems/integer-break/description/ 思路&#xff1a; 题目要求我们拆分n&#xff0c;拆成k个数使其乘积和最大&#xff0c;然而题目中并没有给出k&#xff0c;所以…

[python]基于opencv实现的车道线检测

【检测原理】 一、首先进行canny边缘检测&#xff0c;为获取车道线边缘做准备 二、进行ROI提取获取确切的车道线边缘&#xff08;红色线内部&#xff09; 三、利用概率霍夫变换获取直线&#xff0c;并将斜率正数和复数的线段给分割开来 四、离群值过滤&#xff0c;剔除斜率…

uniapp 高德地图显示

1. uniapp 高德地图显示 使用前需到**高德开放平台&#xff08;https://lbs.amap.com/&#xff09;**创建应用并申请Key   登录 高德开放平台&#xff0c;进入“控制台”&#xff0c;如果没有注册账号请先根据页面提示注册账号   打开 “应用管理” -> “我的应用”页面…

Springboot集成Camunda并完成一条流程实例

&#x1f496;专栏简介 ✔️本专栏将从Camunda(卡蒙达) 7中的关键概念到实现中国式工作流相关功能。 ✔️文章中只包含演示核心代码及测试数据&#xff0c;完整代码可查看作者的开源项目snail-camunda ✔️请给snail-camunda 点颗星吧&#x1f618; &#x1f496;设计流程定…

028 方法的重载

方法重载的定义 使用案例 public static void main(String[] args) {// 匹配到max(int a, int b)System.out.println(max(1, 3));// 匹配到max(double a, double b)System.out.println(max(1L, 3L));// 匹配到max(double a, double b, double c, double d)&#xff0c;int自动…

SQL 函数(十二)

SQL 函数&#xff08;十二&#xff09; 一、函数分类 1.1 单行函数 单行函数仅对单个行进行运算&#xff0c;并且每行返回一个结果。 常见的函数类型&#xff1a; 字符、数字、日期、转换 1.2 多行函数 多行函数能够操纵成组的行&#xff0c;每个行组给出一个结果&#x…

【拜年神器】AI写真保姆级教程

1. 介绍 废话不说&#xff0c;先上图看效果 功能简介 上传几张自己的照片&#xff0c;训练之后&#xff0c;就能生成各种风格的AI写真照&#xff0c;主要有无限风格写真、固定模板写真、AI虚拟试衣、人物说话视频四个类别 无限风格写真 固定模板写真 证件照 工作照 婚纱…

k8s中cert-manager管理https证书

前言 目前https是刚需,但证书又很贵,虽然阿里云有免费的,但没有泛域名证书,每有一个子域名就要申请一个证书,有效期1年,1年一到全都的更换,太麻烦了。经过搜索,发现了自动更新证书神器cert-manager;当然cert-manager是基于k8s的。 安装采用Helm方式 Chart地址: ht…

萝卜视频源码前后端带视频演示

萝卜影视源码前端是用JAVA开发的全原生APP源码&#xff0c;后端用的是二次开发的苹果CMS&#xff0c;支持局域网投屏&#xff0c;视频软解硬解&#xff0c;播放器自带弹幕功能。支持解析官方视频&#xff0c;支持M3U8&#xff0c;MP4。 开屏广告&#xff0c;全局广告&#xff0…

lava学习-接口

接口-Interface 1.什么是接口&#xff1f; 例&#xff1a;构造器&#xff0c;代码块在接口中统统没有&#xff0c;也不能创建对象 构造器的使用-----实现类 例&#xff1a;下图中的B类就是一个 实现类 2.接口的好处 继承只能单继承&#xff0c;而接口可以弥补类单继承的不足&am…