几个实用渲染技术原理和实现

一、快速高斯模糊

1.1 背景

​ ​​  高斯模糊在wiki上定义为一种图像模糊滤波器,使用正态分布计算每个像素输出颜色。正态分布函数和图像如下所示:

G(u,v)=12πδ2e(u2+v2)/(2δ2)

​ ​​  由图可以发现,当x在3δ3δ的时候,函数曲线已经很平滑了,实际运用中大概只采样半径为3δ内的像素计算。模糊的过程如下:

​ ​​  ​​  a. 输入当前像素的uv坐标

​ ​​  ​​  b. 遍历半径为3δ内的像素,代入高斯函数中计算得到权重wi

​ ​​  ​​  c. 将当前遍历的像素颜色乘以权重:wir,g,b

​ ​​  ​​  d. 将所有颜色求和,并除以权重的和Σwir,g,bΣwi

1.2 简化过程

​ ​​  高斯模糊算法因为线性可分,可以在二维图像上对两个独立的一维空间分别计算。对于矩形阴影这样的利用高斯模糊实现的效果,可以取捷径将高斯函数写成如下分段函数形式:

// =============================================================================
// approximation to the gaussian integral [x, infty)
// =============================================================================
static finline float gi(float x)
{
const float i6 = 1.f / 6.0;
const float i4 = 1.f / 4.0;
const float i3 = 1.f / 3.0;
if (x > 1.5) return 0.0;
if (x < -1.5) return 1.0;
float x2 = x * x;
float x3 = x2 * x;
if (x > 0.5) return .5625 - ( x3 * i6 - 3 * x2 * i4 + 1.125 * x);
if (x > -0.5) return 0.5 - (0.75 * x - x3 * i3); // else
return 0.4375 + (-x3 * i6 - 3 * x2 * i4 - 1.125 * x);
}

​ ​​  也就是说对于如下图所示的单色边缘交界面,我们可以通过这个函数快速计算得到中间过渡带函数值,其效果和利用高斯函数多次采样纹理的一致。

1.3 shader实现

uniform sample2D baseTexture;
in vec2 texCoord;
out vec4 fragColor;
// approximation to the gaussian integral [x, infty)
float gi(float x) {
float i6 = 1.0 / 6.0;
float i4 = 1.0 / 4.0;
float i3 = 1.0 / 3.0;
if (x > 1.5) return 0.0;
if (x < -1.5) return 1.0;
float x2 = x * x;
float x3 = x2 * x;
if (x > 0.5) return .5625 - ( x3 * i6 - 3. * x2 * i4 + 1.125 * x);
if (x > -0.5) return 0.5 - (0.75 * x - x3 * i3);
return 0.4375 + (-x3 * i6 - 3. * x2 * i4 - 1.125 * x);
}
// create a line shadow mask
float lineShadow(vec2 border, float pos , float sigma) {
float pos1 = ((border.x - pos) / sigma) * 1.5;
float pos2 = ((pos - border.y) / sigma) * 1.5;
return 1.0 - abs(gi(pos1) - gi(pos2));
}
void main()
{
vec4 texCol1=texture(baseTexture,texCoord);
vec4 texCol2=texture(baseTexture,((texCoord-0.5)*2.0*1.5+1.0)*0.5);
float lineV=lineShaow(vec2(0.18,0.82),texCoord.x,0.05);
float lineH=lineShaow(vec2(0.2,0.8),texCoord.y,0.24);
float dist=dot(texCoord,vec2(1.0,1.0));
vec3 edgeCol=mix(vec3(0.8,0.9,0.8),vec3(0.0),lineV*lineH*smoothstep(1.5,0.5,dist));
edgeCol=mix(edgeCol,texCol2.rgb,texCol2.a);
fragColor=vec4(edgeCol,texCol1.a*mix(lineV*lineH,texCol2.a,texCol2.a));
}

二、色调映射(Tone Mapping)

2.1 背景

​ ​​  色调映射就是让亮的场景变暗,暗的场景变亮,并且保存尽可能多的细节。如下图所示,右上角的图像曝光大,亮度高而右下角图像曝光少,亮度低,通过色调映射算法可以将两者亮度范围综合到一起,显示如左侧图像一样,细节看得更清楚。

2.2 Reinhard tone mapping

​ ​​  经验公式如下:

float3 ReinhardToneMapping(float3 color, float adapted_lum)
{
const float MIDDLE_GREY = 1;
color *= MIDDLE_GREY / adapted_lum;
return color / (1.0f + color);
}

​ ​​  解释为输入一个较小亮度0.1时,输出为0.091,当输入一个较大的亮度10时,输出为0.91。也就是归一化到了0到1区间,但是原本的亮度都变暗了。

2.3 Filmic tone mapping

​ ​​  拟合的公式如下:

float3 F(float3 x)
{
const float A = 0.22f;
const float B = 0.30f;
const float C = 0.10f;
const float D = 0.20f;
const float E = 0.01f;
const float F = 0.30f;
return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
}
float3 Uncharted2ToneMapping(float3 color, float adapted_lum)
{
const float WHITE = 11.2f;
return F(1.6f * adapted_lum * color) / F(WHITE);
}

​ ​​  大部分游戏中用的方法,优点是相比较Reinhard有更大的对比度,颜色更鲜艳一些,缺点是运算量比较大。

三、伽马校正(gamma correction)

3.1 背景

​ ​​  早期的CRT显示器输入0.5的值,输出显示的并不是0.5,而是约等于0.218,进行了伽马系数2.2的幂次运算,这称为伽马校正。为了得到正确的输出,需要先乘以1/2.2的补偿运算。伽马校正除了解决早期CRT显示器的非线性输出问题,同时可以帮我们改善输出的图像质量,因为人眼对较暗的亮度值比较敏感,因此现在仍然保留这一过程。

​​  假如我们要存储0.24和0.243这两个亮度值,如果不进行伽马校正则有0.24255=610.243255=61,输出结果一样;而使用伽马校正后有

0.2412.2255=1330.24312.2255=134

​ ​​  伽马校正增大了较暗数值的表示精度,而减小了较亮数值的表示精度, 人眼又恰好对较暗数值比较敏感,对较亮数值不太敏感,于是从视觉角度讲,输出的图像质量就被伽马校正”改善”了

posted @   王小于的啦  阅读(52)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示