QT with OpenGL(IBL-镜面反射)

这篇具有很好参考价值的文章主要介绍了QT with OpenGL(IBL-镜面反射)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

预滤波

generate Mipmap

Cubemap增加是否生成mipmap选项

if(!mipmap)
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
else
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

if(mipmap) glGenerateMipmap(GL_TEXTURE_CUBE_MAP);

获取每一层级的预滤波图

void CubeMap::getIBLprefilterMapFromEnvCubeMap(unsigned int CubeMap,unsigned int maxMipLevels)
{
    //第一步:编译链接预滤波Shader
    QOpenGLShaderProgram prefilterShader;
    prefilterShader.addShaderFromSourceFile(QOpenGLShader::Vertex,":/cubemap.vert");
    prefilterShader.addShaderFromSourceFile(QOpenGLShader::Fragment,":/prefilterMap.frag");
    prefilterShader.link();

    //第二步:FBO 创建帧缓存、绑定深度缓存和模板缓存
    unsigned int captureFBO, captureRBO;
    glGenFramebuffers(1, &captureFBO);
    glGenRenderbuffers(1, &captureRBO);
    glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
    glBindRenderbuffer(GL_RENDERBUFFER, captureRBO);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, CubeSize, CubeSize);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, captureRBO);

    //第三步:输入Uniform参数,并渲染到当前Cubemap
    prefilterShader.bind();
    prefilterShader.setUniformValue("projection", captureProjection);   //vert
    prefilterShader.setUniformValue("environmentMap", 0);               //frag
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_CUBE_MAP, CubeMap);//此处输入CubeMap
    glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);

    for (unsigned int mip = 0; mip < maxMipLevels; ++mip)
    {
        // 1.计算第i层的mipmap大小
        unsigned int mipi  = CubeSize >> mip;
            //mipmap第i层的长宽为 第0层大小 * 0.5^mip
            // == CubeSize * pow(0.5, mip)
            // == CubeSize >> mip

        // 2.设置该mip层的渲染窗体大小
        glBindRenderbuffer(GL_RENDERBUFFER, captureRBO);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, mipi, mipi);
        glViewport(0, 0, mipi, mipi);

        // 3.根据mipmap的层级选择预滤波的滤波模糊度
        float roughness = (float)mip / (float)(maxMipLevels - 1);
        prefilterShader.setUniformValue("roughness", roughness);        //frag

        // 4.渲染得到该层级的预滤波立方体贴图
        for (unsigned int i = 0; i < 6; ++i)
        {
            prefilterShader.setUniformValue("view", lookatMatrix[i]);   //vert
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                   GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, envCubemap, mip);

            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            renderCube();
        }
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

}

prefilterMap Shader

#version 450 core

//输入:预滤波方向
in vec3 WorldPos;
out vec4 FragColor;
//主要参数:
uniform samplerCube environmentMap;//环境立方体贴图
uniform float roughness;//控制预滤波的模糊程度

//辅助参数:
uniform uint sample_count;//每方向采样数
uniform bool enMapHasMipmap;//是否有mipmap
uniform int environmentMapSize;//环境立方体贴图大小

const float PI = 3.1415926535;
void main(void)
{
    vec3 N = normalize(WorldPos);

    //采样数可设为Uniform
    //const uint SAMPLE_COUNT = 1024u;
    const uint SAMPLE_COUNT = sample_count;
    //采样结果保存
    vec3 prefilteredColor = vec3(0.0);
    float totalWeight = 0.0;

    //重要性采样
    for(uint i = 0;i<SAMPLE_COUNT;++i){
        //得到采样方向
        vec3 L = N;

        //采样(roughness 0-1 属于 mipmap 0 - 7)
        prefilteredColor += texture(environmentMap, L).rgb;
        totalWeight += 1.0f;
    }

    prefilteredColor = prefilteredColor / totalWeight;
    FragColor = vec4(prefilteredColor, 1.0);
}

其中获取采样方向,以及确定采样层级是较为关键的部分。

重要性采样
#version 450 core

//输入:预滤波方向
in vec3 WorldPos;
out vec4 FragColor;
//主要参数:
uniform samplerCube environmentMap;//环境立方体贴图
uniform float roughness;//控制预滤波的模糊程度

//辅助参数:
uniform uint sample_count;//每方向采样数
//uniform bool enMapHasMipmap;//是否有mipmap
//uniform int environmentMapSize;//环境立方体贴图大小

//辅助函数
float RadicalInverse_VdC(uint bits);
vec2 Hammersley(uint i, uint N);
vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness);
float DistributionGGX(vec3 N, vec3 H, float roughness);

//const
const float PI = 3.1415926535;


void main(void)
{
    vec3 N = normalize(WorldPos);

    //采样数可设为Uniform
    //const uint SAMPLE_COUNT = 1024u;
    const uint SAMPLE_COUNT = 1024u;
    //采样结果保存
    vec3 prefilteredColor = vec3(0.0);
    float totalWeight = 0.0;

    //重要性采样
    for(uint i = 0;i<SAMPLE_COUNT;++i){
        //得到采样方向
        vec2 randomVec2 = Hammersley(i,SAMPLE_COUNT);
        vec3 H = ImportanceSampleGGX(randomVec2,N,roughness);
        vec3 L = normalize(2.0 * dot(N, H) * H - N);

        //问题:该反射方向可能会向物体背部反射,所以要去除背向光线
        //那为何不直接使用H作为反射方向,这样可以避免光线的失效,增加采样数,减少L的计算时间
        float NdotL = max(dot(N,L),0.0f);
        if(NdotL > 0.0f){
            //采样(roughness 0-1)
            //float D = DistributionGGX(N,H,roughness);
            prefilteredColor += texture(environmentMap, L).rgb;
            totalWeight += 1.0f;

        }
    }

    prefilteredColor = prefilteredColor / totalWeight;
    FragColor = vec4(prefilteredColor, 1.0);
}
效果展示

mipmap 0
QT with OpenGL(IBL-镜面反射)
mipmap 1
QT with OpenGL(IBL-镜面反射)
QT with OpenGL(IBL-镜面反射)

mipmap 2
QT with OpenGL(IBL-镜面反射)
mipmap 3
QT with OpenGL(IBL-镜面反射)
QT with OpenGL(IBL-镜面反射)
mipmap 4

QT with OpenGL(IBL-镜面反射)
QT with OpenGL(IBL-镜面反射)
mipmap 5 --全黑(就不截图了)
mipmap 4.9
QT with OpenGL(IBL-镜面反射)
可以看出没有被渲染的mipmap层级存在(不会报错),但值为纯黑。
因此如果渲染中间层,会将前一层与该黑色层混合,而不报错。

另外可以看到立方体贴图的贴图之间并未进行滤波。
OpenGL 可以启用 GL_TEXTURE_CUBE_MAP_SEAMLESS,以为我们提供在立方体贴图的面之间进行正确过滤的选项:

glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);  

如下:可以看到边角位置不再有明显边界
mipmap 3.6
QT with OpenGL(IBL-镜面反射)
mipmap 4.9
QT with OpenGL(IBL-镜面反射)

注意:开启该选项后,并不是对纹理做模糊,而是当指向像素边界时,会根据边界临近的纹理像素做插值。
因此,如果只是在预滤波中开启GL_TEXTURE_CUBE_MAP_SEAMLESS,而在显示预滤波图时不开启GL_TEXTURE_CUBE_MAP_SEAMLESS,我们看到的边界结果才是纹理中真正存储的像素值。

纹理真正记录的数据如下,GL_TEXTURE_CUBE_MAP_SEAMLESS相当于边界处的 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR)函数。
QT with OpenGL(IBL-镜面反射)

预过滤卷积的亮点

解决方法

较高采样率区域使用较低mipmap(更清晰)级别的纹理进行采样。
较低采样率区域使用较高mipmap(更模糊)级别的纹理进行采样。

代码解析

首先得确保我们被采样的环境贴图有mipmap贴图
glBindTexture(GL_TEXTURE_CUBE_MAP,envCubemap);
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);

环境贴图mipmap
mipmap 1
QT with OpenGL(IBL-镜面反射)
mipmap 2
QT with OpenGL(IBL-镜面反射)
mipmap 3
QT with OpenGL(IBL-镜面反射)
mipmap 4
QT with OpenGL(IBL-镜面反射)
mipmap 7
QT with OpenGL(IBL-镜面反射)

通过计算决定使用那一层mipmap值

mipmap每加1层,纹理大小就缩小一半,每四个像素合成一个像素。
因此,
如果一个采样点覆盖率为4个像素,则应该在第1层mipmap上采样;
如果一个采样点覆盖率为16个像素,则应该在第2层mipmap上采样;
如果一个采样点覆盖率为8个像素,则应该在 l o g 2 ( 8 1 2 ) = 0.5 ∗ l o g 2 ( 8 ) log_2(8^\frac{1}{2}) = 0.5 * log_2(8) log2(821)=0.5log2(8)层mipmap采样。

现在问题转移到了一个采样点覆盖的像素数为多少

一个采样点覆盖的像素数 = 该方向像素数 该方向采样数 一个采样点覆盖的像素数 = \frac{该方向像素数}{该方向采样数} 一个采样点覆盖的像素数=该方向采样数该方向像素数
已知,球面坐标上的WorldPos指向立方体贴图,每个单位立体角向量指向的贴图像素数也是不同的。
向量指向立方体贴图边角位置,该方向的像素数会偏多。
而指向立方体一面中心位置,像素数就会偏少。

这里做平均处理,将该方向像素数平均。
该方向像素数 = 立方体像素数 球面积分 = 6 ∗ 分辨 率 2 4 π 该方向像素数 = \frac{立方体像素数}{球面积分} = \frac{6 * 分辨率^2}{4\pi} 该方向像素数=球面积分立方体像素数=4π6分辨2

该方向采样数 = 总采样数 ∗ 该方向采样概率 = S A M P L E _ C O U N T ∗ p d f 该方向采样数 = 总采样数 * 该方向采样概率 \\= SAMPLE\_COUNT * pdf 该方向采样数=总采样数该方向采样概率=SAMPLE_COUNTpdf

综上:
一个采样点覆盖的像素数 = 6 ∗ r e s o l u t i o n 2 4 π ∗ 总采样数 ∗ p d f 一个采样点覆盖的像素数 = \frac{6 * resolution^ 2}{4\pi * 总采样数 * pdf } 一个采样点覆盖的像素数=4π总采样数pdf6resolution2

问题又来了:pdf怎么计算?
p d f pdf pdfImportanceSampleGGX函数生成的H向量计算得到L 的分布
H的向量分布为均匀分布的伪随机数。

这里 Chetan Jags 做了近似,将 p d f pdf pdf 近似为法线分布函数计算得到的值。

p d f = D i s t r i b u t i o n G G X ( N ⋅ H , r o u g h n e s s ) ∗ ( N ⋅ H ) 4 ∗ ( H ⋅ V ) pdf = \frac{DistributionGGX(N\cdot H, roughness) * (N \cdot H) }{4 * (H \cdot V)} pdf=4(HV)DistributionGGX(NH,roughness)(NH)
因为 N = = V N==V N==V,所以简化为
p d f = D i s t r i b u t i o n G G X ( N ⋅ H , r o u g h n e s s ) 4 pdf = \frac{DistributionGGX(N\cdot H, roughness) }{4 } pdf=4DistributionGGX(NH,roughness)

综上,代码为:

    //重要性采样
    for(uint i = 0;i<SAMPLE_COUNT;++i){
        //得到采样方向
        vec2 randomVec2 = Hammersley(i,SAMPLE_COUNT);
        vec3 H = ImportanceSampleGGX(randomVec2,N,roughness);
        vec3 L = normalize(2.0 * dot(N, H) * H - N);

        //问题:该反射方向可能会向物体背部反射,所以要去除背向光线
        //那为何不直接使用H作为反射方向,这样可以避免光线的失效,增加采样数,减少L的计算时间
        float NdotL = max(dot(N,L),0.0f);
        if(NdotL > 0.0f){
            //计算采样mipmap
            float D = DistributionGGX(N,H,roughness);
            float pdf = D / 4.0 + 0.0001;

            //一个采样点对应四个采样像素,mipmap=1;
            //mipmap级别 = 0.5 * log_2(一个采样点采样的像素数) ;
            //一个采样点采样的像素数 = 每方向像素数 / (采样数量 * 该方向采样概率)
            // = 6 * res * res / (4 * PI * SAMPLE_COUNT * pdf);
            //每像素平均立体角,当前采样方向的概率
            float resolution = 512.0; // 原空间盒清晰度 (per face)
            float TexPerSample = 4 * resolution * resolution / (6 * PI * SAMPLE_COUNT * pdf);

            float mipLevel = ( roughness == 0.0 ? 0.0 : 0.5 * log2(TexPerSample) );

            //加权
            prefilteredColor += textureLod(environmentMap, L, mipLevel).rgb * NdotL;
            totalWeight      += NdotL;
        }
    }

最后加权NdotL是为了减少较大倾角对像素点的权值。

平均

prefilteredColor = prefilteredColor / totalWeight;
FragColor = vec4(prefilteredColor, 1.0);

效果

上方为解决两点的显示,下方为之前的显示。
mipmap 3
使用预过滤环境贴图LINEAR,使用加权NdotL
QT with OpenGL(IBL-镜面反射)
使用预过滤环境贴图NEAREST,使用加权NdotL
QT with OpenGL(IBL-镜面反射)
未使用预过滤环境贴图LINEAR未使用加权 NdotL

QT with OpenGL(IBL-镜面反射)
未使用预过滤环境贴图LINEAR未使用加权 NdotL
QT with OpenGL(IBL-镜面反射)
未使用预过滤环境贴图NEAREST,使用加权 NdotL

QT with OpenGL(IBL-镜面反射)

mipmap 4

QT with OpenGL(IBL-镜面反射)

QT with OpenGL(IBL-镜面反射)
综上:在一般情况下使用预过滤环境贴图,效果不大,但使用加权 NdotL可以很大程度上改变效果,将更多的采样权值放在中心采样区。

预计算BRFD

生成LUT图

已知视口方向与法线的夹角 N ⋅ V N \cdot V NV),粗糙度 r o u g h n e s s roughness roughness
得到与 F 0 F_0 F0 无关的两个参数。

LUT图纹理的坐标:
横坐标:视口方向与法线的夹角( N ⋅ V N \cdot V NV
纵坐标:粗糙度( r o u g h n e s s roughness roughness

纹理存储的值:
R F 0 F_0 F0 的比例
G F 0 F_0 F0 的偏差

:LUT图与材质无关(粗糙度,金属度),与环境贴图无关,与视口法线无关。因此所有的IBL镜面反射只需要一个LUT图(所有材质的BRDF都是基于金属度粗糙度给定的 F 0 F_0 F0,而且反射方式相同的情况下)。

QT with OpenGL(IBL-镜面反射)

IBL Shading

输入Uniform增加如下参数

// IBL
uniform samplerCube irradianceMap;
uniform samplerCube prefilterMap;
uniform int maxMipmapLevels;

uniform sampler2D LUT;

环境光反射计算

//环境光镜面反射 specular
vec3 kS = fresnelSchlick(max(dot(N, V), 0.0), F0);
vec3 prefilterColor = textureCubeLod(prefilterMap,R,roughness * maxMipmapLevels).rgb;
vec2 brdf = texture2D( LUT, vec2( max(dot(N,V),0.0) , roughness )).rg;
vec3 specular = prefilterColor * (kS * brdf.x + brdf.y);

合入渲染结果

//合计最终值
vec3 color = ambient + specular + Lo;

渲染结果

QT with OpenGL(IBL-镜面反射)

与教材的不同

教材中 k S k_S kS 的计算使用了如下方程

vec3 kS = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness);

//调用如下方程计算kS
vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
{
    return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}

渲染效果如下
QT with OpenGL(IBL-镜面反射)
差别,使用fresnelSchlickRoughness(教材)函数计算的 F 0 F_0 F0 相比于fresnelSchlick(个人)函数会更小一点。也就是说反射量会小一点。

但比较两图,肉眼上并无差别。
QT with OpenGL(IBL-镜面反射)
QT with OpenGL(IBL-镜面反射)

最终结果展示

QT with OpenGL(IBL-镜面反射)文章来源地址https://www.toymoban.com/news/detail-428896.html

到了这里,关于QT with OpenGL(IBL-镜面反射)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Qt OpenGL(四十)——Qt OpenGL 核心模式-雷达扫描效果

    提示:本系列文章的索引目录在下面文章的链接里(点击下面可以跳转查看): Qt OpenGL 核心模式版本文章目录 一、场景 上一篇文章介绍了在雷达坐标系中绘制飞行的飞机,其实雷达坐标系应该还有一个效果,就是扫描的效果,类似于下面的图(注:图片来源于百度):  二

    2024年02月13日
    浏览(36)
  • Qt OpenGL(四十二)——Qt OpenGL 核心模式-GLSL(二)

    提示:本系列文章的索引目录在下面文章的链接里(点击下面可以跳转查看): Qt OpenGL 核心模式版本文章目录 冯一川注:GLSL其实也是不断迭代的,比如像3.3版本中,基本数据类型浮点型只支持float型,而GLSL4.0版本开始就有double型数据的支持了,所以本系列GLSL部分以GLSL4.5版

    2024年02月08日
    浏览(46)
  • 代码笔记:Generate lmages with Stable Diffusion

    目录 1、conda环境 2、使用过程中遇到的问题 (1)环境名称:ldm_py38 (2)pip -e git+https://github.com/CompVis/taming-transformers.git@master#egg=taming-transformers (以及pip -e git+https://github.com/openai/CLIP.git@main#egg=clip)报错:ERROR: Command errored out with exit status 128 报错原因:服务器的SSL证书没有经

    2024年02月10日
    浏览(37)
  • 【nlp-with-transformers】|Transformers中的generate函数解析

    今天社群中的小伙伴面试遇到了一个问题,如何保证生成式语言模型在同样的输入情况下可以保证同样的输出。 这里面造成问题的因素有两个方面: 一个方面是在forward过程中参数的计算出现了差异,这种情况一般发生在游戏显卡中,游戏显卡无法保证每一次底层算子计算都

    2024年02月09日
    浏览(40)
  • 【论文阅读】(DALLE-3)Improving Image Generation with Better Captions

    引用: Betker J, Goh G, Jing L, et al. Improving image generation with better captions[J]. Computer Science. https://cdn. openai. com/papers/dall-e-3. pdf, 2023, 2(3): 8. 论文链接: https://arxiv.org/abs/2212.09748 论文展示了 通过在高度描述性的生成图像captions上训练,可以显著提高文本到图像模型的提示跟随能力 。

    2024年03月12日
    浏览(48)
  • Qt OpenGL 3D模型

    这次教程中,我们将之前几课的基础上,教大家如何创建立体的3D模型。我们将开始生成真正的3D对象,而不是像之前那几课那样3D世界中的2D对象。我们会把之前的三角形变为立体的金字塔模型,把四边形变为立方体。 我们给三角形增加左侧面、右侧面、后侧面来生成一个金

    2024年02月11日
    浏览(49)
  • Qt OpenGL相机系统

    效果展示 一直偷懒没有学习OpenGL,乘着这段有点时间重新学习一下OpenGL,做一个简单的小工具,有助于后面理解OSG。我们都知道OpenGL中存在着下面几个坐标空间:模型空间(物体空间)、世界空间、观察空间(或者称作人眼空间)、裁剪空间以及屏幕空间。本质上他们是五个坐

    2024年02月05日
    浏览(46)
  • OpenGL 网格拾取坐标(Qt)

    有时候我们希望通过鼠标来拾取某个网格中的坐标,这就涉及到一个很有趣的场景:光线投射,也就是求取一条射线与网格的交点,这里如果我们采用普通遍历网格中的每个面片的方式,当网格的面片数据量很大时计算效率就很存在问题,因此这里我们使用一种更为理想的方

    2024年01月17日
    浏览(36)
  • DiffBIR: Towards Blind Image Restoration with Generative Diffusion Prior

    论文链接:https://arxiv.org/abs/2308.15070 项目链接:https://github.com/XPixelGroup/DiffBIR 我们提出了DiffBIR,它利用预训练的文本到图像扩散模型来解决盲图像恢复问题。我们的框架采用两阶段pipeline。在第一阶段,我们在多种退化中预训练恢复模块,以提高现实场景中的泛化能力。第二

    2024年02月09日
    浏览(38)
  • Enabling Large Language Models to Generate Text with Citations

    本文是LLM系列的文章,针对《Enabling Large Language Models to Generate Text with Citations》的翻译。 大型语言模型(LLM)已成为一种广泛使用的信息搜索工具,但其生成的输出容易产生幻觉。在这项工作中,我们的目标是使LLM能够生成带有引用的文本,提高其事实的正确性和可验证性。

    2024年02月12日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包