6、片元着色器之gamma校正

1、什么是gamma校正?
显示器显示片元着色器输出的颜色值是会进行如下换算: C o l o r s c r e e n = C o l o r g a m m a ( 1 ) Color_{screen}=Color^{gamma} \quad(1) Colorscreen=Colorgamma(1)
其中: C o l o r 是片元着色器输出的颜色值, C o l o r s c r e e n 是显示器实际显示的颜色值, g a m m a 为显示器的 g a m m a 值,不同的显示器的 g a m m a 值可能不同,一般为 2.2 。 Color是片元着色器输出的颜色值,Color_{screen}是显示器实际显示的颜色值,gamma为显示器的gamma值,不同的显示器的gamma值可能不同,一般为2.2。 Color是片元着色器输出的颜色值,Colorscreen是显示器实际显示的颜色值,gamma为显示器的gamma值,不同的显示器的gamma值可能不同,一般为2.2
这就会导致显示器显示的颜色和我们程序输出的颜色不一致,为了校正这一个问题,在片元着色器输出颜色时要进行如下换算: C o l o r g a m m a C o r r e c t = C o l o r 1 / g a m m a ( 2 ) Color_{gammaCorrect}=Color^{1/gamma} \quad(2) ColorgammaCorrect=Color1/gamma(2)
最终可以得: C o l o r s c r e e n = ( C o l o r g a m m a C o r r e c t ) g a m m a = ( C o l o r 1 / g a m m a ) g a m m a = C o l o r Color_{screen}=(Color_{gammaCorrect})^{gamma}=(Color^{1/gamma})^{gamma}=Color Colorscreen=(ColorgammaCorrect)gamma=(Color1/gamma)gamma=Color
此时屏幕显示的颜色和片元着色器输出的颜色一致。
公式(1)称为:gamma反校正
公式(2)称为:gamma校正
2、片元着色器中纹理的gamma反校正
经过gamma校正的颜色值处于gamma空间,反之颜色值处于线性空间,我们在片元着色器中计算光照时,需要使用线性空间的颜色值,但是从纹理图片中读取到的颜色值都是gamma空间的,需要应用gamma反校正将其转换为线性空间的颜色值,在进行光照计算,才能够得到真实的结果。计算完毕后,再将最后的计算结果应用gamma校正,最终输出gamma空间的颜色值。
3、示例代码

// Blinn-Phong.glsl
//限制光线步进的范围,防止计算过长的距离。
const float maxDistance=40.;
// 球心在原点,半径为1.0的球体
float sdSphere(vec3 point)
{
    return length(point)-1.;
}
// 光线步进,start指的是相机(视点)位置;direction表示从相机(视点)发出的光线的方向
float rayMarching(vec3 start,vec3 direction)
{
    float d=0.;
    for(int i=0;i<9999;i++)
    {
        vec3 point=start+d*direction;
        // 像素点到球体表面的距离
        float d0=sdSphere(point);
        // 当d0小于0.001时认为射线和球体表面相交了
        if(d0<.001||d>maxDistance)break;
        d+=d0;
    }
    return d;
}

vec3 getNormal(vec3 p)
{
    float d=sdSphere(p);
    vec2 e=vec2(.001,.0);
    float fdx=d-sdSphere(p-e.xyy);
    float fdy=d-sdSphere(p-e.yxy);
    float fdz=d-sdSphere(p-e.yyx);
    return normalize(vec3(fdx,fdy,fdz));
}
// 构建绕y轴旋转的旋转矩阵
mat2 rot(float angle){
    float c=cos(angle);
    float s=sin(angle);
    return mat2(c,-s,s,c);
}
void mainImage(out vec4 fragColor,in vec2 fragCoord){
    // 将像素坐标标准化为 [-1, 1] 的范围
    vec2 uv=(fragCoord-iResolution.xy*.5)/iResolution.y;
    
    vec3 color=vec3(0.);
    // 视点在z轴正方向上,渲染结果就是相机在该位置看到的结果
    vec3 cameraPosition=vec3(0.,0.,5.);
    
    // 改变direction的z分量实际上相当于修改了fov的大小,fov越小,物体看起来越大(前提是其他参数不变)
    //vec3 direction=normalize(vec3(uv,-1.0));// fov是90°
    vec3 direction=normalize(vec3(uv,-2.));// fov是53.13°
    
    float d=rayMarching(cameraPosition,direction);
    if(d<maxDistance){
        vec3 point=cameraPosition+d*direction;
        vec3 normal=getNormal(point);
        // 定义光源位置,在xoy坐标系的第一象限
        vec3 lightPosition=vec3(5.,5.,0.);
        // 相机绕着y轴转
        lightPosition.xz*=rot(iTime);
        // 定义光源方向
        vec3 lightDir=normalize(lightPosition-point);
        
        // 光源的强度
        float lightIntensity=1.;
        
        // 漫反射系数
        float kd=1.;
        // 漫反射光强(兰伯特模型)
        float diffuseIntensity=kd*lightIntensity*max(dot(normal,lightDir),0.);
        
        //视线向量:从表面指向观察者
        vec3 viewDir=normalize(cameraPosition-point);
        // 半角向量
        vec3 halfVector=normalize(lightDir+viewDir);
        // 镜面反射系数
        float ks=1.;
        //控制高光区域大小的系数。数值越大,光斑越小且越亮
        float shininess=16.0;
        //通过点乘来衡量半角向量是否接近法线方向;pow用于实现高光的集中度
        float specular=pow(max(dot(halfVector,normal),0.0),shininess);

        // 灯光颜色
        vec3 lightColor=vec3(1.,.5,.3);
        // 设置球体颜色
        color+=lightColor*(diffuseIntensity+specular);
    }
    fragColor=vec4(color,1.);
}

// gamma.glsl
const float gamma=2.2;

vec3 gammaCorrect(vec3 color)
{
    return pow(color,vec3(1.0/gamma));
}

vec3 invertGamma(vec3 color)
{
    return pow(color,vec3(gamma));
}
// Blinn-Phong+gamma.glsl

#include "gamma.glsl"
//限制光线步进的范围,防止计算过长的距离。
const float maxDistance=40.;

// 球心在原点,半径为1.0的球体
float sdSphere(vec3 point)
{
    return length(point)-1.;
}
// 光线步进,start指的是相机(视点)位置;direction表示从相机(视点)发出的光线的方向
float rayMarching(vec3 start,vec3 direction)
{
    float d=0.;
    for(int i=0;i<9999;i++)
    {
        vec3 point=start+d*direction;
        // 像素点到球体表面的距离
        float d0=sdSphere(point);
        // 当d0小于0.001时认为射线和球体表面相交了
        if(d0<.001||d>maxDistance)break;
        d+=d0;
    }
    return d;
}

vec3 getNormal(vec3 p)
{
    float d=sdSphere(p);
    vec2 e=vec2(.001,.0);
    float fdx=d-sdSphere(p-e.xyy);
    float fdy=d-sdSphere(p-e.yxy);
    float fdz=d-sdSphere(p-e.yyx);
    return normalize(vec3(fdx,fdy,fdz));
}
// 构建绕y轴旋转的旋转矩阵
mat2 rot(float angle){
    float c=cos(angle);
    float s=sin(angle);
    return mat2(c,-s,s,c);
}


void mainImage(out vec4 fragColor,in vec2 fragCoord){
    // 将像素坐标标准化为 [-1, 1] 的范围
    vec2 uv=(fragCoord-iResolution.xy*.5)/iResolution.y;
    
    vec3 color=vec3(0.);
    // 视点在z轴正方向上,渲染结果就是相机在该位置看到的结果
    vec3 cameraPosition=vec3(0.,0.,5.);
    
    // 改变direction的z分量实际上相当于修改了fov的大小,fov越小,物体看起来越大(前提是其他参数不变)
    //vec3 direction=normalize(vec3(uv,-1.0));// fov是90°
    vec3 direction=normalize(vec3(uv,-2.));// fov是53.13°
    
    float d=rayMarching(cameraPosition,direction);
    if(d<maxDistance){
        vec3 point=cameraPosition+d*direction;
        vec3 normal=getNormal(point);
        // 定义光源位置,在xoy坐标系的第一象限
        vec3 lightPosition=vec3(5.,5.,0.);
        // 相机绕着y轴转
        lightPosition.xz*=rot(iTime);
        // 定义光源方向
        vec3 lightDir=normalize(lightPosition-point);
        
        // 光源的强度
        float lightIntensity=1.;
        
        // 漫反射系数
        float kd=1.;
        // 漫反射光强(兰伯特模型)
        float diffuseIntensity=kd*lightIntensity*max(dot(normal,lightDir),0.);
        
        //视线向量:从表面指向观察者
        vec3 viewDir=normalize(cameraPosition-point);
        // 半角向量
        vec3 halfVector=normalize(lightDir+viewDir);
        // 镜面反射系数
        float ks=1.;
        //控制高光区域大小的系数。数值越大,光斑越小且越亮
        float shininess=16.0;
        //通过点乘来衡量半角向量是否接近法线方向;pow用于实现高光的集中度
        float specular=pow(max(dot(halfVector,normal),0.0),shininess);

        // 灯光颜色
        vec3 lightColor=vec3(1.,.5,.3);
        // 设置球体颜色
        color+=lightColor*(diffuseIntensity+specular);

        // Gamma校正:线性空间->伽马空间 color=pow(color,vec3(1.0/2.2))
        // Gamma反校正:伽马空间->线性空间 color=pow(color,vec3(2.2))

        // 在着色器中计算光照时,需要使用线性空间的颜色进行计算,以确保结果更准确。
        // 但是,在显示器渲染时,颜色需要转换回伽马空间(例如 sRGB),这样渲染效果更真实。

        // 将线性空间的颜色转换到伽马空间(sRGB),以便显示时符合人眼的感知。
        // 此处的 color 被视为线性空间的颜色。
        color=gammaCorrect(color);//这个叫做Gamma校正
        // 显示器会假设传入的颜色已经在伽马空间(sRGB)中,不会再对其进行Gamma校正。
    }

    fragColor=vec4(color,1.);
}


你可能感兴趣的:(着色器,着色器)