// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "std3d.h" #include "nel/3d/meshvp_per_pixel_light.h" #include "nel/3d/mesh_base_instance.h" #include "nel/3d/driver.h" #include "nel/3d/scene.h" #include "nel/3d/render_trav.h" #include "nel/3d/render_trav.h" #include "nel/3d/vertex_program_parse.h" #include namespace NL3D { NLMISC::CSmartPtr CMeshVPPerPixelLight::_VertexProgram[NumVp]; // *************************************************************************** // Light VP fragment constants start at 24 static const uint VPLightConstantStart = 24; // *************************************************************************** // *************************************************************************** ///////////////// // omni lights // ///////////////// /** We store the first tangent vector of the tangent space in v[8]. * The third vector can be computed using a cross product. * We assume that normal and tangent are normalized. * The position of light must be expressed in object space, and is stored in c[4] */ // omni light + specular, no normalization static const char* PPLightingVPCodeBegin = "!!VP1.0 \n\ #compute B = N ^ T \n\ MOV R6, v[2]; \n\ MUL R1, R6.yzxw, v[9].zxyw; \n\ MAD R1, v[9].yzxw, -R6.zxyw, R1; \n\ #vector in tangent space = [ T B N ] * L \n\ ADD R2, c[4], -v[0]; # compute L \n\ DP3 R3, R2, R2; # get L normalized \n\ RSQ R3, R3.x; \n\ MUL R2, R3, R2; \n\ DP3 o[TEX0].x, v[9], R2; # get x of light vector in tangent space \n\ DP3 o[TEX0].y, R1, R2; # get y \n\ DP3 o[TEX0].z, R6, R2; # get z \n\ #specular part \n\ ADD R3, c[5], - v[0]; # compute V (return to eye) \n\ #compute inverse norm of V \n\ DP3 R4, R3, R3; \n\ RSQ R4, R4.x; \n\ #we normalize V and add it to L \n\ MAD R2, R4, R3, R2; #H in R1 \n\ \n\ #normalize H \n\ DP3 R3, R2, R2; \n\ RSQ R3, R3.x; \n\ MUL R2, R3, R2; \n\ #compute H in tangent space \n\ DP3 o[TEX2].x, v[9], R2; \n\ DP3 o[TEX2].y, R1, R2; \n\ DP3 o[TEX2].z, R6, R2; \n\ # Position in R5 for additionnal lighting \n\ MOV R5, v[0]; \n\ # Normal is in R6 for additionnal lighting \n\ "; /// The same as above, but for skin / MRM : This normalize the tangent basis. static const char* PPLightingVPNormalizeCodeBegin = "!!VP1.0 \n\ #normalize the normal \n\ DP3 R1, v[2], v[2]; \n\ RSQ R1, R1.x; \n\ MUL R6, v[2], R1; \n\ \n\ #normalize the second vector \n\ DP3 R1, R6, v[9]; \n\ MAD R1, R6, -R1, v[9]; #subtract the normal component \n\ DP3 R2, R1, R1; \n\ RSQ R2, R2.x; \n\ MUL R5, R1, R2; #second basis vector in R5 \n\ #compute B = N ^ T \n\ MUL R1, R6.yzxw, R5.zxyw; \n\ MAD R1, R5.yzxw, -R6.zxyw, R1; #third basis vector in R1 \n\ \n\ #vector in tangent space = [ T B N ] * L \n\ ADD R2, c[4], -v[0]; # compute L \n\ DP3 R3, R2, R2; # get L normalized \n\ RSQ R3, R3.x; \n\ MUL R2, R3, R2; \n\ DP3 o[TEX0].x, R5, R2; # get x of light vector in tangent space \n\ DP3 o[TEX0].y, R1, R2; # get y \n\ DP3 o[TEX0].z, R6, R2; # get z \n\ \n\ #specular part \n\ ADD R3, c[5], - v[0]; # compute V (return to eye) \n\ #compute inverse norm of V \n\ DP3 R4, R3, R3; \n\ RSQ R4, R4.x; \n\ #we normalize V and add it to L \n\ MAD R2, R4, R3, R2; #H in R1 \n\ \n\ #normalize H \n\ DP3 R3, R2, R2; \n\ RSQ R3, R3.x; \n\ MUL R2, R3, R2; \n\ #compute H in tangent space \n\ DP3 o[TEX2].x, R5, R2; \n\ DP3 o[TEX2].y, R1, R2; \n\ DP3 o[TEX2].z, R6, R2; \n\ # Position in R5 for additionnal lighting \n\ MOV R5, v[0]; \n\ # Normal is in R6 for additionnal lighting \n\ "; // Omni light, no specular static const char* PPLightingNoSpecVPCodeBegin = "!!VP1.0 \n\ #compute B = N ^ T \n\ MOV R6, v[2]; \n\ MUL R1, R6.yzxw, v[9].zxyw; \n\ MAD R1, v[9].yzxw, -R6.zxyw, R1; \n\ \n\ #vector in tangent space = [ T B N ] * L \n\ ADD R2, c[4], -v[0]; # compute L \n\ DP3 R3, R2, R2; # get L normalized \n\ RSQ R3, R3.x; \n\ MUL R2, R3, R2; \n\ DP3 o[TEX0].x, v[9], R2; # get x of light vector in tangent space \n\ DP3 o[TEX0].y, R1, R2; # get y \n\ DP3 o[TEX0].z, R6, R2; # get z \n\ \n\ \n\ # Normal is in R6 for additionnal lighting \n\ # Position in R5 for additionnal lighting \n\ MOV R5, v[0]; \n\ "; /// Omni light with normalization and no specular static const char* PPLightingVPNormalizeNoSpecCodeBegin = "!!VP1.0 \n\ #normalize the normal \n\ DP3 R1, v[2], v[2]; \n\ RSQ R1, R1.x; \n\ MUL R6, v[2], R1; \n\ \n\ #normalize the second vector \n\ DP3 R1, R6, v[9]; \n\ MAD R1, R6, -R1, v[9]; #subtract the normal component \n\ DP3 R2, R1, R1; \n\ RSQ R2, R2.x; \n\ MUL R5, R1, R2; #second basis vector in R5 \n\ #compute B = N ^ T \n\ MUL R1, R6.yzxw, R5.zxyw; \n\ MAD R1, R5.yzxw, -R6.zxyw, R1; #third basis vector in R1 \n\ \n\ #vector in tangent space = [ T B N ] * L \n\ ADD R2, c[4], -v[0]; # compute L \n\ DP3 R3, R2, R2; # get L normalized \n\ RSQ R3, R3.x; \n\ MUL R2, R3, R2; \n\ DP3 o[TEX0].x, R5, R2; # get x of light vector in tangent space \n\ DP3 o[TEX0].y, R1, R2; # get y \n\ DP3 o[TEX0].z, R6, R2; # get z \n\ \n\ # Normal is in R6 for additionnal lighting \n\ # Position in R5 for additionnal lighting \n\ MOV R5, v[0]; \n\ "; ///////////////////////// // directionnal lights // ///////////////////////// /// We store the direction of the light rather than its position /// The direction must be normalized and expressed in model space. // directionnal, no normalization static const char* PPLightingDirectionnalVPCodeBegin = "!!VP1.0 \n\ #compute B = N ^ T \n\ MOV R6, v[2]; \n\ MUL R1, R6.yzxw, v[9].zxyw; \n\ MAD R1, v[9].yzxw, -R6.zxyw, R1; \n\ \n\ #vector in tangent space = [ T B N ] * L \n\ DP3 o[TEX0].x, v[9], -c[4]; # get x of light vector in tangent space \n\ DP3 o[TEX0].y, R1, -c[4]; # get y \n\ DP3 o[TEX0].z, R6, -c[4]; # get z \n\ \n\ #specular part \n\ ADD R3, c[5], - v[0]; # compute V (return to eye) \n\ #compute inverse norm of V \n\ DP3 R4, R3, R3; \n\ RSQ R4, R4.x; \n\ #we normalize V and add it to L. It gives H unnormalized \n\ MAD R2, R4, R3, -c[4]; #H in R1 \n\ \n\ #normalize H \n\ DP3 R3, R2, R2; \n\ RSQ R3, R3.x; \n\ MUL R2, R3, R2; \n\ #compute H in tangent space \n\ DP3 o[TEX2].x, v[9], R2; \n\ DP3 o[TEX2].y, R1, R2; \n\ DP3 o[TEX2].z, R6, R2; \n\ \n\ # Normal is in R6 for additionnal lighting \n\ # Position in R5 for additionnal lighting \n\ MOV R5, v[0]; \n\ "; // directionnal + normalization static const char* PPLightingDirectionnalVPNormalizeCodeBegin = "!!VP1.0 \n\ #normalize the normal \n\ DP3 R1, v[2], v[2]; \n\ RSQ R1, R1.x; \n\ MUL R6, v[2], R1; \n\ \n\ #normalize the second vector \n\ DP3 R1, R6, v[9]; \n\ MAD R1, R6, -R1, v[9]; #subtract the normal component \n\ DP3 R2, R1, R1; \n\ RSQ R2, R2.x; \n\ MUL R5, R1, R2; #second basis vector in R5 \n\ #compute B = N ^ T \n\ MUL R1, R6.yzxw, R5.zxyw; \n\ MAD R1, R5.yzxw, -R6.zxyw, R1; #third basis vector in R1 \n\ \n\ #vector in tangent space = [ T B N ] * L \n\ DP3 o[TEX0].x, R5, -c[4]; # get x of light vector in tangent space \n\ DP3 o[TEX0].y, R1, -c[4]; # get y \n\ DP3 o[TEX0].z, R6, -c[4]; # get z \n\ \n\ #specular part \n\ ADD R3, c[5], - v[0]; # compute V (return to eye) \n\ #compute inverse norm of V \n\ DP3 R4, R3, R3; \n\ RSQ R4, R4.x; \n\ #we normalize V and add it to L \n\ MAD R2, R4, R3, -c[4]; #H in R1 \n\ \n\ #normalize H \n\ DP3 R3, R2, R2; \n\ RSQ R3, R3.x; \n\ MUL R2, R3, R2; \n\ #compute H in tangent space \n\ DP3 o[TEX2].x, R5, R2; \n\ DP3 o[TEX2].y, R1, R2; \n\ DP3 o[TEX2].z, R6, R2; \n\ # Normal is in R6 for additionnal lighting \n\ # Position in R5 for additionnal lighting \n\ MOV R5, v[0]; \n\ "; // directionnal, no normalization, no specular static const char* PPLightingDirectionnalNoSpecVPCodeBegin = "!!VP1.0 \n\ #compute B = N ^ T \n\ MOV R6, v[2]; \n\ MUL R1, R6.yzxw, v[9].zxyw; \n\ MAD R1, v[9].yzxw, -R6.zxyw, R1; \n\ \n\ #vector in tangent space = [ T B N ] * L \n\ DP3 o[TEX0].x, v[9], -c[4]; # get x of light vector in tangent space \n\ DP3 o[TEX0].y, R1, -c[4]; # get y \n\ DP3 o[TEX0].z, R6, -c[4]; # get z \n\ # Normal is in R6 for additionnal lighting \n\ # Position in R5 for additionnal lighting \n\ MOV R5, v[0]; \n\ "; // directionnal + normalization, no specular static const char* PPLightingDirectionnalNoSpecVPNormalizeCodeBegin = "!!VP1.0 \n\ #normalize the normal \n\ DP3 R1, v[2], v[2]; \n\ RSQ R1, R1.x; \n\ MUL R6, v[2], R1; \n\ \n\ #normalize the second vector \n\ DP3 R1, R6, v[9]; \n\ MAD R1, R6, -R1, v[9]; #subtract the normal component \n\ DP3 R2, R1, R1; \n\ RSQ R2, R2.x; \n\ MUL R5, R1, R2; #second basis vector in R5 \n\ #compute B = N ^ T \n\ MUL R1, R6.yzxw, R5.zxyw; \n\ MAD R1, R5.yzxw, -R6.zxyw, R1; #third basis vector in R1 \n\ \n\ #vector in tangent space = [ T B N ] * L \n\ DP3 o[TEX0].x, R5, -c[4]; # get x of light vector in tangent space \n\ DP3 o[TEX0].y, R1, -c[4]; # get y \n\ DP3 o[TEX0].z, R6, -c[4]; # get z \n\ \n\ # Normal is in R6 for additionnal lighting \n\ # Position in R5 for additionnal lighting \n\ MOV R5, v[0]; \n\ "; // End of per pixel lighting code : compute pos and setup texture static const char* PPLightingVPCodeEnd= " # compute in Projection space \n\ DP4 o[HPOS].x, c[0], v[0]; \n\ DP4 o[HPOS].y, c[1], v[0]; \n\ DP4 o[HPOS].z, c[2], v[0]; \n\ DP4 o[HPOS].w, c[3], v[0]; \n\ MOV o[TEX1], v[8]; \n\ END \n\ "; /***************************************************************/ /******************* THE FOLLOWING CODE IS COMMENTED OUT *******/ /*************************************************************** // Test code : map the tangent space vector as the diffuse color static const char* PPLightingVPCodeTest = "!!VP1.0 \n\ # compute in Projection space \n\ DP4 o[HPOS].x, c[0], v[0]; \n\ DP4 o[HPOS].y, c[1], v[0]; \n\ DP4 o[HPOS].z, c[2], v[0]; \n\ DP4 o[HPOS].w, c[3], v[0]; \n\ MOV o[COL0], v[9]; \n\ END \n\ "; ***************************************************************/ class CVertexProgramPerPixelLight : public CVertexProgramLighted { public: struct CIdx { /// Position or direction of strongest light uint StrongestLight; /// Viewer position uint ViewerPos; }; CVertexProgramPerPixelLight(uint vp); virtual ~CVertexProgramPerPixelLight() { }; virtual void buildInfo(); const CIdx &idx() const { return m_Idx; } private: CIdx m_Idx; }; CVertexProgramPerPixelLight::CVertexProgramPerPixelLight(uint vp) { // lighted settings m_FeaturesLighted.SupportSpecular = (vp & 2) != 0; m_FeaturesLighted.NumActivePointLights = MaxLight - 1; m_FeaturesLighted.Normalize = false; m_FeaturesLighted.CtStartNeLVP = VPLightConstantStart; // nelvp { // Gives each vp name // Bit 0 : 1 when it is a directionnal light // Bit 1 : 1 when specular is needed // Bit 2 : 1 when normalization of the tangent space is needed static const char *vpName[] = { // no spec PPLightingDirectionnalNoSpecVPCodeBegin, PPLightingNoSpecVPCodeBegin, // specular PPLightingDirectionnalVPCodeBegin, PPLightingVPCodeBegin, /////////////// normalized versions // no spec PPLightingDirectionnalNoSpecVPNormalizeCodeBegin, PPLightingVPNormalizeNoSpecCodeBegin, // spec PPLightingDirectionnalVPNormalizeCodeBegin, PPLightingVPNormalizeCodeBegin, }; uint numvp = sizeof(vpName) / sizeof(const char *); nlassert(CMeshVPPerPixelLight::NumVp == numvp); // make sure that it is in sync with header..todo : compile time assert :) // \todo yoyo TODO_OPTIM Manage different number of pointLights // NB: never call getLightVPFragmentNeLVP() with normalize, because already done by PerPixel fragment before. std::string vpCode = std::string(vpName[vp]) + std::string("# ***************") // temp for debug + CRenderTrav::getLightVPFragmentNeLVP( m_FeaturesLighted.NumActivePointLights, m_FeaturesLighted.CtStartNeLVP, m_FeaturesLighted.SupportSpecular, m_FeaturesLighted.Normalize) + std::string("# ***************") // temp for debug + std::string(PPLightingVPCodeEnd); #ifdef NL_DEBUG /** For test : parse those programs before they are used. * As a matter of fact some program will works with the NV_VERTEX_PROGRAM extension, * but won't with EXT_vertex_shader, because there are some limitations (can't read a temp * register that hasn't been written before..) */ CVPParser vpParser; CVPParser::TProgram result; std::string parseOutput; if (!vpParser.parse(vpCode.c_str(), result, parseOutput)) { nlwarning(parseOutput.c_str()); nlassert(0); } #endif CSource *source = new CSource(); source->DisplayName = NLMISC::toString("nelvp/MeshVPPerPixel/%i", vp); source->Profile = CVertexProgram::nelvp; source->setSource(vpCode); source->ParamIndices["modelViewProjection"] = 0; addSource(source); } // glsl { // TODO_VP_GLSL } } void CVertexProgramPerPixelLight::buildInfo() { CVertexProgramLighted::buildInfo(); if (profile() == nelvp) { m_Idx.StrongestLight = 4; if (m_FeaturesLighted.SupportSpecular) { m_Idx.ViewerPos = 5; } else { m_Idx.ViewerPos = ~0; } } else { // TODO_VP_GLSL } nlassert(m_Idx.StrongestLight != ~0); if (m_FeaturesLighted.SupportSpecular) { nlassert(m_Idx.ViewerPos != ~0); } } //================================================================================= void CMeshVPPerPixelLight::initInstance(CMeshBaseInstance *mbi) { // init the vertexProgram code. static bool vpCreated= false; if (!vpCreated) { vpCreated = true; for (uint vp = 0; vp < NumVp; ++vp) { _VertexProgram[vp] = new CVertexProgramPerPixelLight(vp); } } } //================================================================================= bool CMeshVPPerPixelLight::begin(IDriver *drv, CScene *scene, CMeshBaseInstance *mbi, const NLMISC::CMatrix &invertedModelMat, const NLMISC::CVector &viewerPos) { // test if supported by driver if (drv->isVertexProgramEmulated() || !drv->supportPerPixelLighting(SpecularLighting)) { return false; } // enable(true, drv); // must enable the vertex program before the vb is activated CVertexProgramPerPixelLight *program = _ActiveVertexProgram; if (!program) { // failed to compile vertex program return false; } // CRenderTrav *renderTrav= &scene->getRenderTrav(); /// Setup for gouraud lighting renderTrav->beginVPLightSetup(program, invertedModelMat); // sint strongestLightIndex = renderTrav->getStrongestLightIndex(); if (strongestLightIndex == -1) return false; // if no strongest light, disable this vertex program // setup the strongest light ///\todo disabling of specular lighting with this shader const CLight &strongestLight = renderTrav->getDriverLight(strongestLightIndex); switch (strongestLight.getMode()) { case CLight::DirectionalLight: { // put light direction in object space NLMISC::CVector lPos = invertedModelMat.mulVector(strongestLight.getDirection()); drv->setUniform3f(IDriver::VertexProgram, program->idx().StrongestLight, lPos); _IsPointLight = false; } break; case CLight::PointLight: { // put light in object space NLMISC::CVector lPos = invertedModelMat * strongestLight.getPosition(); drv->setUniform3f(IDriver::VertexProgram, program->idx().StrongestLight, lPos); _IsPointLight = true; } break; default: return false; break; } if (SpecularLighting) { // viewer pos in object space NLMISC::CVector vPos = invertedModelMat * viewerPos; drv->setUniform3f(IDriver::VertexProgram, program->idx().ViewerPos, vPos); } // c[0..3] take the ModelViewProjection Matrix. After setupModelMatrix(); drv->setUniformMatrix(IDriver::VertexProgram, program->getUniformIndex(CProgramIndex::ModelViewProjection), IDriver::ModelViewProjection, IDriver::Identity); return true; } //================================================================================= void CMeshVPPerPixelLight::end(IDriver *drv) { enable(false, drv); } //================================================================================= void CMeshVPPerPixelLight::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { (void)f.serialVersion(0); f.serial(SpecularLighting); } //================================================================================= void CMeshVPPerPixelLight::enable(bool enabled, IDriver *drv) { if (enabled != _Enabled) { nlassert(drv); if (enabled) { /* for (uint k = 0; k < NumVp; ++k) { nlinfo("test vp %d", k); drv->activeVertexProgram(_VertexProgram[k].get()); } */ uint idVP = (drv->isForceNormalize() ? 4 : 0) | (SpecularLighting ? 2 : 0) | (_IsPointLight ? 1 : 0); // if (drv->activeVertexProgram((CVertexProgramPerPixelLight *)_VertexProgram[idVP])) { _ActiveVertexProgram = _VertexProgram[idVP]; } else { _ActiveVertexProgram = NULL; } } else { drv->activeVertexProgram(NULL); _ActiveVertexProgram = NULL; } _Enabled = enabled; } } //================================================================================= bool CMeshVPPerPixelLight::setupForMaterial(const CMaterial &mat, IDriver *drv, CScene *scene ) { bool enabled = (mat.getShader() == CMaterial::PerPixelLighting || mat.getShader() == CMaterial::PerPixelLightingNoSpec); bool change = (enabled != _Enabled); enable(enabled, drv); // enable disable the vertex program (for material that don't have the right shader) if (enabled) { CRenderTrav *renderTrav= &scene->getRenderTrav(); renderTrav->changeVPLightSetupMaterial(mat, true /* exclude strongest*/); NLMISC::CRGBA pplDiffuse, pplSpecular; renderTrav->getStrongestLightColors(pplDiffuse, pplSpecular); drv->setPerPixelLightingLight(pplDiffuse, pplSpecular, mat.getShininess()); } return change; } //================================================================================= void CMeshVPPerPixelLight::setupForMaterial(const CMaterial &mat, IDriver *drv, CScene *scene, CVertexBuffer *vb) { if (setupForMaterial(mat, drv, scene)) // a switch from v.p enabled / disabled force to reactivate the vertex buffer. { drv->activeVertexBuffer(*vb); } } } // NL3D