webGL硬核知识:图形渲染管渲染流程,各个阶段对应的API调用方式

一、图形渲染管线基础流程概述

WebGL 的图形渲染管线大致可分为以下几个主要阶段,每个阶段都有其特定的任务,协同工作将 3D 场景中的物体最终转换为屏幕上呈现的 2D 图像:

  1. 顶点处理(Vertex Processing)阶段
    • 该阶段主要处理传入的顶点数据,包括顶点坐标、顶点颜色、法线向量等顶点相关的属性信息。进行的操作有坐标变换(例如将顶点从模型坐标系转换到世界坐标系、再到视图坐标系、裁剪坐标系等)以及对顶点属性进行必要的设置和计算等。
    • 例如,将一个 3D 模型中各个顶点的初始坐标通过平移、旋转、缩放等矩阵变换,使其放置到正确的空间位置,并且根据光照等需求计算顶点的光照属性(如果在顶点着色器中处理光照部分)。
  1. 图元装配(Primitive Assembly)阶段
    • 把经过顶点处理后的顶点按照指定的绘制模式(如gl.TRIANGLESgl.TRIANGLE_STRIP等)组合成基本图元,像三角形、线段等,这些基本图元是后续光栅化的基础单元。例如,根据传入的顶点数据,按照绘制三角形的模式将 3 个顶点装配成一个三角形图元。
  1. 光栅化(Rasterization)阶段
    • 这个阶段将图元转换为屏幕上对应的像素片段(Fragment),也就是确定哪些像素会被图元覆盖,计算出每个像素对应的坐标等信息,它是从几何图形到离散像素的转换过程。例如,一个三角形图元经过光栅化后,会确定其在屏幕空间中覆盖了哪些具体的像素位置。
  1. 片段处理(Fragment Processing)阶段
    • 对光栅化生成的每个像素片段进行处理,主要是确定每个片段的最终颜色、透明度等属性,会涉及到纹理采样(如果有纹理映射)、光照计算(若在片段着色器中处理光照)、颜色混合等操作,最终决定该片段在屏幕上显示的样子。比如,根据纹理坐标从绑定的纹理图像中获取相应的颜色值,结合光照计算结果等来确定该像素最终呈现的颜色。
  1. 帧缓冲(Framebuffer)阶段
    • 帧缓冲可以看作是一个存储渲染结果的区域,片段处理后的像素颜色等信息会被写入到帧缓冲中对应的位置,然后最终由浏览器将帧缓冲中的内容显示到屏幕上,完成整个渲染过程。例如,渲染的一帧图像数据先存放在帧缓冲里,再根据显示设置展示在网页的画布区域。

二、各阶段对应的 WebGL API 调用方式
顶点处理阶段
  1. 创建缓冲区对象(Buffer Object)并绑定
    • 首先使用gl.createBuffer()函数创建一个缓冲区对象,这个缓冲区用于存储顶点数据(如顶点坐标、颜色等)。例如:

    const buffer = gl.createBuffer();

  • 然后通过gl.bindBuffer()函数将创建好的缓冲区绑定到特定的目标上,常见的目标有gl.ARRAY_BUFFER(用于存储顶点数组数据,比如顶点坐标、颜色等属性数组)和gl.ELEMENT_ARRAY_BUFFER(用于存储索引数据,在使用索引绘制时用到)。比如绑定用于存储顶点坐标数据的缓冲区:

    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

  1. 向缓冲区填充数据
    使用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);

  2. 创建并编译着色器(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);
  1. 获取属性位置并启用顶点属性数组
    在链接好的程序中,通过gl.getAttribLocation()函数获取顶点属性变量(如a_position)对应的位置索引,然后使用gl.enableVertexAttribArray()函数启用该顶点属性数组,以便后续传递数据给顶点着色器进行处理。例如:

    const aPositionLocation = gl.getAttribLocation(program, ‘a_position’);
    gl.enableVertexAttribArray(aPositionLocation);

  2. 指定顶点属性数据的读取方式
    使用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);

其中,xy是视口在屏幕中的起始坐标(通常以左下角为原点),widthheight分别是视口的宽度和高度。例如,设置整个画布区域为视口:

const canvas = document.getElementById('webglCanvas');
gl.viewport(0, 0, canvas.width, canvas.height);
片段处理阶段
  1. 片段着色器编写与相关操作(类似顶点着色器部分步骤)
    • 创建片段着色器对象,使用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是内置的输出变量,用于指定该片段最终的颜色值(这里设置为红色)。

  • 将编译好的片段着色器添加到之前创建的着色器程序中,并链接程序,操作和顶点着色器部分类似,不再赘述。
  1. 纹理相关操作(如果有纹理映射)
    • 创建纹理对象,使用gl.createTexture()函数,然后绑定纹理到特定的纹理单元,例如绑定到gl.TEXTURE0单元:

    const texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE0, texture);

  • 设置纹理参数,如纹理过滤方式(gl.TEXTURE_MIN_FILTERgl.TEXTURE_MAG_FILTER用于控制纹理缩小时和放大时的过滤模式,常见的有gl.NEARESTgl.LINEAR等)、纹理环绕方式(gl.TEXTURE_WRAP_Sgl.TEXTURE_WRAP_T用于确定纹理坐标超出[0, 1]范围时的处理方式,常见的有gl.REPEATgl.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绑定到采样器上

帧缓冲阶段
  1. 创建帧缓冲对象
    使用gl.createFramebuffer()函数创建帧缓冲对象,例如:

    const framebuffer = gl.createFramebuffer();

  2. 绑定帧缓冲并设置相关参数
    通过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 调用方式,整个过程较为复杂且各环节相互关联,需要不断实践和深入理解才能熟练掌握运用,实现想要的图形渲染效果。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大数据面试题--企业面试真题

大数据面试题--企业面试真题 PlanHub 点击访问获取&#xff1a; 大数据面试体系专栏_酷兜科技​www.kudoumh.top/hlwai/85.html 点击访问获取&#xff1a; 大数据面试体系专栏_酷兜科技​www.kudoumh.top/hlwai/85.html 大数据面试题汇总 HDFS 1、 HDFS 读写流程。 2、HDF…

lambda初探(一)

发生捕获时&#xff0c;拿到x,y的值 退出lambda表达式后&#xff0c;foo外层的值不变化。foo内部的x&#xff0c;值是持续的&#xff0c;像static。即使退出foo函数后&#xff0c;值的状态依然保持。 外层x的值变化&#xff0c;并不影响foo内部。 foo运行了两次&#xff0c;内…

【D3.js in Action 3 精译_046】DIY 实战:在 Observable 平台利用饼图布局函数实现 D3 多个环形图的绘制

当前内容所在位置&#xff1a; 第五章 饼图布局与堆叠布局 ✔️ 5.1 饼图和环形图的创建 ✔️ 5.1.1 准备阶段&#xff08;一&#xff09;5.1.2 饼图布局生成器&#xff08;二&#xff09;5.1.3 圆弧的绘制&#xff08;三&#xff09;5.1.4 数据标签的添加&#xff08;四&#…

基于Spring Boot的智慧农业专家远程指导系统

一、系统背景与意义 随着科技的不断进步&#xff0c;农业领域也在积极寻求创新与发展。然而&#xff0c;传统农业生产中农民往往依靠经验进行种植和养殖&#xff0c;缺乏科学的指导和技术支持。同时&#xff0c;农业专家资源有限&#xff0c;难以覆盖广大的农村地区&#xff0…

【JavaEE初阶】线程 和 thread

本节⽬标 认识多线程 掌握多线程程序的编写 掌握多线程的状态 一. 认识线程&#xff08;Thread&#xff09; 1概念 1) 线程是什么 ⼀个线程就是⼀个 "执⾏流". 每个线程之间都可以按照顺序执⾏⾃⼰的代码. 多个线程之间 "同时" 执⾏着多份代码. 还…

练习题 最小栈

最小栈 最小栈 class MinStack {private Stack<Integer> stack;private Stack<Integer> minstack;public MinStack() {stacknew Stack<>();minstacknew Stack<>();}public void push(int val) {stack.push(val);if(minstack.empty()){minstack.push(…

全志H618 Android12修改doucmentsui鼠标单击图片、文件夹选中区域

背景: 由于当前的文件管理器在我们的产品定义当中,某些界面有改动的需求,所以需要在Android12 rom中进行定制以符合当前产品定义。 需求: 在进入File文件管理器后,鼠标左击整个图片、整个文件夹可以选中该类型,进行操作,故代码分析以及客制化如下: 主要涉及的代码:…

堆【Lecode_HOT100】

文章目录 1.数组中的第&#xff2b;个最大元素No.2152.前K个高频元素347 1.数组中的第&#xff2b;个最大元素No.215 方法一&#xff1a;NlogN不能满足时间复杂度的要求 public int findKthLargest(int[] nums, int k) {Arrays.sort(nums);return nums[nums.length-k];}方法二&…

Android 搭建AIDL Client和Server端,双向通信

一、背景 使用AIDL,搭建Client和Server端,实现跨进程通讯,即两个应用之间可以相互通讯。这里列举AIDL实现的方式和需注意的细节&#xff0c;并附上源码。 二、实现方式 2.1 定义AIDL需要的接口,名字为xxx.aidl,Client和Server端 AIDL接口的包名和aidl文件必须一致&#xff0c…

HIPT论文阅读

题目《Scaling Vision Transformers to Gigapixel Images via Hierarchical Self-Supervised Learning》 论文地址&#xff1a;[2206.02647] Scaling Vision Transformers to Gigapixel Images via Hierarchical Self-Supervised Learning 项目地址&#xff1a;mahmoodlab/HI…

[ESP]从零开始的Arduino IDE安装与ESP环境配置教程

一、前言 最近也是在比赛方面比较忙&#xff0c;没有更多的时间和精力去更新长文章了。这几周都更倾向于环境搭建的教程&#xff0c;这类教程写起来确实方便&#xff0c;也不怎么费时间&#xff0c;一个下午基本可以搞定&#xff0c;哈哈&#xff0c;我保证不是在为自己想摆烂找…

投标心态:如何在“标海战术”中保持清醒的头脑?

在竞争激烈的市场环境下&#xff0c;“标海战术”——即大规模参与投标——已经成为许多企业争取市场份额的重要策略。然而&#xff0c;盲目追求投标数量可能导致资源浪费、团队疲劳以及战略目标的模糊化。在这种高强度的竞争模式中&#xff0c;如何保持清醒的头脑&#xff0c;…

wxWidgets使用wxStyledTextCtrl(Scintilla编辑器)的正确姿势

开发CuteMySQL/CuteSqlite开源客户端的时候&#xff0c;需要使用Scintilla编辑器&#xff0c;来高亮显示SQL语句&#xff0c;作为C/C领域最成熟稳定又小巧的开源编辑器&#xff0c;Scintilla提供了强大的功能&#xff0c;wxWidgets对Scintilla进行包装后的是控件类&#xff1a;…

【原生js案例】让你的移动页面实现自定义的上拉加载和下拉刷新

目前很多前端UI都是自带有上拉加载和下拉刷新功能,按照官网配置去实现即可,比如原生小程序,vantUI等UI框架,都替我们实现了内部功能。 那如何自己来实现一个上拉加载和下拉刷新的功能? 实现效果 不用浏览器的css滚动条,自定义实现滚动效果 自定义实现滚动,添加上拉加载…

批处理理解

初识批处理 如何批处理&#xff1a; 命名&#xff1a;.bat 方法&#xff1a;创建一个记事本文件&#xff0c;然后将其扩展改为.bat 批处理作用&#xff1a;自上而下成批处理每一条DOS命令&#xff0c;直到执行到最后一条。运行环境&#xff1a;当然是我们cmd了 回归我学过的…

APM32F411使用IIS外设驱动es8388实现自录自播

前言&#xff1a; 从零开始学习I2s外设&#xff0c;配置Es8288寄存器实现录音播放。本文章使用主控芯片是APM32F411系类。音频相关的概念比较多&#xff0c;就不再次做过多的介绍&#xff0c;本文章只是简单实现边录边播功能。APM系类兼容st的芯片&#xff0c;所以用st的hal库来…

OB删除1.5亿数据耗费2小时

目录 回顾&#xff1a;mysql是怎么删除数据的&#xff1f; 删除方案 代码实现 执行结果 结论 本篇是实际操作 批量处理数据以及线程池线程数设置 记录学习 背景&#xff1a;有一张用户标签表&#xff0c;存储数据量达4个亿&#xff0c;使用OceanBase存储&#xff0c;由于…

20241217使用M6000显卡在WIN10下跑whisper来识别中英文字幕

20241217使用M6000显卡在WIN10下跑whisper来识别中英文字幕 2024/12/17 17:21 缘起&#xff0c;最近需要识别法国电影《地下铁》的法语字幕&#xff0c;使用 字幕小工具V1.2【whisper套壳/GUI封装了】 无效。 那就是直接使用最原始的whisper来干了。 当你重装WIN10的时候&#…

linux普通用户使用sudo不需要输密码

1.root用户如果没有密码&#xff0c;先给root用户设置密码 sudo passwd root #设置密码 2.修改visudo配置 su #切换到root用户下 sudo visudo #修改visudo配置文件 用户名 ALL(ALL) NOPASSWD: ALL #下图所示处新增一行配置 用户名需要输入自己当前主机的用户名

【C++11】可变模板参数

目录 可变模板的定义方式 参数包的展开方式 递归的方式展开参数包 STL中的emplace相关接口函数 STL容器中emplace相关插入接口函数 ​编辑 模拟实现&#xff1a;emplace接口 C11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板&#xff0c;相比 C9…