使用Python和OpenGL渲染PS2存档3D图标

这篇具有很好参考价值的文章主要介绍了使用Python和OpenGL渲染PS2存档3D图标。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

使用Python和OpenGL渲染PS2存档3D图标,粗茶淡饭 代码为伴,python,3d,着色器,游戏

经过前面一系列文章的铺垫,PS2存档3D图标的文件已经全部解析完毕。本篇开始将介绍使用如下工具将3D图标渲染出来,并尽可能接近PS2主机原生的效果。

  • Python3
  • PyGame
  • Numpy
  • ModernGL
  • PyGLM

01 初始化PyGame和ModernGL

第一步先初始化PyGame,设置窗口大小为640x480FPS60。开启OpenGL渲染模式,OpenGL的版本号设置为3.3

import pygame as pg

pg.init()
pg.display.gl_set_attribute(pg.GL_CONTEXT_MAJOR_VERSION, 3)
pg.display.gl_set_attribute(pg.GL_CONTEXT_MINOR_VERSION, 3)
pg.display.gl_set_attribute(pg.GL_CONTEXT_PROFILE_MASK, pg.GL_CONTEXT_PROFILE_CORE)
pg.display.set_mode((640, 480), flags=pg.OPENGL | pg.DOUBLEBUF)
self.clock = pg.time.Clock()
self.clock.tick(60)

接着初始化ModernGL,非常简单,只要创建一个context,开启深度测试和面剔除。

import moderngl as mgl

self.ctx = mgl.create_context()
self.ctx.enable(flags=mgl.DEPTH_TEST | mgl.CULL_FACE)

02 获取顶点、纹理、法线等数据

这部分内容在上一篇解析PS2游戏存档3D图标有详细描述,就不展开了,这里只贴一下icon.sys的数据结构供参考。

struct IconSys {
    char magic[4];
    uint16 unknown; // ignore
    uint16 subtitle_line_break;
    uint16 unknown; // ignore
    uint32 bg_transparency;
    uint32 bg_color_upper_left[4];
    uint32 bg_color_upper_right[4];
    uint32 bg_color_lower_left[4];
    uint32 bg_color_lower_right[4];
    float32 light_pos1[4];
    float32 light_pos2[4];
    float32 light_pos3[4];
    float32 light_color1[4];
    float32 light_color2[4];
    float32 light_color3[4];
    float32 ambient[4];
    char subtitle[68];
    char icon_file_normal[64];
    char icon_file_copy[64];
    char icon_file_delete[64];
    char zeros[512]; // ignore
};

03 坐标系统

这里以右手系统创建坐标系,但是原始的顶点是y轴颠倒的,如下图A。因此我们之后的工作将在转换后的图B坐标系下进行。
使用Python和OpenGL渲染PS2存档3D图标,粗茶淡饭 代码为伴,python,3d,着色器,游戏

04 变换矩阵

观察矩阵

上图B中,摄像机位置在z轴的负延伸方向,我们稍稍向y轴负方向移动一小段距离,这样可以使视线不是对着图标的脚部,而是稍稍靠上一点,因此将摄像机位置坐标设为(0, -2, -10)。因为要将y轴颠倒,可以直接将摄像机向上的方向设置为y轴的负方向。这样一来lookAt矩阵创建如下:

self.position = glm.vec3(0, -2, -10)
self.up = glm.vec3(0, -1, 0)
self.view = glm.lookAt(self.position, glm.vec3(0, -2, 0), self.up)

投影矩阵

投影矩阵可以用如下公式获得

self.proj = glm.perspective(glm.radians(50), window_width / window_height, 0.1, 100)

模型矩阵

创建模型矩阵的目的是控制模型对象在3D空间中的位置变化,在这里模型对象需要在空间里绕着y轴做360度的旋转。

# 初始化模型矩阵
self.m_model = glm.mat4()
# 使模型绕y轴旋转,转过的角度为经过的时间。
# 初始化的180度是为了让模型在开始的时候背对着画面,更接近PS2主机的行为
m_model = glm.rotate(self.m_model, glm.radians(180) + animation_time / 2,
                     glm.vec3(0, 1, 0))

05 创建着色器

这里一共需要创建四个着色器

  • 背景顶点着色器
  • 背景片段着色器
  • Icon顶点着色器
  • Icon片段着色器

背景着色器

背景着色器比较简单,只要创建一个覆盖整个坐标系的矩形,并且设置在离摄像机最远的那个坐标平面上即可。参考上面的图B,这个平面应该是z轴的0.9999。这个矩形的四个顶点的坐标分别为(-1, 1), (-1, -1), (1, -1), (1, 1),对应的颜色在icon.sys中可以解析出来。根据这四个顶点和颜色,就可以构建背景VBO及VAO,这里不做过多描述。

// bg.vert
#version 330 core

in vec2 vertexPos;
in vec4 vertexColor;

out vec3 fragColor0;

void main() {
    fragColor0 = vertexColor.rgb;
    gl_Position = vec4(vertexPos.xy, 0.9999, 1.0);
}
// bg.frag
#version 330 core

in vec3 fragColor0;

out vec4 fragColor;

uniform float alpha0;

void main() {
    fragColor = vec4(fragColor0, alpha0);
}

Icon着色器

Icon着色器会比较复杂,我们先尝试着把Icon顶点渲染出来。还记得每个图标有多个形状吗?形状与动画相关,我们现在只取其中的一个形状组成VBO和VAO。

// icon.vert
#version 330 core

in vec4 vertexPos;

uniform mat4 proj;
uniform mat4 view;
uniform mat4 model;

void main() {
    gl_Position = proj * view * model * vec4(vertexPos.xyz, 1);
}
// icon.frag
#version 330 core

out vec4 fragColor;

void main() {
    fragColor = vec4(0, 0, 0, 1);
}

以下是运行代码后的效果:

使用Python和OpenGL渲染PS2存档3D图标,粗茶淡饭 代码为伴,python,3d,着色器,游戏

添加纹理

在上面的基础上,引入纹理坐标和纹理数据。

// icon.vert
#version 330 core

in vec4 vertexPos;
in vec2 texCoord;
in vec4 vertexColor;

out vec4 fragColor0;
out vec2 uv0;

uniform mat4 proj;
uniform mat4 view;
uniform mat4 model;

void main() {
    uv0 = texCoord;
    fragColor0 = vertexColor;
    gl_Position = proj * view * model * vec4(vertexPos.xyz, 1);
}
// icon.frag
#version 330 core

in vec2 uv0;
in vec4 fragColor0;

out vec4 fragColor;

uniform sampler2D texture0;

void main() {
    float alpha = fragColor0.a;
    vec3 color = fragColor0.rgb * texture(texture0, uv0).rgb;
    fragColor = vec4(color, alpha);
}

使用Python和OpenGL渲染PS2存档3D图标,粗茶淡饭 代码为伴,python,3d,着色器,游戏

添加光照

在上面的基础上,引入光源,环境光以及法线数据。

// icon.vert
#version 330 core

in vec4 vertexPos;
in vec2 texCoord;
in vec4 vertexColor;
in vec4 normal;

out vec4 fragColor0;
out vec2 uv0;
out vec3 normal0;
out vec3 fragPos0;

uniform mat4 proj;
uniform mat4 view;
uniform mat4 model;

void main() {
    uv0 = texCoord;
    fragColor0 = vertexColor;
    normal0 = mat3(model) * normalize(normal.xyz);
    gl_Position = proj * view * model * vec4(vertexPos.xyz, 1);
    fragPos0 = gl_Position.xyz;
}
// icon.frag
#version 330 core
#define MAX_NUM_TOTAL_LIGHTS 3

in vec2 uv0;
in vec4 fragColor0;
in vec3 normal0;
in vec3 fragPos0;

out vec4 fragColor;

struct Light {
    vec4 pos;
    vec4 color;
};

uniform sampler2D texture0;
uniform vec4 ambient;

uniform Light lights[MAX_NUM_TOTAL_LIGHTS];

void main() {
    vec3 normal = normalize(normal0);
    float alpha = fragColor0.a;
    vec3 color = fragColor0.rgb * texture(texture0, uv0).rgb;
    vec3 diffuse = vec3(0.0, 0.0, 0.0);
    for (int i = 0; i < MAX_NUM_TOTAL_LIGHTS; i++) {
        vec3 lightDir = normalize(lights[i].pos.xyz - fragPos0);
        float diff = max(dot(lightDir, normal), 0.0);
        diffuse += diff * lights[i].color.rgb;
    }
    color = (ambient.rgb + diffuse) * color;
    fragColor = vec4(color, alpha);
}

使用Python和OpenGL渲染PS2存档3D图标,粗茶淡饭 代码为伴,python,3d,着色器,游戏

动画效果

动画效果是让着色器按照时间渲染不同形状的顶点数据。我们可以设计一个计时器和一个计数器,以确定当前时间应该渲染哪个形状的顶点。

  • frame_length 完成动画效果需要的实际帧数,实际帧率等于60FPS
  • animation_time 动画运行时间
  • anim_speed 动画播放速度
  • frame_length / animation_shapes 一个形状包含多少帧
animation_time = time.time() - self.start_time
curr_frame = int(animation_time * self.window.fps * self.icon.anim_speed)
             % self.icon.frame_length
curr_shape = int(curr_frame // (self.icon.frame_length / self.icon.animation_shapes))

使用Python和OpenGL渲染PS2存档3D图标,粗茶淡饭 代码为伴,python,3d,着色器,游戏

使动画平滑过渡

使动画平滑过渡需要使用着色器的顶点插值技术。我们在发送着色器顶点的时候,将当前形状和下一个形状的顶点数据同时发送。这样再根据时间因子,着色器会自动计算两个形状之间的顶点。

  • tween_factor 计算当前时间戳在整个形状中所占帧的百分比
curr_frame_in_shape = curr_frame % frames_in_shape / frames_in_shape
tween_factor = glm.float32(curr_frame_in_shape)
// icon.vert
#version 330 core

in vec4 vertexPos;
in vec2 texCoord;
in vec4 vertexColor;
in vec4 nextVertexPos;
in vec4 normal;

out vec4 fragColor0;
out vec2 uv0;
out vec3 normal0;
out vec3 fragPos0;

uniform mat4 proj;
uniform mat4 view;
uniform mat4 model;
uniform float tweenFactor;

void main() {
    uv0 = texCoord;
    fragColor0 = vertexColor;
    normal0 = mat3(model) * normalize(normal.xyz);
    vec4 basePos = vec4(mix(vertexPos.xyz, nextVertexPos.xyz, tweenFactor), 1.0);
    gl_Position = proj * view * model * basePos;
    fragPos0 = gl_Position.xyz;
}
// icon.frag
#version 330 core
#define MAX_NUM_TOTAL_LIGHTS 3

in vec2 uv0;
in vec4 fragColor0;
in vec3 normal0;
in vec3 fragPos0;

out vec4 fragColor;

struct Light {
    vec4 pos;
    vec4 color;
};

uniform sampler2D texture0;
uniform vec4 ambient;

uniform Light lights[MAX_NUM_TOTAL_LIGHTS];

void main() {
    vec3 normal = normalize(normal0);
    float alpha = fragColor0.a;
    vec3 color = fragColor0.rgb * texture(texture0, uv0).rgb;
    vec3 diffuse = vec3(0.0, 0.0, 0.0);
    for (int i = 0; i < MAX_NUM_TOTAL_LIGHTS; i++) {
        vec3 lightDir = normalize(lights[i].pos.xyz - fragPos0);
        float diff = max(dot(lightDir, normal), 0.0);
        diffuse += diff * lights[i].color.rgb;
    }
    color = (ambient.rgb + diffuse) * color;
    fragColor = vec4(color, alpha);
}

最终效果:

使用Python和OpenGL渲染PS2存档3D图标,粗茶淡饭 代码为伴,python,3d,着色器,游戏

06 尾声

所有代码均可在 https://github.com/caol64/ps2mc-browser 下载到。在我的第一篇文章中,我也提到了这个系列的创作初衷:为了纪念逝去的青春,以及对技术永不磨灭的热情。在此收尾,也算还了年少时的一个梦想。

07 参考文献

  • gothi - icon.sys format
  • Martin Akesson - PS2 Icon Format v0.5
  • Florian Märkl - mymcplus
  • Ross Ridge - PlayStation 2 Memory Card File System

08 项目地址

ps2mc-browser

A PS2 game save browser supports displaying 3D icons文章来源地址https://www.toymoban.com/news/detail-775068.html

到了这里,关于使用Python和OpenGL渲染PS2存档3D图标的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【STM32外设系列】双轴按键PS2摇杆

    🎀 文章作者:二土电子 🌸 关注公众号获取更多资料! 🐸 期待大家一起学习交流!   我们首先来看一下双轴按键PS2摇杆长什么样子   该模块有两种输出,X轴和Y轴以模拟信号(电压)的形式输出,Z轴以数字信号(高低电平)的形式输出。其中各个引脚的作用如下 G

    2024年02月22日
    浏览(30)
  • 基于arduino及PS2的麦克纳姆轮遥控小车

           2、设计原理 通过建模模拟车体基本构架,便于结构设计、无实物展示展示和组装;                              (b) 图 3.1  小车 sw 模型    小车简介:小车将采用钢质小车地盘作为车身;以直流减速电机提供动力;麦克纳姆轮作为车轮;采取可装卸推

    2024年02月02日
    浏览(46)
  • 【STM32】SPI与PS2手柄解码(CUBEMX+HAL库)

    本文工程文件以及ps2数据手册在这个链接,我设置成免费了 【免费】STM32PS2解码工程以及代码(CUBEMX+HAL库)资源-CSDN文库 目录   SPI简介 SPI引脚说明 一些参数的含义 通信的四种模式 通信过程简介 关于SPI的常用HAL库函数 PS2简介 ps2手柄 ps2接收器 PS2解码 CUBEMX工程配置 PS2解码

    2024年04月17日
    浏览(24)
  • python opengl 渲染图片和Obj

    目录 安装:  python opengl 渲染图片和Obj 截图png格式和jpg格式: pip install pyopengl

    2024年02月11日
    浏览(23)
  • 如何使用ps制作ico图标文件

    原文链接:https://blog.csdn.net/SDKL_YI/article/details/103513372 如何使用ps制作ico图标文件 先打开ps导入文件,不管什么格式,是图片能显示就行: 选择磁性索套,可以调整频率,频率越高当然抠出来的图边缘约圆滑,当然也是有上限的,具体数据具体操作改就行了: 套完图片然后在

    2024年02月06日
    浏览(24)
  • openpnp - 汇川 Inovance IS620PS2R8I-IAB-C的参数读取

    设备中用到了一台 汇川 Inovance IS620PS2R8I-IAB-C. 部件总是有寿命的, 万一以后坏了, 买一台同型号的伺服容易, 但是里面的参数设置如果不早做准备, 到时候就难了, 得是装伺服的作者才知道. 琢磨了一下怎么读取这台伺服的参数. X伺服型号(汇川 Inovance IS620PS2R8I-IAB-C) X伺服配的电机

    2024年02月16日
    浏览(42)
  • libVLC 提取视频帧使用OpenGL渲染

    在上一节中,我们讲解了如何使用QWidget渲染每一帧视频数据。 由于我们不停的生成的是QImage对象,因此对 CPU 负荷较高。其实在绘制这块我们可以使用 OpenGL去绘制,利用 GPU 减轻 CPU 计算负荷,本节讲解使用OpenGL来绘制每一帧视频数据。 libVLC 提取视频帧使用QWidget渲染-CSDN博

    2024年04月10日
    浏览(34)
  • 使用OpenGL 和 opengl ES 渲染YUV图片文件的QT示例

    头文件:CPlayWidget.h cpp文件:CPlayWidget.cpp 默认打开 ./test.yuv文件 头文件:CPlayWidget.h  与上面没有差别 cpp文件:只替换了着色器代码:

    2024年01月20日
    浏览(32)
  • macOS下使用OpenGL做离屏渲染

    有时,我们想通过GPU做一些视频、图像处理,而处理的结果不需要显示在显示器上,而是直接交给主存,这时候我们可以通过OpenGL的 离屏渲染 ( Offscreen Rendering )来实现。 由于我们不需要将渲染好的像素显示到屏幕上,因此我们可以使用framebuffer object,将像素放到 fbo 上,

    2024年02月03日
    浏览(28)
  • Qt使用OpenGL进行多线程离屏渲染

    基于Qt Widgets的Qt程序,控件的刷新默认状况下都是在UI线程中依次进行的,换言之,各个控件的QWidget::paintEvent方法会在UI线程中串行地被调用。若是某个控件的paintEvent很是耗时(等待数据时间+CPU处理时间+GPU渲染时间),会致使刷新帧率降低,界面的响应速度变慢。 假如这个

    2024年02月02日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包