颜色 /About Color --图形学的B面(二)

本文深入探讨了图形学中颜色的本质,解释了颜色感知、测量及在图形渲染中的应用。从光谱到RGB,再到XYZ颜色空间,揭示了颜色背后的物理学原理。并介绍了光谱渲染、色域限制及色度感知在游戏渲染领域的挑战。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

                                                                                                                                                                                        --图形学的B面(二)

本文谈论“颜色”,是图形学的B面系列文章第二篇,这首先是一篇技术文章,和艺术无关,要深入探讨颜色背后的问题,颜色不是RGB,不是一个数字。本文要解决下面几个问题。

What is a Color?

What Causes a Color?

How to Measure a Color?

 

 

  • What Is a Color

颜色是在特定观察条件下进入视网膜中的一束光线经过视网膜的反应产生的人类感受。颜色是对光的感受。一束光是由各种不同强度不同波长的光波组成的(或者叫做光谱spectrum)。所以颜色是视网膜对光谱的感受。无法客观度量视网膜的主观感受,所以我们度量光波的组成。更直接的说,颜色是光谱组成。一个光谱,一个颜色。

 

每种单波长的光都也都会对应一个颜色,人可以感知的光波的范围在350-750nm之间,下面是一个单波长光的颜色对应。

当然大部分光是由这些基础单波长光复合组成的。

 

  • 光谱

颜色的背后是光谱。我们来探讨光谱的意义。

 

  • 光谱能量分布

下面图是一个光谱的表示,光谱即Spectrum Power Distribution,它描述一束光中每个波长的光所辐射的能量(Intensity,每秒每个立体角的能量通过),这个曲线的积分就是这束光的强度。(有时会以560nm光的强度归为1来绘制这个图,这样只要知道这光的总强度,也能知道每个光波的强度)。

 

                          光谱能量分布SPD(w)

  • 光波能量

我们知道光波的能量更波长呈反比,即下面图的曲线样子

 

      

                       光波能量随波长的变化 (Energy(w))

 

因此我们可以从SPD(w)和Energy(w)算出这束光中没个波长的光波的数量SPD(W)/Energy(w)

 

  • 材质

光存在光谱,但是经过物质的反射,其中一些光谱被吸收多,一些反射的多,因此我们看到的物质反射后的光(即物质的外观)又和原始的光源不一样,这里通常也会存在一个物质对光谱的反射曲线,如下图,他叫做表面光谱反射曲线srf,他表示了不同光谱的光在物体表面被反射的情况,它决定了物质的外观或颜色。

 

  • 光度(Photometry

然而SPD并不能反应出人眼的感知强度,也就是说光能越强,不一定人眼看上去越强,因为颜色是人的感知的结果。这里面存在一个简单的对应关系,下图是CIE(国际照明组织)的光度曲线,反应不同波长的光的感知强度。

 

                  

                                       Photometry(w)

我们可以看到能量最强的蓝光感知起来其实很弱,能量中间的绿光对人眼感受器的效果最强。这也是为什么绿色的彩色照片转换成黑白后要比蓝紫更白一些。我们把SPD(W)*SRF(w)/Energy(w)

同Photometry(w)相乘的积分可以拿到人对这束光的感知强度Intensity,这个Intensity是个标量(即黑白)的强度。记住光能强不代表看上去强。

 

  • 色度(Colorimetry

色度是人眼除了光度之外对颜色的另一种概念,即除了强弱,还有外观的区别,即赤橙黄绿青蓝紫。如何度量色度,这是要讨论的重点问题。

 

  • 度量颜色

怎样度量一个颜色?学过图形学的人会说是用RGB么?

理论上给出一个光的光谱组成,就可以确定它的颜色。所以度量一个色度的真正方式应该是用它的光谱

然而这极其不便,尤其用于图形学计算时极其复杂,光谱是多种光复合而来的,组元众多。计算效率表达起来都有问题。怎么办?我们先理解下异谱同色的概念。

 

上面是两个完全不同的光谱,然而他们给视网膜造成的反应是一致的,即你看到了同样的颜色,这种情况很多。所以人眼很可怜,你能看到两个相同颜色的光,但是你可能看不到他们是完全不同的光波,即人天生是只能看到表象的动物,这物理现象叫异谱同色。但是

表达颜色的方法就有了转机,即我们为了表达光谱A这个颜色,通过构造另外一个光谱B,使得B只有少数的N(n1,n2,n3)个单波长的光组成,这样我们通过表达n1,n2,n3的强度值(或百分比)就能表达这个颜色了。而且表达除A外的其他任意光谱,都要用这一组波长的N(n1,n2,n3)来模拟。n1,n2,n3的强度值就是我们对光谱降到N维后对颜色的表示。例如下面左边这个组元众多的光谱和右边这个只有少数光波有值的颜色一致。我们就用后者的几个光的比例表达这个颜色。

 

N(n1,n2,n3)可以随便挑选几个单波长的光么?可以,但是问题是误差会很大,如果你不精心挑选,会发现无论怎样都无法用N(n1,n2,n3)来模拟一个可见光谱。于是人们精心靠“人肉”尝试挑选了3个单光谱光,分别是444nm  526nm 和645nm,他们的光谱的颜色对应于人类所称为的蓝色附近,绿色附近和红色附近。如果用这三种单波长光表达所有单波长的可见光,图表会变成下面这样

 

     

单波长可见光的RGB波长(异谱同色)的表示 RGB(W)

这里看到为何人肉挑出来的是这三个光,它们组合的特点是恰好在其中一个波长光取极大值时另外两个光近似为0,这很符合正交基的特性,适合作为一个度量坐标系。

那么如何计算一个光谱的rgb颜色,用前面的SPD(W) *SRF(w)/Energy(w)同RGB(W)的三个元素为别相乘求积分即可获得。

但是即使看上去这么完美的RGB也不能很好的表达所有的可见光。比如430-510nm左右的一些蓝光的r为负值,即这些光不能被rgb的体系表达。所以颜色不是RGB,rgb是一种对光谱降维到3个主元后对颜色的近似表达。RGB是对颜色的降维表达,光谱才是颜色。

 

  • 标准照明体和色温

除了rgb这种单波长光谱的组合表达之外,还有一种用色温表示光谱颜色的方式。人们首先通过定义一些预先定义的标准化光谱来定义一些标准化颜色。这些标准化光谱假设这些光是从一个标准的不吸收光源的黑体发射出来的(black body)。这些黑体也称为标准照明体,大多数标准照明体是自然或人工的常见光源,如:白炽灯源,中午太阳等等。

因为根据普朗克的黑体辐射定律,黑体的辐射和它的温度存在对应关系,所以又一般可以一个温度来描述一个标准照明体的颜色,这就是色温的来源,每个光谱都有一个色温,但值得注意的是,色温是对光谱能量的度量,而非感知能量的度量,不同的光谱,色温可能是相同的,色温的度量角度是光谱本身的辐射能,而不是从感知角度出发的,所以色温的大小和感知的强度大小没有关系,色温和标准照明体通常是为了定义一个光源基线。

色温一般随光的辐射能量的增长而增长,色温能量越高,颜色也趋于蓝色,否则趋于红色。

 

简单说,人们预先定义了一些标准化的光谱来表示一些重要的复杂光源产生的颜色,并且可以用色温来更简单的对应这些标准化光谱。如

CIE的A系列表示白炽灯,2856k

CIE的C系列表示北半球的白天光6774k

CIE的D65表示北半球的中午天光 6504k

下图表示两种荧光灯的标准照明体的光谱。

 

  • 全色域

Rgb不能表达所有的可见光颜色。怎么办?人类发现不可能找到有限的几种单波长的光的叠加来表达所有颜色,于是提出了一组(3种)虚假的波长的光,称为X Y Z,所有单波长可见光在XYZ叠加模拟下的曲线是如下图的

 

                            XYZ(W)

 

这里面XYZ这三种单波长光是不存在的,它纯粹为了让人类能够书面表达所有可见光(因为这里没有负值)。XYZ是怎么算出来的?用前面的SPD(W) *SRF(w)/Energy(w)

同上面的xyz色度曲线XYZ(W)相乘积分即可以得到。

XYZ其实是对RGB的简单线性变换,而且有个特点,上图的xyz曲线的覆盖区域是相同的,并且Y恰好等于前面说的光度(即亮度)。XYZ是理论上表达所有可见光的颜色模型。

 

  • Spectrum Lotus光谱迹

当把所有单波长的可见光的在xyz空间坐标系下的坐标点连成线,会变成下图,这被称为可见光的单光谱迹。

 

             

单波长可见光的xyz坐标系下的表示

随着光强的增强,它会长成一个缺口的圆锥性(Cone),这里为什么会有一个“缺口”,那是因为在380的蓝光和780的红光不是首尾相接的。即没有任何单波长的可见光的xyz处于缺口处。(那么缺口处是否有可见颜色呢?有,但那不是单波长光,是多种波长复合出来的,这些颜色叫做超谱色extra-spectrum color,典型的如紫色,紫色区段是少数不能靠单一一种波长的光表现出来的颜色而这个缺口处的直线也被称为purple line,即紫线

 

所有单波长可见光围成的cone。

  • 没有光度的色度

人们为了单纯的讨论颜色的色度,会把颜色中的光度变量解耦出去,单纯的讨论没有亮度概念的色度,就会把这个所有可见色的xyz同x+y+Z=1的平面相截取(这个平面可看做覆盖了所有色度),然后因为xyz三者相加定值,所以可以删去其中一个变量,即将被截的xyz再投影到xy平面,上面这个Cone就会变成下面这个拍扁了的马蹄形。

 

从xyz投影全色域图

 

                                                   全色域图

 

         这个马蹄形的只有xy两个维度的图就很容易看了,它的边界是表示了所有的单波长可见光,依次从左下角的380nm顺时针到右上角的780nm。然后其余的颜色都是靠多种波长的光复合出来的,右下角的那条是我们前面提到的紫线,紫色就在紫线的右下部分。(注意千万不要以为所有可见颜色都在这个马蹄形内,大量的紫色就不在它只是单波长光的颜色的合围

 

  • 色域

虽然人类的所有可见光不只在这个马蹄形内,但是很遗憾人类现有物理设备能够表示的颜色却确实都在这个马蹄形围成的范围内,或者只是其中的部分子集。例如广泛使用的sRGB表示的颜色范围是里的一个三角形,用于印刷行业的AdobeRGB则色域更广一些

从这里看来虽然马蹄形只是可见光的一部分,但是rgb空间能表达的颜色(RGB色域)在整个人类可见颜色的(全色域)中又只占更小的一部分,世间的大部分颜色(那些各种饱和度的绿,还有那大量的多种多样的紫)都不能被你的物理设备展现。很遗憾吧。

 

那么我们为什么不能使用一种基于xyz的物理设备,不就能表达所有的颜色了么,因为前面说了xyz是不真实存在的单波长光,没有设备能够发出xyz的单波长光并将它们复合,xyz只能用来做理论的度量。

 

这里可以再讨论下上面这个问题,为何人类现有物理设备能够表示的颜色都在这个马蹄形里,只能无限逼近它原因就在于物理设备的颜色表现方式都是基于color additive,即将N种单色光波叠加来通过异谱同色的原理模拟任意颜色,即还是像前面说的你用N个主要维度的光叠加是不能完全获得所有的光谱的。除了用完整光谱(即完整的所有维度的波长的光)模拟颜色不可能表达所有可见光。这是一个低维不能完整模拟高维的问题。所以理论上,用更多种单波长颜色的光叠加,就能获得更多的颜色,例如一些高级的显示器用了4色光叠加。而大多数设备基于硬件的制约还只能支持发射rgb三种原色。

那么设备如何模拟支持色域之外的颜色,一般只能通过降低饱和度等方式使其强行变成色域内的一个颜色,这叫做gamut mapping。

 

  • 光谱渲染

当游戏的图形渲染进入了PBR的时候,似乎很多人发现原来我们找到了更贴近物理的表达光线,颜色的方式。但是PBR只解决了光线传递的物理模拟,当pbr还在使用基于rgb这种低微的颜色的量化方式的时候,就还不能说是真正的物理,真正的物理要基于光谱的量化,基于光谱的辐射强度表达,基于光谱的材质,基于光谱的反射,吸收,折射,因此有些领域已经进入了光谱渲染,虽然做不到模拟全光谱的量化,但是选取更主要的4道,6道,8道波长的光也会提高真实度,或者说现有基于rgb的光照计算体系是使用了3道主元波长的光谱渲染。看几个光谱渲染的对比。下面的茶壶在rgb的渲染下甚至得到的颜色都是完全错误的。

 

 

 

  • 色度和色度感知

正如本文正文的第一句话,研究颜色有一个大的前提是在特定的观察条件下,只有当观察环境,光照固定的情况下,用色度去预测人的颜色感受才是合理的。因为人对颜色的感受还受颜色周围的环境对比,人眼的自适应性,颜色的同化作用等影响,如下图,你感受到的是一种颜色么

 

然而在游戏渲染业界,人们也开发了各种方式来模拟这些因素对颜色的影响。图形渲染领域说到底都是在为颜色而打工,你的一切工作只是为了计算一个降了维的颜色值。因为渲染领域的一个终极目标就是,使物理设备对你视网膜的刺激和真实物体对你视网膜的刺激是一样的。

 

本文内容参考了大量siggraph2018 course的相关内容。作为知识记录和分享整理与此。

<think>好的,用户现在想访问本地服务器127.0.0.1:8848的about-us页内容。我需要先理清楚解决这个问题的步骤,并确保回答准确可靠。 首先,用户提到的本地服务器地址是127.0.0.1:8848,端口8848可能对应某个特定的服务,比如Nacos、Spring Boot应用或者其他Web服务器。about-us页通常是一个静态页或者由后端路由处理的动态页。我需要指导用户如何访问这个页,并解决可能遇到的问题。 根据引用内容,用户可能需要检查服务是否运行、端口是否正确、防火墙设置、使用curl或浏览器访问等。例如,引用[3]提到在Docker容器内启动Flask服务时需要设置地址为0.0.0.0,否则外部无法访问,这可能与本地服务绑定有关。如果服务只绑定到localhost或127.0.0.1,可能需要调整配置。 另外,引用[2]和[4]展示了使用curl命令测试不同端口和路径的方法,用户可以参考这些示例来测试自己的端点。例如,使用curl -X GET http://127.0.0.1:8848/about-us来检查响应。 接下来,我需要分步骤说明可能的解决方法: 1. **确认服务运行状态**:检查服务是否正在运行,是否有错误日志。用户可以使用系统服务管理命令(如systemctl)或查看日志文件。 2. **验证端口监听**:使用netstat或lsof命令确认8848端口是否被正确监听。例如,`netstat -tuln | grep 8848`,如果没看到监听,可能是服务未启动或配置错误。 3. **检查防火墙设置**:确保本地防火墙允许8848端口的入站连接。虽然本地访问可能不受防火墙限制,但某些配置可能阻止了访问。 4. **使用curl测试**:通过curl发送请求,查看响应状态码和内容。例如,`curl -v http://127.0.0.1:8848/about-us`,这会显示详细的请求和响应信息,帮助诊断问题。 5. **浏览器访问**:直接在浏览器输入地址,查看是否有页内容或错误提示。如果返回404,可能是路径不正确;如果连接被拒绝,可能是服务未运行或端口错误。 6. **检查应用路由配置**:如果服务是Web应用(如Django、Flask、Spring Boot),需要确认路由是否配置了/about-us路径。查看应用的路由文件或配置文件,确保该路径存在且正确处理。 7. **查看服务日志**:应用的日志可能记录访问请求或错误信息,帮助定位问题。例如,Flask应用的日志会显示接收的请求和异常信息。 8. **验证Host头部**:某些服务可能依赖Host头部来路由请求,类似于引用[2]中的例子,用户可能需要指定Host头部,如`curl -H "Host: example.com" http://127.0.0.1:8848/about-us`。 9. **考虑容器化部署**:如果服务运行在Docker容器内,确保端口映射正确,并且容器内的服务绑定到0.0.0.0,如引用[3]所述。使用`docker ps`检查端口映射,确认宿主机的8848端口映射到容器的正确端口。 10. **检查反向代理配置**:如果使用了Nginx等反向代理,确保代理配置正确地将请求转发到about-us页的后端服务。引用[2]中的Nginx配置示例可能需要参考。 在回答过程中,需要引用相关的引用内容,比如提到容器部署时的绑定地址问题,或者使用curl测试的方法。同时,用户可能遇到的问题包括端口被占用(引用[1]提到代理端口被占用的情况)、服务未正确绑定、路由未配置等,需要逐一排查。 最后,生成的相关问题需要围绕用户可能进一步的需求,比如如何查看端口占用、配置Web服务器路由、调试服务启动问题等,确保覆盖用户可能的后续疑问。</think>### 分步解答 要访问本地服务器 $127.0.0.1:8848$ 的 `/about-us` 页内容,需按以下步骤操作: --- #### 步骤 1:确认服务运行状态 - **检查服务进程**:通过命令查看服务是否运行: ```bash ps aux | grep <服务名称> # 如java、python、nginx等 ``` 若服务未启动,需先启动服务。例如Spring Boot项目: ```bash java -jar your-app.jar ``` --- #### 步骤 2:验证端口监听状态 - **查看端口占用**: ```bash netstat -tuln | grep 8848 # 或使用 lsof lsof -i :8848 ``` - 若输出为空,说明端口未被监听,需检查服务配置或启动日志。 - 若服务绑定到 `127.0.0.1`(仅本地访问),可跳过防火墙检查[^3]。 --- #### 步骤 3:使用浏览器或命令行测试访问 - **浏览器直接访问**: 输入 `http://127.0.0.1:8848/about-us`,可能出现以下情况: - **200 OK**:页正常显示。 - **404 Not Found**:路径 `/about-us` 未配置路由。 - **Connection refused**:服务未运行或端口错误。 - **通过curl命令测试**: ```bash curl -v http://127.0.0.1:8848/about-us ``` 若返回结果类似引用[4]的JSON数据,说明接口存在;若返回 `404`,需检查路由配置。 --- #### 步骤 4:检查应用路由配置 - **Web框架路由检查**: - **Flask示例**: ```python @app.route('/about-us') def about_us(): return "About Us Page" ``` - **Spring Boot示例**: ```java @GetMapping("/about-us") public String aboutUs() { return "about-us"; } ``` 确保路由定义与访问路径完全匹配(注意大小写和特殊符号)。 --- #### 步骤 5:排查容器化部署问题(如适用) - **检查Docker端口映射**: ```bash docker ps # 查看容器端口映射 ``` 确保容器启动命令包含 `-p 8848:8848`,且容器内服务绑定到 `0.0.0.0`[^3]。 --- #### 步骤 6:分析服务日志 - **查看实时日志**: ```bash tail -f /var/log/<服务日志文件>.log ``` 日志中可能出现以下关键信息: - `Path "/about-us" not found`:路由未配置。 - `Address already in use`:端口被占用(需终止占用进程)[^1]。 --- ### 常见问题解决方案 | 问题现象 | 解决方案 | |--------------------------|--------------------------------------------------------------------------| | 端口被占用 | 终止占用进程:`kill -9 $(lsof -t -i:8848)` 或修改服务端口[^1] | | 路由未配置 | 检查代码中的路由定义,添加 `/about-us` 处理逻辑 | | 容器内服务无法访问 | 确保服务绑定到 `0.0.0.0`,而非 `127.0.0.1`[^3] | | 防火墙拦截 | 本地访问一般无需处理,跨主机访问需开放端口:`ufw allow 8848` | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值