- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
书接上文 https://www.cnblogs.com/onsummer/p/cesium-primitive-api-tutorial.html 。
明确一个定义,在 Primitive API 中应用着色器,实际上是给 Appearance 的 vertexShaderSource 、 fragmentShaderSource 或 Material 中的 fabric.source 设置着色器代码,它们所能控制的层级不太一样。但是他们的共同目的都是为了 Geometry 服务的,它们会随着 CesiumJS 的每帧 update 过程,创建 ShaderProgram,创建 DrawCommand,最终去到 WebGL 的底层渲染中.
有了之前的 fabric.uniforms 、 fabric.materials 、 fabric.components 基础,你可能迫不及待想写自定义着色器代码了。需要知道的一点是,有了 fabric.source ,就不兼容 fabric.components 了,只能二选一.
关于 fabric.uniforms ,它的所有键名都可以在着色器代码中作为 GLSL Uniform 变量使用;关于 fabric.materials ,它的所有键名都可以在着色器代码中作为 GLSL 变量使用,也就是一个计算完成的 czm_material 结构体变量.
编写 fabric.source ,实际上就是写一个函数,它必须返回一个 czm_material 结构体,且输入一些特定的、当前片元的信息:
czm_material czm_getMaterial(czm_materialInput materialInput) {
czm_material material = czm_getDefaultMaterial(materialInput);
// ... 一系列处理
return material;
}
czm_material 已经在之前提及过了,它包含了实时渲染所需的一些基本材质参数。而 materialInput 这个变量,它是 czm_materialInput 类型的结构体,定义如下:
struct czm_materialInput {
float s;
vec2 st;
vec3 str;
mat3 tangentToEyeMatrix;
vec3 positionToEyeEC;
vec3 normalEC;
};
其中:
s - 一维纹理坐标 。
st - 二维纹理坐标 。
str - 三维纹理坐标。注意, materialInput.str.st 不一定就是 materialInput.st ,也不能保证 materialInput.st.s == materialInput.s ,例如对于椭球体而言, s 是底部到顶部的纹理坐标, st 是经纬度, str 可能是范围框的轴向值,这要参考源代码 。
tangentToEyeMatrix - 片元切线空间到眼坐标系的转换矩阵,用于法线计算等 。
positionToEyeEC - 片元坐标到观察坐标系(眼坐标系)原点的向量,模长为片元到原点的距离,单位是米,可以用于反射或者折射计算 。
normalEC - 可用于凹凸映射、反射、折射计算中的眼睛坐标系下的标准化法线 。
那个 czm_getDefaultMaterial 函数就是获取默认的材质结构,这个函数很简单:
czm_material czm_getDefaultMaterial(czm_materialInput materialInput) {
czm_material material;
material.diffuse = vec3(0.0);
material.specular = 0.0;
material.shininess = 1.0;
material.normal = materialInput.normalEC;
material.emission = vec3(0.0);
material.alpha = 1.0;
return material;
}
有了上面这些基础,你就可以在这个 czm_getMaterial() 函数体里写你想要的片元着色内容了,注意任意 CesiumJS 的内置变量、自动 Uniform、结构体、内置函数都可以用.
参考 前端3D引擎-Cesium自定义动态材质 - 掘金 。
有了 3.1 的基础,我们直接参考网上的一些案例.
const polylinePulseLinkFabric = {
type: 'PolylinePulseLink',
uniforms: {
color: Color.fromCssColorString('rgba(0, 255, 255, 1)'),
speed: 0,
image: 'http:/localhost:3000/images/bell.png', // 可以自己指定泛光墙体渐变材质
},
source: `czm_material czm_getMaterial(czm_materialInput materialInput) {
czm_material material = czm_getDefaultMaterial(materialInput);
// 获取纹理坐标
vec2 st = materialInput.st;
// 对 uniforms.image 的纹理图片进行采样
// 这里需要根据时间来采样,公式含义读者自行研究,czm_frameNumber * 0.005 * speed 就是根据内置的
// czm_frameNumber,即当前帧数来代表大致时间
vec4 colorImage = texture2D(image, vec2(fract((st.t - speed * czm_frameNumber * 0.005)), st.t));
vec4 fragColor;
fragColor.rgb = color.rgb / 1.0;
fragColor = czm_gammaCorrect(fragColor); // 伽马校正
material.alpha = colorImage.a * color.a;
material.diffuse = (colorImage.rgb + color.rgb) / 2.0;
material.emission = fragColor.rgb;
return material;
}`,
}
// 使用
const wallInstance = new GeometryInstance({
geometry: WallGeometry.fromConstantHeights({
positions: Cartesian3.fromDegreesArray([
97.0, 43.0,
107.0, 43.0,
107.0, 40.0,
97.0, 40.0,
97.0, 43.0,
]),
maximumHeight: 100000.0,
vertexFormat: MaterialAppearance.VERTEX_FORMAT,
}),
})
new Primitive({
geometryInstances: wallInstance,
appearance: new MaterialAppearance({
material: new Material({ fabric: polylinePulseLinkFabric }),
}),
})
其用到的渐变纹理可以是任意的一个横向颜色至透明的渐变 png:
效果:
文中还介绍了 Entity 使用自定义 MaterialProperty 的方法,实际上底层也是 Material :
class PolylineTrailMaterialProperty {
// ...
getType() {
return 'PolylineTrail'
}
getValue(time, result) {
if (!defined(result)) {
result = {}
}
result.color = Property.getValueOrClonedDefault(
this._color,
time,
Color.WHITE,
result.color
)
result.image = this.trailImage
result.time = ((performance.now() - this._time) % this.duration) / this.duration
return result
}
// ... 其余封装参考原文
}
const shader = `czm_material czm_getMaterial(czm_materialInput materialInput) {
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
// 简化版,显然纹理采样的 time 就来自 PolylineTrailMaterialProperty 了,不需要自己控制
vec4 colorImage = texture2D(image, vec2(fract(st.s - time), st.t));
material.alpha = colorImage.a * color.a;
material.diffuse = (colorImage.rgb + color.rgb) / 2.0;
return material;
}`
// 创建一个 'PolylineTrail' 类型的材质对象,并缓存起来:
const polylineTrailMaterial = new Material({
fabric: {
type: 'PolylineTrail',
uniforms: {
color: new Color(1.0, 0.0, 0.0, 0.5),
image: 'http:/localhost:3000/images/bell.png',
time: 0,
},
source: shader,
}
})
详细的完整封装调用就不列举了,需要有 Entity API 的使用经验,不在本篇范围。想知道 Property 是如何调用底层的,也需要自己研究 EntityAPI 的底层.
fabric.source 只能作用于材质的片元着色,当然也可以通过编写外观对象的两个着色器实现更大自由.
默认情况下, MaterialAppearance 的顶点着色器与片元着色器是这样的:
// GLSL 300 语法,顶点着色器
in vec3 position3DHigh;
in vec3 position3DLow;
in vec3 normal;
in vec2 st;
in float batchId;
out vec3 v_positionEC;
out vec3 v_normalEC;
out vec2 v_st;
void main() {
vec4 p = czm_computePosition();
v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates
v_normalEC = czm_normal * normal; // normal in eye coordinates
v_st = st;
gl_Position = czm_modelViewProjectionRelativeToEye * p;
}
顶点着色器调用 czm_computePosition() 函数将 position3DHigh 和 position3DLow 合成为 vec4 的模型坐标,然后乘以 czm_modelViewProjectionRelativeToEye 这个内置的矩阵,得到裁剪坐标。然后是片元着色器:
in vec3 v_positionEC;
in vec3 v_normalEC;
in vec2 v_st;
void main()
{
vec3 positionToEyeEC = -v_positionEC;
vec3 normalEC = normalize(v_normalEC);
#ifdef FACE_FORWARD
normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);
#endif
czm_materialInput materialInput;
materialInput.normalEC = normalEC;
materialInput.positionToEyeEC = positionToEyeEC;
materialInput.st = v_st;
czm_material material = czm_getMaterial(materialInput);
#ifdef FLAT
out_FragColor = vec4(material.diffuse + material.emission, material.alpha);
#else
out_FragColor = czm_phong(normalize(positionToEyeEC), material, czm_lightDirectionEC);
#endif
}
如果想完全定制 Primitive 的着色行为,需要十分熟悉你所定制的 Geometry 的 VertexBuffer,也要控制好两大着色器之间相互传递的值.
可以看得出来,Primitive API 使用的材质光照模型是冯氏(Phong)光照模型,可参考 基本光照 .
案例就不放了,有能力的可以直接参考 CesiumJS 曾经推过的一个 3D 风场可视化的案例,它不仅自己写了一个顶点着色器、片元着色器都是自定义的 Appearance,还写了自定义的 Primitive(不是原生 Primitive,是连 DrawCommand 都自己创建的似 Primitive,似 Primitive 将在下文解释).
这段要讲讲源码,定位到 Primitive.prototype.update() 方法:
Primitive.prototype.update = function (frameState) {
const appearance = this.appearance;
const material = appearance.material;
let createRS = false;
let createSP = false;
// 一系列判断是否需要重新创建 ShaderProgram,会修改 createSP 的值
if (createSP) {
const spFunc = defaultValue(
this._createShaderProgramFunction,
createShaderProgram
);
// 默认情况下,会使用 createShaderProgram 函数创建新的 ShaderProgram
spFunc(this, frameState, appearance);
}
};
使用 createShaderProgram 函数会用到外观对象.
function createShaderProgram(primitive, frameState, appearance) {
// ...
// 装配顶点着色器
let vs = primitive._batchTable.getVertexShaderCallback()(
appearance.vertexShaderSource
);
// 从这开始,是给外观对象的片元着色器添加一系列 Buff
vs = Primitive._appendOffsetToShader(primitive, vs);
vs = Primitive._appendShowToShader(primitive, vs);
vs = Primitive._appendDistanceDisplayConditionToShader(
primitive,
vs,
frameState.scene3DOnly
);
vs = appendPickToVertexShader(vs);
vs = Primitive._updateColorAttribute(primitive, vs, false);
vs = modifyForEncodedNormals(primitive, vs);
vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly);
// 装配片元着色器
let fs = appearance.getFragmentShaderSource();
fs = appendPickToFragmentShader(fs); // 为片元着色器添加 pick 所需的 vec4 颜色 in(varying) 变量
// 生成 ShaderProgram,并予以校验匹配情况
primitive._sp = ShaderProgram.replaceCache({
context: context,
shaderProgram: primitive._sp,
vertexShaderSource: vs,
fragmentShaderSource: fs,
attributeLocations: attributeLocations,
});
validateShaderMatching(primitive._sp, attributeLocations);
// ...
}
总之,外观的两个着色器也仅仅是 CesiumJS 这个庞大的着色器系统中的一部分,仍有非常多的状态需要添加到着色器对象( ShaderProgram )上.
可能通用的 Primitive 就是需要这么多状态附加吧,读者可以自行研究其它似 Primitive 的着色器创建过程。似 Primitive 将于本文的最后一大节说明.
注意到一个东西: appearance.renderState ,在创建外观对象时可以传入一个对象字面量:
new MaterialAppearance({
// ...
renderState: {},
})
也可以不传递,默认会生成这样一个对象:
{
depthTest: {
enabled: true,
},
depthMask: false,
blending: BlendingState.ALPHA_BLEND, // 来自 BlendingState 的静态常量成员 ALPHA_BLEND
}
这个对象会记录在外观对象上,伴随着 Primitive 的更新过程,还会增增减减、修改状态值,在 Primitive 的 createRenderStates 函数中,用这个对象的即时值创建或取得缓存的 RenderState 实例,等待着在 createCommands 函数中传递给 DrawCommand .
RenderState 的状态值和 WebGL 最终渲染有关,在 Context 模块的 beginDraw 函数、 applyRenderState 函数中,就有大量使用渲染状态的代码(还要往里进去两三层),举例:
function applyDepthMask(gl, renderState) {
gl.depthMask(renderState.depthMask);
}
function applyStencilMask(gl, renderState) {
gl.stencilMask(renderState.stencilMask);
}
这两个函数就是在修改 WebGL 全局状态的值,值来自 RenderState 实例的 depthMask 和 stencilMask 字段.
CesiumJS 漫长的一帧的更新过程中,有两个状态对象可以关注一下,一个是挂载在 Scene 上的 帧状态 对象(FrameState 实例),另一个就是身处于各个实际三维对象上的 渲染状态 对象(RenderState 实例)。前者记录一些整装待发的资源,例如 DrawCommand 清单等,后者则为三维对象标记在实际渲染时要更改 WebGL 全局状态的状态值。两大状态的链接桥梁是 DrawCommand .
还有一个贯穿于帧更新过程的状态对象:统一值对象(UniformState 实例),是 Context 的成员字段,作用同其名,用于更新要传给着色器的统一值.
这一节介绍的内容将有助于理解 CesiumJS 单帧更新的核心思路。别看 CesiumJS 拥有这么多加载数据、模型的 API 类,实际上是可以根据它们在场景结构中的层级,做个简单的分类:
Entity 与 DataSource,高层级的数据 API,是高级的人类友好的数据格式加载封装,还能与时间关联 。
Globe 与 ImageryLayer,负责地球本身的渲染,含皮肤(影像 Provider)和肌肉(地形 Provider) 。
Primitive 家族,含本篇介绍的 Primitive ,以及 glTF 、 3DTiles 等数据 。
Entity 和 DataSource 实际上底层也是在调用 Primitive 家族,只不过这两个属于 Viewer;中间的 Globe 与 ImageryLayer 和最后的 Primitive 家族,属于 Scene 容器.
既然这篇是介绍的 Primitive,那么就重点介绍 Primitive 家族.
你一定注意过可以向 scene.primitives 这个 PrimitiveCollection 中添加好几种对象: Model 、 Cesium3DTileset 、 PrimitiveCollection (是的,可以嵌套添加)、 PointPrimitive 、 GroundPrimitive 、 ClassificationPrimitive 以及本篇介绍的 Primitive 均可以,在 1.101 版本的更新中还添加了一个体素: VoxelPrimitive (仍在测试).
我将这类 Primitive 家族类,称为 PrimitiveLike 类,即“似 Primitive”.
这些似 Primitive 有一个共同点,才能添加到 PrimitiveCollection 中,伴随着场景的单帧更新过程进入 WebGL 渲染。它们的共同点:
有 update 实例方法 。
有 destroy 实例方法 。
在 update 方法中,它接受 FrameState 对象传入,然后经过自己的渲染逻辑,创建出一系列的指令对象(主要是 DrawCommand ),并送入帧状态对象的指令数组中,待更新完毕最终进入 WebGL 的渲染.
所以知道这些有什么用呢?
Cesium 团队是一个求稳的团队,2012 年还在内测的时候,ES5 标准才落地没多久,哪怕现在的代码也仍然是使用函数来创建类,而不是用 ES6 的 Class(尽管现在切换过去已经没什么技术难点了)。ES6 实现类继承是很简单的,但是在那个时候就比较困难了。像这种似 Primitive 的情况,ES6 来写实际上就是有一个共同的父类罢了,如果是 TypeScript,那更是可以抽象为更轻量的 interface :
interface PrimitiveLike {
update(frameState: FrameState): void
destroy(): void
}
这构成了编写自定义 Primitive 的基础,CesiumJS 团队和 CesiumLab 核心成员 vtxf 均有一些古早的资料,告诉你如何编写自定义的 Primitive 类。我之前也写有一篇较为相近的、介绍 DrawCommand 并创建简易自定义三角形 Primitive 的文章,列举如下:
CesiumJS Wiki - Geometry and Appearance 。
cesiumlab vtxf - cesium-custom-primitive 。
知乎 - Cesium DrawCommand [1] 不谈地球 画个三角形 。
著名的 Cesium 3D 风场案例就是一个非常经典的应用:
如果读过我写的源码系列,应该知道 Primitive 在 Scene 的更新位置( Scene 模块下的 render 函数),简单放个图吧:
这样就能大致看到在什么时候更新的 PrimitiveCollection 了.
有兴趣了解源码渲染架构的可以去补补我之前写的系列.
这两篇文章集合了我三年前的几篇不成熟的文章,我终于系统地写出了这几个内容:
一般性的 Primitive API 用法,包括 。
Geometry API 的自定义几何、参数内置几何 。
Appearance + Material API 所表达的 CesiumJS Fabric 材质规范 。
提出似 Primitive 的概念,为之后自定义 Primitive 学习挡在 WebGL 原生接口之前的最底层 API 打下基础 。
简单思考了 CesiumJS 的着色器设计和应用 。
希望对读者有用.
最后此篇关于CesiumJSPrimitiveAPI高级着色入门-从参数化几何与Fabric材质到着色器-下篇的文章就讲到这里了,如果你想了解更多关于CesiumJSPrimitiveAPI高级着色入门-从参数化几何与Fabric材质到着色器-下篇的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我想知道是否可以将简单的位图转换为几何对象 最佳答案 是的,您可以使用跟踪。 Potrace是一个开源的位图到矢量跟踪器库。 然而,位图追踪并不完美。对于高质量的矢量图像,line tracer 一般
这类似于this question,但是却相反。 我有两个地理位置(纬度,经度)A和B。假设它们相距40海里。我想计算在A和B之间的直线上,从A点起10海里处的坐标。数学(我每天使用的其他一些数学),
我想计算一个点到由 2 个点定义的直线的距离。 我正在使用 javascript,这就是我使用维基百科得出的结论:https://en.wikipedia.org/wiki/Distance_from
我对 boost::geomentry 有疑问。 #include #include #include #include int main(){ typedef boost::geometry
我有一个问题。我想将四边形与四边形相交。 int main(){ typedef boost::geometry::model::point_xy TBoostPoint; typedef b
我无法在 OpleGL 中获得正确的转换。 我有 point3D - P(X,Y,Z) 和投影矩阵 M,它等于 K*(R|T) 其中 K - 相机标定矩阵 (R|T)——点(物)坐标系变换(R——旋转
我想做一个凸面(由一些直线或圆弧组成)围绕它的几何中心(Cx,Cy)旋转。同时凸面两侧有两个圆(由半径给出:R 和左中心:(Lx,Cy),右中心:(Rx,Cy))。表示与几何(Cy) X 轴相同的圆心
我有一个 DrawingVisual表示路径的元素,该路径的几何描述由此 syntax : "m106,59.3c0-1.98,0,0-4.95,0.989-3.96,0.989-13.8,3.96-
如何将我自己的数据集转换为可供 pytorch 几何图形神经网络使用的数据集? 所有教程都使用已转换为 pytorch 可用的现有数据集。例如,如果我有自己的点云数据集,我如何使用它来训练图神经网络的
我正在使用 PyQt5 和 OpenCV。我想创建一个读取视频帧并执行橡皮筋拉伸(stretch)以生成几何图形的类,该几何图形将由不同的类用于裁剪视频流(此示例中不包括第二类)。 在此示例中,从网络
我们有两个 (PostgreSQL 9.2) 表。第一城市: loc_id | integer | not null name | character
我有一张 table : create table if not exists places( id bigserial not null constraint places_pkey primary
我在 postgresql 中有一个带有 PostGIS geometry(point, 4326) 列(位置,使用 SRID 4326)的表,我有一个使用 SQL Alchemy 更新表(其余列)的
我开始使用 c++11 并尝试使用 boost geometry 运行一些示例代码 #include #include #include #include BOOST_GEOMETRY_REG
我有一个存储为 csv 文件的数据框,其中一列是多边形对象。但是,此列存储为字符串而不是 GeoPandas 几何对象。如何将此列转换为 Geopandas 几何对象以便执行地理分析? 这是我的数据的
我从两台相同品牌的相机拍摄的两张图像相距一定距离,拍摄相同的场景。我想计算两个相机之间的真实世界旋转和平移。为了实现这一点,我首先提取了两个图像的 SIFT 特征并匹配它们。 我现在有了基本矩阵以及单
我目前正在使用 boost 几何/空间索引库,以便对 3d 边界框执行范围查询。例如,我能够获得与查询边界框重叠的所有边界框的列表。 文档 ( http://www.boost.org/doc/lib
boost::geometry::model::point 将点的维度作为编译时参数。例如, typedef bg::model::point point; 有没有什么方法可以在运行时指定维度,比如说
我一直在寻找一种在 three.js 中将 uv 映射添加到我的自定义几何体的方法。我找到了这样做的方法,但我找到的解决方案都没有用。谁能解释一下 uv-mapping 的工作原理以及如何正确使用它?
在我的应用程序中,用户可以使用 iPhone 的 GPS 定义足球场的三个角落,方法是一个接一个地走到角落,然后点击按钮。这很好用,我可以在屏幕上绘制生成的矩形,类似于它在 Google map 中的
我是一名优秀的程序员,十分优秀!