From 3f59c5a628feabd6ecf0b65553e1c4f3d9537f84 Mon Sep 17 00:00:00 2001 From: Fernando Sahmkow Date: Sun, 22 Jan 2017 18:21:46 -0500 Subject: [PATCH] Implemented Tangent Space Bump maps. --- .../renderer_opengl/gl_rasterizer.cpp | 28 +------- .../renderer_opengl/gl_rasterizer.h | 8 +-- .../renderer_opengl/gl_shader_gen.cpp | 71 ++++++++++++------- 3 files changed, 48 insertions(+), 59 deletions(-) diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 5a306a5c8..bc9b9c013 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -139,34 +139,12 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) { RasterizerOpenGL::~RasterizerOpenGL() {} -/** - * This is a helper function to resolve an issue with opposite quaternions being interpolated by - * OpenGL. See below for a detailed description of this issue (yuriks): - * - * For any rotation, there are two quaternions Q, and -Q, that represent the same rotation. If you - * interpolate two quaternions that are opposite, instead of going from one rotation to another - * using the shortest path, you'll go around the longest path. You can test if two quaternions are - * opposite by checking if Dot(Q1, W2) < 0. In that case, you can flip either of them, therefore - * making Dot(-Q1, W2) positive. - * - * NOTE: This solution corrects this issue per-vertex before passing the quaternions to OpenGL. This - * should be correct for nearly all cases, however a more correct implementation (but less trivial - * and perhaps unnecessary) would be to handle this per-fragment, by interpolating the quaternions - * manually using two Lerps, and doing this correction before each Lerp. - */ -static bool AreQuaternionsOpposite(Math::Vec4 qa, Math::Vec4 qb) { - Math::Vec4f a{qa.x.ToFloat32(), qa.y.ToFloat32(), qa.z.ToFloat32(), qa.w.ToFloat32()}; - Math::Vec4f b{qb.x.ToFloat32(), qb.y.ToFloat32(), qb.z.ToFloat32(), qb.w.ToFloat32()}; - - return (Math::Dot(a, b) < 0.f); -} - void RasterizerOpenGL::AddTriangle(const Pica::Shader::OutputVertex& v0, const Pica::Shader::OutputVertex& v1, const Pica::Shader::OutputVertex& v2) { - vertex_batch.emplace_back(v0, false); - vertex_batch.emplace_back(v1, AreQuaternionsOpposite(v0.quat, v1.quat)); - vertex_batch.emplace_back(v2, AreQuaternionsOpposite(v0.quat, v2.quat)); + vertex_batch.emplace_back(v0); + vertex_batch.emplace_back(v1); + vertex_batch.emplace_back(v2); } void RasterizerOpenGL::DrawTriangles() { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index e1a9cb361..9b1611921 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -271,7 +271,7 @@ private: /// Structure that the hardware rendered vertices are composed of struct HardwareVertex { - HardwareVertex(const Pica::Shader::OutputVertex& v, bool flip_quaternion) { + HardwareVertex(const Pica::Shader::OutputVertex& v) { position[0] = v.pos.x.ToFloat32(); position[1] = v.pos.y.ToFloat32(); position[2] = v.pos.z.ToFloat32(); @@ -294,12 +294,6 @@ private: view[0] = v.view.x.ToFloat32(); view[1] = v.view.y.ToFloat32(); view[2] = v.view.z.ToFloat32(); - - if (flip_quaternion) { - for (float& x : normquat) { - x = -x; - } - } } GLfloat position[4]; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 4c4f98ac9..da884dc67 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -363,8 +363,13 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { "vec3 light_vector = vec3(0.0);\n" "vec3 refl_value = vec3(0.0);\n"; + out += "vec3 f_normal = normalize(normal);\n" + "vec3 f_tangent = normalize(tangent);\n"; // Compute fragment normals if (lighting.bump_mode == Pica::Regs::LightingBumpMode::NormalMap) { + out += "f_tangent = normalize(f_tangent - dot(f_tangent, f_normal) * f_normal);\n" + "vec3 f_bitangent = normalize(cross(f_tangent,f_normal));\n" + "mat3 TBN = mat3(f_tangent, f_bitangent, f_normal);\n"; // Bump mapping is enabled using a normal map, read perturbation vector from the selected // texture std::string bump_selector = std::to_string(lighting.bump_selector); @@ -373,23 +378,27 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { // Recompute Z-component of perturbation if 'renorm' is enabled, this provides a higher // precision result - if (lighting.bump_renorm) { - std::string val = - "(1.0 - (surface_normal.x*surface_normal.x + surface_normal.y*surface_normal.y))"; - out += "surface_normal.z = sqrt(max(" + val + ", 0.0));\n"; - } + if (lighting.bump_renorm) + out += "surface_normal.z = normal_recalculate_ZComponent(surface_normal);\n"; + out += "f_normal = normalize(TBN * surface_normal);\n"; } else if (lighting.bump_mode == Pica::Regs::LightingBumpMode::TangentMap) { - // Bump mapping is enabled using a tangent map - LOG_CRITICAL(HW_GPU, "unimplemented bump mapping mode (tangent mapping)"); - UNIMPLEMENTED(); - } else { - // No bump mapping - surface local normal is just a unit normal - out += "vec3 surface_normal = vec3(0.0, 0.0, 1.0);\n"; - } + // Same as above but the TBN matrix is transposed + // the light direction should be in view space. + out += "f_tangent = normalize(f_tangent - dot(f_tangent, f_normal) * f_normal);\n" + "vec3 f_bitangent = normalize(cross(f_tangent,f_normal));\n" + "mat3 TBN = transpose(mat3(f_tangent, f_bitangent, f_normal));\n"; + // Bump mapping is enabled using a normal map, read perturbation vector from the selected + // texture + std::string bump_selector = std::to_string(lighting.bump_selector); + out += "vec3 surface_normal = 2.0 * texture(tex[" + bump_selector + "], texcoord[" + + bump_selector + "]).rgb - 1.0;\n"; - // Rotate the surface-local normal by the interpolated normal quaternion to convert it to - // eyespace - out += "vec3 normal = normalize(quaternion_rotate(normquat, surface_normal));\n"; + // Recompute Z-component of perturbation if 'renorm' is enabled, this provides a higher + // precision result + if (lighting.bump_renorm) + out += "surface_normal.z = normal_recalculate_ZComponent(surface_normal);\n"; + out += "f_normal = normalize(TBN * surface_normal);\n"; + } // Gets the index into the specified lookup table for specular lighting auto GetLutIndex = [&lighting](unsigned light_num, Regs::LightingLutInput input, bool abs) { @@ -397,7 +406,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { std::string index; switch (input) { case Regs::LightingLutInput::NH: - index = "dot(normal, " + half_angle + ")"; + index = "dot(f_normal, " + half_angle + ")"; break; case Regs::LightingLutInput::VH: @@ -405,11 +414,11 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { break; case Regs::LightingLutInput::NV: - index = std::string("dot(normal, normalize(view))"); + index = std::string("dot(f_normal, normalize(view))"); break; case Regs::LightingLutInput::LN: - index = std::string("dot(light_vector, normal)"); + index = std::string("dot(light_vector, f_normal)"); break; default: @@ -451,8 +460,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { // Compute dot product of light_vector and normal, adjust if lighting is one-sided or // two-sided std::string dot_product = light_config.two_sided_diffuse - ? "abs(dot(light_vector, normal))" - : "max(dot(light_vector, normal), 0.0)"; + ? "abs(dot(light_vector, f_normal))" + : "max(dot(light_vector, f_normal), 0.0)"; // If enabled, compute distance attenuation value std::string dist_atten = "1.0"; @@ -467,7 +476,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { // If enabled, clamp specular component if lighting result is negative std::string clamp_highlights = - lighting.clamp_highlights ? "(dot(light_vector, normal) <= 0.0 ? 0.0 : 1.0)" : "1.0"; + lighting.clamp_highlights ? "(dot(light_vector, f_normal) <= 0.0 ? 0.0 : 1.0)" : "1.0"; // Specular 0 component std::string d0_lut_value = "1.0"; @@ -585,7 +594,8 @@ std::string GenerateFragmentShader(const PicaShaderConfig& config) { in vec4 primary_color; in vec2 texcoord[3]; in float texcoord0_w; -in vec4 normquat; +in vec3 normal; +in vec3 tangent; in vec3 view; in vec4 gl_FragCoord; @@ -622,9 +632,9 @@ uniform sampler2D tex[3]; uniform sampler1D lut[6]; uniform usampler1D fog_lut; -// Rotate the vector v by the quaternion q -vec3 quaternion_rotate(vec4 q, vec3 v) { - return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v); + +float normal_recalculate_ZComponent(vec3 v) { + return sqrt(max(0.0,1.0 - v.x*v.x - v.y*v.y)); } void main() { @@ -726,16 +736,23 @@ std::string GenerateVertexShader() { out vec4 primary_color; out vec2 texcoord[3]; out float texcoord0_w; -out vec4 normquat; +out vec3 normal; +out vec3 tangent; out vec3 view; +// Rotate the vector v by the quaternion q +vec3 quaternion_rotate(vec4 q, vec3 v) { + return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v); +} + void main() { primary_color = vert_color; texcoord[0] = vert_texcoord0; texcoord[1] = vert_texcoord1; texcoord[2] = vert_texcoord2; texcoord0_w = vert_texcoord0_w; - normquat = vert_normquat; + normal = normalize(quaternion_rotate(vert_normquat,vec3(0.0,0.0,1.0))); + tangent = normalize(quaternion_rotate(vert_normquat,vec3(1.0,0.0,0.0))); view = vert_view; gl_Position = vec4(vert_position.x, vert_position.y, -vert_position.z, vert_position.w); }