光照的个人推导过程与GL实现

news2024/11/13 15:09:55

目录

1、前提知识

1.1、GL的绘图过程:

1.2、点积的规则和作用:

1.3、normalize在方向处理上的作用

2、光照控制的理论基础

2.1、自由的实现:

2.2、带有方向性的光——基于dot product的实现

最终效果演示如下:

3、关键代码介绍:

具体代码位置:


        对于感兴趣的领域。不求甚解地做出同样的东西其实是不让我满足的,我想要的是获得思考出这些规则的基础能力,这样才可能自由地创造更多的东西。创造力的基础就是真正的掌握自身,所以我打算从0开始自己推导一遍,完全靠自己实现这些功能,所以不一定完全和教程的一致。掌握怎么用是不够的,我还想掌握这是怎么想出来的

1、前提知识

1.1、GL的绘图过程:

        不知道大家有没有想过,为什么设定3个顶点,GL就能帮助用户形成一个三角形面呢?其实是因为GL会根据顶点之间的dx、dy、dz以线性的方式自动逐渐变化填充而成一个面。每一次drawcall都可以这么理解:gl都会按照用户设定的分组配置,只要是vertexAttribute形态的对象,就会以每次一组数据,如一组顶点、一组颜色,以它们之间的线性变化值,每变化一次就送入顶点shader和片元shader执行,如下所示:

        举个例子,设置顶点[ [(-1,0,0), (1,0,0), (0,1,0) ]]。

        x轴会以一个很小的dx,从-1慢慢到1再到0,每次dx变化都会应用顶点shader和片元shader的逻辑,形成一个各参数按要求连续变换的面。

        因此哪怕只是设定了3个顶点和3个颜色,GL也依然能填充出一个光滑的三角形并以光滑的片元颜色进行填充,而无需人为地沿着顶点逐步微分绘制,非常方便。这种自动微分的特性让用户得以方便地实现各种数学方法。

        若以二维来看,可以观察如下例子,在设定了三个顶点后,GL就会微分并遍历这些顶点,就像下图所示。向量并微分为无数个小向量并形成一个三角形面:

 

1.2、点积的规则和作用:

        这里其他作者已经写得挺好了,就直接转载吧:

向量点乘与叉乘的概念及几何意义 - 知乎 (zhihu.com)

        这里只截取一些关键概念介绍

 

1.3、normalize在方向处理上的作用

可以参考我之前的博客文章:

http://t.csdn.cn/pZ6Tt

        简单来说,normalize处理后的向量只有各轴投影相对向量长度的比例,也就是它的方向,这在很多场合都有重要作用,例如把不同大小的向量拉到同一标准比较、一些只需要对向量的方向关系进行处理和比较,而向量长度对处理过程无益的场合等,都会用到normalize进行处理。图形图像中大量用到这个方法。

2、光照控制的理论基础

2.1、自由的实现:

        因为所有的效果都是基于数学和逻辑之上,所以实现的方式是完全自由的、甚至可以是不自然的,不像显示的光线必须有一点发出,再通过物体反射到视点,在gl的虚拟空间中,可以简化为:

        1、光源顶点距离物体顶点越远,顶点对应片元颜色越浅

        2、从光源出发到某顶点的向量和物体顶点的方向性越一直,或者夹角越小,顶点对应片元颜色越深

2.2、带有方向性的光——基于dot product的实现

首先

        已知1:标准化计算可以得到向量的方向特征,

        已知2:点积计算可以计算两个向量的方向上的相似性,换句话说就是夹角越小值越大。

        但颜色值最大只为1,如果使用原始向量,点积后的结果可能比1大,因此我打算先标准化再进行对比。

        于是颜色浓度公式即:

dot(normalize(光向量), normalize(顶点向量))

        但这个式子的问题在于,GL的顶点向量实际可以理解为无数同方向特征的、长度很小的分向量相加起来的,这些分向量和顶点向量本身有相同的方向特征,因此dot结果对总向量处处有效,无法实现只让部分分向量显示颜色的需求。

        于是,我们可以这样想,光向量只要在顶点构成的面范围内,那么必然有一条顶点向量与它完全重合,若把顶点向量-光向量,即把光向量的起始点从原点,变为顶点向量-光向量的位置上,也就是变为顶点向量中某长度的一点上,这样在这个长度之下的分向量的点积就会变成负数(负数颜色在GL中不显示),从而实现“只让总向量部分分向量显示颜色的需求”。

 以上逻辑的式子:

        dot(normalize(光向量), normalize(顶点向量 - 光向量))

以上逻辑的图示如下:

        红色为光向量,整个右半象限的顶点向量和分向量的方向特征向量与光向量的方向特征向量点积都>0,也就是整个右半象限都有光,即以原点为起点的光线,而光线向量其实唯一的作用就是提供一个方向特征

 

        但现在我希望以光向量的终点为起点进行照射,那我就把当前gl传入的顶点分向量,减去光向量(即绿色的新向量),以绿色向量根部为原点作二维坐标系,可以很容易发现,绿色向量的方向特征向量与顶点向量的方向特征向量点积后,只有绿色向量根部右侧的向量能得到>0的值,从而顺利筛选了发光与不发光的部分。此时光线向量除了提供方向外,它的终点提供了光线起点的作用。

        所有向量以及分向量都做一次上述操作后,即可实现任意起点、任意方向的光线效果了。

        

 

        另外,在真实世界中,光的强度与距离成反比,因此可以再乘以一个与距离 成反比的式子实现更真实的光照:

        (10.0 / distance(光顶点, 物体顶点)) * 5.0

最终效果演示如下:

        以原点到光斑方向设定为光线方向,可以看到看到盒子内部只有被照射到的 地方有颜色,而且有正常的渐变。

 

 

 

3、关键代码介绍:

        3.1、 盒子的顶点shader。因为我的demo里只用了一个面,通过变换矩阵旋转多次形成一个盒子,因此在做向量计算时不能直接用传入的顶点,而是变换后的最终结果的顶点,结果保存在objPos中。而为了保持场景的一致性,光线向量也要跟随场景缩放变换,结果保存在lightPos中

#version 300 es
uniform mat4 uMVPMatrix; //旋转平移缩放 总变换矩阵。物体矩阵乘以它即可产生变换
uniform mat4 uMVPMatrixWithoutRotate; //总变换矩阵,但没有旋转变换,契合光斑不参与房间旋转的特性
uniform mat4 objMatrix;
in vec3 objectPosition; //物体位置向量,参与运算但不输出给片源
uniform vec3 lightDotPos;
in vec4 objectColor; //物理颜色向量
in vec2 vTexCoord; //纹理内坐标
out vec4 fragObjectColor;//输出处理后的颜色值给片元程序
out vec2 fragVTexCoord;//输出处理后的纹理内坐标给片元程序
out vec3 objPos;
out vec3 lightPos;

void main() {
    vec4 pos = uMVPMatrix * vec4(objectPosition, 1.0);
    gl_Position = pos; //设置物体位置
    fragVTexCoord = vTexCoord; //默认无任何处理,直接输出物理内采样坐标
    fragObjectColor = objectColor; //默认无任何处理,输出颜色值到片源
    objPos = (uMVPMatrix * vec4(objectPosition, 1.0)).xyz; //给片元传一下实际转换后实际的坐标
    lightPos = (uMVPMatrixWithoutRotate * vec4(lightDotPos, 1.0)).xyz;
}

        3.2、盒子的片元shader:通过之前描述的逻辑,把片元颜色的浓度按照顶点的关系进行处理

#version 300 es
precision highp float;
in vec2 fragVTexCoord;//接收vertShader处理后的纹理内坐标给片元程序
in vec4 fragObjectColor;
in vec3 objPos;
in vec3 lightPos;
out vec4 fragColor;//输出到的片元颜色
uniform int funcChoice; //光照方式选择

void main() {
    vec4 color;
    vec3 lightVec = lightPos;
    //通过dot product得到光线向量和顶点向量之间的相似性,再把相似性系数作为颜色深度系数
    switch (funcChoice) {
        default:
        case 0: //使用dot product方法
            //怎样才可以表达一条不是由原点出发的光线?
            /*nice1 假设顶点向量包含了光源向量,那么顶点向量 - 光源向量 = 光源为源头到顶点的向量,也就是去除了原点到光源的距离,
            其具体含义就是把光线设定为原点到光圈的方向,通过点乘计算当前顶点与前述向量的相关性,以这个相关性作为颜色的浓淡系数*/
            color = vec4(fragObjectColor.rgb * dot(normalize(lightVec), normalize(objPos - lightVec)), fragObjectColor.a);
            color = color * (10.0 / distance(lightVec, objPos)) * 5.0; //叠加一下与光强度与光源距离成反比的关系式
            break;
        case 1: //使用距离光:
            color = vec4(fragObjectColor.rgb * (1.0 / distance(lightPos, objPos)) * 5.0, fragObjectColor.a);
            break;
    }
    fragColor = color;
}

        3.3、光斑的顶点shader:

#version 300 es

uniform mat4 uMVPMatrix; //旋转平移缩放 总变换矩阵。物体矩阵乘以它即可产生变换
uniform mat4 objMatrix;
in vec3 objectPosition; //物体位置向量,参与运算但不输出给片源
in vec2 vTexCoord; //纹理内坐标
out vec2 fragVTexCoord;//输出处理后的纹理内坐标给片元程序
out vec3 objPos;

void main() {
    vec4 pos = uMVPMatrix * vec4(objectPosition, 1.0);
    gl_Position = pos; //设置物体位置
    fragVTexCoord = vTexCoord; //默认无任何处理,直接输出物理内采样坐标
    objPos = pos.xyz;
}

        3.4、光斑的片元shader,那种球形电灯的效果是利用纹理采样坐标距离中心点成非线性的反比实现,并通过光线向量本身的方向特征,偏移中心点实现形变:


   intensity 扩散程度**/
vec4 getSpotLightOne(vec2 uv, vec2 center, float intensity, vec3 color) {
    //使用公式 1 / sqrt(当前遍历到的x,y 到 目标x,y 的距离),实现二维平面范围内距离中心点越远颜色越非线性变淡的效果
    float dist = intensity / sqrt(distance(uv, center));
    float alpha = 1.0;
    if (dist > 0.3) {
        alpha = dist;
    } else {
        alpha = 0.0;
    }
    return vec4(color * dist, alpha);
}

void fireflyEffect(out vec4 fragColor, in vec2 fragCoord) {
    //以面的中心点位置发光 光斑纹理原点在左上角,因此最大点为(1,1),所以中i的那为(1/2, 1/2),再减去光线本身的方向性即可向发光方向拉伸光斑
    fragColor = getSpotLightOne(fragCoord, vec2(0.5, 0.5) - normalize(objPos).xy * 0.5, 0.2, vec3(1.0, 1.0, 1.0));
}

void main() {
    fireflyEffect(fragColor, fragVTexCoord);
}

最后效果可以看我的视频:

        【OpenGL ES使用向量点积实现有方向的光照效果】 https://www.bilibili.com/video/BV1yT411n7BY/?share_source=copy_web&vd_source=c870102a8d6b63ae5be697515b19d95f

具体代码位置:

learnopengl/app/src/main/java/com/cjztest/glMyLightModel at main · cjzjolly/learnopengl · GitHub

learnopengl/app/src/main/assets/cjztest/lightmodel at main · cjzjolly/learnopengl (github.com)

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

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

相关文章

可能是史上最详细的MySQL用户和权限原理和实战

前言 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系…

Python - Jupyter - 远程连接Jupyter内核

Python - Jupyter - 远程连接Jupyter内核 前言 假设你有一台高性能服务器(电脑B),并且在上面安装好了Jupyter 现在你想使用你自己常用的电脑(电脑A)编码,但使用电脑B的计算资源。 怎么办呢?…

WPS以普通会员价格升级超级会员

文章目录 一、新会员体系二、基本原理三、升级超级会员1、购买会员时长2、成功通知3、兑换时长 一、新会员体系 4月17日,WPS会员体系全新升级。本次升级,WPS将原“WPS会员”、“稻壳会员”及“超级会员”合并、升级,推出全新的“WPS超级会员…

235:vue+openlayers 绘制带有径向渐变填充色的圆形

第235个 点击查看专栏目录 本示例的目的是介绍如何在vue+openlayer中绘制带有径向渐变填充色的圆形。 如果填充线性渐变的多边形,可以参考这个篇文章 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共136行)相关A…

通过python代码自定义ssh密码爆破

通过python代码自定义ssh密码爆破 一,这段代码的意义:二,直接上写好的代码:三,使用pip3 install paramiko 命令安装库四,使用 python3 test.py 主机地址 -u 用户名 -p 字典路径/五,字典的选取 一&#xff0…

RT-DETR的学习笔记

1. RT-DETR GitHub: PaddleDetection/tree/develop/configs/rtdetr 2. 复现训练流程 2.1 原文使用设备 2.2 环境要求 4*v100 cuda 10.2 paddlepaddle-gpu > 2.4.1 2.3 创建conda环境 conda create --name ppdet python3.102.4 安装RT-DETR推荐的paddle版本 前往官网…

4.26和4.27、selectAPI介绍(4.27、select代码)

4.26和4.27、selectAPI介绍(4.27、select代码) 1.selectAPI介绍①select多路复用流程图②select多路复用缺点 2.select代码使用介绍3.select代码实现①select服务端实现②select客户端实现 1.selectAPI介绍 主旨思想: 首先要构造一个关于文件…

FreeRTOS 其他任务 API 函数

文章目录 一、任务相关 API 函数预览二、任务相关 API 函数详解1. 函数 uxTaskPriorityGet()2. 函数 vTaskPrioritySet()3. uxTaskGetSystemState()4. 函数 vTaskGetInfo()5. 函数 xTaskGetApplicationTaskTag()6. 函数 xTaskGetCurrentTaskHandle()7. 函数 xTaskGetHandle()8.…

(十二)rk3568 NPU 中部署自己训练的模型,(1)使用yolov5训练自己的数据集-模型训练部分

一、rknn的demo中已经给了yolov5的后处理demo。但是这个后处理只适合yolov5特定版本(v5.0),还有下载特定的分支,如下为下载位置:。 下载地址 ONNX > CoreML > TFLite">GitHub - ultralytics/yolov5: YOLOv5 🚀 in PyTorch > ONNX > CoreML > T…

LeetCode 27.移除元素

文章目录 💡题目分析💡解题思路🚩思路1:暴力求解 --- 遍历🔔接口源码:🚩思路2:空间换时间🔔接口源码:🚩思路3:双指针(快慢指针)🔔接口…

Linux系统之部署Samba服务

Linux系统之部署Samba服务 一、Samba服务介绍1.Samba服务简介2.NFS和CIFS简介3.Smaba服务相关包4.samba监听端口4.samba相关工具及命令 二、环境规划介绍1.环境规划2.本次实践介绍 三、Samba服务端配置1.检查yum仓库2.安装smaba相关软件包3.创建共享目录4.设置共享目录权限5.新…

Adobe认证证书

Adobe认证证书是Adobe公司颁发的一种专业认证证书,用于证明持有人在相关Adobe软件的使用和应用方面具有专业水平。该证书是业内公认的专业认证,具有较高的价值和认可度,可以帮助持有人提高职业竞争力和工作效率。 Adobe公司提供了多种认证考…

CentOS7(三)MySQL8 Redis7 (单机)安装

文章目录 一、MySQL安装1、确认是否有老版本2、在线安装3、本地安装 二、启动MySQL三、MySQL常用配置1、密码修改2、配置远程登录3、开启防火墙 3306 端口4、 报错5、MySQL中Java写入时间少14小时 四、Redis 安装1、安装Redis依赖2、启动redis3、指定配置启动 & 后台运行4、…

手机端H5地图调起开发实战案例解析(百度高德腾讯地图调起、底部弹出层、提示安装地图导航APP)

文章目录 1.导航菜单配置构建导航菜单容器设置取消事件调起菜单样式表 2.地图调起事件导航到这里获取导航坐标百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换 3.地图调起效果 地图调起功能,是地图URI API是为开发者提供直接调起地图产品(手机客户端&a…

nextjs13临时笔记

动态路由 文件夹以中括号命名[id] -pages: --list: ---[id]: ----index.jsx(访问路径/list/1 即这种形式/list/:id) ---index.jsx(访问路径/list)[...params]gpt接口分析 初始化项目 npm install next react react-dom # or yarn add next react react-dom # or pnpm add n…

WPF教程(六)--依赖属性(2)--属性值优先级与继承

一、 依赖属性的优先级 由于WPF 允许我们可以在多个地方设置依赖属性的值,所以我们就必须要用一个标准来保证值的优先级别。比如下面的例子中,我们在三个地方设置了按钮的背景颜色,那么哪一个设置才会是最终的结果呢?是Black、Re…

[oeasy]python0136_接收输入_input函数_字符串_str

输入变量 回忆上次内容 上次研究了 一行赋值多个变量 a b 5a, b 7, 8 还研究了 标识符的惯用法 python使用的是 snake_case蛇形命名法用下划线 分隔开小写字母的 方法这样就可以 更合理地 命名变量了 变量变量 能变的量我可以 手工输入变量的值 吗?&#x1f9…

MongoDB 数据库数据导入 - 关于如何使用 csv 导入数据的命令方法、图形界面可视化导入方法

序言 兴趣使然,突发奇想,想到了就写,就当打发时间了。 一、使用 csv 导入数据的命令方法 csv文件路径问题,绝对路径和相对路径都可以 方法1 type 没有号,也是可以的,空格自动识别 将测试表.csv 文件导…

pikachu靶场-csrf

csrf 跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方…

【C++ 六】内存分区、引用

内存分区、引用 文章目录 内存分区、引用前言1 内存分区模型1.1 程序运行前1.2 程序运行后1.3 new 操作符 2 引用2.1 引用基本使用2.2 引用注意事项2.3 引用做函数参数2.4 引用做函数返回值2.5 引用本质2.6 常量引用 总结 前言 本文包含内存分区、引用基本使用、引用注意事项、…