mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-08 17:57:02 +08:00
* refactor(player): extract player rendering into PlayerRenderer class Move player rendering code from Renderer to new PlayerRenderer class. Add normal and tangent vertex attributes for improved per-pixel lighting. * feat(player-renderer): add shadow rendering and include local player data * feat(gameplay): add player yaw/pitch synchronization and rendering * refactor(player_renderer): split rendering into per-body-part VAOs and VBOs Move `PlayerVertex` struct to header and replace single VAO/VBO with arrays of 6 parts. Implement head pitch rotation in `render()` and `shadow_render()` by applying a separate model matrix for the head (index 0). Remove old `vao()` and `sum()` accessors. Adjust model translation for consistency. * feat(player): add texture mapping to player model using skin * fix(player-renderer): correct player rendering rotation and texture coordinates * feat(gameplay): add player gait system with limb animation * fix(player): update leg UVs and remove yaw wrap Update left and right leg UV coordinates to align with updated player texture. Remove yaw modulo operation to allow unrestricted yaw rotation beyond 360 degrees. * feat(camera): add third-person perspective and walk animation - Introduce Perspective enum (FIRST_PERSON, THIRD_PERSON_BACK, THIRD_PERSON_FRONT) and m_front member for camera direction. - Handle F5 key to cycle perspectives. - Add angle and walk_time to ClientPlayer for walking animation. - Update player rendering to skip local player only in first person and fix rotation sign. - Remove redundant player info reporting; update player data with animation angle. * fix(camera): replace magic number with constant and fix missing break * fix(client-player): stop sprinting on forward key release * feat: add camera collision and sphere collision for world * fix(skin): update player001 texture * refactor(gameplay): improve thread safety and remove debug logging * refactor(shaders): extract shadow and normal logic into include files and add lighting to player shaders * chore(shader): remove unused uniforms cameraPos and specularStrength * fix: inlcude <array> header
368 lines
13 KiB
C++
368 lines
13 KiB
C++
#include "Cubed/texture_manager.hpp"
|
|
|
|
#include "Cubed/config.hpp"
|
|
#include "Cubed/constants.hpp"
|
|
#include "Cubed/map_table.hpp"
|
|
#include "Cubed/tools/cubed_assert.hpp"
|
|
#include "Cubed/tools/log.hpp"
|
|
#include "Cubed/tools/shader_tools.hpp"
|
|
|
|
namespace {
|
|
constexpr int BLOCK_SIZE = 16;
|
|
constexpr int BLOCK_NORMAL_SIZE = 128;
|
|
constexpr int CROSS_PLANE_SIZE = 16;
|
|
constexpr int BLOCK_ITEM_SIZE = 16;
|
|
constexpr int UI_SIZE = 16;
|
|
constexpr int BLOCK_STATUS_SIZE = 16;
|
|
constexpr int SKIN_SIZE = 64;
|
|
|
|
unsigned char* generate_flat_normal_map(int width = BLOCK_NORMAL_SIZE,
|
|
int height = BLOCK_NORMAL_SIZE) {
|
|
unsigned char* data = new unsigned char[width * height * 4];
|
|
for (int i = 0; i < width * height; i++) {
|
|
data[i * 4 + 0] = 128; // R -> X = 0
|
|
data[i * 4 + 1] = 128; // G -> Y = 0
|
|
data[i * 4 + 2] = 255; // B -> Z = 1
|
|
data[i * 4 + 3] = 255; // A
|
|
}
|
|
return data;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace Cubed {
|
|
|
|
TextureManager::TextureManager() {}
|
|
|
|
TextureManager::~TextureManager() { delet_texture(); }
|
|
|
|
void TextureManager::delet_texture() {
|
|
if (m_init) {
|
|
glDeleteTextures(1, &m_texture_array);
|
|
glDeleteTextures(1, &m_block_status_array);
|
|
glDeleteTextures(1, &m_cross_plane_array);
|
|
glDeleteTextures(1, &m_normal_texture_array);
|
|
for (auto& id : m_item_textures) {
|
|
glDeleteTextures(1, &id);
|
|
}
|
|
glDeleteTextures(1, &m_skin);
|
|
Logger::info("Successfully delete all texture");
|
|
}
|
|
}
|
|
|
|
GLuint TextureManager::get_block_status_array() const {
|
|
return m_block_status_array;
|
|
}
|
|
|
|
GLuint TextureManager::get_texture_array() const { return m_texture_array; }
|
|
|
|
GLuint TextureManager::get_cross_plane_array() const {
|
|
return m_cross_plane_array;
|
|
}
|
|
GLuint TextureManager::get_ui_array() const { return m_ui_array; }
|
|
|
|
GLuint TextureManager::get_pbr_texture() const {
|
|
return m_normal_texture_array;
|
|
}
|
|
|
|
const std::vector<GLuint>& TextureManager::item_textures() const {
|
|
return m_item_textures;
|
|
}
|
|
|
|
GLuint TextureManager::get_skin() const { return m_skin; }
|
|
|
|
void TextureManager::load_block_status(unsigned id) {
|
|
|
|
ASSERT_MSG(id < MAX_BLOCK_STATUS, "Exceed the max status sum limit");
|
|
std::string path = "texture/status/" + std::to_string(id) + ".png";
|
|
unsigned char* image_data = nullptr;
|
|
image_data = (Tools::load_image_data(path));
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, m_block_status_array);
|
|
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, id, BLOCK_STATUS_SIZE,
|
|
BLOCK_STATUS_SIZE, 1, GL_RGBA, GL_UNSIGNED_BYTE,
|
|
image_data);
|
|
Tools::delete_image_data(image_data);
|
|
}
|
|
|
|
void TextureManager::load_block_texture(unsigned id) {
|
|
ASSERT_MSG(id < BlockManager::sums(), "Exceed the max block sum limit");
|
|
std::string name{BlockManager::name_form_id(id)};
|
|
// air don`t need texture
|
|
if (id == 0) {
|
|
return;
|
|
}
|
|
|
|
if (BlockManager::is_cross_plane(id)) {
|
|
load_cross_plane_texture(id);
|
|
return;
|
|
}
|
|
|
|
unsigned char* image_data[6];
|
|
|
|
std::string block_texture_path = "texture/block/" + name;
|
|
image_data[0] = (Tools::load_image_data(block_texture_path + "/front.png"));
|
|
image_data[1] = (Tools::load_image_data(block_texture_path + "/right.png"));
|
|
image_data[2] = (Tools::load_image_data(block_texture_path + "/back.png"));
|
|
image_data[3] = (Tools::load_image_data(block_texture_path + "/left.png"));
|
|
image_data[4] = (Tools::load_image_data(block_texture_path + "/top.png"));
|
|
image_data[5] = (Tools::load_image_data(block_texture_path + "/base.png"));
|
|
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_array);
|
|
Tools::check_opengl_error();
|
|
for (int i = 0; i < 6; i++) {
|
|
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, id * 6 + i, BLOCK_SIZE,
|
|
BLOCK_SIZE, 1, GL_RGBA, GL_UNSIGNED_BYTE,
|
|
image_data[i]);
|
|
Tools::check_opengl_error();
|
|
Tools::delete_image_data(image_data[i]);
|
|
}
|
|
}
|
|
|
|
void TextureManager::load_block_item_texture(unsigned id) {
|
|
|
|
ASSERT_MSG(id < BlockManager::sums(), "Exceed the max block sum limit");
|
|
std::string name{BlockManager::name_form_id(id)};
|
|
|
|
std::string path = "texture/item/block/" + name + ".png";
|
|
unsigned char* data = nullptr;
|
|
data = Tools::load_image_data(path);
|
|
GLuint texture;
|
|
glGenTextures(1, &texture);
|
|
glBindTexture(GL_TEXTURE_2D, texture);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, BLOCK_ITEM_SIZE, BLOCK_ITEM_SIZE,
|
|
0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
|
GL_LINEAR_MIPMAP_LINEAR);
|
|
glGenerateMipmap(GL_TEXTURE_2D);
|
|
if (m_aniso >= 1) {
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY,
|
|
static_cast<GLfloat>(m_aniso));
|
|
}
|
|
m_item_textures.push_back(texture);
|
|
Tools::delete_image_data(data);
|
|
}
|
|
|
|
void TextureManager::load_cross_plane_texture(unsigned id) {
|
|
std::string path =
|
|
"texture/block/" + BlockManager::name_form_id(id) + "/cross.png";
|
|
unsigned char* image_data = Tools::load_image_data(path);
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, m_cross_plane_array);
|
|
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0,
|
|
BlockManager::cross_plane_index(id), CROSS_PLANE_SIZE,
|
|
CROSS_PLANE_SIZE, 1, GL_RGBA, GL_UNSIGNED_BYTE, image_data);
|
|
Tools::delete_image_data(image_data);
|
|
}
|
|
|
|
void TextureManager::load_ui_texture(unsigned id) {
|
|
ASSERT_MSG(id < MAX_UI_NUM, "Exceed the max ui sum limit");
|
|
|
|
std::string path = "texture/ui/" + std::to_string(id) + ".png";
|
|
unsigned char* image_data = nullptr;
|
|
image_data = (Tools::load_image_data(path));
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, m_ui_array);
|
|
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, id, UI_SIZE, UI_SIZE, 1,
|
|
GL_RGBA, GL_UNSIGNED_BYTE, image_data);
|
|
Tools::delete_image_data(image_data);
|
|
}
|
|
|
|
void TextureManager::load_pbr_texture(unsigned id) {
|
|
|
|
if (id == 0) {
|
|
return;
|
|
}
|
|
|
|
if (BlockManager::is_cross_plane(id)) {
|
|
|
|
return;
|
|
}
|
|
|
|
std::string path = "normal/block/" + BlockManager::name_form_id(id);
|
|
|
|
unsigned char* image_data[6];
|
|
|
|
image_data[0] = (Tools::load_image_data(path + "/front_n.png", false));
|
|
image_data[1] = (Tools::load_image_data(path + "/right_n.png", false));
|
|
image_data[2] = (Tools::load_image_data(path + "/back_n.png", false));
|
|
image_data[3] = (Tools::load_image_data(path + "/left_n.png", false));
|
|
image_data[4] = (Tools::load_image_data(path + "/top_n.png", false));
|
|
image_data[5] = (Tools::load_image_data(path + "/base_n.png", false));
|
|
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, m_normal_texture_array);
|
|
for (int i = 0; i < 6; i++) {
|
|
unsigned char* data = image_data[i];
|
|
bool is_fallback = false;
|
|
if (!data) {
|
|
is_fallback = true;
|
|
data = generate_flat_normal_map();
|
|
}
|
|
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, id * 6 + i,
|
|
BLOCK_NORMAL_SIZE, BLOCK_NORMAL_SIZE, 1, GL_RGBA,
|
|
GL_UNSIGNED_BYTE, data);
|
|
if (is_fallback) {
|
|
delete[] data;
|
|
} else {
|
|
Tools::delete_image_data(image_data[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TextureManager::init_block() {
|
|
|
|
glGenTextures(1, &m_texture_array);
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_array);
|
|
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, BLOCK_SIZE, BLOCK_SIZE,
|
|
BlockManager::sums() * 6, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
|
nullptr);
|
|
|
|
glGenTextures(1, &m_cross_plane_array);
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, m_cross_plane_array);
|
|
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, CROSS_PLANE_SIZE,
|
|
CROSS_PLANE_SIZE, BlockManager::cross_plane_sum(), 0, GL_RGBA,
|
|
GL_UNSIGNED_BYTE, nullptr);
|
|
glGenTextures(1, &m_normal_texture_array);
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, m_normal_texture_array);
|
|
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, BLOCK_NORMAL_SIZE,
|
|
BLOCK_NORMAL_SIZE, BlockManager::sums() * 6, 0, GL_RGBA,
|
|
GL_UNSIGNED_BYTE, nullptr);
|
|
for (unsigned i = 0; i < BlockManager::sums(); i++) {
|
|
load_block_texture(i);
|
|
load_block_item_texture(i);
|
|
load_pbr_texture(i);
|
|
}
|
|
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_array);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER,
|
|
GL_LINEAR_MIPMAP_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
|
|
if (m_aniso >= 1) {
|
|
glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_ANISOTROPY,
|
|
static_cast<GLfloat>(m_aniso));
|
|
}
|
|
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, m_cross_plane_array);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER,
|
|
GL_LINEAR_MIPMAP_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
|
|
if (m_aniso >= 1) {
|
|
glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_ANISOTROPY,
|
|
static_cast<GLfloat>(m_aniso));
|
|
}
|
|
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, m_normal_texture_array);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER,
|
|
GL_LINEAR_MIPMAP_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
|
|
if (m_aniso >= 1) {
|
|
glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_ANISOTROPY,
|
|
static_cast<GLfloat>(m_aniso));
|
|
}
|
|
|
|
Logger::info("Block Texture Load Success");
|
|
}
|
|
void TextureManager::init_ui() {
|
|
glGenTextures(1, &m_ui_array);
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, m_ui_array);
|
|
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, UI_SIZE, UI_SIZE, MAX_UI_NUM,
|
|
0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
|
for (int i = 0; i < MAX_UI_NUM; i++) {
|
|
load_ui_texture(i);
|
|
}
|
|
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, m_ui_array);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
}
|
|
|
|
void TextureManager::init_skin() {
|
|
|
|
glGenTextures(1, &m_skin);
|
|
glBindTexture(GL_TEXTURE_2D, m_skin);
|
|
std::string path = "texture/skin/player001.png";
|
|
unsigned char* image_data = nullptr;
|
|
image_data = (Tools::load_image_data(path));
|
|
glBindTexture(GL_TEXTURE_2D, m_skin);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SKIN_SIZE, SKIN_SIZE, 0, GL_RGBA,
|
|
GL_UNSIGNED_BYTE, image_data);
|
|
Tools::delete_image_data(image_data);
|
|
glBindTexture(GL_TEXTURE_2D, m_skin);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
|
GL_LINEAR_MIPMAP_LINEAR);
|
|
glGenerateMipmap(GL_TEXTURE_2D);
|
|
|
|
if (m_aniso >= 1) {
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY,
|
|
static_cast<GLfloat>(m_aniso));
|
|
}
|
|
}
|
|
|
|
void TextureManager::init_block_status() {
|
|
glGenTextures(1, &m_block_status_array);
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, m_block_status_array);
|
|
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, BLOCK_STATUS_SIZE,
|
|
BLOCK_STATUS_SIZE, MAX_BLOCK_STATUS, 0, GL_RGBA,
|
|
GL_UNSIGNED_BYTE, nullptr);
|
|
for (int i = 0; i < MAX_BLOCK_STATUS; i++) {
|
|
load_block_status(i);
|
|
}
|
|
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, m_block_status_array);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER,
|
|
GL_LINEAR_MIPMAP_LINEAR);
|
|
glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
|
|
|
|
if (m_aniso >= 1) {
|
|
glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_ANISOTROPY,
|
|
static_cast<GLfloat>(m_aniso));
|
|
}
|
|
}
|
|
void TextureManager::init_texture() {
|
|
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &m_max_aniso);
|
|
if (m_max_aniso > 0.0f) {
|
|
Logger::info("Support anisotropic filtering max_aniso is {}",
|
|
m_max_aniso);
|
|
}
|
|
m_aniso = Config::get().get<int>("texture.aniso");
|
|
m_aniso = std::min(static_cast<int>(m_max_aniso), m_aniso);
|
|
Logger::info("Setting Texture Aniso is {}", m_aniso);
|
|
MapTable::init_map();
|
|
Logger::info("Map Init Success");
|
|
|
|
init_block();
|
|
init_block_status();
|
|
init_ui();
|
|
init_skin();
|
|
m_init = true;
|
|
}
|
|
|
|
void TextureManager::update() {
|
|
if (m_need_reload) {
|
|
hot_reload();
|
|
}
|
|
}
|
|
|
|
void TextureManager::need_reload() { m_need_reload = true; }
|
|
|
|
void TextureManager::hot_reload() {
|
|
delet_texture();
|
|
|
|
init_texture();
|
|
m_need_reload = false;
|
|
}
|
|
|
|
int TextureManager::max_aniso() const { return static_cast<int>(m_max_aniso); }
|
|
|
|
} // namespace Cubed
|