/* This file is part of jpcsp. Jpcsp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Jpcsp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.graphics.RE.software; import static jpcsp.graphics.GeCommands.LIGHT_AMBIENT_DIFFUSE; import static jpcsp.graphics.GeCommands.LIGHT_DIRECTIONAL; import static jpcsp.graphics.GeCommands.LIGHT_POWER_DIFFUSE_SPECULAR; import static jpcsp.graphics.GeCommands.LIGHT_SPOT; import static jpcsp.graphics.RE.software.PixelColor.ZERO; import static jpcsp.graphics.RE.software.PixelColor.addBGR; import static jpcsp.graphics.RE.software.PixelColor.getAlpha; import static jpcsp.graphics.RE.software.PixelColor.getColor; import static jpcsp.graphics.RE.software.PixelColor.multiplyBGR; import static jpcsp.graphics.RE.software.PixelColor.multiplyComponent; import static jpcsp.graphics.RE.software.PixelColor.setAlpha; import static jpcsp.graphics.VideoEngine.NUM_LIGHTS; import static jpcsp.util.Utilities.clamp; import static jpcsp.util.Utilities.dot3; import static jpcsp.util.Utilities.length3; import static jpcsp.util.Utilities.max; import static jpcsp.util.Utilities.normalize3; import static jpcsp.util.Utilities.pow; import static jpcsp.util.Utilities.vectorMult34; import jpcsp.graphics.GeCommands; import jpcsp.graphics.GeContext.EnableDisableFlag; import jpcsp.graphics.VideoEngine; import org.apache.log4j.Logger; /** * @author gid15 * */ public final class Lighting { protected static final Logger log = VideoEngine.log; protected static final boolean disableLighting = false; private final int materialEmission; private final int ambient; private final int ambientAlpha; private final boolean[] lightEnabled = new boolean[NUM_LIGHTS]; private final boolean someLightsEnabled; private final float[][] ecLightPosition = new float[NUM_LIGHTS][3]; private final int[] lightKind = new int[NUM_LIGHTS]; private final int[] lightAmbientColor = new int[NUM_LIGHTS]; private final int[] lightDiffuseColor = new int[NUM_LIGHTS]; private final int[] lightSpecularColor = new int[NUM_LIGHTS]; private final float[] constantAttenuation = new float[NUM_LIGHTS]; private final float[] linearAttenuation = new float[NUM_LIGHTS]; private final float[] quadraticAttenuation = new float[NUM_LIGHTS]; private final float[] spotCutoff = new float[NUM_LIGHTS]; private final float[] spotCosCutoff = new float[NUM_LIGHTS]; private final float[][] ecSpotDirection = new float[NUM_LIGHTS][3]; private final float[] spotExponent = new float[NUM_LIGHTS]; private float shininess; private boolean separateSpecularColor; private boolean[] isSpotLight = new boolean[NUM_LIGHTS]; private boolean[] isDirectionalLight = new boolean[NUM_LIGHTS]; private boolean hasSomeNonDirectionalLight; private final float[] L = new float[3]; private final float[] H = new float[3]; private final float[] nL = new float[3]; private final float[] nH = new float[3]; private final float[] nSD = new float[3]; private final float[] Ve = new float[3]; private final float[] Ne = new float[3]; private int Al; private int Dl; private int Sl; private boolean hasNormal; public Lighting(float[] viewMatrix, float[] materialEmission, float[] ambient, EnableDisableFlag[] lightEnabled, float[][] lightPosition, int[] lightKind, int[] lightType, float[][] lightAmbientColor, float[][] lightDiffuseColor, float[][] lightSpecularColor, float[] constantAttenuation, float[] linearAttenuation, float[] quadraticAttenuation, float[] spotCutoff, float[] spotCosCutoff, float[][] spotDirection, float[] spotExponent, float shininess, int lightMode, boolean hasNormal) { this.materialEmission = getColor(materialEmission); this.ambient = getColor(ambient); this.ambientAlpha = getAlpha(this.ambient); this.shininess = shininess; this.separateSpecularColor = lightMode == GeCommands.LMODE_SEPARATE_SPECULAR_COLOR; this.hasNormal = hasNormal; boolean someLightsEnabled = false; boolean hasSomeNonDirectionalLight = false; for (int l = 0; l < NUM_LIGHTS; l++) { boolean isLightEnabled = lightEnabled[l].isEnabled() && !disableLighting; this.lightEnabled[l] = isLightEnabled; if (isLightEnabled) { someLightsEnabled |= isLightEnabled; this.lightKind[l] = lightKind[l]; this.lightAmbientColor[l] = getColor(lightAmbientColor[l]); this.lightDiffuseColor[l] = getColor(lightDiffuseColor[l]); this.lightSpecularColor[l] = getColor(lightSpecularColor[l]); this.constantAttenuation[l] = constantAttenuation[l]; this.linearAttenuation[l] = linearAttenuation[l]; this.quadraticAttenuation[l] = quadraticAttenuation[l]; this.spotCutoff[l] = spotCutoff[l]; this.spotCosCutoff[l] = spotCosCutoff[l]; this.spotExponent[l] = spotExponent[l]; isSpotLight[l] = lightType[l] == LIGHT_SPOT && spotCutoff[l] < 180.f; isDirectionalLight[l] = lightType[l] == LIGHT_DIRECTIONAL; hasSomeNonDirectionalLight |= !isDirectionalLight[l]; // The Model transformation does not apply to the light positions and directions. // Only apply the View transformation to map to the Eye Coordinate system. vectorMult34(ecLightPosition[l], viewMatrix, lightPosition[l]); vectorMult34(ecSpotDirection[l], viewMatrix, spotDirection[l]); } } this.someLightsEnabled = someLightsEnabled; this.hasSomeNonDirectionalLight = hasSomeNonDirectionalLight; } /** * This is the equivalent of the vertex shader implementation: * shader.vert: ComputeLight * * @param pixel * @param l */ private final void computeLight(int l) { boolean isDirectionalLight = this.isDirectionalLight[l]; if (!hasNormal && isDirectionalLight) { // A simple case... Al = addBGR(Al, lightAmbientColor[l]); return; } float att = 1.f; L[0] = ecLightPosition[l][0]; L[1] = ecLightPosition[l][1]; L[2] = ecLightPosition[l][2]; if (!isDirectionalLight) { L[0] -= Ve[0]; L[1] -= Ve[1]; L[2] -= Ve[2]; float d = length3(L); att = clamp(1.f / (constantAttenuation[l] + (linearAttenuation[l] + quadraticAttenuation[l] * d) * d), 0.f, 1.f); if (isSpotLight[l]) { normalize3(nSD, ecSpotDirection[l]); float spot = dot3(nSD, -L[0], -L[1], -L[2]); att *= spot < spotCosCutoff[l] ? 0.f : pow(spot, spotExponent[l]); } } if (hasNormal) { H[0] = L[0]; H[1] = L[1]; H[2] = L[2] + 1.f; normalize3(nL, L); float NdotL = max(dot3(nL, Ne), 0.f); normalize3(nH, H); float NdotH = max(dot3(nH, Ne), 0.f); float k = shininess; float Dk = lightKind[l] == LIGHT_POWER_DIFFUSE_SPECULAR ? max(pow(NdotL, k), 0.f) : NdotL; float Sk = lightKind[l] != LIGHT_AMBIENT_DIFFUSE ? max(pow(NdotH, k), 0.f) : 0.f; Dl = addBGR(Dl, multiplyBGR(lightDiffuseColor[l], att * Dk)); Sl = addBGR(Sl, multiplyBGR(lightSpecularColor[l], att * Sk)); } Al = addBGR(Al, multiplyBGR(lightAmbientColor[l], att)); } /** * Apply the PSP lighting model. * The implementation is based on the vertex shader implementation: * shader.vert: ApplyLighting and ComputeLight * * @param colors the primary and secondary colors will be returned in this object * @param pixel the current pixel values. The following values are used: * - material colors: materialAmbient, materialDiffuse, materialSpecular * - normal (in eye coordinates): normalizedNe * - vertex (in eye coordinates): Ve */ public final void applyLighting(PrimarySecondaryColors colors, PixelState pixel) { if (ambient == 0xFFFFFFFF && pixel.materialAmbient == 0xFFFFFFFF) { if (!someLightsEnabled || !separateSpecularColor || !hasNormal) { // A very simple case... colors.primaryColor = ambient; colors.secondaryColor = ZERO; return; } } int primary = materialEmission; int secondary = ZERO; Al = ambient; if (someLightsEnabled) { // Get the vector and normal in the eye coordinates // (only if they will be used by some light) if (hasSomeNonDirectionalLight) { pixel.getVe(Ve); } if (hasNormal) { pixel.getNormalizedNe(Ne); } Dl = ZERO; Sl = ZERO; for (int l = 0; l < NUM_LIGHTS; l++) { if (lightEnabled[l]) { computeLight(l); } } if (Dl != ZERO) { primary = addBGR(primary, multiplyBGR(Dl, pixel.materialDiffuse)); } if (Sl != ZERO) { if (separateSpecularColor) { secondary = multiplyBGR(Sl, pixel.materialSpecular); } else { primary = addBGR(primary, multiplyBGR(Sl, pixel.materialSpecular)); } } } if (Al != ZERO) { primary = addBGR(primary, multiplyBGR(Al, pixel.materialAmbient)); } primary = setAlpha(primary, multiplyComponent(ambientAlpha, getAlpha(pixel.materialAmbient))); colors.primaryColor = primary; colors.secondaryColor = secondary; } }