Learn OpenGL 08 颜色+基础光照+材质+光照贴图

news2025/1/10 17:03:21

我们在现实生活中看到某一物体的颜色并不是这个物体真正拥有的颜色,而是它所反射的(Reflected)颜色。物体的颜色为物体从一个光源反射各个颜色分量的大小

创建光照场景

首先需要创建一个光源,因为我们以及有一个立方体数据,我们只需要进行模型变换就可以将它变成我们的光源

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);

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

为了实现这个目标,我们需要为灯的绘制创建另外的一套着色器,从而能保证它能够在其它光照着色器发生改变的时候不受影响。顶点着色器与我们当前的顶点着色器是一样的,所以你可以直接把现在的顶点着色器用在灯上。灯的片段着色器给灯定义了一个不变的常量白色,保证了灯的颜色一直是亮的:

然后再渲染完物体以后把光源渲染出来

       lightShader.use();

       lightShader.setMat4("projection", projection);
       lightShader.setMat4("view", view);
       model = glm::mat4(1.0f);
       model = glm::translate(model, lightPos);
       model = glm::scale(model, glm::vec3(0.2f)); // a smaller cube
       lightShader.setMat4("model", model);

       glBindVertexArray(lightVAO);
       glDrawArrays(GL_TRIANGLES, 0, 36);

基础光照模型

  • 环境光照(Ambient Lighting):即使在黑暗的情况下,世界上通常也仍然有一些光亮(月亮、远处的光),所以物体几乎永远不会是完全黑暗的。为了模拟这个,我们会使用一个环境光照常量,它永远会给物体一些颜色。
  • 漫反射光照(Diffuse Lighting):模拟光源对物体的方向性影响(Directional Impact)。它是冯氏光照模型中视觉上最显著的分量。物体的某一部分越是正对着光源,它就会越亮。
  • 镜面光照(Specular Lighting):模拟有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色

法线变换矩阵

法向量只是一个方向向量,不能表达空间中的特定位置。同时,法向量没有齐次坐标(顶点位置中的w分量)。这意味着,位移不应该影响到法向量。因此,如果我们打算把法向量乘以一个模型矩阵,我们就要从矩阵中移除位移部分,只选用模型矩阵左上角3×3的矩阵(注意,我们也可以把法向量的w分量设置为0,再乘以4×4矩阵;这同样可以移除位移)。对于法向量,我们只希望对它实施缩放和旋转变换。

其次,如果模型矩阵执行了不等比缩放,顶点的改变会导致法向量不再垂直于表面了。因此,我们不能用这样的模型矩阵来变换法向量。下面的图展示了应用了不等比缩放的模型矩阵对法向量的影响:

每当我们应用一个不等比缩放时(注意:等比缩放不会破坏法线,因为法线的方向没被改变,仅仅改变了法线的长度,而这很容易通过标准化来修复),法向量就不会再垂直于对应的表面了,这样光照就会被破坏。

修复这个行为的诀窍是使用一个为法向量专门定制的模型矩阵。这个矩阵称之为法线矩阵(Normal Matrix)。

这里对法线矩阵的解释参照了入门精要里的

变换后的法线与切线的点乘的结果也应该是0,这里将变换后的矩阵与切线转置的原因是M*T得到的结果是一个3行1列的矩阵,GN的结果是3行1列的矩阵,而我们是为了获得法线变换的矩阵,将前面得到的矩阵进行转置矩阵乘法才可以继续进行,而且书里前面说过一个向量是可以当作列矩阵或者行矩阵的。最后的结果就是原变换矩阵的逆转置矩阵。

光照合并

顶点着色器

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

out vec2 TexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec3 FragPos;  
out vec3 Normal;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = mat3(transpose(inverse(model))) * aNormal;
}

片段着色器

#version 330 core
out vec4 FragColor;
in vec3 FragPos;
in vec3 Normal;

uniform vec3 objectColor;
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 viewPos;

void main()
{
    float ambientStrength = 0.1;
    float specularStrength = 0.5;
    vec3 ambient = ambientStrength * lightColor;
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 16);
    vec3 specular = specularStrength * spec * lightColor;
    vec3 result = (ambient + diffuse+specular) * objectColor;
    FragColor = vec4(result, 1.0);
}

 材质

将三个材质的构成分量创建一个结构体

将光的属性也设置成结构体,进而可以更好的控制着色器的各种属性

顶点着色器

#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 projection;

void main()
{
    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = mat3(transpose(inverse(model))) * aNormal;  
    
    gl_Position = projection * view * vec4(FragPos, 1.0);
}

片段着色器

#version 330 core
out vec4 FragColor;

struct Material {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;    
    float shininess;
}; 

struct Light {
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

in vec3 FragPos;  
in vec3 Normal;  
  
uniform vec3 viewPos;
uniform Material material;
uniform Light light;

void main()
{
    // ambient
    vec3 ambient = light.ambient * material.ambient;
  	
    // diffuse 
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(light.position - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = light.diffuse * (diff * material.diffuse);
    
    // specular
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);  
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = light.specular * (spec * material.specular);  
        
    vec3 result = ambient + diffuse + specular;
    FragColor = vec4(result*10, 1.0);
} 

光照贴图

效果

因为很多物体并不只是由一种材质构成的,我们控制不同部分的属性,所以引入了光照贴图

修改原来的结构体,把漫反射和高光都修改为贴图,另外练习中增加的自发光贴图也加进去

贴图采样的原理和之前的教程差不多

顶点着色器

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

out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = mat3(transpose(inverse(model))) * aNormal;  
    TexCoords = aTexCoords;
    gl_Position = projection * view * vec4(FragPos, 1.0);
}

片段着色器

最后的自发光贴图的加了个随时间移动和淡入淡出的效果,并且箱子的钢铁部分是不应该有自发光的,对自发光进行了限制

#version 330 core
out vec4 FragColor;

struct Material {
    sampler2D diffuse;
    sampler2D specular;    
    sampler2D emission;
    float shininess;
}; 

struct Light {
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

in vec3 FragPos;  
in vec3 Normal;  
in vec2 TexCoords;
  
uniform vec3 viewPos;
uniform Material material;
uniform Light light;
uniform float time;

void main()
{
    // ambient
    vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));  	
    // diffuse 
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(light.position - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));    
    // specular
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);  
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); 

    vec3 emission = texture(material.emission,TexCoords+vec2(0.0,time/2)).rgb;
        
    vec3 result = ambient + diffuse + specular+emission;
    FragColor = vec4(result, 1.0);



} 

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

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

相关文章

【论文阅读笔记】Attention Is All You Need

1.论文介绍 Attention Is All You Need 2017年 NIPS transformer 开山之作 回顾一下经典,学不明白了 Paper Code 2. 摘要 显性序列转导模型基于包括编码器和解码器的复杂递归或卷积神经网络。性能最好的模型还通过注意力机制连接编码器和解码器。我们提出了一个新…

Redis部署方式(三)主从模式

在前面单机版的基础上,41为主,30为从。 一、主从搭建 1、主Redis安装 41机器redis主要配置 requirepass redis#!_41 bind 0.0.0.0 port 6379 daemonize yes 2、从redis安装 30机器redis主要配置 requirepass redis#!_30 bind 0.0.0.0 port 6380 da…

Oracle 部署及基础使用

1. Oracle 简介 Oracle Database,又名 Oracle RDBMS,简称 Oracle Oracle系统,即是以Oracle关系数据库为数据存储和管理作为构架基础,构建出的数据库管理系统。是目前最流行的客户/服务器(client/server)或…

关于Transfomer的思考

为何诞生 在说transformer是什么,有什么优势之类的之前,先谈一谈它因何而诞生。transformer诞生最重要的原因是早先的语言模型,比如RNN,由于其本身的训练机制导致其并行度不高,特别是遇到一些长句子的情况下。其次&…

面试题手撕篇

参考博客 开始之前,理解递归 手写 浅拷贝 function shallow(target){if(target instanceof Array){return [...resObj]}else{return Object.assign({},target);} }手写深拷贝 const _sampleDeepClone target > {// 补全代码return JSON.parse(JSON.stringify…

深度学习神经网络训练环境配置以及演示

🎬个人简介:一个全栈工程师的升级之路! 📋个人专栏:高性能(HPC)开发基础教程 🎀CSDN主页 发狂的小花 🌄人生秘诀:学习的本质就是极致重复! 目录 1 NVIDIA Dr…

Flink源码解析(1)TM启动

首先在看之前,回顾一下akka模型: Flink通讯模型—Akka与Actor模型-CSDN博客 注:ActorRef就是actor的引用,封装好了actor 下面是jm和tm在通讯上的概念图: RpcGateway 用于定义RPC协议,是客户端和服务端沟通的桥梁。服务端实现了RPC协议,即实现了接口中定义的方法,做具…

云原生(二)、Docker基础

Docker Docker 是一种开源的容器化平台,用于开发、部署和运行应用程序。它允许开发者将应用程序及其所有依赖项打包到一个可移植的容器中,这个容器可以在任何支持 Docker 的环境中运行,无论是开发人员的个人笔记本电脑、测试环境、生产服务器…

逆序对的数量 刷题笔记

思路 使用归并排序 在每次返回时 更新增加答案数 因为归并排序的两个特点 第一 使用双指针算法 第二 层层返回 从局部有序合并到整体有序 例如 {4 ,1 ,2 ,3} 划分到底层是四个数组 {4},{1},{3}, {…

Java项目:54 springboot工资信息管理系统453

作者主页:舒克日记 简介:Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本系统的使用角色可以被分为用户和管理员, 用户具有注册、查看信息、留言信息等功能, 管理员具有修改用户信息,发…

k8s部署hadoop

(作者:陈玓玏) 配置和模板参考helm仓库:https://artifacthub.io/packages/helm/apache-hadoop-helm/hadoop 先通过以下命令生成yaml文件: helm template hadoop pfisterer-hadoop/hadoop > hadoop.yaml用kube…

《操作系统实践-基于Linux应用与内核编程》第10章-Linux综合应用

前言: 内容参考《操作系统实践-基于Linux应用与内核编程》一书的示例代码和教材内容,所做的读书笔记。本文记录再这里按照书中示例做一遍代码编程实践加深对操作系统的理解。 引用: 《操作系统实践-基于Linux应用与内核编程》 作者:房胜、李旭健、黄…

vue3+ts动态表单渲染,antd的useForm改造

let fieldList: any getFormFields(fieldInfo.coreNavigationList[0].list[0].list,fieldInfo.positionCodeRespVO,isCanBeUpdateProcess.value,isDetail.value 1); fieldInfo数据格式: {"name": "默认模板","status": "ENA…

微信小程序-webview分享

项目背景 最近有个讨论区项目需要补充分享功能,希望可以支持在微信小程序进行分享,讨论区是基于react的h5项目,在小程序中是使用we-view进行承载的 可行性 目标是在打开web-view的页面进行分享,那就需要涉及h5和小程序的通讯问…

K8S CNI

OCI概念 OCI,Open Container Initiative,开放容器标准,是一个轻量级,开放的治理结构(项目),在 Linux 基金会的支持下成立,致力于围绕容器格式和运行时创建开放的行业标准。 OCI 项目…

虚拟机开机字体变大,进入系统后字体模糊

问题 虚拟机开机字体变大,进入系统后字体模糊。 原因 虚拟机配置问题。 解决办法 修改配置为如下:

【兆易创新GD32H759I-EVAL开发板】图像处理加速器(IPA)的应用

GD32H7系列的IPA(Image Pixel Accelerator)是一个高效的图像处理硬件加速器,专门设计用于加速图像处理操作,如像素格式转换、图像旋转、缩放等。它的优势在于能够利用硬件加速来实现这些操作,相比于软件实现&#xff0…

Ubuntu软件开发环境搭建

Ubuntu软件开发环境搭建 安装VMware Tools网络桥接更新软件源常用功能配置时间同步共享文件夹双向复制粘贴终端初始大小和字体设置安装必要的工具 常用指令 安装VMware Tools 点击虚拟机->安装VMware Tools… 打开终端,cd到/media/用户名/VMware Tools/下&#…

JS 事件捕获、事件冒泡、事件委托

js事件机制在开发中可以说时刻使用,例如dom绑定事件、监听其自身事件等。js事件机制有事件捕获、事件冒泡俩种机制,我们分别说下这俩种机制的使用场景。 一、概念 事件捕获顺序如下: window > document > body > div 事件冒泡顺序…

【一】【单片机】有关LED的实验

点亮一个LED灯 根据LED模块原理图,我们可以知道,通过控制P20、P21...P27这八个位置的高低电平,可以实现D1~D8八个LED灯的亮灭。VCC接的是高电平,如果P20接的是低电平,那么D1就可以亮。如果P20接的是高电平,…