[Manjaro] OpenGL 配合着色器实现光线跟踪之引入光线

2023-11-10

概述

本文介绍 GLFW + GLAD 在 RayTracing in one weekend 的实现。

实验环境:Manjaro Linux 22.0.0

整体思路:

使用基于屏幕空间的光线跟踪算法,每个像素点代表一个光线。使用 GLSL 着色器语言编写顶点着色器,获取顶点在屏幕空间的坐标,经过函数计算得到 RGB 值并传输到片段着色器中。

目录

概述

输出第一张图片

顶点着色器代码

片段着色器代码

向场景中发射光线

顶点着色器代码

片段着色器代码

画一个实心球

顶点着色器代码

简单的着色——基于表面法向量

顶点着色器


输出第一张图片

首先设置窗口尺寸

const unsigned int SCR_WIDTH = 400;
const unsigned int SCR_HEIGHT = 400;

设置参数

// 确定顶点大小
const int point_size = 1;

// 均匀等距采样
const int sample_x = SCR_WIDTH / point_size;
const int sample_y = SCR_HEIGHT / point_size;

// 确定顶点数组
const int num_of_points = sample_y * sample_x;
float vertices[num_of_points * 3];

因为 GPU 绘制顶点的时候是要通过输入的顶点数组来计算屏幕空间中的顶点,因此在 main 函数中通过 CPU 设置顶点三维坐标信息。

平常我们认为图像的空间坐标系中,坐标从 0 开始一直到边界,但是 OpenGL 中,屏幕空间是一个坐标取值为 [-1.0, 1.0] 的二维空间,因此需要进行坐标的转换。

int dy = SCR_HEIGHT / sample_y; // 顶点在虚拟屏幕空间的 y 轴步长
int dx = SCR_WIDTH / sample_x; // 顶点在虚拟屏幕空间 x 轴步长

int idx = 0;
printf("dy: %d dx: %d\n", dy, dx);

/* 遍历虚拟屏幕空间,将 [0, SCR_HEIGHT] 坐标变换到 [-1.0, 1.0] 的
 * 屏幕空间坐标并记录在 vertices 数组中 */
for (int y = 0; y < SCR_HEIGHT; y += dy) {
  for (int x = 0; x < SCR_WIDTH; x += dx) {
    float ny = (static_cast<float>(y) / SCR_HEIGHT - 0.5) * 2.0f;
    float nx = (static_cast<float>(x) / SCR_WIDTH - 0.5) * 2.0f;
    vertices[idx++] = nx;
    vertices[idx++] = ny;
    vertices[idx++] = 0.0f; // z 轴坐标,暂时设置为 0.0f
  }
}

在渲染主循环中,我们需要将顶点数据发送到 GPU,因此使用 glDrawArrays() 函数 ,参数设置为顶点个数。

glPointSize(point_size);
glDrawArrays(GL_POINTS, 0, num_of_points);

为了将顶点坐标映射成颜色值,还需要在着色器程序中进行一点小小的调整,将 [-1.0, 1.0] 的坐标轴变换到 [0.0, 1.0] 范围内。

vsOutColor.x = aPos.x / 2 + 0.5;
vsOutColor.y = aPos.y / 2 + 0.5;
vsOutColor.z = 0.25;

最终结果图如下

顶点着色器代码

// vertex shader
#version 460 core
layout (location = 0) in vec3 aPos;
out vec3 vsOutColor;
void main()
{
    vsOutColor.x = aPos.x / 2 + 0.5;
    vsOutColor.y = aPos.y / 2 + 0.5;
	vsOutColor.z = 0.25;
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
};

片段着色器代码

// fragment shader
#version 460 core
out vec4 FragColor;
in vec3 vsOutColor;
void main()
{
   FragColor = vec4(vsOutColor, 1.0f);
}

向场景中发射光线

没有光,就看不到任何东西。假设有一个能够从镜头中发射光线的仪器,称为“相机”(显然并不现实)在场景中有固定的坐标。“相机”向其“底片”上的每个像素逐个发射一条光线,“底片” 的位置恰好位于镜头的焦距处。经过各种光学作用,“底片”能够显示出最终的图像。

Vec3 类的代码可以参考 Ray Tracing in One Weekend 的原文或者使用 glm::vec3 等实现,由于 GLSL 的 vec3 使用的是 float 类型,且类型检查严格,因此务必确保数值类型的对应

首先需要布置相机的位置

// “底片”的尺寸
float viewport_height = 2.0;
float viewport_width = aspect_ratio * viewport_height;
float focal_length = 1.0; // 相机焦距

// “底片”的坐标
auto origin = Vec3(0, 0, 0);
auto horizontal = Vec3(viewport_width, 0, 0);
auto vertical = Vec3(0, viewport_height, 0);
auto lower_left_corner = origin - horizontal / 2 - vertical / 2 - Vec3(0, 0, focal_length);

为了计算像素值,这里在顶点着色器中增加一个函数

vec3 ray_color(vec3 direction) 
{
    // 计算单位向量
    vec3 unit_direction = vec3(direction / length(direction));
    
    // 归一化到 [0.0, 1.0]
    float t = 0.5 * (unit_direction.y + 1.0);
    vec3 blue = vec3(0.5, 0.7, 1.0);
    vec3 white = vec3(1.0, 1.0, 1.0);
    
    // 使用线性插值计算颜色值
    return mix(white, blue, t); 
}

将计算好的像素值命名为 RayColor 传输给片段着色器

in vec3 RayColor;
void main()
{
    FragColor = vec4(RayColor, 1.0f);
}

最终结果如下

顶点着色器代码

#version 460 core
layout (location = 0) in vec3 aPos;
uniform vec3 origin;
uniform vec3 lower_left_corner;
uniform vec3 horizontal;
uniform vec3 vertical;
out vec3 RayColor;
vec3 ray_color(vec3 direction)
{
   vec3 unit_direction = vec3(direction / length(direction));
   float t = 0.5 * (unit_direction.y + 1.0);
   vec3 blue = vec3(0.5, 0.7, 1.0);
   vec3 white = vec3(1.0, 1.0, 1.0);
   return mix(white, blue, t);
}
void main()
{
   float u = 0.5 * (aPos.x + 1.0);
   float v = 0.5 * (aPos.y + 1.0);
   vec3 dir = lower_left_corner + u * horizontal + v * vertical - origin;
   RayColor = ray_color(dir);
   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

片段着色器代码

#version 460 core
out vec4 FragColor;
in vec3 RayColor;
void main()
{
   FragColor = vec4(RayColor, 1.0f);
}

画一个实心球

我们都知道一个对于一个三维空间中的球,如果它的半径为 R ,圆心坐标为 C(x, y, z) ,则可以得到圆的标准方程如下

(x-C_x)^2+(y-C_y)^2+(z-C_z)^2 = R^2

对于光线跟踪算法,我们需要知道空间中有哪些点需要上色,它的颜色值是什么。这里假设空间中有一个点 P(x, y, z) ,如果它在圆上或者在圆内,则将其颜色值设定为红色。

在这里需要注意,我们的 ray_color() 函数使用的参数是 vec3 direction ,即点 P 的参数化形式表示,而不是直接使用直角坐标的形式。因此在方程求解时需要将原式换成关于参数坐标 t 的方程。

经过推导后得到的公式如下

b\cdot b\ t^2 + 2b(A-C) t + (A-C)\cdot (A-C) - R^2 = 0

其中

b = direction \ vector, (A-C) = (origin \ point) - (center \ point)

从此转换为求关于 t 的一元二次方程的根的问题。

  • 如果判别式大于0,则射线与圆存在两个交点;
  • 如果判别式等于零,则射线与圆有一个交点;
  • 如果判别式小于零,则射线与圆没有交点。

有了这些知识,我们就可以在顶点着色器中编写程序了

bool hit_sphere(vec3 center, float radius, vec3 direction)
{
   vec3 oc = origin - center;
   float a = dot(direction, direction);
   float b = 2.0 * dot(oc, direction);
   float c = dot(oc, oc) - radius * radius;
   float discriminant = b*b - 4*a*c;
   if (discriminant > 0)
      return true;
   else
      return false;
}

在 ray_color() 函数中添加下面的代码

vec3 circ_center = vec3(0.0,0.0,-1);
vec3 red = vec3(1.0, 0.0, 0.0);
if(hit_sphere(circ_center, 0.5, direction))
    return red;

渲染的结果如下

顶点着色器代码

#version 460 core
layout (location = 0) in vec3 aPos;
uniform vec3 origin;
uniform vec3 lower_left_corner;
uniform vec3 horizontal;
uniform vec3 vertical;
out vec3 RayColor;

bool hit_sphere(vec3 center, float radius, vec3 direction)
{
   vec3 oc = origin - center;
   float a = dot(direction, direction);
   float b = 2.0 * dot(oc, direction);
   float c = dot(oc, oc) - radius * radius;
   float discriminant = b*b - 4*a*c;
   if (discriminant > 0)
      return true;
   else
      return false;
}

vec3 ray_color(vec3 direction)
{
   vec3 circ_center = vec3(0.0,0.0,-1);
   vec3 red = vec3(1.0, 0.0, 0.0);
   if(hit_sphere(circ_center, 0.5, direction))
      return red;
   vec3 unit_direction = vec3(direction / length(direction));
   float t = 0.5 * (unit_direction.y + 1.0);
   vec3 blue = vec3(0.5, 0.7, 1.0);
   vec3 white = vec3(1.0, 1.0, 1.0);
   return mix(white, blue, t);
}

void main()
{
   float u = 0.5 * (aPos.x + 1.0);
   float v = 0.5 * (aPos.y + 1.0);
   vec3 dir = lower_left_corner + u * horizontal + v * vertical - 
origin;
   RayColor = ray_color(dir);
   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

片段着色器代码不变

简单的着色——基于表面法向量

现在我们已经能在三维空间中表示一个球了,但是一个单色的球显然并不能满足我们的好奇心。因此现在试着给小球上色,我们想让颜色有变化。

为了产生变化,可以利用小球表面的数学性质:小球的表面是一个曲面,上面每一个点的法向量都不相同。

由于 RGB 颜色空间的表示范围是有限的([0.0, 1.0] 或 [0, 255]),因此需要将计算出的法向量归一化成单位向量,然后再映射到颜色值上。

因此,ray_color() 函数修改如下

vec3 ray_color(vec3 origin, vec3 direction)
{
   vec3 ball_center = vec3(0.0,0.0,-1);
   float t = hit_sphere(ball_center, 0.5, direction);
   if(t > 0.0) {
     vec3 P = origin + t * direction;
	 vec3 N = P - ball_center;
     N = vec3(N / length(N));
	 return 0.5 * vec3(N + 1.0);
   }
   vec3 unit_direction = vec3(direction / length(direction));
   t = 0.5 * (unit_direction.y + 1.0);
   vec3 blue = vec3(0.5, 0.7, 1.0);
   vec3 white = vec3(1.0, 1.0, 1.0);
   return mix(white, blue, t);
}

hit_sphere() 函数现在需要计算出参数值 t,也就是一元二次方程的根。

float hit_sphere(vec3 center, float radius, vec3 direction)
{
   vec3 oc = origin - center;
   float a = dot(direction, direction);
   float b = 2.0 * dot(oc, direction);
   float c = dot(oc, oc) - radius * radius;
   float discriminant = b*b - 4*a*c;
   if (discriminant < 0)
      return -1.0;
   else
      return (-b - sqrt(discriminant)) / (2.0*a);
}

最终结果

顶点着色器

#version 460 core
layout (location = 0) in vec3 aPos;
uniform vec3 origin;
uniform vec3 lower_left_corner;
uniform vec3 horizontal;
uniform vec3 vertical;
out vec3 RayColor;

float hit_sphere(vec3 center, float radius, vec3 direction)
{
   vec3 oc = origin - center;
   float a = dot(direction, direction);
   float b = 2.0 * dot(oc, direction);
   float c = dot(oc, oc) - radius * radius;
   float discriminant = b*b - 4*a*c;
   if (discriminant < 0)
      return -1.0;
   else
      return (-b - sqrt(discriminant)) / (2.0*a);
}

vec3 ray_color(vec3 origin, vec3 direction)
{
   vec3 ball_center = vec3(0.0,0.0,-1);
   float t = hit_sphere(ball_center, 0.5, direction);
   if(t > 0.0) {
     vec3 P = origin + t * direction;
	 vec3 N = P - ball_center;
     N = vec3(N / length(N));
	 return 0.5 * vec3(N + 1.0);
   }
   vec3 unit_direction = vec3(direction / length(direction));
   t = 0.5 * (unit_direction.y + 1.0);
   vec3 blue = vec3(0.5, 0.7, 1.0);
   vec3 white = vec3(1.0, 1.0, 1.0);
   return mix(white, blue, t);
}

void main()
{
   float u = 0.5 * (aPos.x + 1.0);
   float v = 0.5 * (aPos.y + 1.0);
   vec3 dir = lower_left_corner + u * horizontal + v * vertical - 
origin;
   RayColor = ray_color(dir);
   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

至此,本文结束。

总结一下,本文使用 OpenGL 实现了屏幕空间的光线跟踪,并最终利用表面法向量绘制出一个带有颜色渐变的球。

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

[Manjaro] OpenGL 配合着色器实现光线跟踪之引入光线 的相关文章

  • 为 DocumentDb 设置自定义 json 转换器

    我正在使用类型化 DocumentQuery 从 Azure DocumentDb 集合中读取文档 from f in client CreateDocumentQuery
  • 错误:表达式不可赋值三元运算符

    我有以下代码 MPLABX XC8 编译器给出此错误 错误 表达式不可分配 U1ERRIRbits RXFOIF uart1 oerr 1 uart1 oerr 0 这是相关代码部分 typedef union struct bool fe
  • 与 MinGW 的静态和动态/共享链接

    我想从一个简单的链接用法开始来解释我的问题 假设有一个图书馆z它可以编译为共享库 libz dll D libs z shared libz dll 或静态库 libz a D libs z static libz a 让我想要链接它 然后
  • 非模板函数中的尾随返回类型[重复]

    这个问题在这里已经有答案了 我见过有人使用以下语法来实现函数 auto get next gt int 代替 int get next 我理解两者 并且我知道尾随返回类型语法对于使用 decltype 的模板代码很有用 就我个人而言 我会避
  • C++中类成员函数相互调用有什么好处?

    我是 C 新手 我发现下面的编程风格对我来说很有趣 我在这里写了一个简化版本 include
  • 从结构调用 C++ 成员函数指针

    我找到了有关调用 C 成员函数指针和调用结构中的指针的信息 但我需要调用结构内部存在的成员函数指针 但我无法获得正确的语法 我在类 MyClass 的方法中有以下代码片段 void MyClass run struct int MyClas
  • Visual Studio 2013 调试器显示 std::string 的奇怪值

    我有一个大型的 cmake 生成的解决方案 其中包含许多项目 由于某种原因 我无法查看字符串的内容 因为根据调试器 Bx Buf含有一些垃圾 text c str 正确返回 Hello 该问题不仅仅发生在本地字符串上 返回的函数std st
  • 加载 QPixmap 数据的更好方法

    更好的方法来做到这一点 没有QImage QImage image width height QImage Format RGB888 memcpy image bits m frameRGB gt data 0 height width
  • 公交车公共交通算法

    我正在开发一个可以查找公交路线的离线 C 应用程序 我可以提取时间表 巴士 路线数据 我正在寻找适用于基本数据的最简单的解决方案 可以使用什么算法来查找从巴士站 A 到巴士站 B 的路线 是否有适用于 C Java 的开源解决方案 数据库的
  • ASP.NET - Crystal Report Viewer 打印按钮在 ASP.NET 中不起作用

    我正在使用 Visual Studio 2008 但我遇到了水晶报告问题 当我单击打印按钮时 它会将我带到弹出窗口 但未找到页面 弹出的网址是 http localhost aspnet client System Web 2 0 5072
  • 如何从 Powerpoint 2010 导出电影?

    如何使用 MS Office PIA 主互操作程序集 或其他方式以编程方式将嵌入视频从 powerpoint 2010 导出到外部文件 在演示文稿中嵌入视频是 Powerpoint 2010 中的一项新功能 我找不到解决方案 PPTX 文件
  • MINIX内部碎片2

    我正在用 C 语言编写一些软件 它递归地列出给定目录中的所有文件 现在我需要计算出内部碎片 我花了很长时间研究这个问题 发现 ext2 上的内部碎片只发生在最后一个块中 我知道理论上你应该能够从索引节点号获得第一个和最后一个块地址 但我不知
  • 从单应性估计 R/T

    我一直在尝试计算 2 个图像中的特征 然后将这些特征传递回CameraParams R没有运气 特征已成功计算并匹配 但是问题是将它们传递回R t 我明白你必须分解Homography为了使这一点成为可能 我已经使用如下方法完成了 http
  • 具有多个父项的 Qt 树模型

    我想构建一棵树 其中一个元素可以引用另一个元素 我想要构建的树是 像这样的东西 A B C D E F P this is a pointer to C D first child of C E second child of C I fo
  • 使用未命名命名空间而不是静态命名空间

    我可以假设在未命名命名空间中声明的对象相当于static namespace int x 1 static int x 2 FWIK 在这两种情况下 x将具有静态存储期限和内部链接 声明为的对象的所有规则也是如此static适用于未命名名称
  • 让 Windows 尝试读取文件

    我正在对 Windows 文件系统进行某种封装 当用户请求打开文件时 Windows 调用我的驱动程序来提供数据 在正常操作中 驱动程序返回缓存的文件内容 但是 在某些情况下 实际文件没有缓存 我需要从网络下载它 问题是是否有可能让 Win
  • C# 多维数组解析

    我有一个多维数组 内容在调试器中看起来像这样 数组设置为 String s new String 6 4 A B Yes C A B Yes C A B No C A B Yes C A B Yes C A B Yes C A B No C
  • 使用空的weak_ptr作为参数调用map::count安全吗?

    打电话安全吗map count http www cplusplus com reference map map count on an 未初始化因此为空weak ptr http en cppreference com w cpp mem
  • NHibernate:无状态会话错误消息无法获取代理

    我正在使用 nHibernate 无状态会话来获取对象 更新一个属性并将对象保存回数据库 我不断收到错误消息 无状态会话无法获取代理 我在其他地方有类似的代码 所以我不明白为什么这不起作用 有谁知道问题可能是什么 我正在尝试更新Screen
  • 如何使用 Microsoft Graph API 更新 MailboxSettings

    我想从不同的日历更新邮箱设置 如何构建可以通过 Microsoft Graph 更新 MailboxSetting 的请求 这是我的代码示例 但有例外 代码示例 User obj GraphServiceClient Users roomC

随机推荐

  • dos bat批量创建软链接

    windows 下 要将 train2017 val2017 两个目录下的图片并入一个目录 images 用 mklink 创建软链接 1 可以不用额外空间 win10 也可以写 sh 脚本用 ln s 但效果似乎同 copy 因为用 ln
  • uni-app实战教程

    一 准备 下载HBuilderX编辑器 前往下载 注册百度AI账号 创建应用获得Appid和Secret 前往注册 百度AI通用物体识别文档 前往查阅 Uni App文档 前往查阅 HTML5 文档 前往查阅 HTML5 文档 前往查阅 二
  • IDEA插件分享(实用推荐)

    1 SequenceDiagram 序列图插件 查看方法内部的调用其他的序列图 使用方法 选中对应的方法 右击选择 SequenceDiagram 或者右上角点击SequenceDiagramtu bi 2 Maven Search 快速搜
  • 万户协同办公平台ezoffice未授权访问漏洞

    文章目录 0x01 前言 0x02 漏洞描述 0x03 影响范围 0x04 漏洞环境 0x05 漏洞复现 1 构造POC 2 进行MD5值解密 3 尝试进行登录 0x06 复现建议 0x01 前言 本次测试仅供学习使用 如若非法他用 与本文
  • [论文分享] TREX: Learning Execution Semantics from Micro-Traces for Binary Similarity

    TREX Learning Execution Semantics from Micro Traces for Binary Similarity Kexin Pei Columbia University Zhou Xuan Univer
  • HashMap的扩容机制、ConcurrentHashMap的原理

    HashMap的扩容机制 ConcurrentHashMap的原理 n 1 hash 相当于hash n public V put K key V value return putVal hash key key value false t
  • Android性能调优工具TraceView介绍

    转自 http www trinea cn android android traceview 本文主要介绍Android性能调优工具TraceView的使用及通过其确定性能点 目前性能优化专题已完成以下部分 性能优化总纲 性能问题及性能调
  • WIN10软件开机自启动设置(基于win10系统,不依赖第三方软件)

    开机启动项设置教程 第一步 win R打开运行窗口 输入命令msconfig 回车 第二步打开系统配置 切换到 启动 选项卡 第三步打开任务管理器 第四步单击选中想要禁止或者开启的软件 已启动代表开机时软件会自动启动 已禁用代表开机是软件不
  • “Intel VT-x处于禁用状态”怎么解决(图形化)

    Intel VT x处于禁用状态 如何解决 开启虚拟机时弹出这个提示 说明电脑的虚拟化没有开启 只有开启它 才能运行虚拟机 网上大多数 Intel VT x处于禁用状态 解决方案都是开机按Fn系列键进入BIOS设置 可是博主试了好多次 电脑
  • WEB攻击与防御

    这里列举一些常见的攻击类型与基本防御手段 XSS攻击 跨站脚本 Cross site scripting 简称XSS 把JS代码注入到表单中运行例如在表单中提交含有可执行的JS的内容文本 如果服务器端没有过滤或转义这些脚本 而这些脚本由通过
  • 判断带头结点的循环双链表是否对称

    题目 设计一个算法 用于判断带头结点的循环双链表是否对称 分析 循环双链表的特点是 当前结点方便找到前后节点 且尾指针指向第一个结点 对称性 判断第一个结点和最后一个结点的值是否相等 如果相等 再判断第二个结点和倒数第二个结点 以此类推 从
  • [论文阅读] (18)英文论文Model Design和Overview如何撰写及精句摘抄——以系统AI安全顶会为例

    娜璋带你读论文 系列主要是督促自己阅读优秀论文及听取学术讲座 并分享给大家 希望您喜欢 由于作者的英文水平和学术能力不高 需要不断提升 所以还请大家批评指正 非常欢迎大家给我留言评论 学术路上期待与您前行 加油 前一篇介绍CCS2019的P
  • 电子设计大赛作品_电子设计大赛

    为了进一步提高学生对电子和科技的兴趣 培养学生的动手能力和想象能力 增强学生的团队合作意识 提高学生分析和解决问题的能力 现决定开展电子设计大赛 电子设计大赛详情 一 参赛对象 全体全日制在校大学生 1 3人自由组队 并指定队长一名 可自由
  • 华为OD机试 - 简易内存池(Java)

    题目描述 请实现一个简易内存池 根据请求命令完成内存分配和释放 内存池支持两种操作命令 REQUEST和RELEASE 其格式为 REQUEST 请求的内存大小 表示请求分配指定大小内存 如果分配成功 返回分配到的内存首地址 如果内存不足
  • java-map-put方法源码分析

    HashMap是由数组 链表和红黑树组成的数据结构 而其中put方法可以算的上HashMap中的核心方法 这个方法给我们展示了HashMap的大部分精髓 我们首先来看一下map的核心变量 transient Node
  • 2022年一起努力应对互联网寒冬吧,5G音视频时代还不学NDK开发吗

    前言 找工作还是需要大家不要紧张 有我们干这一行的接触人本来就不多 难免看到面试官会紧张 主要是因为怕面试官问的问题到不上来 那时候不要着急 答不上了的千万不然胡扯一些 直接就给面试官说这块我还没接触到 以后如果工作当中遇到的话我可以很快的
  • i2c 编程接口

    1 通信接口 i2c发送或者接收一次数据都以数据包 struct i2c msg 封装 struct i2c msg u16 addr 从机地址 u16 flags 标志 define I2C M TEN 0x0010 十位地址标志 def
  • Vert.X通过Hoverfly满足服务虚拟化

    服务虚拟化是一种用于模拟基于组件的应用程序的依赖关系行为的技术 Hoverfly是用Go语言编写的服务虚拟化工具 可让您模拟HTTP S 服务 它是一个代理 它使用存储的响应来响应HTTP S 请求 并假装它是真正的对应对象 食蚜蝇Java
  • 使用 IO 流 读取 本 地 文 件 (两种方式)

    使用IO 流读取本地文件 public class FileReadWrite public static void main String args FileReader fr null try 1 创建读取文件 fr new FileR
  • [Manjaro] OpenGL 配合着色器实现光线跟踪之引入光线

    概述 本文介绍 GLFW GLAD 在 RayTracing in one weekend 的实现 实验环境 Manjaro Linux 22 0 0 整体思路 使用基于屏幕空间的光线跟踪算法 每个像素点代表一个光线 使用 GLSL 着色器