73 Matching Annotations
  1. May 2024
    1. 其实是 JavaScript 为了吸引 Java 程序员、在语法层面去蹭 Java 热点,所以就被硬生生地强制加入了非常不协调的关键字 new。

      锐评JavaScript 中的 new 关键字

    2. 闭包给惰性解析带来的问题:上文的 d 不能随着 foo 函数的执行上下文被销毁掉。

      有待思考。为什么会带来这个问题?

    1. 6. 输出是什么

      做到这里

    2. 输出是什么?

      事件循环 + 变量引用(基础) + 作用域。

      变量的两种赋值方式:1. 原始类型一般可以直接赋值 2. 引用赋值

  2. developer.mozilla.org developer.mozilla.org
    1. 换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。

      我记得是作用域链实现的这个目的吧:作用域链是的内部函数可以访问外部函数。或者说闭包就是作用域链实质展示其功能的一个应用。

    1. 我们使用一个很小的常量(光照)颜色,添加到物体片段的最终颜色中,这样子的话即便场景中没有直接的光源也能看起来存在有一些发散的光。

      简化的全局照明模型:

      ambientStrength * lightColor * objectColor

      ,即环境光照。

    1. 我们将会广泛地使用颜色来模拟现实世界中的光照效果

      其实我感觉这句话贯穿整个光照理论。颜色的浓淡深浅 + 人眼的视错觉 = 模拟的光照效果

    2. 当我们把光源的颜色与物体的颜色值相乘,所得到的就是这个物体所反射的颜色(也就是我们所感知到的颜色)。

      一个及其简单但在本章中很重要的公式:lightColor * toyColor 计算物体的颜色。

    3. 这一段从物理的角度解释了现实中物体为何会呈现颜色。物体本没有颜色,是光照到了物体上吸收一部分和反射一部分才有了颜色。(=... =)地上本没有路,走的人多了才有路。

    1. OpenGL本身没有摄像机(Camera)的概念,但我们可以通过把场景中的所有物体往相反方向移动的方式来模拟出摄像机

      难道不能移动观察坐标系吗?

    1. 纹理坐标的范围通常是从(0, 0)到(1, 1),那如果我们把纹理坐标设置在范围之外会发生什么?

      没怎么看明白

    1. Fragment Interpolation

      本例中opengl自动进行

    2. glVertexAttribPointer

      void glVertexAttribPointer(

       GLuint index,
      
       GLint size,
      
       GLenum type,
      
       GLboolean normalized,
      
       GLsize stride,
      
       const GLvoid *pointer
      

      );

      其中:

      index 指定要配置的顶点属性的编号。

      size 指定每个顶点属性的分量数(1、2、3 或 4,就像向量的维度一样)。

      type 指定每个分量的数据类型,可以是 GL_BYTE、GL_UNSIGNED_BYTE、GL_SHORT、GL_UNSIGNED_SHORT、GL_INT、GL_UNSIGNED_INT、GL_FLOAT 或 GL_DOUBLE。 normalized1 指定是否将数据归一化到 [0,1] 或 [-1,1] 范围内。

      stride (步长)指定连续两个顶点属性间的字节数。如果为 0,则表示顶点属性是紧密排列的。

      pointer 指向缓冲对象中第一个顶点属性的第一个分量的地址。(offset的作用)

    3. uniform对于设置一个在渲染迭代中会改变的属性是一个非常有用的工具,

      正如本例子中 三角形的面颜色随帧渲染的进行而改变。

    4. 所以它不支持类型重载

      类型重载(Type Overloading),在编程中通常称为函数重载(Function Overloading)或运算符重载(Operator Overloading),是一种允许多个同名函数或运算符具有不同参数列表或实现方式的技术。这种技术使得同一个函数名或运算符可以用于处理不同类型或数量的参数,从而提高代码的可读性和灵活性。

      函数重载

      函数重载是指在同一个作用域内,允许定义多个具有相同名称但参数列表不同的函数。编译器会根据函数调用时传递的参数类型和数量,选择匹配的函数进行调用。函数重载在很多编程语言中都是支持的,例如C++、Java和Python等。

      运算符重载

      运算符重载是指在类定义中重载标准运算符,使得这些运算符可以用于类的实例对象。运算符重载通常用于定义类的行为,例如加法、减法、比较等操作,使得类对象可以像内置类型一样进行运算。

      无论是函数重载还是运算符重载,都依赖于编译器在编译时进行参数匹配,选择合适的重载版本进行调用。

    5. 注意,查询uniform地址不要求你之前使用过着色器程序,但是更新一个uniform之前你必须先使用程序(调用glUseProgram),因为它是在当前激活的着色器程序中设置uniform的。

      顺序如下

      glGetUniformLocation

      glUseProgram

      glUniform4f

      也就是glUniform4f必须在glGetUniformLocation和glUseProgram后面

    6. 这是我们接下来要做的事情

      看到这里,已经知道了基础的三个:

      1、in

      2、out

      3、uniform

      它们是需要在声明一个变量前的修饰符。

    7. 这是在链接程序对象时完成的

      在着色器链接阶段就完成了输入输出的对接

    8. 我们使用location这一元数据指定输入变量

      在OpenGL中,顶点着色器(vertex shader)需要明确指出它的输入变量,以便这些变量能正确地与CPU端发送的顶点数据关联起来。为了实现这一点,我们使用layout标识符来指定输入变量的location。这个location是一个整数值,用于在CPU和GPU之间建立顶点数据的对应关系。

      在顶点着色器中,我们使用layout(location = X)语法来定义输入变量的位置。使用形似这样 glVertexAttribPointer(X, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); 的代码来解释 VBO 中的数据。

      如果不使用 layout (location = 0) 标识符,通过在OpenGL代码中使用glGetAttribLocation查询属性位置值(Location)。然后再使用形似这样 glVertexAttribPointer(Location, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); 的代码

    9. 另一个例外是片段着色器,它需要一个vec4颜色输出变量

      这个输出是必须的,如果没有人为定义输出颜色,会有一个默认值输出。

    10. 顶点着色器的输入特殊在,它从顶点数据中直接接收输入。

    11. 很多新颖的管理向量的例子

      1、

    12. Swizzling

      有趣而灵活的分量选择方式。很常用的吧应该。

    13. 我们能声明的顶点属性是有上限的,它一般由硬件来决定。

      这一点我可以深刻的理解了。在这个视频中https://www.bilibili.com/video/BV13w4m1U7fN?t=1298.4 有对于这句话的解释。可能是一个东西吧,我也不清楚,先记着吧。

    1. 这种所谓的索引绘制(Indexed Drawing)正是我们问题的解决方案。

      信息没有减少,但是数据量变少了。可以理解为数据被无损压缩了。

    2. 要做的只是使用glBindVertexArray绑定VAO

      在函数调用上看并不是像 VBO 那样绑定到一个目标上。实际上还是要绑定再一个目标上。此结论从 WebGL 相关知识出发得出的。 参考:https://webgl2fundamentals.org/webgl/lessons/resources/webgl-state-diagram.html?lang=zh_cn&exampleId=triangle#no-help

    3. 通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象

      VAO是一个容器,它将所有 可以由glVertexAttribPointer和其它一些函数进行设置的 状态包装到一起。在使用一个VAO时,所有通过一次 glVertexAttribPointer 调用来指定的状态都会被存储到当前的VAO中,glVertexAttribPointer 指定了渲染时索引值为 index 的顶点属性数组的数据格式和位置。它们之间也是通过上下文,只有唯一的激活VAO,在VAO后创建的VBO都属于该VAO。关联VBO数据用取得当前激活的缓存区对象偏移来指定。

      原文链接:https://blog.csdn.net/danshiming/article/details/56286880

      再补充说明一下:glVertexAttribPointer 是用来给 VAO 写入数据的 “解释性话语“。而至于哪个 VAO 给 哪个VBO 做解释,这是可以通过上下文中那个唯一的 VAO 和代码执行后的 VBO 。

    4. 核心模式要求我们使用VAO

      如果按照这样理解的话:VAO管理的是对VBO中数据的解释权,VBO管理的是数据本体。那么自然可以理解为什么 OpeGL 必须要求我们使用 VAO 了,不解释数据就无法展示。

      WebGL 中会有一个默认创建好的 VAO 。

    5. 任何随后的顶点属性调用都会储存在这个VAO中

      可以这样理解吗?VAO管理的是对VBO中数据的解释权,VBO管理的是数据本体。

    6. 我们使用一个顶点缓冲对象将顶点数据初始化至缓冲中,建立了一个顶点和一个片段着色器,并告诉了OpenGL如何把顶点数据链接到顶点着色器的顶点属性上。

      概括了上面的事情

    7. 必须手动指定输入数据的哪一个部分对应顶点着色器的哪一个顶点属性

      glVertexAttribPointer 函数实现这个操作

    8. 现在,我们已经把输入顶点数据发送给了GPU,并指示了GPU如何在顶点和片段着色器中处理它。就快要完成了,但还没结束,OpenGL还不知道它该如何解释内存中的顶点数据,以及它该如何将顶点数据链接到顶点着色器的属性上。

      只是给了一坨数据,没有解释数据

    9. 编译是否成功

      错误异常提示?这方面感觉挺需要的

    10. 运行时动态编译它的源代码

      嚯,动态编译

    11. 下一步

      下面一段内容可以类比C语言来理解。着色器是一个函数,in 表示数据需要外界输入, out 表示该着色器会将变量数据输出给 OpenGL(应该是 OpenGL 上下文吧可能),vec3 类比 int、float 等变量类型。

    12. 硬编码

      硬编码指的是将数据直接嵌入到程序或其他可执行对象的源代码中,与从外部获取数据或在运行时生成数据不同。

    13. 在真实的程序里输入数据通常都不是标准化设备坐标,所以我们首先必须先把它们转换至OpenGL的可视区域内。

      啊哈,这里也解释了为啥要在声明和赋值顶点数组时就直接把数值限定在-1.0 - 1.0 之间。

    14. 现在我们已经把顶点数据储存在显卡的内存中,用VBO这个顶点缓冲对象管理

      前面讲了这么多,终于点明了 VBO 的作用了。VBO 绑定在某个目标上,而这些目标配备有一些函数,那么 VBO 就可以借助这些函数配置自己的,比如说GL_STATIC_DRAW形式,这是什么设计思想呢?暂且不论。

      VBO 就是用来管理在GPU显存中的顶点位置数据。应该不止位置数据

    15. 显卡如何管理给定的数据

      我理解的是在做优化,按不同形式给不同的策略。

    16. 我们使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)

      看了两三遍看明白了。意思是:GL_ARRAY_BUFFER目标上绑定这一个VBO,你所做的任何对于该目标的操作都是用来配置 VBO 这个对象的(换句话说,在你所调用的函数中,参数写的是GL_ARRAY_BUFFER目标,实际上是在配置当前绑定的缓冲VBO)。

      感觉有点像代一样,GL_ARRAY_BUFFER目标 “ 代理” 了当前绑定的 VBO ,这么解释不妥,应该站在状态机的角度来看待这种情况。

    17. 之前定义的顶点数据复制到缓冲的内存中

      是复制了一份数据还是说得到该份数据的地址索引,(即深拷贝还是浅拷贝)。应该是 内存中的数据 复制了一份到 显卡显存中,即深拷贝。

    18. OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。

      OpenGL有不同的缓冲类型,允许我们同时在这些不同的缓冲类型上绑定各自的缓冲,即所谓的同时绑定多个缓冲。

    19. Screen-space Coordinates

      我们将裁剪坐标变换为屏幕坐标,我们将使用一个叫做视口变换(Viewport Transform)的过程。视口变换将位于-1.0到1.0范围的坐标变换到由glViewport函数所定义的坐标范围内。最后变换出来的坐标将会送到光栅器,即经过图形渲染管线的光栅化 rasterization ,将其转化为片段。

    20. 一旦你的顶点坐标已经在顶点着色器中处理过,它们就应该是标准化设备坐标了

      哦~~~,我知道了。原来这里为了简化学习的流程,没有告诉你顶点着色器会把物体的局部空间坐标转变到世界空间中,再转变到观察空间中(是坐标系的转变啦)。而这里直接给顶点着色器标准化设备坐标的话,就不需要进行坐标转换了,也就是矩阵运算,简化了本节的代码。

      实际上,就是你手动完成了坐标的转换,给后面流程的标准化设备坐标就是你写的 float vertices[] 了。

    21. 以标准化设备坐标的形式(OpenGL的可见区域)定义为一个float数组

      ❓那要是不以标准化设备坐标的形式定义的话,怎么办捏?

    22. 所有OpenGL高级效果

      wuo,居然说是所有高级效果。但终归是通过一系列的公式计算出某一个位置的颜色的浓浅深淡。毕竟屏幕只能展示颜色,不能真的表现三维空间,可以理解为通过颜色的不同营造一种3D的绚烂效果的错觉。(有那么一点魔怔了,哈哈,就想当初有人质疑显微镜只是实际物体的光的投影假象一样,光在空间的传播会受到各种不明影响,不能代表或反映物体本身,即显微镜不可信,哈哈哈)

      假如说哪一天3D屏幕出现了,或许图形学相关的一些也要与时俱进了。

    23. 我们必须定义至少一个顶点着色器和一个片段着色器(因为GPU中没有默认的顶点/片段着色器)

      必要性,就好比少了C项目里少了main函数,西方失去了耶路撒冷。

    24. Alpha测试和混合(Blending)

      如果物体时透明的,就算我们正确计算了物体的颜色,但也会收到其他物体的颜色的影响,那么这两个过程就需要在物体颜色计算完成后再进行。比如说白色的半透明杯子放在蓝色的桌子上,从杯口上方看,那么杯子的底部是半透明白+蓝色的混合色。这也就解释了在一些软件或者库中只要使用了透明值会导致颜色的表现和我们的预期不一致,因为你没有考虑在他后面的物体的颜色。而颜色混合后是什么样子的,一般人无法提前知道是什么样子。

    25. Clipping

      坐标位置摆放到为裁剪坐标系。

    26. 顶点着色器主要的目的是把3D坐标转为另一种3D坐标

      顶点数据的值随着不同坐标系的转换而改变。坐标系有局部坐标系、世界坐标系、观察坐标系、裁剪坐标系、屏幕坐标系。但似乎只会使用到有局部坐标系、世界坐标系、观察坐标系这三个坐标系。

    27. OpenGL需要你去指定这些数据所表示的渲染类型

      形状(图元)装配?

    28. 图形渲染管线包含很多部分,每个部分都将在转换顶点数据到最终像素这一过程中处理各自特定的阶段

      转换顶点数据到最终像素分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。

    29. 一个图形渲染管线

      这个渲染管线可能比较基础或者说它老旧。

    1. 归一化数据的最常见用途是颜色。 大多数时候颜色仅从0.0到1.0。 如果红色,绿色,蓝色和Alpha分别使用一个完整的浮点数,每个顶点的每种颜色将使用16个字节。 如果您有复杂的几何体,将会可以增加很多字节。 相反,您可以将颜色转换为UNSIGNED_BYTEs (其中0代表0.0,255代表1.0)。 现在每种颜色只需要4个字节,每个顶点可节省75%的空间。

      没有代价吗

  3. Apr 2024
    1. 更新 Scene 中的 Primitive

      ❓我对这句表述持怀疑态度。应该是更新渲染过程,包括更新射线拾取、预加载等。

    1. 事件机制

      比较重要的点。我绘制了一个关系图来辅助理解和记忆这种设计。

    2. 每当 EntityCollection 有增删改变化时

      ❓啊?哪个变量变了?

      ✅原来是 EntityCollection 中的 _addedEntities、_removedEntities、_changedEntities 这仨。

    3. Updater 根据不同的条件甩到 Visualizer 上不同的批次容器中的过程

      🤔怎么甩的?条件是什么?批次容器有哪些?

    4. _solidItems 和 _translucentItems 都是普通的数组,保存的是模块内部定义 Batch 类型的对象

      🤔从何看出的?难道要调试源码才可以吗?

    5. 最后创建 Primitive 时所需的 GeometryInstance 的创建者

      例子:createFillGeometryInstance 方法会返回一个 GeometryInstance 实例,这个实例可以用于创建 Primitive 。

    6. 等待 Scene 在帧渲染流程中更新 PrimitiveCollection 进而创建出 DrawCommand

      🤔从哪里看出的

    7. Entity 使用 Property API 去修改实体的形状、外观

      🤔不知道作者是依据什么直接得出我们需要看一看 Property API 的,按我的思路来看,需要继续看 GeometryVisualizer.prototype.update 这个方法。

    8. Entity 这个时候仍然是参数对象

      ❓不太理解,可以理解为这样吗:此时第一帧中还没有创建出 Entity 实例。还没分解为 Primitive 。

    9. 进一步更新 DataSourceDisplay 中所有的数据源(无论是数据源容器中的还是默认的 CustomDataSource 的)的 可视化器(Visualizer)

      更新 DataSourceDisplay 中的 DataSourceCollection 和 CustomDataSource 数据源,同时更新这两个数据源所对应(绑定)的 Visualizer 可视化器。

      ❓疑惑: Visualizer 是与 EntityCollection 容器对象绑定的,似乎不是和数据源 CustomDataSource 绑定的,为什么源码中可以从 CustomDataSource 中获取到 Visualizer 数组。

      ✅解惑:好好好,我是菜狗,我又忘了在 DataSourceDisplay 原型链上有一个重要的函数 _onDataSourceAdded ,它会在 new DataSourceDisplay 时调用一次,套娃后为 CustomDataSource 实例添加(绑定)Visualizer 数组。DataSourceDisplay 也会拥有 CustomDataSource 实例,从而可以调取 Visualizer 数组。

      🌟 DataSourceDisplay 来更新数据源和对应的可视化器

    10. 注意到 DataSourceDisplay 创建 defaultDataSource 时,它会主动调用 _onDataSourceAdded 方法

      这个函数非常重要。

      给 defaultDataSource 再创建一个私有的 PrimitiveCollection,塞入 DataSourceDisplay 的 PrimitiveCollection 中(好家伙,套娃是吧);重点是在 _onDataSourceAdded 方法中会紧接着调用 _visualizersCallback 方法创建 可视化器(Visualizer)

    11. 真正监听 Entity 变化的是通过 EntityCollection 的事件机制完成的,EntityCollection 无论发生什么变化,都会传递给 Visualizer

      EntityCollection 中发生的变化都会告知给 Visualizer 。

      抽象一下: A 中发生的改变都会告知给 B , A 会吧改变的事物传递给 B ,让 B 来对这个改变的事物进行处理。

      疑惑:

      1、是告知给 对应的 Visualizer 还是 Visualizers 数组? 2、如何告知的?

    12. fireChangedEvent()

      在 EntityCollection.prototype.add 方法中也必然调用一次,而这个 add 方法也就是 View.entities.add 方法。当 Viewer 实例 viewer 调用 add 方法时,也就会调用 fireChangedEvent 方法。

      这个方法的主要内容是:如果collection中 _addedEntities、_removedEntities、_changedEntities这仨任意一个有改变,那么就处理一下这些变化。

      对于上面提到的 add 方法,那必然会是 _addedEntities 中有了一个 entity ( push 了至少一个 Entity 实例,需要调试代码),然后 add 内部调用 fireChangedEven 根据那仨状态来触发相关事件。

    13. 也就是这个重要的时钟,让 Viewer 通过事件机制参与了 CesiumWidget 调度的渲染循环。

      通过一个重要的函数 Viewer.prototype._onTick 和一个事件助手对象 eventHelper.add(clock.onTick, Viewer.prototype._onTick, this); 实现了 Viewer 对 CesiumWidget 渲染循环过程的介入。

      介入内容在 Viewer.prototype._onTick 方法中: 1、获取当前时间: 首先,从时钟对象中获取当前时间。

      2、更新数据源显示: 然后,调用 _dataSourceDisplay.update 方法来更新数据源的显示状态,并根据返回值更新时钟视图模型的动画状态。

      3、更新实体视图: 如果存在实体视图 (entityView),则检查是否有被跟踪的实体 (trackedEntity)。如果有,则获取该实体的边界球状态,并根据状态更新实体视图。

      4、处理选中实体: 如果存在选中的实体 (selectedEntity) 并且启用了信息或选择功能 (_enableInfoOrSelection),则执行以下操作: 获取选中实体的边界球状态,如果获取成功则获取边界球的中心作为相机的位置。 更新相机的使能状态,使其为真(true)表示相机可用。 更新选择指示器视图模型 (selectionIndicatorViewModel),设置位置并根据相机的使能状态来显示选择指示器。 更新信息框视图模型 (infoBoxViewModel),设置显示信息、相机的使能状态以及是否正在跟踪相机。

      5、处理信息框: 最后,根据是否有选中实体,更新信息框的标题和描述信息。

    14. 每一帧渲染调度函数中 跳动

      如何准确的在每一帧中跳动?因为每一帧都会调用一次 CesiumWidget.prototype.render 。

    15. Visualizer 在创建的时候

      什么时候创建的,哦!在 new DataSourceDisplay 时创建了一个 _visualizers ,这是一个 Visualizer 数组。

      _onDataSourceAdded:在 new DataSourceDisplay 时,会主动执行一次 _onDataSourceAdded ,这个方法搞了一个 primitiveCollection 套娃后调用了 DataSourceDisplay 她自身的 _visualizersCallback 方法(这个方法最终会是 defaultVisualizersCallback 或者自定义回调函数),这个方法会返回一个 Visualizer 数组,然后赋给 _visualizers 属性。至此,DataSourceDisplay 实例就有了 Visualizer 实例小弟了。

    16. dataSource._visualizers = this._visualizersCallback

      在 DataSourceDisplay 实例中创建可视化器

    1. Cesium内部通过事件的机制,在DataSourceDisplay中根据Entity类型安排具体模块

      1、哪些代码体现了“事件的机制” 2、哪些代码体现了“DataSourceDisplay中根据Entity类型安排具体模块”