webgl编程指南源码_ThreeJS 源码剖析之 Renderer(一)

引子?

最近,忽然想起曾在 WebGL 基础系列 文章中立下 flag:“后续还打算出 《ThreeJS 源码剖析》 系列”(特意翻出原话?),项目忙了一阵后,便决定开始写此系列,更新周期不固定,毕竟项目排期“天晓得”。此系列与其说是分享,倒不如说是共同学习罢了。作为最出名的 WebGL 的开源库,ThreeJS 已受到了广泛认可和好评,同时其自身也在不断更新,让开发者用的舒心放心。

说回自己,本初入图形学世界,仅凭个人兴趣撞的鼻青脸肿,但也乐在其中。自认为技术本不该有距离感,以有趣且通俗易懂的方式传播技术也是一件趣事。任凭其吹的天花乱坠,何等高大上,但其本质是服务于我们的,归根结底还是要我们接受才可。故之前的文章也尽量保持着基础、有趣、易懂的风格(偶尔会开开车?),未来亦会尝试更多元的文章风格。但其宗旨都是为了让各位更容易接受。

以上仅个人观点,如与各位观念有相悖之处,可相互交流,以彼之长补己之短。

下面正式开始 ThreeJS 源码剖析 系列?

前言?

当学习一种新技术时,大部分情况都会首选其官方文档,而文档中能最快让我们上手的章节便是 Getting Started。ThreeJS 也不例外,我们进入到其对应的 Getting Started 页面后,便能看到 Creating the scene 的代码仅有下面寥寥几行:

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

再滚动至最下方,各位会看到完整的 Demo:

// ...
var animate = function () {
  requestAnimationFrame( animate );

  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;

  renderer.render( scene, camera );
};

animate();

阅读完简单的十几行代码之后,各位可能会发现这个 Demo 中在一直调用 animate() 方法,而此方法中我们修改了立方体的旋转角度,并执行了这一行代码:renderer.render( scene, camera )。假如你没有接触过图形学或 WebGL,仅凭语义会翻译成:“渲染器渲染场景和摄像机”。没错,这一行的作用正是如此!

“我看你骨骼惊奇,理解力非凡,一定是个学图形学的天才”(这也体现了命名规范合理是多么重要)?

ae17f6ab8dba595526cdfa016885d253.png

假如我们将上面那句话拆解一下:“渲染器 - 渲染 - 场景和摄像机”,这句话正符合了我们的 “主 - 谓 - 宾”的结构。那么就可见主语(即渲染器)是个很重要的存在!所以此系列的第一篇文章我们就从渲染器 —— Renderer 说起。

Renderer⚙️

将 ThreeJS 文档目录滚动至 Renderers 一栏,可以看到有 WebGLMultisampleRenderTarget 、WebGLRenderer 、WebGL1Renderer 、WebGLRenderTarget 和 WebGLCubeRenderTarget 五类渲染器,而我们就聚焦于 WebGLRenderer 这个渲染器即可,稍后会给大家介绍一下为何会有 WebGL1Renderer,至于其他三种渲染器,我们就不先讨论(其实我也没用过,后续了解了咱们再聊)?

WebGL1Renderer

假如你对 WebGL 稍有了解,那么就会知道其实 WebGL 有 1.0 和 2.0 两个版本,那么两个版本分别是基于 OpenGL ES 2.0 和 OpenGL ES 3.0 的,至于不基于 OpenGL ES 1.0 是因为 1.0 是固定管线的,2.0 和 3.0 是可编程管线。所谓固定管线就是我们给管线配置相应参数和开关即可,可编程管线的可编程部分就是我们所熟知的 顶点着色器 和 片元着色器。

扯远了,再说回 WebGL,这俩版本有什么差别呢?大家可以看本文 WebGL What's New,简单概括一下就是多了更多纹理格式、内置函数、3D 纹理贴图,同时还支持了非2的整数次方大小的图片。同时,WebGL 2.0 与 WebGL 1.0 在对浏览器的兼容性上有很大的差异,以 Chrome 为例,WebGL 1.0 兼容 9 及以上的 Chrome 版本,而 WebGL 2.0 则兼容 56 及以上的 Chrome 版本,对浏览器的兼容性的巨大差异就有极大的可能让一些陈旧的 WebGL 1.0 的系统崩溃,故 ThreeJS 提供了 WebGL1Renderer 来进行适配兼容。ThreeJS 已经从 r118 版本就全面升级到 WebGL 2.0 了,所以如果你的系统使用的 ThreeJS 版本低于 r118,并且准备升级到最新的 r120 版本,为了避免程序挂掉你需要将 Renderer 替换成 WebGL1Renderer!如何查看最新 ThreeJS 版本请见下图:

e25efc5f191e1254a1ebcd0e2260d9a0.png

而查看当前项目使用的 ThreeJS 版本请见 package.json 中的 ThreeJS 依赖版本的 MINOR 版本号即可,比如:three: 0.120.0 就表明是 r120 版本,npm 语义化版本请见官方文档。

WebGLRenderer

说回我们的主角 WebGLRenderer,进入到源码(src/renderers/WebGLRenderer.js)后可以看到头部有很多参数初始化的操作,在次就不一一介绍,关于参数含义大家可参考文档:了解参数含义请阅读 Constructor 部分。其中需要提一下的是,大家可能会发现这条语句:

const _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );

这里使用的是 createElementNS 并非 createElement,二者区别及用途请阅读 Document.createElementNS: What's the difference and why we need it。

getContext

当我们 new 一个 Renderer 时,其内部会最先进入这下面的语句:

if ( _gl === null ) {
  const contextNames = [ 'webgl2', 'webgl', 'experimental-webgl' ];
  if ( _this.isWebGL1Renderer === true ) {
    contextNames.shift();
  }
  _gl = getContext( contextNames, contextAttributes );
  if ( _gl === null ) {
    if ( getContext( contextNames ) ) {
      throw new Error( 'Error creating WebGL context with your selected attributes.' );
    } else {
      throw new Error( 'Error creating WebGL context.' );
    }
  }
}

首先当 WebGL 上下文不存在时,Renderer 内部列出了浏览器所提供的所有 WebGL 上下文名称,如若是 WebGL1Renderer,则删去 WebGL 2.0 的上下文名称,然后调用 getContext 方法获取上下文。getContext 方法实现也十分简单:

function getContext( contextNames, contextAttributes ) {
  for ( let i = 0; i     const contextName = contextNames[ i ];
    const context = _canvas.getContext( contextName, contextAttributes );
    if ( context !== null ) return context;
  }
  return null;
}

遍历上方列举的上下文名称,如果获取上下文则返回,否则返回 null,这么简单的代码聪明的你也能写出来你,四舍五入你和 ThreeJS 贡献者的水平就是一样的?

initContext

当获取到上下文后,紧接着调用的就是 initGLContext 方法了:

function initGLContext() {
  extensions = new WebGLExtensions( _gl );
  capabilities = new WebGLCapabilities( _gl, extensions, parameters );
  // 若不是 WebGL 2.0,则需额外获取以下 extensions
  if ( capabilities.isWebGL2 === false ) {
    // ...
  }
  extensions.get( 'OES_texture_float_linear' );
  
  utils = new WebGLUtils( _gl, extensions, capabilities );

  state = new WebGLState( _gl, extensions, capabilities );
  state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor() );
  state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor() );

  // ...
}

用导游的话来讲:“首先映入我们眼帘的是 new WebGLExtensionsnew WebGLCapabilities 两条语句”,WebGLExtensions 的作用是判断某个 extension 是否存在,以及获取指定的 extension 并 全量返回,而 WebGLCapabilities 则是获取当前 WebGL 系统的一些属性,如:支持的最大精度、是否是 WebGL 2.0 以及所支持的最大贴图大小等。而下面的 WebGLUtils 中只有一个 convert 方法,该方法主要是 将自定义的类型转换成 WebGL 内置的类型。

紧随其后的就是 WebGLState,别看它名字很短,但是它却很重要!用描述蔡总(我们项目组的翘楚)的一句话就是 “短,但有用!” 通过这个 state,我们可以设置视口大小、设置混合、设置材质、绑定纹理等。其余的内容就先省略,后续有必要再讲,先让各位知道 Renderer 内部的工作流程。

调用完 initGLContext 方法后就有一句:

const xr = new WebXRManager( _this, _gl );

WebXR 是一组支持将渲染3D场景用来呈现虚拟世界(虚拟现实,也称作 VR)或将图形图像添加到现实世界(增强现实,也称作 AR)的标准,详情请了解MDN文档。

animation

调用完 initContext 后,就来到了 animation 环节:

const animation = new WebGLAnimation();
animation.setAnimationLoop( onAnimationFrame );
if ( typeof window !== 'undefined' ) animation.setContext( window );

animation 的调用更是只有这简单的 3 行语句,WebGLAnimation 的类型如下:

class WebGLAnimation {
 start(): void;
 stop(): void;
 setAnimationLoop( callback: Function ): void;
 setContext( value: Window ): void;
}

当调用 setAnimationLoop 时,其实就是将传入的 callback 赋给 WenGLAnimation 内部的 animationLoop 变量,当调用 start 方法时,就调用 windowrequestAnimationFrame 方法。所以 ThreeJS 内部的动画也是不断调用 requestAnimationFrame 来实现的,呵!凡人!

0b8c4f79dc323e79a2cc629708974316.png

setContext 则是将 context 设为 window,至于 stop 方法大家应该也能猜到里面如何停止动画的了吧!

结束语?

这么快就结束语了?你是不是给我偷工减料了!?

其实 WebGLRenderer 大约 2000 行代码,但其中绝大部分是函数声明供我们后续的使用。回顾本文,大家就会发现当我们 new 一个 Renderer 时 ThreeJS 只不过是帮我们初始化了 context,并设置了一下 animation,其余的函数都是后续才会用到。故初始化一个 Renderer 也没什么故弄玄虚的!

既然内部有这么多的函数让我们后续使用,那么下篇文章就来讲讲我们常用的 Renderer 中的方法!

感谢阅读!

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

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

相关文章

python之集合与字典

01 一山不容二虎的集合 1.通俗来说,集合(set)是一个无序的不重复元素序列,就是一个用来存放数据的容器。 1)集合里面的元素是不可重复的: 如: s {1,2,3,4,1,2,3} print(s,type(s)) # 输出结…

求最大公约数——辗转相除法

最大公约数的基本原理: 两个数的最大公约数是指能同时整除它们的最大正整数。 设两数为a、b(a≥b)&#xff0c;求a和b最大公约数。 辗转相除法 代码如下&#xff1a; //只截取了一部分&#xff0c;完整代码可看下方 while (b<0){t a % b;a b;b t;}我们可以发现辗转相…

opencv roberts算子_边缘检测 Roberts算子

Roberts算子是一种最简单的算子&#xff0c;利用局部差分算子寻找边缘的算子。采用对角线相邻两像素之差近似梯度幅值检测边缘。。检测垂直边缘的效果比斜向边缘要好&#xff0c;定位精度高&#xff0c;对噪声比较敏感&#xff0c;无法抑制噪声的影响。Roberts边缘算子是一个2x…

Python习题week1

知识点1&#xff1a; 输入变为浮点数&#xff1a;score input("请输入成绩&#xff1a;") score float(score) 1题目&#xff1a;利用条件运算符的嵌套来完成此题&#xff1a;学习成绩>90分的同学用A表示&#xff0c;60-89分之间的用B表示&#xff0c;60分以下…

参数传值swap

对于下面的代码&#xff1a; #include <stdio.h> void swap(int a,int b);int main() {int a5;int b7;swap(a,b);printf("a%d,b%d",a,b);return 0; }void swap(int a,int b) {int t a;ab;bt; }输出结果却是&#xff1a; a5,b7 Process returned 0 (0x0) ex…

python在材料方面的应用_python记录材料题带标准答案

1.Python 是如何进行内存管理的&#xff1f; 答 : 从三个方面来说 , 一对象的引用计数机制 , 二垃圾回收机制 , 三内存池机制 一、对象的引用计数机制 Python 内部使用引用计数&#xff0c; 来保持追踪内存中的对象&#xff0c; 所有对象都有引用计数。 引用计数增加的情况&…

Python习题week2

知识点总结&#xff1a; 1.去除数字前面的零 Num int(input("请输入正整数&#xff1a;")) 2. 矩阵方式输入处理 # 输入方法3 按矩阵形式输入 list_juzhen [] for i in range(3):input_str input()input_list input_str.split(" ")list_juzhen.app…

74ls90设计十进制计数器电路图_PLC控制系统的设计与调试步骤你知多少?

作为电气工程技术人员我们在平时工作中常常要对PLC控制系统进行设计与调试&#xff0c;下面给各位朋友分享一下PLC控制系统的设计与调试步骤。PLC控制系统的设计与调试过程是这样的&#xff1b;第一点需要我们深入了解被控制系统。我们在设计前应该熟悉图样资料&#xff0c;并深…

python习题week3

1、从输入的字典中获取给定key的值&#xff0c; 实现函数&#xff1a;get_key_value(source_dict, key, index0)&#xff0c;source_dict指给定的字典&#xff0c;key指需要获取的key字段&#xff0c;index指的是需要获取相同key的第几个值&#xff0c;从0开始&#xff0c;默认…

python硬件_「大神器!」硬件的AI性能测试Python库发布

目前人工智能技术发展速度很快&#xff0c;也很吸引眼球。但是对于各种多如牛毛的方法&#xff0c;目前并米有一个可靠的精准的基准来衡量各项硬件在不同算法训练和推理的性能。现在&#xff0c;不用愁了。国外的一个哥们&#xff0c; Andrey Ignatov发布了一个python库。大家可…

Python random模块seed理解

想要在同一个程序中产生同一组随机数,需要在下一个函数设置一个相同的随机种子 random.seed(0)&#xff0c;其中的0是对应的随机数的种子&#xff0c;如果不设置这个值&#xff0c;则系统根据时间来自己选择这个值&#xff0c;此时每次生成的随机数因时间差异而不同 import …

python生成1到100的列表_python列表生成式与列表生成器的使用

列表生成式&#xff1a;会将所有的结果全部计算出来&#xff0c;把结果存放到内存中&#xff0c;如果列表中数据比较多&#xff0c;就会占用过多的内存空间&#xff0c;可能会导致MemoryError内存错误或者导致程序在运行时出现卡顿的情况 列表生成器&#xff1a;会创建一个列表…

python之异常处理

关于错误和异常 &#xff08;1&#xff09;概念&#xff1a;错误是无法通过其他代码进行处理问题&#xff0c;如语法错误和逻辑错误&#xff0c;语法错误是单词或格式等写错&#xff0c;只能根据系统提示去修改相应的代码&#xff0c;逻辑错误是代码实现功能的逻辑有问题&…

python图像质量评价_OpenCV图像质量评价的SSIM算法(图像相似度)

添加函数体。将SSIM函数添加至命名空间后。该函数主要功能是时哟功能ssim算法对两张图像进行比较&#xff0c;并将图像在各通道比较的结果以scalar形式返回。 Scalar getMSSIM(char * imagePatha,char * imagePathb) { Mat i1imread(imagePatha); Mat i2imread(imagePathb); co…

Python 装饰器理解

1、定义&#xff1a; &#xff08;1&#xff09;装饰器指的是为被装饰对象添加额外功能的工具/函数。 2、使用装饰器的意义&#xff08;原因&#xff09;&#xff1a; 如果我们已经上线了一个项目&#xff0c;我们需要修改某一个方法&#xff0c;但是我们不想修改方法的使用…

什么是python标识符_Python:标识符Identifier

什么是标识符&#xff1f; 标识符 (ldentifier) :是一个用来标识变量、函数、类、模块或其他对象的名称。 标识符规范 1&#xff09;标识符由字母、下划线和数字组成 2&#xff09;不能以数字开头 3&#xff09;区分大小写 4&#xff09;不能使用关键字 命名规则 1、见名知意。…

Python 可变参数*args和**kwargs

多个实参&#xff0c;放到一个元组里面,以*开头&#xff0c;可以传多个参数&#xff1b;**是形参中按照关键字传值把多余的传值以字典的方式呈现 *args&#xff1a;表示的就是将实参中按照位置传值&#xff0c;多出来的值都给args&#xff0c;且以元组的方式呈现&#xff0c; …

springboot security 权限校验_十二、SpringBoot 优雅的集成Spring Security

前言至于什么是Spring security &#xff0c;主要两个作用&#xff0c;用户认证和授权。即我们常说的&#xff0c;用户只有登录了才能进行其他操作&#xff0c;没有登录的话就重定向到登录界面。有的用户有权限执行某一操作&#xff0c;而有的用户不能执行则是授权。算是一个项…

python模块下载过程_常用的python模块及安装方法

bsddb3&#xff1a;BerkeleyDB的连接组件 Cheetah-1.0&#xff1a;我比较喜欢这个版本的cheetah cherrypy&#xff1a;一个WEB framework ctypes&#xff1a;用来调用动态链接库 DBUtils&#xff1a;数据库连接池 django&#xff1a;一个WEB framework docutils&#xff1a;用来…

python3 week4

1 实现四舍五入&#xff0c;禁止使用python内建函数。 func(soure_num, precise)&#xff0c;source_num为需要处理的数据&#xff0c;precise为需要保留的位数 """ File: 01四舍五入.py Author: chde_wang Date: 2020-08-09 12&#xff1a;14&#xff1a;59 D…