几个实用渲染技术原理和实现
一、快速高斯模糊
1.1 背景
高斯模糊在wiki上定义为一种图像模糊滤波器,使用正态分布计算每个像素输出颜色。正态分布函数和图像如下所示:
由图可以发现,当x在
a. 输入当前像素的
b. 遍历半径为
c. 将当前遍历的像素颜色乘以权重:
d. 将所有颜色求和,并除以权重的和
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这两个亮度值,如果不进行伽马校正则有
伽马校正增大了较暗数值的表示精度,而减小了较亮数值的表示精度, 人眼又恰好对较暗数值比较敏感,对较亮数值不太敏感,于是从视觉角度讲,输出的图像质量就被伽马校正”改善”了
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具