feat: pbr (#20)

* feat: add pbr texture

* feat(rendering): add normal mapping support for blocks

* fix: normal map load

* feat(scripts): add batch nearest neighbor upscale script
This commit is contained in:
zhenyan121
2026-06-20 15:03:40 +08:00
committed by GitHub
parent a8d2ddbacd
commit 5385876a8a
52 changed files with 462 additions and 65 deletions

View File

@@ -617,6 +617,10 @@ void DevPanel::show_shader_tab_item() {
static const char* samples[] = {"8", "16", "32"};
if (ImGui::BeginTabItem("shader")) {
ImGui::Checkbox("Shader", &m_app.renderer().shader_on());
ImGui::SameLine();
ImGui::Checkbox("PBR", &m_app.renderer().pbr());
ImGui::SameLine();
ImGui::Checkbox("Flip Y", &m_app.renderer().flip_y());
if (ImGui::SliderFloat("AmbientStrength",
&m_app.renderer().ambient_strength(), 0.0f,
0.35f))

View File

@@ -383,7 +383,10 @@ void Chunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) {
NORMALS[face][i][0],
NORMALS[face][i][1],
NORMALS[face][i][2],
BlockManager::roughness(cur_id)
BlockManager::roughness(cur_id),
TANGENTS[face][i][0],
TANGENTS[face][i][1],
TANGENTS[face][i][2]
};
if (BlockManager::is_transparent(cur_id)) {
@@ -441,7 +444,10 @@ void Chunk::gen_cross_plane_vertices(int world_x, int world_y, int world_z,
CROSS_NORMALS[face][i][0],
CROSS_NORMALS[face][i][1],
CROSS_NORMALS[face][i][2],
BlockManager::roughness(id)
BlockManager::roughness(id),
CROSS_TANGENTS[face][i][0],
CROSS_TANGENTS[face][i][1],
CROSS_TANGENTS[face][i][2]
};
m_vertex_data[1].m_vertices.emplace_back(vex);

View File

@@ -53,12 +53,14 @@ void VertexData::upload() {
(void*)offsetof(Vertex3D, nx));
glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex3D),
(void*)offsetof(Vertex3D, roughness));
glVertexAttribPointer(5, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D),
(void*)offsetof(Vertex3D, tx));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
glEnableVertexAttribArray(4);
glEnableVertexAttribArray(5);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}

View File

@@ -677,7 +677,7 @@ void Renderer::render_world() {
float dist2d = glm::distance(camera_pos_xz, center_xz);
if (dist2d <= CROSS_PLANE_DISTANCE * 16) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array());
m_texture_manager.get_cross_plane_array());
glBindVertexArray(snapshot.cross_vao);
glDrawArrays(GL_TRIANGLES, 0,
@@ -694,7 +694,6 @@ void Renderer::render_world() {
}
}
}
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glCullFace(GL_BACK);
@@ -702,16 +701,12 @@ void Renderer::render_world() {
const auto& normal_block_shader = get_shader("normal_block");
normal_block_shader.use();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_depth_map_texture);
glActiveTexture(GL_TEXTURE1);
m_m_mat = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.0f));
m_v_mat = m_camera.get_camera_lookat();
m_mv_mat = m_v_mat * m_m_mat;
m_norm_mat = glm::transpose(glm::inverse(m_mv_mat));
glm::vec3 light_dir_view = glm::normalize(glm::mat3(m_v_mat) * lightdir);
normal_block_shader.set_loc("model_matrix", m_m_mat);
normal_block_shader.set_loc("mv_matrix", m_mv_mat);
normal_block_shader.set_loc("proj_matrix", m_p_mat);
normal_block_shader.set_loc("norm_matrix", m_norm_mat);
@@ -732,7 +727,7 @@ void Renderer::render_world() {
normal_block_shader.set_loc("samples", m_samples);
normal_block_shader.set_loc("specularStrength", m_specular_strength);
normal_block_shader.set_loc("cameraPos", m_camera.get_camera_pos());
normal_block_shader.set_loc("flipY", m_flip_y);
m_mvp_mat = m_p_mat * m_mv_mat;
auto& m_planes = m_world.planes();
@@ -741,12 +736,18 @@ void Renderer::render_world() {
int rendered_sum = 0;
glEnable(GL_DEPTH_TEST);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_depth_map_texture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array());
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_pbr_texture());
normal_block_shader.set_loc("enablePBR", m_pbr);
for (const auto& snapshot : m_render_snapshots) {
if (Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents,
m_planes)) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array());
glBindVertexArray(snapshot.normal_vao);
@@ -755,8 +756,24 @@ void Renderer::render_world() {
rendered_sum++;
}
}
// cross_plane and discard
// discard
for (const auto& snapshot : m_render_snapshots) {
if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents,
m_planes)) {
continue;
}
if (snapshot.normal_discard_vertices_count != 0) {
glBindVertexArray(snapshot.normal_discard_vao);
glDrawArrays(GL_TRIANGLES, 0,
snapshot.normal_discard_vertices_count);
}
}
// cross_plane
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_cross_plane_array());
normal_block_shader.set_loc("enablePBR", false);
for (const auto& snapshot : m_render_snapshots) {
if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents,
m_planes)) {
@@ -767,22 +784,11 @@ void Renderer::render_world() {
glm::vec2 center_xz{snapshot.center.x, snapshot.center.z};
float dist2d = glm::distance(camera_pos_xz, center_xz);
if (dist2d <= CROSS_PLANE_DISTANCE * 16) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_cross_plane_array());
glBindVertexArray(snapshot.cross_vao);
glDrawArrays(GL_TRIANGLES, 0, snapshot.cross_vertices_count);
}
}
if (snapshot.normal_discard_vertices_count != 0) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array());
glBindVertexArray(snapshot.normal_discard_vao);
glDrawArrays(GL_TRIANGLES, 0,
snapshot.normal_discard_vertices_count);
}
}
// copy depth buffer
@@ -830,6 +836,7 @@ void Renderer::render_world() {
set_accum_loc(accum_shader);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array());
for (const auto& snapshot : m_render_snapshots) {
if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents,
m_planes)) {
@@ -837,8 +844,7 @@ void Renderer::render_world() {
}
if (snapshot.normal_blend_vertices_count != 0) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array());
glBindVertexArray(snapshot.normal_blend_vao);
glDrawArrays(GL_TRIANGLES, 0, snapshot.normal_blend_vertices_count);
@@ -879,6 +885,7 @@ void Renderer::render_world() {
glBindTexture(GL_TEXTURE_2D, m_screen_depth_texture);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array());
for (const auto& snapshot : m_render_snapshots) {
if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents,
m_planes)) {
@@ -886,8 +893,7 @@ void Renderer::render_world() {
}
if (snapshot.water_vertices_count != 0) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array());
glBindVertexArray(snapshot.water_vao);
glDrawArrays(GL_TRIANGLES, 0, snapshot.water_vertices_count);
@@ -896,9 +902,10 @@ void Renderer::render_world() {
auto& composite_shader = get_shader("composite");
glDisable(GL_BLEND);
composite_shader.use();
glUniform1i(composite_shader.loc("u_accumTex"), 0);
glUniform1i(composite_shader.loc("u_revealTex"), 1);
composite_shader.set_loc("u_accumTex", 0);
composite_shader.set_loc("u_revealTex", 1);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glEnable(GL_BLEND);
@@ -977,6 +984,8 @@ bool& Renderer::discard_transparent() { return m_discard_tranparent; }
bool& Renderer::shader_on() { return m_shader_on; }
bool& Renderer::water_perturb() { return m_water_perturb; }
bool& Renderer::water_depth_fade() { return m_water_depth_fade; }
bool& Renderer::pbr() { return m_pbr; }
bool& Renderer::flip_y() { return m_flip_y; }
int& Renderer::shadow_mode() { return m_shadow_mode; }
int& Renderer::light_cull_face() { return m_light_cull_face; }
int& Renderer::light_size_uv() { return m_light_size_uv; }

View File

@@ -7,6 +7,28 @@
#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;
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() {}
@@ -17,6 +39,7 @@ void TextureManager::delet_texture() {
glDeleteTextures(1, &m_texture_array);
glDeleteTextures(1, &m_block_status_array);
glDeleteTextures(1, &m_cross_plane_array);
glDeleteBuffers(1, &m_pbr_texture_array);
for (auto& id : m_item_textures) {
glDeleteTextures(1, &id);
}
@@ -34,6 +57,8 @@ GLuint TextureManager::get_cross_plane_array() const {
}
GLuint TextureManager::get_ui_array() const { return m_ui_array; }
GLuint TextureManager::get_pbr_texture() const { return m_pbr_texture_array; }
const std::vector<GLuint>& TextureManager::item_textures() const {
return m_item_textures;
}
@@ -45,8 +70,9 @@ void TextureManager::load_block_status(unsigned id) {
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, 16, 16, 1, GL_RGBA,
GL_UNSIGNED_BYTE, image_data);
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);
}
@@ -76,8 +102,9 @@ void TextureManager::load_block_texture(unsigned id) {
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, 16, 16, 1,
GL_RGBA, GL_UNSIGNED_BYTE, image_data[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]);
}
@@ -94,8 +121,8 @@ void TextureManager::load_block_item_texture(unsigned id) {
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 16, 16, 0, GL_RGBA,
GL_UNSIGNED_BYTE, data);
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,
@@ -115,8 +142,8 @@ void TextureManager::load_cross_plane_texture(unsigned id) {
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), 16, 16, 1, GL_RGBA,
GL_UNSIGNED_BYTE, image_data);
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);
}
@@ -127,27 +154,74 @@ void TextureManager::load_ui_texture(unsigned id) {
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, 16, 16, 1, GL_RGBA,
GL_UNSIGNED_BYTE, image_data);
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_pbr_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, 16, 16,
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, 16, 16,
BlockManager::cross_plane_sum(), 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
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_pbr_texture_array);
glBindTexture(GL_TEXTURE_2D_ARRAY, m_pbr_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);
@@ -172,13 +246,25 @@ void TextureManager::init_block() {
static_cast<GLfloat>(m_aniso));
}
glBindTexture(GL_TEXTURE_2D_ARRAY, m_pbr_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_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));
}
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, 16, 16, MAX_UI_NUM, 0,
GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
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);
}
@@ -190,8 +276,9 @@ void TextureManager::init_ui() {
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, 16, 16, MAX_BLOCK_STATUS, 0,
GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
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);
}

View File

@@ -118,16 +118,31 @@ std::string read_shader_source(const std::string& file_path) {
return content;
}
void delete_image_data(unsigned char* data) { SOIL_free_image_data(data); }
void delete_image_data(unsigned char* data) {
if (data == nullptr) {
return;
}
SOIL_free_image_data(data);
}
unsigned char* load_image_data(const std::string& tex_image_path) {
unsigned char* load_image_data(const std::string& tex_image_path,
bool check_exist) {
fs::path path = ASSETS_PATH + tex_image_path;
ASSERT_MSG(fs::is_regular_file(path), path.c_str());
if (check_exist) {
ASSERT_MSG(fs::is_regular_file(path), path.c_str());
}
unsigned char* data = nullptr;
int width, height, channels;
data = SOIL_load_image(path.string().c_str(), &width, &height, &channels,
SOIL_LOAD_AUTO);
ASSERT_MSG(data, "Could not load texture" + path.string());
data =
SOIL_load_image(path.string().c_str(), &width, &height, &channels,
SOIL_LOAD_RGBA); // Materials are all RGBA; must force
// RGBA, otherwise sampling will fail.
if (check_exist) {
if (!data) {
ASSERT_MSG(data, "Could not load texture" + path.string());
std::abort();
}
}
return data;
}