当前位置: 首页 > news >正文

[OpenGL]实现屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO)

一、简介

本文介绍了 屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO) 的基本概念,实现流程和简单的代码实现。实现 SSAO 时使用到了 OpenGL 中的延迟着色 (Deferred shading)技术。
按照本文代码实现后,可以实现以下效果:

渲染结果

二、SSAO介绍以及实现流程

1. SSAO 介绍

(1). 什么是 Ambient Occlusion, AO

简单来说 Ambient Occlusion(AO) 是一种全局照明(Global Illumination,GI)中的根据环境光(Ambient Light)参数和环境几何信息来计算场景中任何一点的光照强度系数的算法。
AO 描述了表面上的任何一点所接受到的环境光被周围几何体所遮蔽的百分比, 因此使得渲染的结果更加富有层次感,对比度更高。
例如:对于下图中的车辆缝隙处(红色),会受到周边模型面片的遮挡,导致接收到的环境光 (Ambient light)较少,因此这些地方会更暗。而在车辆的边缘处(绿色),几乎不会受到周围模型面片的遮挡,接收到的环境光较多,因此这些地方会更亮。
AO示意图

(2). 什么是 Screen-Space Ambient Occlusion, SSAO

为了计算准确的 AO,可以使用光线跟踪算法。但是光线跟踪消耗计算资源太大,而 屏幕空间环境光遮蔽 (Screen-Space Ambient Occlusion, SSAO) 是一种仅仅基于屏幕信息(例如,屏幕上各像素对应 片元 的空间位置信息)快速估计 AO 的算法。

SSAO 算法的基本思想为:
对于目标着色点,在其周围的一个球(或者面向相机方向的半球)内采样多个采样点,如果采样点大多被模型的其他面片遮挡,那么说明该目标着色点的 Ambien Occlusion 比较大,因此该着色点理应较暗些。而反之,目标着色点周围得到的采样点大部分并不会被模型的其他面片遮挡,那么说明该着色点的 Ambient Occlusion 更小,因此会更亮。

以下图为例:
在图中的 红色着色点 附近的一个球内采样,得到 8 个采样点,其中只有两个采样点(白色采样点)相比模型中的其他面片更靠近相机,不会被模型面片遮挡。而其他 6 个采样点(灰色采样点)都会被模型中的其他面片遮挡,则红色目标着色点的 Ambient Occlusion 更大,渲染结果中此着色点会更暗。
而图中的 绿色着色点 附近的大多数采样点(白色采样点)都不会被模型面片遮挡,只有两个采样点(灰色采样点)会被遮挡,则绿色目标着色点的 Ambient Occlusion 更小,渲染结果中此着色点会更亮。

SSAO 示意图
在实现时也可以在朝向相机的半球内采样,理论上这样的结果会更加准确,而不是上图中所示的整个球内采样。在计算得到 各点的 AO 值后,也可以使用 滤波 操作对屏幕上各点的 AO 值进行滤波操作,平滑遮蔽效果,消除噪点。

2. SSAO 实现流程

实现 SSAO 主要分为 4 趟 pass。

(1). Geometry Pass

该 pass 对输入场景模型进行处理,将屏幕各像素对应片元的 texture_color, positon (in world space), normal 和 position (in view space) 输出到 GBuffer 中;

(2). Cal SSAO Pass

该 pass 根据 屏幕各像素对应片元(目标着色点)的 position (in view space) 信息,在各 片元 周围采样,得到采样点,根据采样点 是否会被模型遮挡计算目标着色点的 AO 值;

(3.) Blur SSAO Pass

该 pass 对 上趟流程中计算得到的 AO 进行滤波操作,的到滤波后的 blurredAO;

(4). Lighting (Shading) Pass

该 pass 根据 pass (1) 中得到的 texture_color, positon (in world space), normal 和 pass (3) 中得到的 blurredAO 计算各着色点的颜色值。使用 Phong 着色模型,公式如下:
I = I a ∗ b l u r r e d A O + I d + I s I=Ia * blurredAO + Id + Is I=IablurredAO+Id+Is

3. 主要代码讲解

(1). Geometry Pass Shader

geometryPassShader.vert:

#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNor;
layout(location = 2) in vec2 aTexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;out vec3 vertexPos;
out vec3 vertexNor;
out vec2 textureCoord;
out vec4 vertexViewPos;void main() {textureCoord = aTexCoord;// 裁剪空间坐标系 (clip space) 中 点的位置gl_Position = projection * view * model * vec4(aPos, 1.0f);// 世界坐标系 (world space) 中 点的位置vertexPos = (model * vec4(aPos, 1.0f)).xyz;// 世界坐标系 (world space) 中 点的法向vertexNor = mat3(transpose(inverse(model))) * aNor;// 视图坐标系 (view space) 中 点的位置vertexViewPos = view * model * vec4(aPos, 1.0f);
}

geometryPassShader.frag:

#version 330 core
layout(location = 0) out vec4 FragColor;   // diffuse color
layout(location = 1) out vec3 FragPos;     // position in world space
layout(location = 2) out vec3 FragNor;     // normal in world space
layout(location = 3) out vec4 FragViewPos; // position in view spacein vec3 vertexPos;
in vec3 vertexNor;
in vec2 textureCoord;
in vec4 vertexViewPos;uniform sampler2D texture0;void main() {FragPos = vertexPos;FragNor = vertexNor;FragColor = texture(texture0, textureCoord);FragViewPos = vertexViewPos;
}

(2). Cal SSAO Pass Shader

calSSAOPassShader.vert

#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNor;
layout(location = 2) in vec2 aTexCoord;
out vec2 textureCoord;
void main() {gl_Position = vec4(aPos, 1.0f);textureCoord = aTexCoord;
}

calSSAOPassShader.frag:

#version 330 core
out float AO;
in vec2 textureCoord;uniform mat4 projection;uniform sampler2D textureViewPos; // position (in view space)uniform vec3 gKernel[64]; // random position offset// 计算目标片段的 Ambient Occlusion (AO) 值
// AO in [0,1]
// 为了便于后续计算 代码中的 AO 规定为:
// AO 越接近 0,说明该片段被遮挡的越多(越暗)
// AO 越接近 1,说明该片段被遮挡的越多(越亮)
void main() {vec3 shadeViewPos = texture(textureViewPos, textureCoord).xyz; // 目标片段在 view space 中的坐标AO = 0.0;float gSampleRad = 1.5f;for (int i = 0; i < 64; i++) {vec3 sampleViewPos = shadeViewPos + gKernel[i]; // 在目标片段周围随机采样vec4 sampleProPos =vec4(sampleViewPos, 1.0); // 采样点 在 view space 中的坐标sampleProPos = projection * sampleProPos;sampleProPos.xy /= sampleProPos.w;// 采样点 投影到屏幕,再归一化到[0,1]的 xy 坐标 (即采样点对应的 uv 坐标)sampleProPos.xy = sampleProPos.xy * 0.5 + vec2(0.5, 0.5);// 相机-采样点 射线与场景相交点(场景表面点)对应的 z 值(在 view space 中)float surfaceDepth = texture(textureViewPos, sampleProPos.xy).z;if (abs(shadeViewPos.z - surfaceDepth) < gSampleRad) {// step(a,b) = if (a<b) return 1.0 else return 0.0;// 在 view sapce 中, camera position 为 (0,0,0)// 假如 abs(surfaceDepth) < abs(sampleViewPos.z) 说明 场景表面点 比// 采样点距离相机更近,那么 AO += 1// 假如 abs(surfaceDepth) >= abs(sampleViewPos.z) 说明 采样点 比// 场景表面点 距离相机更近,那么 AO += 0AO += step(abs(surfaceDepth), abs(sampleViewPos.z));}}// 前面 AO 记录的是 '采样点 被 场景表面 遮挡的次数'// 因此需要 令 AO = 1.0 - AO / (采样次数)// 最后得到的 AO 才是目标片段的 AO 值AO = 1.0 - AO / 64;
}

(3). Blur SSAO Pass Shader

blurSSAOPassShader.vert:

#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNor;
layout(location = 2) in vec2 aTexCoord;
out vec2 textureCoord;
void main() {gl_Position = vec4(aPos, 1.0f);textureCoord = aTexCoord;
}

blurSSAOPassShader.frag:

#version 330 core
// out vec4 FragColor;
out float blurredAO;in vec2 textureCoord;uniform sampler2D textureAO;void main() {blurredAO = 0.0;float Offsets[4] = float[](-1.5, -0.5, 0.5, 1.5);float originAO = texture(textureAO, textureCoord).x;for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {float AO = texture(textureAO, textureCoord).r;vec2 tc = textureCoord;tc.x = textureCoord.x + Offsets[j] / textureSize(textureAO, 0).x;tc.y = textureCoord.y + Offsets[i] / textureSize(textureAO, 0).y;blurredAO += texture(textureAO, tc).x;}}blurredAO /= 16.0;
}

(4). Lighting (Shading) Pass Shader

lightingSSAOPassShader.vert:

#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNor;
layout(location = 2) in vec2 aTexCoord;
out vec2 textureCoord;
void main() {gl_Position = vec4(aPos, 1.0f);textureCoord = aTexCoord;
}

lightingSSAOPassShader.frag:

#version 330 core
out vec4 FragColor;in vec2 textureCoord;uniform int state;uniform vec3 lightPos;uniform vec3 cameraPos;
uniform vec3 k;uniform sampler2D textureColor;     // color
uniform sampler2D texturePos;       // position (in world space)
uniform sampler2D textureNor;       // normal (in world space)
uniform sampler2D textureBlurredAO; // blurredAOvoid main() {vec3 vertexPos = texture(texturePos, textureCoord).xyz;vec3 vertexNor = texture(textureNor, textureCoord).xyz;vec3 lightColor = vec3(1.0f, 1.0f, 1.0f);// Ambient// Ia = ka * Lafloat ambientStrenth = k[0];vec3 ambient = ambientStrenth * lightColor;float blurredAO = texture(textureBlurredAO, textureCoord).x;if (state == 0) {// Rendering scene with SSAO on.ambient = ambient * vec3(blurredAO);} else if (state == 1) {// Rendering scene with SSAO off.} else {// Rendering AO.FragColor = vec4(blurredAO);return;}vec3 diffuse = vec3(0, 0, 0);vec3 specular = vec3(0, 0, 0);// Diffuse// Id = kd * max(0, normal dot light) * Ldfloat diffuseStrenth = k[1];vec3 normalDir = normalize(vertexNor);vec3 lightDir = normalize(lightPos - vertexPos);diffuse = diffuseStrenth * max(dot(normalDir, lightDir), 0.0) * lightColor;// Specular (Phong)// Is = ks * (view dot reflect)^s * Lsfloat specularStrenth = k[2];vec3 viewDir = normalize(cameraPos - vertexPos);vec3 reflectDir = reflect(-lightDir, normalDir);specular = specularStrenth * pow(max(dot(viewDir, reflectDir), 0.0f), 2) *lightColor;// Specular (Blinn-Phong)// Is = ks * (normal dot halfway)^s Ls// float specularStrenth = k[2];// vec3 viewDir = normalize(cameraPos - vertexPos);// vec3 halfwayDir = normalize(lightDir + viewDir);// vec3 temp_specular = specularStrenth *//                      pow(max(dot(normalDir, halfwayDir), 0.0f), 2) *//                      lightColor;diffuse = clamp(diffuse, 0.0, 1.0);specular = clamp(specular, 0.0, 1.0);// Obejct colorvec3 objectColor = texture(textureColor, textureCoord).xyz;// Color = Ambient + Diffuse + Specular// I = Ia + Id + IsFragColor = vec4((ambient + diffuse + specular) * objectColor, 1.0f);
}

4. 全部代码及模型文件

使用OpenGL实现 屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO) 的全部代码以及模型文件可以在 OpenGL实现屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO) 中下载。程序运行后按下 空格 键在使用SSAO渲染场景直接渲染场景渲染AO值三种模式中切换。
渲染结果如下:
渲染结果

三、参考

[1].ogl-tutorial45 Screen Space Ambient Occlusion
[2].游戏后期特效第四发 – 屏幕空间环境光遮蔽(SSAO)

相关文章:

[OpenGL]实现屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO)

一、简介 本文介绍了 屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO) 的基本概念&#xff0c;实现流程和简单的代码实现。实现 SSAO 时使用到了 OpenGL 中的延迟着色 &#xff08;Deferred shading&#xff09;技术。 按照本文代码实现后&#xff0c;可以实现以下…...

linux-NFS网络共享存储服务配置

1.NFS服务原理 NFS会经常用到&#xff0c;用于在网络上共享存储&#xff0c;这样讲&#xff0c;你对NFS可能不太了解&#xff0c;举一个例子&#xff0c; 加入有三台机器A,B,C&#xff0c;它们需要访问同一个目录&#xff0c;目录中都是图片&#xff0c;传统的做法是把这些 图…...

w-form-select.vue(自定义下拉框组件)

文章目录 1、w-form-select.vue 组件中每个属性的含义2、实例3、源代码 1、w-form-select.vue 组件中每个属性的含义 好的&#xff0c;我们来详细解释 w-form-select.vue 组件中每个属性的含义&#xff0c;并用表格列出它们是否与后端字段直接相关&#xff1a; 属性解释表格&…...

ovs实现lb负载均衡

负载均衡定义 负载均衡器的实现原理是通过硬件或软件设备将客户端访问流量根据转发策略分发到多个服务器或设备上&#xff0c;以确保系统的负载均衡。常见的实现方式包括&#xff1a; 二层负载均衡‌&#xff1a;使用虚拟MAC地址方式&#xff0c;根据OSI模型的二层进行负载均…...

机器学习-核函数(Kernel Function)

核函数&#xff08;Kernel Function&#xff09;是一种数学函数&#xff0c;主要用于将数据映射到一个更高维的特征空间&#xff0c;以便于在这个新特征空间中更容易找到数据的结构或模式。核函数的主要作用是在不需要显式计算高维特征空间的情况下&#xff0c;通过内积操作来实…...

计算最接近的数

计算最接近的数 真题目录: 点击去查看 E B卷 100分题型 题目描述 给定一个数组X和正整数K&#xff0c;请找出使表达式&#xff1a; X[i] - X[i 1] - … - X[i K - 1] 结果最接近于数组中位数的下标 i &#xff0c;如果有多个 i 满足条件&#xff0c;请返回最大的 i. 其中&…...

【QNX】QNX侧查看内存信息的方法

在QNX实时操作系统中&#xff0c;&#x1f251;查看内存信息的方法有showmem、pidin、top以及hogs等&#x1f447;&#x1f3fb;。 ① showmem &#x1f98b;&#x1f98b;&#x1f98b;showmem可用于显示进程的内存使用情况。 &#x1f98b;&#x1f98b;&#x1f98b;通过…...

逐笔成交逐笔委托Level2高频数据下载和分析:20250121

逐笔成交逐笔委托下载 链接: https://pan.baidu.com/s/15NI2zLXYiczrUMQtwHgUrg?pwdbeiu 提取码: beiu Level2逐笔成交逐笔委托数据分享下载 通过Level2的逐笔成交与委托记录&#xff0c;这种高精度的毫秒级数据能够洞察诸多重要信息&#xff0c;包括庄家目的、误导性行为&am…...

AutoSar架构学习笔记

1.AUTOSAR&#xff08;Automotive Open System Architecture&#xff0c;汽车开放系统架构&#xff09;是一个针对汽车行业的软件架构标准&#xff0c;旨在提升汽车电子系统的模块化、可扩展性、可重用性和互操作性。AUTOSAR的目标是为汽车电子控制单元&#xff08;ECU&#xf…...

2024年智慧消防一体化安全管控年度回顾与2025年预测

随着科技的飞速发展&#xff0c;智慧营区一体化安全管控在2024年取得了显著进展&#xff0c;同时也为2025年的发展奠定了坚实基础。 2024年年度回顾 政策支持力度持续加大&#xff1a;国家对消防安全的重视程度不断提高&#xff0c;出台了一系列涵盖技术创新、市场应用、人才培…...

基于单片机的智能台灯设计

摘要: 方向和亮度,采用的是手动调节。而对于儿童来说,他们通常不知道如何调整以及调整到何种程度。本文设计了一款智能台灯,当有人的 台灯是用于阅读学习而设计使用的灯,一般台灯用的灯泡是白炽灯、节能灯泡以及市面上流行的护眼台灯,可以调节高度、光照的时候,可以根据…...

HJ108 求最小公倍数(Java版本)

一、试题地址 求最小公倍数_牛客题霸_牛客网 二、试题描述 描述 对于给定的两个正整数 a,b&#xff0c;它们的最小公倍数 lcm⁡(a,b) 是指能同时被 a 和 b 整除的最小正整数。 求解 lcm⁡(a,b)。 输入描述&#xff1a; 在一行上输入两个整数 a,b(1≦a,b≦105)。 输出描述…...

使用tritonserver完成clip-vit-large-patch14图像特征提取模型的工程化。

1、关于clip-vit-large-patch14模型 关于openapi开源的clip-vit-large-patch14模型的特征提取&#xff0c;可以参考之前的文章&#xff1a;Elasticsearch向量检索需要的数据集以及768维向量生成这篇文章详细介绍了模型的下载地址、使用方式、测试脚本&#xff0c;可以让你一步…...

实操演练第003讲-数据通途:客户端连接SQL Server的完美攻略

SQL Server简介 基本概念 SQL Server是由微软公司开发的关系型数据库管理系统。它基于SQL&#xff08;Structured Query Language&#xff0c;结构化查询语言&#xff09;来管理和操作数据。SQL Server可以存储大量结构化数据&#xff0c;如客户信息、订单记录、库存数据等&a…...

golang接口

1.概念 golang接口是一个动态类型和动态值的集合&#xff0c;定义了对象的行为&#xff0c;不指定实现。只要一个类型定义了接口全部的方法&#xff0c;就可被认为是实现接口 **动态类型&#xff1a;**实现接口的具体数据类型 **动态值&#xff1a;**实现接口的数据的值或者引…...

LeetCode:37. 解数独

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;37. 解数独 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff…...

数据结构与算法之递归: LeetCode 37. 解数独 (Ts版)

解数独 https://leetcode.cn/problems/sudoku-solver/description/ 描述 编写一个程序&#xff0c;通过填充空格来解决数独问题数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次数字 1-9 在每一列只能出现一次数字 1-9 在每一个以粗实线分隔的 3x3 宫内…...

【氮化镓】香港科技大学陈Kevin-单片集成GaN比较器

一、引言(Introduction) GaN HEMT的重要性 文章开篇便强调了氮化镓(GaN)高电子迁移率晶体管(HEMT)在下一代功率转换系统中的巨大潜力。GaN HEMT具备高开关频率、低导通电阻、高击穿电压以及宽工作温度范围等优势,使其成为功率电子领域的热门研究对象。这些特性使得GaN…...

axios的使用总结

一、Axios 简介 Axios 是一个基于 Promise 的 HTTP 客户端&#xff0c;用于浏览器和 Node.js。在 Vue 项目中&#xff0c;它主要用于发送 HTTP 请求来获取数据&#xff08;如从 API 获取数据&#xff09;或者提交数据&#xff08;如用户登录、注册等表单数据&#xff09;。 二…...

革新未来:高效智能数字人技术引领多元化应用

随着科技的不断进步&#xff0c;数字人技术已逐渐成为企业数字化转型中的重要工具。数字人不仅能够优化客户体验&#xff0c;还可以显著提升企业运营效率。本文将详细介绍一种高性能、高质量、低延迟、快速响应以及安全稳定的数字人技术方案&#xff0c;帮助企业在多元化场景中…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:

根据万维钢精英日课6的内容&#xff0c;使用AI&#xff08;2025&#xff09;可以参考以下方法&#xff1a; 四个洞见 模型已经比人聪明&#xff1a;以ChatGPT o3为代表的AI非常强大&#xff0c;能运用高级理论解释道理、引用最新学术论文&#xff0c;生成对顶尖科学家都有用的…...

以光量子为例,详解量子获取方式

光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学&#xff08;silicon photonics&#xff09;的光波导&#xff08;optical waveguide&#xff09;芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中&#xff0c;光既是波又是粒子。光子本…...

嵌入式学习笔记DAY33(网络编程——TCP)

一、网络架构 C/S &#xff08;client/server 客户端/服务器&#xff09;&#xff1a;由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序&#xff0c;负责提供用户界面和交互逻辑 &#xff0c;接收用户输入&#xff0c;向服务器发送请求&#xff0c;并展示服务…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

三分算法与DeepSeek辅助证明是单峰函数

前置 单峰函数有唯一的最大值&#xff0c;最大值左侧的数值严格单调递增&#xff0c;最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值&#xff0c;最小值左侧的数值严格单调递减&#xff0c;最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

R 语言科研绘图第 55 期 --- 网络图-聚类

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…...