渲染到纹理:原理及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.mzph.cn/news/184270.shtml

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

相关文章

ffmpeg 把mp4文件中某段视频转成gif文件

一 缘起背景: 有视频文件转gif动图的需求;网上下载的转换工具需要注册会员、否则带水印,还限制时长。 二 工具环境: win10 下 dos 操作 ffmpeg 三 操作命令: ffmpeg -i test.mp4 -ss 00:01:01 -t 00:00:19 -vf &q…

什么牌子的台灯对孩子的眼睛好?安利五款适合孩子备考的护眼台灯

近年来,青少年的近视问题越来越严重,近视率持续升高,不少上小学一年级就已经戴上了厚厚的近视眼镜。导致这种现象发生的原因有两个,一个是孩子长时间使用电子产品导致。还有就是现在孩子的学习任务,不仅远比80、90后上…

HNU 练习八 结构体编程题4. 看电影

【问题描述】 湖南大学正在举办一场重要的国际学术会议,出席会议的 n 位科学家来自不同的国家,每位科学家都只熟悉一种语言,为方便起见,世界上所有的语言用1~1000的数字编号来列出。 晚上,主办方安排所有科学家去看电影…

【开源】基于JAVA的高校学生管理系统

项目编号: S 029 ,文末获取源码。 \color{red}{项目编号:S029,文末获取源码。} 项目编号:S029,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 学生管理模块2.2 学院课程模块2.3 学…

WebSocket 是什么原理?为什么可以实现持久连接?

WebSocket 是一种用于实现持久连接的通信协议,它的原理和工作方式相对复杂,但我们可以尝试以尽可能简单和清晰的方式来解释它。 WebSocket 的原理 在理解 WebSocket 的工作原理之前,我们首先要了解 HTTP 协议的短连接性质。在传统的 HTTP 通信…

Python基础语法之学习表达式进行符串格式化

Python基础语法之学习表达式进行符串格式化 一、代码二、效果 一、代码 print("11等于%d" % (1 1)) print(f"2/1等于{2 / 1}") print("字符串类型是%s" % type("字符串"))二、效果 坚持追求自己的梦想,即使道路漫长曲折&…

Android设置文字颜色渐变

项目中用到了很多文字颜色渐变的设计,因此做一下记录。 核心代码如下: /*** 统一文字渐变色设置* param colors 渐变色字符串数组* param positions 渐变色位置数组,可为空* param start 渐变起始点,可为空* param end 渐变结束…

模板引擎详解

📑打牌 : da pai ge的个人主页 🌤️个人专栏 : da pai ge的博客专栏 ☁️宝剑锋从磨砺出,梅花香自苦寒来 🌤️动态页面的渲染方式 …

盘点68个Android系统源码安卓爱好者不容错过

盘点68个Android系统源码安卓爱好者不容错过 学习知识费力气,收集整理更不易。 知识付费甚欢喜,为咱码农谋福利。 源码下载链接:https://pan.baidu.com/s/1FcBxCe7KpJsh0zFxNZ_7wg?pwd8888 提取码:8888 项目名称 Android …

外贸B2B自建站怎么建?做海洋建站的方法?

如何搭建外贸B2B自建站?外贸独立站建站方法有哪些? 对于许多初次涉足者来说,搭建一个成功的外贸B2B自建站并不是一件轻松的任务。海洋建站将为您详细介绍如何有效地建设外贸B2B自建站,让您的国际贸易之路更加畅通无阻。 外贸B2B…

Android中使用Google Map

在app的使用过程中,我们经常会跟地图进行交互,如果是海外的应用,那选择使用Google Map 是最合适的选择。 在Android中如何使用Google Map,这里做一个简要的说明。 Google API_KEY的申请 Google Map 的使用并不是免费的&#xf…

主播岗位面试

一、自我介绍 在面试的开始阶段,你需要准备一个简洁而有力的自我介绍。这个自我介绍应该包括你的姓名、教育背景、工作经验以及你为何对这个主播职位感兴趣。这个自我介绍应该控制在1-2分钟之内,避免冗长的表述。 二、主播经历和特点 在这个环节&…

javaagent字节码增强浅尝

概述 javaagent 技术广泛应用于对代码的增强,比如统计方法执行时间、GC 信息打印、分布式链路跟踪等;实现方式包括 javassist 和 bytebuddy,bytebuddy 是对 javassist 的改进;类似于 spring 中的 AOP; Instrumentati…

京东数据运营-京东数据平台-京东店铺数据分析-2023年10月京东烘干机品牌销售榜

鲸参谋监测的京东平台10月份烘干机市场销售数据已出炉! 10月份,烘干机市场整体销售上涨。鲸参谋数据显示,今年10月份,京东平台上烘干机的销量将近5万件,环比增长约77%,同比增长约22%;销售额将近…

1657. 确定两个字符串是否接近 --力扣 --JAVA

题目 如果可以使用以下操作从一个字符串得到另一个字符串,则认为两个字符串 接近 : 操作 1:交换任意两个 现有 字符。 例如,abcde -> aecdb操作 2:将一个 现有 字符的每次出现转换为另一个 现有 字符,并…

我的养生指南

作为一名程序员,一直坐在电脑前工作对身体造成很大的负担,引起颈椎病、近视、肥胖等问题,因此养生对于程序员来说非常重要。深有体会,不知各位大佬是否有体会 以下是我的养生指南: 做一些适量运动,保持身体…

XUbuntu22.04之OBS强大录屏工具(一百九十五)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…

Windows——安装WSL子系统

下载及安装 教程:https://zhuanlan.zhihu.com/p/35801201 报错解决: WSL安装无法打开(WslRegisterDistribution failed with error: 0x800701bc…) https://www.jianshu.com/p/e2df6d091f73 环境配置 WSL2 的开发环境配置 (基…

NX/UG二次开发—踩坑(边上点与面上点)

获取视图内遮挡面时,特别是与视图平行的面认为是可视面,但NX选择认为是非可视面,设计方案时只检查边上的点,发现一些面显示干涉遮挡,通过打印数据发现,以边上点为参考,获取面上点,会…

kubernetes(K8s)(Namespace、Pod、Deployment、Service资源的基本操作)-04

Namespace Namespace是kubernetes系统中的一种非常重要资源,它的主要作用是用来实现多套环境的资源隔离或者多租户的资源隔离。 默认情况下,kubernetes集群中的所有的Pod都是可以相互访问的。但是在实际中,可能不想让两个Pod之间进行互相的…