渲染到纹理:原理及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 0const 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 mipsgl.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 attributegl.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 iterationvar type = gl.FLOAT;   // the data is 32bit floatsvar normalize = false; // don't normalize the datavar stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next positionvar offset = 0;        // start at the beginning of the buffergl.vertexAttribPointer(positionLocation, size, type, normalize, stride, offset)// Turn on the texcoord attributegl.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 iterationvar type = gl.FLOAT;   // the data is 32bit floatsvar normalize = false; // don't normalize the datavar stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next positionvar offset = 0;        // start at the beginning of the buffergl.vertexAttribPointer(texcoordLocation, size, type, normalize, stride, offset)// Compute the projection matrixvar 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_texturegl.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 framebuffergl.bindFramebuffer(gl.FRAMEBUFFER, fb);// render cube with our 3x2 texturegl.bindTexture(gl.TEXTURE_2D, texture);// Tell WebGL how to convert from clip space to pixelsgl.viewport(0, 0, targetTextureWidth, targetTextureHeight);// Clear the attachment(s).gl.clearColor(0, 0, 1, 1);   // clear to bluegl.clear(gl.COLOR_BUFFER_BIT| gl.DEPTH_BUFFER_BIT);const aspect = targetTextureWidth / targetTextureHeight;drawCube(aspect)}{// render to the canvasgl.bindFramebuffer(gl.FRAMEBUFFER, null);// render the cube with the texture we just rendered togl.bindTexture(gl.TEXTURE_2D, targetTexture);// Tell WebGL how to convert from clip space to pixelsgl.viewport(0, 0, gl.canvas.width, gl.canvas.height);// Clear the canvas AND the depth buffer.gl.clearColor(1, 1, 1, 1);   // clear to whitegl.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.rhkb.cn/news/205563.html

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

相关文章

webpack external 详解

作用:打包时将依赖独立出来,在运行时(runtime)再从外部获取这些扩展依赖,目的时解决打包文件过大的问题。 使用方法: 附上代码块 config.set(externals, {vue: Vue,vue-router: VueRouter,axios: axios,an…

Spark on yarn 模式的安装与部署

任务描述 本关任务: Spark on YARN 模式的安装与部署。 相关知识 为了完成本关任务,你需要掌握: Spark 部署模式的种类;Spark on YARN 模式的安装。 Spark 部署模式 Spark 部署模式主要分为以下几种,Spark Stand…

Compose入门

​ 本篇文章主要是为了对Compose有一个初步了解。知道Compose是做什么的,用Compose能干什么,在目前的各种UI框架下面有些优势,参考Google官网的解释加上一些自己的理解生成的一篇文章。本人也是Compose初学者,通过每一步学习遇到哪…

系统频繁崩溃,如何考虑系统的稳定性和可扩展性?

最近网传互联网应用信息系统频繁崩溃,语雀崩完淘宝崩,淘宝崩完滴滴崩,随着业务的发展和技术的进步,对于信息系统的要求也越来越高。信息应用系统为了满足不断增长的用户和业务需求,提高系统的稳定性和扩展性至关重要。…

网络入门---网络的大致了解

目录标题 网络发展的简单认识协议作用的理解协议的本质什么是协议分层网络通信所面对的问题OSI七层模型TCP/IP模型协议报头的理解局域网通信局域网通信基本原理报头的问题局域网的特点跨网的网络链接如何查看mac地址 网络发展的简单认识 通过之前的学习我们知道计算机是给人提…

【深度学习】基于深度学习的超分辨率图像技术一览

超分辨率(Super-Resolution)即通过硬件或软件的方法提高原有图像的分辨率,图像超分辨率是计算机视觉和图像处理领域一个非常重要的研究问题,在医疗图像分析、生物特征识别、视频监控与安全等实际场景中有着广泛的应用。 SR取得了显著进步。一般可以将现有…

创建Asp.net MVC项目实现视图页面数据传值显示

MVC中视图传值 ViewData ViewBag TempData 举例创建三中传值方式实现页面数据展示 MVC中视图传值 Asp.net MVC中Controller向View传值有多种方式,这里简单说一下其中3种方式 ViewData、ViewBag和TempData ViewData ViewData存储数据,ViewData的声明和赋值方…

扫地机器人市场持续火爆,景联文科技数据采集标注方案助力扫地机器人智能化升级

随着消费者对智能家居和清洁卫生的需求增加,扫地机器人市场规模不断扩大。市场竞争也日益激烈,各品牌都在努力提升产品性能和服务质量,以获取更大的市场份额。 IDC的统计数据显示,今年双十一前两周(2023年10月23日至20…

滴滴打车崩了!全过程

滴滴发布致歉10元补偿券,文末可领取 。 事情发生于 2023年11月27日晚~28日中午,滴滴打车服务出现大面积故障,登上微博热搜。 许多用户在使用滴滴出行时遇到了无法叫车、订单异常等问题,导致大量用户滞留在外,出行受阻…

吃火锅(Python)

题目描述 吃火锅 以上图片来自微信朋友圈:这种天气你有什么破事打电话给我基本没用。但是如果你说“吃火锅”,那就厉害了,我们的故事就开始了。 本题要求你实现一个程序,自动检查你朋友给你发来的信息里有没有 chi1 huo3 guo1。…

BUUCTF-pwn-ciscn_2019_ne_51

简单查看保护: 32为程序没有canary没有PIE,应该是简单的栈溢出。我们照着这个思路去找溢出点在哪,运行下程序看看什么情况: 程序上来是输入一个密码验证。随便输入下错误直接退出。因此我们需要到IDA中看看怎么回事: 主…

python -- python安装

1、python的诞生和发展: python语言是一种解释型、面向对象型、动态数据类型的高级程序设计语言。 2、python的安装: 1、安装解析器: 在安装的过程中需要注意的是: 在安装pycharm的时候也是同样的道理,需要指定安装…

【数据结构】——解决topk问题

前言:我们前面已经学习了小堆并且也实现了小堆,那么我们如果要从多个数据里选出最大的几个数据该怎么办呢,这节课我们就来解决这个问题。我们就用建小堆的方法来解决。 首先我们来看到这个方法的时间复杂度,我们先取前k个数据建立…

Egg.js中Cookie和Session

Cookie HTTP请求是无状态的,但是在开发时,有些情况是需要知道请求的人是谁的。为了解决这个问题,HTTP协议设计了一个特殊的请求头:Cookie。服务端可以通过响应头(set-cookie)将少量数据响应给客户端&#…

解决msvcr71.dll丢失5个方法,修复程序运行缺失dll问题

在计算机使用过程中,我们经常会遇到一些错误提示,其中之一就是“msvcr71.dll丢失”。这个错误提示通常出现在运行某些程序或游戏时,给使用者带来了很大的困扰。那么,究竟是什么原因导致了msvcr71.dll文件的丢失呢?本文…

Vue3-目录调整

默认生成的目录结构不满足我们的开发需求,所以这里需要做一些自定义改动。 主要是以下工作: 1.删除一些初始化的默认文件 2.修改剩余代码内容 3.新增调整我们需要的目录结构 在src文件夹下创建两个新文件夹,一个叫api(请求模…

Spring 拾枝杂谈—Spring原生容器结构剖析(通俗易懂)

目录 一、前言 二、Spring快速入门 1.简介 : 2. 入门实例 : 三、Spring容器结构分析 1.bean配置信息的存储 : 2.bean对象的存储 : 3.bean-id的快捷访问 : 四、总结 一、前言 开门见山,11.25日开始我们正式进入Java框架—Spring的学习,此前&…

基于springboot+vue的在线考试系统(前后端分离)

博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容:毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

Drool 7 SpreadSheet Decision Template 笔记

1 Excel Decision table 1.1 很棒的示意图,来自https://blog.csdn.net/justlpf/article/details/128109731 1.2 参考URL 1.2.1 https://blog.csdn.net/justlpf/article/details/128109731 1.3 多sheet 模式 默认是用第一个sheet如果要支持多sheet,需…

JavaScript包装类型

前端面试大全JavaScript包装类型 🌟经典真题 🌟包装类型 🌟真题解答 🌟总结 🌟经典真题 是否了解 JavaScript 中的包装类型? 🌟包装类型 在 ES 中,数据的分类分为基本数据类型…