纹理贴图原理与实践【图形学基础】

news2024/9/22 3:48:27

纹理贴图是 20世纪90 年代 CG 的主要创新之一。 它允许我们在不添加大量几何基元(线、顶点、面)的情况下添加大量表面细节。 想一想 Caroline 的 loadedDemo 的所有纹理映射是多么有趣:
在这里插入图片描述

推荐:使用 NSDT场景编辑器 快速搭建 3D场景。

1、概念图

纹理贴图将图片绘制到多边形上。 虽然名称是纹理贴图,但一般方法只是采用像素数组并将它们绘制到表面上。 像素数组只是一张图片,它可能是布料、砖块或草地之类的纹理,也可能是荷马·辛普森 (Homer Simpson) 的照片。 它可能是你的程序计算和使用的东西。 更有可能的是,它是你从原始图像文件加载的内容。

演示:这些都是 307 演示列表的一部分。 你还不必担心代码。 我们稍后再看。

平面旗帜:这些纹理是我们用 JavaScript 计算的简单数组。 它们被映射到一个平面上:

  • 灰度(黑白)棋盘,
  • RGB 棋盘(黑色和红色)
  • 灰度美国国旗
  • 红色、白色和蓝色美国国旗

平面上的巴菲:这些是从单独的图像文件加载的纹理。

从概念上讲,要使用纹理,你必须执行以下操作:

  • 定义一个纹理:一个矩形像素阵列——纹素,纹理元素的缩写。 我们几乎可以互换使用这些术语,其中纹素是用于纹理映射的数组中的像素。
  • 为几何体的每个顶点指定一对纹理坐标 (s,t)
  • 图形系统将纹理“绘制”到多边形上。

2、纹理贴图原理

纹理贴图是一种光栅操作,不同于我们已经看过的任何其他东西。 然而,我们在 3D 模型中将纹理应用于 2D 表面,并且图形系统必须弄清楚如何在光栅化(也称为扫描转换)期间修改像素。

由于纹理映射是光栅化过程的一部分,所以让我们从这里开始。

3、光栅化

当显卡渲染多边形时,它理论上:

  • 确定每个角的像素坐标。
  • 确定多边形的边缘像素,使用画线程序(一个重要的是 Bresenham 的算法,我们没有时间研究)。
  • 确定单行边缘像素的颜色(通过顶点颜色的线性插值)。
  • 沿着行着色每个像素(通过两个边缘像素的线性插值)。

注意:标准术语是多边形称为片元(Fragment),因为它可能是贝塞尔曲面的片段或某种类似的多边形近似值。 因此,显卡将纹理应用于片元。

这一切都发生在帧缓冲区(保存屏幕上显示的像素的视频内存)或类似的数组中。

4、纹理贴图的实现

要进行纹理贴图,显卡必须:

  • 在光栅化过程中使用双线性插值计算每个像素的纹理坐标
  • 在纹素数组中查找纹理坐标(使用最近的或四个最近的线性插值)
  • 使用纹理的颜色作为像素的颜色,或者组合纹理和像素的颜色

5、纹理空间

我们可以有 1D 或 2D 纹理,尽管几乎总是 2D。 纹理参数将在每个维度的 [0,1] 范围内。 请注意,如果你的纹理数组不是正方形并且你的多边形也不是正方形,则你可能需要处理纵横比的变化。

纹理始终是一个数组,因此始终是一个矩形。 将纹理映射到矩形(作为 OpenGL 对象)相当容易; 将其映射到其他形状可能会导致失真。 在这些情况下我们需要小心。

将多边形的每个顶点与纹理参数相关联,就像我们将它与法线、颜色等相关联一样。 Three.js 具有 Geometry 对象的属性,专门用于表示三角形面的每个顶点的纹理坐标。

在这里插入图片描述

纹理坐标与二维纹理元素数组有何关系? 这最容易用这样的图片来解释:
在这里插入图片描述

这是一个包含 260 个像素的阵列,编号从 0 到 259,排列成 13 x 20 像素的矩形阵列。 注意,这在 OpenGL 和 Three.js 中是非法的,因为这两个维度都不是 2 的幂,但我们还是使用它吧。

  • 如你所料,纹素数组的第一个元素,即元素 [0][0] 与纹理坐标 (0,0) 相同。
  • 当我们沿着数组的第一行向下移动,直到到达元素 [0][RowLength]([0][19],即元素 19),我们到达纹理坐标 (1,0)。 这可能看起来很奇怪,但这是事实。
  • 当我们沿着数组的第一列向下移动时,直到到达元素 [ColLength][0]([12][0],即元素 240),我们到达纹理坐标 (0,1)。 同样,这可能看起来很奇怪,但这是事实。
  • 不出所料,纹素数组的最后一个元素是第一个元素对面的角,因此数组元素 [ColLength][RowLength]([12][19] 即元素 259)对应于纹理坐标 (1,1)。

通常,纹理坐标称为 (s,t),就像空间坐标称为 (x,y,z) 一样。 因此,我们可以说 s 沿着纹理的行(沿着旗帜的“fly”)。 t 坐标沿着纹理的列(沿着旗帜的“hoist”)。

虽然你经常会使用整个纹理,所以你所有的纹理坐标都是 0 或 1,但这不是必需的。 事实上,由于纹理数组的维度需要是 2 的幂,所以你想要的实际图像通常只是整个数组的一部分。

计算出的美国国旗数组具有该属性。 该数组为 256 像素宽 x 128 像素高,但旗帜本身为 198 像素宽 x 104 像素高。 因此,最大纹理坐标(如果你只想要标志而不想要灰色区域)是:

fly	= 198/256 = 0.7734
hoist	= 104/128 = 0.8125

在这里插入图片描述

结果可能如上图所示。

当然,我们还需要确保我们放置国旗的矩形与美国国旗的纵横比相同,即:1.9。 请参阅官方美国国旗规格。

纹理参数也可以大于1,在这种情况下,可以使用参数设置来获得纹理的重复。 如果 s 是某个参数,其中 0 < s < 1,中途指定纹理的某个部分,则 1+s、2+s 等是纹理中的相同位置。

6、使用计算的纹理

让我们从使用计算纹理的纹理贴图开始。 因为它们是经过计算的,所以它们会非常简单,但我们使用它们有两个原因:

  • 它强化了纹理只是一个数组的概念,并且
  • 它避免了加载额外文件和必须使用事件处理程序的问题

现在是时候查看我们第一个基本演示的代码了。

这是平面旗帜演示。 创建棋盘等的代码中包含什么并不重要,但只要意识到每个都返回一个像素数组即可。 最重要的代码行在最后。
在这里插入图片描述

这是代码的基本部分。 请特别注意 makeFlag() 的实现。 我试图让它尽可能简单。 下面的代码仅用来:

  • 设置纹理
  • 创建网格

请注意,纹理是材质的属性,而不是几何体的属性。 但是,几何体为每个顶点定义(默认的)纹理参数。 面的各个像素的纹理参数是通过面部三个顶点的纹理参数进行插值来完成的。

其他一切都与我们之前看到的相似:

TW.makeFlagTexture = function (nickname) {
    // creates a texture as an array, then creates and returns an
    // THREE.DataTexture possible nicknames are 'nascar', 'checks'
    // 'US-Gray' and 'US-RWB' The last is the red, white and blue US flag.
    var size, array, width, height, format;
    switch (nickname) {
    case 'nascar':
        size = 3;
        array = TW.createCheckerboardGray(size);
        width = height = TW.power2(size);
        format = THREE.LuminanceFormat;
        break;
    case 'checks':
        size = 3;
        array = TW.createCheckerboardRedWhite(size);
        width = height = TW.power2(size);
        format = THREE.RGBFormat;
        break;
    case 'US-Gray':
        size = 4;
        array = TW.createUSFlagGray(size);
        height = TW.power2(size);
        width = 2*height;
        format = THREE.LuminanceFormat;
        break;
    case 'US-RWB':
        size = 4;
        array = TW.createUSFlagRedWhiteBlue(size);
        height = TW.power2(size);
        width = 2*height;
        format = THREE.RGBFormat;
        break;
    default:
        throw "don't know this flag nickname: "+nickname;
    }
    // console.log("flag stuff: ",array, width, height, format);
    var obj = new THREE.DataTexture( array, width, height, format);
    // we'll explain these filters soon
    obj.minFilter = THREE.NearestFilter;
    obj.magFilter = THREE.NearestFilter;
    obj.needsUpdate = true;
    return obj;
}
 
 
function makeFlag(name) {
    var flagTexture = TW.makeFlagTexture(name);
    var flagGeom = new THREE.PlaneGeometry( 8, 4);
    var flagMat = new THREE.MeshBasicMaterial(
        {
            color: THREE.ColorKeywords.white,
            map: flagTexture,
        });
    var flagMesh = new THREE.Mesh( flagGeom, flagMat );
    return flagMesh;
}

7、设置纹理坐标

前面我们看到几何对象定义每个顶点的纹理坐标。 更早的时候,我们并不总是想使用默认的 (0,1) 纹理坐标。 我们可能想使用 (0.77,0.81) 作为美国国旗的最大纹理坐标。 那么,如何更改默认纹理坐标,或将它们设置在你自己的几何对象上?

在 THREE.js 中,纹理坐标保存在 THREE.Geometry 的一个名为 faceVertexUvs 的属性中,有些人不使用 S 和 T,而是使用 U 和 V;它们都出现在 THREE.js 代码中。 这个属性是单元素数组(目前我还没有确定),该元素是一个面uv的数组,其中一个面UV是一个三元数组,对应面的三个顶点,每一个 其中的元素是一个 THREE.Vector2,它捕获 U 和 V 值。

让我们试着用一个具体的例子来理解这一点。 我们将考虑之前用于映射 Buffy 面部的几何对象。 这是一个简单的二维平面(一个矩形):

  planeGeom = new THREE.PlaneGeometry( 4, 4);

让我们看看这个数据结构。 首先,顶点:

JSON.stringify(planeGeom.vertices)
[{"x":-2,"y":2,"z":0},   // 0
 {"x":2,"y":2,"z":0},    // 1
 {"x":-2,"y":-2,"z":0},  // 2
 {"x":2,"y":-2,"z":0}    // 3
]

没有什么太令人惊讶的了。 有四个顶点,所有 z = 0,x 和 y 值在 +2 和 -2 中。 现在让我们看看这两个面,它们的顶点定义为上面数组的索引。

planeGeom.faces[0]
THREE.Face3 {a: 0, b: 2, c: 1, normal: THREE.Vector3, vertexNormals: Array[3]…}
planeGeom.faces[1]
THREE.Face3 {a: 2, b: 3, c: 1, normal: THREE.Vector3, vertexNormals: Array[3]…}

所以,这两个三角形面就是左上三角形和右下三角形。 最后,让我们看看 6 个顶点中每个顶点的 UV 值(两个面各三个):

> JSON.stringify(planeGeom.faceVertexUvs)
[
  // array of two elements
  [
   [{"x":0,"y":1},{"x":0,"y":0},{"x":1,"y":1}],  // elt 0 is for face 0
   [{"x":0,"y":0},{"x":1,"y":0},{"x":1,"y":1}]   // elt 1 is for face 1
  ]
]

奇怪的是,这两个坐标在这些对象中被命名为“x”和“y”,而不是你可能期望的“u”和“v”(甚至是“s”和“t”)。

这是一张可能有帮助的图片:
在这里插入图片描述

六组纹理坐标,两个三角形面(绿色面和红色面)各三个。

8、修改 faceVertexUvs

考虑以下函数,它像我们一样更新 THREE.PlaneGeometry 的 S 和 T 值:

function updateTextureParams(quad, sMin, sMax, tMin, tMax) {
    var elt = quad.faceVertexUvs[0]; // dunno why they have this 1-elt array
    var face0 = elt[0];
    face0[0] = new THREE.Vector2(sMin,tMax);
    face0[1] = new THREE.Vector2(sMin,tMin);
    face0[2] = new THREE.Vector2(sMax,tMax);
    var face1 = elt[1];
    face1[0] = new THREE.Vector2(sMin,tMin);
    face1[1] = new THREE.Vector2(sMax,tMin);
    face1[2] = new THREE.Vector2(sMax,tMax);
    quad.uvsNeedUpdate = true;
}

使用该函数,我们可以将美国国旗映射到没有灰色区域的平面上:

在这里插入图片描述

然而,这样做的代码并不直观,因为默认的 THREE.js 行为是翻转垂直纹理参数。 这称为 .flipY。 所以,不是设置T 参数:

  • 从左上角的0开始,
  • 到左下角的 0.8

我们实际上将其设置为:

  • 从左下角的 0.2 = 1-0.8,
  • 到左上角的 1

也就是说,对于翻转的 Y,左上角的坐标为 (0,1),左下角的坐标为 (0,0.2)。 要拉出那一块,我们必须按照以下方式设置纹理参数:

  updateTextureParams(flagGeom,0,0.75,1-0.81,1);

9、加载图像

这个演示展示了一个图像文件被加载并纹理映射到我们之前使用的同一平面上。

不过这个代码有一个非常棘手的部分。 当我们计算一个数组并将其用作纹理时,该数组已经可用于渲染。 对于外部图像,在数据从某个网络源到达之前会有一些延迟。 这种延迟可能会持续到几百毫秒,但与代码在 JavaScript 中的运行速度相比,即使是几毫秒也是一个巨大的时间量。

因此,如果我们所做的唯一渲染是在引用图像之后,代码将根本无法工作。 这是我描述的情况的伪代码:

    var buffyTexture = new THREE.ImageUtils.loadTexture( "../../images/buffy.gif",
                                                         new THREE.UVMapping());
    var buffyMat = new THREE.MeshBasicMaterial(
        {color: THREE.ColorKeywords.white,
         map: buffyTexture});
    
    var buffyMesh = new THREE.Mesh( planeGeom, buffyMat );
    scene.add(buffyMesh);
    TW.render();

在第一行(当对图像的请求被发送到服务器时)和最后一行(当渲染场景时)之间根本没有时间加载图像。 如果你尝试这样做,平面将是空白的。

解决方案是使用事件处理程序。 事件处理程序是你希望在某些事件发生后运行的代码的通用解决方案。 在这种情况下,事件是图像数据终于从服务器到达。 然后事件处理程序可以调用渲染器。

THREE.js 做这件事的方式也很标准:传入一个函数,当事件发生时调用它。 这是改进后的代码:

 var planeGeom = new THREE.PlaneGeometry( 4, 4);
    var imageLoaded = false;
    var buffyTexture = new THREE.ImageUtils.loadTexture( "../../images/buffy.gif",
                                                         new THREE.UVMapping(),
                                                         // onload event handler
                                                         function () {
                                                             console.log("image is loaded.");
                                                             imageLoaded = true;
                                                             TW.render();
                                                         });
    var buffyMat = new THREE.MeshBasicMaterial(
        {color: THREE.ColorKeywords.white,
         map: buffyTexture});
    
    var buffyMesh = new THREE.Mesh( planeGeom, buffyMat );
    return buffyMesh;
}

在上面的代码中,我们传入了一个匿名函数作为事件处理程序。 当图像完成加载并渲染场景时,它会被调用。


原文链接:纹理贴图原理与实践 — BimAnt

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

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

相关文章

树的重心(树和图的遍历--dfs)

树和图的存储&#xff1a; 定义h[N]&#xff0c;用来存储多个head指针。然后利用单链表的思想将数字插入进去。 void add( int a , int b ) { e[idx]b , ne[idx]h[a] , h[a]idx; } -----------------------------------------------------------------------------…

什么是类?怎样声明类的继承关系?

在现实生活中&#xff0c;说到继承&#xff0c;多会想到子女继承父辈的财产、事业等。在程序中&#xff0c;继承描述的是事物之间的所属关系&#xff0c;通过继承可以使多种事物之间形成一种关联体系。例如猫和狗都属于动物&#xff0c;程序中便可以描述为猫和狗继承自动物&…

从零开始的数模(十二)时间序列

目录 一、概念 1.2方法 二、基于python的时间序列 2.1移动平均法 2.2指数平滑法 2.3灰色预测 2.4灰色关联 2.5ARIMA模型 模型系 三、 基于matlab的时间序列 3.1移动平均法 3.2指数平滑法 一次指数平滑法 二次指数平滑法 一、概念 1.1带有时间的数据有哪些特殊性 带…

OAuthApp v2.2.3 更新 | 前端发布工具

OAuthApp 是一个前端发布工具&#xff0c;用于快速开发前端网页项目&#xff0c;并发布到服务器。具有引入脚本库就能使用服务端 API、在线发布 H5、站点数据独立存储的特性。 2023-2-3 主要更新 1&#xff0c;[修复] 站点文件功能&#xff0c;上传图片报错。 2&#xff0c;[新…

某FPS游戏飞天辅助及原理

FPS游戏先天的竞技性以及对战性决定了他必然有很多的BUG可以被利用又必须的去检测解决。 FPS游戏中有这样的外挂&#xff0c;飞在高空中打敌人,因为很少有人会注意头顶 躲在墙壁中攻击敌人&#xff0c;敌人根本无法看到高空墙壁中的人物。 那么胜利就很简单了。 FPS游戏能够…

4000字IB EE论文该怎么解决?

IB课程的Extend Essay&#xff0c;即EE拓展论文&#xff0c;是一篇基于六门学科课程的论文&#xff0c;需要学生在一年左右的时间里&#xff0c;完成一篇将近4000字&#xff08;中文为4800字&#xff09;的论文。该论文与某一门IB学科组课程相关。学生在论文中要体现自己对这门…

Windows Server 2022 Install Veeam Backup 12

Veeam Backup & Replication 是一款可靠的四合一解决方案&#xff0c;将备份、复制副本、存储快照和 CDP 复制副本统一在一款可靠的数据保护解决方案下&#xff0c;可助力实现数据保护现代化并消除停机。通过有效的勒索软件防护实现更快速、更灵活的恢复和保留选项&#xf…

seo网站内容优化有哪些(网站链接怎么做)

网站链接seo过程中&#xff0c;如果存在死链接会对网站造成影响吗&#xff1f;如果网站中有过多的死链接存在&#xff0c;会对网站产生什么样的影响呢&#xff1f;针对这个问题&#xff0c;小编就带大家一起来分析一下。 如果网站内存在着大量的死链接&#xff0c;当搜索引擎蜘…

vue2低代码平台搭建(二)揭秘页面设计器

前言 大家好,我是L丶Y。我们在上一篇文章中走进了低代码的世界,这一章节我们要开始干货内容了,来探索一下低代码开发的核心 —— 页面设计器。 我们知道,低代码开发平台都是通过拖拉拽可视化的页面设计器进行页面开发的,在这一章节,我们来探索一下页面设计器的实现方式…

在Manjaro平台安装flutter开发环境

安装flutter 安装flutter&#xff0c;配套的java建议使用openjdk 11。 yay -S flutter安装好以后注意看提示&#xff0c;还需要额外执行几条命令&#xff1a; sudo gpasswd -a ${USER} flutterusers newgrp flutterusers安装android studio yay -S android-studio配置andro…

yaml、yml:配置介绍及用法

目录 1、yaml介绍 2、yaml语法规则 3、yml显示自动提示 1、yaml介绍 YAML (YAML Ain t Markup Language) &#xff0c;一种数据序列化格式 优点: 1、容易阅读 2、容易与脚本语言交互 3、以数据为核心&#xff0c;重数据轻格式 YAML文件扩展名 .yml(主流) .yaml 2、ya…

2022年末29个省市区14企业征信机构分析及申请建议

2013年1月21日&#xff0c;国务院颁布了《征信业管理条例》(国务院令第631号)&#xff0c;自2013年3月15日起施行。该条例要求设立经营企业征信业务的征信机构&#xff0c;自公司登记机关准予登记之日起30日内向所在地的国务院征信业监督管理部门派出机构办理备案。后陆续出台了…

ubuntu 下的opencv3的下载与实现简单功能

看自己用户的名字 whoami 例子1&#xff1a; #include <stdio.h> #include <opencv2/opencv.hpp>using namespace std; using namespace cv;int main() {cv::Mat image;namedWindow("DebugWindow", WINDOW_NORMAL); resizeWindow("DebugWindow&q…

OAuth2(2)

目录 一、OAuth2的项目介绍 1.搭建OAuth2流程演示 2.创建项目结构 ① 客户 ② 认证服务器 ③ 资源所有者 ④ 资源服务器 ⑤ 客户发送请求 3.流程 一、OAuth2的项目介绍 1.搭建OAuth2流程演示 ① 下载代码 演示代码下载&#xff1a; https://gitee.com/lisenaq/…

MySQL8.0-Linux版安装

MySQL8.0.26-Linux版安装 1. 准备一台Linux服务器 云服务器或者虚拟机都可以; Linux的版本为 CentOS7; 2. 下载Linux版MySQL安装包 https://downloads.mysql.com/archives/community/ 3. 上传MySQL安装包 4. 创建目录,并解压 mkdir mysqltar -xvf mysql-8.0.26-1.el7.x86…

redis四:redis实现分布式锁

文章目录redis实现分布式锁环境搭建redis手写分布式锁redisson 分布式锁分析springboot 整合 redissonredisson原理分析redisson源码分析加锁逻辑锁续命逻辑redisson获取不到锁自旋逻辑解锁逻辑redis实现分布式锁 环境搭建 搭建nginx 模拟分布式情况 upstream redissonlock{…

排序算法的实现

文章目录 一、排序的概念及其运用 1.排序的概念2.常见的排序算法二、常见排序算法的实现 1.插入排序 1.直接插入排序2.希尔排序2.选择排序 1.直接选择排序2.堆排序3.交换排序 1.冒泡排序2.快速排序 1.hoare版本2.挖坑法3.前后指针版本4.归并排序5.非比较排序三、排序算法复杂度…

AOSP 8.0 系统启动之一内核启动

目录 一、前言 二、涉及源码​​​​​​​ 三、源码分析​​​​​​​ 一、前言 Android本质上就是一个基于Linux内核的操作系统&#xff0c;与Ubuntu Linux、Fedora Linux类似&#xff0c;我们要讲Android&#xff0c;必定先要了解一些Linux内核的知识。 Linux内核的东西…

LeetCode - 630 课程表Ⅲ

目录 题目来源 题目描述 示例 提示 题目解析 算法源码 题目来源 630. 课程表 III - 力扣&#xff08;LeetCode&#xff09; 题目描述 这里有 n 门不同的在线课程&#xff0c;按从 1 到 n 编号。给你一个数组 courses &#xff0c;其中 courses[i] [durationi, lastDay…

被面试官问住了,MySQL两阶段提交是什么鬼?

前言 MySQL通过两阶段提交的机制&#xff0c;保证了redo log和bin log的逻辑一致性&#xff0c;进而保证了数据的不丢失以及主从库的数据一致。 而说起两阶段提交&#xff0c;就不得不先介绍一下redo log和bin log。 redo log redo log即重做日志&#xff0c;是InnoDB引擎特…