【OpenGL学习】OpenGL实现 基于Phong模型的基础光照

news2024/9/28 3:25:28

基于Phong模型的基础光照

在本节中,我们将利用 Phong 光照模型来完成一个简单的光照场景的渲染。

一、Phong 光照模型

Phong光照模型是20世纪70年代被提出的一种渲染逼真图像的方法,模型的提出者是越南出生的计算机图形学研究员Bui Tuong Phong(1942-1975),他提出的近似方法实现了较逼真的图像。

Phong 模型是一种基于经验的光照模型,因此渲染出的图像一般不够真实,但是该模型计算消耗的性能较低,非常适合在一些对真实感要求不太高的场景中使用。

Phong 模型的计算主要由三个分量组成: 环境光项(Ambient)、漫反射项(Diffuse)以及高光反射项(Specular)。三个分量对应的光照结果如下图所示:

img

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

二、添加环境光照

环境光产生的原因一般是某种光源照亮了某个物体,这个物体将光线反射到我们观察的物体上面,使我们能够看到间接的光照,考虑到这种情况,计算就会变得非常复杂,这也是==全局光照(Global Illumination)==算法中研究的内容,Phong 模型允许我们对该部分的照明进行简化,也就是我们所说的环境光照,原理是将一个很小的颜色分量加到物体的每个片元着色计算当中,来模拟间接光照的影响。

在箱子的片元着色器中,添加一个环境光颜色:

void main()
{
	float ambient_strength = 0.1;
	vec3 ambient = ambient_strength * light_color;

	vec3 color = ambient * object_color;
	frag_color = vec4(color, 1.0);
}

这里添加了一个环境光强度,将环境光设置为光源亮度的十分之一,然后我们仅使用环境光照亮物体,得到的结果是这样的:

在这里插入图片描述

三、添加漫反射光照

漫反射表示粗糙物体表面向四面八方反射入射光的现象,这样不论你从哪个位置观察同一个点的光强都是一样的,也就是漫反射光跟观察者的方位无关。但是漫反射和光源的位置有关,对于某个 shading point ,其和光源连线与该 shading point 对应的物体表面的法线之间的夹角越小,漫反射光照越强,如下图:

img

这也很好理解, θ \theta θ 越小,物体表面接收到的光的竖直分量越多(水平分量不照亮物体)。因此,Phong 模型中对于漫反射部分的计算是这样的:

C d i f f u s e = k d C l i g h t m a x ( 0 , n ⃗ ⋅ l ⃗ ) C_{diffuse} = k_d C_{light}max(0,\vec n \cdot \vec l ) Cdiffuse=kdClightmax(0,n l )

各个分量表示的含义:

  • C d i f f u s e C_{diffuse} Cdiffuse 最终计算得到的漫反射颜色;
  • k d k_d kd 物体的漫反射系数(可以简单理解为物体的颜色)
  • C l i g h t C_{light} Clight 光源的颜色
  • $\vec n $ 物体表面的法线方向(需要归一化)
  • $\vec l $ 入射光的方向(便于计算一般指向光源)

因此,计算漫反射光照还需要的两个分量就是法线方向和入射光方向。

首先需要更新顶点数据,加入法线方向信息:新的顶点数据数组

然后重新指定定点布局,并在顶点着色器中进行布局指定:

Buffer_Layout layout2 = {
		{Shader_Data_Type::Float3, "a_Position"},
		{Shader_Data_Type::Float3, "a_normal"}
	};
layout(location = 0) in vec3 a_position;
layout(location = 1) in vec3 a_normal;

在 Phong 模型中,我们所有的光照计算都是在片元着色器中进行的,因此需要将顶点数据中的 normal 传到片元着色器中:

out vec3 v_normal;

void main()
{
	v_normal = a_normal;
	gl_Position = u_mvp * vec4(a_position, 1.0);
}

in vec3 v_normal;

最后就是根据之前的公式对漫反射光照进行计算,现在还缺少的变量是入射光的方向,这个也很好计算,只需要用光源的位置减去渲染目标点的位置即可,注意要使用同一个空间的坐标向量进行相减,这里我们均使用世界空间的坐标进行计算。

这里会用到 model 矩阵,所以我们将 model 矩阵作为 uniform 变量传入:

#version 330 core

layout(location = 0) in vec3 a_position;
layout(location = 1) in vec3 a_normal;

out vec3 v_normal;
out vec3 v_world_pos;

uniform mat4 u_model;
uniform mat4 u_mvp;

void main()
{
		gl_Position = u_mvp * vec4(a_position, 1.0);
    	v_normal = a_normal;
    	v_world_pos = vec3(model * vec4(a_position, 1.0));
    
}

片元着色器中的计算如下:

#version 330 core

out vec4 frag_color;

in vec3 v_normal;
in vec3 v_world_pos;

uniform vec3 light_color;
uniform vec3 object_color;
uniform vec3 light_pos;

void main()
{
	float ambient_strength = 0.1;
	vec3 ambient = ambient_strength * light_color;

	vec3 light_dir = normalize(light_pos - v_world_pos);
	vec3 diffuse_color = light_color * object_color * max(0.0, dot(v_normal, light_dir));

	vec3 color = ambient * object_color + diffuse_color;
	frag_color = vec4(color, 1.0);
}

如果一切都没问题,运行结果是这样的:

在这里插入图片描述

要注意的点是,我们是在世界空间中计算的光照,但是我们并没有把法向量 normal 也转化到世界空间中,那么该如何转化呢?首先要明确一点的是,法线的变换和顶点是不同的,不能像顶点一样乘上变换矩阵转化到别的空间,因为可能会出现下面的情况:

img

法线向量只能保证方向的一致性,而不能保证位置的一致性,所以,所有线向量必须以面的形式进行变换,具体就是要乘变换矩阵的逆转置矩阵,推导如下:

如果我们用一个法向量n = [a, b, c, d] 来描述一个平面,对于平面上的任意点p = [x, y, z, 1] 都遵循如下等式:

ntp = ax + by + cz + d = 0

如果将平面上的点进行可逆变换,乘以个可逆矩阵 R 来得到变换后的点 p1, 假设该平面做相同的变换之后对应的法向量为 n1,法向量 n 可以通过乘变换矩阵 Q 来得到 n1,这里 Q 是未知的:

p1 = R p

n1 = Q n

然后根据等式 n1tp1 = 0, 可以解得矩阵 Q

(Q n)t (R p) = 0

ntQtR p = 0

同时又有ntp = 0,所以QtR = E (单位矩阵),因此 Qt = R-1 , Q = (R-1)t 也就是逆转置矩阵。

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

计算逆转置矩阵并且在顶点着色器中进行声明:

		glm::mat4 normal_matrix = glm::transpose(glm::inverse(model));
		mvp = camera.get_view_projection_matrix() * model;
		box_shader.bind();
		box_shader.set_float3("light_color", glm::vec3(1.0f, 1.0f, 1.0f));
		box_shader.set_float3("object_color", glm::vec3(1.0f, 0.5f, 0.31f));
		box_shader.set_mat4("u_mvp", mvp);
		box_shader.set_mat4("u_model", model);
		box_shader.set_mat4("u_normal_matrix", normal_matrix);
		v_normal = mat3(u_normal_matrix) * a_normal;

四、添加高光反射光照

和漫反射光照一样,镜面光照也决定于光的方向向量和物体的法向量,同时也决定于观察方向,比如眼睛是从什么方向看向这个着色点的。镜面光照决定于表面的反射特性。如果把物体表面设想为一面镜子,那么镜面光照最强的地方就是反射光所在的方向。

在这里插入图片描述

反射向量与观察方向的夹角越小,镜面光的作用就越大。

Phong 模型中,镜面高光的计算是这样的:

C s p e c u l a r = k s C l i g h t p o w ( m a x ( 0 , r ⃗ ⋅ v ⃗ ) , ρ ) C_{specular} = k_s C_{light}pow(max(0,\vec r \cdot \vec v), \rho) Cspecular=ksClightpow(max(0,r v ),ρ)

其中 ρ \rho ρ 是高光反射的反光度(Shininess),物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小。

因此式中需要求解的向量有反射向量 r 和视线方向 v,反射向量 r 可以通过reflect 函数来实现,v则需呀获取视点方向,也就是相机的位置,因此需要声明视点方向的全局变量:

// shader
uniform vec3 view_pos;
//cpp
box_shader.set_float3("view_pos", camera.get_position());

然后就可以在片元着色器中进行高光计算了:

	vec3 view_dir = normalize(view_pos - v_world_pos);
	vec3 reflect_dir = reflect(-light_dir, normal);

	float specular_strength = 0.5;
	vec3 specular_color = specular_strength * light_color * pow(max(dot(view_dir, reflect_dir), 0.0), 35.0);
	vec3 diffuse_color = light_color * max(0.0, dot(normal, light_dir));

	vec3 color = (ambient + diffuse_color + specular_color) * object_color;

通过控制反光度,可以得到下面的效果:

img

将光源的位置实时变化,可以得到下面的结果:

在这里插入图片描述

Reference:

[模型视图变换时,法线向量要乘模型视图矩阵的逆转置矩阵【转】_51CTO博客_模型视图矩阵的作用](

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

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

相关文章

JavaScript中的String和自定义对象~

String对象: 它是 JavaScript 的一种基本的数据类型 String 对象的 length 属性声明了该字符串中的字符数,String 类定义了大量操作字符串的方法,例如从字符串中提取字符或子串,或者检索字符或子串 需要注意的是,Ja…

单行文本域,多行文本域隐藏问题

多行文本域隐藏问题 overflow: hidden; 首先是溢出隐藏&#xff0c;不可或缺 display: -webkit-box; 以弹性盒模型显示 -webkit-box-orient: vertical; 盒模型元素的排列方式 -webkit-line-clamp: 3; 显示行数 <style>.postnameStyle{font-size: 30rpx;font-weight: …

【科研】ET-BERT资料库梳理

作者原repo链接 https://github.com/linwhitehat/ET-BERT 0.资料总库 分为数据模型语料库 1.数据集 包含fine-tuning数据集&#xff08;cstnet-tls 1.3&#xff09;与公开数据集&#xff08;USTC-TFC、VPN-app、VPN-service的数据包级和流级&#xff09;目录链接 1.1 微调…

【博客602】net.ipv4.conf.eth0.route_localnet的作用

net.ipv4.conf.eth0.route_localnet的作用 背景&#xff1a;默认情况下不能将本机的请求跳转/转发到回环接口上 在某些场景下会用在一台主机内网络流量重定向&#xff0c;比如将在本机回环设备中的数据包强行转发到另一台主机上。结果发现原本在正常的NAT场景中生效的iptables…

windbg抓一个windows蓝屏分析

前言 设备一直以来挺稳定&#xff0c;但还是小概率事件意外出现某设备突然蓝屏了。查看windows事件查看器提示计算机已经从检测错误后重新启动。检测错误: 0x0000009f (0x0000000000000003, 0xffffad0f4edc7570, 0xfffff8046a09ec20, 0xffffad0f4ef318a0)。已将转储的数据保存…

搭建不同网络训练MNIST

问题在之前的学习过程中&#xff0c;我们学习了如何搭建全连接神经网络训练Mnist数据集。初始时&#xff0c;全连接神经网络训练结果验证集和训练集的精确度不高&#xff0c;在对数据进行归一化&#xff0c;调参等操作提高了精确度。我们这次使用Le-Net5和VGG对MNIST进行训练&a…

STM32—串口

串口介绍 串行接口简称串口&#xff0c;也称串行通信接口或串行通讯接口&#xff08;通常指COM接口&#xff09;&#xff0c;是采用串行通信方式的扩展接口。串行接口&#xff08;Serial Interface&#xff09;是指数据一位一位地顺序传送。其特点是通信线路简单&#xff0c;只…

.NET MAUI 安卓 UI 资源设置

本文主要介绍使用 MAUI 开发安卓应用时&#xff0c;如何更换和处理 UI 资源&#xff1a;应用名称&#xff0c;图标&#xff0c;主题配色&#xff0c;状态栏&#xff0c;闪屏。 文章目录1. 背景2. 资源设置2.1 项目创建2.2 应用名称2.3 应用图标2.4 应用闪屏2.5 沉浸式状态栏1. …

通用智能如何拥有生命的简单设计

如第一个图所示 是和环境交互的时候 行为交互时间和环境反馈时间T0 T1 还有行为消耗能量E0 环境反馈能量E1 如图有四种情况 其中反馈时间T1小于交互时间T0的任务是积极反馈 和打游戏一样要及时反馈才能提起兴趣 这个是整个行为交互过程中最小的记录单元 图二的每个元素都代表多…

STM32项目-STM32智能小车-电子设计大赛-STM32cubemx-STM32f103c8t6STM32串口通信-

记录项目的详细制作过程&#xff0c;所以笔记很长&#xff0c;图很多、很多图不好CSDN搬运&#xff0c; 我把笔记放网盘或者自己根据资料下载 笔记网盘下载: 链接&#xff1a;https://pan.baidu.com/s/1Mk2EVIha7Fpj4Xductg3Uw?pwdVCC1 提取码&#xff1a;VCC1 笔记CSDN下载:…

C++11 入门

作者&#xff1a;小萌新 专栏&#xff1a;C进阶 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;介绍C11的一些背景知识 本篇博客主要是讲解一些关键字 C11前言C11诞生简介列表初始化{}初始化关键字autodecltypenullptr范围forSTL的更…

技术开发117

技术开发117 业务内容&#xff1a; 半导体制造设备零件&#xff08;阀门零件、管件&#xff09;、汽车的各种功能部件&#xff08;发动机、动力转向器、空调、刹车和传动系统&#xff09;、 建筑和工业设备部件、电信设备的零件、气动特殊气缸、供热和制冷系统的零件、其他一…

CAD中怎么绘制攒尖屋顶?CAD设计攒尖屋顶技巧

在给排水CAD设计中&#xff0c;有些时候为了需要会在图纸中绘制攒尖屋顶&#xff0c;那么你知道CAD软件中怎么构造攒尖屋顶三维模型吗&#xff1f;其实很简单&#xff0c;浩辰CAD给排水软件中提供了实用的攒尖屋顶功能&#xff0c;下面就和小编一起来看看浩辰CAD给排水软件中CA…

Android RCLayout 圆角布局,支持边框,渐变色,渐变色方向等

RCLayout 圆角布局,支持边框,渐变色,渐变色方向等 支持布局 RcRelativeLayout RcLinearLayout RcFrameLayout RcConstraintLayout RcAbsoluteLayout RcTextView 引入 implementation com.github.IHoveYou:RCLayout:1.0.1 项目地址 链接: github 布局属性 <!-- 背景色/渐…

Java-基础-1.异常

一&#xff1a;异常架构 Error 类层次描述了 Java 运行时系统内部错误和资源耗尽错 误。这类错误是我们无法控制的&#xff0c;同时也是非常罕见的错误。所以在编程中&#xff0c;不去处理这类错误。Error 表明系统 JVM 已经处于不可恢复的崩溃状态中。我们不需要管他。 如:写代…

电商前台项目(五):完成加入购物车功能和购物车页面

Vue2项目前台开发&#xff1a;第五章一、加入购物车1.路由跳转前先发请求把商品数据给服务器&#xff08;1&#xff09;观察接口文档&#xff08;2&#xff09;写接口&#xff08;3&#xff09;dispatch调用接口传数据&#xff08;4&#xff09;判断服务器是否已经收到商品数据…

Spring-相关概念入门

Spring-相关概念&入门 2&#xff0c;Spring相关概念 2.1 初识Spring 在这一节&#xff0c;主要通过以下两个点来了解下Spring: 2.1.1 Spring家族 官网&#xff1a;https://spring.io&#xff0c;从官网我们可以大概了解到&#xff1a; Spring能做什么:用以开发web、微服…

六、附近商户,连续签到,UV统计

文章目录附近商户GEO的基本用法导入店铺数据到GEO实现附近商户功能签到BitMap的基本用法实现签到功能实现连续签到统计功能补充&#xff1a;Java中>>和>>>的区别UV统计HyperLogLog的基本用法测试百万数据的统计官方命令文档&#xff1a;https://redis.io/comman…

OpenGLES(一)——介绍

一、OpenGL介绍 OpenGL&#xff08;全写Open Graphics Library&#xff09;是指定义了一个跨编程语言、跨平台的编程接口规格的专业的图形程序接口。它用于三维图像&#xff08;二维的亦可&#xff09;&#xff0c;是一个功能强大&#xff0c;调用方便的底层图形库。     O…

六、创建Gitee仓库和提交代码

1、创建仓库 1.1、创建远程仓库 (1)登录Gitee.com&#xff0c;点击右上角 号&#xff0c;再点击新建仓库。 (2)填写仓库名称&#xff0c;设置公开(一般指开源项目)或者私有&#xff0c;其他默认(也可以根据自己需要选择) (3)这里要勾选设置模板&#xff0c;Readme文件。(如果…