webGL硬核知识:图形渲染管渲染流程,各个阶段对应的API调用方式
一、图形渲染管线基础流程概述
WebGL 的图形渲染管线大致可分为以下几个主要阶段,每个阶段都有其特定的任务,协同工作将 3D 场景中的物体最终转换为屏幕上呈现的 2D 图像:
- 顶点处理(Vertex Processing)阶段:
-
- 该阶段主要处理传入的顶点数据,包括顶点坐标、顶点颜色、法线向量等顶点相关的属性信息。进行的操作有坐标变换(例如将顶点从模型坐标系转换到世界坐标系、再到视图坐标系、裁剪坐标系等)以及对顶点属性进行必要的设置和计算等。
- 例如,将一个 3D 模型中各个顶点的初始坐标通过平移、旋转、缩放等矩阵变换,使其放置到正确的空间位置,并且根据光照等需求计算顶点的光照属性(如果在顶点着色器中处理光照部分)。
- 图元装配(Primitive Assembly)阶段:
-
- 把经过顶点处理后的顶点按照指定的绘制模式(如
gl.TRIANGLES
、gl.TRIANGLE_STRIP
等)组合成基本图元,像三角形、线段等,这些基本图元是后续光栅化的基础单元。例如,根据传入的顶点数据,按照绘制三角形的模式将 3 个顶点装配成一个三角形图元。
- 把经过顶点处理后的顶点按照指定的绘制模式(如
- 光栅化(Rasterization)阶段:
-
- 这个阶段将图元转换为屏幕上对应的像素片段(Fragment),也就是确定哪些像素会被图元覆盖,计算出每个像素对应的坐标等信息,它是从几何图形到离散像素的转换过程。例如,一个三角形图元经过光栅化后,会确定其在屏幕空间中覆盖了哪些具体的像素位置。
- 片段处理(Fragment Processing)阶段:
-
- 对光栅化生成的每个像素片段进行处理,主要是确定每个片段的最终颜色、透明度等属性,会涉及到纹理采样(如果有纹理映射)、光照计算(若在片段着色器中处理光照)、颜色混合等操作,最终决定该片段在屏幕上显示的样子。比如,根据纹理坐标从绑定的纹理图像中获取相应的颜色值,结合光照计算结果等来确定该像素最终呈现的颜色。
- 帧缓冲(Framebuffer)阶段:
-
- 帧缓冲可以看作是一个存储渲染结果的区域,片段处理后的像素颜色等信息会被写入到帧缓冲中对应的位置,然后最终由浏览器将帧缓冲中的内容显示到屏幕上,完成整个渲染过程。例如,渲染的一帧图像数据先存放在帧缓冲里,再根据显示设置展示在网页的画布区域。
二、各阶段对应的 WebGL API 调用方式
顶点处理阶段
- 创建缓冲区对象(Buffer Object)并绑定:
-
- 首先使用
gl.createBuffer()
函数创建一个缓冲区对象,这个缓冲区用于存储顶点数据(如顶点坐标、颜色等)。例如:
const buffer = gl.createBuffer();
- 首先使用
-
然后通过
gl.bindBuffer()
函数将创建好的缓冲区绑定到特定的目标上,常见的目标有gl.ARRAY_BUFFER
(用于存储顶点数组数据,比如顶点坐标、颜色等属性数组)和gl.ELEMENT_ARRAY_BUFFER
(用于存储索引数据,在使用索引绘制时用到)。比如绑定用于存储顶点坐标数据的缓冲区:gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
-
向缓冲区填充数据:
使用gl.bufferData()
函数将实际的顶点数据填充到绑定的缓冲区中,需要指定数据的类型(如gl.STATIC_DRAW
表示数据不会或很少改变,gl.DYNAMIC_DRAW
表示数据会较频繁改变等)以及具体的数据内容(通常是一个类型化数组,如Float32Array
)。例如,填充顶点坐标数据:const vertices = new Float32Array([
// 这里是一系列顶点坐标值,比如一个三角形的三个顶点坐标
0.0, 0.5, 0.0,
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0
]);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); -
创建并编译着色器(Shader):
顶点处理主要通过顶点着色器来实现,需要创建顶点着色器对象,使用gl.createShader(gl.VERTEX_SHADER)
创建,然后通过gl.shaderSource()
函数设置着色器的源代码(用 GLSL 语言编写),最后用gl.compileShader()
函数编译着色器。例如:const vertexShader = gl.createShader(gl.VERTEX_SHADER);
const vertexShaderSource =attribute vec4 a_position; void main() { gl_Position = a_position; }
;
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
这里的attribute
变量用于接收从外部传入的顶点属性数据(如顶点坐标),gl_Position
是内置的输出变量,用于将处理后的顶点坐标传递给下一个阶段。
4. 创建着色器程序(Program)并链接:
创建一个着色器程序对象,将编译好的顶点着色器(以及后续的片段着色器,如果有的话)添加到程序中,然后通过gl.linkProgram()
函数链接它们,使其成为一个完整可执行的程序。例如:
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
// 假设已经创建并编译好片段着色器fragmentShader,也添加进来
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
-
获取属性位置并启用顶点属性数组:
在链接好的程序中,通过gl.getAttribLocation()
函数获取顶点属性变量(如a_position
)对应的位置索引,然后使用gl.enableVertexAttribArray()
函数启用该顶点属性数组,以便后续传递数据给顶点着色器进行处理。例如:const aPositionLocation = gl.getAttribLocation(program, ‘a_position’);
gl.enableVertexAttribArray(aPositionLocation); -
指定顶点属性数据的读取方式:
使用gl.vertexAttribPointer()
函数来指定顶点属性数据在缓冲区中的读取方式,包括数据的类型、每个顶点属性的分量数量、是否需要归一化、步长(相邻顶点属性数据之间的间隔字节数)以及起始偏移量等信息。例如:gl.vertexAttribPointer(
aPositionLocation, // 顶点属性位置索引
3, // 每个顶点属性的分量数量(这里是3个坐标值,x、y、z)
gl.FLOAT, // 数据类型为浮点数
false, // 不需要归一化
0, // 步长为0,表示紧密排列
0 // 起始偏移量为0
);
图元装配阶段
在 WebGL 中,图元装配阶段主要通过指定绘制模式来进行,使用gl.drawArrays()
或gl.drawElements()
函数来触发图元的装配和绘制操作,同时也隐含了图元装配这个过程。
-
**gl.drawArrays()**
函数调用方式:
它用于直接根据缓冲区中的顶点数据按照指定的绘制模式来绘制图元,语法如下:gl.drawArrays(mode, first, count);
其中,mode
参数指定绘制模式,常见的值有gl.TRIANGLES
(绘制独立的三角形)、gl.TRIANGLE_STRIP
(绘制三角形带,可节省顶点数据)、gl.LINES
(绘制线段)等;first
表示从缓冲区中的哪个顶点开始绘制;count
表示要绘制的顶点数量。例如,用gl.TRIANGLES
模式绘制一个简单的三角形:
gl.drawArrays(gl.TRIANGLES, 0, 3);
-
**gl.drawElements()**
函数调用方式:
当使用索引数据来指定图元的顶点顺序时,使用该函数,语法如下:gl.drawElements(mode, count, type, offset);
mode
同样是绘制模式;count
是要绘制的索引数量;type
是索引数据的类型(如gl.UNSIGNED_SHORT
等);offset
是索引数据在索引缓冲区中的偏移量(字节数)。例如,假设有索引数据存储在另一个缓冲区中,通过索引绘制三角形:
// 假设已经创建并绑定好索引缓冲区,填充好索引数据
const indices = new Uint16Array([0, 1, 2]);
gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_SHORT, 0);
光栅化阶段
光栅化阶段在 WebGL 中是自动进行的,由 GPU 根据前面图元装配阶段确定的图元信息来执行,开发者一般不需要直接调用特定的 API 来干预这个过程,但可以通过一些设置间接影响,比如设置视口(Viewport)大小,通过gl.viewport()
函数来指定渲染结果在屏幕上显示的区域范围,语法如下:
gl.viewport(x, y, width, height);
其中,x
、y
是视口在屏幕中的起始坐标(通常以左下角为原点),width
和height
分别是视口的宽度和高度。例如,设置整个画布区域为视口:
const canvas = document.getElementById('webglCanvas');
gl.viewport(0, 0, canvas.width, canvas.height);
片段处理阶段
- 片段着色器编写与相关操作(类似顶点着色器部分步骤):
-
- 创建片段着色器对象,使用
gl.createShader(gl.FRAGMENT_SHADER)
,设置源代码(用 GLSL 编写,主要用于确定片段的颜色等属性),然后编译它,例如:
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
const fragmentShaderSource =precision mediump float; void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 设置片段颜色为红色 }
;
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader); - 创建片段着色器对象,使用
这里gl_FragColor
是内置的输出变量,用于指定该片段最终的颜色值(这里设置为红色)。
- 将编译好的片段着色器添加到之前创建的着色器程序中,并链接程序,操作和顶点着色器部分类似,不再赘述。
- 纹理相关操作(如果有纹理映射):
-
- 创建纹理对象,使用
gl.createTexture()
函数,然后绑定纹理到特定的纹理单元,例如绑定到gl.TEXTURE0
单元:
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE0, texture); - 创建纹理对象,使用
-
设置纹理参数,如纹理过滤方式(
gl.TEXTURE_MIN_FILTER
和gl.TEXTURE_MAG_FILTER
用于控制纹理缩小时和放大时的过滤模式,常见的有gl.NEAREST
、gl.LINEAR
等)、纹理环绕方式(gl.TEXTURE_WRAP_S
和gl.TEXTURE_WRAP_T
用于确定纹理坐标超出[0, 1]
范围时的处理方式,常见的有gl.REPEAT
、gl.CLAMP_TO_EDGE
等),例如:gl.texParameteri(gl.TEXTURE0, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE0, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE0, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE0, gl.TEXTURE_WRAP_T, gl.REPEAT);
-
加载纹理图像数据,可以通过
gl.texImage2D()
函数来加载外部的纹理图像(支持多种图像格式,如 PNG、JPEG 等),例如:const image = new Image();
image.onload = function() {
gl.texImage2D(gl.TEXTURE0, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
// 这里假设图像是RGBA格式,数据类型是无符号字节类型
};
image.src = ‘texture.png’; // 纹理图像的路径 -
在片段着色器中通过采样器(Sampler)来获取纹理颜色值,需要先在片段着色器中声明一个采样器变量(如
uniform sampler2D u_texture;
),然后在 JavaScript 代码中获取其位置,并将纹理单元绑定到该采样器上,例如:const uTextureLocation = gl.getUniformLocation(program, ‘u_texture’);
gl.activeTexture(gl.TEXTURE0);
gl.uniform1i(uTextureLocation, 0); // 将纹理单元0绑定到采样器上
帧缓冲阶段
-
创建帧缓冲对象:
使用gl.createFramebuffer()
函数创建帧缓冲对象,例如:const framebuffer = gl.createFramebuffer();
-
绑定帧缓冲并设置相关参数:
通过gl.bindFramebuffer()
函数将创建好的帧缓冲绑定到特定的目标(如gl.FRAMEBUFFER
),然后可以设置一些参数,比如绑定纹理或渲染缓冲(Renderbuffer)到帧缓冲的颜色附件、深度附件等位置,以确定渲染结果的存储方式。例如,绑定一个纹理作为颜色附件:gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
这里假设texture
是之前创建并配置好的纹理对象,用于存储渲染后的颜色数据。
3. 渲染到帧缓冲并最终显示到屏幕:
在进行渲染操作(如前面的绘制图元操作)时,渲染结果就会存储到绑定的帧缓冲中对应的附件里,然后可以通过交换缓冲区(如果是双缓冲机制,常用于动画等场景,避免画面闪烁)等操作,最终将帧缓冲中的内容显示到屏幕上。在 WebGL 中,通常是浏览器自动处理将帧缓冲内容显示到对应的<canvas>
元素所占据的屏幕区域,无需额外复杂的显式调用,但开发者可以通过控制帧缓冲的使用和渲染操作的时机等来间接影响显示效果。
以上就是 WebGL 图形渲染管线各阶段对应的主要 API 调用方式,整个过程较为复杂且各环节相互关联,需要不断实践和深入理解才能熟练掌握运用,实现想要的图形渲染效果。
相关文章:

webGL硬核知识:图形渲染管渲染流程,各个阶段对应的API调用方式
一、图形渲染管线基础流程概述 WebGL 的图形渲染管线大致可分为以下几个主要阶段,每个阶段都有其特定的任务,协同工作将 3D 场景中的物体最终转换为屏幕上呈现的 2D 图像: 顶点处理(Vertex Processing)阶段࿱…...

区块链详解
1. 概述 1.1 什么是区块链? 区块链是一种分布式数据库技术,它以链式数据结构的形式存储数据,每个数据块与前一个数据块相关联,形成了一个不断增长的数据链。每个数据块中包含了一定数量的交易信息或其他数据,这些数据…...

【EXCEL 逻辑函数】AND、OR、XOR、NOT、IF、IFS、IFERROR、IFNA、SWITCH
目录 AND:当所有条件都为真时返回 TRUE,否则返回 FALSE OR:当任一条件为真时返回 TRUE,否则返回 FALSE XOR:当奇数个条件为真时返回 TRUE,否则返回 FALSE NOT :反转逻辑值 IF:根…...

ubuntu下gdb调试ROS
参考: 使用VsCode进行ROS程序调试_ros vscode 调试-CSDN博客 https://blog.csdn.net/weixin_45031801/article/details/134399664?spm1001.2014.3001.5506 一、调试准备 1.1 CMakeLists改动 注释文件中的 set(CMAKE_BUILD_TYPE "Release") #构建类…...

Docke_常用命令详解
这篇文章分享一下笔者常用的Docker命令供各位读者参考。 为什么要用Docker? 简单来说:Docker通过提供轻量级、隔离且可移植的容器化环境,使得应用在不同平台上保持一致性、易于部署和管理,具体如下 环境一致性: Docker容器使得…...

使用vue2.0或vue3.0创建自定义组件
Vue2.0创建自定义组件 在 Vue 2.0 中创建自定义组件是一个相对简单的过程。以下是一个详细的步骤指南,帮助你创建一个自定义组件。 步骤 1: 创建 Vue 组件文件 首先,你需要创建一个新的 Vue 文件(.vue 文件)。假设我们要创建一…...

Elasticsearch-DSL高级查询操作
一、禁用元数据和过滤数据 1、禁用元数据_source GET product/_search {"_source": false, "query": {"match_all": {}} }查询结果不显示元数据 禁用之前: {"took" : 0,"timed_out" : false,"_shards" : {&quo…...

【Linux】重启系统后开不开机(内核模块丢失问题)
问题 重启后开不开机报错如下: FAILED failed to start load kernel moduiles 可以看到提示module dm_mod not found 缺少了dm_mod 在内核module目录中 reboot重启可以看到这个现象: 可以看到重启启动磁盘,加载不到root 原因 dm_mod模块…...

对golang的io型进程进行off-cpu分析
背景: 对于不能占满所有cpu核数的进程,进行on-cpu的分析是没有意义的,因为可能程序大部分时间都处在阻塞状态。 实验例子程序: 以centos8和golang1.23.3为例,测试下面的程序: pprof_netio.go package m…...

Springboot中使用Retrofit
Retrofit官网 https://square.github.io/retrofit/ 配置gradle implementation("com.squareup.okhttp3:okhttp:4.12.0")implementation ("com.squareup.retrofit2:retrofit:2.11.0")implementation ("com.squareup.retrofit2:converter-gson:2.11.0…...

Ubuntu中配置内网固定IP
文章目录 背景一、配置步骤(一)首先确认网卡名称(二)确认网关(三)备份配置文件(四)编辑配置文件(五)应用配置(六)验证配置 二、注意事…...

ExcelVBA编程输出ColorIndex与对应颜色色谱
标题 ExcelVBA编程输出ColorIndex与对应颜色色谱 正文 解决问题编程输出ColorIndex与对应色谱共56,打算分4纵列输出,标题是ColorIndex,Color,Name 1. 解释VBA中的ColorIndex属性 在VBA(Visual Basic for Applications)中ÿ…...

MySQL中in和exists的使用场景
在MySQL中,IN 和 EXISTS 是用于子查询的两种常见方法,它们在不同的场景下有不同的表现和适用性。下面我将详细介绍这两种方法的使用场景、优劣,并通过实验来说明问题。 IN 子查询 使用场景: 当子查询返回的结果集较小且不包含 …...

【多线程2】start 和 run 区别,终止线程,等待线程
Thread 类使用 start 方法,启动一个线程,对于同一个 Thread 对象来说,start 只能调用一次!!! 不怕名字起的长,就怕含义不清楚! 想要启动更多线程,就是得创建新的对象&am…...

富途证券C++面试题及参考答案
C++ 中堆和栈的区别 在 C++ 中,堆和栈是两种不同的内存区域,它们有许多区别。 从内存分配方式来看,栈是由编译器自动分配和释放的内存区域。当一个函数被调用时,函数内的局部变量、函数参数等会被压入栈中,这些变量的内存空间在函数执行结束后会自动被释放。例如,在下面的…...

Go使用sqlx操作MySQL完整指南
# Go使用sqlx操作MySQL完整指南## 1. 安装依赖bash go get github.com/go-sql-driver/mysql go get github.com/jmoiron/sqlx2. 数据库基础操作 package mainimport ("fmt"_ "github.com/go-sql-driver/mysql""github.com/jmoiron/sqlx" )// 定…...

Python 爬取网页文字并保存为 txt 文件教程
引言 在网络数据获取的过程中,我们常常需要从网页中提取有用的文字信息。Python 提供了强大的库来帮助我们实现这一目标。本教程将以https://theory.gmw.cn/2023 - 08/31/content_36801268.htm为例,介绍如何使用requests库和BeautifulSoup库爬取网页文字…...

时间序列预测论文阅读和相关代码库
时间序列预测论文阅读和相关代码库列表 MLP-based的时间序列预测资料DLinearUnetTSFPDMLPLightTS 代码库以及论文库:Time-Series-LibraryUnetTSFLightTS MLP-based的时间序列预测资料 我会定期把我的所有时间序列预测论文有关的资料链接全部同步到这个文章中&#…...

Mamba安装环境和使用,anaconda环境打包
什么是mamba Mamba是一个极速版本的conda,它是conda的C重新实现,使用多线程并行处理来加速包和依赖项的下载。 Mamba旨在提高安装、更新和卸载Python包的速度,同时保持与conda相同的兼容性和命令行接口。 Mamba的核心部分使用C实现ÿ…...

SSH连接成功,但VSCode连接不成功
环境 在实验室PC上连接服务器234 解决方案:在VSCode中重新添加远程主机 删除旧的VSCode Server 在远程主机上,VSCode会安装一个‘vscode-server’服务来支持远程开发,有时旧的‘vscode-server’文件可能会导致问题,删除旧的&am…...

springboot结合AES和国密SM4进行接口加密
api接口加密 1.为什么需要api接口加密呢? 1.防止爬虫 2.防止数据被串改 3.确保数据安全 2.如何实现接口加密呢? 3.我们可以使用哪些加密算法来加密呢? AES 密码学中的高级加密标准(Advanced Encryption Standard,…...

iOS在项目中设置 Dev、Staging 和 Prod 三个不同的环境
在 Objective-C 项目中设置 Dev、Staging 和 Prod 三个不同的环境,并为每个环境使用不同的 Bundle ID,可以通过以下步骤实现: 步骤 1: 创建不同的 Build Configuration 打开项目: 启动 Xcode 并打开你的项目。 选择项目文件&…...

openeuler24.09 系统无需配置 docker 源即可安装 docker 和 docker-composer
准备工作 1、准备一台刚刚创建的 openeuler24.09 lxc 虚拟机 2、使用 dnf 更新到最新,安装常用 工具 dnf update -y dnf install vim net-tools wget3、设置 ssh 由于ssh 与通常网上教程大同小异,在此我们就略过。 从下图我们可以看到 openeuler24.09 已经远程连接上。 …...

Flask入门:打造简易投票系统
目录 准备工作 创建项目结构 编写HTML模板 编写Flask应用 代码解读 进一步优化 结语 Flask,这个轻量级的Python Web框架,因其简洁和易用性,成为很多开发者入门Web开发的首选。今天,我们就用Flask来做一个简单的投票系统,让你快速上手Web开发,同时理解Flask的核心概…...

日常思考笔记
技术管理, 团队管理,人才培养,梯队建设 项目管理,项目全生命周期,项目进度 考核规范, AQS 是CountDownLatch,ReentrantLock,Semaphore,ReentrantReadWriteLock的基础 vo…...

【JAVA】后台管理系统密码复杂度和修改密码处理
一、后台管理系统密码要求 后台管理系统密码要求 口令有效期:90天 口令长度8位及8位以上 口令复杂度要求,至少包含以下四类字符中的三类字符: 英文大写字母(A 到 Z)、英文小写字母(a 到 z)、10个基本数字(0 到 9)、特殊字符(例如 !、$、#、%、、^、&a…...

微服务SpringCloud链路追踪之Micrometer+Zipkin
视频教程: https://www.bilibili.com/video/BV12LBFYjEvR 效果演示 当我们发送一个请求给 Gateway 的时候,由 Micrometer trace 进行链路追踪和数据收集,由 Zipkin 进行数据展示。可以清楚的看到微服务的调用过程,以及每个微服务…...

Quartz(2-Trigger)
相关文章链接 定时任务工具类(Cron Util)SpringBoot TaskQuartz(1-Job)Quartz(2-Trigger) Trigger 方法 优先级(priority) 如果你的 trigger 很多(或者 Quartz 线程…...

【微信小程序开发 - 3】:项目组成介绍
文章目录 项目组成介绍项目的基本组成结构小程序页面的组成部分JSON配置文件的作用app.json文件project.config.json文件sitemap.json文件页面的 .json 配置文件新建小程序页面修改项目首页 XWML模板XWML 和 HTML 的区别 WXSS样式WXSS 和 CSS 的区别 .js文件 项目组成介绍 项目…...

Leetcode 三角形最小路径和
算法思想与代码详解 这段代码采用的是**动态规划(Dynamic Programming)**的思想,用来解决“120. 三角形最小路径和”问题。动态规划通过将问题分解成更小的子问题,并通过保存子问题的解来避免重复计算,从而提高效率。…...