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

这篇文章是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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue修改密码功能的源代码

基本需求 输入框不能为空 旧密码表单提交时必须正确 两次输入新密码一致 限定新密码的复杂度&#xff0c;这里是长度在 6 到 20 个字符 <template><el-form ref"form" :model"user" :rules"rules" label-width"80px"><…

线性表——(2)线性表的顺序存储及其运算的实现

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 看到美好&#xff0c;感受美好&a…

ucharts中,当数据为0时,不显示

当为0时&#xff0c;会显示出来&#xff0c;值比较小的时候&#xff0c;数据会显示在一起&#xff0c;不美观 期望效果&#xff1a; 实现步骤&#xff1a; 我是将uCharts插件下载导入到src/uni_modules下的 1、修改src/uni_modules/qiun-data-charts/js_sdk/u-charts/confi…

数据结构day4作业

1.单链表任意位置删除 datetype pos;printf("please input pos");scanf("%d",&pos);headdelete_all(head,pos);Output(head);Linklist delete_all(Linklist head,datetype pos) {if(pos<1||pos>length(head)||headNULL)return head;if(head->…

Spring boot命令执行 (CVE-2022-22947)漏洞复现和相关利用工具

Spring boot命令执行 (CVE-2022-22947)漏洞复现和相关利用工具 名称: spring 命令执行 (CVE-2022-22947) 描述: Spring Cloud Gateway是Spring中的一个API网关。其3.1.0及3.0.6版本&#xff08;包含&#xff09;以前存在一处SpEL表达式注入漏洞&#xff0c;当攻击者可以访问A…

数据结构 / day06 作业

1.下面的代码打印在屏幕上的值是多少? /下面的代码打印在屏幕上的值是多少?#include "stdio.h"int compute_data(int arr[], unsigned int len) {long long int result 0;if(result len)return arr[0];resultcompute_data(arr,--len);printf("len%d, res…

基于单片机智能液位水位监测控制系统

**单片机设计介绍&#xff0c; 基于单片机智能液位水位监测控制系统 文章目录 一 概要特点应用场景工作原理实现方式 系统功能实时监测控制调节报警功能数据记录与分析 总结 二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 ## 系统介绍 基于单片机…

【Python】yaml.safe_load()函数详解和示例

在Python中&#xff0c;PyYAML库提供了对YAML&#xff08;YAML Ain’t Markup Language&#xff09;文件的强大支持。YAML是一种直观的数据序列化标准&#xff0c;可以方便地存储和加载配置文件、数据日志等。 yaml.safe_load和yaml.load是Python的PyYAML库提供的两个函数&…

uniapp开发小程序使用axios进行网络请求 uniapp 小程序调试

前言 本篇最好放到项目的【README.md】文件中,方便每次发布的时候检查纠错,毕竟好记性不如烂笔头。而且其他开发者帮忙修改bug、发布新版本的时候,只需要根据这个事项就能实现整个流程的提审发布,提高效率。 1、微信小程序配置 1.1、检查APPID是否正确 测试:wx--------…

函数学习 PTA 1使用函数输出一个整数的逆序数;3判断满足条件的三位数;5使用函数求余弦函数的近似值

其实一共有五道题&#xff0c;但那两道实在太过简单&#xff0c;也不好意思打出来给大家看&#xff0c;那么这篇博客&#xff0c;就让我一次性写三道题吧&#xff01;也当是个小总结&#xff0c;睡前深思。 6-1 使用函数输出一个整数的逆序数 本题要求实现一个求整数的逆序数的…

vuepress-----6、时间更新

# 6、时间更新 基于Git提交时间修改文字时间格式 moment # 最后更新时间 # 时间格式修改 下载库文件 yarn add momentconst moment require(moment); moment.locale(zh-cn)module.exports {themeConfig: {lastUpdated: 更新时间,},plugins: [[vuepress/last-updated,{trans…

智能优化算法应用:基于群居蜘蛛算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于群居蜘蛛算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于群居蜘蛛算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.群居蜘蛛算法4.实验参数设定5.算法结果6.参考…

【Vue】【uni-app】实现工单列表项详情页面

这次主要实现的是一个工单详情页面 从工单列表项中点击详情 跳转到工单详情页面&#xff0c;这个详情页面就是这次我们要实现的页面&#xff0c;并可以通过点击这个关闭按钮返回到工单列表页面 首先是在我们原有的工单列表页面的按钮增加一个点击跳转 <button size"m…

微服务API网关Spring Cloud Gateway实战

概述 微服务网关是为了给不同的微服务提供统一的前置功能&#xff1b;网关服务可以配置集群&#xff0c;以承载更多的流量&#xff1b;负载均衡与网关互相成就&#xff0c;一般使用负载均衡&#xff08;例如 nginx&#xff09;作为总入口&#xff0c;然后将流量分发到多个网关…

504. 七进制数

这篇文章会收录到 : 算法通关第十三关-青铜挑战数学基础问题-CSDN博客 七进制数 描述 : 给定一个整数 num&#xff0c;将其转化为 7 进制&#xff0c;并以字符串形式输出。 题目 : LeetCode 504. 七进制数 : 504. 七进制数 分析 : 我们先通过二进制想一下7进制数的变化特…

C++二分查找算法:包含每个查询的最小区间

题目 给你一个二维整数数组 intervals &#xff0c;其中 intervals[i] [lefti, righti] 表示第 i 个区间开始于 lefti 、结束于 righti&#xff08;包含两侧取值&#xff0c;闭区间&#xff09;。区间的 长度 定义为区间中包含的整数数目&#xff0c;更正式地表达是 righti -…

<蓝桥杯软件赛>零基础备赛20周--第8周第1讲--十大排序

报名明年4月蓝桥杯软件赛的同学们&#xff0c;如果你是大一零基础&#xff0c;目前懵懂中&#xff0c;不知该怎么办&#xff0c;可以看看本博客系列&#xff1a;备赛20周合集 20周的完整安排请点击&#xff1a;20周计划 每周发1个博客&#xff0c;共20周&#xff08;读者可以按…

【C语言】把歌词里的播放时间跟歌词提取出来

一&#xff0c;介绍 给到一个字符串&#xff0c;里面包含了时间&#xff08;唱该歌词的时间以及该歌词&#xff09;例如“[02:16.33][04:11.44][05:11.44]我想大声宣布对你依依不舍”&#xff0c;如何把两者都给打印出来呢&#xff1f;下面给出解释 二&#xff0c;代码 #incl…

【Openstack Train安装】四、MariaDB/RabbitMQ 安装

本章介绍了MariaDB/RabbitMQ的安装步骤&#xff0c;MariaDB/RabbitMQ仅需要在控制节点安装。 在安装MariaDB/RabbitMQ前&#xff0c;请确保您按照以下教程进行了相关配置&#xff1a; 【Openstack Train安装】一、虚拟机创建 【Openstack Train安装】二、NTP安装 【Opensta…

UI自动化测试的正确姿势 —— Airtest设备连接API详解第一篇

一、背景 Airtest作为一款优秀的自动化测试工具&#xff0c;有着强大的API功能&#xff0c;处理日常自动化测试过程中需要的各类操作。今天就给大家逐一介绍关于设备连接和常用API部分&#xff0c;结合自动化测试中的各类需求&#xff0c;看看如何通过使用Airtest来快速实现。…