|
| 1 | +/**************************************************************************/ |
| 2 | +/* cubemap_filter.cpp */ |
| 3 | +/**************************************************************************/ |
| 4 | +/* This file is part of: */ |
| 5 | +/* GODOT ENGINE */ |
| 6 | +/* https://godotengine.org */ |
| 7 | +/**************************************************************************/ |
| 8 | +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | +/* */ |
| 11 | +/* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | +/* a copy of this software and associated documentation files (the */ |
| 13 | +/* "Software"), to deal in the Software without restriction, including */ |
| 14 | +/* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | +/* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | +/* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | +/* the following conditions: */ |
| 18 | +/* */ |
| 19 | +/* The above copyright notice and this permission notice shall be */ |
| 20 | +/* included in all copies or substantial portions of the Software. */ |
| 21 | +/* */ |
| 22 | +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | +/**************************************************************************/ |
| 30 | + |
| 31 | +#ifdef GLES3_ENABLED |
| 32 | + |
| 33 | +#include "cubemap_filter.h" |
| 34 | + |
| 35 | +#include "../storage/texture_storage.h" |
| 36 | +#include "core/config/project_settings.h" |
| 37 | + |
| 38 | +using namespace GLES3; |
| 39 | + |
| 40 | +CubemapFilter *CubemapFilter::singleton = nullptr; |
| 41 | + |
| 42 | +CubemapFilter::CubemapFilter() { |
| 43 | + singleton = this; |
| 44 | + ggx_samples = GLOBAL_GET("rendering/reflections/sky_reflections/ggx_samples"); |
| 45 | + |
| 46 | + { |
| 47 | + String defines; |
| 48 | + defines += "\n#define MAX_SAMPLE_COUNT " + itos(ggx_samples) + "\n"; |
| 49 | + cubemap_filter.shader.initialize(defines); |
| 50 | + cubemap_filter.shader_version = cubemap_filter.shader.version_create(); |
| 51 | + } |
| 52 | + |
| 53 | + { // Screen Triangle. |
| 54 | + glGenBuffers(1, &screen_triangle); |
| 55 | + glBindBuffer(GL_ARRAY_BUFFER, screen_triangle); |
| 56 | + |
| 57 | + const float qv[6] = { |
| 58 | + -1.0f, |
| 59 | + -1.0f, |
| 60 | + 3.0f, |
| 61 | + -1.0f, |
| 62 | + -1.0f, |
| 63 | + 3.0f, |
| 64 | + }; |
| 65 | + |
| 66 | + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6, qv, GL_STATIC_DRAW); |
| 67 | + glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind |
| 68 | + |
| 69 | + glGenVertexArrays(1, &screen_triangle_array); |
| 70 | + glBindVertexArray(screen_triangle_array); |
| 71 | + glBindBuffer(GL_ARRAY_BUFFER, screen_triangle); |
| 72 | + glVertexAttribPointer(RS::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, nullptr); |
| 73 | + glEnableVertexAttribArray(RS::ARRAY_VERTEX); |
| 74 | + glBindVertexArray(0); |
| 75 | + glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +CubemapFilter::~CubemapFilter() { |
| 80 | + glDeleteBuffers(1, &screen_triangle); |
| 81 | + glDeleteVertexArrays(1, &screen_triangle_array); |
| 82 | + |
| 83 | + cubemap_filter.shader.version_free(cubemap_filter.shader_version); |
| 84 | + singleton = nullptr; |
| 85 | +} |
| 86 | + |
| 87 | +// Helper functions for IBL filtering |
| 88 | + |
| 89 | +Vector3 importance_sample_GGX(Vector2 xi, float roughness4) { |
| 90 | + // Compute distribution direction |
| 91 | + float phi = 2.0 * Math_PI * xi.x; |
| 92 | + float cos_theta = sqrt((1.0 - xi.y) / (1.0 + (roughness4 - 1.0) * xi.y)); |
| 93 | + float sin_theta = sqrt(1.0 - cos_theta * cos_theta); |
| 94 | + |
| 95 | + // Convert to spherical direction |
| 96 | + Vector3 half_vector; |
| 97 | + half_vector.x = sin_theta * cos(phi); |
| 98 | + half_vector.y = sin_theta * sin(phi); |
| 99 | + half_vector.z = cos_theta; |
| 100 | + |
| 101 | + return half_vector; |
| 102 | +} |
| 103 | + |
| 104 | +float distribution_GGX(float NdotH, float roughness4) { |
| 105 | + float NdotH2 = NdotH * NdotH; |
| 106 | + float denom = (NdotH2 * (roughness4 - 1.0) + 1.0); |
| 107 | + denom = Math_PI * denom * denom; |
| 108 | + |
| 109 | + return roughness4 / denom; |
| 110 | +} |
| 111 | + |
| 112 | +float radical_inverse_vdC(uint32_t bits) { |
| 113 | + bits = (bits << 16) | (bits >> 16); |
| 114 | + bits = ((bits & 0x55555555) << 1) | ((bits & 0xAAAAAAAA) >> 1); |
| 115 | + bits = ((bits & 0x33333333) << 2) | ((bits & 0xCCCCCCCC) >> 2); |
| 116 | + bits = ((bits & 0x0F0F0F0F) << 4) | ((bits & 0xF0F0F0F0) >> 4); |
| 117 | + bits = ((bits & 0x00FF00FF) << 8) | ((bits & 0xFF00FF00) >> 8); |
| 118 | + |
| 119 | + return float(bits) * 2.3283064365386963e-10; |
| 120 | +} |
| 121 | + |
| 122 | +Vector2 hammersley(uint32_t i, uint32_t N) { |
| 123 | + return Vector2(float(i) / float(N), radical_inverse_vdC(i)); |
| 124 | +} |
| 125 | + |
| 126 | +void CubemapFilter::filter_radiance(GLuint p_source_cubemap, GLuint p_dest_cubemap, GLuint p_dest_framebuffer, int p_source_size, int p_mipmap_count, int p_layer) { |
| 127 | + glActiveTexture(GL_TEXTURE0); |
| 128 | + glBindTexture(GL_TEXTURE_CUBE_MAP, p_source_cubemap); |
| 129 | + glBindFramebuffer(GL_FRAMEBUFFER, p_dest_framebuffer); |
| 130 | + |
| 131 | + CubemapFilterShaderGLES3::ShaderVariant mode = CubemapFilterShaderGLES3::MODE_DEFAULT; |
| 132 | + |
| 133 | + if (p_layer == 0) { |
| 134 | + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); |
| 135 | + // Copy over base layer without filtering. |
| 136 | + mode = CubemapFilterShaderGLES3::MODE_COPY; |
| 137 | + } |
| 138 | + |
| 139 | + int size = p_source_size >> p_layer; |
| 140 | + glViewport(0, 0, size, size); |
| 141 | + glBindVertexArray(screen_triangle_array); |
| 142 | + |
| 143 | + bool success = cubemap_filter.shader.version_bind_shader(cubemap_filter.shader_version, mode); |
| 144 | + if (!success) { |
| 145 | + return; |
| 146 | + } |
| 147 | + |
| 148 | + if (p_layer > 0) { |
| 149 | + const uint32_t sample_counts[4] = { 1, ggx_samples / 4, ggx_samples / 2, ggx_samples }; |
| 150 | + uint32_t sample_count = sample_counts[MIN(3, p_layer)]; |
| 151 | + |
| 152 | + float roughness = float(p_layer) / (p_mipmap_count); |
| 153 | + float roughness4 = roughness * roughness; |
| 154 | + roughness4 *= roughness4; |
| 155 | + |
| 156 | + float solid_angle_texel = 4.0 * Math_PI / float(6 * size * size); |
| 157 | + |
| 158 | + LocalVector<float> sample_directions; |
| 159 | + sample_directions.resize(4 * sample_count); |
| 160 | + |
| 161 | + uint32_t index = 0; |
| 162 | + float weight = 0.0; |
| 163 | + for (uint32_t i = 0; i < sample_count; i++) { |
| 164 | + Vector2 xi = hammersley(i, sample_count); |
| 165 | + Vector3 dir = importance_sample_GGX(xi, roughness4); |
| 166 | + Vector3 light_vec = (2.0 * dir.z * dir - Vector3(0.0, 0.0, 1.0)); |
| 167 | + |
| 168 | + if (light_vec.z < 0.0) { |
| 169 | + continue; |
| 170 | + } |
| 171 | + |
| 172 | + sample_directions[index * 4] = light_vec.x; |
| 173 | + sample_directions[index * 4 + 1] = light_vec.y; |
| 174 | + sample_directions[index * 4 + 2] = light_vec.z; |
| 175 | + |
| 176 | + float D = distribution_GGX(dir.z, roughness4); |
| 177 | + float pdf = D * dir.z / (4.0 * dir.z) + 0.0001; |
| 178 | + |
| 179 | + float solid_angle_sample = 1.0 / (float(sample_count) * pdf + 0.0001); |
| 180 | + |
| 181 | + float mip_level = MAX(0.5 * log2(solid_angle_sample / solid_angle_texel) + float(MAX(1, p_layer - 3)), 1.0); |
| 182 | + |
| 183 | + sample_directions[index * 4 + 3] = mip_level; |
| 184 | + weight += light_vec.z; |
| 185 | + index++; |
| 186 | + } |
| 187 | + |
| 188 | + glUniform4fv(cubemap_filter.shader.version_get_uniform(CubemapFilterShaderGLES3::SAMPLE_DIRECTIONS_MIP, cubemap_filter.shader_version, mode), sample_count, sample_directions.ptr()); |
| 189 | + cubemap_filter.shader.version_set_uniform(CubemapFilterShaderGLES3::WEIGHT, weight, cubemap_filter.shader_version, mode); |
| 190 | + cubemap_filter.shader.version_set_uniform(CubemapFilterShaderGLES3::SAMPLE_COUNT, index, cubemap_filter.shader_version, mode); |
| 191 | + } |
| 192 | + |
| 193 | + for (int i = 0; i < 6; i++) { |
| 194 | + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, p_dest_cubemap, p_layer); |
| 195 | +#ifdef DEBUG_ENABLED |
| 196 | + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); |
| 197 | + if (status != GL_FRAMEBUFFER_COMPLETE) { |
| 198 | + WARN_PRINT("Could not bind sky radiance face: " + itos(i) + ", status: " + GLES3::TextureStorage::get_singleton()->get_framebuffer_error(status)); |
| 199 | + } |
| 200 | +#endif |
| 201 | + cubemap_filter.shader.version_set_uniform(CubemapFilterShaderGLES3::FACE_ID, i, cubemap_filter.shader_version, mode); |
| 202 | + |
| 203 | + glDrawArrays(GL_TRIANGLES, 0, 3); |
| 204 | + } |
| 205 | + glBindVertexArray(0); |
| 206 | + glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); |
| 207 | +} |
| 208 | + |
| 209 | +#endif // GLES3_ENABLED |
0 commit comments