渲染到纹理:原理及WebGL实现

news2024/11/20 1:44:32

这篇文章是WebGL系列的延续。 第一个是从基础知识开始的,上一个是向纹理提供数据。 如果你还没有阅读过这些内容,请先查看它们。

NSDT在线工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 

在上一篇文章中,我们讨论了如何从 JavaScript 向纹理提供数据。 在本文中,我们将使用 WebGL 渲染纹理。 请注意,图像处理部分简要介绍了该主题,但让我们更详细地介绍它。

渲染到纹理非常简单。 我们创建一定大小的纹理:

// create to render to
const targetTextureWidth = 256;
const targetTextureHeight = 256;
const targetTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
 
{
  // define size and format of level 0
  const level = 0;
  const internalFormat = gl.RGBA;
  const border = 0;
  const format = gl.RGBA;
  const type = gl.UNSIGNED_BYTE;
  const data = null;
  gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
                targetTextureWidth, targetTextureHeight, border,
                format, type, data);
 
  // set the filtering so we don't need mips
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
}

注意数据是如何为空的。 我们不需要提供任何数据。 我们只需要 WebGL 来分配纹理。

接下来我们创建一个帧缓冲区。 帧缓冲区只是附件的集合。 附件是纹理或渲染缓冲区。 我们之前已经讨论过纹理。 渲染缓冲区与纹理非常相似,但它们支持纹理不支持的格式和选项。 此外,与纹理不同,你不能直接使用渲染缓冲区作为着色器的输入。

让我们创建一个帧缓冲区并附加我们的纹理:

// Create and bind the framebuffer
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
 
// attach the texture as the first color attachment
const attachmentPoint = gl.COLOR_ATTACHMENT0;
gl.framebufferTexture2D(
    gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, level);

就像纹理和缓冲区一样,创建帧缓冲区后,我们需要将其绑定到 FRAMEBUFFER 绑定点。 之后,与帧缓冲区相关的所有函数都会引用绑定在那里的任何帧缓冲区。

通过我们的帧缓冲区绑定,任何时候我们调用 gl.clear、 gl.drawArrays 或  gl.drawElements,WebGL 都会渲染到我们的纹理而不是画布。

让我们将之前的渲染代码变成一个函数,这样我们就可以调用它两次。 一次渲染到纹理,再次渲染到画布。

function drawCube(aspect) {
  // Tell it to use our program (pair of shaders)
  gl.useProgram(program);
 
  // Turn on the position attribute
  gl.enableVertexAttribArray(positionLocation);
 
  // Bind the position buffer.
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
 
  // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
  var size = 3;          // 3 components per iteration
  var type = gl.FLOAT;   // the data is 32bit floats
  var normalize = false; // don't normalize the data
  var stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next position
  var offset = 0;        // start at the beginning of the buffer
  gl.vertexAttribPointer(
      positionLocation, size, type, normalize, stride, offset)
 
  // Turn on the texcoord attribute
  gl.enableVertexAttribArray(texcoordLocation);
 
  // bind the texcoord buffer.
  gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
 
  // Tell the texcoord attribute how to get data out of texcoordBuffer (ARRAY_BUFFER)
  var size = 2;          // 2 components per iteration
  var type = gl.FLOAT;   // the data is 32bit floats
  var normalize = false; // don't normalize the data
  var stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next position
  var offset = 0;        // start at the beginning of the buffer
  gl.vertexAttribPointer(
      texcoordLocation, size, type, normalize, stride, offset)
 
  // Compute the projection matrix
 
  var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  var projectionMatrix =
      m4.perspective(fieldOfViewRadians, aspect, 1, 2000);
 
  var cameraPosition = [0, 0, 2];
  var up = [0, 1, 0];
  var target = [0, 0, 0];
 
  // Compute the camera's matrix using look at.
  var cameraMatrix = m4.lookAt(cameraPosition, target, up);
 
  // Make a view matrix from the camera matrix.
  var viewMatrix = m4.inverse(cameraMatrix);
 
  var viewProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);
 
  var matrix = m4.xRotate(viewProjectionMatrix, modelXRotationRadians);
  matrix = m4.yRotate(matrix, modelYRotationRadians);
 
  // Set the matrix.
  gl.uniformMatrix4fv(matrixLocation, false, matrix);
 
  // Tell the shader to use texture unit 0 for u_texture
  gl.uniform1i(textureLocation, 0);
 
  // Draw the geometry.
  gl.drawArrays(gl.TRIANGLES, 0, 6 * 6);
}

请注意,我们需要传递用于计算投影矩阵的方面,因为我们的目标纹理具有与画布不同的方面。

我们是这样调用它的:

// Draw the scene.
function drawScene(time) {
 
  ...
 
  {
    // render to our targetTexture by binding the framebuffer
    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
 
    // render cube with our 3x2 texture
    gl.bindTexture(gl.TEXTURE_2D, texture);
 
    // Tell WebGL how to convert from clip space to pixels
    gl.viewport(0, 0, targetTextureWidth, targetTextureHeight);
 
    // Clear the attachment(s).
    gl.clearColor(0, 0, 1, 1);   // clear to blue
    gl.clear(gl.COLOR_BUFFER_BIT| gl.DEPTH_BUFFER_BIT);
 
    const aspect = targetTextureWidth / targetTextureHeight;
    drawCube(aspect)
  }
 
  {
    // render to the canvas
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
 
    // render the cube with the texture we just rendered to
    gl.bindTexture(gl.TEXTURE_2D, targetTexture);
 
    // Tell WebGL how to convert from clip space to pixels
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
 
    // Clear the canvas AND the depth buffer.
    gl.clearColor(1, 1, 1, 1);   // clear to white
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
 
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    drawCube(aspect)
  }
 
  requestAnimationFrame(drawScene);
}

结果如下:

记住调用 gl.viewport 并将其设置为渲染对象的大小非常重要。 在这种情况下,我们第一次渲染纹理,因此我们设置视口来覆盖纹理。 第二次我们渲染到画布上,因此我们将视口设置为覆盖画布。

类似地,当我们计算投影矩阵时,我们需要为要渲染的对象使用正确的方面。 我花费了无数个小时的调试时间,想知道为什么某些东西渲染得很有趣或者根本不渲染,最后却发现我忘记了一个或两个调用 gl.viewport 并计算正确的方面。 我很容易忘记,现在我尽量不在自己的代码中直接调用 gl.bindFramebuffer 。 相反,我创建了一个函数来执行类似的操作:

function bindFramebufferAndSetViewport(fb, width, height) {
   gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
   gl.viewport(0, 0, width, height);
}

然后我只使用该函数来更改我要渲染的内容。 这样我就不会忘记。

需要注意的一件事是我们的帧缓冲区上没有深度缓冲区。 我们只有纹理。 这意味着没有深度测试,3D 将无法工作。 如果我们画 3 个立方体,我们就能看到这一点:

如果你看一下中心的立方体,会看到 3 个垂直的立方体绘制在其上,一个在后面,一个在中间,另一个在前面,但我们将所有 3 个立方体绘制在相同的深度。 观察画布上绘制的 3 个水平立方体,你会发现它们彼此正确相交。 这是因为我们的帧缓冲区没有深度缓冲区,但我们的画布有:

要添加深度缓冲区,我们需要创建一个深度缓冲区并将其附加到我们的帧缓冲区:

// create a depth renderbuffer
const depthBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
 
// make a depth buffer and the same size as the targetTexture
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, targetTextureWidth, targetTextureHeight);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);

结果如下:

现在我们已经将深度缓冲区附加到帧缓冲区,内部立方体正确相交:

值得注意的是,WebGL 仅承诺 3 种附件组合工作。 根据规范,唯一有保证的附件组合是:

  • COLOR_ATTACHMENT0 = RGBA/UNSIGNED_BYTE 纹理
  • COLOR_ATTACHMENT0 = RGBA/UNSIGNED_BYTE 纹理 + DEPTH_ATTACHMENT = DEPTH_COMPONENT16 渲染缓冲区
  • COLOR_ATTACHMENT0 = RGBA/UNSIGNED_BYTE 纹理 + DEPTH_STENCIL_ATTACHMENT = DEPTH_STENCIL 渲染缓冲区

对于任何其他组合,你必须检查用户的系统/GPU/驱动程序/浏览器是否支持该组合。 要检查你是否创建了帧缓冲区,请创建并附加附件,然后调用:

var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);

如果状态为 FRAMEBUFFER_COMPLETE,则该附件组合适用于该用户。 否则它不起作用,你将不得不做其他事情,例如告诉用户他们运气不好或回退到其他方法。

Canvas本身实际上是一个纹理

这只是小事,但浏览器使用上述技术来实现画布本身。 他们在幕后创建颜色纹理、深度缓冲区、帧缓冲区,然后将其绑定为当前帧缓冲区。 你进行渲染并绘制到该纹理中。 然后,他们使用该纹理将画布渲染到网页中。

原文链接:WebGL渲染到纹理 - BimAnt

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

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

相关文章

快速入门opencv(python版)

Open Source Computer Vision Library。OpenCV是一个(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C 类构成,同时提供了Python、Ruby、MATLAB等语言的…

PHP微信UI在线聊天系统源码 客服私有即时通讯系统 附安装教程

DuckChat是一套完整的私有即时通讯解决方案,包含服务器端程序和各种客户端程序(包括iOS、Android、PC等)。通过DuckChat,站点管理员可以快速在自己的服务器上建立私有的即时通讯服务,用户可以使用客户端连接至此服务器…

CAN网络出现错误帧从哪些方面去分析解决

标题:CAN网络出现错误帧从哪些方面去分析 实例1: 断电重启后,会有错误帧产生。 检查方案: 查看收发模块的初始化、使能是否在发送CAN报文之前完成? 实例2: 周期性报文,有时会冒出一帧错误帧&…

四则计算机实现(C++)(堆栈的应用)

算法要求: 输入一个数学表达式(假定表达式输入格式合法),计算表达式结果并输出。数学表达式由单个数字和运算符“”、“-”、“*”、“/”、“(、) ”构成,例如 2 3 * ( 4 5 ) - 6 / 4。变量、输出采用整数,只舍不入。 图解算…

MySQL InnoDB Cluster

MySQL InnoDB Cluster 一、InnoDB Cluster 基本概述 MySQL InnoDB Cluster 为 MySQL 提供了一个完整的高可用解决方案。通过使用 MySQL Shell 提供的 AdminAPI,你可以轻松地配置和管理一组至少由3个MySQL服务器实例组成的 InnoDB 集群。 InnoDB 集群中的每个 MySQL 服务器实例…

linux无网络 无ip,显示网络未连接

标题:linux无网络 无ip,显示网络未连接 参考blog:Linux无网络连接问题排查 首先我们发现ens33没有ip地址,说明这个接口并没有被分到ip; 我们可以通过手动方式来给ens33获得网络ip sudo dhclient ens33,之后再输入ifc…

大数据Hadoop-HDFS_元数据持久化

大数据Hadoop-HDFS_元数据持久化 (1)在HDFS第一次格式化后,NameNode(即图中的主NameNode)就会生成fsimage和editslog两个文件; (2)备用NameNode(即图中的备NameNode&…

ChatGPT Plus/GPT4高级数据分析和插件功能详解

ChatGPT 在论文写作与编程方面也具备强大的能力。无论是进行代码生成、错误调试还是解决编程难题,ChatGPT都能为您提供实用且高质量的建议和指导,提高编程效率和准确性。此外,ChatGPT是一位出色的合作伙伴,可以为您提供论文写作的…

微服务实战系列之Redis(cache)

前言 云淡天高,落木萧萧,一阵西北风掠过,似寒刀。冬天渐渐变得更名副其实了。“暖冬”的说法有点言过其实了。——碎碎念 微服务实战系列之Cache微服务实战系列之Nginx(技巧篇)微服务实战系列之Nginx微服务实战系列之F…

Structured Streaming: Apache Spark的流处理引擎

欢迎来到我们的技术博客!今天,我们要探讨的主题是Apache Spark的一个核心组件——Structured Streaming。作为一个可扩展且容错的流处理引擎,Structured Streaming使得处理实时数据流变得更加高效和简便。 什么是Structured Streaming&#…

数据结构中的二分查找(折半查找)

二分法:顾名思义,把问题一分为2的处理,是一种常见的搜索算法,用于在有序数组或这有序列表中查找指定元素的位置,它的思想是将待搜索的区间不断二分,然后比较目标值与中间元素的大小关系,然后确定…

Failed to connect to gitee.com port 443: Time out 连接超时提示【Bug已完美解决-鸿蒙开发】

文章目录 项目场景:问题描述原因分析:解决方案:解决方案1解决方案2:解决方案3:此Bug解决方案总结解决方案总结**心得体会:解决连接超时问题的三种方案**项目场景: 导入Sample时遇到导入失败的情况,并提示“Failed to connect to gitee.com port 443: Time out”连接超…

实现一个通讯录

目录 项目要求及功能实现 通讯录的代码实现 contact.h: contanct.c main.c 项目要求及功能实现 1、可以存放100个人的信息 2、每个人的信息 名字 性别 年龄 电话 地址 3、增加联系人 4、删除指定联系人 5、查找指定联系人 6、修改指定联系人 7、显示联系人信息 8、排…

运维笔记111

运维笔记 Navicat中查询指定字段名所在的表名tomcat设置JVM的初始堆内存修改catalina.sh文件修改完保存并关闭tomcat启动tomcat 查询数据库连接数查询是否存在死锁 Navicat中查询指定字段名所在的表名 SELECT * FROM information_schema.COLUMNS WHERE COLUMN_NAME‘替换成你要…

Redis-Redis 高级数据结构 HyperLogLog与事务

Redis 高级数据结构 HyperLogLog HyperLogLog(Hyper [ˈhaɪpə(r)] ) 并不是一种新的数据结构 ( 实际类型为字符串类 型) ,而是一种基数算法 , 通过 HyperLogLog 可以利用极小的内存空间完成独立总数的统计,数据集可以是 IP 、 Email 、 ID 等。 如…

【brew】Mac上安装vue3

先安装node。 这里我从其他博客找的方案,原始脚本下载太慢了。 cnpm的安装: 让npm更快一点。 npm install -g cnpm --registryhttps://registry.npm.taobao.org安装vue脚手架 2.0版本:sudo npm install -g vue-cli 3.0版本: sud…

对话式数据需求激增,景联文科技提供高质量多轮对话数据定制采集标注服务

大模型的快速发展使得数据服务需求激增,产品整体处于供不应求状态。对话式数据集成为当下需求热点,人们对于更复杂、更真实的多轮对话数据需求不断增加,定制化服务占据市场需求主流。 通过对多轮对话数据的训练,模型可以更好地理解…

解析Top-K问题及堆排序算法

Top-K问题是在海量数据中找到最大或最小的K个元素,它在实际应用中非常常见,例如专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。在面对大规模数据时,直接对数据进行排序可能效率低下,因为排序的时间复杂度通常为O(n lo…

Notepad++批量添加引号

工作中经常会遇到这样情景:业务给到一批订单号,需要查询这批订单的某些字段信息。在where条件中需要传入这些订单号的数组,并且订单号用引号引起,用引号隔开。 字符串之间长度相同 可以按住CtrlAlt和鼠标左键选中区域&#xff0…

IDEA如何配置Git 遇到问题的解决

新建项目 点击 会变红 会生成.git隐藏文件 配置远程仓库路径:点击Manage Remotes:将远程仓库的链接放到这里: 得到如下样式: 此时提交到本地仓库 点击add,添加到暂存文件: 此时文件变绿&#xf…