From bf89870437ebb0d983cfc20c3ac0490169f59f44 Mon Sep 17 00:00:00 2001 From: bunnei Date: Sat, 14 Nov 2015 23:23:08 -0500 Subject: [PATCH] renderer_opengl: Initial implementation of basic specular lighting. --- src/video_core/pica.h | 8 +- .../renderer_opengl/gl_rasterizer.cpp | 68 +++++++++++++++ .../renderer_opengl/gl_rasterizer.h | 20 +++++ .../renderer_opengl/gl_shader_gen.cpp | 82 ++++++++++++++++--- 4 files changed, 165 insertions(+), 13 deletions(-) diff --git a/src/video_core/pica.h b/src/video_core/pica.h index aad9effdc..c63d87a36 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -659,6 +659,8 @@ struct Regs { enum class LightingLutInput { NH = 0, // Cosine of the angle between the normal and half-angle vectors + VH = 1, // Cosine of the angle between the view and half-angle vectors + NV = 2, // Cosine of the angle between the normal and the view vector LN = 3, // Cosine of the angle between the light and the normal vectors }; @@ -709,7 +711,11 @@ struct Regs { LightColor global_ambient; // emission + (material.ambient * lighting.ambient) INSERT_PADDING_WORDS(0x1); BitField<0, 3, u32> src_num; // number of enabled lights - 1 - INSERT_PADDING_WORDS(0x1); + + union { + BitField< 4, 4, u32> config; + BitField<27, 1, u32> clamp_highlights; + } light_env; union { // Each bit specifies whether distance attenuation should be applied for the diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 80693fa29..c6fb37c53 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -297,6 +297,58 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { SyncCombinerColor(); break; + // Fragment lighting specular 0 color + case PICA_REG_INDEX_WORKAROUND(lighting.light[0].specular_0, 0x140 + 0 * 0x10): + SyncLightSpecular0(0); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[1].specular_0, 0x140 + 1 * 0x10): + SyncLightSpecular0(1); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[2].specular_0, 0x140 + 2 * 0x10): + SyncLightSpecular0(2); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[3].specular_0, 0x140 + 3 * 0x10): + SyncLightSpecular0(3); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[4].specular_0, 0x140 + 4 * 0x10): + SyncLightSpecular0(4); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[5].specular_0, 0x140 + 5 * 0x10): + SyncLightSpecular0(5); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[6].specular_0, 0x140 + 6 * 0x10): + SyncLightSpecular0(6); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[7].specular_0, 0x140 + 7 * 0x10): + SyncLightSpecular0(7); + break; + + // Fragment lighting specular 1 color + case PICA_REG_INDEX_WORKAROUND(lighting.light[0].specular_1, 0x141 + 0 * 0x10): + SyncLightSpecular1(0); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[1].specular_1, 0x141 + 1 * 0x10): + SyncLightSpecular1(1); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[2].specular_1, 0x141 + 2 * 0x10): + SyncLightSpecular1(2); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[3].specular_1, 0x141 + 3 * 0x10): + SyncLightSpecular1(3); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[4].specular_1, 0x141 + 4 * 0x10): + SyncLightSpecular1(4); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[5].specular_1, 0x141 + 5 * 0x10): + SyncLightSpecular1(5); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[6].specular_1, 0x141 + 6 * 0x10): + SyncLightSpecular1(6); + break; + case PICA_REG_INDEX_WORKAROUND(lighting.light[7].specular_1, 0x141 + 7 * 0x10): + SyncLightSpecular1(7); + break; + // Fragment lighting diffuse color case PICA_REG_INDEX_WORKAROUND(lighting.light[0].diffuse, 0x142 + 0 * 0x10): SyncLightDiffuse(0); @@ -835,6 +887,22 @@ void RasterizerOpenGL::SyncLightingLUT(unsigned lut_index) { } } +void RasterizerOpenGL::SyncLightSpecular0(int light_index) { + auto color = PicaToGL::LightColor(Pica::g_state.regs.lighting.light[light_index].specular_0); + if (color != uniform_block_data.data.light_src[light_index].specular_0) { + uniform_block_data.data.light_src[light_index].specular_0 = color; + uniform_block_data.dirty = true; + } +} + +void RasterizerOpenGL::SyncLightSpecular1(int light_index) { + auto color = PicaToGL::LightColor(Pica::g_state.regs.lighting.light[light_index].specular_1); + if (color != uniform_block_data.data.light_src[light_index].specular_1) { + uniform_block_data.data.light_src[light_index].specular_1 = color; + uniform_block_data.dirty = true; + } +} + void RasterizerOpenGL::SyncLightDiffuse(int light_index) { auto color = PicaToGL::LightColor(Pica::g_state.regs.lighting.light[light_index].diffuse); if (color != uniform_block_data.data.light_src[light_index].diffuse) { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index ba0b05802..9e93b8b2f 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -87,6 +87,10 @@ struct PicaShaderConfig { res.light_src[light_index].dist_atten_scale = Pica::float20::FromRawFloat20(light.dist_atten_scale).ToFloat32(); } + res.lighting_lut.d0_abs = (regs.lighting.abs_lut_input.d0 == 0); + res.lighting_lut.d0_type = (Pica::Regs::LightingLutInput)regs.lighting.lut_input.d0.Value(); + res.clamp_highlights = regs.lighting.light_env.clamp_highlights; + return res; } @@ -118,6 +122,12 @@ struct PicaShaderConfig { bool lighting_enabled = false; unsigned num_lights = 0; + bool clamp_highlights = false; + + struct { + bool d0_abs = false; + Pica::Regs::LightingLutInput d0_type = Pica::Regs::LightingLutInput::NH; + } lighting_lut; }; }; @@ -231,6 +241,10 @@ private: }; struct LightSrc { + std::array specular_0; + INSERT_PADDING_WORDS(1); + std::array specular_1; + INSERT_PADDING_WORDS(1); std::array diffuse; INSERT_PADDING_WORDS(1); std::array ambient; @@ -316,6 +330,12 @@ private: /// Syncs the specified light's position to match the PICA register void SyncLightPosition(int light_index); + /// Syncs the specified light's specular 0 color to match the PICA register + void SyncLightSpecular0(int light_index); + + /// Syncs the specified light's specular 1 color to match the PICA register + void SyncLightSpecular1(int light_index); + /// Syncs the remaining OpenGL drawing state to match the current PICA state void SyncDrawState(); diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index cf99cff76..abcc89f1d 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -35,8 +35,7 @@ static void AppendSource(std::string& out, TevStageConfig::Source source, out += "primary_fragment_color"; break; case Source::SecondaryFragmentColor: - // HACK: Until we implement fragment lighting, use zero - out += "vec4(0.0)"; + out += "secondary_fragment_color"; break; case Source::Texture0: out += "texture(tex[0], texcoord[0])"; @@ -334,6 +333,8 @@ in vec3 view; out vec4 color; struct LightSrc { + vec3 specular_0; + vec3 specular_1; vec3 diffuse; vec3 ambient; vec3 position; @@ -358,6 +359,7 @@ uniform sampler2D tex[3]; void main() { vec4 primary_fragment_color = vec4(0.0); +vec4 secondary_fragment_color = vec4(0.0); )"; if (config.lighting_enabled) { @@ -367,41 +369,97 @@ vec4 primary_fragment_color = vec4(0.0); out += " 1.f - 2.f*(normquat.x*normquat.x + normquat.y*normquat.y)));\n"; out += "vec4 secondary_color = vec4(0.0);\n"; out += "vec3 diffuse_sum = vec3(0.0);\n"; + out += "vec3 specular_sum = vec3(0.0);\n"; out += "vec3 fragment_position = -view;\n"; + out += "vec3 light_vector = vec3(0.0);\n"; + out += "float dist_atten = 1.0;\n"; + + // Gets the index into the specified lookup table for specular lighting + auto GetLutIndex = [&](unsigned light_num, Regs::LightingLutInput input, bool abs) { + const std::string half_angle = "normalize(view + light_vector)"; + std::string index; + switch (input) { + case Regs::LightingLutInput::NH: + index = "dot(normal, " + half_angle + ")"; + break; + + case Regs::LightingLutInput::VH: + index = std::string("dot(view, " + half_angle + ")"); + break; + + case Regs::LightingLutInput::NV: + index = std::string("dot(normal, view)"); + break; + + case Regs::LightingLutInput::LN: + index = std::string("dot(light_vector, normal)"); + break; + + default: + LOG_CRITICAL(HW_GPU, "Unknown lighting LUT input %d\n", (int)input); + UNIMPLEMENTED(); + break; + } + + if (abs) { + // In the range of [ 0.f, 1.f] + index = config.light_src[light_num].two_sided_diffuse ? "abs(" + index + ")" : "max(" + index + ", 0.f)"; + return "clamp(int(" + index + " * 256.0), 0, 255)"; + } else { + // In the range of [-1.f, 1.f] + index = "clamp(" + index + ", -1.0, 1.0)"; + return std::string("uint(int(" + index + " * 127.f) & 0xff)"); + } + + return std::string(); + }; for (unsigned light_index = 0; light_index < config.num_lights; ++light_index) { unsigned num = config.light_src[light_index].num; std::string light_src = "light_src[" + std::to_string(num) + "]"; - std::string light_vector; if (config.light_src[light_index].directional) - light_vector = "normalize(-" + light_src + ".position)"; + out += "light_vector = normalize(-" + light_src + ".position);\n"; else - light_vector = "normalize(" + light_src + ".position - fragment_position)"; + out += "light_vector = normalize(" + light_src + ".position - fragment_position);\n"; std::string dot_product; if (config.light_src[light_index].two_sided_diffuse) - dot_product = "abs(dot(" + light_vector + ", normal))"; + dot_product = "abs(dot(light_vector, normal))"; else - dot_product = "max(dot(" + light_vector + ", normal), 0.0)"; + dot_product = "max(dot(light_vector, normal), 0.0)"; - std::string dist_atten = "1.0"; + // Compute distance attenuation value + out += "dist_atten = 1.0;\n"; if (config.light_src[light_index].dist_atten_enabled) { std::string scale = std::to_string(config.light_src[light_index].dist_atten_scale); std::string bias = std::to_string(config.light_src[light_index].dist_atten_bias); std::string lut_index = "(" + scale + " * length(fragment_position - " + light_src + ".position) + " + bias + ")"; std::string clamped_lut_index = "((clamp(int(" + lut_index + " * 256.0), 0, 255)))"; - unsigned lut_num = ((unsigned)Regs::LightingSampler::DistanceAttenuation + num); - - dist_atten = "lighting_lut_" + std::to_string(lut_num /4) + "[" + clamped_lut_index + "][" + std::to_string(lut_num & 3) + "]"; + const unsigned lut_num = ((unsigned)Regs::LightingSampler::DistanceAttenuation + num); + out += "dist_atten = lighting_lut_" + std::to_string(lut_num / 4) + "[" + clamped_lut_index + "][" + std::to_string(lut_num & 3) + "];\n"; } - out += "diffuse_sum += ((light_src[" + std::to_string(num) + "].diffuse * " + dot_product + ") + light_src[" + std::to_string(num) + "].ambient) * " + dist_atten + ";\n"; + // Compute primary fragment color (diffuse lighting) function + out += "diffuse_sum += ((light_src[" + std::to_string(num) + "].diffuse * " + dot_product + ") + light_src[" + std::to_string(num) + "].ambient) * dist_atten;\n"; + + // Compute secondary fragment color (specular lighting) function + std::string clamped_lut_index = GetLutIndex(num, config.lighting_lut.d0_type, config.lighting_lut.d0_abs); + const unsigned lut_num = (unsigned)Regs::LightingSampler::Distribution0; + std::string lut_lookup = "lighting_lut_" + std::to_string(lut_num / 4) + "[" + clamped_lut_index + "][" + std::to_string(lut_num & 3) + "]"; + + out += "specular_sum += (" + lut_lookup + " * light_src[" + std::to_string(num) + "].specular_0 * dist_atten);\n"; + } + + out += "float clamp_highlights = 1.0;\n"; + if (config.clamp_highlights) { + out += "if (dot(light_vector, normal) <= 0.0) clamp_highlights = 0.0;\n"; } out += "diffuse_sum += lighting_global_ambient;\n"; out += "primary_fragment_color = vec4(clamp(diffuse_sum, vec3(0.0), vec3(1.0)), 1.0);\n"; + out += "secondary_fragment_color = vec4(clamp(clamp_highlights * specular_sum, vec3(0.0), vec3(1.0)), 1.0);\n"; } // Do not do any sort of processing if it's obvious we're not going to pass the alpha test