⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 etlightmap.cpp

📁 etm2.2是基于Ogre引擎开发的地形库
💻 CPP
字号:
/*
EDITABLE TERRAIN MANAGER for Ogre
Copyright (C) 2007  Holger Frydrych <h.frydrych@gmx.de>

This program 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 2
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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

As a special exception, you may use this file as part of a free software
library without restriction.  Specifically, if other files instantiate
templates or use macros or inline functions from this file, or you compile
this file and link it with other files to produce an executable, this
file does not by itself cause the resulting executable to be covered by
the GNU General Public License.  This exception does not however
invalidate any other reasons why the executable file might be covered by
the GNU General Public License.
*/

#include "ETTerrainInfo.h"
#include <OgreImage.h>
#include <OgreColourValue.h>
#include <OgreVector3.h>
#include <OgreAxisAlignedBox.h>
#include <OgreException.h>

using namespace Ogre;
using namespace std;

namespace ET
{
  namespace Impl
  {
    /** Add terrain shadows to lightmap. */
    void addTerrainShadowsToLightmap(uchar* lightMap, const TerrainInfo& info,
      size_t width, size_t height, const Vector3& lightDir, const ColourValue& ambient)
    {
      // algorithm for ray traced shadow map as described here:
      // http://gpwiki.org/index.php/Faster_Ray_Traced_Terrain_Shadow_Maps
      size_t i, j;
      size_t *x, *z;
      int iDir, jDir;
      size_t iSize, jSize;
      float lDirXAbs = fabs(lightDir.x);
      float lDirZAbs = fabs(lightDir.z);

      // based on the direction of light, decide in which order to traverse
      // to speed up calculations
      if (lDirXAbs > lDirZAbs)
      {
        z = &i;
        x = &j;
        iSize = height;
        jSize = width;
        if (lightDir.x < 0)
        {
          j = jSize - 1;
          jDir = -1;
        }
        else
        {
          j = 0;
          jDir = 1;
        }
        if (lightDir.z < 0)
        {
          i = iSize - 1;
          iDir = -1;
        }
        else
        {
          i = 0;
          iDir = 1;
        }
      }
      else
      {
        x = &i;
        z = &j;
        jSize = height;
        iSize = width;
        if (lightDir.x < 0)
        {
          i = iSize - 1;
          iDir = -1;
        }
        else
        {
          i = 0;
          iDir = 1;
        }
        if (lightDir.z < 0)
        {
          j = jSize - 1;
          jDir = -1;
        }
        else
        {
          j = 0;
          jDir = 1;
        }
      }

      // calculate the step size to use
      AxisAlignedBox extents = info.getExtents();
      Vector3 pos = extents.getMinimum();
      Vector3 step = extents.getMaximum() - extents.getMinimum();
      step.x /= width;
      step.z /= height;

      float* flagMap = new float[width*height];
      memset(flagMap, 0, width*height*sizeof(float));

      while (1)
      {
        while (1)
        {
          // travel along terrain until we:
          // (1) intersect another point
          // (2) find another point with previous collision data
          // (3) reach the edge of the map
          float px = *x;
          float pz = *z;
          size_t index = (*z) * width + (*x);

          // travel along ray
          while (1)
          {
            px -= lightDir.x;
            pz -= lightDir.z;

            // check if we've reached the boundary
            if (px < 0 || px >= width || pz < 0 || pz >= height)
            {
              flagMap[index] = -1.0f;
              break;
            }

            // calculate interpolated values
            int x0 = (int)floor(px);
            int x1 = (int)ceil(px);
            int z0 = (int)floor(pz);
            int z1 = (int)ceil(pz);

            float du = px - x0;
            float dv = pz - z0;
            float invdu = 1.0 - du;
            float invdv = 1.0 - dv;
            float w0 = invdu * invdv;
            float w1 = invdu * dv;
            float w2 = du * invdv;
            float w3 = du * dv;

            // get interpolated height at position
            Vector3 curPos = pos + Vector3(px*step.x, 0, pz*step.z);
            float ipHeight = info.getHeightAt(curPos.x, curPos.z) - pos.y;

            // compute interpolated flagmap value
            float pixels[4];
            pixels[0] = flagMap[z0*width+x0];
            pixels[1] = flagMap[z1*width+x0];
            pixels[2] = flagMap[z0*width+x1];
            pixels[3] = flagMap[z1*width+x1];
            float ipFlag = w0*pixels[0] + w1*pixels[1] + w2*pixels[2] + w3*pixels[3];

            // get distance from original point to current point
            float realXDist = (px - *x) * step.x;
            float realZDist = (pz - *z) * step.z;
            float distance = sqrt(realXDist*realXDist + realZDist*realZDist);

            // calculate ray height at current point
            float height = info.getHeightAt(pos.x + (*x)*step.x, pos.z + (*z)*step.z) - pos.y - lightDir.y*distance;

            // check intersection with either terrain or flagMap
            // if ipHeight < ipFlag check against flagMap value
            float val = (ipHeight < ipFlag ? ipFlag : ipHeight);
            if (height < val)
            {
              // point in shadow
              flagMap[index] = val - height;
              lightMap[index*3+0] = (uchar) (255*ambient.r);
              lightMap[index*3+1] = (uchar) (255*ambient.g);
              lightMap[index*3+2] = (uchar) (255*ambient.b);
              break;
            }

            // check if pixel we moved to is unshadowed
            // since the flagMap value is interpolated, we use an epsilon value to check
            // if it's close enough to -1 to indicate non-shadow
            const float epsilon = 0.5f;
            if (ipFlag < -1.0f+epsilon && ipFlag > -1.0f-epsilon)
            {
              flagMap[index] = -1.0f;
              break;
            }
          }

          // update inner loop
          j += jDir;
          if (j >= jSize) // due to size_t, if j < 0, will wrap around and be > jSize ;)
            break;
        }

        // reset inner loop starting point
        if (jDir < 0)
          j = jSize-1;
        else
          j = 0;

        // update outer loop variable
        i += iDir;
        if (i >= iSize)
          break;

      }

      delete[] flagMap;
    }


    void boxFilterLightmap(uchar* lightMap, size_t width, size_t height)
    {
      // this box filter assigns to a pixel the average of itself and all
      // surrounding pixels in a 5x5 grid
      for (size_t i = 0; i < width; ++i)
      {
        for (size_t j = 0; j < height; ++j)
        {
          int col[3] = {0, 0, 0};
          // sum up all colours from 5x5 grid around the current pixel
          int cnt = 0;
          for (int x = -1; x <= 1; ++x)
          {
            if ((int)i+x < 0 || i+x >= width)
              continue;
            for (int y = -1; y <= 1; ++y)
            {
              if ((int)j+y < 0 || j+y >= height)
                continue;
              size_t index = (i+x + (j+y)*width)*3;
              col[0] += lightMap[index+0];
              col[1] += lightMap[index+1];
              col[2] += lightMap[index+2];
              ++cnt;
            }
          }
          // building average
          col[0] /= cnt;
          col[1] /= cnt;
          col[2] /= cnt;
          // write back
          size_t index = (i + j*width)*3;
          lightMap[index+0] = (uchar)col[0];
          lightMap[index+1] = (uchar)col[1];
          lightMap[index+2] = (uchar)col[2];
        }
      }
    }
  }



  void createTerrainLightmap(const TerrainInfo& info, Image& image,
    size_t width, size_t height, Vector3 lightDir, const ColourValue& lightCol,
    const ColourValue& ambient, bool shadowed)
  {
    lightDir.normalise();

    // calculate lightmap by multiplying light dir with terrain normals

    // calculate the step size to use
    AxisAlignedBox extents = info.getExtents();
    Vector3 startPos = extents.getMinimum();
    Vector3 step = extents.getMaximum() - extents.getMinimum();
    step.x /= width;
    step.z /= height;
    Vector3 pos = startPos;

    uchar* lightMap = new uchar[width*height * 3];
    memset(lightMap, 255, width*height*3);

    for (size_t z = 0; z < height; ++z)
    {
      for (size_t x = 0; x < width; ++x)
      {
        size_t index = (z * width + x)*3;
        // calculate diffuse light from light source
        Vector3 norm = info.getNormalAt(pos.x, pos.z);
        float l = std::max(0.0f, -lightDir.dotProduct(norm));

        ColourValue v = ambient;
        v.r = std::min(1.0f, v.r+l*lightCol.r);
        v.g = std::min(1.0f, v.g+l*lightCol.g);
        v.b = std::min(1.0f, v.b+l*lightCol.b);
        lightMap[index+0] = (uchar) (255*v.r);
        lightMap[index+1] = (uchar) (255*v.g);
        lightMap[index+2] = (uchar) (255*v.b);

        pos.x += step.x;
      }
      pos.x = startPos.x;
      pos.z += step.z;
    }

    if (shadowed && (lightDir.x != 0 || lightDir.z != 0))
    {
      // add terrain shadows
      Impl::addTerrainShadowsToLightmap(lightMap, info, width, height, lightDir, ambient);
    }

    // use a box filter to smoothen the lightmap
    Impl::boxFilterLightmap(lightMap, width, height);

    // save lightmap to image
    image.loadDynamicImage(lightMap, width, height, 1, PF_BYTE_RGB, true);
    // ownership of lightMap was transfered to image, don't need to delete
  }
}

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -