341 lines
9.7 KiB
C++
341 lines
9.7 KiB
C++
/*
|
|
Copyright (c) 2013 yvt
|
|
|
|
This file is part of OpenSpades.
|
|
|
|
OpenSpades 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.
|
|
|
|
OpenSpades 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 OpenSpades. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include "GLBasicShadowMapRenderer.h"
|
|
#include "IGLDevice.h"
|
|
#include "GLRenderer.h"
|
|
#include "../Core/Debug.h"
|
|
#include "../Core/Exception.h"
|
|
#include "../Core/Settings.h"
|
|
#include "GLProfiler.h"
|
|
|
|
SPADES_SETTING(r_shadowMapSize, "2048");
|
|
|
|
namespace spades {
|
|
namespace draw {
|
|
|
|
GLBasicShadowMapRenderer::GLBasicShadowMapRenderer(GLRenderer *r):
|
|
IGLShadowMapRenderer(r){
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
device = r->GetGLDevice();
|
|
|
|
textureSize = r_shadowMapSize;
|
|
|
|
|
|
colorTexture = device->GenTexture();
|
|
device->BindTexture(IGLDevice::Texture2D,
|
|
colorTexture);
|
|
device->TexImage2D(IGLDevice::Texture2D,
|
|
0,
|
|
IGLDevice::RGB,
|
|
textureSize,
|
|
textureSize,
|
|
0,
|
|
IGLDevice::RGB,
|
|
IGLDevice::UnsignedByte, NULL);
|
|
device->TexParamater(IGLDevice::Texture2D,
|
|
IGLDevice::TextureMagFilter,
|
|
IGLDevice::Linear);
|
|
device->TexParamater(IGLDevice::Texture2D,
|
|
IGLDevice::TextureMinFilter,
|
|
IGLDevice::Linear);
|
|
device->TexParamater(IGLDevice::Texture2D,
|
|
IGLDevice::TextureWrapS,
|
|
IGLDevice::ClampToEdge);
|
|
device->TexParamater(IGLDevice::Texture2D,
|
|
IGLDevice::TextureWrapT,
|
|
IGLDevice::ClampToEdge);
|
|
|
|
for(int i = 0; i < NumSlices; i++){
|
|
texture[i] = device->GenTexture();
|
|
device->BindTexture(IGLDevice::Texture2D, texture[i]);
|
|
device->TexImage2D(IGLDevice::Texture2D,
|
|
0,
|
|
IGLDevice::DepthComponent24,
|
|
textureSize,
|
|
textureSize,
|
|
0,
|
|
IGLDevice::DepthComponent,
|
|
IGLDevice::UnsignedInt, NULL);
|
|
device->TexParamater(IGLDevice::Texture2D,
|
|
IGLDevice::TextureMagFilter,
|
|
IGLDevice::Linear);
|
|
device->TexParamater(IGLDevice::Texture2D,
|
|
IGLDevice::TextureMinFilter,
|
|
IGLDevice::Linear);
|
|
device->TexParamater(IGLDevice::Texture2D,
|
|
IGLDevice::TextureWrapS,
|
|
IGLDevice::ClampToEdge);
|
|
device->TexParamater(IGLDevice::Texture2D,
|
|
IGLDevice::TextureWrapT,
|
|
IGLDevice::ClampToEdge);
|
|
|
|
|
|
framebuffer[i] = device->GenFramebuffer();
|
|
device->BindFramebuffer(IGLDevice::Framebuffer,
|
|
framebuffer[i]);
|
|
device->FramebufferTexture2D(IGLDevice::Framebuffer,
|
|
IGLDevice::ColorAttachment0,
|
|
IGLDevice::Texture2D,
|
|
colorTexture, 0);
|
|
device->FramebufferTexture2D(IGLDevice::Framebuffer,
|
|
IGLDevice::DepthAttachment,
|
|
IGLDevice::Texture2D,
|
|
texture[i], 0);
|
|
|
|
device->BindFramebuffer(IGLDevice::Framebuffer,
|
|
0);
|
|
}
|
|
}
|
|
|
|
GLBasicShadowMapRenderer::~GLBasicShadowMapRenderer(){
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
for(int i = 0; i < NumSlices; i++){
|
|
device->DeleteTexture(texture[i]);
|
|
device->DeleteFramebuffer(framebuffer[i]);
|
|
}
|
|
device->DeleteTexture(colorTexture);
|
|
}
|
|
|
|
#define Segment GLBSMRSegment
|
|
|
|
struct Segment {
|
|
float low, high;
|
|
bool empty;
|
|
Segment():empty(true){}
|
|
Segment(float l, float h):
|
|
low(std::min(l,h)), high(std::max(l,h)),empty(false){}
|
|
void operator +=(const Segment& seg) {
|
|
if(seg.empty)
|
|
return;
|
|
if(empty){
|
|
low = seg.low;
|
|
high = seg.high;
|
|
empty = false;
|
|
}else{
|
|
low = std::min(low, seg.low);
|
|
high = std::max(high, seg.high);
|
|
}
|
|
}
|
|
void operator +=(float v){
|
|
if(empty){
|
|
low = high = v;
|
|
}else{
|
|
if(v < low)low = v;
|
|
if(v > high) high = v;
|
|
}
|
|
}
|
|
};
|
|
|
|
static Vector3 FrustrumCoord(const client::SceneDefinition& def,
|
|
float x, float y, float z){
|
|
x *= z; y *= z;
|
|
return def.viewOrigin +
|
|
def.viewAxis[0] * x +
|
|
def.viewAxis[1] * y +
|
|
def.viewAxis[2] * z;
|
|
}
|
|
|
|
static Segment ZRange(Vector3 base, Vector3 dir,
|
|
Plane3 plane1, Plane3 plane2){
|
|
return Segment(plane1.GetDistanceTo(base)/Vector3::Dot(dir,plane1.n),
|
|
plane2.GetDistanceTo(base)/Vector3::Dot(dir,plane2.n));
|
|
}
|
|
|
|
void GLBasicShadowMapRenderer::BuildMatrix(float near, float far){
|
|
// TODO: variable light direction?
|
|
Vector3 lightDir = MakeVector3(0, -1, -1).Normalize();
|
|
// set better up dir?
|
|
Vector3 up = MakeVector3(0, 0, 1);
|
|
Vector3 side = Vector3::Cross(up, lightDir).Normalize();
|
|
up = Vector3::Cross(lightDir, side).Normalize();
|
|
|
|
// build frustrum
|
|
client::SceneDefinition def = GetRenderer()->GetSceneDef();
|
|
Vector3 frustrum[8];
|
|
float tanX = tanf(def.fovX * .5f);
|
|
float tanY = tanf(def.fovY * .5f);
|
|
|
|
frustrum[0] = FrustrumCoord(def, tanX, tanY, near);
|
|
frustrum[1] = FrustrumCoord(def, tanX, -tanY, near);
|
|
frustrum[2] = FrustrumCoord(def, -tanX, tanY, near);
|
|
frustrum[3] = FrustrumCoord(def, -tanX, -tanY, near);
|
|
frustrum[4] = FrustrumCoord(def, tanX, tanY, far);
|
|
frustrum[5] = FrustrumCoord(def, tanX, -tanY, far);
|
|
frustrum[6] = FrustrumCoord(def, -tanX, tanY, far);
|
|
frustrum[7] = FrustrumCoord(def, -tanX, -tanY, far);
|
|
|
|
// compute frustrum's x,y boundary
|
|
float minX, maxX, minY, maxY;
|
|
minX = maxX = Vector3::Dot(frustrum[0], side);
|
|
minY = maxY = Vector3::Dot(frustrum[0], up);
|
|
for(int i = 1; i < 8; i++){
|
|
float x = Vector3::Dot(frustrum[i], side);
|
|
float y = Vector3::Dot(frustrum[i], up);
|
|
if(x < minX) minX = x;
|
|
if(x > maxX) maxX = x;
|
|
if(y < minY) minY = y;
|
|
if(y > maxY) maxY = y;
|
|
}
|
|
|
|
// compute frustrum's z boundary
|
|
Segment seg;
|
|
Plane3 plane1(0,0,1,-4.f);
|
|
Plane3 plane2(0,0,1,64.f);
|
|
seg += ZRange(side * minX + up * minY,
|
|
lightDir, plane1, plane2);
|
|
seg += ZRange(side * minX + up * maxY,
|
|
lightDir, plane1, plane2);
|
|
seg += ZRange(side * maxX + up * minY,
|
|
lightDir, plane1, plane2);
|
|
seg += ZRange(side * maxX + up * maxY,
|
|
lightDir, plane1, plane2);
|
|
|
|
|
|
for(int i = 1; i < 8; i++){
|
|
seg += Vector3::Dot(frustrum[i], lightDir);
|
|
}
|
|
|
|
|
|
// build frustrum obb
|
|
Vector3 origin = side * minX + up * minY + lightDir * seg.low;
|
|
Vector3 axis1 = side * (maxX - minX);
|
|
Vector3 axis2 = up * (maxY - minY);
|
|
Vector3 axis3 = lightDir * (seg.high - seg.low);
|
|
|
|
obb = OBB3(Matrix4::FromAxis(axis1, axis2, axis3,
|
|
origin));
|
|
vpWidth = 2.f / axis1.GetLength();
|
|
vpHeight = 2.f / axis2.GetLength();
|
|
|
|
// convert to projectionview matrix
|
|
matrix = obb.m.InversedFast();
|
|
|
|
matrix = Matrix4::Scale(2.f) * matrix;
|
|
matrix = Matrix4::Translate(-1, -1, -1) * matrix;
|
|
|
|
// scale a little big for padding
|
|
matrix = Matrix4::Scale(.98f) * matrix;
|
|
//
|
|
matrix = Matrix4::Scale(1,1,-1) * matrix;
|
|
|
|
// make sure frustrums in range
|
|
#ifndef NDEBUG
|
|
for(int i = 0; i < 8; i++){
|
|
Vector4 v = matrix * frustrum[i];
|
|
SPAssert(v.x >= -1.f);
|
|
SPAssert(v.y >= -1.f);
|
|
//SPAssert(v.z >= -1.f);
|
|
SPAssert(v.x < 1.f);
|
|
SPAssert(v.y < 1.f);
|
|
//SPAssert(v.z < 1.f);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void GLBasicShadowMapRenderer::Render() {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
IGLDevice::Integer lastFb = device->GetInteger(IGLDevice::FramebufferBinding);
|
|
|
|
//client::SceneDefinition def = GetRenderer()->GetSceneDef();
|
|
|
|
float nearDist = 0.f;
|
|
|
|
for(int i = 0; i < NumSlices; i++){
|
|
|
|
GLProfiler profiler(device, "Slice %d / %d", i + 1,
|
|
(int)NumSlices);
|
|
|
|
float farDist;
|
|
// TODO: variable far distance according to the scene definition
|
|
// (note that this needs uniform shader variable)
|
|
switch(i){
|
|
case 0:
|
|
farDist = 12.f;
|
|
break;
|
|
case 1:
|
|
farDist = 40.f;
|
|
break;
|
|
case 2:
|
|
farDist = 150.f;
|
|
break;
|
|
}
|
|
|
|
BuildMatrix(nearDist, farDist);
|
|
matrices[i] = matrix;
|
|
/*
|
|
printf("m[%d]=\n[%f,%f,%f,%f]\n[%f,%f,%f,%f]\n[%f,%f,%f,%f]\n[%f,%f,%f,%f]\n",
|
|
i, matrix.m[0], matrix.m[4], matrix.m[8], matrix.m[12],
|
|
matrix.m[1], matrix.m[5], matrix.m[9], matrix.m[13],
|
|
matrix.m[2], matrix.m[6], matrix.m[10], matrix.m[14],
|
|
matrix.m[3], matrix.m[7], matrix.m[11], matrix.m[15]);*/
|
|
/*
|
|
matrix = Matrix4::Identity();
|
|
matrix = Matrix4::Scale(1.f / 16.f);
|
|
matrix = matrix * Matrix4::Rotate(MakeVector3(1, 0, 0), M_PI / 4.f);
|
|
matrix = matrix * Matrix4::Translate(-def.viewOrigin);
|
|
matrix = Matrix4::Scale(1,1,16.f / 70.f) * matrix;*/
|
|
|
|
device->BindFramebuffer(IGLDevice::Framebuffer,
|
|
framebuffer[i]);
|
|
device->Viewport(0, 0, textureSize, textureSize);
|
|
device->ClearDepth(1.f);
|
|
device->Clear(IGLDevice::DepthBufferBit);
|
|
|
|
RenderShadowMapPass();
|
|
|
|
nearDist = farDist;
|
|
}
|
|
|
|
|
|
|
|
device->BindFramebuffer(IGLDevice::Framebuffer,
|
|
lastFb);
|
|
|
|
device->Viewport(0, 0, device->ScreenWidth(), device->ScreenHeight());
|
|
|
|
}
|
|
|
|
bool GLBasicShadowMapRenderer::Cull(const spades::AABB3 &){
|
|
|
|
// TODO: who uses this?
|
|
SPNotImplemented();
|
|
return true;
|
|
}
|
|
|
|
bool GLBasicShadowMapRenderer::SphereCull(const spades::Vector3 ¢er,
|
|
float rad){
|
|
Vector4 vw = matrix * center;
|
|
float xx = fabsf(vw.x);
|
|
float yy = fabsf(vw.y);
|
|
float rx = rad * vpWidth;
|
|
float ry = rad * vpHeight;
|
|
|
|
return xx < (1.f + rx) && yy < (1.f + ry);
|
|
|
|
//return true;
|
|
//return obb.GetDistanceTo(center) < rad;
|
|
}
|
|
}
|
|
}
|