WebGL正射投影

news2025/1/21 18:55:45

目录

可视范围(正射类型)

可视空间

正射投影的盒状可视空间的工作原理

盒状可视空间

定义盒状可视空间

Matrix4.setOrtho()

按键控制near、far

​编辑 示例效果

示例代码

代码详解

修改near和far值

通过右方向键增大near的值 

通过下方向键减小far的值 

修改近裁截面宽高导致物体“变形”

宽高缩小一半,保持宽高比

宽高缩小一半,高度不变,改变宽高比


可视范围(正射类型)

虽然你可以将三维物体放在三维空间中的任何地方,但是只有当它在可视范围内时,WebGL才会绘制它。事实上,不绘制可视范围外的对象,是基本的降低程序开销的手段。绘制可视范围外的对象没有意义,即使把它们绘制出来也不会在屏幕上显示。从某种程序上来说,这样做也模拟了人类观察物体的方式,如下图所示。我们人类也只能看到眼前的东西,水平视角大约200度左右。总之,WebGL就是以类似的方式,只绘制可视范围内的三维对象。

除了水平和垂直范围内的限制,WebGL还限制观察者的可视深度,即“能够看多远”。所有这些限制,包括水平视角、垂直视角和可视深度,定义了可视空间(view volume)。由于我们没有显式地指定可视空间,默认的可视深度又不够远,所以三角形的一个角看上去就消失了,如下图所示。 

可视空间

有两类常用的可视空间: 

● 长方体可视空间,也称盒状空间,由正射投影(orthographic projection)产生。

● 四棱锥/金字塔可视空间,由透视投影(perspective projection)产生。

在透视投影下,产生的三维场景看上去更是有深度感,更加自然,因为我们平时观察真实世界用的也是透视投影。在大多数情况下,比如三维射击类游戏中,我们都应当采用透视投影。相比之下,正射投影的好处是用户可以方便地比较场景中物体(比如两个原子的模型)的大小,这是因为物体看上去的大小与其所在的位置没有关系。在建筑平面图等技术绘图的相关场合,应当使用这种投影。

正射投影的盒状可视空间的工作原理

盒状可视空间的形状如下图所示。可视空间由前后两个矩形表面确定,分别称近裁界面(near clipping plane)和远裁截面(far clipping plane),前者的四个顶点为(right,top,-near),(-left,top,-near),(-left,-bottom,-near),(right,-bottom,-near),而后者的四个顶点为(right,top,far),(-left,top,far),(-left,-bottom,far),(right,-bottom,far)。

盒状可视空间

<canvas>上显示的就是可视空间中物体在近裁剪面上的投影。如果裁剪面的宽高比和<canvas>不一样,那么画面就会被按照<canvas>的宽高比进行压缩,物体会被扭曲(稍后详细讨论)。近裁剪面与远裁剪面之间的盒形空间就是可视空间,只有在此空间内的物体会被显示出来。如果某个物体一部分在可视空间内,一部分在其外,那就只显示空间内的部分。 

定义盒状可视空间

矩阵库WebGL矩阵变换库_山楂树の的博客-CSDN博客提供的Matrix4.setOrtho()方法可用来设置投影矩阵,定义盒装可视空间。

Matrix4.setOrtho()

我们在这里又用到了矩阵。这个矩阵被称为正射投影矩阵(orthographic projection matrix)。示例程序OrthoView将使用这种矩阵定义盒状可视空间,本例把视点置于原点处,视线为Z轴负方向。可视空间定义下图所示,near=0.0,far=0.5,left=-1.0,right=1.0,bottom=-1.0,top=1.0,三角形处于Z轴0.0到-0.4区间上。

此外,示例程序还允许通过键盘按键修改可视空间的near和far值。这样我们就能直观地看到这两个值具体对可视空间有什么影响。下面列出了各按键的作用。

按键控制near、far

 示例效果

示例代码

var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'attribute vec4 a_Color;\n' +
  'uniform mat4 u_ProjMatrix;\n' +
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  gl_Position = u_ProjMatrix * a_Position;\n' +
  '  v_Color = a_Color;\n' +
  '}\n';

var FSHADER_SOURCE =
  '#ifdef GL_ES\n' +
  'precision mediump float;\n' +
  '#endif\n' +
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  gl_FragColor = v_Color;\n' +
  '}\n';

function main() {
  var canvas = document.getElementById('webgl');
  // 获取dom对象nearFar
  var nf = document.getElementById('nearFar');
  var gl = getWebGLContext(canvas);
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to intialize shaders.');
    return;
  }
  var n = initVertexBuffers(gl);
  gl.clearColor(0, 0, 0, 1);
  var u_ProjMatrix = gl.getUniformLocation(gl.program, 'u_ProjMatrix');

  // 创建矩阵以设置视点和视线
  var projMatrix = new Matrix4();
  // 注册键盘事件响应事件
  document.onkeydown = function (ev) { keydown(ev, gl, n, u_ProjMatrix, projMatrix, nf); };
  draw(gl, n, u_ProjMatrix, projMatrix, nf);  
}

function initVertexBuffers(gl) {
  var verticesColors = new Float32Array([
    // 顶点和颜色数据
    0.0, 0.6, -0.4, 0.4, 1.0, 0.4, // 最后面的绿色三角形
    -0.5, -0.4, -0.4, 0.4, 1.0, 0.4,
    0.5, -0.4, -0.4, 1.0, 0.4, 0.4,

    0.5, 0.4, -0.2, 1.0, 0.4, 0.4, // 中间的黄色三角形
    -0.5, 0.4, -0.2, 1.0, 1.0, 0.4,
    0.0, -0.6, -0.2, 1.0, 1.0, 0.4,

    0.0, 0.5, 0.0, 0.4, 0.4, 1.0, // 最前面的蓝色三角形
    -0.5, -0.5, 0.0, 0.4, 0.4, 1.0,
    0.5, -0.5, 0.0, 1.0, 0.4, 0.4,
  ]);
  var n = 9;

  // 创建缓冲区对象
  var vertexColorbuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorbuffer);
  gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
  var FSIZE = verticesColors.BYTES_PER_ELEMENT;
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0);
  gl.enableVertexAttribArray(a_Position);
  var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
  gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
  gl.enableVertexAttribArray(a_Color);
  return n;
}

// 视点与近远裁面的距离
var g_near = 0.0, g_far = 0.5;
function keydown(ev, gl, n, u_ProjMatrix, projMatrix, nf) {
  switch (ev.keyCode) {
    case 39: g_near += 0.01; break;  // 按下右方向键
    case 37: g_near -= 0.01; break;  // 按下左方向键
    case 38: g_far += 0.01; break;  // 按下上方向键
    case 40: g_far -= 0.01; break;  // 按下下方向键
    default: return; // 按下了其他键
  }
  draw(gl, n, u_ProjMatrix, projMatrix, nf);
}

function draw(gl, n, u_ProjMatrix, projMatrix, nf) {
  // 使用矩阵设置可视空间(left right bottom top 近裁面 远裁面)
  projMatrix.setOrtho(-1.0, 1.0, -1.0, 1.0, g_near, g_far);
  // 将投影矩阵传给u_ProjMatrix变量
  gl.uniformMatrix4fv(u_ProjMatrix, false, projMatrix.elements);
  gl.clear(gl.COLOR_BUFFER_BIT);       // 清除 <canvas>
  // 显示当前的near和far值
  nf.innerHTML = 'near: ' + Math.round(g_near * 100) / 100 + ', far: ' + Math.round(g_far * 100) / 100;
  gl.drawArrays(gl.TRIANGLES, 0, n);   // 绘制三角形
}

本例定义了keydown()函数(第36行),每当按下按键时,匿名的事件响应函数就会调用keydown()函数。keydown()函数首先更新near和far的值,然后调用draw()函数进行绘制(第37行)。draw()函数将设置可视空间更新页面上文本显示的near和far的值,并绘制3个三角形(第84行)。最关键的事情是设置可视空间,就发生在draw()函数中。

代码详解

uniform变量u_ProjMatrix存储了可视空间的投影矩阵,我们将投影矩阵与顶点坐标相乘,再赋值给gl_Position(第7行)。

当键盘的上方向键被按下时,事件响应函数就会执行(第36行)并调用keydown()。注意我们将nf作为最后一个参数传入,这样keydown()函数就能够访问并修改dom元素了。keydown()函数最后调用了draw()函数绘制三角形,这样每次按键后都会重绘整个图形。 

keydown()函数首先检查哪个键被按下,然后根据按下的键,修改g_near和g_far的值(稍后这两个值将被传给setOrtho()函数以创建投影矩阵,设置可视空间),最后调用draw()函数。注意,这里g_near和g_far是全局变量(第72行),不管是keydown()还是draw()函数都可以访问它。 

再看一下draw()函数(第91行)它修改了网页上的文本信息。 

draw()函数计算出可视空间对应的投影矩阵projMatrix(第86行),将其传递给着色器中的u_ProjMatrix变量(第88行),接着在页面上更新near和far的值(第91行),最后绘制出三角形(第92行)。 

修改near和far值

运行程序,按下右方向键逐渐增加near值,你会看到三角形逐个消失了,如下图所示。

通过右方向键增大near的值 

 默认情况下,near值为0.0,此时3个三角形都出现了。当我们首次按下右方向键,将near值增加至0.01时,处在最前面的蓝色的三角形消失了,如上图中)所示。这是因为,蓝色三角形就在XY平面上,近裁剪面越过了蓝色三角形,使其处在了可视空间外,如下图所示。

蓝色三角形处于可视空间外

同样,如果你改变far的值,也会产生类似的效果,如下图所示。随着far值的逐渐减小,当值小于0.4时,绿色三角形首先消失,小于0.2时,黄色三角形消失,最终只剩下蓝色三角形。

通过下方向键减小far的值 

 这个示例程序清晰地展示了可视空间的作用。如果你想绘制任何东西,就必须把它置于可视空间中。

修改近裁截面宽高导致物体“变形”

在上述“可视空间”中曾说过,如果可视空间近裁剪面的宽高比与<canvas>不一致,显示出的物体就会被压缩变形。现在就来研究这一点。本例将近裁剪面的宽度和高度改为了原来的一半,但是保持了宽高比:

宽高缩小一半,保持宽高比

  

结果如下图所示,三角形变成了之前大小的两倍。这是由于<canvas>的大小没有发生变化,但是它表示的可视空间却缩小了一半。注意,三角形的有些部分越过了可视空间并被裁剪了。

宽高缩小一半,高度不变,改变宽高比

结果如下图所示,由于近裁剪面宽度缩小而高度不变,相当于把长方形的近裁剪面映射到了正方形的<canvas>上,所以绘制出来的三角形就在宽度上拉伸而导致变形了。 

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

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

相关文章

从物理叠加到化学反应,看方太如何把洗碗机玩出「新价值」

文 | 智能相对论 作者 | 佘凯文 随着酷暑烈日的逐渐远去&#xff0c;秋意也开始浓厚了起来。所谓“秋风起&#xff0c;秋膘贴”&#xff0c;在“金九银十”的当下&#xff0c;美食自是不可辜负的恩赐。肥美多膏的大闸蟹、软糯香甜的南瓜羹、爽脆可口的莲藕汤..... 秋季的美食…

idea中启动maven项目报错-java: 程序包lombok.extern.slf4j不存在问题如何解决

1、 现象&#xff1a; 在springboot的maven项目启动时&#xff0c;报错&#xff1a; Error:(3, 27) java: 程序包lombok.extern.slf4j不存在 编译不报错&#xff0c;maven依赖也合适&#xff0c;项目就是无法启动 原因&#xff1a; 其实不是项目本身或者maven本身的问题&am…

Java垃圾收集机制

目录 前言 判断对象是否存活 引用计数算法 可达性分析算法 GC Root的产生 Java中的四种引用类型 1.强引用 强引用弱化方式 方式1&#xff1a;使对象指向null 方式2&#xff1a;使对象超出作用域范围 2.软引用 3.弱引用 4.虚引用 垃圾收集算法 分代收集理论 垃圾…

Linux内核及可加载内核模块编程

图1 Linux系统整体结构 图2 Linux的源代码结构 下面显示一段内核模块代码案例&#xff1a; #include <linux/moduLe.h> #include <linux/kernel.h #include <linux/intt.h> /*模块的初始化函数lkp_ init()_init是用于初始化的修饰符 */ static int __init lk…

第十五届全国大学生数学竞赛报名快要截止了,你报上名了吗?

关于组织参加 第十五届全国大学生数学竞赛的通知 01 为了培养人才、服务教学、提高大学生学习数学的兴趣&#xff0c;培养学生分析问题、解决问题的能力&#xff0c;发现和选拔数学创新人才&#xff0c;为学生提供一个展示基础知识和思维能力的舞台&#xff0c;我校决定组织参…

SSM整合demo及个人思考

SSM整合 项目整体架构说明1. 创建Maven项目2. 配置web.xml4. 配置springmvc.xml5. 配置spring.xml6. 配置mybatis-config.xml以及创建mapper接口和mapper配置文件7. 配置log4j.xml8. 后端CURD测试8.1 在数据库中插入数据8.2 pojo中的实体类Employee8.3 mapper层的EmployMapper接…

oppo手机便签隐藏了一条怎样打开?手机如何找到隐藏便签?

有不少用户在使用OPPO手机的过程中&#xff0c;遇到了一些问题&#xff0c;例如自己在使用手机系统便签应用时&#xff0c;把一条重要的便签设置了隐藏&#xff0c;但是现在找不到隐藏的便签了。 那么oppo手机便签隐藏了一条怎样打开&#xff1f;OPPO手机如何找到隐藏便签&…

【C++】基础知识点回顾 上:命名空间与输入输出

前言 学习C一段时间后&#xff0c;再回过头来看这些C的基础知识&#xff0c;感觉有很多细节是自己当时没有注意的&#xff0c;所以写一篇文章来回顾复习一下C的基础知识。 命名空间的使用 相信很多朋友在学习C的第一个代码的时候&#xff0c;在写上头文件之后&#xff0c;紧…

运营商大数据实时获取精准数据

随着大数据技术的快速发展和完善&#xff0c;出现了一种新的扩张方式——互联网大数据的精准扩张。如果没有一个好的渠道来获得顾客&#xff0c;这就像准备热情地做饭&#xff0c;但当饭吃完后&#xff0c;人们只能饿了。 今天的消费者已经从最早的线下消费逐渐过渡到互联网消…

浅谈6种API架构模式

在摸鱼刷X时&#xff0c;看到一张非常棒的图&#xff0c;是关于不同API架构的&#xff0c;下面学习记录一下。 &#xff08;摘自网络&#xff09; 1、gRPC gRPC是一种高性能、跨语言、易扩展的远程过程调用(RPC)框架&#xff0c;可用于分布式系统之间的通信。gRPC被广泛地应用…

金融贷款行业实时高精准获客 ——三网运营商大数据

都说生产是第一因素&#xff0c;但对于任何企业来说&#xff0c;客户来源才是第一因素。 在大多数行业&#xff0c;获得客户的困难已经成为行业的挑战。如今&#xff0c;许多行业和企业获得客户的主要来源是在线促销和客户获取。现在几乎每个人都有一部手机。运营商可以根据移…

每日一练 | 网络工程师软考真题Day33

阅读以下说明&#xff0c;答复以下【问题1】和【问题2】 【说明】 某单位内部网络拓扑结构如图5-1所示&#xff0c;在该网络中采用RIP路由协议。 【问题1】 1&#xff0e;路由器第一次设置时&#xff0c;必须通过Console口连接运行终端仿真软件的微机进行配置&#xff0c;此时…

ByteV联合“智农”打造--数字孪生大棚可视化

ByteV联合“智农”打造的数字孪生大棚可视化&#xff0c;不仅要让粮食稳产、增产&#xff0c;更要对土壤肥力进行改良和提升。不仅能够实现科技引领农业发展&#xff0c;更在智慧农业的基础上实现一站式托管&#xff0c;真正做到技术提升、5G引领、建后管护的闭环管理。让高标准…

预测多基因扰动的转录结果

了解细胞对基因扰动的反应是许多生物医学应用的核心&#xff0c;从识别癌症中涉及的基因相互作用到开发再生医学方法。然而&#xff0c;可能的多基因扰动数量的组合爆炸严重限制了实验验证。在这里&#xff0c;作者提出了图增强的基因激活和抑制模拟器&#xff08;GEARS&#x…

Linux内核4.14版本——drm框架分析(13)——DRM_IOCTL_MODE_SETPLANE(drm_mode_setplane)

目录 1. drm_mode_setplane 1.1 根据应用的plane_req->plane_id找到plane 1.2 根据应用的plane_req->fb_id找到struct drm_framebuffer 1.3 调用setplane_internal 2. setplane_internal->__setplane_internal 2.1 struct drm_framebuffer是否为空 2.2 判断此p…

Cesium对WMS地图服务进行查询并弹框展示信息

一、简介 Cesium加载WMS地图服务&#xff0c;然后调用接口GetFeatureInfo对要素信息进行查询 二、测试接口 {type: "Point | MultiPoint | LineString | MultiLineString | Polygon | MultiPolygon",coordinates: 数组 } 查询点 http://123.56.67.147:8080/geoserve…

智慧公厕构建城市公共厕所中枢网络,民生服务更高效,城市管理更精准

随着城市化进程的加快&#xff0c;城市人口的不断增长&#xff0c;城市公共厕所问题愈发凸显。大量的人口涌入&#xff0c;公厕资源的不足、管理的不到位&#xff0c;已经成为困扰城市发展的一大难题。然而&#xff0c;智慧公厕的出现&#xff0c;为解决这一问题提供了新的思路…

2023-09-12 LeetCode每日一题(课程表 IV)

2023-03-29每日一题 一、题目编号 1462. 课程表 IV二、题目链接 点击跳转到题目位置 三、题目描述 你总共需要上 numCourses 门课&#xff0c;课程编号依次为 0 到 numCourses-1 。你会得到一个数组 prerequisite &#xff0c;其中 prerequisites[i] [ai, bi] 表示如果你…

这泼天的富贵,轮到数字化转型升级的企业了

数字化转型是建立在现代信息技术上&#xff0c;利用数字化的一切相关技术创建一种新的、或者对已有的商业模式进行重塑&#xff0c;以此来满足时代变化中传统的业务和市场进行变革。 数字化转型 - 派可数据BI可视化分析平台 出现这种变革是因为经过几十年的经济发展&#xff0…

怎么合并pdf文件到一起?快点过来尝试一下吧

怎么合并pdf文件到一起&#xff1f;pdf文件的使用越来越频繁&#xff0c;相信每个小伙伴都有这样的感受&#xff0c;不管是网上下载到的文件资料&#xff0c;还是合作伙伴发送过来的合作意向书&#xff0c;基本上都会做成pdf格式的文件&#xff0c;因为pdf文件具有更强的稳定性…