LearnOpenGL-光照章节(颜色、基础光照、材质、光照贴图)

news2025/1/9 14:53:06

LearnOpenGL-光照章节(颜色、基础光照、材质、光照贴图)

  • 颜色
    • 创建一个光照场景
  • 基础光照
    • 一、环境光照
    • 二、漫反射光照
    • 三、镜面反射
  • 材质
  • 光照贴图
    • 一、漫反射贴图
    • 二、镜面光贴图
    • 三、放射光贴图

颜色

我们在现实生活中看到某一物体的颜色并不是这个物体真正拥有的颜色,而是它所反射的(Reflected)颜色。换句话说,那些不能被物体所吸收(Absorb)的颜色(被拒绝的颜色)就是我们能够感知到的物体的颜色。
当把光源的颜色与物体的颜色值相乘,所得到的就是这个物体所反射的颜色(也就是我们所感知到的颜色)。

vec3 lightColor = vec3(1.0,1.0,1.0); //光是白色
vec3 toyColor = vec3(1.0,0.5,0.31);
vec3 res = light * toyColor;

创建一个光照场景

物体的片元着色器新建两个uniform变量:lightColor、objectColor

#version 330 core
out vec4 FragColor;

uniform vec3 objectColor;
uniform vec3 lightColor;

void main()
{
    FragColor = vec4(lightColor * objectColor, 1.0);
}

我们还要创建一个光源立方体,所以要为这个物体创建专门的VAO

unsigned int lightVAO;
glGenVertexArrays(1, &lightVAO);
glBindVertexArray(lightVAO);
// 只需要绑定VBO不用再次设置VBO的数据,因为箱子的VBO数据中已经包含了正确的立方体顶点数据
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 设置灯立方体的顶点属性(对我们的灯来说仅仅只有位置数据)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

然后设置颜色uniform变量

// 在此之前不要忘记首先 use 对应的着色器程序(来设定uniform)
lightingShader.use();
lightingShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("lightColor",  1.0f, 1.0f, 1.0f);

当我们修改顶点或者片段着色器后,灯的位置或颜色也会随之改变,这并不是我们想要的效果。我们不希望灯的颜色在接下来的教程中因光照计算的结果而受到影响,而是希望它能够与其它的计算分离。我们希望灯一直保持明亮,不受其它颜色变化的影响(这样它才更像是一个真实的光源)。所以为其准备一个专门的片元着色器

#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0f);
}

然后根据需求分别计算物体和光源的mvp矩阵

基础光照

Phong光照模型的主要结构由3个分量组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。
关于光照模型的原理可以看 Games101学习笔记 Shading
在这里插入图片描述

  • 环境光:其他物体的光,在Phong模型中设置为常量
  • 漫反射:假设光线到物体是均匀反射出去的,模拟光源对物体方向性影响
  • 镜面光照(高光反射):模拟有光泽物体上面出现的亮点。完全反射

一、环境光照

考虑物体接受来自其他物体的反射光,叫做全局光照,我们一般把环境光照设置为常量

float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;

二、漫反射光照

计算漫反射光照需要用到法线向量,可以直接在顶点数据中定义,顶点着色器也要声明aNormal以及输出变量Normal

计算漫反射光照:diffuse = lightColor * objectColor * max(dot(normal, lightDir), 0)。为了得到光源方向,我们需要得到光源位置(在片段着色器中设置为uniform变量 lightPos)和顶点位置(在顶点着色器中作为输出 FragPos变量)

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

out vec3 FragPos;
out vec3 Normal;

uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;

void main()
{
    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = aNormal;  
    
    gl_Position = proj * view * vec4(FragPos, 1.0);
}
#version 330 core
out vec4 FragColor;

in vec3 Normal;  
in vec3 FragPos;  
  
uniform vec3 lightPos; 
uniform vec3 lightColor;
uniform vec3 objectColor;

void main()
{
    // ambient
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;
  	
    // diffuse 
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;
            
    vec3 result = (ambient + diffuse) * objectColor;
    FragColor = vec4(result, 1.0);
} 

目前片段着色器里的计算都是在世界空间坐标中进行的。所以,我们应该把法向量也转换为世界空间坐标,我们需要一个法线矩阵来进行转换,法线矩阵被定义为「model矩阵左上角3x3部分的逆矩阵的转置矩阵」,可以在顶点着色器中进行

Normal = mat3(transpose(inverse(model))) * aNormal;

矩阵求逆是一项对于着色器开销很大的运算,因为它必须在场景中的每一个顶点上进行,所以应该尽可能地避免在着色器中进行求逆运算。最好先在CPU上计算出法线矩阵,再通过uniform把它传递给着色器(就像模型矩阵一样)。

三、镜面反射

通过根据法向量翻折入射光的方向来计算反射向量。然后我们计算反射向量与观察方向的角度差,它们之间夹角越小,镜面光的作用就越大。

计算镜面反射:specular = lightColor * specularColor * max(dot(halfDir, normal)) ^ gloss
halfDir半程向量为光线方向与观察方向的角平分线方向向量,normalize(lightDir, viewDir)即可
viewDir即为摄像机位置-顶点位置

//specular
float specularStrength = 0.5;
vec3 viewDir = normalize(viewPos - FragPos);
vec3 halfDir = normalize(viewDir + lightDir);
vec3 specular = lightColor * specularStrength * pow(max(dot(norm, halfDir), 0.0), 32);

材质

如果我们想要在OpenGL中模拟多种类型的物体,我们必须针对每种表面定义不同的材质(Material)属性。我们可以分别为三个光照分量定义一个材质颜色(Material Color):环境光照(Ambient Lighting)、漫反射光照(Diffuse Lighting)和镜面光照(Specular Lighting)。通过为每个分量指定一个颜色,我们就能够对表面的颜色输出有细粒度的控制了。
在片段着色器中,我们创建一个结构体(Struct)来储存物体的材质属性

#version 330 core
struct Material {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    float shininess; //反光度(Shininess)分量,影响镜面高光的散射/半径。
}; 

uniform Material material;

如果要调整每个部分的光照影响度

struct Light {
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform Light light;

光照贴图

一、漫反射贴图

它是一个表现了物体所有的漫反射颜色的纹理图像。在着色器中使用漫反射贴图的方法和“纹理”教程是完全一样的。但这次会将纹理储存为Material结构体中的一个sampler2D。我们将之前定义的vec3漫反射颜色向量替换为漫反射贴图

struct Material {
    sampler2D diffuse;
    vec3      specular;
    float     shininess;
}; 
...
in vec2 TexCoords;

更新两个VAO的顶点属性指针来匹配新的顶点数据,并加载箱子图像为一个纹理。在绘制箱子之前,我们希望将要用的纹理单元赋值到material.diffuse这个uniform采样器,并绑定箱子的纹理到这个纹理单元

lightingShader.setInt("material.diffuse", 0);
...
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);

二、镜面光贴图

对于木头来说,我们不想让它镜面反射,可以将镜面反射材质设为vec3(0.0),但是对于金属边框,我们可以使用专门用于镜面光的贴图。
镜面高光的强度可以通过每个像素的亮度(每个像素都可以用一个颜色向量表示)来获取,在片元着色器中,使用采样对应的颜色值乘上镜面强度

struct Material {
    sampler2D diffuse;
    sampler2D specular;
    float     shininess;
};
.....
vec3 ambient  = light.ambient  * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse  = light.diffuse  * diff * vec3(texture(material.diffuse, TexCoords));  
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
FragColor = vec4(ambient + diffuse + specular, 1.0);

在主程序中,新建一个纹理,在渲染之前先把它绑定到合适的纹理单元上

lightingShader.setInt("material.specular", 1);
...
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, specularMap);

三、放射光贴图

是储存了每个片段的发光值(Emission Value)的贴图
直接用采样得到的像素值来附在纹理上就好,不受光照影响

struct Material {
    sampler2D diffuse;
    sampler2D specular;
    sampler2D emissive;
    float shininess; 
}; 
......
 //自发光
vec3 emissive = vec3(texture(material.emissive, TexCoords)).rgb;
vec3 result = (ambient + diffuse + specular + emissive) * objectColor;
FragColor = vec4(result, 1.0);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, emissiveMap);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1987710.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity强化工程 之 SpriteShape(精灵地形编辑器)

本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正 1.什么是SpriteShape? SpriteShape 是一个灵活且功能强大的世界构建资源,它可以沿着形状的轮廓进…

基于springboot+vue+uniapp的“口腔助手”小程序

开发语言:Java框架:springbootuniappJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包&#…

秒懂C++之deque及反向迭代器

目录 前言 一.deque的常用接口 二.deque的原理 2.1 vector与list的优缺点 2.2 deque的原理 三.反向迭代器 四.全部代码 前言 秒懂C之List-CSDN博客 秒懂C之vector(下)-CSDN博客 本文后面关于反向迭代器的操作会涉及到前面的文章~ 一.deque的常用接…

WriterSide 文档、接口自动编译并部署到GitPage

WriterSide 自动编译并部署到GitPage 1. GitHub 创建空仓库2. 配置GitHub 仓库的编译部署方式3. WriteSide 创建项目4. 创建自动、编译部署配置文件5. 自动编译、部署1. GitHub 创建空仓库 在 GitHub 创建一个空的仓库 仓库创建成功后, 记录仓库的远程地址 仓库地址需要修改…

弥散制氧机与变压吸附制氧机的差异

在氧气供应领域,弥散制氧机和变压吸附制氧机是常见的两种设备,它们在工作原理、性能特点、应用场景等方面存在着显著的区别。 工作原理: 弥散制氧机是通过富氧膜的渗透作用,将空气中的氧气分离并富集,从而提供一定浓度…

计算机的错误计算(五十四)

摘要 回复网友关于正确计算计算机的错误计算(五十一)与(五十二)中所述案例时的 3点注意事项。 问:对于计算机的错误计算(五十一)中的案例 ,由(五十二)知&a…

中国云计算技术(二)

目录 三、国产大数据库技术(一)阿里巴巴OceanBase(二)云创存储数据立方(DataCube) 三、国产大数据库技术 (一)阿里巴巴OceanBase OceanBase主要是为了解决淘宝网的大规模数据而产生…

临床数据科学中如何用R来进行缺失值的处理(上)

在临床科研中,由于失访、无应答或记录不清等各种原因,经常会遇到数据缺失的问题。本文将深入探讨医学科研中数据缺失的成因、分类、影响以及应对方法,结合R语言的实际应用,为医学研究人员提供全面的解决方案。 一、认识缺失数据 …

Python酷库之旅-第三方库Pandas(070)

目录 一、用法精讲 281、pandas.Series.dt.daysinmonth属性 281-1、语法 281-2、参数 281-3、功能 281-4、返回值 281-5、说明 281-6、用法 281-6-1、数据准备 281-6-2、代码示例 281-6-3、结果输出 282、pandas.Series.dt.tz属性 282-1、语法 282-2、参数 282-…

AndroidStudio 两种Debug模式

第一种:直接运行Debug 第二种:运行 attach debugger to android process 优缺点: 第一种是需要把整个工程运行起来,耗时, 第二种是触发式调试,在出错的情况下,经过判断在出错的地方,…

Leetcode75-6 反转字符串中的单词

思路 1. 先把全部字符串反转 然后按空格分割字符串 最后输出即可 有一个问题就是 多个空格的情况 需要用正则表达式 参考文章【JAVA学习之字符串分割空格】_如何将字符串用不确定的空格分开-CSDN博客 分割多个空格时可以需要用到正则表达式。。 正则表达式\s表示匹配任何空白字…

乡村振兴旅游综合体建设方案

1. 乡村振兴旅游综合体概述 乡村振兴旅游综合体建设方案旨在通过现代信息技术的应用,如云计算、物联网、大数据等,实现旅游行业的智慧化升级。该方案涵盖了游客、旅游管理部门、商家等不同角色的需求,以期提升旅游体验,推动乡村振…

OpenCV专栏介绍

在当今人工智能和计算机视觉领域,OpenCV作为一个功能强大的开源库,已经成为实现各种视觉算法的基石。本“OpenCV”专栏致力于帮助读者深入理解并掌握OpenCV的使用,从而在计算机视觉项目中发挥关键作用。 专栏导读 随着技术的不断进步&#…

免费代理池是什么,如何使用代理IP进行网络爬虫?

互联网是一个庞大的数据集合体,网络信息资源丰富且繁杂,想要从中找到自己需要的信息要花费较多的时间。为了解决这个问题,网络爬虫技术应运而生,它的主要作用就是在海量的互联网信息中进行爬取,抓取有效信息并存储。然…

【原型模式】设计模式系列:高效克隆的艺术(深入解析)

文章目录 Java设计模式之原型模式详解1. 引言2. 原型模式概述2.1 定义与基本原理2.2 原型模式与其他模式的关系2.3 使用场景分析 3. Java中的Cloneable接口3.1 Cloneable接口简介3.2 Object类中的clone方法3.3 实现Cloneable接口的步骤3.4 克隆方法的重写示例 4. 深克隆与浅克隆…

Django-Oscar开发独立站/外贸商城教程与问题记录

​特别说明: 本博客为个人开发Django-Oscar时的经验总结,方便后期维护!(第一次这么认真的记录这种大型项目,打个广告吧:本人可接单算法程序开发,包含深度学习和图像相关……等相关)…

秒懂C++之stack、queue、堆

目录 前言 一.stack常用接口 二.stack模拟实现 三.例题 3.1 最小栈 题目解析: 算法解析: 代码: 3.2 栈的压入、弹出序列 题目解析: 算法解析: 代码: 3.3 逆波兰表达式求值 题目解析: 算法解析…

【Web】从TFCCTF-FUNNY浅析PHPCGI命令行注入漏洞利用

目录 背景 CVE-2012-1823 发散利用 法一:读文件 法二:数据外带 背景 CVE-2012-1823 PHP-CGI远程代码执行漏洞(CVE-2012-1823)分析 | 离别歌 省流: 命令行参数不光可以通过#!/usr/local/bin/php-cgi -d include…

目标检测 | yolov4 原理和介绍

1. 简介 YOLOv4是一种高效且准确的目标检测模型,它在YOLOv3的基础上引入了多项改进,这些改进主要集中在网络结构的优化和训练技巧的更新上。以下是YOLOv4中的一些关键技术或模块,它们对提高目标检测性能起到了重要作用: CSPDarkne…

MATLAB基础应用精讲-【数模应用】配对样本Wilcoxon检验(附MATLAB、R语言和python代码实现)

目录 知识储备 常用的统计假设检验的方法 算法原理 什么是Wilcoxon符号秩检验? 何时使用Wilcoxon符号秩检验 适用条件 SPSS-符号秩检验 一统计理论 二实例分析 三拓展知识 SAS --配对样本Wilcoxon符号秩检验 SPSSAU 配对样本Wilcoxon检验案例 1、背景 2、理论 …