WebGL系列教程六(纹理映射与立方体贴图)

server/2025/1/16 2:57:44/

目录

  • 1 前言
  • 2 思考题
  • 3 纹理映射介绍
  • 4 怎么映射?
  • 5 开始绘制
    • 5.1 声明顶点着色器和片元着色器
    • 5.2 修改顶点的颜色为纹理坐标
    • 5.3 指定顶点位置和纹理坐标的值
    • 5.4 获取图片成功后进行绘制
    • 5.5 效果
    • 5.6 完整代码
  • 6 总结

1 前言

  上一讲我们讲了如何使用索引绘制彩色立方体,还留了一个思考题:怎么让立方体的每个面都保持一个颜色?这一讲我们就来解决这个问题,并引出纹理映射立方体贴图

2 思考题

  怎么让立方体的每个面都保持一个颜色?那当然是让每个面的两个三角形都保持一个颜色就行了,那怎么让每个三角形都保持一个颜色呢?因为三角形的颜色是通过顶点的颜色插值出来的,自然是让三角形的每个顶点都保持一个颜色就可以了。
  但是这样又产生了新问题,因为顶点是公用的,难道给每个顶点多个颜色吗?答:是的。
在这里插入图片描述
  比如v0这个顶点,它被三个面公用,分别是前面、上面、右面。那么也就意味着它会有三个颜色,因为我们要让立方体每个表面都是一个颜色。回顾一下上一讲我们是怎么给顶点颜色的:

//顶点和颜色
let verticesColors = new Float32Array([1.0, 1.0, 1.0,   1.0,1.0,1.0,//v0 近平面 右上 颜色-1.0, 1.0, 1.0,   1.0,0.0,1.0,//v1 近平面 左上 颜色-1.0,-1.0, 1.0,   1.0,0.0,1.0,//v2 近平面 左下 颜色1.0,-1.0, 1.0,   1.0,1.0,0.0,//v3 近平面 右下 颜色1.0,-1.0,-1.0,   1.0,0.0,1.0,//v4 远平面 右下 颜色-1.0,-1.0,-1.0,   1.0,1.0,1.0,//v5 远平面 左下 颜色-1.0, 1.0,-1.0,   0.0,0.0,1.0,//v6 远平面 左上 颜色1.0, 1.0,-1.0,   0.0,1.0,1.0 //v7 远平面 右上 颜色
]);
//顶点索引
let indices = new Uint8Array([0,1,2,  0,2,3,//近平面4,5,6,  4,6,7,//远平面1,2,5,  1,5,6,//左平面0,3,4,  0,4,7,//右平面3,4,2,  3,5,2,//下平面0,7,6,  0,1,6 //上平面
]);

  现在一个顶点三个颜色,那岂不是说顶点不够用了?是的,所以每个顶点我们要写三次。也就是说,这次顶点不能公用了。我们对上述代码进行修改

const verticesColors = new Float32Array([// 前面-1.0, -1.0,  1.0,     1.0, 0.0,1.0,//v2 红色1.0, -1.0,  1.0,     1.0, 0.0,1.0,//v3 红色1.0,  1.0,  1.0,     1.0, 0.0,1.0,//v0 红色-1.0,  1.0,  1.0,     1.0, 0.0,1.0,//v1 红色// 后面-1.0, -1.0, -1.0,     0.0, 1.0, 0.0,//v5 绿色1.0, -1.0, -1.0,     0.0, 1.0, 0.0,//v4 绿色1.0,  1.0, -1.0,     0.0, 1.0, 0.0,//v7 绿色-1.0,  1.0, -1.0,     0.0, 1.0, 0.0,//v6 绿色// 上面-1.0,  1.0,  1.0,     0.0, 0.0,1.0,//v1 蓝色1.0,  1.0,  1.0,     0.0, 0.0,1.0,//v0 蓝色1.0,  1.0, -1.0,     0.0, 0.0,1.0,//v7 蓝色-1.0,  1.0, -1.0,     0.0, 0.0,1.0,//v6 蓝色// 下面-1.0, -1.0,  1.0,     0.0, 0.0,0.0,//v2 黑色1.0, -1.0,  1.0,     0.0, 0.0,0.0,//v3 黑色1.0, -1.0, -1.0,     0.0, 0.0,0.0,//v4 黑色-1.0, -1.0, -1.0,     0.0, 0.0,0.0,//v5 黑色// 左面-1.0, -1.0, -1.0,     0.0, 1.0,1.0,//v5 青色-1.0, -1.0,  1.0,     0.0, 1.0,1.0,//v2 青色-1.0,  1.0,  1.0,     0.0, 1.0,1.0,//v1 青色-1.0,  1.0, -1.0,     0.0, 1.0,1.0,//v6 青色// 右面1.0, -1.0,  1.0,     1.0, 1.0,1.0,//v3 白色1.0, -1.0, -1.0,     1.0, 1.0,1.0,//v4 白色1.0,  1.0, -1.0,     1.0, 1.0,1.0,//v7 白色1.0,  1.0,  1.0,     1.0, 1.0,1.0,//v0 白色
]);
//顶点索引
let indices = new Uint8Array([0, 1, 2, 0, 2, 3, // 前面4, 5, 6, 4, 6, 7, // 后面8, 9, 10, 8, 10, 11, // 上面12, 13, 14, 12, 14, 15, // 下面16, 17, 18, 16, 18, 19, // 左面20, 21, 22, 20, 22, 23  // 右面
]);

  看下效果,prefect!
在这里插入图片描述

3 纹理映射介绍

  其实这个词语还是比较好理解。见名知意,大概意思就是把纹理映射到某个地方,纹理是什么?现在你可以简单的认为纹理就是一张图片。把一张二维的图片映射到一个三维物体的表面,就叫纹理映射
在这里插入图片描述

4 怎么映射?

  很简单,你只需要将立方体的的和图片的位置对应起来告诉WebGL就可以了。比如立方体的左上角对应图片的左上角,立方体右上角对应图片右上角,左下角、右下角类似。那么中间的怎么办?中间的WebGL会自动帮我们去映射。纹理坐标我们用uv来表示,相当于xy。不论图片是什么尺寸的,图片的左下角uv始终是【0,0】,右上角始终是【1,1】,这一点也是WebGL帮我们实现的。
在这里插入图片描述

5 开始绘制

5.1 声明顶点着色器和片元着色器

<script id="vertex-shader" type="x-shader/x-vertex">//顶点位置attribute vec4 a_Position;//纹理坐标attribute vec2 a_TexCoord;//传递纹理坐标varying vec2 v_TexCoord;void main(){gl_Position = a_Position;//直接将纹理坐标赋值给传递变量v_TexCoord = a_TexCoord;}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">precision highp float;//采样器,固定写法uniform sampler2D u_Sampler;//接收顶点着色器传过来的值varying vec2 v_TexCoord;void main(){//到某个纹理坐标去采样,也是固定写法gl_FragColor = texture2D(u_Sampler,v_TexCoord);}
</script>

5.2 修改顶点的颜色为纹理坐标

  前面我们给立方体的表面赋值的是颜色,现在我们把它变为纹理坐标

const verticesColors = new Float32Array([// 前面-1.0, -1.0,  1.0,   0.0, 0.0,//v2 图片左下角纹理坐标1.0, -1.0,  1.0,   1.0, 0.0,//v3 图片左下角纹理坐标1.0,  1.0,  1.0,   1.0, 1.0,//v0 图片右下角纹理坐标-1.0,  1.0,  1.0,   0.0, 1.0,//v1 图片左上角纹理坐标// 后面-1.0, -1.0, -1.0,   0.0, 0.0,//v5 同上1.0, -1.0, -1.0,   1.0, 0.0,//v4 同上1.0,  1.0, -1.0,   1.0, 1.0,//v7 同上-1.0,  1.0, -1.0,   0.0, 1.0,//v6 同上// 上面-1.0,  1.0,  1.0,   0.0, 0.0,//v1 同上1.0,  1.0,  1.0,   1.0, 0.0,//v0 同上1.0,  1.0, -1.0,   1.0, 1.0,//v7 同上-1.0,  1.0, -1.0,   0.0, 1.0,//v6 同上// 下面-1.0, -1.0, 1.0,   0.0, 0.0,//v2 同上1.0,  -1.0, 1.0,   1.0, 0.0,//v3 同上1.0,  -1.0,-1.0,   1.0, 1.0,//v4 同上-1.0, -1.0,-1.0,   0.0, 1.0,//v5 同上// 左面-1.0, -1.0, -1.0,   0.0, 0.0,//v5 同上-1.0, -1.0,  1.0,   1.0, 0.0,//v2 同上-1.0,  1.0,  1.0,   1.0, 1.0,//v1 同上-1.0,  1.0, -1.0,   0.0, 1.0,//v6 同上// 右面1.0, -1.0,  1.0,   0.0, 0.0,//v3 同上1.0, -1.0, -1.0,   1.0, 0.0,//v4 同上1.0,  1.0, -1.0,   1.0, 1.0,//v7 同上1.0,  1.0,  1.0,   0.0, 1.0,//v0 同上
]);const indices = new Uint8Array([0, 1, 2, 0, 2, 3, // 前面4, 5, 6, 4, 6, 7, // 后面8, 9, 10, 8, 10, 11, // 上面12, 13, 14, 12, 14, 15, // 下面16, 17, 18, 16, 18, 19, // 左面20, 21, 22, 20, 22, 23  // 右面
]);

5.3 指定顶点位置和纹理坐标的值

 //顶点let vertexColorBuffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,vertexColorBuffer);gl.bufferData(gl.ARRAY_BUFFER,verticesColors,gl.STATIC_DRAW);let FSIZE = verticesColors.BYTES_PER_ELEMENT;let a_Position = gl.getAttribLocation(program,'a_Position');gl.vertexAttribPointer(a_Position,3,gl.FLOAT,false,FSIZE*5,0);gl.enableVertexAttribArray(a_Position);//指定纹理坐标let a_TexCoord = gl.getAttribLocation(program,'a_TexCoord');gl.vertexAttribPointer(a_TexCoord,2,gl.FLOAT,false,5*FSIZE,3*FSIZE);gl.enableVertexAttribArray(a_TexCoord);

5.4 获取图片成功后进行绘制

let image = new Image();
image.src = 'static/sky.jpg';
image.onload = function(){console.log('image ok');//创建纹理对象let texture = gl.createTexture();//获取采样器let u_Sampler = gl.getUniformLocation(program,'u_Sampler');//反转Y轴,canvas的Y轴和WebGL的Y轴方向是反的gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1);//启用0号纹理gl.activeTexture(gl.TEXTURE0);gl.bindTexture(gl.TEXTURE_2D,texture);//设置纹理为,缩小纹理时,取纹理坐标周围四个像素的颜色均值gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);//设置对象使用的图片,mipmap层级,图像的格式,纹理的格式,纹理数据类型,图片gl.texImage2D(gl.TEXTURE_2D,0,gl.RGB,gl.RGB,gl.UNSIGNED_BYTE,image);//将0号纹理赋值给采样器gl.uniform1i(u_Sampler,0);//绑定索引缓冲let indexBuffer =  gl.createBuffer();gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,indices,gl.STATIC_DRAW);//清空颜色缓冲和深度缓冲gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);//绘制//顶点索引数组如果是Uint8Array,就是UNSIGNED_BYTE,表示数组里的值在0-2^8-1(255)//................Uint16Array,就是UNSIGNED_SHORT,表示数组里的值在0-2^16-1(65535)//................Uint32Array,就是UNSIGNED_INT,表示数组里的值在0-2^32-1(4294967295)gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_BYTE, 0);
};

5.5 效果

  为了能看到明显的效果,我将立方体进行了旋转,具体如何旋转,我们后面的文章会进行介绍。
在这里插入图片描述

5.6 完整代码

// Create a cube
//    v6----- v7
//   /|      /|
//  v1------v0|
//  | |     | |
//  | |v5---|-|v4
//  |/      |/
//  v2------v3
const verticesColors = new Float32Array([// 前面-1.0, -1.0,  1.0,   0.0, 0.0,//v2 图片左下角纹理坐标1.0, -1.0,  1.0,   1.0, 0.0,//v3 图片左下角纹理坐标1.0,  1.0,  1.0,   1.0, 1.0,//v0 图片右下角纹理坐标-1.0,  1.0,  1.0,   0.0, 1.0,//v1 图片左上角纹理坐标// 后面-1.0, -1.0, -1.0,   0.0, 0.0,//v5 同上1.0, -1.0, -1.0,   1.0, 0.0,//v4 同上1.0,  1.0, -1.0,   1.0, 1.0,//v7 同上-1.0,  1.0, -1.0,   0.0, 1.0,//v6 同上// 上面-1.0,  1.0,  1.0,   0.0, 0.0,//v1 同上1.0,  1.0,  1.0,   1.0, 0.0,//v0 同上1.0,  1.0, -1.0,   1.0, 1.0,//v7 同上-1.0,  1.0, -1.0,   0.0, 1.0,//v6 同上// 下面-1.0, -1.0, 1.0,   0.0, 0.0,//v2 同上1.0,  -1.0, 1.0,   1.0, 0.0,//v3 同上1.0,  -1.0,-1.0,   1.0, 1.0,//v4 同上-1.0, -1.0,-1.0,   0.0, 1.0,//v5 同上// 左面-1.0, -1.0, -1.0,   0.0, 0.0,//v5 同上-1.0, -1.0,  1.0,   1.0, 0.0,//v2 同上-1.0,  1.0,  1.0,   1.0, 1.0,//v1 同上-1.0,  1.0, -1.0,   0.0, 1.0,//v6 同上// 右面1.0, -1.0,  1.0,   0.0, 0.0,//v3 同上1.0, -1.0, -1.0,   1.0, 0.0,//v4 同上1.0,  1.0, -1.0,   1.0, 1.0,//v7 同上1.0,  1.0,  1.0,   0.0, 1.0,//v0 同上
]);const indices = new Uint8Array([0, 1, 2, 0, 2, 3, // 前面4, 5, 6, 4, 6, 7, // 后面8, 9, 10, 8, 10, 11, // 上面12, 13, 14, 12, 14, 15, // 下面16, 17, 18, 16, 18, 19, // 左面20, 21, 22, 20, 22, 23  // 右面
]);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.enable(gl.DEPTH_TEST);
//顶点
let vertexColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,vertexColorBuffer);
gl.bufferData(gl.ARRAY_BUFFER,verticesColors,gl.STATIC_DRAW);
let FSIZE = verticesColors.BYTES_PER_ELEMENT;
let a_Position = gl.getAttribLocation(program,'a_Position');
gl.vertexAttribPointer(a_Position,3,gl.FLOAT,false,FSIZE*5,0);
gl.enableVertexAttribArray(a_Position);
//指定纹理坐标
let a_TexCoord = gl.getAttribLocation(program,'a_TexCoord');
gl.vertexAttribPointer(a_TexCoord,2,gl.FLOAT,false,5*FSIZE,3*FSIZE);
gl.enableVertexAttribArray(a_TexCoord);
let image = new Image();
image.src = 'static/sky.jpg';
image.onload = function(){console.log('image ok');//创建纹理对象let texture = gl.createTexture();//获取采样器let u_Sampler = gl.getUniformLocation(program,'u_Sampler');//反转Y轴,canvas的Y轴和WebGL的Y轴方向是反的gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1);//启用0号纹理gl.activeTexture(gl.TEXTURE0);gl.bindTexture(gl.TEXTURE_2D,texture);//设置纹理为,缩小纹理时,取纹理坐标周围四个像素的颜色均值gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);//设置对象使用的图片,mipmap层级,图像的格式,纹理的格式,纹理数据类型,图片gl.texImage2D(gl.TEXTURE_2D,0,gl.RGB,gl.RGB,gl.UNSIGNED_BYTE,image);//将0号纹理赋值给采样器gl.uniform1i(u_Sampler,0);//绑定索引缓冲let indexBuffer =  gl.createBuffer();gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,indices,gl.STATIC_DRAW);//清空颜色缓冲和深度缓冲gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);//绘制//顶点索引数组如果是Uint8Array,就是UNSIGNED_BYTE,表示数组里的值在0-2^8-1(255)//................Uint16Array,就是UNSIGNED_SHORT,表示数组里的值在0-2^16-1(65535)//................Uint32Array,就是UNSIGNED_INT,表示数组里的值在0-2^32-1(4294967295)gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_BYTE, 0);
};

6 总结

  本节我们从如何将立方体每个面的颜色改为相同的颜色开始,介绍到了如何将图片贴到立方体的表面,系统的分析了为什么不能再共用顶点,并将代码进行了修改,以及讲解了纹理坐标的使用。这一节的内容相对较多,但理解起来并不难,希望读者认真体会,回见~


http://www.ppmy.cn/server/116436.html

相关文章

Zabbix监控自动化

监控在运维工作中所占的比例为 30%左右&#xff0c;监控做得好&#xff0c;会省很多事&#xff0c;让工作能有序地进行。理想的监控应该是自动化的&#xff0c;只需要配置规则&#xff0c;即可自动完成所有的事情&#xff0c;比如主机的自动添加和注册、模板的自动添加、分组的…

学习Vue3的第五天

目录 API对比 shallowRef 与 shallowReactive 对比总结 使用场景 总结 readonly 与 shallowReadonly 对比总结 使用场景 总结 toRaw 与 markRaw 对比总结 使用场景 总结 customRef 应用场景 总结 示例&#xff1a;异步数据获取 Vue3新组件 Teleport Suspen…

AIPaperGPT写论文靠谱吗?

AIPaperGPT&#xff0c;论文写作神器~ https://www.aipapergpt.com/ 在信息爆炸的今天&#xff0c;学术写作的挑战日益增加&#xff0c;而AIPaperGPT作为一款旨在提升写作效率的工具&#xff0c;其可靠性自然成为了用户关注的焦点。本文将从多个维度对AIPaperGPT进行全面评估&…

AI prompt(提示词)

# 好用的用于学习的AI提示词 ## 费曼学习法 请使用费曼学习法&#xff0c;用简单的语言解释&#xff08;量子力学&#xff09;是什么&#xff0c;并提供一个简单的例子来说明它如何应用 ## 帕累托法则&#xff08;80/20原则&#xff09; 将&#xff08;量子力学&#xff09;最…

网络运维管理:确保企业网络系统稳定运行之道

在当今数字化时代&#xff0c;企业网络系统的稳定运行和高效管理是企业业务持续发展和创新的关键。网络运维管理的核心目标正是确保网络设备和服务的高可用性、优化网络性能、保护网络安全、快速解决网络故障以及有效管理网络配置。本文将深入探讨网络运维管理系统的关键组件、…

接口测试原理及Postman详解

接口测试定义 接口是前后端沟通的桥梁&#xff0c;是数据传输的通道&#xff0c;包括外部接口、内部接口。内部接口又包括:上层服 务与下层服务接口&#xff0c;同级接口 生活中常见接口&#xff1a;电脑上的键盘、USB接口&#xff0c;电梯按钮&#xff0c;KFC下单 接口测试…

计算机毕业设计选题推荐-农家乐综合服务系统-乡村游乐购系统-田园休闲生活系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

Qt 界面设计布局编辑

第1 部分 Qt 基础 (2) 双击dialog.ui 文件&#xff0c;打开Qt 的设计器&#xff0c;中间的空白视窗为一个Parent Widget, 需要建立一些Child Widget 。在左边的工具箱中找到所需要的Widget: 拖曳出一个Label、一个Line Edit&#xff08;用于输入文字&#xff09;、一个Horizo…