光照贴图教程

2023-10-30

http://www.alsprogrammingresource.com/lightmapping_tutorial.html

光照贴图教程

 

作者:Alan Baylis 19/12/2001

 

在大多数游戏中,光照贴图仍然是首选的照明方法,也就是因为无论场景中有多少灯光,它都是快速的,如果你曾经拍过一个没有破裂或者没有破坏的灯泡而是周围的灯光然后,你已经看到了行动(或缺乏)的光照贴图。它们不适合动态照明,但可以应用光照贴图主题的轻微变化来产生假动态光,例如开/关或闪烁的灯光(通常使用多个光照贴图)。

 

光照贴图基本上只是包含亮度信息而不是图像的纹理。光照贴图的元素称为亮度,因为它们代表光度的元素。生成光照贴图后,应用于多边形时,要点亮的纹理和光照贴图会混合在一起,以产生最终效果。可以在运行之前预先计算混合以加速程序,尽管现在的趋势是使用硬件多纹理。

 

光照贴图对于任何游戏引擎来说几乎都是必不可少的,但我发现这个主题缺乏教程或明确的来源,所以提供本教程和演示来填补一些空白。这是我创建光照贴图的第一次尝试,因此可能存在错误的来源或其他方式。

 

在开始之前,请注意示例代码与OpenGL API兼容,并且只处理三角形(我仍然将它们称为多边形。)

 

第一步是计算光照贴图的UV坐标。在某些情况下,您可以使用纹理的UV坐标,但这并不适用于所有情况,因此为此,我们使用称为平面映射的过程。简而言之,这意味着我们将多边形的坐标投影到主轴平面上,然后将新坐标转换为2D纹理空间,为我们留下0到1之间的正交UV坐标。

 

要将多边形的坐标投影到主轴平面上,我们必须首先确定要使用的三个平面中的哪一个。这是通过检查多边形法线的哪个分量最大(使用绝对值),如果x分量最大,我们映射到YZ平面,如果y分量最大,我们映射到XZ平面,或者如果z分量是我们映射到XY平面上的最大值。现在我们知道要映射到哪个平面,我们通过使用每个顶点的两个相关组件并丢弃第三个,将多边形的坐标投影到平面上。换句话说,如果我们映射到YZ平面,我们使用每个多边形顶点的y和z分量并删除x分量。

 

在此示例代码中,假设我们为具有三个顶点的单个多边形制作光照贴图,并且具有每个顶点具有UV坐标的光照贴图结构。我还设置了一个标志,指示我们要映射到哪个平面,稍后将使用该平面。

  
        poly_normal = polygon.GetNormal();

        poly_normal.Normalize();



        if(fabs(poly_normal.x)> fabs(poly_normal.y)&& 

            fabs(poly_normal.x)> fabs(poly_normal.z))

        {

            flag = 1;

            lightmap.Vertex [0] .u = polygon.Vertex [0] .y;

            lightmap.Vertex [0] .v = polygon.Vertex [0] .z;

            lightmap.Vertex [1] .u = polygon.Vertex [1] .y;

            lightmap.Vertex [1] .v = polygon.Vertex [1] .z;

            lightmap.Vertex [2] .u = polygon.Vertex [2] .y;

            lightmap.Vertex [2] .v = polygon.Vertex [2] .z;

        }

        否则if(fabs(poly_normal.y)> fabs(poly_normal.x)&& 

                 fabs(poly_normal.y)> fabs(poly_normal.z))

        {

            flag = 2;

            lightmap.Vertex [0] .u = polygon.Vertex [0] .x;

            lightmap.Vertex [0] .v = polygon.Vertex [0] .z;

            lightmap.Vertex [1] .u = polygon.Vertex [1] .x;

            lightmap.Vertex [1] .v = polygon.Vertex [1] .z;

            lightmap.Vertex [2] .u = polygon.Vertex [2] .x;

            lightmap.Vertex [2] .v = polygon.Vertex [2] .z;

        }

        其他

        {

            flag = 3;

            lightmap.Vertex [0] .u = polygon.Vertex [0] .x;

            lightmap.Vertex [0] .v = polygon.Vertex [0] .y;

            lightmap.Vertex [1] .u = polygon.Vertex [1] .x;

            lightmap.Vertex [1] .v = polygon.Vertex [1] .y;

            lightmap.Vertex [2] .u = polygon.Vertex [2] .x;

            lightmap.Vertex [2] .v = polygon.Vertex [2] .y;

        }

然后,我们将这些光照贴图UV坐标转换为2D纹理空间。要做到这一点,我们必须首先找到这些坐标的边界框(通过使用

最小值和最大值)并找出这些最小值和最大值之间的差值(delta)。完成此操作后,我们可以通过从UV坐标中减去最小UV值然后除以delta值来缩放相对于原点的所有光照贴图的UV坐标。


        Min_U = lightmap.Vertex [0] .u;

        Min_V = lightmap.Vertex [0] .v;

        Max_U = lightmap.Vertex [0] .u;

        Max_V = lightmap.Vertex [0] .v;



        for(int i = 0; i <3; i ++)

        {

            if(lightmap.Vertex [i] .u <Min_U)

                Min_U = lightmap.Vertex [i] .u;

            if(lightmap.Vertex [i] .v <Min_V)

                Min_V = lightmap.Vertex [i] .v;

            if(lightmap.Vertex [i] .u> Max_U)

                Max_U = lightmap.Vertex [i] .u;

        if(lightmap.Vertex [i] .v> Max_V)

                Max_V = lightmap.Vertex [i] .v;

        }



        Delta_U = Max_U  -  Min_U;

        Delta_V = Max_V  -  Min_V;



        for(int i = 0; i <3; i ++)

        {

            lightmap.Vertex [i] .u  -  = Min_U;

            lightmap.Vertex [i] .v  -  = Min_V;

            lightmap.Vertex [i] .u / = Delta_U;

            lightmap.Vertex [i] .v / = Delta_V;

        }

所以现在我们可以使用光照贴图UV坐标来计算lumels。我们需要使用两个边进行插值,我们可以使用我们计算的最小和最大UV坐标来制作这些边,但是使用平面方程Ax + By + Cz + D = 0将它们投影回多边形平面。

    

距离=  - (poly_normal.x * pointonplane.x + poly_normal.y

                      * pointonplane.y + poly_normal.z * pointonplane.z);



    开关(旗帜)

        {

            案例1:// YZ飞机

                X =  - (poly_normal.y * Min_U + poly_normal.z * Min_V + Distance)

                        / poly_normal.x;

                UVVector.x = X;

                UVVector.y = Min_U;

                UVVector.z = Min_V;

                X =  - (poly_normal.y * Max_U + poly_normal.z * Min_V + Distance)

                        / poly_normal.x;

                Vect1.x = X;

                Vect1.y = Max_U;

                Vect1.z = Min_V;

                X =  - (poly_normal.y * Min_U + poly_normal.z * Max_V + Distance)

                        / poly_normal.x;

                Vect2.x = X;

                Vect2.y = Min_U;

                Vect2.z = Max_V;

            打破;



            案例2:// XZ Plane

                Y =  - (poly_normal.x * Min_U + poly_normal.z * Min_V + Distance)

                        / poly_normal.y;

                UVVector.x = Min_U;

                UVVector.y = Y;

                UVVector.z = Min_V;

                Y =  - (poly_normal.x * Max_U + poly_normal.z * Min_V + Distance)

                        / poly_normal.y;

                Vect1.x = Max_U;

                Vect1.y = Y;

                Vect1.z = Min_V;

                Y =  - (poly_normal.x * Min_U + poly_normal.z * Max_V + Distance)

                        / poly_normal.y;

                Vect2.x = Min_U;

                Vect2.y = Y;

                Vect2.z = Max_V;

            打破;



            情况3:// XY平面

                Z =  - (poly_normal.x * Min_U + poly_normal.y * Min_V + Distance)

                        / poly_normal.z;

                UVVector.x = Min_U;

                UVVector.y = Min_V;

                UVVector.z = Z;

                Z =  - (poly_normal.x * Max_U + poly_normal.y * Min_V + Distance)

                        / poly_normal.z;

                Vect1.x = Max_U;

                Vect1.y = Min_V;

                Vect1.z = Z;

                Z =  - (poly_normal.x * Min_U + poly_normal.y * Max_V + Distance)

                        / poly_normal.z;

                Vect2.x = Min_U;

                Vect2.y = Max_V;

                Vect2.z = Z;

            打破;

        }



        edge1.x = Vect1.x  -  UVVector.x;

        edge1.y = Vect1.y  -  UVVector.y;

        edge1.z = Vect1.z  -  UVVector.z;

        edge2.x = Vect2.x  -  UVVector.x;

        edge2.y = Vect2.y  -  UVVector.y;

        edge2.z = Vect2.z  -  UVVector.z;

现在我们有两个边矢量,我们可以通过使用光照贴图的宽度和高度沿这些边插值来找到世界空间中的lumel位置。我用来计算每个lumel颜色的方法是通过反复试验找到的,但基本上使用Lambert公式来缩放RGB强度,我很快就能找到关于这个主题的一些信息,它会得到改进。我还检查了灯光所在的多边形的哪一侧,以便只有前面的多边形被点亮。

        
  
  for(int iX = 0; iX <Width; iX ++)

        {

            for(int iY = 0; iY <Height; iY ++)

            {

                ufactor =(iX /(GLfloat)Width);

                vfactor =(iY /(GLfloat)高度);

                newedge1.x = edge1.x * ufactor;

                newedge1.y = edge1.y * ufactor;

                newedge1.z = edge1.z * ufactor;

                newedge2.x = edge2.x * vfactor;

                newedge2.y = edge2.y * vfactor;

                newedge2.z = edge2.z * vfactor;



                lumels [iX] [iY] .x = UVVector.x + newedge2.x + newedge1.x;

                lumels [iX] [iY] .y = UVVector.y + newedge2.y + newedge1.y;

                lumels [iX] [iY] .z = UVVector.z + newedge2.z + newedge1.z;



                combinedred = 0.0;

                combinedgreen = 0.0;

                combinedblue = 0.0;

                for(int i = 0; i <numStaticLights; i ++)

                {

                    if(ClassifyPoint(staticlight [i] .Position,

                        pointonplane,poly_normal)== 1)

                    {

                      lightvector.x = staticlight [i] .Position.x  -  lumels [iX] [iY] .x;

                      lightvector.y = staticlight [i] .Position.y  -  lumels [iX] [iY] .y;

                      lightvector.z = staticlight [i] .Position.z  -  lumels [iX] [iY] .z;

                      lightdistance = lightvector.GetMagnitude();

                      lightvector.Normalize();

                        cosAngle = DotProduct(poly_normal,lightvector);

                        if(lightdistance <staticlight [i] .Radius)

                        {

                            intensity =(staticlight [i] .Brightness * cosAngle)

                                          / lightdistance;

                            combinedred + = staticlight [i] .Red * intensity;

                            combinedgreen + = staticlight [i] .Green * intensity;

                            combinedblue + = staticlight [i] .Blue * intensity;

                        }

                    }

                }

                if(combinedred> 255.0)

                    combinedred = 255.0;

                if(combinedgreen> 255.0)

                    combinedgreen = 255.0;

                if(combinedblue> 255.0)

                    combinedblue = 255.0;

                lumelcolor [iX] [iY] [0] = combinedred;

                lumelcolor [iX] [iY] [1] = combinedgreen;

                lumelcolor [iX] [iY] [2] = combinedblue;

            }

        }

下一部分采用lumelcolor数组并将其放入RGBA格式,适合保存为首选图像类型。


        for(int iX = 0; iX <(Width * 4); iX + = 4)

        {

            for(int iY = 0; iY <Height; iY + = 1)

            {

              lightmap [iX + iY * Height * 4] =(char)lumelcolor [iX / 4] [iY] [0];

              光照贴图[iX + iY *高度* 4 + 1] =(字符)lumelcolor [iX / 4] [iY] [1];

              光照贴图[iX + iY *高度* 4 + 2] =(字符)lumelcolor [iX / 4] [iY] [2];

              光照贴图[iX + iY *高度* 4 + 3] = 255;                            

            }

        }

最后,需要注意的一些事项是:

 

静态灯包含红色,绿色和蓝色值,范围为0-255以及亮度和半径值。平衡亮度和半径是一项手动任务,可以产生不同的效果; 根据您想要的光效实验这些。

 

与直觉相反,较小的光照贴图可以产生更好的效果。如果光照贴​​图太大,您将得到非常明显的光环而不是光滑的混合。16x16大小的光照贴图似乎确实是最佳尺寸

 

当两个不同大小的多边形从一个边缘到另一个边缘放置,并且两个光线都照射在它们上面时,边缘的任何一侧的光值都不符合我们的要求。我很确定这是因为光照贴图对每个多边形的缩放比例不同,当光照贴图较大并且有更多的采样点时它不是问题(但是我们遇到上述光环问题)。要使触摸的多边形具有相同的边长,但这将是乏味的。我仍在努力解决这个问题,并希望对此问题有任何想法

 

参考文献:

 

托比亚斯约翰逊

 

Tomasz Zaniewski

 

库尔特米勒

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

光照贴图教程 的相关文章

  • 模拟客户端和服务端

    import org junit jupiter api Test import java io import java net InetAddress import java net ServerSocket import java ne
  • spirng-Alibaba的介绍和导入

    3 spirng Alibaba 3 1spirng Alibaba概述和导入依赖 外链图片转存失败 源站可能有防盗链机制 建议将图片保存下来直接上传 img 4hqZgDfQ 1637066858419 https static01 im
  • 使用Python、OpenCV,ImageMagick工具箱根据原始视频制作GIF动画

    使用Python OpenCV ImageMagick工具箱根据原始视频制作GIF动画 python读取原始视频流每一帧 并生成照片保存到本地文件夹 读取本地文件夹图片 根据图片名排序 支持根据帧率过滤 指定最大多少帧照片去生成gif 调用
  • 剑指 Offer 62. 圆圈中最后剩下的数字 <约瑟夫环>

    看了诸多大神的解题还是有点不明白 故记录一下 如题 0 1 n 1这n个数字排成一个圆圈 从数字0开始 每次从这个圆圈里删除第m个数字 删除后从下一个数字开始计数 求出这个圆圈里剩下的最后一个数字 方法一 递归 数学 递归 class So
  • SpringBoot(13)过滤器+拦截器+监听器

    文章目录 一 过滤器 1 过滤器介绍 2 Filter生命周期 4 注解方式实现过滤器 WebFilter WebFilter Order 1 ServletComponentScan 启动类代码 Filter代码 5 直接注入到sprin
  • shell巡检脚本

    巡检主机系统版本 centos7 编程需求 日常巡检是每一个运维必须要做的事情 如果能用脚本实现的话 那将大大简化巡检的工作难度 root localhost shell vim sys check sh bin bash 第一部分 系统信
  • 第3篇:JVM中内存分配及回收策略

    文章目录 一 分配原理 二 原理图 小结 一 分配原理 当执行创建对象操作时 首先进行逃逸分析 其实就是该对象是否会被外部方法所引用 就是作用域 若不会则进行标量替换 就是对象中成员变量是基本数据类型的在栈帧 寄存器中进行创建 可以分担堆内
  • vue 递归组件

    递归组件 递归组件就是在模板中引用自身的组件 我们有时希望在一个组件内部渲染该组件本身 例如渲染树形结构时 需要在树根渲染子树 而子树与树根的结构是一样的 因此存在递归 一个简单的递归组件的例子如Tree vue Tree vue
  • 计算机温度压力测试,鲁大师温度压力测试怎么看结果 鲁大师的温度压力测试教程...

    很多朋友都在网上问鲁大师的温度压力测试怎么看结果 结果到底在哪里 其实小编也想知道 下面就是小编收集来的各路大神的说法 大家可以看看对比一下 说法一 鲁大师的温度压力测试不需要你开任何应用程序的 你只需要点击鲁大师 温度检测 里的 进行温度
  • Python3爬虫学习——urlib库笔记

    Python3爬虫学习 urllib库 前言 本笔记仅个人认知和见解 水平有限 还请见谅 内容大多来自Python文档和学习材料 作相应的扩充或压缩后的笔记 没有很多实例 大多是理论知识 文章目录 Python3爬虫学习 urllib库 前
  • wordpress付费阅读_免费和付费WordPress托管之间的7个区别

    wordpress付费阅读 If you ve been looking around for WordPress hosting you might have come across a number of companies offer
  • python lambda的用法

    欢迎转载 转载请注明原文地址 http blog csdn net majianfei1023 article details 45269343 lambda函数也叫匿名函数 函数没有具体的名称 先来看一个最简单例子 python view
  • IDC:云效产品能力No.1,领跑中国DevOps市场

    近日 全球领先的专业市场调查机构国际数据公司 IDC 发布了 IDC MarketScape 中国 DevOps 平台市场厂商评估 2022 报告 此报告中对中国主流 DevOps 云厂商从战略 Strategies 能力 Capabili
  • 详解rem布局-利用rem布局实现移动端高清显示

    目录 目录 一 初探rem布局 1 1 rem是什么 1 2 rem实现新闻字体 小中大 设置 二 利用rem布局实现移动端高清显示 一 初探rem布局 1 1 rem是什么 rem是CSS3新增的一个相对单位 root em 根em 这个
  • Android图片加载神器之Fresco,基于各种使用场景的讲解

    Fresco是Facebook开源Android平台上一个强大的图片加载库 也是迄今为止Android平台上最强大的图片加载库 优点 相对于其他开源的第三方图片加载库 Fresco拥有更好的内存管理和强大的功能 基本上能满足所有的日常使用场
  • TurboPower Async Professional 在Delphi2010及Delphi7中的安装

    这里我们介绍一下TurboPower Async Professional 串口控件的安装方法 Delphi 2010 1 下载http sourceforge net projects tpapro 2 解压 在Delphi2010下找到
  • python正则表达式爬取【豆瓣电影top250】(新手向)

    最近在学崔大的 网络爬虫开发与实战 学到正则表达式那块儿 便迎来了自己的第一个实战项目 话不多说 正式进入正文 本次爬虫工具使用的是pycharm 已经提前安装好了所有包 安装方式 file Settings Project Interpr

随机推荐

  • 双亲委派机制及其部分源码分析

    双亲委派机制 双亲委派机制 我理解的 双亲委派机制 简单来讲就是 类加载器加载类的时候是 自顶向下 的过程来加载 详情如下 在某个加载器进行类加载是 会逐级向上找到他最终的父类 BootstrapClassLoader 先进行加载 逐级向下
  • List元素移除-迭代器删除

    Exception in thread main java util ConcurrentModificationException异常解决方案 使用迭代器方式删除List元素内容 当直接用List的remove移除元素时 会报如上异常 比
  • Hinton关于RBM的代码注解之(三)mnistclassify.m

    mnistclssify m clear all close all maxepoch 50 最大迭代次数 numhid 500 numpen 500 numpen2 2000 对应的1 2 3层隐含层单元的个数 fprintf 1 Con
  • C++ 的封装、继承、多态

    面向对象的三个基本特征 面向对象的三个基本特征是 封装 继承 多态 封装可以隐藏实现细节 使得代码模块化 继承可以扩展已存在的代码模块 类 多态则是为了实现另一个目的 接口重用 它们的目的都是为了 代码重用 封装 目的 隐藏实现细节 使得代
  • Python 控制 Raspberry Pi 云台多舵机

    多舵机控制 使用 Python 和云台机制构造进行 Raspberry Pi 相机定位 所需材料 在本教程中 我们将探索如何在 Raspberry Pi 上使用 Python 控制多个舵机 我们的目标是使用云台机制来定位相机 PiCam 如
  • 【测试开发】基于 MeterSphere 的接口测试流程

    基于 MeterSphere 的接口测试流程 MeterSphere 接口测试模块提供了 接口定义 接口自动化 等接口测试相关功能 用户可以使用树状多级模块来分级分组管理项目下的接口列表 创建执行接口用例测试接口 组合编排多个接口用例进行场
  • 电子产品推荐系统的设计与实现

    其他项目 点击作者主页 目录 1 系统简介 2 系统相关技术 2 1 JSP技术 2 2 B S架构 2 3 MySQL数据库技术 2 4 SSM 3 需求分析 3 1 系统功能需求分析 3 2 系统非功能需求分析 4 系统设计 4 1 系
  • websocket协议

    WebSocket是一种在Web应用程序中实现实时双向通信的协议 一种在单个TCP连接上进行全双工通信的协议 它使得客户端和服务器之间的数据交换变得更加简单 允许服务端主动向客户端推送数据 WebSocket 与 HTTP 2 一样 其实都
  • Java如何对一个对象进行深拷贝?

    深拷贝实现代码 https github com wudashan java deep copy 介绍 在Java语言里 当我们需要拷贝一个对象时 有两种类型的拷贝 浅拷贝与深拷贝 浅拷贝只是拷贝了源对象的地址 所以源对象的值发生变化时 拷
  • 【Transformers】第 1 章 :Hello Transformers

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • QWidget: Must construct a QApplication before a QWidget 请按任意键继续. . .

    系列文章目录 文章目录 系列文章目录 前言 一 错误原因 前言 一 错误原因 在调试examples工程时 遇到编译报错 QWidget Must construct a QApplication before a QWidget 根据字面
  • 打开软件或游戏出现找不到d3dcompiler_43.dll文件如何解决?

    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题 如果是新手第一时间会认为是软件或游戏出错了 其实并不是这样 其主要原因就是你电脑系统的该dll文件丢失了或者损坏了 这时你只需下载这个d3dcompiler 43 dll文件进行安装
  • SpringBoot的日志配置 logging.file.path和logging.file.name不能同时生效

    这里写自定义目录标题 logging file path和logging file name logging file path和logging file name logging file path data logs lra sdk l
  • day20 网络编程(上)

    day20 网络编程 上 课程目标 掌握网络相关的基础知识并可以基于Python开发程序 基于网络进行数据传输 课程概要 网络必备基础 网络编程 Python代码 B S和C S架构 1 必备基础 你必须了解的网络相关设备和基础概念 1 1
  • Caffe学习之自定义创建新的Layer层

    caffe源码中已经帮我封装好了各种各样的layer 但是有时候现有的layer不能满足设计的网络要求 这个时候需要自己定义一个新的layer 本文参考here 进行简单讲解 具体方式如下 一 创建 hpp文件 1 添加你的layer头文件
  • 利用PIFU-HD生成自己的三维人体图像

    这个小项目本身是在研一的时候调的 记录一下过程 以后在别的电脑上配置就容易点 我是在windows系统下配置的 gpu贼拉跨 报错很多 欢迎借鉴 总有一款错误适合你 然后友情提示 跑项目就是会遇到各种各样的错误 请需要的小伙伴耐心看 遇到问
  • 简析区块链的特点和每个分层的作用

    区块链技术在这几年不停的发展更新 相应的区块链服务应用和技术也逐渐走向成熟 区块链技术相对于行业内的内来说就相对熟悉一些 但对于那些还未接触这些技术知识的人来说就相当陌生 区块链技术有什么特点 区块链底层系统有哪些分层 对应的作用分别是什么
  • 设备树学习之(二)点灯

    开发板 tiny4412SDK S702 4GB Flash 要移植的内核版本 Linux 4 4 0 支持device tree u boot版本 友善之臂自带的 U Boot 2010 12 busybox版本 busybox 1 25
  • 获取当天,本周,本月,本季度,本半年,本年时间

    1 使用场景 elemet plus 组件库 日期选择器快捷使用选择本月 本季度时间 2 方法 const useDate gt const now new Date 当前日期 const nowYear now getFullYear 当
  • 光照贴图教程

    http www alsprogrammingresource com lightmapping tutorial html 光照贴图教程 作者 Alan Baylis 19 12 2001 在大多数游戏中 光照贴图仍然是首选的照明方法 也