mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-25 22:00:15 +00:00
Implemented Tangent Space Bump maps.
This commit is contained in:
parent
25c5aa5869
commit
3f59c5a628
@ -139,34 +139,12 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
|
|||||||
|
|
||||||
RasterizerOpenGL::~RasterizerOpenGL() {}
|
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<Pica::float24> qa, Math::Vec4<Pica::float24> 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,
|
void RasterizerOpenGL::AddTriangle(const Pica::Shader::OutputVertex& v0,
|
||||||
const Pica::Shader::OutputVertex& v1,
|
const Pica::Shader::OutputVertex& v1,
|
||||||
const Pica::Shader::OutputVertex& v2) {
|
const Pica::Shader::OutputVertex& v2) {
|
||||||
vertex_batch.emplace_back(v0, false);
|
vertex_batch.emplace_back(v0);
|
||||||
vertex_batch.emplace_back(v1, AreQuaternionsOpposite(v0.quat, v1.quat));
|
vertex_batch.emplace_back(v1);
|
||||||
vertex_batch.emplace_back(v2, AreQuaternionsOpposite(v0.quat, v2.quat));
|
vertex_batch.emplace_back(v2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RasterizerOpenGL::DrawTriangles() {
|
void RasterizerOpenGL::DrawTriangles() {
|
||||||
|
@ -271,7 +271,7 @@ private:
|
|||||||
|
|
||||||
/// Structure that the hardware rendered vertices are composed of
|
/// Structure that the hardware rendered vertices are composed of
|
||||||
struct HardwareVertex {
|
struct HardwareVertex {
|
||||||
HardwareVertex(const Pica::Shader::OutputVertex& v, bool flip_quaternion) {
|
HardwareVertex(const Pica::Shader::OutputVertex& v) {
|
||||||
position[0] = v.pos.x.ToFloat32();
|
position[0] = v.pos.x.ToFloat32();
|
||||||
position[1] = v.pos.y.ToFloat32();
|
position[1] = v.pos.y.ToFloat32();
|
||||||
position[2] = v.pos.z.ToFloat32();
|
position[2] = v.pos.z.ToFloat32();
|
||||||
@ -294,12 +294,6 @@ private:
|
|||||||
view[0] = v.view.x.ToFloat32();
|
view[0] = v.view.x.ToFloat32();
|
||||||
view[1] = v.view.y.ToFloat32();
|
view[1] = v.view.y.ToFloat32();
|
||||||
view[2] = v.view.z.ToFloat32();
|
view[2] = v.view.z.ToFloat32();
|
||||||
|
|
||||||
if (flip_quaternion) {
|
|
||||||
for (float& x : normquat) {
|
|
||||||
x = -x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GLfloat position[4];
|
GLfloat position[4];
|
||||||
|
@ -363,8 +363,13 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
|
|||||||
"vec3 light_vector = vec3(0.0);\n"
|
"vec3 light_vector = vec3(0.0);\n"
|
||||||
"vec3 refl_value = 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
|
// Compute fragment normals
|
||||||
if (lighting.bump_mode == Pica::Regs::LightingBumpMode::NormalMap) {
|
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
|
// Bump mapping is enabled using a normal map, read perturbation vector from the selected
|
||||||
// texture
|
// texture
|
||||||
std::string bump_selector = std::to_string(lighting.bump_selector);
|
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
|
// Recompute Z-component of perturbation if 'renorm' is enabled, this provides a higher
|
||||||
// precision result
|
// precision result
|
||||||
if (lighting.bump_renorm) {
|
if (lighting.bump_renorm)
|
||||||
std::string val =
|
out += "surface_normal.z = normal_recalculate_ZComponent(surface_normal);\n";
|
||||||
"(1.0 - (surface_normal.x*surface_normal.x + surface_normal.y*surface_normal.y))";
|
out += "f_normal = normalize(TBN * surface_normal);\n";
|
||||||
out += "surface_normal.z = sqrt(max(" + val + ", 0.0));\n";
|
|
||||||
}
|
|
||||||
} else if (lighting.bump_mode == Pica::Regs::LightingBumpMode::TangentMap) {
|
} else if (lighting.bump_mode == Pica::Regs::LightingBumpMode::TangentMap) {
|
||||||
// Bump mapping is enabled using a tangent map
|
// Same as above but the TBN matrix is transposed
|
||||||
LOG_CRITICAL(HW_GPU, "unimplemented bump mapping mode (tangent mapping)");
|
// the light direction should be in view space.
|
||||||
UNIMPLEMENTED();
|
out += "f_tangent = normalize(f_tangent - dot(f_tangent, f_normal) * f_normal);\n"
|
||||||
} else {
|
"vec3 f_bitangent = normalize(cross(f_tangent,f_normal));\n"
|
||||||
// No bump mapping - surface local normal is just a unit normal
|
"mat3 TBN = transpose(mat3(f_tangent, f_bitangent, f_normal));\n";
|
||||||
out += "vec3 surface_normal = vec3(0.0, 0.0, 1.0);\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
|
// Recompute Z-component of perturbation if 'renorm' is enabled, this provides a higher
|
||||||
// eyespace
|
// precision result
|
||||||
out += "vec3 normal = normalize(quaternion_rotate(normquat, surface_normal));\n";
|
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
|
// Gets the index into the specified lookup table for specular lighting
|
||||||
auto GetLutIndex = [&lighting](unsigned light_num, Regs::LightingLutInput input, bool abs) {
|
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;
|
std::string index;
|
||||||
switch (input) {
|
switch (input) {
|
||||||
case Regs::LightingLutInput::NH:
|
case Regs::LightingLutInput::NH:
|
||||||
index = "dot(normal, " + half_angle + ")";
|
index = "dot(f_normal, " + half_angle + ")";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Regs::LightingLutInput::VH:
|
case Regs::LightingLutInput::VH:
|
||||||
@ -405,11 +414,11 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Regs::LightingLutInput::NV:
|
case Regs::LightingLutInput::NV:
|
||||||
index = std::string("dot(normal, normalize(view))");
|
index = std::string("dot(f_normal, normalize(view))");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Regs::LightingLutInput::LN:
|
case Regs::LightingLutInput::LN:
|
||||||
index = std::string("dot(light_vector, normal)");
|
index = std::string("dot(light_vector, f_normal)");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
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
|
// Compute dot product of light_vector and normal, adjust if lighting is one-sided or
|
||||||
// two-sided
|
// two-sided
|
||||||
std::string dot_product = light_config.two_sided_diffuse
|
std::string dot_product = light_config.two_sided_diffuse
|
||||||
? "abs(dot(light_vector, normal))"
|
? "abs(dot(light_vector, f_normal))"
|
||||||
: "max(dot(light_vector, normal), 0.0)";
|
: "max(dot(light_vector, f_normal), 0.0)";
|
||||||
|
|
||||||
// If enabled, compute distance attenuation value
|
// If enabled, compute distance attenuation value
|
||||||
std::string dist_atten = "1.0";
|
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
|
// If enabled, clamp specular component if lighting result is negative
|
||||||
std::string clamp_highlights =
|
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
|
// Specular 0 component
|
||||||
std::string d0_lut_value = "1.0";
|
std::string d0_lut_value = "1.0";
|
||||||
@ -585,7 +594,8 @@ std::string GenerateFragmentShader(const PicaShaderConfig& config) {
|
|||||||
in vec4 primary_color;
|
in vec4 primary_color;
|
||||||
in vec2 texcoord[3];
|
in vec2 texcoord[3];
|
||||||
in float texcoord0_w;
|
in float texcoord0_w;
|
||||||
in vec4 normquat;
|
in vec3 normal;
|
||||||
|
in vec3 tangent;
|
||||||
in vec3 view;
|
in vec3 view;
|
||||||
|
|
||||||
in vec4 gl_FragCoord;
|
in vec4 gl_FragCoord;
|
||||||
@ -622,9 +632,9 @@ uniform sampler2D tex[3];
|
|||||||
uniform sampler1D lut[6];
|
uniform sampler1D lut[6];
|
||||||
uniform usampler1D fog_lut;
|
uniform usampler1D fog_lut;
|
||||||
|
|
||||||
// Rotate the vector v by the quaternion q
|
|
||||||
vec3 quaternion_rotate(vec4 q, vec3 v) {
|
float normal_recalculate_ZComponent(vec3 v) {
|
||||||
return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v);
|
return sqrt(max(0.0,1.0 - v.x*v.x - v.y*v.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@ -726,16 +736,23 @@ std::string GenerateVertexShader() {
|
|||||||
out vec4 primary_color;
|
out vec4 primary_color;
|
||||||
out vec2 texcoord[3];
|
out vec2 texcoord[3];
|
||||||
out float texcoord0_w;
|
out float texcoord0_w;
|
||||||
out vec4 normquat;
|
out vec3 normal;
|
||||||
|
out vec3 tangent;
|
||||||
out vec3 view;
|
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() {
|
void main() {
|
||||||
primary_color = vert_color;
|
primary_color = vert_color;
|
||||||
texcoord[0] = vert_texcoord0;
|
texcoord[0] = vert_texcoord0;
|
||||||
texcoord[1] = vert_texcoord1;
|
texcoord[1] = vert_texcoord1;
|
||||||
texcoord[2] = vert_texcoord2;
|
texcoord[2] = vert_texcoord2;
|
||||||
texcoord0_w = vert_texcoord0_w;
|
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;
|
view = vert_view;
|
||||||
gl_Position = vec4(vert_position.x, vert_position.y, -vert_position.z, vert_position.w);
|
gl_Position = vec4(vert_position.x, vert_position.y, -vert_position.z, vert_position.w);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user